xt-element-ui 1.2.8 → 1.3.1
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/docs/components/base/xt-map.md +331 -0
- package/docs/components/base/xt-step-price.md +103 -217
- package/lib/index.common.js +2218 -87
- package/lib/index.css +1 -1
- package/lib/index.umd.js +2218 -87
- package/lib/index.umd.min.js +5 -5
- package/package.json +4 -3
- package/src/components/ex-button/index.js +8 -2
- package/src/components/ex-chart/index.js +6 -0
- package/src/components/ex-date-picker/index.js +8 -2
- package/src/components/ex-icon/index.js +6 -0
- package/src/components/ex-page/index.js +8 -2
- package/src/components/ex-select-tree/index.js +8 -2
- package/src/components/ex-table/index.js +6 -0
- package/src/components/ex-upload/index.js +6 -0
- package/src/components/xt-button/index.js +8 -2
- package/src/components/xt-button/index.vue +53 -10
- package/src/components/xt-button/style/index.scss +147 -43
- package/src/components/xt-card/index.js +8 -2
- package/src/components/xt-card-item/index.js +8 -2
- package/src/components/xt-config-provider/index.js +6 -0
- package/src/components/xt-flex-box/index.js +8 -2
- package/src/components/xt-flex-box/style/index.scss +0 -9
- package/src/components/xt-grid-box/index.js +8 -2
- package/src/components/xt-grid-item/index.js +7 -1
- package/src/components/xt-input/index.js +8 -2
- package/src/components/xt-input/index.vue +66 -37
- package/src/components/xt-input/style/index.scss +70 -13
- package/src/components/xt-map/adapters/amap.js +235 -0
- package/src/components/xt-map/adapters/baidu.js +254 -0
- package/src/components/xt-map/adapters/base.js +267 -0
- package/src/components/xt-map/adapters/index.js +29 -0
- package/src/components/xt-map/adapters/tianditu.js +242 -0
- package/src/components/xt-map/config/xt-map-config.js +197 -0
- package/src/components/xt-map/index.js +22 -0
- package/src/components/xt-map/index.vue +351 -0
- package/src/components/xt-map/loaders/script-loader.js +114 -0
- package/src/components/xt-map/provider.vue +200 -0
- package/src/components/xt-map/style/index.scss +77 -0
- package/src/components/xt-step-price/index.js +9 -0
- package/src/components/xt-step-price/index.vue +12 -10
- package/src/components/xt-step-price/style/index.scss +32 -24
- package/src/components/xt-step-price-item/index.js +6 -0
- package/src/components/xt-step-price-item/index.vue +13 -9
- package/src/components/xt-text/index.js +8 -2
- package/src/components/xt-time/index.js +6 -0
- package/src/index.js +6 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XtMap 全局配置管理
|
|
3
|
+
* 支持通过 Vue.use 配置或运行时动态设置地图提供商、密钥、URL、主题等
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 支持的地图提供商
|
|
7
|
+
export const MAP_PROVIDERS = ['amap', 'tianditu', 'baidu']
|
|
8
|
+
|
|
9
|
+
// 支持的地图类型(统一枚举,各适配器会转成对应引擎的类型)
|
|
10
|
+
export const MAP_TYPES = ['standard', 'satellite', 'hybrid', 'traffic']
|
|
11
|
+
|
|
12
|
+
// 支持的主题
|
|
13
|
+
export const MAP_THEMES = ['light', 'dark']
|
|
14
|
+
|
|
15
|
+
// 默认配置 - 以高德地图为基准
|
|
16
|
+
const defaultConfig = {
|
|
17
|
+
provider: 'amap',
|
|
18
|
+
apiKey: '',
|
|
19
|
+
apiUrl: null,
|
|
20
|
+
mapType: 'standard',
|
|
21
|
+
theme: 'light',
|
|
22
|
+
center: [116.397428, 39.90923],
|
|
23
|
+
zoom: 11,
|
|
24
|
+
plugins: [],
|
|
25
|
+
securityJsCode: null,
|
|
26
|
+
// 天地图专用:图层类型(vec:矢量, img:影像, ter:地形)
|
|
27
|
+
tiandituLayerType: 'vec',
|
|
28
|
+
// 百度地图专用:坐标系(bd09ll:百度经纬度, bd09mc:百度墨卡托)
|
|
29
|
+
baiduCoordType: 'bd09ll'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 当前配置
|
|
33
|
+
let currentConfig = { ...defaultConfig }
|
|
34
|
+
|
|
35
|
+
// 配置变更监听器
|
|
36
|
+
const configChangeListeners = []
|
|
37
|
+
|
|
38
|
+
const emitConfigChange = (key, value) => {
|
|
39
|
+
configChangeListeners.forEach(listener => {
|
|
40
|
+
try {
|
|
41
|
+
listener(key, value)
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn('[XtMap] 配置变更监听异常:', e)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 获取完整配置
|
|
49
|
+
export const getMapConfig = () => ({ ...currentConfig })
|
|
50
|
+
|
|
51
|
+
// 设置完整配置
|
|
52
|
+
export const setMapConfig = (config) => {
|
|
53
|
+
if (typeof config !== 'object' || config === null) {
|
|
54
|
+
console.warn('[XtMap] setMapConfig 必须传入对象参数')
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.provider !== undefined) setMapProvider(config.provider)
|
|
59
|
+
if (config.apiKey !== undefined) setMapApiKey(config.apiKey)
|
|
60
|
+
if (config.apiUrl !== undefined) setMapApiUrl(config.apiUrl)
|
|
61
|
+
if (config.mapType !== undefined) setMapType(config.mapType)
|
|
62
|
+
if (config.theme !== undefined) setMapTheme(config.theme)
|
|
63
|
+
if (config.center !== undefined) setMapCenter(config.center)
|
|
64
|
+
if (config.zoom !== undefined) setMapZoom(config.zoom)
|
|
65
|
+
if (config.plugins !== undefined) setMapPlugins(config.plugins)
|
|
66
|
+
if (config.securityJsCode !== undefined) setSecurityJsCode(config.securityJsCode)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const setMapProvider = (provider) => {
|
|
70
|
+
if (!MAP_PROVIDERS.includes(provider)) {
|
|
71
|
+
console.warn(`[XtMap] 无效的地图提供商: ${provider},可选: ${MAP_PROVIDERS.join(', ')}`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
currentConfig.provider = provider
|
|
75
|
+
emitConfigChange('provider', provider)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const setMapApiKey = (key) => {
|
|
79
|
+
currentConfig.apiKey = key
|
|
80
|
+
emitConfigChange('apiKey', key)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const setMapApiUrl = (url) => {
|
|
84
|
+
currentConfig.apiUrl = url
|
|
85
|
+
emitConfigChange('apiUrl', url)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const setMapType = (type) => {
|
|
89
|
+
if (!MAP_TYPES.includes(type)) {
|
|
90
|
+
console.warn(`[XtMap] 无效的地图类型: ${type},可选: ${MAP_TYPES.join(', ')}`)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
currentConfig.mapType = type
|
|
94
|
+
emitConfigChange('mapType', type)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const setMapTheme = (theme) => {
|
|
98
|
+
if (!MAP_THEMES.includes(theme)) {
|
|
99
|
+
console.warn(`[XtMap] 无效的主题: ${theme},可选: ${MAP_THEMES.join(', ')}`)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
currentConfig.theme = theme
|
|
103
|
+
emitConfigChange('theme', theme)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const setMapCenter = (center) => {
|
|
107
|
+
if (!Array.isArray(center) || center.length !== 2) {
|
|
108
|
+
console.warn('[XtMap] center 必须是 [lng, lat] 数组')
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
currentConfig.center = center
|
|
112
|
+
emitConfigChange('center', center)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const setMapZoom = (zoom) => {
|
|
116
|
+
const z = Number(zoom)
|
|
117
|
+
if (isNaN(z)) {
|
|
118
|
+
console.warn('[XtMap] zoom 必须是数字')
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
currentConfig.zoom = z
|
|
122
|
+
emitConfigChange('zoom', z)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const setMapPlugins = (plugins) => {
|
|
126
|
+
if (!Array.isArray(plugins)) {
|
|
127
|
+
console.warn('[XtMap] plugins 必须是数组')
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
currentConfig.plugins = plugins
|
|
131
|
+
emitConfigChange('plugins', plugins)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const setSecurityJsCode = (code) => {
|
|
135
|
+
currentConfig.securityJsCode = code
|
|
136
|
+
// 高德地图安全密钥:设置 window._AMapSecurityConfig
|
|
137
|
+
if (typeof window !== 'undefined' && code) {
|
|
138
|
+
window._AMapSecurityConfig = {
|
|
139
|
+
securityJsCode: code,
|
|
140
|
+
...(window._AMapSecurityConfig || {})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
emitConfigChange('securityJsCode', code)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 获取当前配置的快捷方法
|
|
147
|
+
export const getMapProvider = () => currentConfig.provider
|
|
148
|
+
export const getMapApiKey = () => currentConfig.apiKey
|
|
149
|
+
export const getMapApiUrl = () => currentConfig.apiUrl
|
|
150
|
+
export const getMapType = () => currentConfig.mapType
|
|
151
|
+
export const getMapTheme = () => currentConfig.theme
|
|
152
|
+
export const getMapCenter = () => currentConfig.center
|
|
153
|
+
export const getMapZoom = () => currentConfig.zoom
|
|
154
|
+
export const getMapPlugins = () => currentConfig.plugins
|
|
155
|
+
|
|
156
|
+
// 监听配置变更
|
|
157
|
+
export const onMapConfigChange = (listener) => {
|
|
158
|
+
if (typeof listener === 'function') {
|
|
159
|
+
configChangeListeners.push(listener)
|
|
160
|
+
return () => {
|
|
161
|
+
const index = configChangeListeners.indexOf(listener)
|
|
162
|
+
if (index > -1) configChangeListeners.splice(index, 1)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 重置为默认配置
|
|
168
|
+
export const resetMapConfig = () => {
|
|
169
|
+
setMapConfig(defaultConfig)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default {
|
|
173
|
+
MAP_PROVIDERS,
|
|
174
|
+
MAP_TYPES,
|
|
175
|
+
MAP_THEMES,
|
|
176
|
+
getMapConfig,
|
|
177
|
+
setMapConfig,
|
|
178
|
+
setMapProvider,
|
|
179
|
+
setMapApiKey,
|
|
180
|
+
setMapApiUrl,
|
|
181
|
+
setMapType,
|
|
182
|
+
setMapTheme,
|
|
183
|
+
setMapCenter,
|
|
184
|
+
setMapZoom,
|
|
185
|
+
setMapPlugins,
|
|
186
|
+
setSecurityJsCode,
|
|
187
|
+
getMapProvider,
|
|
188
|
+
getMapApiKey,
|
|
189
|
+
getMapApiUrl,
|
|
190
|
+
getMapType,
|
|
191
|
+
getMapTheme,
|
|
192
|
+
getMapCenter,
|
|
193
|
+
getMapZoom,
|
|
194
|
+
getMapPlugins,
|
|
195
|
+
onMapConfigChange,
|
|
196
|
+
resetMapConfig
|
|
197
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import XtMap from './index.vue'
|
|
2
|
+
import XtMapProvider from './provider.vue'
|
|
3
|
+
import './style/index.scss'
|
|
4
|
+
|
|
5
|
+
XtMap.install = function (Vue) {
|
|
6
|
+
Vue.component(XtMap.name, XtMap)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
XtMapProvider.install = function (Vue) {
|
|
10
|
+
Vue.component(XtMapProvider.name, XtMapProvider)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { XtMap, XtMapProvider }
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
install(Vue) {
|
|
17
|
+
Vue.component(XtMap.name, XtMap)
|
|
18
|
+
Vue.component(XtMapProvider.name, XtMapProvider)
|
|
19
|
+
},
|
|
20
|
+
XtMap,
|
|
21
|
+
XtMapProvider
|
|
22
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="xt-map-wrapper">
|
|
3
|
+
<div ref="mapContainer" class="xt-map-container" :data-theme="mergedTheme"></div>
|
|
4
|
+
<div v-if="loading" class="xt-map-loading">
|
|
5
|
+
<span>地图加载中...</span>
|
|
6
|
+
</div>
|
|
7
|
+
<div v-if="errorMessage" class="xt-map-error">
|
|
8
|
+
<span>{{ errorMessage }}</span>
|
|
9
|
+
</div>
|
|
10
|
+
<slot name="overlay"></slot>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { getAdapterClass } from './adapters/index'
|
|
16
|
+
import { clearScriptCache } from './loaders/script-loader'
|
|
17
|
+
import {
|
|
18
|
+
getMapConfig,
|
|
19
|
+
getMapProvider,
|
|
20
|
+
getMapApiKey,
|
|
21
|
+
getMapApiUrl,
|
|
22
|
+
getMapType,
|
|
23
|
+
getMapTheme,
|
|
24
|
+
getMapCenter,
|
|
25
|
+
getMapZoom,
|
|
26
|
+
setSecurityJsCode,
|
|
27
|
+
MAP_PROVIDERS,
|
|
28
|
+
MAP_TYPES,
|
|
29
|
+
MAP_THEMES,
|
|
30
|
+
onMapConfigChange
|
|
31
|
+
} from './config/xt-map-config'
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
name: 'XtMap',
|
|
35
|
+
|
|
36
|
+
props: {
|
|
37
|
+
provider: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
validator: (val) => val === '' || MAP_PROVIDERS.includes(val)
|
|
41
|
+
},
|
|
42
|
+
apiKey: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: ''
|
|
45
|
+
},
|
|
46
|
+
apiUrl: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: ''
|
|
49
|
+
},
|
|
50
|
+
mapType: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: '',
|
|
53
|
+
validator: (val) => val === '' || MAP_TYPES.includes(val)
|
|
54
|
+
},
|
|
55
|
+
theme: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: '',
|
|
58
|
+
validator: (val) => val === '' || MAP_THEMES.includes(val)
|
|
59
|
+
},
|
|
60
|
+
center: {
|
|
61
|
+
type: Array,
|
|
62
|
+
default: () => null
|
|
63
|
+
},
|
|
64
|
+
zoom: {
|
|
65
|
+
type: Number,
|
|
66
|
+
default: null
|
|
67
|
+
},
|
|
68
|
+
plugins: {
|
|
69
|
+
type: Array,
|
|
70
|
+
default: () => []
|
|
71
|
+
},
|
|
72
|
+
width: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: '100%'
|
|
75
|
+
},
|
|
76
|
+
height: {
|
|
77
|
+
type: String,
|
|
78
|
+
default: '400px'
|
|
79
|
+
},
|
|
80
|
+
securityJsCode: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: ''
|
|
83
|
+
},
|
|
84
|
+
tiandituLayerType: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: 'vec'
|
|
87
|
+
},
|
|
88
|
+
baiduCoordType: {
|
|
89
|
+
type: String,
|
|
90
|
+
default: 'bd09ll'
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
data() {
|
|
95
|
+
return {
|
|
96
|
+
loading: true,
|
|
97
|
+
errorMessage: '',
|
|
98
|
+
adapter: null,
|
|
99
|
+
_unsubscribeConfig: null
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
computed: {
|
|
104
|
+
mergedProvider() {
|
|
105
|
+
return this.provider || getMapProvider() || 'amap'
|
|
106
|
+
},
|
|
107
|
+
mergedApiKey() {
|
|
108
|
+
return this.apiKey || getMapApiKey() || ''
|
|
109
|
+
},
|
|
110
|
+
mergedApiUrl() {
|
|
111
|
+
return this.apiUrl || getMapApiUrl() || ''
|
|
112
|
+
},
|
|
113
|
+
mergedMapType() {
|
|
114
|
+
return this.mapType || getMapType() || 'standard'
|
|
115
|
+
},
|
|
116
|
+
mergedTheme() {
|
|
117
|
+
return this.theme || getMapTheme() || 'light'
|
|
118
|
+
},
|
|
119
|
+
mergedCenter() {
|
|
120
|
+
return this.center || getMapCenter() || [116.397428, 39.90923]
|
|
121
|
+
},
|
|
122
|
+
mergedZoom() {
|
|
123
|
+
return this.zoom !== null ? this.zoom : getMapZoom()
|
|
124
|
+
},
|
|
125
|
+
mergedSecurityJsCode() {
|
|
126
|
+
return this.securityJsCode || (getMapConfig().securityJsCode) || ''
|
|
127
|
+
},
|
|
128
|
+
mergedPlugins() {
|
|
129
|
+
return this.plugins.length > 0 ? this.plugins : getMapConfig().plugins || []
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
watch: {
|
|
134
|
+
mergedProvider: {
|
|
135
|
+
handler() {
|
|
136
|
+
this.$nextTick(() => this.rebuildMap())
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
mergedApiKey: {
|
|
140
|
+
handler() {
|
|
141
|
+
this.$nextTick(() => this.rebuildMap())
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
mergedMapType: {
|
|
145
|
+
handler(newVal) {
|
|
146
|
+
if (this.adapter) this.adapter.setMapType(newVal)
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
mergedTheme: {
|
|
150
|
+
handler(newVal) {
|
|
151
|
+
if (this.adapter) this.adapter.setTheme(newVal)
|
|
152
|
+
if (this.$refs.mapContainer) {
|
|
153
|
+
this.$refs.mapContainer.setAttribute('data-theme', newVal)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
mergedCenter: {
|
|
158
|
+
handler(newVal) {
|
|
159
|
+
if (this.adapter) this.adapter.setCenter(newVal)
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
mergedZoom: {
|
|
163
|
+
handler(newVal) {
|
|
164
|
+
if (this.adapter) this.adapter.setZoom(newVal)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
width: {
|
|
168
|
+
handler() {
|
|
169
|
+
this.$nextTick(() => {
|
|
170
|
+
if (this.adapter) this.adapter.resize()
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
height: {
|
|
175
|
+
handler() {
|
|
176
|
+
this.$nextTick(() => {
|
|
177
|
+
if (this.adapter) this.adapter.resize()
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
securityJsCode: {
|
|
182
|
+
handler(newVal) {
|
|
183
|
+
if (newVal) {
|
|
184
|
+
setSecurityJsCode(newVal)
|
|
185
|
+
// 高德 2.0:SDK 已加载后再设置 securityJsCode 无效,需要重建地图
|
|
186
|
+
if (this.mergedProvider === 'amap' && this.adapter && this.adapter.ready) {
|
|
187
|
+
this.$nextTick(() => this.rebuildMap())
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
mounted() {
|
|
195
|
+
// 监听全局配置变更
|
|
196
|
+
this._unsubscribeConfig = onMapConfigChange((key, value) => {
|
|
197
|
+
if (!this.provider && (key === 'provider')) this.$nextTick(() => this.rebuildMap())
|
|
198
|
+
if (!this.apiKey && (key === 'apiKey')) this.$nextTick(() => this.rebuildMap())
|
|
199
|
+
if (!this.apiUrl && (key === 'apiUrl')) this.$nextTick(() => this.rebuildMap())
|
|
200
|
+
if (!this.theme && key === 'theme' && this.adapter) this.adapter.setTheme(value)
|
|
201
|
+
if (!this.mapType && key === 'mapType' && this.adapter) this.adapter.setMapType(value)
|
|
202
|
+
if (!this.center && key === 'center' && this.adapter) this.adapter.setCenter(value)
|
|
203
|
+
if (this.zoom === null && key === 'zoom' && this.adapter) this.adapter.setZoom(value)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// 设置高德安全密钥
|
|
207
|
+
if (this.securityJsCode) setSecurityJsCode(this.securityJsCode)
|
|
208
|
+
|
|
209
|
+
this.$nextTick(() => this.initMap())
|
|
210
|
+
|
|
211
|
+
// 监听窗口尺寸变化
|
|
212
|
+
this._onWindowResize = () => {
|
|
213
|
+
if (this.adapter) this.adapter.resize()
|
|
214
|
+
}
|
|
215
|
+
if (typeof window !== 'undefined') {
|
|
216
|
+
window.addEventListener('resize', this._onWindowResize)
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
beforeDestroy() {
|
|
221
|
+
this.destroyMap()
|
|
222
|
+
if (this._unsubscribeConfig) this._unsubscribeConfig()
|
|
223
|
+
if (this._onWindowResize && typeof window !== 'undefined') {
|
|
224
|
+
window.removeEventListener('resize', this._onWindowResize)
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
methods: {
|
|
229
|
+
async initMap() {
|
|
230
|
+
const container = this.$refs.mapContainer
|
|
231
|
+
if (!container) return
|
|
232
|
+
|
|
233
|
+
// 设置容器尺寸
|
|
234
|
+
container.style.width = this.width
|
|
235
|
+
container.style.height = this.height
|
|
236
|
+
|
|
237
|
+
// 设置安全密钥(高德需要)
|
|
238
|
+
if (this.securityJsCode) setSecurityJsCode(this.securityJsCode)
|
|
239
|
+
|
|
240
|
+
this.loading = true
|
|
241
|
+
this.errorMessage = ''
|
|
242
|
+
|
|
243
|
+
const AdapterClass = getAdapterClass(this.mergedProvider)
|
|
244
|
+
|
|
245
|
+
this.adapter = new AdapterClass(container, {
|
|
246
|
+
apiKey: this.mergedApiKey,
|
|
247
|
+
apiUrl: this.mergedApiUrl,
|
|
248
|
+
mapType: this.mergedMapType,
|
|
249
|
+
theme: this.mergedTheme,
|
|
250
|
+
center: this.mergedCenter,
|
|
251
|
+
zoom: this.mergedZoom,
|
|
252
|
+
plugins: this.mergedPlugins,
|
|
253
|
+
securityJsCode: this.mergedSecurityJsCode,
|
|
254
|
+
tiandituLayerType: this.tiandituLayerType,
|
|
255
|
+
baiduCoordType: this.baiduCoordType
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
await this.adapter.init()
|
|
260
|
+
this.loading = false
|
|
261
|
+
|
|
262
|
+
// 绑定事件
|
|
263
|
+
this._bindAdapterEvents()
|
|
264
|
+
|
|
265
|
+
// 向父组件发出 ready 事件
|
|
266
|
+
this.$emit('ready', {
|
|
267
|
+
provider: this.mergedProvider,
|
|
268
|
+
map: this.adapter.getNativeMap(),
|
|
269
|
+
adapter: this.adapter
|
|
270
|
+
})
|
|
271
|
+
} catch (err) {
|
|
272
|
+
this.loading = false
|
|
273
|
+
this.errorMessage = err.message || '地图初始化失败'
|
|
274
|
+
this.$emit('error', err)
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
rebuildMap() {
|
|
279
|
+
this.destroyMap()
|
|
280
|
+
// 清理脚本缓存,确保新配置能生效(例如切换密钥后重新加载)
|
|
281
|
+
if (typeof clearScriptCache === 'function') {
|
|
282
|
+
clearScriptCache()
|
|
283
|
+
}
|
|
284
|
+
this.$nextTick(() => this.initMap())
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
destroyMap() {
|
|
288
|
+
if (this.adapter) {
|
|
289
|
+
this.adapter.destroy()
|
|
290
|
+
this.adapter = null
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
_bindAdapterEvents() {
|
|
295
|
+
if (!this.adapter) return
|
|
296
|
+
|
|
297
|
+
const events = ['click', 'moveend', 'zoomend', 'zoomchange', 'mapmove']
|
|
298
|
+
events.forEach((evt) => {
|
|
299
|
+
this.adapter.on(evt, (data) => {
|
|
300
|
+
this.$emit(evt, data)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
// 对外暴露的统一 API
|
|
306
|
+
setCenter(center) {
|
|
307
|
+
if (this.adapter) this.adapter.setCenter(center)
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
setZoom(zoom) {
|
|
311
|
+
if (this.adapter) this.adapter.setZoom(zoom)
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
setMapType(type) {
|
|
315
|
+
if (this.adapter) this.adapter.setMapType(type)
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
setTheme(theme) {
|
|
319
|
+
if (this.adapter) this.adapter.setTheme(theme)
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
getCenter() {
|
|
323
|
+
return this.adapter ? this.adapter.getCenter() : this.mergedCenter
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
getZoom() {
|
|
327
|
+
return this.adapter ? this.adapter.getZoom() : this.mergedZoom
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
getNativeMap() {
|
|
331
|
+
return this.adapter ? this.adapter.getNativeMap() : null
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
resize() {
|
|
335
|
+
if (this.adapter) this.adapter.resize()
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
on(eventName, handler) {
|
|
339
|
+
if (this.adapter) this.adapter.on(eventName, handler)
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
off(eventName) {
|
|
343
|
+
if (this.adapter) this.adapter.off(eventName)
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
rebuild() {
|
|
347
|
+
this.rebuildMap()
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
</script>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动态脚本加载器
|
|
3
|
+
* 负责动态注入地图 SDK <script> 标签,并返回 Promise
|
|
4
|
+
* 同一 URL 只会加载一次,缓存已加载的结果
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const loadedScripts = new Map() // URL -> Promise
|
|
8
|
+
const loadingScripts = new Map() // URL -> Promise (正在加载中)
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 动态加载单个脚本
|
|
12
|
+
* @param {string} url - 脚本 URL
|
|
13
|
+
* @param {object} options - 配置
|
|
14
|
+
* @param {string} options.charset - 字符集,默认 'utf-8'
|
|
15
|
+
* @param {boolean} options.async - 是否异步,默认 true
|
|
16
|
+
* @param {number} options.timeout - 超时时间(ms),默认 30000
|
|
17
|
+
* @returns {Promise<HTMLScriptElement>}
|
|
18
|
+
*/
|
|
19
|
+
export const loadScript = (url, options = {}) => {
|
|
20
|
+
if (typeof window === 'undefined') {
|
|
21
|
+
return Promise.reject(new Error('[XtMap] 非浏览器环境'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { charset = 'utf-8', async = true, timeout = 30000 } = options
|
|
25
|
+
|
|
26
|
+
if (loadedScripts.has(url)) {
|
|
27
|
+
return loadedScripts.get(url)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (loadingScripts.has(url)) {
|
|
31
|
+
return loadingScripts.get(url)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const promise = new Promise((resolve, reject) => {
|
|
35
|
+
const script = document.createElement('script')
|
|
36
|
+
script.type = 'text/javascript'
|
|
37
|
+
script.src = url
|
|
38
|
+
script.charset = charset
|
|
39
|
+
script.async = async
|
|
40
|
+
|
|
41
|
+
let timer = null
|
|
42
|
+
let done = false
|
|
43
|
+
|
|
44
|
+
const cleanup = () => {
|
|
45
|
+
done = true
|
|
46
|
+
if (timer) clearTimeout(timer)
|
|
47
|
+
script.onload = null
|
|
48
|
+
script.onerror = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
script.onload = () => {
|
|
52
|
+
if (done) return
|
|
53
|
+
cleanup()
|
|
54
|
+
loadingScripts.delete(url)
|
|
55
|
+
loadedScripts.set(url, Promise.resolve(script))
|
|
56
|
+
resolve(script)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
script.onerror = (err) => {
|
|
60
|
+
if (done) return
|
|
61
|
+
cleanup()
|
|
62
|
+
loadingScripts.delete(url)
|
|
63
|
+
reject(new Error(`[XtMap] 脚本加载失败: ${url}`))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (timeout > 0) {
|
|
67
|
+
timer = setTimeout(() => {
|
|
68
|
+
if (done) return
|
|
69
|
+
cleanup()
|
|
70
|
+
loadingScripts.delete(url)
|
|
71
|
+
reject(new Error(`[XtMap] 脚本加载超时: ${url}`))
|
|
72
|
+
}, timeout)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
document.head.appendChild(script)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
loadingScripts.set(url, promise)
|
|
79
|
+
return promise
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 批量加载脚本(按顺序)
|
|
84
|
+
* @param {string[]} urls
|
|
85
|
+
* @param {object} options
|
|
86
|
+
*/
|
|
87
|
+
export const loadScripts = async (urls, options = {}) => {
|
|
88
|
+
for (const url of urls) {
|
|
89
|
+
await loadScript(url, options)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 清除加载缓存(切换密钥等场景使用)
|
|
95
|
+
*/
|
|
96
|
+
export const clearScriptCache = () => {
|
|
97
|
+
loadedScripts.clear()
|
|
98
|
+
loadingScripts.clear()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 检查全局对象是否存在
|
|
103
|
+
*/
|
|
104
|
+
export const hasGlobal = (name) => {
|
|
105
|
+
if (typeof window === 'undefined') return false
|
|
106
|
+
return window[name] !== undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default {
|
|
110
|
+
loadScript,
|
|
111
|
+
loadScripts,
|
|
112
|
+
clearScriptCache,
|
|
113
|
+
hasGlobal
|
|
114
|
+
}
|