@dindo-group/cloud-map 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/images/layers-2x.png +0 -0
- package/dist/images/layers.png +0 -0
- package/dist/images/marker-icon-2x.png +0 -0
- package/dist/images/marker-icon.png +0 -0
- package/dist/images/marker-shadow.png +0 -0
- package/dist/index.css +1 -0
- package/dist/index.esm.css +1 -0
- package/dist/index.esm.js +540 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +545 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/src/TiandituMap.vue +560 -0
- package/src/index.js +26 -0
- package/src/leaflet/images/layers-2x.png +0 -0
- package/src/leaflet/images/layers.png +0 -0
- package/src/leaflet/images/marker-icon-2x.png +0 -0
- package/src/leaflet/images/marker-icon.png +0 -0
- package/src/leaflet/images/marker-shadow.png +0 -0
- package/src/leaflet/leaflet-src.esm.js +14419 -0
- package/src/leaflet/leaflet-src.esm.js.map +1 -0
- package/src/leaflet/leaflet-src.js +14512 -0
- package/src/leaflet/leaflet-src.js.map +1 -0
- package/src/leaflet/leaflet.css +661 -0
- package/src/leaflet/leaflet.js +6 -0
- package/src/leaflet/leaflet.js.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dindo-group/cloud-map",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "基于天地图的地图选择组件,支持地点搜索、位置标注、逆地理编码等功能",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "rollup -c && npm run copy:images",
|
|
14
|
+
"copy:images": "mkdir -p dist/images && cp -r src/leaflet/images/* dist/images/",
|
|
15
|
+
"dev": "rollup -c -w",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"publish:npm": "npm publish --registry=https://registry.npmjs.org"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"map",
|
|
21
|
+
"tianditu",
|
|
22
|
+
"leaflet",
|
|
23
|
+
"vue",
|
|
24
|
+
"location",
|
|
25
|
+
"geocoding"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"vue": "^2.6.0 || ^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
37
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
38
|
+
"rollup": "^3.29.4",
|
|
39
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
40
|
+
"rollup-plugin-vue2": "^0.8.1",
|
|
41
|
+
"sass": "^1.69.0",
|
|
42
|
+
"vue": "^2.6.14",
|
|
43
|
+
"vue-template-compiler": "^2.7.14"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="visible" class="tianditu-map-container">
|
|
3
|
+
<!-- 搜索框 -->
|
|
4
|
+
<div v-if="showSearch && !readonly" class="search-box">
|
|
5
|
+
<el-autocomplete v-model="searchKeyword" :fetch-suggestions="handleSearch" :loading="searchLoading"
|
|
6
|
+
:trigger-on-focus="false" clearable placeholder="请输入地点名称进行搜索或输入详细地址" style="width: 100%" value-key="label"
|
|
7
|
+
@select="handleSelectSearchResult" @input="handleSearchInput">
|
|
8
|
+
<template slot-scope="{ item }">
|
|
9
|
+
<div class="search-option">
|
|
10
|
+
<div class="option-name">{{ item.name }}</div>
|
|
11
|
+
<div class="option-address">{{ item.address || '' }}</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
</el-autocomplete>
|
|
15
|
+
</div>
|
|
16
|
+
<div v-if="!showSearch" class="search-box">
|
|
17
|
+
<el-input disabled v-model="addressValue" placeholder="详细地址"></el-input>
|
|
18
|
+
</div>
|
|
19
|
+
<div :id="mapId" class="map-container" :style="{ height: height }"></div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
export default {
|
|
25
|
+
name: 'TiandituMap',
|
|
26
|
+
props: {
|
|
27
|
+
// 是否显示组件
|
|
28
|
+
visible: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: true,
|
|
31
|
+
},
|
|
32
|
+
// 初始经纬度 [lng, lat]
|
|
33
|
+
initialPosition: {
|
|
34
|
+
type: Array,
|
|
35
|
+
default: () => [116.37304, 39.92594], // 默认北京坐标
|
|
36
|
+
},
|
|
37
|
+
// 是否显示搜索框
|
|
38
|
+
showSearch: {
|
|
39
|
+
type: Boolean,
|
|
40
|
+
default: true,
|
|
41
|
+
},
|
|
42
|
+
// 是否只读模式(纯标注展示,去除手动标注和搜索)
|
|
43
|
+
readonly: {
|
|
44
|
+
type: Boolean,
|
|
45
|
+
default: false,
|
|
46
|
+
},
|
|
47
|
+
// 是否禁用(禁用时不能点击标注)
|
|
48
|
+
disabled: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
52
|
+
// 地图高度
|
|
53
|
+
height: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: '400px',
|
|
56
|
+
},
|
|
57
|
+
// 天地图密钥
|
|
58
|
+
tk: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: '37058149a9a118dab5ccbffe7b0ec7dd',
|
|
61
|
+
},
|
|
62
|
+
// 地址输入框的值(用于双向绑定)
|
|
63
|
+
addressValue: {
|
|
64
|
+
type: String,
|
|
65
|
+
default: '',
|
|
66
|
+
},
|
|
67
|
+
// 是否初始化时进行逆地理编码和标记
|
|
68
|
+
initGeocode: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
data() {
|
|
74
|
+
return {
|
|
75
|
+
mapId: `tianditu-map-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
76
|
+
selectedLocation: null,
|
|
77
|
+
selectedAddress: '',
|
|
78
|
+
searchKeyword: '',
|
|
79
|
+
searchOptions: [],
|
|
80
|
+
searchLoading: false,
|
|
81
|
+
map: null,
|
|
82
|
+
marker: null,
|
|
83
|
+
tiandituLayer: null,
|
|
84
|
+
tiandituLabelLayer: null,
|
|
85
|
+
tiandituGroup: null,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
watch: {
|
|
89
|
+
visible(newVal) {
|
|
90
|
+
if (newVal) {
|
|
91
|
+
this.$nextTick(() => {
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
this.initMap();
|
|
94
|
+
}, 300);
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
this.cleanupMap();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
initialPosition: {
|
|
101
|
+
handler(newVal) {
|
|
102
|
+
if (newVal && newVal.length === 2 && this.map) {
|
|
103
|
+
const [lng, lat] = newVal;
|
|
104
|
+
// 只有当经纬度有效时,才更新标记
|
|
105
|
+
if (lng && lat && !isNaN(lng) && !isNaN(lat) && lng !== 0 && lat !== 0) {
|
|
106
|
+
if (this.initGeocode) {
|
|
107
|
+
this.selectedLocation = { lng, lat };
|
|
108
|
+
this.marker = L.marker([lat, lng]).addTo(this.map);
|
|
109
|
+
this.getAddress(lng, lat);
|
|
110
|
+
} else {
|
|
111
|
+
this.selectedLocation = { lng, lat };
|
|
112
|
+
this.marker = L.marker([lat, lng]).addTo(this.map);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
deep: true,
|
|
118
|
+
},
|
|
119
|
+
addressValue: {
|
|
120
|
+
handler(newVal) {
|
|
121
|
+
// 只有当有有效地址值时才同步到搜索框
|
|
122
|
+
// 新增时 addressValue 为空字符串,不应该填充
|
|
123
|
+
if (newVal && newVal.trim() && newVal !== this.searchKeyword) {
|
|
124
|
+
this.searchKeyword = newVal;
|
|
125
|
+
} else if (!newVal && this.searchKeyword) {
|
|
126
|
+
// 如果 addressValue 被清空,也清空搜索框(仅在编辑模式下)
|
|
127
|
+
// 新增模式下,不自动清空用户输入
|
|
128
|
+
if (this.readonly || this.disabled) {
|
|
129
|
+
this.searchKeyword = '';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
immediate: true,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
mounted() {
|
|
137
|
+
if (this.visible) {
|
|
138
|
+
this.$nextTick(() => {
|
|
139
|
+
this.initMap();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
beforeDestroy() {
|
|
144
|
+
this.cleanupMap();
|
|
145
|
+
},
|
|
146
|
+
methods: {
|
|
147
|
+
// 完全清理地图资源
|
|
148
|
+
cleanupMap() {
|
|
149
|
+
if (this.map) {
|
|
150
|
+
// 先移除标记
|
|
151
|
+
if (this.marker) {
|
|
152
|
+
this.map.removeLayer(this.marker);
|
|
153
|
+
this.marker = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 移除所有事件监听器
|
|
157
|
+
this.map.off();
|
|
158
|
+
|
|
159
|
+
// 移除所有图层
|
|
160
|
+
this.map.eachLayer((layer) => {
|
|
161
|
+
this.map.removeLayer(layer);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 移除地图实例
|
|
165
|
+
this.map.remove();
|
|
166
|
+
this.map = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 清理图层引用
|
|
170
|
+
this.tiandituLayer = null;
|
|
171
|
+
this.tiandituLabelLayer = null;
|
|
172
|
+
this.tiandituGroup = null;
|
|
173
|
+
|
|
174
|
+
// 清理地图容器元素
|
|
175
|
+
const mapElement = document.getElementById(this.mapId);
|
|
176
|
+
if (mapElement) {
|
|
177
|
+
// 移除 Leaflet 的内部 ID
|
|
178
|
+
if (mapElement._leaflet_id) {
|
|
179
|
+
delete mapElement._leaflet_id;
|
|
180
|
+
}
|
|
181
|
+
// 移除 Leaflet 相关的类,只保留基础类名
|
|
182
|
+
mapElement.className = 'map-container';
|
|
183
|
+
// 清空内容
|
|
184
|
+
mapElement.innerHTML = '';
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// 初始化地图
|
|
189
|
+
initMap() {
|
|
190
|
+
// 先清理旧地图(如果有)
|
|
191
|
+
this.cleanupMap();
|
|
192
|
+
|
|
193
|
+
const mapElement = document.getElementById(this.mapId);
|
|
194
|
+
if (!mapElement) {
|
|
195
|
+
console.error('地图容器元素未找到');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 检查 Leaflet 是否已加载
|
|
200
|
+
if (typeof L === 'undefined') {
|
|
201
|
+
console.error('Leaflet 未加载,请确保已引入 Leaflet');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 初始化地图,使用传入的初始位置或默认位置(GCJ-02坐标系)
|
|
206
|
+
const [lng, lat] = this.initialPosition;
|
|
207
|
+
this.map = L.map(this.mapId).setView([lat, lng], 13);
|
|
208
|
+
|
|
209
|
+
// 天地图底图(中国官方地图服务)
|
|
210
|
+
this.tiandituLayer = L.tileLayer(
|
|
211
|
+
`https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${this.tk}`,
|
|
212
|
+
{
|
|
213
|
+
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
|
214
|
+
attribution: '© 天地图',
|
|
215
|
+
maxZoom: 18,
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// 天地图标注图层(包含建筑物名称、POI等)
|
|
220
|
+
this.tiandituLabelLayer = L.tileLayer(
|
|
221
|
+
`https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${this.tk}`,
|
|
222
|
+
{
|
|
223
|
+
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
|
224
|
+
attribution: '© 天地图',
|
|
225
|
+
maxZoom: 18,
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// 创建天地图图层组(底图+标注)
|
|
230
|
+
this.tiandituGroup = L.layerGroup([this.tiandituLayer, this.tiandituLabelLayer]);
|
|
231
|
+
|
|
232
|
+
// 默认加载天地图
|
|
233
|
+
this.tiandituGroup.addTo(this.map);
|
|
234
|
+
|
|
235
|
+
// 添加比例尺控件
|
|
236
|
+
L.control.scale().addTo(this.map);
|
|
237
|
+
|
|
238
|
+
// 如果有初始位置且有效,添加标记
|
|
239
|
+
// 通过 addressValue 判断是否是新增状态
|
|
240
|
+
// 只有当有 addressValue 且不为空(编辑模式)时,才添加标记和获取地址
|
|
241
|
+
if (this.initialPosition && this.initialPosition.length === 2) {
|
|
242
|
+
const [initialLng, initialLat] = this.initialPosition;
|
|
243
|
+
if (initialLng && initialLat && !isNaN(initialLng) && !isNaN(initialLat) && initialLng !== 0 && initialLat !== 0) {
|
|
244
|
+
this.selectedLocation = { lng: initialLng, lat: initialLat };
|
|
245
|
+
this.marker = L.marker([initialLat, initialLng]).addTo(this.map);
|
|
246
|
+
if (this.initGeocode) {
|
|
247
|
+
// 获取地址
|
|
248
|
+
this.getAddress(initialLng, initialLat);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 如果不是只读模式,添加地图点击事件
|
|
254
|
+
if (!this.readonly && !this.disabled) {
|
|
255
|
+
this.map.on('click', (e) => {
|
|
256
|
+
const { lng, lat } = e.latlng;
|
|
257
|
+
this.selectedLocation = { lng, lat };
|
|
258
|
+
|
|
259
|
+
// 更新标记位置
|
|
260
|
+
this.updateMarker(lng, lat);
|
|
261
|
+
|
|
262
|
+
// 获取地址
|
|
263
|
+
this.getAddress(lng, lat);
|
|
264
|
+
|
|
265
|
+
// 触发位置变化事件
|
|
266
|
+
this.$emit('location-change', {
|
|
267
|
+
lng,
|
|
268
|
+
lat,
|
|
269
|
+
address: this.selectedAddress,
|
|
270
|
+
});
|
|
271
|
+
// 地址会在 getAddress 中更新并触发 address-change 事件
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// 更新标记位置
|
|
277
|
+
updateMarker(lng, lat) {
|
|
278
|
+
if (this.marker) {
|
|
279
|
+
this.marker.setLatLng([lat, lng]);
|
|
280
|
+
} else {
|
|
281
|
+
this.marker = L.marker([lat, lng]).addTo(this.map);
|
|
282
|
+
}
|
|
283
|
+
// 地图移动到该位置
|
|
284
|
+
this.map.setView([lat, lng], 15);
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// 获取地址(逆地理编码)
|
|
288
|
+
async getAddress(lng, lat) {
|
|
289
|
+
try {
|
|
290
|
+
// 使用天地图逆地理编码服务
|
|
291
|
+
const postStr = `{'lon':${lng},'lat':${lat},'ver':1}`;
|
|
292
|
+
const url = `http://api.tianditu.gov.cn/geocoder?postStr=${encodeURIComponent(
|
|
293
|
+
postStr
|
|
294
|
+
)}&type=geocode&tk=${this.tk}`;
|
|
295
|
+
|
|
296
|
+
const response = await fetch(url);
|
|
297
|
+
const data = await response.json();
|
|
298
|
+
|
|
299
|
+
if (data.status === '0' && data.result) {
|
|
300
|
+
// 优先使用 formatted_address,如果没有则使用 addressComponent 中的地址
|
|
301
|
+
this.selectedAddress =
|
|
302
|
+
data.result.formatted_address ||
|
|
303
|
+
data.result.addressComponent?.address ||
|
|
304
|
+
data.result.addressComponent?.city ||
|
|
305
|
+
'';
|
|
306
|
+
// 同步到搜索框
|
|
307
|
+
this.searchKeyword = this.selectedAddress;
|
|
308
|
+
// 触发地址变化事件
|
|
309
|
+
this.$emit('address-change', this.selectedAddress);
|
|
310
|
+
} else {
|
|
311
|
+
this.selectedAddress = '';
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('获取地址失败:', error);
|
|
315
|
+
this.selectedAddress = '';
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// 搜索地点
|
|
320
|
+
async handleSearch(queryString, cb) {
|
|
321
|
+
// 更新搜索关键词
|
|
322
|
+
this.searchKeyword = queryString;
|
|
323
|
+
|
|
324
|
+
if (!queryString || !queryString.trim()) {
|
|
325
|
+
this.searchOptions = [];
|
|
326
|
+
cb([]);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!this.map) {
|
|
331
|
+
console.error('地图未初始化');
|
|
332
|
+
cb([]);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.searchLoading = true;
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
// 获取当前地图边界
|
|
340
|
+
const bounds = this.map.getBounds();
|
|
341
|
+
const sw = bounds.getSouthWest();
|
|
342
|
+
const ne = bounds.getNorthEast();
|
|
343
|
+
const mapBound = `${sw.lng},${sw.lat},${ne.lng},${ne.lat}`;
|
|
344
|
+
|
|
345
|
+
// 获取当前地图缩放级别
|
|
346
|
+
const level = this.map.getZoom();
|
|
347
|
+
|
|
348
|
+
// 构造搜索参数
|
|
349
|
+
const postStrObj = {
|
|
350
|
+
keyWord: queryString.trim(),
|
|
351
|
+
level,
|
|
352
|
+
mapBound,
|
|
353
|
+
queryType: 7, // 1: 普通POI搜索
|
|
354
|
+
start: 0,
|
|
355
|
+
count: 100,
|
|
356
|
+
show: 2,
|
|
357
|
+
// specify: '156410000', // 指定行政区码
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const postStr = JSON.stringify(postStrObj);
|
|
361
|
+
const url = `http://api.tianditu.gov.cn/v2/search?postStr=${encodeURIComponent(
|
|
362
|
+
postStr
|
|
363
|
+
)}&type=query&tk=${this.tk}`;
|
|
364
|
+
|
|
365
|
+
const response = await fetch(url);
|
|
366
|
+
const data = await response.json();
|
|
367
|
+
|
|
368
|
+
// 处理搜索结果
|
|
369
|
+
if (data && data.status && data.status.infocode === 1000) {
|
|
370
|
+
const result = data;
|
|
371
|
+
|
|
372
|
+
// 普通POI结果 (resultType = 1)
|
|
373
|
+
if (result.resultType === 1 && result.pois && Array.isArray(result.pois)) {
|
|
374
|
+
const options = result.pois
|
|
375
|
+
.filter((poi) => poi.lonlat && poi.name)
|
|
376
|
+
.map((poi, index) => {
|
|
377
|
+
const lonlatParts = poi.lonlat.split(',');
|
|
378
|
+
const lng = lonlatParts[0] ? Number.parseFloat(lonlatParts[0]) : null;
|
|
379
|
+
const lat = lonlatParts[1] ? Number.parseFloat(lonlatParts[1]) : null;
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
label: `${poi.name} - ${poi.address || ''}`,
|
|
383
|
+
value: `${poi.name}_${index}_${poi.lonlat}`,
|
|
384
|
+
name: poi.name || '',
|
|
385
|
+
address: poi.address || '',
|
|
386
|
+
lonlat: poi.lonlat || '',
|
|
387
|
+
lng,
|
|
388
|
+
lat,
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
this.searchOptions = options;
|
|
393
|
+
cb(options);
|
|
394
|
+
} else {
|
|
395
|
+
console.warn('搜索结果格式异常:', result);
|
|
396
|
+
this.searchOptions = [];
|
|
397
|
+
cb([]);
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
const errorMsg = data.status?.cndesc || data.msg || '搜索失败,请稍后重试';
|
|
401
|
+
console.warn('搜索失败:', errorMsg);
|
|
402
|
+
this.searchOptions = [];
|
|
403
|
+
cb([]);
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error('搜索地点失败:', error);
|
|
407
|
+
this.searchOptions = [];
|
|
408
|
+
cb([]);
|
|
409
|
+
} finally {
|
|
410
|
+
this.searchLoading = false;
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// 选择搜索结果
|
|
415
|
+
handleSelectSearchResult(item) {
|
|
416
|
+
if (!item || !this.map) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!item.lng || !item.lat) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 设置选中位置
|
|
425
|
+
this.selectedLocation = { lng: item.lng, lat: item.lat };
|
|
426
|
+
|
|
427
|
+
// 更新地址(优先使用 address,如果没有则使用 name)
|
|
428
|
+
// 先设置地址,避免后续 getAddress 覆盖
|
|
429
|
+
this.selectedAddress = item.address + item.name || '';
|
|
430
|
+
// 同步到搜索框
|
|
431
|
+
this.searchKeyword = this.selectedAddress;
|
|
432
|
+
|
|
433
|
+
// 更新标记位置(不调用 getAddress,因为地址已经设置好了)
|
|
434
|
+
if (this.marker) {
|
|
435
|
+
this.marker.setLatLng([item.lat, item.lng]);
|
|
436
|
+
} else {
|
|
437
|
+
this.marker = L.marker([item.lat, item.lng]).addTo(this.map);
|
|
438
|
+
}
|
|
439
|
+
// 地图移动到该位置
|
|
440
|
+
this.map.setView([item.lat, item.lng], 15);
|
|
441
|
+
|
|
442
|
+
// 触发位置变化事件
|
|
443
|
+
this.$emit('location-change', {
|
|
444
|
+
lng: item.lng,
|
|
445
|
+
lat: item.lat,
|
|
446
|
+
address: this.selectedAddress,
|
|
447
|
+
});
|
|
448
|
+
// 触发地址变化事件
|
|
449
|
+
this.$emit('address-change', this.selectedAddress);
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
// 获取当前选中的位置
|
|
453
|
+
getLocation() {
|
|
454
|
+
return this.selectedLocation
|
|
455
|
+
? {
|
|
456
|
+
lng: this.selectedLocation.lng,
|
|
457
|
+
lat: this.selectedLocation.lat,
|
|
458
|
+
address: this.selectedAddress,
|
|
459
|
+
}
|
|
460
|
+
: null;
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// 设置位置
|
|
464
|
+
setLocation(lng, lat) {
|
|
465
|
+
if (lng && lat && this.map) {
|
|
466
|
+
this.updateMarker(lng, lat);
|
|
467
|
+
this.selectedLocation = { lng, lat };
|
|
468
|
+
if (this.initGeocode) {
|
|
469
|
+
this.getAddress(lng, lat);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
// 搜索框输入处理(用于手动输入详细地址)
|
|
475
|
+
handleSearchInput(value) {
|
|
476
|
+
// 实时更新地址
|
|
477
|
+
this.$emit('address-change', value || '');
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
// 根据区域名称获取坐标并移动地图
|
|
481
|
+
async moveToRegion(regionName) {
|
|
482
|
+
if (!regionName || !this.map) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
// 使用天地图地理编码服务(根据地址获取坐标)
|
|
488
|
+
// 参考文档:http://lbs.tianditu.gov.cn/server/geocoding.html
|
|
489
|
+
const postStr = `{'address':'${regionName}','ver':1}`;
|
|
490
|
+
const url = `http://api.tianditu.gov.cn/geocoder?postStr=${encodeURIComponent(
|
|
491
|
+
postStr
|
|
492
|
+
)}&type=geocode&tk=${this.tk}`;
|
|
493
|
+
|
|
494
|
+
const response = await fetch(url);
|
|
495
|
+
const data = await response.json();
|
|
496
|
+
|
|
497
|
+
// 天地图地理编码返回格式:{status: '0', result: {location: {lon: xxx, lat: xxx}}}
|
|
498
|
+
if (data.status === '0' && data.result && data.result.location) {
|
|
499
|
+
const { lon, lat } = data.result.location;
|
|
500
|
+
if (lon && lat) {
|
|
501
|
+
// 移动地图到该位置(缩放级别12,显示城市级别)
|
|
502
|
+
this.map.setView([lat, lon], 12);
|
|
503
|
+
// 不自动添加标记,只移动视图
|
|
504
|
+
// 如果需要,可以清除之前的标记
|
|
505
|
+
if (this.marker) {
|
|
506
|
+
this.map.removeLayer(this.marker);
|
|
507
|
+
this.marker = null;
|
|
508
|
+
}
|
|
509
|
+
// 重置选中位置
|
|
510
|
+
this.selectedLocation = null;
|
|
511
|
+
this.selectedAddress = '';
|
|
512
|
+
// 不清空搜索框,保留用户输入的区域名称
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
console.warn('未找到该区域的坐标信息:', regionName);
|
|
516
|
+
}
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error('根据区域移动地图失败:', error);
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
</script>
|
|
524
|
+
|
|
525
|
+
<style lang="scss" scoped>
|
|
526
|
+
.tianditu-map-container {
|
|
527
|
+
width: 100%;
|
|
528
|
+
position: relative;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.search-box {
|
|
532
|
+
margin-bottom: 15px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.search-option {
|
|
536
|
+
padding: 10px 0;
|
|
537
|
+
|
|
538
|
+
.option-name {
|
|
539
|
+
font-size: 14px;
|
|
540
|
+
font-weight: 500;
|
|
541
|
+
color: #333;
|
|
542
|
+
margin-bottom: 6px;
|
|
543
|
+
line-height: 1;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.option-address {
|
|
547
|
+
font-size: 12px;
|
|
548
|
+
color: #999;
|
|
549
|
+
line-height: 1;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
.map-container {
|
|
555
|
+
width: 100%;
|
|
556
|
+
border-radius: 4px;
|
|
557
|
+
overflow: hidden;
|
|
558
|
+
border: 1px solid #dcdfe6;
|
|
559
|
+
}
|
|
560
|
+
</style>
|
package/src/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// 引入本地 leaflet
|
|
2
|
+
import './leaflet/leaflet.css';
|
|
3
|
+
import './leaflet/leaflet.js';
|
|
4
|
+
|
|
5
|
+
import TiandituMap from './TiandituMap.vue';
|
|
6
|
+
|
|
7
|
+
// 支持 Vue 2 和 Vue 3
|
|
8
|
+
const install = function(Vue) {
|
|
9
|
+
if (install.installed) return;
|
|
10
|
+
install.installed = true;
|
|
11
|
+
Vue.component('TiandituMap', TiandituMap);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// 自动安装(如果 Vue 是通过 script 标签引入的)
|
|
15
|
+
if (typeof window !== 'undefined' && window.Vue) {
|
|
16
|
+
install(window.Vue);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 导出组件和安装方法
|
|
20
|
+
export default {
|
|
21
|
+
install,
|
|
22
|
+
TiandituMap,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// 支持命名导出
|
|
26
|
+
export { TiandituMap };
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|