Commit cbd885743c93d34106820a82f81cc4075cde4dfe

Authored by xp.Huang
2 parents 60f291ee d2c92faf

Merge branch 'fix/firefox-bug/08-13' into 'main_dev'

fix: 修复针对火狐浏览器,随便拖拽一个组件,默认位置都在左上角并遮住的问题。并优化设备分布地图,弹窗可自定义显示内容

See merge request yunteng/thingskit-view!283
... ... @@ -4,7 +4,6 @@ import { OverrideMapAmapConfig } from './index'
4 4 import { chartInitConfig } from '@/settings/designSetting'
5 5 import cloneDeep from 'lodash/cloneDeep'
6 6 import dataJson from './data.json'
7   -import defaultImg from '../../../../../../assets/external/marker/1.png'
8 7
9 8 export type dataExtraInfoType = typeof dataJson.markers[number]['extraInfo'] //data.json下的extraInfo类型
10 9
... ... @@ -137,8 +136,15 @@ export const option = {
137 136 mapMarkerType: MarkerEnum.MARKER,
138 137 viewMode: ViewModeEnum.PLANE,
139 138 lang: LangEnum.ZH_CN,
140   - features: [FeaturesEnum.BG, FeaturesEnum.POINT, FeaturesEnum.ROAD, FeaturesEnum.BUILDING]
141   - }
  139 + features: [FeaturesEnum.BG, FeaturesEnum.POINT, FeaturesEnum.ROAD, FeaturesEnum.BUILDING],
  140 + },
  141 + // 自定义配置项
  142 + dialogCustomField: false, // 弹窗内的label是否自定义
  143 + dialogConfigField: [{
  144 + label:'',
  145 + value:'',
  146 + realValue:''
  147 + }] // 弹窗内配置字段
142 148 }
143 149
144 150 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
1 1 <template>
2 2 <collapse-item name="基础" :expanded="true">
  3 + <setting-item-box name="弹窗label" :alone="true">
  4 + <setting-item>
  5 + <n-switch v-model:value="optionData.dialogCustomField" size="small"></n-switch>
  6 + </setting-item>
  7 + </setting-item-box>
  8 + <setting-item-box name="使用注意" :alone="true">
  9 + <n-gradient-text :size="14" type="success">弹窗字段里面配置的键值需要</n-gradient-text>
  10 + <n-gradient-text :size="14" type="success">和静态数据里的customField</n-gradient-text>
  11 + <n-gradient-text :size="14" type="success">对象里的键相对应</n-gradient-text>
  12 + </setting-item-box>
  13 + <setting-item-box v-if="optionData.dialogCustomField" name="弹窗字段" :alone="true">
  14 + <setting-item>
  15 + <template v-for="(item, index) in optionData.dialogConfigField" :key="index">
  16 + <setting-item-box name="键名" :alone="true">
  17 + <setting-item>
  18 + <n-input-group>
  19 + <n-input v-model:value="item.label" size="small" placeholder="请输入键名"></n-input>
  20 + </n-input-group>
  21 + </setting-item>
  22 + </setting-item-box>
  23 + <setting-item-box name="键值" :alone="true">
  24 + <setting-item>
  25 + <n-input-group>
  26 + <n-input v-model:value="item.value" size="small" placeholder="请输入键值"></n-input>
  27 + </n-input-group>
  28 + </setting-item>
  29 + </setting-item-box>
  30 + <setting-item-box :alone="true">
  31 + <setting-item>
  32 + <n-button size="small" @click="optionData.dialogConfigField.splice(index, 1)"> - </n-button>
  33 + </setting-item>
  34 + </setting-item-box>
  35 + </template>
  36 + <setting-item-box :alone="true">
  37 + <setting-item>
  38 + <n-button
  39 + v-if="optionData.dialogConfigField.length < 9"
  40 + size="small"
  41 + @click="
  42 + optionData.dialogConfigField.push({
  43 + label: '',
  44 + value: '',
  45 + realValue: ''
  46 + })
  47 + "
  48 + >
  49 + +
  50 + </n-button>
  51 + </setting-item>
  52 + </setting-item-box>
  53 + </setting-item>
  54 + </setting-item-box>
