Commit 6883e1f74944b79df8b2ba718b321d6922c2b9e7
Merge branch 'ft' into 'main_dev'
feat(src/packages): 设备分布新增点击弹出抽屉展示设备最新属性数据值 See merge request yunteng/thingskit-view!138
Showing
12 changed files
with
177 additions
and
43 deletions
| ... | ... | @@ -16,7 +16,9 @@ enum Api { |
| 16 | 16 | VIDEOURL = '/video/url/', |
| 17 | 17 | GEOJSONURL = '/map/geo_json/', |
| 18 | 18 | DEVICE_URL = '/device', |
| 19 | - GET_ATTRBUTELIST = '/device/attributes/' | |
| 19 | + GET_ATTRBUTELIST = '/device/attributes/', | |
| 20 | + GET_DEVICE_LATEST = '/plugins/telemetry/DEVICE/', | |
| 21 | + DEVICE_ATTR = '/device/attributes', | |
| 20 | 22 | } |
| 21 | 23 | |
| 22 | 24 | export const getDictItemByCode = (value: string) => { |
| ... | ... | @@ -127,3 +129,20 @@ export const getAttribute = (deviceProfileId: string) => |
| 127 | 129 | defHttp.get({ |
| 128 | 130 | url: `${Api.GET_ATTRBUTELIST}${deviceProfileId}` |
| 129 | 131 | }) |
| 132 | + | |
| 133 | +// 获取设备最新数据 | |
| 134 | +export const getDeviceLatest = (tbDeviceId: string) => | |
| 135 | +defHttp.get({ | |
| 136 | + url: `${Api.GET_DEVICE_LATEST}${tbDeviceId}/values/timeseries` | |
| 137 | +}, { | |
| 138 | + joinPrefix: false | |
| 139 | +}) | |
| 140 | + | |
| 141 | +//获取产品属性 | |
| 142 | +export const getProfileAttrs = (params: { deviceProfileId: string; dataType?: string }) => { | |
| 143 | + const { deviceProfileId, dataType } = params; | |
| 144 | + return defHttp.get({ | |
| 145 | + url: `${Api.DEVICE_ATTR}/${deviceProfileId}`, | |
| 146 | + params: { dataType }, | |
| 147 | + }); | |
| 148 | +}; | |
| \ No newline at end of file | ... | ... |
src/assets/external/marker/1.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/1.png
1.24 KB
src/assets/external/marker/2.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/2.png
1.4 KB
src/assets/external/marker/3.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/3.png
904 Bytes
src/assets/external/marker/4.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/4.png
1.02 KB
src/assets/external/marker/5.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/5.png
970 Bytes
src/packages/components/external/Charts/Maps/OverrideMapAmap/components/DeviceLatestTable.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <n-drawer display-directive="if" v-model:show="modelShow" :width="502" :placement="placement"> | |
| 3 | + <n-drawer-content :title="drawerTitle" closable> | |
| 4 | + <n-space vertical> | |
| 5 | + <n-data-table size="small" :columns="dimensions" :data="source" :pagination="paginationReactive" /> | |
| 6 | + </n-space> | |
| 7 | + </n-drawer-content> | |
| 8 | + </n-drawer> | |
| 9 | +</template> | |
| 10 | + | |
| 11 | +<script lang="ts" setup> | |
| 12 | +import { ref, reactive } from 'vue' | |
| 13 | +import { NDataTable,NDrawer, NDrawerContent, NSpace } from 'naive-ui' | |
| 14 | +import { dimensions } from '../config' | |
| 15 | +import type { sourceInterface } from '../config' | |
| 16 | +import dayjs from 'dayjs' | |
| 17 | +import { Placement } from 'naive-ui/es/drawer/src/DrawerBodyWrapper' | |
| 18 | + | |
| 19 | +const placement = ref<Placement>('right') | |
| 20 | + | |
| 21 | +const modelShow =ref(false) | |
| 22 | + | |
| 23 | +const drawerTitle = ref('') | |
| 24 | + | |
| 25 | +const source =ref<sourceInterface[]>([]) | |
| 26 | + | |
| 27 | +const paginationReactive = reactive({ | |
| 28 | + page: 1, | |
| 29 | + pageSize: 10, | |
| 30 | + showSizePicker: true, | |
| 31 | + pageSizes: [10, 20], | |
| 32 | + onChange: (page: number) => { | |
| 33 | + paginationReactive.page = page | |
| 34 | + }, | |
| 35 | + onUpdatePageSize: (pageSize: number) => { | |
| 36 | + paginationReactive.pageSize = pageSize | |
| 37 | + paginationReactive.page = 1 | |
| 38 | + } | |
| 39 | + }) | |
| 40 | + | |
| 41 | +const setValue = (value: Recordable, attrs: Recordable[]) => { | |
| 42 | + source.value = [] | |
| 43 | + try { | |
| 44 | + if (!value) return | |
| 45 | + const deviceLatestList = Object.keys(value).map((item:string) => { | |
| 46 | + return { | |
| 47 | + key: item, | |
| 48 | + value: value[item][0]?.value, | |
| 49 | + lastUpdateTime: dayjs( value[item][0]?.ts)?.format('YYYY-MM-DD HH:mm:ss') | |
| 50 | + } | |
| 51 | + }) | |
| 52 | + const list = deviceLatestList?.reduce((acc:sourceInterface[],curr: sourceInterface)=>{ | |
| 53 | + const byKeyFindName = attrs?.find(item => item.identifier === curr.key)?.name | |
| 54 | + curr.key = byKeyFindName | |
| 55 | + acc.push({...curr}) | |
| 56 | + return [...acc] | |
| 57 | + },[]) | |
| 58 | + source.value = list.filter(item=>Boolean(item.key)) | |
| 59 | + }catch(e){ | |
| 60 | + console.log(e) | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | |
| 64 | +const setDrawerTitle = (title:string) => drawerTitle.value = title | |
| 65 | + | |
| 66 | +const openDrawer = () => modelShow.value = true | |
| 67 | + | |
| 68 | +defineExpose({ | |
| 69 | + setValue, | |
| 70 | + openDrawer, | |
| 71 | + setDrawerTitle | |
| 72 | +}) | |
| 73 | +</script> | |
| 74 | + | |
| 75 | +<style lang="scss" scoped></style> | ... | ... |
| ... | ... | @@ -4,7 +4,7 @@ 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 './images/marker/1.png' | |
| 7 | +import defaultImg from '../../../../../../assets/external/marker/1.png' | |
| 8 | 8 | |
| 9 | 9 | export type dataExtraInfoType = typeof dataJson.markers[number]['extraInfo'] //data.json下的extraInfo类型 |
| 10 | 10 | |
| ... | ... | @@ -26,6 +26,7 @@ export const filterDevice = (items: any) => { |
| 26 | 26 | organizationDTO: curr.organizationDTO, |
| 27 | 27 | deviceState: curr.deviceState, |
| 28 | 28 | deviceProfile: curr.deviceProfile, |
| 29 | + deviceProfileId: curr.deviceProfileId, | |
| 29 | 30 | deviceInfo: curr.deviceInfo |
| 30 | 31 | } |
| 31 | 32 | }) |
| ... | ... | @@ -36,6 +37,34 @@ export const filterDevice = (items: any) => { |
| 36 | 37 | } |
| 37 | 38 | } |
| 38 | 39 | |
| 40 | +export interface devicePartInfoInterface{ | |
| 41 | + tbDeviceId:string | |
| 42 | + name:string | |
| 43 | + alias:string | |
| 44 | + deviceProfileId:string | |
| 45 | +} | |
| 46 | + | |
| 47 | +export const dimensions= [ | |
| 48 | + { | |
| 49 | + "title": "键", | |
| 50 | + "key": "key" | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + "title": "值", | |
| 54 | + "key": "value" | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + "title": "最后更新时间", | |
| 58 | + "key": "lastUpdateTime" | |
| 59 | + } | |
| 60 | +] | |
| 61 | + | |
| 62 | +export interface sourceInterface { | |
| 63 | + key: string | |
| 64 | + value: string | |
| 65 | + lastUpdateTime: string | |
| 66 | +} | |
| 67 | + | |
| 39 | 68 | export enum ThemeEnum { |
| 40 | 69 | NORMAL = 'normal', |
| 41 | 70 | DARK = 'dark', | ... | ... |
| ... | ... | @@ -254,7 +254,7 @@ const isHref = (url: string) => { |
| 254 | 254 | |
| 255 | 255 | // import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 |
| 256 | 256 | const iconMarkerOptions = computed(() => { |
| 257 | - const pathList = import.meta.glob('./images/marker/*') | |
| 257 | + const pathList = import.meta.glob('../../../../../../assets/external/marker/*') | |
| 258 | 258 | return Object.keys(pathList).map(item => { |
| 259 | 259 | const imgName = item.split('/').at(-1) |
| 260 | 260 | return { |
| ... | ... | @@ -266,7 +266,7 @@ const iconMarkerOptions = computed(() => { |
| 266 | 266 | // |
| 267 | 267 | |
| 268 | 268 | const getMarkerImagePath = (name: string) => { |
| 269 | - return isHref(name) ? name : new URL(`./images/marker/${name}`, import.meta.url).href | |
| 269 | + return isHref(name) ? name : new URL(`../../../../../../assets/external/marker/${name}`, import.meta.url).href | |
| 270 | 270 | } |
| 271 | 271 | |
| 272 | 272 | const renderOption = (option: SelectOption) => { |
| ... | ... | @@ -334,6 +334,7 @@ const selectMapBorderHandle = (value: string) => { |
| 334 | 334 | onMounted(() => { |
| 335 | 335 | iconMarkerValue.value = props.optionData.mapOptions.iconMarker?.split('/')?.at(-1) as string |
| 336 | 336 | props.optionData.mapOptions.iconMarker = getMarkerImagePath(iconMarkerValue.value) |
| 337 | + console.log(getMarkerImagePath(iconMarkerValue.value)); | |
| 337 | 338 | mapSelectBorderValue.value = `${props.optionData.mapOptions.mpBorderConfig.value?.toLocaleLowerCase()}.png` |
| 338 | 339 | }) |
| 339 | 340 | </script> | ... | ... |
| ... | ... | @@ -15,6 +15,7 @@ |
| 15 | 15 | "deviceProfile": { |
| 16 | 16 | "transportType": "MQTT" |
| 17 | 17 | }, |
| 18 | + "deviceProfileId":"", | |
| 18 | 19 | "deviceInfo": { |
| 19 | 20 | "address": "四川省", |
| 20 | 21 | "longitude": 103.856504, |
| ... | ... | @@ -37,6 +38,7 @@ |
| 37 | 38 | "deviceProfile": { |
| 38 | 39 | "transportType": "TCP" |
| 39 | 40 | }, |
| 41 | + "deviceProfileId":"", | |
| 40 | 42 | "deviceInfo": { |
| 41 | 43 | "address": "四川省", |
| 42 | 44 | "longitude": 104.095368, | ... | ... |
404 Bytes
| 1 | 1 | <template> |
| 2 | - <div @mouseenter="handleMouseenter" @mouseleave="handleMouseleave" class="chart-amap" ref="vChartRef"> | |
| 3 | - <!-- <div v-show="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div> | |
| 4 | - <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box> --> | |
| 2 | + <div class="chart-amap" ref="vChartRef"> | |
| 3 | + <device-latest-table ref="deviceLatestTableRef"></device-latest-table> | |
| 5 | 4 | </div> |
| 6 | 5 | </template> |
| 7 | 6 | |
| 8 | 7 | <script setup lang="ts"> |
| 9 | -import { ref, PropType, toRefs, watch, render, h } from 'vue' | |
| 8 | +import { ref, PropType, toRefs, watch, render, h, onMounted, onUnmounted, reactive} from 'vue' | |
| 10 | 9 | import AMapLoader from '@amap/amap-jsapi-loader' |
| 11 | 10 | import { CreateComponentType } from '@/packages/index.d' |
| 12 | 11 | import { useChartDataFetch } from '@/hooks' |
| 13 | 12 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 14 | -import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType } from './config' | |
| 13 | +import { MarkerEnum, ThemeEnum, dataExtraInfoType, dataJsonType, dataJsonMarkersType, devicePartInfoInterface } from './config' | |
| 15 | 14 | import { isArray } from '@/utils' |
| 16 | 15 | import djh from './images/djh.png' |
| 17 | 16 | import online from './images/online.png' |
| 18 | 17 | import lx1 from './images/lx1.png' |
| 19 | -import onLineImg from './images/marker/3.png' | |
| 18 | +import onLineImg from '../../../../../../assets/external/marker/3.png' | |
| 19 | +import listImg from './images/list.png' | |
| 20 | 20 | import { getDeviceActiveTime } from '@/api/external/common/index' |
| 21 | 21 | import dayjs from 'dayjs' |
| 22 | -// import SearchBox from './components/SearchBox.vue' | |
| 22 | +import DeviceLatestTable from './components/DeviceLatestTable.vue' | |
| 23 | +import { getDeviceLatest, getProfileAttrs } from '@/api/external/common' | |
| 23 | 24 | |
| 24 | 25 | const props = defineProps({ |
| 25 | 26 | chartConfig: { |
| ... | ... | @@ -28,9 +29,7 @@ const props = defineProps({ |
| 28 | 29 | } |
| 29 | 30 | }) |
| 30 | 31 | |
| 31 | -// const modelShow = ref(false) | |
| 32 | - | |
| 33 | -const showSearchBox = ref(false) | |
| 32 | +const deviceLatestTableRef = ref<Nullable<InstanceType<typeof DeviceLatestTable>>>(null); | |
| 34 | 33 | |
| 35 | 34 | const { |
| 36 | 35 | amapKey, |
| ... | ... | @@ -88,7 +87,7 @@ const initMap = (newData: any) => { |
| 88 | 87 | `amap://styles/${amapStyleKeyCustom.value !== '' ? amapStyleKeyCustom.value : amapStyleKey.value}` |
| 89 | 88 | ) |
| 90 | 89 | } |
| 91 | - mapIns.on('mouseout', () => { | |
| 90 | + mapIns.on('click', () => { | |
| 92 | 91 | mapIns.clearInfoWindow() |
| 93 | 92 | }) |
| 94 | 93 | }) |
| ... | ... | @@ -97,10 +96,23 @@ const initMap = (newData: any) => { |
| 97 | 96 | }) |
| 98 | 97 | } |
| 99 | 98 | |
| 99 | + | |
| 100 | +//携带设备的tbDeviceId和别名或者名称 | |
| 101 | +const devicePartInfo = reactive<devicePartInfoInterface>({ | |
| 102 | + tbDeviceId:'', | |
| 103 | + name:'', | |
| 104 | + alias:'', | |
| 105 | + deviceProfileId:'', | |
| 106 | +}) | |
| 107 | + | |
| 100 | 108 | //创建信息弹窗 |
| 101 | 109 | const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 102 | 110 | try { |
| 103 | - const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId } = extraInfo | |
| 111 | + const { name, alias, organizationDTO, deviceState, deviceProfile, deviceInfo, tbDeviceId, deviceProfileId } = extraInfo | |
| 112 | + devicePartInfo.tbDeviceId = tbDeviceId | |
| 113 | + devicePartInfo.alias = alias | |
| 114 | + devicePartInfo.name = name | |
| 115 | + devicePartInfo.deviceProfileId = deviceProfileId | |
| 104 | 116 | if (tbDeviceId.startsWith('@')) return //假的模拟数据则终止 |
| 105 | 117 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 |
| 106 | 118 | let { lastUpdateTs } = res[0] |
| ... | ... | @@ -142,7 +154,10 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 142 | 154 | </div>` |
| 143 | 155 | } |
| 144 | 156 | </div> |
| 145 | - <div style="display:flex;flex-direction:column;justify-content:space-between;color:white;margin-top:2rem;gap:0.8rem;"> | |
| 157 | + <div style="display:flex;justify-content:flex-end;cursor:pointer;" onclick="handleOpenDrawer()"> | |
| 158 | + <img style="${deviceStateImg};position:relative;top:0.7rem" src="${listImg}"/> | |
| 159 | + </div> | |
| 160 | + <div style="display:flex;flex-direction:column;justify-content:space-between;color:white;margin-top:1rem;gap:0.8rem;"> | |
| 146 | 161 | <div style="${textOverflow}">所属组织:${organizationDTO.name}</div> |
| 147 | 162 | <div style="${textOverflow}">接入协议:${deviceProfile.transportType}</div> |
| 148 | 163 | <div style="${textOverflow}">设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address}</div> |
| ... | ... | @@ -156,37 +171,30 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 156 | 171 | } |
| 157 | 172 | } |
| 158 | 173 | |
| 159 | -const handleMouseenter = () => (showSearchBox.value = true) | |
| 160 | - | |
| 161 | -const handleMouseleave = () => (showSearchBox.value = false) | |
| 162 | - | |
| 163 | -// const handleOpenSearchBox = () => (modelShow.value = true) | |
| 174 | +const handleOpenDrawer = async () => { | |
| 175 | + deviceLatestTableRef.value?.openDrawer() | |
| 176 | + deviceLatestTableRef.value?.setDrawerTitle(devicePartInfo.alias || devicePartInfo.name) | |
| 177 | + if (!devicePartInfo.tbDeviceId) return | |
| 178 | + const resp = await getDeviceLatest(devicePartInfo.tbDeviceId) | |
| 179 | + const respGetAttrs = await getProfileAttrs({deviceProfileId: devicePartInfo.deviceProfileId}) | |
| 180 | + deviceLatestTableRef.value?.setValue(resp,respGetAttrs) | |
| 181 | +} | |
| 164 | 182 | |
| 165 | -// const handleCloseDrawer = () => (modelShow.value = false) | |
| 183 | +onMounted(() => { | |
| 184 | + (window as any).handleOpenDrawer = handleOpenDrawer; | |
| 185 | +}); | |
| 166 | 186 | |
| 167 | -// const handleSearchParams = async (searchPage: any, params: any) => { | |
| 168 | -// try { | |
| 169 | -// Object.keys(params).forEach(item => { | |
| 170 | -// if (!params[item]) Reflect.deleteProperty(params, item) | |
| 171 | -// }) | |
| 172 | -// const { items } = await getDeviceList(searchPage, params) | |
| 173 | -// const values = filterDevice(items) | |
| 174 | -// if (!values) return | |
| 175 | -// setTimeout(() => { | |
| 176 | -// dataHandle(values) | |
| 177 | -// }, 1000) | |
| 178 | -// } finally { | |
| 179 | -// handleCloseDrawer() | |
| 180 | -// } | |
| 181 | -// } | |
| 187 | +onUnmounted(() => { | |
| 188 | + (window as any).handleOpenDrawer = null; | |
| 189 | +}); | |
| 182 | 190 | |
| 183 | 191 | //地图鼠标hover |
| 184 | 192 | const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { |
| 185 | 193 | markerInstance.setExtData({ |
| 186 | 194 | extraInfo: markerItem.extraInfo |
| 187 | 195 | }) |
| 188 | - // click | |
| 189 | - markerInstance.on('mouseover', async (e: any) => { | |
| 196 | + // mouseover | |
| 197 | + markerInstance.on('click', async (e: any) => { | |
| 190 | 198 | const { extraInfo } = e.target.getExtData() |
| 191 | 199 | if (extraInfo.tbDeviceId.startsWith('@')) return //假的模拟数据则终止弹窗 |
| 192 | 200 | let infoWindow = new AMapIns.InfoWindow({ |
| ... | ... | @@ -195,9 +203,9 @@ const mapClick = (markerInstance: any, markerItem: dataJsonMarkersType) => { |
| 195 | 203 | }) |
| 196 | 204 | infoWindow.open(mapIns, e.target.getPosition()) |
| 197 | 205 | }) |
| 198 | - markerInstance.on('mouseout', () => { | |
| 199 | - mapIns.clearInfoWindow() | |
| 200 | - }) | |
| 206 | + // markerInstance.on('click', () => { | |
| 207 | + // mapIns.clearInfoWindow() | |
| 208 | + // }) | |
| 201 | 209 | } |
| 202 | 210 | |
| 203 | 211 | const dataHandle = (newData: dataJsonType) => { | ... | ... |