| ... | ... | @@ -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 | /**去除高德地图原生弹窗*/ | 
... | ... |  |