3 55 <setting-item-box name="语言类型" :alone="true">
4 56 <setting-item>
5 57 <n-select size="small" v-model:value="optionData.mapOptions.lang" :options="langOptions" />
... ... @@ -284,8 +336,8 @@ const renderOption = (option: SelectOption) => {
284 336
285 337 const selectHandle = (value: string) => {
286 338 iconMarkerValue.value = value
287   - getUrlBase64(getMarkerImagePath(value),'png',(baseEncodeText: string)=>{
288   - props.optionData.mapOptions.iconMarker =baseEncodeText
  339 + getUrlBase64(getMarkerImagePath(value), 'png', (baseEncodeText: string) => {
  340 + props.optionData.mapOptions.iconMarker = baseEncodeText
289 341 })
290 342 }
291 343
... ... @@ -335,7 +387,7 @@ const selectMapBorderHandle = (value: string) => {
335 387 }
336 388
337 389 onMounted(() => {
338   - getUrlBase64(getMarkerImagePath(props.optionData.mapOptions.iconMarker),'png',(baseEncodeText: string)=>{
  390 + getUrlBase64(getMarkerImagePath(props.optionData.mapOptions.iconMarker), 'png', (baseEncodeText: string) => {
339 391 props.optionData.mapOptions.iconMarker = baseEncodeText
340 392 })
341 393 mapSelectBorderValue.value = `${props.optionData.mapOptions.mpBorderConfig.value?.toLocaleLowerCase()}.png`
... ...
1 1 {
2 2 "markers": [
3 3 {
4   - "name": "模拟数据1",
  4 + "name": "模拟数据",
5 5 "value": 20,
6   - "position": [103.856504, 30.687278],
  6 + "position": [
  7 + 103.856504,
  8 + 30.687278
  9 + ],
7 10 "extraInfo": {
8 11 "tbDeviceId": "@xxxxxxxxxxx",
9   - "name": "模拟数据1",
10   - "alias": "模拟数据1",
  12 + "name": "模拟数据",
  13 + "alias": "模拟数据",
11 14 "organizationDTO": {
12   - "name": "模拟数据1"
  15 + "name": "模拟数据"
13 16 },
14 17 "deviceState": "INACTIVE",
15 18 "deviceProfile": {
16 19 "transportType": "MQTT"
17 20 },
18   - "deviceProfileId":"",
  21 + "deviceProfileId": "",
19 22 "deviceInfo": {
20   - "address": "四川省",
  23 + "address": "四川省成都市",
21 24 "longitude": 103.856504,
22 25 "latitude": 30.687278
23   - }
24   - }
25   - },
26   - {
27   - "name": "模拟数据2",
28   - "value": 30,
29   - "position": [104.095368, 30.716787],
30   - "extraInfo": {
31   - "tbDeviceId": "@xxxxxxxxxxxxxxx",
32   - "name": "模拟数据2",
33   - "alias": "模拟数据2",
34   - "organizationDTO": {
35   - "name": "模拟数据2"
36 26 },
37   - "deviceState": "INACTIVE",
38   - "deviceProfile": {
39   - "transportType": "TCP"
40   - },
41   - "deviceProfileId":"",
42   - "deviceInfo": {
43   - "address": "四川省",
44   - "longitude": 104.095368,
45   - "latitude": 30.716787
  27 + "customField": {
  28 + "devicetype": "模拟数据",
  29 + "devicecode": "1211"
46 30 }
47 31 }
48 32 }
49 33 ]
50   -}
  34 +}
\ No newline at end of file
... ...
... ... @@ -27,6 +27,7 @@ import { getDeviceActiveTime } from '@/api/external/common/index'
27 27 import dayjs from 'dayjs'
28 28 import DeviceLatestTable from './components/DeviceLatestTable.vue'
29 29 import { getDeviceLatest, getProfileAttrs } from '@/api/external/common'
  30 +import { isObject } from '@/utils/external/is'
30 31
31 32 const props = defineProps({
32 33 chartConfig: {
... ... @@ -51,11 +52,12 @@ const {
51 52 pitch,
52 53 skyColor,
53 54 marker,
54   - iconMarker,
55 55 mpBorderConfig,
56 56 bgColor
57 57 } = toRefs(props.chartConfig.option.mapOptions)
58 58
  59 +const { dialogCustomField, dialogConfigField } = toRefs(props.chartConfig.option)
  60 +
59 61 //官方没有高德地图api的ts,所以类型全用的any
60 62 let mapIns: any = null
61 63 let markers: any = []
... ... @@ -116,21 +118,48 @@ const devicePartInfo = reactive<devicePartInfoInterface>({
116 118 //创建信息弹窗
117 119 const createInfoWindow = async (extraInfo: dataExtraInfoType) => {
118 120 try {
119   - const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId, deviceProfileId } =
120   - extraInfo
  121 + const {
  122 + name,
  123 + alias,
  124 + organizationDTO,
  125 + deviceState,
  126 + deviceProfile,
  127 + deviceInfo,
  128 + tbDeviceId,
  129 + deviceProfileId,
  130 + customField
  131 + } = extraInfo
  132 + // customField 用于自定义服务端返回的数据
  133 + let realDialogConfigFieldValue = []
  134 + if ('customField' in extraInfo && isObject(customField)) {
  135 + realDialogConfigFieldValue = dialogConfigField.value?.reduce((acc: Recordable[], curr: Recordable) => {
  136 + const realValue = Object.entries(customField)?.map(([label, value]) => ({
  137 + label,
  138 + value
  139 + }))
  140 + const findRealValue = realValue?.find(realItem => realItem.label === curr.value)?.value
  141 + curr['realValue'] = findRealValue
  142 + acc.push(curr)
  143 + return [...acc]
  144 + }, [])
  145 + }
  146 + //
121 147 devicePartInfo.tbDeviceId = tbDeviceId
122 148 devicePartInfo.alias = alias
123 149 devicePartInfo.name = name
124 150 devicePartInfo.deviceProfileId = deviceProfileId
125   - if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止
126 151 let deviceLastUpdateTs = null
127   - const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间
128   - if (!res) {
129   - deviceLastUpdateTs = null
130   - } else {
131   - deviceLastUpdateTs = res[0]['lastUpdateTs']
  152 + try {
  153 + const res = !dialogCustomField.value ? await getDeviceActiveTime(tbDeviceId) : [{ lastUpdateTs: '' }] //查询设备最后离线时间
  154 + if (!res) {
  155 + deviceLastUpdateTs = null
  156 + } else {
  157 + deviceLastUpdateTs = res[0]['lastUpdateTs']
  158 + }
  159 + } catch (e) {
  160 + console.error(`${e}`, '查询设备最后离线时间(tbDeviceId错误)')
132 161 }
133   - const lastUpdateFormatTs = deviceLastUpdateTs ? dayjs(deviceLastUpdateTs).format('YYYY-MM-DD HH:mm:ss'): ''
  162 + const lastUpdateFormatTs = deviceLastUpdateTs ? dayjs(deviceLastUpdateTs).format('YYYY-MM-DD HH:mm:ss') : ''
134 163 //以render方式渲染小组件里的边框组件
135 164 const BorderInstance = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/index.vue`)
136 165 const config = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/config.ts`)
... ... @@ -141,48 +170,66 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => {
141 170 render(h(BorderInstance.default, { chartConfig: BorderConfigInstance }), document.getElementById(id)!)
142 171 }, 100)
143 172 //
144   - const textOverflow = `width:16rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;`
145   - const textOverflowFontBold = `width:12rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;font-size:15px;font-weight:bold;`
146   - const deviceStateContainer = `display:flex;justify-content:space-between;align-items:center;`
147   - const deviceStateImg = `width:1.2rem;height:1.2rem;`
148   - const deviceStateText = `margin-left:0.6rem;font-weight:bold;`
  173 +
  174 + const dialogStyles = {
  175 + textOverflowStyle: `width:15rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;`,
  176 + customFieldBox: `display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;color:white;`,
  177 + defaultFieldHeaderBox: `display:flex;justify-content:space-between;align-items:center;color:white;`,
  178 + defaultFieldHeaderBoldText: `width:12rem;text-overflow:ellipsis;overflow:hidden;word-break:break-all;white-space:nowrap;font-size:15px;font-weight:bold;`,
  179 + defaultFieldBox: `display:flex;flex-direction:column;justify-content:space-between;color:white;margin-top:1rem;gap:0.95rem;`,
  180 + iconSize: `width:1.2rem;height:1.2rem;`,
  181 + textStatus: `margin-left:0.6rem;font-weight:bold;`,
  182 + defaultFieldHeaderRightBox: `display:flex;justify-content:space-between;align-items:center;margin-right:16px`
  183 + }
  184 +
149 185 return `
150   - <div id="${id}" style="width:30rem;">
151   - <div style="display:flex;flex-direction:column;margin:3.5rem 5.5rem 2rem 6.5rem;position:relative;">
152   - <div style="display:flex;justify-content:space-between;align-items:center;color:white;">
153   - <span style="${textOverflowFontBold}">${alias || name}</span>
154   - ${
155   - deviceState === 'INACTIVE'
156   - ? `<div style="${deviceStateContainer}">
157   - <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/>
158   - <img style="${deviceStateImg};margin-left:0.3rem" src="${inactive}"/>
159   - <span style="${deviceStateText}">待激活</span>
160   - </div>`
161   - : deviceState === 'ONLINE'
162   - ? `<div style="${deviceStateContainer}">
163   - <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/>
164   - <img style="${deviceStateImg};margin-left:0.3rem" src="${online}"/>
165   - <span style="${deviceStateText}">在线</span>
166   - </div>`
167   - : `<div style="${deviceStateContainer}">
168   - <img onclick="handleOpenDrawer()" title="最新数据" style="${deviceStateImg};cursor:pointer;" src="${listView}"/>
169   - <img style="${deviceStateImg};margin-left:0.3rem" src="${offline}"/>
170   - <span style="${deviceStateText}">离线</span>
171   - </div>`
172   - }
173   - </div>
174   - <div style="display:flex;flex-direction:column;justify-content:space-between;color:white;margin-top:1rem;gap:0.95rem;">
175   - <div style="${textOverflow}">所属组织:${organizationDTO.name}</div>
176   - <div style="${textOverflow}">接入协议:${deviceProfile.transportType}</div>
177   - <div style="${textOverflow}">设备位置:${
178   - !deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address
179   - }</div>
180   - <div style="${textOverflow}">${
181   - deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线'
182   - }时间:${lastUpdateFormatTs}</div>
  186 + <div id="${id}" style="position: absolute;top: 70px">
  187 + </div>
  188 + ${
  189 + dialogCustomField.value
  190 + ? `
  191 + <div style="position: absolute;top: 135px;left:110px;width:20rem">
  192 + <div style="${dialogStyles.customFieldBox}">
  193 + ${realDialogConfigFieldValue?.map((item: { label: string; value: string; realValue: any }) => {
  194 + const { label, realValue } = item
  195 + return `
  196 + <div style="${dialogStyles.textOverflowStyle}">
  197 + <span >${label}:${realValue}</span>
  198 + </div>
  199 + `
  200 + })}
183 201 </div>
184 202 </div>
185   - </div>
  203 + `
  204 + : `
  205 + <div style="position: absolute;top: 135px;left:110px;width:20rem">
  206 + <div style="${dialogStyles.defaultFieldHeaderBox}">
  207 + <span style="${dialogStyles.defaultFieldHeaderBoldText}">${alias || name}</span>
  208 + <div style="${dialogStyles.defaultFieldHeaderRightBox}">
  209 + <img onclick="handleOpenDrawer()" style="${
  210 + dialogStyles.iconSize
  211 + };cursor:pointer;margin-right:0.5rem" src="${listView}"/>
  212 + <img style="${dialogStyles.iconSize};margin-left:0.5rem" src="${
  213 + deviceState === 'INACTIVE' ? inactive : deviceState === 'ONLINE' ? online : offline
  214 + }"/>
  215 + <span style="${dialogStyles.textStatus}">${
  216 + deviceState === 'INACTIVE' ? '待激活' : deviceState === 'ONLINE' ? '在线' : '离线'
  217 + }</span>
  218 + </div>
  219 + </div>
  220 + <div style="${dialogStyles.defaultFieldBox}">
  221 + <div style="${dialogStyles.textOverflowStyle}">所属组织:${organizationDTO?.name}</div>
  222 + <div style="${dialogStyles.textOverflowStyle}">接入协议:${deviceProfile?.transportType}</div>
  223 + <div style="${dialogStyles.textOverflowStyle}">设备位置:${
  224 + !deviceInfo?.address ? '该设备暂无地理位置' : deviceInfo?.address
  225 + }</div>
  226 + <div style="${dialogStyles.textOverflowStyle}">${
  227 + deviceState === 'ONLINE' ? '在线' : deviceState === 'INACTIVE' ? '创建' : '离线'
  228 + }时间:${lastUpdateFormatTs}</div>
  229 + </div>
  230 + `
  231 + }
  232 +
186 233 `
187 234 } catch (e) {
188 235 console.error(e)
... ... @@ -199,11 +246,11 @@ const handleOpenDrawer = async () => {
199 246 }
200 247
201 248 onMounted(() => {
202   - (window as any).handleOpenDrawer = handleOpenDrawer
  249 + ;(window as any).handleOpenDrawer = handleOpenDrawer
203 250 })
204 251
205 252 onUnmounted(() => {
206   - (window as any).handleOpenDrawer = null
  253 + ;(window as any).handleOpenDrawer = null
207 254 })
208 255
209 256 //地图鼠标hover
... ... @@ -214,10 +261,9 @@ const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => {
214 261 // mouseover
215 262 markerInstance.on('click', async (e: any) => {
216 263 const { extraInfo } = e.target.getExtData()
217   - if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗
218 264 let infoWindow = new AMapIns.InfoWindow({
219   - content: await createInfoWindow(extraInfo),
220   - offset: new AMapIns.Pixel(3, -30)
  265 + content: await createInfoWindow(extraInfo)
  266 + // offset: new AMapIns.Pixel(3, -30)
221 267 })
222 268 infoWindow.open(mapIns, e.target.getPosition())
223 269 })
... ... @@ -238,13 +284,17 @@ const dataHandle = (newData: dataJsonType) => {
238 284 // 记录新标记
239 285 if (mapMarkerType.value === MarkerEnum.MARKER) {
240 286 newData.markers.forEach((markerItem: dataJsonMarkersType) => {
241   - console.log("🚀 ~ newData.markers.forEach ~ markerItem:", markerItem.extraInfo.deviceState)
  287 + console.log('🚀 ~ newData.markers.forEach ~ markerItem:', markerItem.extraInfo.deviceState)
242 288 const markerInstance = new AMapIns.Marker({
243 289 position: [markerItem.position[0], markerItem.position[1]],
244 290 offset: new AMapIns.Pixel(-13, 5),
245 291 icon: new AMapIns.Icon({
246   - // image: iconMarker.value,
247   - image: markerItem.extraInfo.deviceState === 'ONLINE' ? online : markerItem.extraInfo.deviceState === 'INACTIVE' ? inactive : offline,
  292 + image:
  293 + markerItem.extraInfo.deviceState === 'ONLINE'
  294 + ? online
  295 + : markerItem.extraInfo.deviceState === 'INACTIVE'
  296 + ? inactive
  297 + : offline,
248 298 size: new AMapIns.Size(35, 35), //图标所处区域大小
249 299 imageSize: new AMapIns.Size(35, 35) //图标大小
250 300 })
... ... @@ -324,7 +374,10 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
324 374 }
325 375
326 376 .amap-info-content {
  377 + width: 30rem;
  378 + height: 20rem;
327 379 overflow: hidden !important;
  380 + position: relative;
328 381 }
329 382
330 383 /**去除高德地图原生弹窗*/
... ...
... ... @@ -9,6 +9,7 @@ const tryRunFunction = (v: string) => {
9 9 }
10 10
11 11 export const JSONParse = (data: string) => {
  12 + if (!data) return
12 13 return JSON.parse(data, (k, v) => {
13 14 // 过滤函数字符串
14 15 if (excludeParseEventKeyList.includes(k)) return v
... ...
... ... @@ -39,8 +39,13 @@ export const dragHandle = async (e: DragEvent) => {
39 39 newComponent.chartConfig.title = dropData.title
40 40 newComponent.chartConfig.chartFrame = dropData.chartFrame
41 41 }
42   -
43   - setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
  42 + /**
  43 + * 修复火狐浏览器随便拖拽一个组件,默认位置都在左上角并遮住。
  44 + * 原因是浏览器兼容问题,火狐浏览器的offsetX和offsetY始终为0值,而应该使用layerX和layerY,其余浏览器使用offsetX和offsetY即可。
  45 + */
  46 + // 源码 setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
  47 + setComponentPosition(newComponent, (e.offsetX === 0 ? (e as any).layerX: e.offsetX) - newComponent.attr.w / 2,(e.offsetY === 0 ? (e as any).layerY: e.offsetY) - newComponent.attr.h / 2)
  48 + //
44 49 chartEditStore.addComponentList(newComponent, false, true)
45 50 chartEditStore.setTargetSelectChart(newComponent.id)
46 51 loadingFinish()
... ...
... ... @@ -14,6 +14,7 @@ const isSharePageMode = () => {
14 14
15 15 // THINGS_KIT 兼容之前的用户数据
16 16 const compatibleOldUserDataHook = (fileData:object)=>{
  17 + if(!fileData) return
17 18 let userData:Recordable = fileData
18 19 if(!Reflect.get(fileData,'pageConfig')){
19 20 userData={
... ...