Commit 150cdf23b4bea17f5b1af6e4581693e7c1e85708

Authored by fengwotao
1 parent d73251e3

feat(src/packages): 优化高德地图标点弹窗和数据展示

@@ -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>
@@ -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;