Commit 150cdf23b4bea17f5b1af6e4581693e7c1e85708
1 parent
d73251e3
feat(src/packages): 优化高德地图标点弹窗和数据展示
Showing
9 changed files
with
215 additions
and
29 deletions
@@ -88,6 +88,12 @@ export const option = { | @@ -88,6 +88,12 @@ export const option = { | ||
88 | amapLon: 104.108689, | 88 | amapLon: 104.108689, |
89 | amapLat: 30.66176, | 89 | amapLat: 30.66176, |
90 | amapZindex: 11, | 90 | amapZindex: 11, |
91 | + typeMarker: '', | ||
92 | + mpBorderConfig: { | ||
93 | + label: '弹窗边框1', | ||
94 | + value: 'Border01', | ||
95 | + config: 'Border01' | ||
96 | + }, | ||
91 | marker: { | 97 | marker: { |
92 | fillColor: '#E98984FF', | 98 | fillColor: '#E98984FF', |
93 | fillOpacity: 0.5, | 99 | fillOpacity: 0.5, |
@@ -71,19 +71,53 @@ | @@ -71,19 +71,53 @@ | ||
71 | <setting-item name="类型"> | 71 | <setting-item name="类型"> |
72 | <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" /> | 72 | <n-select size="small" v-model:value="optionData.mapOptions.mapMarkerType" :options="MarkerOptions" /> |
73 | </setting-item> | 73 | </setting-item> |
74 | - <setting-item name="颜色"> | 74 | + <setting-item v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.CIRCLE_MARKER" name="颜色"> |
75 | <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker> | 75 | <n-color-picker v-model:value="optionData.mapOptions.marker.fillColor" size="small"></n-color-picker> |
76 | </setting-item> | 76 | </setting-item> |
77 | </setting-item-box> | 77 | </setting-item-box> |
78 | + <setting-item-box name="图标选择" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | ||
79 | + <setting-item name="图标选择"> | ||
80 | + <NSelect | ||
81 | + size="small" | ||
82 | + placeholder="请选择您要使用的图标" | ||
83 | + style="width: 250px" | ||
84 | + :value="typeMarkerValue" | ||
85 | + :options="typeMarkerOptions" | ||
86 | + :render-label="renderOption" | ||
87 | + clearable | ||
88 | + filterable | ||
89 | + @update:value="selectHandle" | ||
90 | + /> | ||
91 | + </setting-item> | ||
92 | + </setting-item-box> | ||
93 | + <setting-item-box name="弹窗选择" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | ||
94 | + <setting-item name="弹窗选择"> | ||
95 | + <NSelect | ||
96 | + size="small" | ||
97 | + placeholder="请选择您要使用的弹窗" | ||
98 | + style="width: 250px" | ||
99 | + :value="mapSelectBorderValue" | ||
100 | + :options="mapSelectBorderOption.option" | ||
101 | + @update:value="mapSelectBorderHandle" | ||
102 | + clearable | ||
103 | + filterable | ||
104 | + /> | ||
105 | + </setting-item> | ||
106 | + </setting-item-box> | ||
78 | </collapse-item> | 107 | </collapse-item> |
79 | </template> | 108 | </template> |
80 | 109 | ||
81 | <script setup lang="ts"> | 110 | <script setup lang="ts"> |
82 | -import { PropType } from 'vue' | 111 | +import { PropType, ref, computed, h, onMounted, reactive } from 'vue' |
83 | import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' | 112 | import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' |
84 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 113 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
114 | +import { NEllipsis, NImage, NSelect, NSpace, SelectOption } from 'naive-ui' | ||
115 | + | ||
116 | +interface BaseSelectBorderIF extends SelectOption { | ||
117 | + config?: string | ||
118 | +} | ||
85 | 119 | ||
86 | -defineProps({ | 120 | +const props = defineProps({ |
87 | optionData: { | 121 | optionData: { |
88 | type: Object as PropType<typeof option>, | 122 | type: Object as PropType<typeof option>, |
89 | required: true | 123 | required: true |
@@ -187,10 +221,10 @@ const featuresOptions = [ | @@ -187,10 +221,10 @@ const featuresOptions = [ | ||
187 | ] | 221 | ] |
188 | 222 | ||
189 | const MarkerOptions = [ | 223 | const MarkerOptions = [ |
190 | - { | ||
191 | - value: MarkerEnum.CIRCLE_MARKER, | ||
192 | - label: '圆形标点' | ||
193 | - }, | 224 | + // { |
225 | + // value: MarkerEnum.CIRCLE_MARKER, | ||
226 | + // label: '圆形标点' | ||
227 | + // }, | ||
194 | { | 228 | { |
195 | value: MarkerEnum.MARKER, | 229 | value: MarkerEnum.MARKER, |
196 | label: '定位标点' | 230 | label: '定位标点' |
@@ -200,4 +234,119 @@ const MarkerOptions = [ | @@ -200,4 +234,119 @@ const MarkerOptions = [ | ||
200 | label: '隐藏标点' | 234 | label: '隐藏标点' |
201 | } | 235 | } |
202 | ] | 236 | ] |
237 | + | ||
238 | +onMounted(() => { | ||
239 | + props.optionData.mapOptions.typeMarker = getMarkerImagePath(props.optionData.mapOptions.typeMarker || 'position1.png') | ||
240 | + typeMarkerValue.value = props.optionData.mapOptions.typeMarker || 'position1.png' | ||
241 | + mapSelectBorderValue.value = props.optionData.mapOptions.mpBorderConfig.value || '弹窗边框1' | ||
242 | +}) | ||
243 | + | ||
244 | +const typeMarkerValue = ref<string | null>('position1.png') | ||
245 | + | ||
246 | +const mapSelectBorderValue = ref<string | null>('Border01') | ||
247 | + | ||
248 | +const mapSelectBorderOption = reactive<{ option: BaseSelectBorderIF[] }>({ | ||
249 | + option: [ | ||
250 | + { | ||
251 | + label: '弹窗边框1', | ||
252 | + value: 'Border01' | ||
253 | + }, | ||
254 | + { | ||
255 | + label: '弹窗边框2', | ||
256 | + value: 'Border02' | ||
257 | + }, | ||
258 | + { | ||
259 | + label: '弹窗边框3', | ||
260 | + value: 'Border03' | ||
261 | + }, | ||
262 | + { | ||
263 | + label: '弹窗边框4', | ||
264 | + value: 'Border04' | ||
265 | + }, | ||
266 | + { | ||
267 | + label: '弹窗边框5', | ||
268 | + value: 'Border05' | ||
269 | + }, | ||
270 | + { | ||
271 | + label: '弹窗边框6', | ||
272 | + value: 'Border06' | ||
273 | + }, | ||
274 | + { | ||
275 | + label: '弹窗边框7', | ||
276 | + value: 'Border07' | ||
277 | + }, | ||
278 | + { | ||
279 | + label: '弹窗边框8', | ||
280 | + value: 'Border08' | ||
281 | + }, | ||
282 | + { | ||
283 | + label: '弹窗边框9', | ||
284 | + value: 'Border09' | ||
285 | + }, | ||
286 | + { | ||
287 | + label: '弹窗边框10', | ||
288 | + value: 'Border10' | ||
289 | + }, | ||
290 | + { | ||
291 | + label: '弹窗边框11', | ||
292 | + value: 'Border11' | ||
293 | + }, | ||
294 | + { | ||
295 | + label: '弹窗边框12', | ||
296 | + value: 'Border12' | ||
297 | + }, | ||
298 | + { | ||
299 | + label: '弹窗边框13', | ||
300 | + value: 'Border13' | ||
301 | + } | ||
302 | + ] | ||
303 | +}) | ||
304 | + | ||
305 | +const mapSelectBorderHandle = (value: string, option: BaseSelectBorderIF) => { | ||
306 | + mapSelectBorderValue.value = value | ||
307 | + ;(props.optionData.mapOptions.mpBorderConfig as BaseSelectBorderIF) = { ...option } | ||
308 | +} | ||
309 | + | ||
310 | +const isHref = (url: string) => { | ||
311 | + try { | ||
312 | + new URL(url) | ||
313 | + return true | ||
314 | + } catch (error) { | ||
315 | + return false | ||
316 | + } | ||
317 | +} | ||
318 | + | ||
319 | +// import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 | ||
320 | +const typeMarkerOptions = computed(() => { | ||
321 | + const pathList = import.meta.glob('./images/marker/*') | ||
322 | + return Object.keys(pathList).map(item => { | ||
323 | + const imgName = item.split('/').at(-1) | ||
324 | + return { | ||
325 | + label: imgName, | ||
326 | + value: imgName | ||
327 | + } as SelectOption | ||
328 | + }) | ||
329 | +}) | ||
330 | +// | ||
331 | + | ||
332 | +const getMarkerImagePath = (name: string) => { | ||
333 | + return isHref(name) ? name : new URL(`./images/marker/${name}`, import.meta.url).href | ||
334 | +} | ||
335 | + | ||
336 | +const renderOption = (option: SelectOption) => { | ||
337 | + return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | ||
338 | + h(NImage, { | ||
339 | + width: 25, | ||
340 | + src: getMarkerImagePath(option.value as string), | ||
341 | + previewDisabled: true, | ||
342 | + style: { height: '25px' } | ||
343 | + } as Recordable), | ||
344 | + h(NEllipsis, null, () => option.label) | ||
345 | + ]) | ||
346 | +} | ||
347 | + | ||
348 | +const selectHandle = (value: string) => { | ||
349 | + typeMarkerValue.value = value | ||
350 | + props.optionData.mapOptions.typeMarker = getMarkerImagePath(value) | ||
351 | +} | ||
203 | </script> | 352 | </script> |
1.24 KB
1.4 KB
904 Bytes
1.02 KB
970 Bytes
1.51 KB
@@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
6 | </template> | 6 | </template> |
7 | 7 | ||
8 | <script setup lang="ts"> | 8 | <script setup lang="ts"> |
9 | -import { ref, PropType, toRefs, watch } from 'vue' | 9 | +import { ref, PropType, toRefs, watch, render, h } from 'vue' |
10 | import AMapLoader from '@amap/amap-jsapi-loader' | 10 | import AMapLoader from '@amap/amap-jsapi-loader' |
11 | import { CreateComponentType } from '@/packages/index.d' | 11 | import { CreateComponentType } from '@/packages/index.d' |
12 | import { useChartDataFetch } from '@/hooks' | 12 | import { useChartDataFetch } from '@/hooks' |
@@ -16,6 +16,7 @@ import { isArray } from '@/utils' | @@ -16,6 +16,7 @@ import { isArray } from '@/utils' | ||
16 | import djh from './images/djh.png' | 16 | import djh from './images/djh.png' |
17 | import online from './images/online.png' | 17 | import online from './images/online.png' |
18 | import lx1 from './images/lx1.png' | 18 | import lx1 from './images/lx1.png' |
19 | +import positionImg from './images/marker/position1.png' | ||
19 | import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index' | 20 | import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index' |
20 | import dayjs from 'dayjs' | 21 | import dayjs from 'dayjs' |
21 | import SearchBox from './components/SearchBox.vue' | 22 | import SearchBox from './components/SearchBox.vue' |
@@ -44,7 +45,9 @@ let { | @@ -44,7 +45,9 @@ let { | ||
44 | viewMode, | 45 | viewMode, |
45 | pitch, | 46 | pitch, |
46 | skyColor, | 47 | skyColor, |
47 | - marker | 48 | + marker, |
49 | + typeMarker, | ||
50 | + mpBorderConfig | ||
48 | } = toRefs(props.chartConfig.option.mapOptions) | 51 | } = toRefs(props.chartConfig.option.mapOptions) |
49 | 52 | ||
50 | //官方没有高德地图api的ts,所以类型全用的any | 53 | //官方没有高德地图api的ts,所以类型全用的any |
@@ -84,8 +87,7 @@ const initMap = (newData: any) => { | @@ -84,8 +87,7 @@ const initMap = (newData: any) => { | ||
84 | `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` | 87 | `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` |
85 | ) | 88 | ) |
86 | } | 89 | } |
87 | - //点击地图任意地方关闭infoWindow窗体 | ||
88 | - mapIns.on('click', () => { | 90 | + mapIns.on('mouseout', () => { |
89 | mapIns.clearInfoWindow() | 91 | mapIns.clearInfoWindow() |
90 | }) | 92 | }) |
91 | }) | 93 | }) |
@@ -102,26 +104,38 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { | @@ -102,26 +104,38 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { | ||
102 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 | 104 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 |
103 | let { lastUpdateTs } = res[0] | 105 | let { lastUpdateTs } = res[0] |
104 | const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') | 106 | const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') |
107 | + //render方式渲染小组件里的边框组件 | ||
108 | + const BorderInstance = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/index.vue`) | ||
109 | + const config = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/config.ts`) | ||
110 | + const BorderConfigInstance = new config.default() | ||
111 | + const id = Date.now().toString() | ||
112 | + setTimeout(() => { | ||
113 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
114 | + render(h(BorderInstance.default, { chartConfig: BorderConfigInstance }), document.getElementById(id)!) | ||
115 | + }, 100) | ||
116 | + // | ||
105 | return ` | 117 | return ` |
106 | - <div style="width:15vw;height:18vh;background-color:white;"> | ||
107 | - <div style="margin:0px 10px"> | 118 | + <div id="${id}" style="width:25vw;height:29vh;position:relative;top:30px"> |
119 | + <div style="position: absolute;top: 52px;left: 105px;"> | ||
108 | <div style="display:flex;justify-content:space-between; margin:20px 0px;"> | 120 | <div style="display:flex;justify-content:space-between; margin:20px 0px;"> |
109 | - <div style="font-size:16px;font-weight:bold">${alias || name}</div> | 121 | + <div style="width:12vw;text-overflow: ellipsis;overflow: hidden; word-break: break-all;white-space: nowrap;font-size:18px;font-weight:bold;color:white">${ |
122 | + alias || name | ||
123 | + }</div> | ||
110 | ${ | 124 | ${ |
111 | deviceState === 'INACTIVE' | 125 | deviceState === 'INACTIVE' |
112 | - ? `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${djh}" class="mr-1">待激活</div>` | 126 | + ? `<div style="display:flex;align-items:center;color:#999999"><img style="width:15px;height:15px;margin-right:5px" src="${djh}">待激活</div>` |
113 | : deviceState === 'ONLINE' | 127 | : deviceState === 'ONLINE' |
114 | - ? `<div style="display:flex;align-items:center; "> | ||
115 | - <img style="width:15px;height:15px" src="${online}" class="mr-1">在线</div>` | ||
116 | - : `<div style="display:flex;align-items:center;"><img style="width:15px;height:15px" src="${lx1}" class="mr-1">离线</div>` | 128 | + ? `<div style="display:flex;align-items:center;color:green"> |
129 | + <img style="width:15px;height:15px;margin-right:5px" src="${online}" class="mr-1">在线</div>` | ||
130 | + : `<div style="display:flex;align-items:center;color:#F5222D"><img style="width:15px;height:15px;margin-right:5px" src="${lx1}">离线</div>` | ||
117 | } | 131 | } |
118 | </div> | 132 | </div> |
119 | - <div>所属组织:${organizationDTO.name}</div> | ||
120 | - <div style="margin-top:6px;">接入协议:${deviceProfile.transportType}</div> | ||
121 | - <div style="margin-top:6px;"> | 133 | + <div style="color:white;">所属组织:${organizationDTO.name}</div> |
134 | + <div style="margin-top:6px;color:white">接入协议:${deviceProfile.transportType}</div> | ||
135 | + <div style="margin-top:6px;color:white"> | ||
122 | 设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address} | 136 | 设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address} |
123 | </div> | 137 | </div> |
124 | - <div style="margin-top:6px;"> | 138 | + <div style="margin-top:6px;color:white"> |
125 | ${ | 139 | ${ |
126 | deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线' | 140 | deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线' |
127 | }时间:${lastUpdateFormatTs} | 141 | }时间:${lastUpdateFormatTs} |
@@ -153,23 +167,23 @@ const handleSearchParams = async (searchPage: any, params: any) => { | @@ -153,23 +167,23 @@ const handleSearchParams = async (searchPage: any, params: any) => { | ||
153 | } | 167 | } |
154 | } | 168 | } |
155 | 169 | ||
156 | -//地图点击 | 170 | +//地图鼠标hover |
157 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { | 171 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { |
158 | markerInstance.setExtData({ | 172 | markerInstance.setExtData({ |
159 | extraInfo: markerItem.extraInfo | 173 | extraInfo: markerItem.extraInfo |
160 | }) | 174 | }) |
161 | - markerInstance.setLabel({ | ||
162 | - content: markerItem.extraInfo.alias || markerItem.extraInfo.name | ||
163 | - }) | ||
164 | - markerInstance.on('click', async (e: any) => { | 175 | + markerInstance.on('mouseover', async (e: any) => { |
165 | const { extraInfo } = e.target.getExtData() | 176 | const { extraInfo } = e.target.getExtData() |
166 | if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗 | 177 | if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗 |
167 | let infoWindow = new AMapIns.InfoWindow({ | 178 | let infoWindow = new AMapIns.InfoWindow({ |
168 | content: await createInfoWindow(extraInfo), | 179 | content: await createInfoWindow(extraInfo), |
169 | - offset: new AMapIns.Pixel(0, -50) | 180 | + offset: new AMapIns.Pixel(3, -30) |
170 | }) | 181 | }) |
171 | infoWindow.open(mapIns, e.target.getPosition()) | 182 | infoWindow.open(mapIns, e.target.getPosition()) |
172 | }) | 183 | }) |
184 | + markerInstance.on('mouseout', () => { | ||
185 | + mapIns.clearInfoWindow() | ||
186 | + }) | ||
173 | } | 187 | } |
174 | 188 | ||
175 | const dataHandle = (newData: dataJsonType) => { | 189 | const dataHandle = (newData: dataJsonType) => { |
@@ -186,7 +200,8 @@ const dataHandle = (newData: dataJsonType) => { | @@ -186,7 +200,8 @@ const dataHandle = (newData: dataJsonType) => { | ||
186 | newData.markers.forEach((markerItem: dataJsonMarkersType) => { | 200 | newData.markers.forEach((markerItem: dataJsonMarkersType) => { |
187 | const markerInstance = new AMapIns.Marker({ | 201 | const markerInstance = new AMapIns.Marker({ |
188 | position: [markerItem.position[0], markerItem.position[1]], | 202 | position: [markerItem.position[0], markerItem.position[1]], |
189 | - offset: new AMapIns.Pixel(-13, -30) | 203 | + offset: new AMapIns.Pixel(-13, -30), |
204 | + icon: typeMarker.value || positionImg | ||
190 | }) | 205 | }) |
191 | // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的 | 206 | // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的 |
192 | // markerInstance.setMap(mapIns) | 207 | // markerInstance.setMap(mapIns) |
@@ -244,6 +259,22 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | @@ -244,6 +259,22 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { | ||
244 | }) | 259 | }) |
245 | </script> | 260 | </script> |
246 | 261 | ||
262 | +<style> | ||
263 | +.amap-info-outer { | ||
264 | + background-color: rgba(0, 0, 0, 0) !important; | ||
265 | +} | ||
266 | +.amap-info-close { | ||
267 | + display: none !important; | ||
268 | +} | ||
269 | +.amap-info-content { | ||
270 | + overflow: hidden !important; | ||
271 | +} | ||
272 | +.amap-info-content .go-border-box { | ||
273 | + position: absolute; | ||
274 | + transform: scale(0.7); | ||
275 | + top: -10px; | ||
276 | +} | ||
277 | +</style> | ||
247 | <style lang="scss" scoped> | 278 | <style lang="scss" scoped> |
248 | .chart-amap { | 279 | .chart-amap { |
249 | position: relative; | 280 | position: relative; |