Files
Pointly-Tel/web/src/App.vue
T
2026-07-30 13:23:06 +08:00

653 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app">
<!-- 地图全屏底层 -->
<PoiMap ref="mapRef" @marker-click="onMarkerClick" @map-click="onMapClick" />
<!-- 顶部搜索栏 -->
<div class="top-bar">
<div class="search-form">
<input
v-model="keywords"
class="input"
placeholder="关键词(选填,如:火锅店)"
@keyup.enter="doSearch"
/>
<select v-model="province" class="select province-select" @change="provinceChanged">
<option value="">所有省份</option>
<option v-for="p in provinces" :key="p" :value="p">{{ p }}</option>
</select>
<select v-model="city" class="select city-select" :disabled="!province">
<option value="">{{ province ? '所有城市' : '请先选省份' }}</option>
<option v-for="c in cities" :key="c" :value="c">{{ c }}</option>
</select>
<select v-model="radius" class="select radius-select">
<option v-for="r in radiusOptions" :key="r.value" :value="r.value">{{ r.label }}</option>
</select>
<button class="btn" :disabled="loading" @click="doSearch">
{{ loading ? '搜索中...' : '搜索' }}
</button>
</div>
<!-- 已选中心点 -->
<div v-if="centerLat || centerLng" class="center-badge">
<span>中心点{{ centerLng.toFixed(4) }}, {{ centerLat.toFixed(4) }}</span>
<button class="clear-center" @click="clearCenter">×</button>
</div>
</div>
<!-- 右侧结果面板 -->
<div v-if="searched" class="side-panel" :class="{ 'has-results': results.length > 0 }">
<!-- 加载中 -->
<div v-if="loading" class="panel-loading">
<div class="spinner"></div>
<span>正在查询...</span>
</div>
<!-- 错误 -->
<div v-else-if="error" class="panel-error">{{ error }}</div>
<!-- 空结果 -->
<div v-else-if="results.length === 0" class="panel-empty">
未找到相关商户<br /><small>请尝试其他关键词</small>
</div>
<!-- 结果列表 -->
<template v-else>
<div class="panel-header">
<span class="panel-title"> {{ total }} 条结果</span>
<span class="panel-page"> {{ page }}/{{ maxPage }} </span>
</div>
<div class="panel-list">
<div
v-for="item in results"
:key="item.poiId"
class="result-card"
:class="{ active: activePoi && activePoi.poiId === item.poiId }"
@click="focusItem(item)"
>
<div class="card-name">{{ item.name }}</div>
<div class="card-phone">{{ item.phone || '暂无电话' }}</div>
<div class="card-addr">{{ item.address }}</div>
</div>
</div>
<div class="panel-footer">
<button class="page-btn" :disabled="page <= 1" @click="page--; doSearch()">上一页</button>
<button class="page-btn" :disabled="page >= maxPage" @click="page++; doSearch()">下一页</button>
</div>
</template>
</div>
<!-- 操作提示 -->
<div class="hint-bar">
<span>点击地图设置搜索中心点</span>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { searchPoi } from './api/poi.js'
import PoiMap from './components/PoiMap.vue'
const mapRef = ref(null)
const keywords = ref('')
const city = ref('')
const radius = ref(3000)
const centerLat = ref(0)
const centerLng = ref(0)
const results = ref([])
const total = ref(0)
const loading = ref(false)
const searched = ref(false)
const error = ref('')
const page = ref(1)
const offset = ref(20)
const maxPage = ref(1)
const activePoi = ref(null)
const province = ref('')
const provinces = [
'北京', '天津', '上海', '重庆',
'广东', '浙江', '江苏', '福建', '山东', '四川', '湖北', '湖南',
'河南', '河北', '安徽', '江西', '陕西', '山西', '辽宁', '吉林',
'黑龙江', '广西', '内蒙古', '西藏', '新疆', '宁夏', '青海',
'甘肃', '贵州', '云南', '海南',
]
const regions = {
'北京': ['北京'],
'天津': ['天津'],
'上海': ['上海'],
'重庆': ['重庆'],
'广东': ['广州', '深圳', '东莞', '佛山', '珠海', '中山', '惠州', '汕头', '江门', '湛江', '肇庆', '茂名', '揭阳', '梅州', '清远', '韶关', '阳江', '河源', '汕尾', '潮州', '云浮'],
'浙江': ['杭州', '宁波', '温州', '绍兴', '嘉兴', '金华', '台州', '湖州', '衢州', '舟山', '丽水'],
'江苏': ['南京', '苏州', '无锡', '常州', '南通', '徐州', '扬州', '盐城', '淮安', '连云港', '镇江', '泰州', '宿迁'],
'福建': ['福州', '厦门', '泉州', '漳州', '莆田', '宁德', '龙岩', '三明', '南平'],
'山东': ['济南', '青岛', '烟台', '潍坊', '临沂', '淄博', '济宁', '威海', '泰安', '德州', '聊城', '东营', '枣庄', '日照', '滨州', '菏泽'],
'四川': ['成都', '绵阳', '德阳', '宜宾', '南充', '泸州', '自贡', '乐山', '眉山', '达州', '内江', '遂宁', '广安', '攀枝花', '资阳', '广元', '雅安', '巴中'],
'湖北': ['武汉', '襄阳', '宜昌', '荆州', '黄冈', '十堰', '孝感', '荆门', '鄂州', '黄石', '咸宁', '随州'],
'湖南': ['长沙', '株洲', '湘潭', '衡阳', '岳阳', '常德', '益阳', '邵阳', '郴州', '怀化', '永州', '娄底', '湘西'],
'河南': ['郑州', '洛阳', '南阳', '许昌', '周口', '新乡', '安阳', '开封', '商丘', '信阳', '驻马店', '平顶山', '焦作', '濮阳', '三门峡', '漯河', '鹤壁'],
'河北': ['石家庄', '唐山', '保定', '邯郸', '沧州', '廊坊', '秦皇岛', '张家口', '衡水', '邢台', '承德'],
'安徽': ['合肥', '芜湖', '安庆', '阜阳', '滁州', '马鞍山', '蚌埠', '宿州', '六安', '宣城', '铜陵', '池州', '淮南', '淮北', '黄山'],
'江西': ['南昌', '赣州', '九江', '宜春', '吉安', '上饶', '抚州', '萍乡', '景德镇', '新余', '鹰潭'],
'陕西': ['西安', '咸阳', '宝鸡', '渭南', '汉中', '延安', '榆林', '安康', '商洛', '铜川'],
'山西': ['太原', '大同', '长治', '临汾', '晋中', '运城', '晋城', '吕梁', '忻州', '阳泉', '朔州'],
'辽宁': ['沈阳', '大连', '鞍山', '抚顺', '锦州', '营口', '盘锦', '丹东', '本溪', '辽阳', '朝阳', '阜新', '葫芦岛', '铁岭'],
'吉林': ['长春', '吉林', '延边', '四平', '通化', '松原', '白城', '辽源', '白山'],
'黑龙江': ['哈尔滨', '大庆', '齐齐哈尔', '牡丹江', '佳木斯', '绥化', '鸡西', '鹤岗', '双鸭山', '伊春', '七台河', '黑河', '大兴安岭'],
'广西': ['南宁', '桂林', '柳州', '北海', '玉林', '百色', '梧州', '贵港', '钦州', '河池', '防城港', '崇左', '来宾', '贺州'],
'内蒙古': ['呼和浩特', '包头', '赤峰', '鄂尔多斯', '通辽', '呼伦贝尔', '乌兰察布', '巴彦淖尔', '兴安', '锡林郭勒', '阿拉善'],
'西藏': ['拉萨', '日喀则', '昌都', '林芝', '山南', '那曲', '阿里'],
'新疆': ['乌鲁木齐', '克拉玛依', '吐鲁番', '哈密', '昌吉', '伊犁', '喀什', '阿克苏', '和田', '塔城', '阿勒泰', '博尔塔拉', '巴音郭楞', '克孜勒苏'],
'宁夏': ['银川', '石嘴山', '吴忠', '固原', '中卫'],
'青海': ['西宁', '海东', '海西', '海南', '海北', '黄南', '玉树', '果洛'],
'甘肃': ['兰州', '天水', '白银', '金昌', '嘉峪关', '武威', '张掖', '平凉', '酒泉', '庆阳', '定西', '陇南', '临夏', '甘南'],
'贵州': ['贵阳', '遵义', '六盘水', '安顺', '毕节', '铜仁', '黔东南', '黔南', '黔西南'],
'云南': ['昆明', '大理', '丽江', '曲靖', '玉溪', '红河', '楚雄', '普洱', '昭通', '文山', '西双版纳', '保山', '德宏', '临沧', '怒江', '迪庆'],
'海南': ['海口', '三亚', '儋州', '三沙', '琼海', '文昌', '万宁', '陵水', '五指山'],
}
const cities = computed(() => regions[province.value] || [])
// 城市中心坐标(用于选中城市时地图跳转)
const cityCoords = {
'北京': [39.9042, 116.4074], '天津': [39.1252, 117.1908], '上海': [31.2304, 121.4737], '重庆': [29.4316, 106.9123],
'广州': [23.1291, 113.2644], '深圳': [22.5431, 114.0579], '东莞': [23.0208, 113.7518], '佛山': [23.0219, 113.1219],
'珠海': [22.2710, 113.5767], '中山': [22.5150, 113.3926], '惠州': [23.1110, 114.4162], '汕头': [23.3545, 116.6822],
'江门': [22.5786, 113.0945], '湛江': [21.2713, 110.3594], '肇庆': [23.0469, 112.4653], '茂名': [21.6630, 110.9252],
'揭阳': [23.5499, 116.3727], '梅州': [24.2884, 116.1176], '清远': [23.6810, 113.0560], '韶关': [24.8104, 113.5975],
'阳江': [21.8583, 111.9822], '河源': [23.7437, 114.7009], '汕尾': [22.7870, 115.3753], '潮州': [23.6564, 116.6300],
'云浮': [22.9150, 112.0445],
'杭州': [30.2741, 120.1551], '宁波': [29.8683, 121.5440], '温州': [28.0000, 120.6994], '绍兴': [30.0300, 120.5800],
'嘉兴': [30.7711, 120.7551], '金华': [29.0792, 119.6474], '台州': [28.6562, 121.4208], '湖州': [30.8931, 120.0875],
'衢州': [28.9359, 118.8742], '舟山': [29.9855, 122.2076], '丽水': [28.4680, 119.9228],
'南京': [32.0603, 118.7969], '苏州': [31.2990, 120.5853], '无锡': [31.4910, 120.3119], '常州': [31.8107, 119.9737],
'南通': [31.9798, 120.8938], '徐州': [34.2044, 117.2858], '扬州': [32.3937, 119.4126], '盐城': [33.3876, 120.1633],
'淮安': [33.5516, 119.1131], '连云港': [34.5969, 119.2215], '镇江': [32.1898, 119.4251], '泰州': [32.4555, 119.9227],
'宿迁': [33.9630, 118.2755],
'福州': [26.0745, 119.2965], '厦门': [24.4798, 118.0895], '泉州': [24.8741, 118.6756], '漳州': [24.5134, 117.6470],
'莆田': [25.4540, 119.0077], '宁德': [26.6657, 119.5479], '龙岩': [25.0751, 117.0170], '三明': [26.2634, 117.6392],
'南平': [26.6417, 118.1779],
'济南': [36.6512, 116.9972], '青岛': [36.0671, 120.3826], '烟台': [37.4645, 121.4479], '潍坊': [36.7068, 119.1618],
'临沂': [35.1046, 118.3564], '淄博': [36.8135, 118.0550], '济宁': [35.4148, 116.5872], '威海': [37.5133, 122.1210],
'泰安': [36.1994, 117.0875], '德州': [37.4363, 116.3594], '聊城': [36.4568, 115.9854], '东营': [37.4341, 118.6746],
'枣庄': [34.8105, 117.3211], '日照': [35.4204, 119.5272], '滨州': [37.3819, 117.9724], '菏泽': [35.2337, 115.4812],
'成都': [30.5728, 104.0668], '绵阳': [31.4675, 104.6791], '德阳': [31.1268, 104.3980], '宜宾': [28.7519, 104.6417],
'南充': [30.7991, 106.0827], '泸州': [28.8717, 105.4430], '自贡': [29.3390, 104.7784], '乐山': [29.5527, 103.7654],
'眉山': [30.0595, 103.8265], '达州': [31.2091, 107.4677], '内江': [29.5802, 105.0584], '遂宁': [30.5329, 105.5929],
'广安': [30.4560, 106.6332], '攀枝花': [26.5823, 101.7186], '资阳': [30.1286, 104.6276], '广元': [32.4355, 105.8450],
'雅安': [29.9765, 103.0094], '巴中': [31.8674, 106.7477],
'武汉': [30.5928, 114.3055], '襄阳': [32.0090, 112.1226], '宜昌': [30.6920, 111.2864], '荆州': [30.3345, 112.2407],
'黄冈': [30.4544, 114.8724], '十堰': [32.6319, 110.7939], '孝感': [30.9178, 113.9168], '荆门': [31.0354, 112.1992],
'鄂州': [30.3908, 114.8950], '黄石': [30.2010, 115.0769], '咸宁': [29.8327, 114.3222], '随州': [31.6902, 113.3826],
'长沙': [28.2282, 112.9388], '株洲': [27.8278, 113.1339], '湘潭': [27.8298, 112.9442], '衡阳': [26.8932, 112.5720],
'岳阳': [29.3572, 113.1288], '常德': [29.0316, 111.6985], '益阳': [28.5889, 112.3551], '邵阳': [27.2389, 111.4673],
'郴州': [25.7705, 113.0145], '怀化': [27.5697, 109.9985], '永州': [26.4207, 111.6132], '娄底': [27.7000, 112.0004],
'湘西': [28.3118, 109.7394],
'郑州': [34.7470, 113.6247], '洛阳': [34.6202, 112.4545], '南阳': [32.9908, 112.5286], '许昌': [34.0355, 113.8523],
'周口': [33.6260, 114.6473], '新乡': [35.3036, 113.9269], '安阳': [36.0969, 114.3931], '开封': [34.7973, 114.3073],
'商丘': [34.4140, 115.6554], '信阳': [32.1471, 114.0932], '驻马店': [32.9815, 114.0220], '平顶山': [33.7662, 113.1924],
'焦作': [35.2159, 113.2418], '濮阳': [35.7624, 115.0297], '三门峡': [34.7731, 111.1999], '漯河': [33.5814, 114.0165],
'鹤壁': [35.8995, 114.2975],
'石家庄': [38.0423, 114.5145], '唐山': [39.6303, 118.1824], '保定': [38.8739, 115.4646], '邯郸': [36.6256, 114.5391],
'沧州': [38.3044, 116.8387], '廊坊': [39.5379, 116.6838], '秦皇岛': [39.8891, 119.5997], '张家口': [40.7680, 114.8862],
'衡水': [37.7389, 115.6707], '邢台': [37.0736, 114.5047], '承德': [40.9495, 117.9593],
'合肥': [31.8206, 117.2272], '芜湖': [31.3525, 118.4329], '安庆': [30.5429, 117.0635], '阜阳': [32.8901, 115.8141],
'滁州': [32.3019, 118.3071], '马鞍山': [31.6714, 118.5071], '蚌埠': [32.9188, 117.3894], '宿州': [33.6476, 116.9639],
'六安': [31.7331, 116.5199], '宣城': [30.9404, 118.7587], '铜陵': [30.8789, 117.8107], '池州': [30.6648, 117.4916],
'淮南': [32.6261, 116.9997], '淮北': [33.9653, 116.7985], '黄山': [29.7153, 118.3370],
'南昌': [28.6829, 115.8582], '赣州': [25.8310, 114.9356], '九江': [29.6619, 115.9973], '宜春': [27.8143, 114.4161],
'吉安': [27.1130, 114.9934], '上饶': [28.4546, 117.9435], '抚州': [27.9479, 116.3581], '萍乡': [27.6229, 113.8548],
'景德镇': [29.2686, 117.1784], '新余': [27.8178, 114.9171], '鹰潭': [28.2588, 117.0692],
'西安': [34.3416, 108.9398], '咸阳': [34.3293, 108.7092], '宝鸡': [34.3621, 107.2373], '渭南': [34.5205, 109.5103],
'汉中': [33.0677, 107.0236], '延安': [36.5854, 109.4888], '榆林': [38.2855, 109.7345], '安康': [32.6849, 109.0291],
'商洛': [33.8683, 109.9202], '铜川': [34.8970, 108.9451],
'太原': [37.8706, 112.5499], '大同': [40.0769, 113.3001], '长治': [36.1954, 113.1164], '临汾': [36.0880, 111.5193],
'晋中': [37.6875, 112.7528], '运城': [35.0265, 111.0070], '晋城': [35.4905, 112.8513], '吕梁': [37.5193, 111.1446],
'忻州': [38.4170, 112.7340], '阳泉': [37.8568, 113.5803], '朔州': [39.3315, 112.4327],
'沈阳': [41.8057, 123.4315], '大连': [38.9140, 121.6148], '鞍山': [41.1078, 122.9945], '抚顺': [41.8796, 123.9572],
'锦州': [41.0952, 121.1270], '营口': [40.6241, 122.2351], '盘锦': [41.1198, 122.0708], '丹东': [40.0024, 124.3567],
'本溪': [41.4870, 123.7716], '辽阳': [41.2691, 123.2378], '朝阳': [41.5737, 120.4508], '阜新': [42.0210, 121.6703],
'葫芦岛': [40.7109, 120.8372], '铁岭': [42.2860, 123.8426],
'长春': [43.8960, 125.3265], '吉林': [43.8379, 126.5493], '延边': [42.8907, 129.5094], '四平': [43.1639, 124.3504],
'通化': [41.7278, 125.9395], '松原': [45.1410, 124.8255], '白城': [45.6193, 122.8389], '辽源': [42.8877, 125.1437],
'白山': [41.9406, 126.4244],
'哈尔滨': [45.8038, 126.5350], '大庆': [46.5967, 125.1041], '齐齐哈尔': [47.3543, 123.9180], '牡丹江': [44.5528, 129.6186],
'佳木斯': [46.7998, 130.3194], '绥化': [46.6368, 126.9692], '鸡西': [45.2953, 130.9661], '鹤岗': [47.3499, 130.2978],
'双鸭山': [46.6467, 131.1585], '伊春': [47.7277, 128.8404], '七台河': [45.7708, 131.0153], '黑河': [50.2454, 127.5284],
'大兴安岭': [50.5506, 126.2050],
'南宁': [22.8170, 108.3665], '桂林': [25.2344, 110.1798], '柳州': [24.3249, 109.4280], '北海': [21.4679, 109.1197],
'玉林': [22.6529, 110.1411], '百色': [23.9020, 106.6184], '梧州': [23.4769, 111.2786], '贵港': [23.1131, 109.5980],
'钦州': [21.9799, 108.6541], '河池': [24.6929, 108.0854], '防城港': [21.6869, 108.3783], '崇左': [22.3795, 107.3652],
'来宾': [23.7332, 109.2213], '贺州': [24.4035, 111.5667],
'呼和浩特': [40.8422, 111.7498], '包头': [40.6571, 109.8402], '赤峰': [42.2576, 118.8888], '鄂尔多斯': [39.6083, 109.7809],
'通辽': [43.6530, 122.2445], '呼伦贝尔': [49.2000, 119.7600], '乌兰察布': [40.9935, 113.1310], '巴彦淖尔': [40.7274, 107.3862],
'兴安': [46.0820, 122.0380], '锡林郭勒': [43.9335, 116.0480], '阿拉善': [38.8483, 105.6878],
'拉萨': [29.6500, 91.1000], '日喀则': [29.2670, 88.8810], '昌都': [31.1400, 97.1700], '林芝': [29.6500, 94.3600],
'山南': [29.2300, 91.7700], '那曲': [31.4700, 92.0600], '阿里': [30.4000, 81.1400],
'乌鲁木齐': [43.8256, 87.6168], '克拉玛依': [45.5750, 84.8820], '吐鲁番': [42.9400, 89.1900], '哈密': [42.8200, 93.5100],
'昌吉': [44.0130, 87.3050], '伊犁': [43.9200, 81.3200], '喀什': [39.4700, 75.9900], '阿克苏': [41.1700, 80.2600],
'和田': [37.1100, 79.9200], '塔城': [46.7500, 82.9800], '阿勒泰': [47.8700, 88.1400],
'博尔塔拉': [44.9000, 82.0700], '巴音郭楞': [41.7700, 86.1500], '克孜勒苏': [39.7100, 76.1700],
'银川': [38.4710, 106.2590], '石嘴山': [38.9860, 106.3850], '吴忠': [37.9990, 106.2010], '固原': [36.0170, 106.2860],
'中卫': [37.5170, 105.1960],
'西宁': [36.6230, 101.7800], '海东': [36.5020, 102.1040], '海西': [37.3730, 97.3710],
'海南': [36.2800, 100.6200], '海北': [36.9600, 100.9000], '黄南': [35.5200, 102.0200], '玉树': [33.0000, 97.0100],
'果洛': [34.4800, 100.2400],
'兰州': [36.0611, 103.8343], '天水': [34.5811, 105.7250], '白银': [36.5447, 104.1382], '金昌': [38.5210, 102.1880],
'嘉峪关': [39.7730, 98.2900], '武威': [37.9277, 102.6379], '张掖': [38.9349, 100.4599], '平凉': [35.5434, 106.6682],
'酒泉': [39.7368, 98.4940], '庆阳': [35.7100, 107.6440], '定西': [35.5800, 104.6250], '陇南': [33.3700, 104.9200],
'临夏': [35.6000, 103.2100], '甘南': [34.9900, 102.9100],
'贵阳': [26.6470, 106.6302], '遵义': [27.7254, 106.9270], '六盘水': [26.5927, 104.8300], '安顺': [26.2537, 105.9462],
'毕节': [27.3020, 105.2900], '铜仁': [27.7300, 109.1800],
'黔东南': [26.5800, 107.9700], '黔南': [26.2600, 107.5200], '黔西南': [25.0900, 104.9000],
'昆明': [25.0389, 102.7183], '大理': [25.6065, 100.2280], '丽江': [26.8720, 100.2300], '曲靖': [25.4900, 103.8000],
'玉溪': [24.3520, 102.5470], '红河': [23.3700, 103.3800], '楚雄': [25.0400, 101.5400], '普洱': [22.8300, 100.9700],
'昭通': [27.3400, 103.7200], '文山': [23.3800, 104.2400], '西双版纳': [22.0100, 100.8000],
'保山': [25.1200, 99.1700], '德宏': [24.4400, 98.5900], '临沧': [23.8900, 100.0900], '怒江': [25.8500, 98.8600],
'迪庆': [27.8200, 99.7100],
'海口': [20.0440, 110.3500], '三亚': [18.2540, 109.5020], '儋州': [19.5210, 109.5810], '三沙': [16.8300, 112.3400],
'琼海': [19.2590, 110.4660], '文昌': [19.5440, 110.7980], '万宁': [18.7970, 110.3890], '陵水': [18.5060, 110.0370],
'五指山': [18.7750, 109.5170],
}
const radiusOptions = [
{ value: 1000, label: '1km' },
{ value: 3000, label: '3km' },
{ value: 5000, label: '5km' },
{ value: 10000, label: '10km' },
{ value: 20000, label: '20km' },
{ value: 50000, label: '50km' },
]
// 选中城市时地图跳转
watch(city, (val, oldVal) => {
if (!val || val === oldVal || !mapRef.value) return
const coord = cityCoords[val]
if (!coord) return
centerLat.value = 0
centerLng.value = 0
mapRef.value.clearRadiusCircle()
})
// 范围变更时只重绘圆圈大小,不改变地图视角
watch(radius, (val) => {
if (centerLng.value && centerLat.value && mapRef.value) {
mapRef.value.updateRadiusCircle(val)
}
})
async function doSearch() {
error.value = ''
// 关键词和坐标必须至少有一个
const kw = keywords.value.trim()
const hasCoord = centerLng.value && centerLat.value
if (!kw && !hasCoord) {
error.value = '请输入关键词或点击地图选择搜索范围'
searched.value = true
return
}
loading.value = true
searched.value = true
activePoi.value = null
try {
const data = await searchPoi(
kw,
city.value,
page.value,
offset.value,
centerLng.value,
centerLat.value,
radius.value,
)
results.value = data.list || []
total.value = data.total || 0
maxPage.value = data.maxPage || 1
page.value = data.page || 1
offset.value = data.offset || 20
// 在地图上标注
if (mapRef.value && results.value.length > 0) {
mapRef.value.addMarkers(results.value)
}
} catch (e) {
error.value = e.response?.data?.message || e.message || '请求失败'
results.value = []
total.value = 0
maxPage.value = 1
} finally {
loading.value = false
}
}
function focusItem(item) {
activePoi.value = item
if (mapRef.value) {
mapRef.value.focusMarker(item)
}
}
function onMarkerClick(poi) {
activePoi.value = poi
}
function onMapClick({ lat, lng }) {
centerLat.value = lat
centerLng.value = lng
// 切换为周边搜索时清除城市条件
province.value = ''
city.value = ''
// 地图上画圆圈
if (mapRef.value) {
mapRef.value.drawRadiusCircle(lat, lng, radius.value)
}
}
function clearCenter() {
centerLat.value = 0
centerLng.value = 0
if (mapRef.value) {
mapRef.value.clearRadiusCircle()
}
}
function provinceChanged() {
city.value = ''
}
</script>
<style>
/* === Global reset === */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #app {
width: 100%;
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
color: #333;
}
/* === App layout === */
.app {
width: 100%;
height: 100%;
position: relative;
}
/* === Top search bar === */
.top-bar {
position: absolute;
top: 16px;
left: 16px;
right: 380px;
z-index: 1000;
pointer-events: none;
}
.search-form {
display: flex;
gap: 10px;
background: #fff;
padding: 12px 16px;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
pointer-events: auto;
max-width: 700px;
}
.input {
flex: 1;
padding: 9px 14px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.input:focus {
border-color: #1a73e8;
}
.select {
padding: 9px 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
outline: none;
background: #fff;
cursor: pointer;
transition: border-color 0.2s;
}
.select:focus {
border-color: #1a73e8;
}
.province-select {
max-width: 110px;
}
.city-select {
max-width: 110px;
}
.radius-select {
max-width: 90px;
}
.btn {
padding: 9px 24px;
background: #1a73e8;
color: #fff;
border: none;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s;
}
.btn:hover:not(:disabled) {
background: #1557b0;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.center-badge {
margin-top: 8px;
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.95);
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
color: #1a73e8;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
pointer-events: auto;
}
.clear-center {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #999;
line-height: 1;
}
.clear-center:hover {
color: #dc2626;
}
/* === Side panel === */
.side-panel {
position: absolute;
top: 80px;
right: 16px;
width: 350px;
max-height: calc(100% - 100px);
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
z-index: 1000;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px 10px;
border-bottom: 1px solid #f0f0f0;
}
.panel-title {
font-size: 14px;
font-weight: 600;
color: #333;
}
.panel-page {
font-size: 12px;
color: #999;
}
.panel-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.result-card {
padding: 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
margin-bottom: 4px;
}
.result-card:hover {
background: #f5f7fa;
}
.result-card.active {
background: #e8f0fe;
border-left: 3px solid #1a73e8;
}
.card-name {
font-size: 14px;
font-weight: 600;
margin-bottom: 3px;
}
.card-phone {
font-size: 15px;
color: #1a73e8;
font-weight: 600;
margin-bottom: 2px;
}
.card-addr {
font-size: 12px;
color: #888;
}
.panel-footer {
display: flex;
gap: 8px;
justify-content: center;
padding: 10px 16px 14px;
border-top: 1px solid #f0f0f0;
}
.page-btn {
flex: 1;
padding: 7px 0;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.page-btn:hover:not(:disabled) {
border-color: #1a73e8;
color: #1a73e8;
}
.page-btn:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.panel-loading,
.panel-error,
.panel-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 16px;
text-align: center;
color: #888;
font-size: 14px;
}
.panel-error {
color: #dc2626;
}
.panel-empty small {
font-size: 12px;
margin-top: 4px;
}
.spinner {
width: 22px;
height: 22px;
border: 3px solid #e5e7eb;
border-top-color: #1a73e8;
border-radius: 50%;
animation: spin 0.7s linear infinite;
margin-bottom: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* === Hint bar === */
.hint-bar {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(0, 0, 0, 0.55);
color: #fff;
padding: 6px 18px;
border-radius: 20px;
font-size: 12px;
pointer-events: none;
white-space: nowrap;
}
</style>