Commit 6883e1f74944b79df8b2ba718b321d6922c2b9e7

Authored by xp.Huang
2 parents 7b80d266 cb6dbbed

Merge branch 'ft' into 'main_dev'

feat(src/packages): 设备分布新增点击弹出抽屉展示设备最新属性数据值

See merge request yunteng/thingskit-view!138
... ... @@ -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

  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,
... ...
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) => {
... ...