af-mobile-client-vue3 1.0.95 → 1.0.97
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/.env +6 -6
- package/.env.development +4 -4
- package/.env.envoiceShow +6 -6
- package/.env.production +6 -6
- package/.husky/commit-msg +1 -1
- package/.husky/pre-commit +1 -1
- package/.vscode/settings.json +61 -61
- package/build/vite/index.ts +91 -91
- package/eslint.config.js +9 -1
- package/mock/modules/user.mock.ts +152 -152
- package/package.json +2 -1
- package/public/favicon.svg +4 -4
- package/public/safari-pinned-tab.svg +32 -32
- package/scripts/verifyCommit.js +19 -19
- package/src/App.vue +43 -43
- package/src/api/user/index.ts +40 -40
- package/src/bootstrap.ts +18 -18
- package/src/components/core/App/MicroAppView.vue +3 -3
- package/src/components/core/NavBar/index.vue +12 -12
- package/src/components/core/SvgIcon/index.vue +1 -1
- package/src/components/core/Tabbar/index.vue +38 -38
- package/src/components/core/Uploader/index.vue +1 -1
- package/src/components/core/XGridDropOption/index.vue +151 -151
- package/src/components/core/XMultiSelect/index.vue +183 -183
- package/src/components/data/XCellDetail/index.vue +106 -106
- package/src/components/data/XCellList/XCellList.md +28 -28
- package/src/components/data/XCellList/index.vue +50 -11
- package/src/components/data/XCellListFilter/QrScanner/index.vue +1 -1
- package/src/components/data/XCellListFilter/VpnRecognition/index.vue +3 -3
- package/src/components/data/XCellListFilter/index.vue +160 -62
- package/src/components/data/XForm/index.vue +2 -2
- package/src/components/data/XFormGroup/index.vue +3 -3
- package/src/components/data/XFormItem/index.vue +25 -26
- package/src/components/data/XOlMap/demo.vue +209 -0
- package/src/components/data/XOlMap/index.vue +644 -0
- package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
- package/src/components/data/XReportForm/index.vue +1079 -1079
- package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +4 -7
- package/src/components/data/XReportGrid/XAddReport/index.md +3 -7
- package/src/components/data/XReportGrid/XAddReport/index.ts +2 -2
- package/src/components/data/XReportGrid/XReport.vue +25 -38
- package/src/components/data/XReportGrid/XReportDesign.vue +46 -46
- package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +3 -3
- package/src/components/data/XReportGrid/XReportDrawer/index.ts +2 -2
- package/src/components/data/XReportGrid/XReportJsonRender.vue +20 -7
- package/src/components/data/XReportGrid/XReportTrGroup.vue +4 -4
- package/src/components/data/XReportGrid/index.md +4 -6
- package/src/components/data/XSignature/index.vue +285 -285
- package/src/components/data/XTag/index.vue +10 -10
- package/src/components/layout/NormalDataLayout/index.vue +70 -70
- package/src/components/layout/TabBarLayout/index.vue +40 -40
- package/src/components.d.ts +53 -53
- package/src/env.d.ts +16 -16
- package/src/font-style/font.css +3 -3
- package/src/hooks/useCommon.ts +9 -9
- package/src/layout/GridView/index.vue +1 -1
- package/src/layout/PageLayout.vue +4 -4
- package/src/layout/SingleLayout.vue +2 -2
- package/src/locales/en-US.json +25 -25
- package/src/locales/zh-CN.json +25 -25
- package/src/plugins/AppData.ts +38 -38
- package/src/plugins/index.ts +1 -1
- package/src/router/guards.ts +59 -59
- package/src/router/index.ts +61 -60
- package/src/router/invoiceRoutes.ts +33 -33
- package/src/router/routes.ts +20 -14
- package/src/services/api/common.ts +109 -109
- package/src/services/api/manage.ts +8 -8
- package/src/services/restTools.ts +52 -52
- package/src/services/v3Api.ts +46 -35
- package/src/stores/modules/cachedView.ts +1 -1
- package/src/stores/modules/setting.ts +52 -52
- package/src/stores/modules/user.ts +5 -5
- package/src/stores/mutation-type.ts +7 -7
- package/src/styles/app.less +6 -1
- package/src/utils/Storage.ts +1 -1
- package/src/utils/authority-utils.ts +84 -84
- package/src/utils/crypto.ts +39 -39
- package/src/utils/http/index.ts +6 -6
- package/src/utils/i18n.ts +41 -41
- package/src/utils/indexedDB.ts +180 -180
- package/src/utils/mobileUtil.ts +26 -26
- package/src/utils/routerUtil.ts +271 -271
- package/src/utils/runEvalFunction.ts +13 -13
- package/src/utils/validate.ts +1 -1
- package/src/utils/wechatUtil.ts +9 -9
- package/src/views/chat/index.vue +1 -1
- package/src/views/common/LoadError.vue +64 -64
- package/src/views/common/NotFound.vue +68 -68
- package/src/views/component/EvaluateRecordView/index.vue +40 -40
- package/src/views/component/XCellDetailView/index.vue +217 -216
- package/src/views/component/XCellListView/index.vue +1 -1
- package/src/views/component/XFormAppraiseView/index.vue +4 -4
- package/src/views/component/XFormGroupView/index.vue +1 -1
- package/src/views/component/XFormView/index.vue +6 -7
- package/src/views/component/XReportFormIframeView/index.vue +47 -47
- package/src/views/component/XReportFormView/index.vue +13 -13
- package/src/views/component/XReportGridView/index.vue +2 -3
- package/src/views/component/XSignatureView/index.vue +50 -50
- package/src/views/component/index.vue +4 -4
- package/src/views/component/menu.vue +117 -117
- package/src/views/component/notice.vue +46 -46
- package/src/views/component/topNav.vue +36 -36
- package/src/views/invoiceShow/index.vue +61 -62
- package/src/views/user/login/ForgetPasswordForm.vue +94 -93
- package/src/views/user/login/LoginForm.vue +8 -7
- package/src/views/user/login/LoginTitle.vue +68 -68
- package/src/views/user/login/index.vue +22 -22
- package/src/views/user/my/index.vue +230 -230
- package/src/vue-router.d.ts +9 -9
- package/tsconfig.json +43 -43
- package/uno.config.ts +1 -1
- package/vite.config.ts +123 -123
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* OpenLayers地图组件
|
|
4
|
+
* 支持多种底图切换:
|
|
5
|
+
* - 高德地图(矢量)
|
|
6
|
+
* - 高德卫星图
|
|
7
|
+
* - 天地图(矢量)
|
|
8
|
+
* - 天地图卫星图
|
|
9
|
+
*/
|
|
10
|
+
import { Map, View } from 'ol'
|
|
11
|
+
import { defaults as defaultControls, ScaleLine } from 'ol/control'
|
|
12
|
+
import Feature from 'ol/Feature'
|
|
13
|
+
import Point from 'ol/geom/Point'
|
|
14
|
+
import { defaults as defaultInteractions } from 'ol/interaction'
|
|
15
|
+
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
|
16
|
+
import { fromLonLat } from 'ol/proj'
|
|
17
|
+
import { Vector as VectorSource, XYZ } from 'ol/source'
|
|
18
|
+
import { Circle as CircleStyle, Fill, Icon, Stroke, Style } from 'ol/style'
|
|
19
|
+
import { onUnmounted, ref } from 'vue'
|
|
20
|
+
import 'vant/lib/index.css' // 添加 Vant 样式
|
|
21
|
+
|
|
22
|
+
/** 初始化参数接口 */
|
|
23
|
+
interface InitParams {
|
|
24
|
+
/** 地图中心点坐标 [经度, 纬度] */
|
|
25
|
+
center?: [number, number]
|
|
26
|
+
/** 地图缩放级别 */
|
|
27
|
+
zoom?: number
|
|
28
|
+
/** 最大缩放级别 */
|
|
29
|
+
maxZoom?: number
|
|
30
|
+
/** 最小缩放级别 */
|
|
31
|
+
minZoom?: number
|
|
32
|
+
/** 天地图密钥 */
|
|
33
|
+
tianDiTuKey?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 点位数据接口 */
|
|
37
|
+
interface PointData {
|
|
38
|
+
/** 经度 */
|
|
39
|
+
longitude: number
|
|
40
|
+
/** 纬度 */
|
|
41
|
+
latitude: number
|
|
42
|
+
/** 点标题 */
|
|
43
|
+
title?: string
|
|
44
|
+
/** 自定义数据 */
|
|
45
|
+
extData?: Record<string, any>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 点位图层配置接口 */
|
|
49
|
+
interface PointLayerOptions {
|
|
50
|
+
/** 图标URL地址 */
|
|
51
|
+
icon: string
|
|
52
|
+
/** 图标大小 [宽, 高] */
|
|
53
|
+
iconSize?: [number, number]
|
|
54
|
+
/** 图标锚点位置 [x, y],范围 0-1 */
|
|
55
|
+
iconAnchor?: [number, number]
|
|
56
|
+
/** 点位数据数组 */
|
|
57
|
+
data: PointData[]
|
|
58
|
+
/** 点击事件回调 */
|
|
59
|
+
onClick?: (point: PointData, event: any) => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** WebGL渲染配置接口 */
|
|
63
|
+
interface WebGLPointOptions extends PointLayerOptions {
|
|
64
|
+
/** 是否开启聚合 */
|
|
65
|
+
enableCluster?: boolean
|
|
66
|
+
/** 聚合距离,单位像素 */
|
|
67
|
+
clusterDistance?: number
|
|
68
|
+
/** 聚合样式配置 */
|
|
69
|
+
clusterStyle?: {
|
|
70
|
+
/** 填充颜色 */
|
|
71
|
+
fillColor?: string
|
|
72
|
+
/** 边框颜色 */
|
|
73
|
+
strokeColor?: string
|
|
74
|
+
/** 文字颜色 */
|
|
75
|
+
textColor?: string
|
|
76
|
+
}
|
|
77
|
+
/** 渲染性能配置 */
|
|
78
|
+
performance?: {
|
|
79
|
+
/** 是否开启分块加载 */
|
|
80
|
+
enableChunk?: boolean
|
|
81
|
+
/** 每块数据量 */
|
|
82
|
+
chunkSize?: number
|
|
83
|
+
/** 是否开启节流 */
|
|
84
|
+
enableThrottle?: boolean
|
|
85
|
+
/** 节流时间间隔(ms) */
|
|
86
|
+
throttleWait?: number
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 地图容器引用 */
|
|
91
|
+
const mapRef = ref<HTMLDivElement>()
|
|
92
|
+
/** 地图实例 */
|
|
93
|
+
let map: Map | null = null
|
|
94
|
+
/** 当前底图类型 */
|
|
95
|
+
const currentMapType = ref<string>('tianditu')
|
|
96
|
+
|
|
97
|
+
/** 图层选项配置 */
|
|
98
|
+
const layerOptions = [
|
|
99
|
+
{ text: '高德地图', value: 'gaode' },
|
|
100
|
+
{ text: '高德卫星', value: 'gaodeSatellite' },
|
|
101
|
+
{ text: '天地图', value: 'tianditu' },
|
|
102
|
+
{ text: '天地图卫星', value: 'tianditusatellite' },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
/** 存储所有底图图层 */
|
|
106
|
+
const baseMaps: Record<string, TileLayer<XYZ>> = {}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 切换地图图层
|
|
110
|
+
* @param type - 图层类型
|
|
111
|
+
*/
|
|
112
|
+
function handleMapChange(type: string): void {
|
|
113
|
+
// 隐藏所有图层
|
|
114
|
+
Object.keys(baseMaps).forEach((key) => {
|
|
115
|
+
baseMaps[key].setVisible(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// 根据选择显示对应图层
|
|
119
|
+
switch (type) {
|
|
120
|
+
case 'gaodeSatellite':
|
|
121
|
+
baseMaps.gaodeSatellite.setVisible(true)
|
|
122
|
+
baseMaps.gaodelabelLayer.setVisible(true)
|
|
123
|
+
break
|
|
124
|
+
case 'tianditu':
|
|
125
|
+
baseMaps.tianditu.setVisible(true)
|
|
126
|
+
baseMaps.tianditulabel.setVisible(true)
|
|
127
|
+
break
|
|
128
|
+
case 'tianditusatellite':
|
|
129
|
+
baseMaps.tianditusatellite.setVisible(true)
|
|
130
|
+
baseMaps.tianditusatlabel.setVisible(true)
|
|
131
|
+
break
|
|
132
|
+
default:
|
|
133
|
+
baseMaps.gaode.setVisible(true)
|
|
134
|
+
}
|
|
135
|
+
currentMapType.value = type
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 初始化底图图层
|
|
140
|
+
* @param tianDiTuKey - 天地图密钥
|
|
141
|
+
*/
|
|
142
|
+
function initializeLayers(tianDiTuKey = ''): void {
|
|
143
|
+
// 高德地图
|
|
144
|
+
baseMaps.gaode = new TileLayer({
|
|
145
|
+
source: new XYZ({
|
|
146
|
+
url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}',
|
|
147
|
+
crossOrigin: 'anonymous',
|
|
148
|
+
projection: 'EPSG:3857',
|
|
149
|
+
}),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// 高德卫星图
|
|
153
|
+
baseMaps.gaodeSatellite = new TileLayer({
|
|
154
|
+
source: new XYZ({
|
|
155
|
+
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
|
156
|
+
crossOrigin: 'anonymous',
|
|
157
|
+
projection: 'EPSG:3857',
|
|
158
|
+
}),
|
|
159
|
+
visible: false,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// 高德标注图层
|
|
163
|
+
baseMaps.gaodelabelLayer = new TileLayer({
|
|
164
|
+
source: new XYZ({
|
|
165
|
+
url: 'http://webst02.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
|
|
166
|
+
crossOrigin: 'anonymous',
|
|
167
|
+
projection: 'EPSG:3857',
|
|
168
|
+
}),
|
|
169
|
+
visible: false,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// 天地图矢量图层
|
|
173
|
+
baseMaps.tianditu = new TileLayer({
|
|
174
|
+
source: new XYZ({
|
|
175
|
+
url: 'http://t0.tianditu.gov.cn/vec_w/wmts?'
|
|
176
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&'
|
|
177
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
178
|
+
projection: 'EPSG:3857',
|
|
179
|
+
}),
|
|
180
|
+
visible: false,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// 天地图标注图层
|
|
184
|
+
baseMaps.tianditulabel = new TileLayer({
|
|
185
|
+
source: new XYZ({
|
|
186
|
+
url: 'http://t0.tianditu.gov.cn/cva_w/wmts?'
|
|
187
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&'
|
|
188
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
189
|
+
projection: 'EPSG:3857',
|
|
190
|
+
}),
|
|
191
|
+
visible: false,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// 天地图卫星图层
|
|
195
|
+
baseMaps.tianditusatellite = new TileLayer({
|
|
196
|
+
source: new XYZ({
|
|
197
|
+
url: 'http://t0.tianditu.gov.cn/img_w/wmts?'
|
|
198
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&'
|
|
199
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
200
|
+
projection: 'EPSG:3857',
|
|
201
|
+
}),
|
|
202
|
+
visible: false,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// 天地图卫星标注
|
|
206
|
+
baseMaps.tianditusatlabel = new TileLayer({
|
|
207
|
+
source: new XYZ({
|
|
208
|
+
url: 'http://t0.tianditu.gov.cn/cia_w/wmts?'
|
|
209
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&'
|
|
210
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
211
|
+
projection: 'EPSG:3857',
|
|
212
|
+
}),
|
|
213
|
+
visible: false,
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 初始化地图
|
|
219
|
+
* @param params - 初始化参数
|
|
220
|
+
*/
|
|
221
|
+
function init(params: InitParams = {}): void {
|
|
222
|
+
if (!mapRef.value)
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
// 设置默认参数
|
|
226
|
+
const {
|
|
227
|
+
center = [116.404, 39.915], // 默认北京坐标
|
|
228
|
+
zoom = 10,
|
|
229
|
+
maxZoom = 18,
|
|
230
|
+
minZoom = 4,
|
|
231
|
+
tianDiTuKey = '', // 天地图密钥
|
|
232
|
+
} = params
|
|
233
|
+
|
|
234
|
+
// 初始化所有底图图层
|
|
235
|
+
initializeLayers(tianDiTuKey)
|
|
236
|
+
|
|
237
|
+
// 创建地图实例
|
|
238
|
+
map = new Map({
|
|
239
|
+
target: mapRef.value,
|
|
240
|
+
layers: Object.values(baseMaps),
|
|
241
|
+
view: new View({
|
|
242
|
+
center: fromLonLat(center),
|
|
243
|
+
zoom,
|
|
244
|
+
projection: 'EPSG:3857',
|
|
245
|
+
maxZoom,
|
|
246
|
+
minZoom,
|
|
247
|
+
}),
|
|
248
|
+
controls: defaultControls({
|
|
249
|
+
zoom: false,
|
|
250
|
+
rotate: false,
|
|
251
|
+
attribution: false,
|
|
252
|
+
}).extend([
|
|
253
|
+
new ScaleLine({
|
|
254
|
+
units: 'metric',
|
|
255
|
+
className: 'ol-scale-line',
|
|
256
|
+
}),
|
|
257
|
+
]),
|
|
258
|
+
// 使用默认交互,包括拖动、缩放等
|
|
259
|
+
interactions: defaultInteractions({
|
|
260
|
+
altShiftDragRotate: false, // 禁用 Alt+Shift+拖动 旋转
|
|
261
|
+
pinchRotate: false, // 禁用双指旋转
|
|
262
|
+
}),
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// 设置鼠标样式
|
|
266
|
+
if (mapRef.value) {
|
|
267
|
+
mapRef.value.style.cursor = 'grab'
|
|
268
|
+
|
|
269
|
+
// 监听地图事件
|
|
270
|
+
const mapElement = mapRef.value
|
|
271
|
+
|
|
272
|
+
// 鼠标按下时
|
|
273
|
+
mapElement.addEventListener('mousedown', () => {
|
|
274
|
+
mapElement.style.cursor = 'grabbing'
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// 鼠标释放时
|
|
278
|
+
mapElement.addEventListener('mouseup', () => {
|
|
279
|
+
mapElement.style.cursor = 'grab'
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// 鼠标离开地图时
|
|
283
|
+
mapElement.addEventListener('mouseleave', () => {
|
|
284
|
+
mapElement.style.cursor = 'grab'
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 获取地图实例
|
|
291
|
+
* @returns OpenLayers Map 实例
|
|
292
|
+
*/
|
|
293
|
+
function getMap(): Map | null {
|
|
294
|
+
return map
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 设置地图中心点
|
|
299
|
+
* @param center - 经纬度坐标 [经度, 纬度]
|
|
300
|
+
* @param animate - 是否使用动画效果,默认true
|
|
301
|
+
*/
|
|
302
|
+
function setCenter(center: [number, number], animate = true): void {
|
|
303
|
+
if (!map)
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
const view = map.getView()
|
|
307
|
+
if (animate) {
|
|
308
|
+
view.animate({
|
|
309
|
+
center: fromLonLat(center),
|
|
310
|
+
duration: 500,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
view.setCenter(fromLonLat(center))
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 设置地图缩放级别
|
|
320
|
+
* @param zoom - 缩放级别
|
|
321
|
+
* @param animate - 是否使用动画效果,默认true
|
|
322
|
+
*/
|
|
323
|
+
function setZoom(zoom: number, animate = true): void {
|
|
324
|
+
if (!map)
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
const view = map.getView()
|
|
328
|
+
if (animate) {
|
|
329
|
+
view.animate({
|
|
330
|
+
zoom,
|
|
331
|
+
duration: 500,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
view.setZoom(zoom)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 获取当前地图缩放级别
|
|
341
|
+
* @returns 当前缩放级别
|
|
342
|
+
*/
|
|
343
|
+
function getZoom(): number {
|
|
344
|
+
if (!map)
|
|
345
|
+
return 0
|
|
346
|
+
return map.getView().getZoom() || 0
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 设置地图中心点和缩放级别
|
|
351
|
+
* @param center - 经纬度坐标 [经度, 纬度]
|
|
352
|
+
* @param zoom - 缩放级别
|
|
353
|
+
* @param animate - 是否使用动画效果,默认true
|
|
354
|
+
*/
|
|
355
|
+
function setCenterAndZoom(center: [number, number], zoom: number, animate = true): void {
|
|
356
|
+
if (!map)
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
const view = map.getView()
|
|
360
|
+
if (animate) {
|
|
361
|
+
view.animate({
|
|
362
|
+
center: fromLonLat(center),
|
|
363
|
+
zoom,
|
|
364
|
+
duration: 500,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
view.setCenter(fromLonLat(center))
|
|
369
|
+
view.setZoom(zoom)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 渲染点位
|
|
375
|
+
* @param options - 点位图层配置
|
|
376
|
+
* @returns 返回图层实例
|
|
377
|
+
*/
|
|
378
|
+
function addVectorPoints(options: PointLayerOptions): VectorLayer<VectorSource> | null {
|
|
379
|
+
if (!map)
|
|
380
|
+
return null
|
|
381
|
+
|
|
382
|
+
const {
|
|
383
|
+
icon,
|
|
384
|
+
iconAnchor = [0.5, 1],
|
|
385
|
+
data,
|
|
386
|
+
onClick,
|
|
387
|
+
} = options
|
|
388
|
+
|
|
389
|
+
const vectorSource = new VectorSource()
|
|
390
|
+
const vectorLayer = new VectorLayer({
|
|
391
|
+
source: vectorSource,
|
|
392
|
+
zIndex: 1,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
data.forEach((point) => {
|
|
396
|
+
const feature = new Feature({
|
|
397
|
+
geometry: new Point(fromLonLat([point.longitude, point.latitude])),
|
|
398
|
+
properties: {
|
|
399
|
+
...point,
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const style = new Style({
|
|
404
|
+
image: new Icon({
|
|
405
|
+
src: icon,
|
|
406
|
+
scale: 0.5,
|
|
407
|
+
anchor: iconAnchor,
|
|
408
|
+
anchorXUnits: 'fraction',
|
|
409
|
+
anchorYUnits: 'fraction',
|
|
410
|
+
crossOrigin: 'anonymous',
|
|
411
|
+
}),
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
feature.setStyle(style)
|
|
415
|
+
vectorSource.addFeature(feature)
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
map.addLayer(vectorLayer)
|
|
419
|
+
|
|
420
|
+
if (onClick) {
|
|
421
|
+
map.on('click', (event) => {
|
|
422
|
+
map.forEachFeatureAtPixel(event.pixel, (feature) => {
|
|
423
|
+
if (feature) {
|
|
424
|
+
const properties = feature.getProperties()
|
|
425
|
+
onClick(properties as PointData, event)
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
return false
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return vectorLayer
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* 渲染海量点(适用于10万+数据量)
|
|
438
|
+
* @param options - WebGL渲染配置
|
|
439
|
+
* @returns 返回图层实例
|
|
440
|
+
*/
|
|
441
|
+
function addWebGLPoints(options: WebGLPointOptions): VectorLayer<VectorSource> | null {
|
|
442
|
+
if (!map)
|
|
443
|
+
return null
|
|
444
|
+
|
|
445
|
+
const {
|
|
446
|
+
iconSize = [16, 16],
|
|
447
|
+
data,
|
|
448
|
+
performance = {
|
|
449
|
+
enableChunk: true,
|
|
450
|
+
chunkSize: 5000,
|
|
451
|
+
enableThrottle: true,
|
|
452
|
+
throttleWait: 100,
|
|
453
|
+
},
|
|
454
|
+
} = options
|
|
455
|
+
|
|
456
|
+
// 创建矢量图层,使用 Canvas 渲染器优化性能
|
|
457
|
+
const vectorSource = new VectorSource()
|
|
458
|
+
const vectorLayer = new VectorLayer({
|
|
459
|
+
source: vectorSource,
|
|
460
|
+
style: new Style({
|
|
461
|
+
image: new CircleStyle({
|
|
462
|
+
radius: iconSize[0] / 2,
|
|
463
|
+
fill: new Fill({
|
|
464
|
+
color: 'rgba(0, 150, 255, 0.8)',
|
|
465
|
+
}),
|
|
466
|
+
stroke: new Stroke({
|
|
467
|
+
color: '#fff',
|
|
468
|
+
width: 1,
|
|
469
|
+
}),
|
|
470
|
+
}),
|
|
471
|
+
}),
|
|
472
|
+
updateWhileAnimating: false, // 动画时不更新以提高性能
|
|
473
|
+
updateWhileInteracting: false, // 交互时不更新以提高性能
|
|
474
|
+
zIndex: 1,
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// 分块加载数据
|
|
478
|
+
if (performance.enableChunk) {
|
|
479
|
+
const chunkSize = performance.chunkSize || 5000
|
|
480
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
481
|
+
const chunk = data.slice(i, i + chunkSize)
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
const features = chunk.map(point =>
|
|
484
|
+
new Feature({
|
|
485
|
+
geometry: new Point(fromLonLat([point.longitude, point.latitude])),
|
|
486
|
+
properties: {
|
|
487
|
+
title: point.title,
|
|
488
|
+
...point.extData,
|
|
489
|
+
},
|
|
490
|
+
}),
|
|
491
|
+
)
|
|
492
|
+
vectorSource.addFeatures(features)
|
|
493
|
+
}, 0)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
const features = data.map(point =>
|
|
498
|
+
new Feature({
|
|
499
|
+
geometry: new Point(fromLonLat([point.longitude, point.latitude])),
|
|
500
|
+
properties: {
|
|
501
|
+
title: point.title,
|
|
502
|
+
...point.extData,
|
|
503
|
+
},
|
|
504
|
+
}),
|
|
505
|
+
)
|
|
506
|
+
vectorSource.addFeatures(features)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 添加到地图
|
|
510
|
+
map.addLayer(vectorLayer)
|
|
511
|
+
|
|
512
|
+
return vectorLayer
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 暴露方法给父组件
|
|
516
|
+
defineExpose({
|
|
517
|
+
init,
|
|
518
|
+
getMap,
|
|
519
|
+
setCenter,
|
|
520
|
+
setZoom,
|
|
521
|
+
getZoom,
|
|
522
|
+
setCenterAndZoom,
|
|
523
|
+
addVectorPoints,
|
|
524
|
+
addWebGLPoints,
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
// 组件卸载时清理地图实例
|
|
528
|
+
onUnmounted(() => {
|
|
529
|
+
if (map) {
|
|
530
|
+
map.setTarget(undefined)
|
|
531
|
+
map = null
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
</script>
|
|
535
|
+
|
|
536
|
+
<template>
|
|
537
|
+
<div class="map-wrapper">
|
|
538
|
+
<div ref="mapRef" class="ol-map" />
|
|
539
|
+
|
|
540
|
+
<!-- 图层切换下拉框 -->
|
|
541
|
+
<div class="layer-selector">
|
|
542
|
+
<select v-model="currentMapType" @change="handleMapChange(currentMapType)">
|
|
543
|
+
<option v-for="layer in layerOptions" :key="layer.value" :value="layer.value">
|
|
544
|
+
{{ layer.text }}
|
|
545
|
+
</option>
|
|
546
|
+
</select>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</template>
|
|
550
|
+
|
|
551
|
+
<style scoped lang="less">
|
|
552
|
+
.map-wrapper {
|
|
553
|
+
position: relative;
|
|
554
|
+
width: 100%;
|
|
555
|
+
height: 100%;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.ol-map {
|
|
559
|
+
width: 100%;
|
|
560
|
+
height: 100%;
|
|
561
|
+
min-height: 400px;
|
|
562
|
+
touch-action: none;
|
|
563
|
+
|
|
564
|
+
&:active {
|
|
565
|
+
cursor: grabbing;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.layer-selector {
|
|
570
|
+
position: absolute;
|
|
571
|
+
right: 10px;
|
|
572
|
+
top: 10px;
|
|
573
|
+
z-index: 1000;
|
|
574
|
+
background: white;
|
|
575
|
+
border-radius: 4px;
|
|
576
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
577
|
+
padding: 2px;
|
|
578
|
+
|
|
579
|
+
select {
|
|
580
|
+
padding: 6px 24px 6px 12px;
|
|
581
|
+
font-size: 14px;
|
|
582
|
+
border: none;
|
|
583
|
+
border-radius: 4px;
|
|
584
|
+
background-color: white;
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
outline: none;
|
|
587
|
+
appearance: none;
|
|
588
|
+
-webkit-appearance: none;
|
|
589
|
+
-moz-appearance: none;
|
|
590
|
+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
591
|
+
background-repeat: no-repeat;
|
|
592
|
+
background-position: right 6px center;
|
|
593
|
+
background-size: 12px;
|
|
594
|
+
|
|
595
|
+
&:hover {
|
|
596
|
+
background-color: #f5f5f5;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
option {
|
|
600
|
+
padding: 8px;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// 比例尺样式
|
|
606
|
+
:deep(.ol-scale-line) {
|
|
607
|
+
position: absolute;
|
|
608
|
+
left: 0.5em;
|
|
609
|
+
bottom: 0.5em;
|
|
610
|
+
background: rgba(255, 255, 255, 0.8);
|
|
611
|
+
padding: 2px;
|
|
612
|
+
border-radius: 4px;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
:deep(.ol-scale-line-inner) {
|
|
616
|
+
border: 2px solid #333;
|
|
617
|
+
border-top: none;
|
|
618
|
+
color: #333;
|
|
619
|
+
font-size: 12px;
|
|
620
|
+
text-align: center;
|
|
621
|
+
margin: 1px;
|
|
622
|
+
will-change: contents, width;
|
|
623
|
+
transition: width 0.25s;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// 移动端适配
|
|
627
|
+
@media screen and (max-width: 768px) {
|
|
628
|
+
.layer-selector {
|
|
629
|
+
right: 8px;
|
|
630
|
+
top: 8px;
|
|
631
|
+
|
|
632
|
+
select {
|
|
633
|
+
padding: 4px 20px 4px 8px;
|
|
634
|
+
font-size: 12px;
|
|
635
|
+
background-size: 10px;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
:deep(.ol-scale-line) {
|
|
640
|
+
transform: scale(0.8);
|
|
641
|
+
transform-origin: left bottom;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
</style>
|