Browse Source

feat: 在线设备根据站点区分

main
betaqi 4 weeks ago
parent
commit
8210c143e8
  1. 2
      global.types/components.d.ts
  2. 109
      src/stores/transferData.ts
  3. 151
      src/views/stationData/index.vue
  4. 2
      src/views/stationData/transfer/index.vue

2
global.types/components.d.ts vendored

@ -34,6 +34,8 @@ declare module 'vue' {
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']

109
src/stores/transferData.ts

@ -1,10 +1,16 @@
import { ref, computed } from 'vue' import { ref, computed, reactive } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { IOnlineDevice, IUpFirmwareStatus } from '@/views/stationData/type' import type { IOnlineDevice, IUpFirmwareStatus } from '@/views/stationData/type'
import ZMQWorker from '@/composables/useZMQJsonWorker' import ZMQWorker from '@/composables/useZMQJsonWorker'
import { getSubTopic, type SubMsgData } from '@/utils/zmq' import { getSubTopic, type SubMsgData } from '@/utils/zmq'
import { getDeviceTopic } from '@/views/stationData/utils' import { getDeviceTopic } from '@/views/stationData/utils'
import dayjs from "dayjs";
export interface SiteInfo {
id: string
onlineCount: number
offlineCount: number
devices: Map<string, IOnlineDevice>
}
export const useTransferDataStore = defineStore('transfer', () => { export const useTransferDataStore = defineStore('transfer', () => {
const subDevices = getSubTopic('client', 'status', 'transfer') const subDevices = getSubTopic('client', 'status', 'transfer')
@ -13,20 +19,52 @@ export const useTransferDataStore = defineStore('transfer', () => {
const connectSite = ref<any>(null) const connectSite = ref<any>(null)
async function initConnectSite() { const checkDeviceStatusInterval = ref<NodeJS.Timeout>()
if (connectSite.value) return
connectSite.value = { const siteMap = reactive(new Map<string, SiteInfo>())
title: '未命名站点',
edit: true,
editTitle: '未命名站点',
}
}
const devicesMap = reactive(new Map<string, IOnlineDevice>()) const devicesMap = reactive(new Map<string, IOnlineDevice>())
const checkDeviceStatusInterval = ref<NodeJS.Timeout>()
function checkDeviceStatus() { // =========== mock =================
checkDeviceStatusInterval.value = setInterval(checkDeviceStatusFn, 100); // let i = 0
//
// function mockSubDeviceMsg() {
// // 随机生成 SN
// const sn = `SN-${Math.floor(Math.random() * 1000)}`
// // const siteId = `${i % 2 === 0 ? 'site1' : 'site2'}`
// const siteId = `siteId-${Math.floor(Math.random() * 1000)}`
// const clientIp = `192.168.0.${Math.floor(Math.random() * 255)}`
// const version = `v${(Math.random() * 2 + 1).toFixed(2)}`
// const footprint = (Math.random() * 50000).toFixed(2) // KB
//
// // const sn = 123
// // const siteId = 123123
// // const clientIp = `192.168.0.${Math.floor(Math.random() * 255)}`
// // const version = `v${(Math.random() * 2 + 1).toFixed(2)}`
// // const footprint = (Math.random() * 50000).toFixed(2) // KB
//
// const msg: any = {
// feedback: [clientIp, sn, siteId, version, footprint],
// }
//
// getSubDevicesCb(msg)
// }
//
//
// let a = setInterval(() => {
// i++
// mockSubDeviceMsg()
// }, 1000)
// setTimeout(() => {
// clearInterval(a)
// }, 8000)
// =========== mock end =================
function startCheckStatus() {
clearInterval(checkDeviceStatusInterval.value)
checkDeviceStatusInterval.value = setInterval(checkDeviceStatusFn, 1000);
} }
const checkDeviceStatusFn = () => { const checkDeviceStatusFn = () => {
@ -37,6 +75,7 @@ export const useTransferDataStore = defineStore('transfer', () => {
device.status = '离线'; device.status = '离线';
} }
}); });
reassignDevicesToSites()
} }
function formatSizeFromKB(num: number): string { function formatSizeFromKB(num: number): string {
@ -77,8 +116,34 @@ export const useTransferDataStore = defineStore('transfer', () => {
} }
devicesMap.set(sn, device) devicesMap.set(sn, device)
} }
}
function reassignDevicesToSites() {
devicesMap.forEach((device) => {
const siteId = device.site_id
if (!siteId) return
// 若该站点还未创建,则初始化
if (!siteMap.has(siteId)) {
siteMap.set(siteId, {
id: siteId,
onlineCount: 0,
offlineCount: 0,
devices: reactive(new Map<string, IOnlineDevice>()),
})
}
// 获取站点信息并更新
const siteInfo = siteMap.get(siteId)!
siteInfo.devices.set(device.sn, device)
})
isConnected.value = devicesMap.size > 0 // 更新site 在线数量和离线数量
siteMap.forEach((siteInfo) => {
const devices = Array.from(siteInfo.devices.values())
siteInfo.onlineCount = devices.filter(item => item.status === '在线').length
siteInfo.offlineCount = devices.filter(item => item.status === '离线').length
})
} }
const onlineCount = computed(() => { const onlineCount = computed(() => {
@ -93,8 +158,8 @@ export const useTransferDataStore = defineStore('transfer', () => {
onMounted(() => { onMounted(() => {
worker.subscribe(getDeviceTopic, getSubDevicesCb) worker.subscribe(getDeviceTopic, getSubDevicesCb)
checkDeviceStatus() startCheckStatus()
document.addEventListener('visibilitychange', checkDeviceStatusFn) document.addEventListener('visibilitychange', handleVisibilityChange)
}) })
const route = useRoute() const route = useRoute()
@ -102,11 +167,18 @@ export const useTransferDataStore = defineStore('transfer', () => {
watch(() => route.path, (val) => { watch(() => route.path, (val) => {
if (!['/station/data-transfer', '/station'].includes(val)) { if (!['/station/data-transfer', '/station'].includes(val)) {
clearInterval(checkDeviceStatusInterval.value) clearInterval(checkDeviceStatusInterval.value)
document.removeEventListener('visibilitychange', checkDeviceStatusFn) document.removeEventListener('visibilitychange', handleVisibilityChange)
worker.unsubscribe(subDevices) worker.unsubscribe(subDevices)
} }
}) })
function handleVisibilityChange() {
if (document.hidden) {
clearInterval(checkDeviceStatusInterval.value)
} else {
startCheckStatus()
}
}
function upFirmwareStatus(sn: string, feedback: any[]) { function upFirmwareStatus(sn: string, feedback: any[]) {
const device = devicesMap.get(sn) const device = devicesMap.get(sn)
@ -181,14 +253,13 @@ export const useTransferDataStore = defineStore('transfer', () => {
return { return {
siteMap,
isConnected, isConnected,
devicesMap, devicesMap,
connectSite, connectSite,
checkDeviceStatusInterval, checkDeviceStatusInterval,
onlineCount, onlineCount,
offlineCount, offlineCount,
checkDeviceStatus,
initConnectSite,
upFirmwarePending, upFirmwarePending,
upFirmwareReset, upFirmwareReset,
upFirmwareStatus, upFirmwareStatus,

151
src/views/stationData/index.vue

@ -1,97 +1,108 @@
<template> <template>
<div class="flex-col gap-16 wh-full"> <div class="flex-col gap-16 wh-full">
<template v-if="env.VITE_APP_ENV == 'local'"> <template v-if="env.VITE_APP_ENV == 'local'">
<EdfsWrap title="当前连接站点" v-if="connectSite?.title" shape="circle" shapeColor="#4B9E5F" <EdfsWrap title="当前连接站点" shape="circle" shapeColor="#4B9E5F"
class="h-auto"> class="h-auto max-h[462px]">
<div class="station-list-flow"> <el-scrollbar>
<div class="station-item"> <div class="station-list-flow">
<div class="title bg-[#4B9E5F]"> <div class="wh-full flex-center items-center"
<div>{{ connectSite.title }}</div> v-if="!Array.from(siteMap.values()).length">
<el-empty style="height: 180px;" :image-size="130"/>
<!-- :description=""-->
</div> </div>
<div class="body"> <template v-else v-for=" site in Array.from(siteMap.values())">
<div class="info"> <div class="station-item">
<div class="info-item"> <div class="title bg-[#4B9E5F]">
<div class="info-item-label">在线设备</div> <div>{{ site.id }}</div>
<div class="info-item-value"> </div>
<div class="i-octicon:cloud-16 color-[#4B9E5F] text-16px"></div> <div class="body">
{{ onlineCount }} <div class="info">
<div class="info-item">
<div class="info-item-label">在线设备</div>
<div class="info-item-value">
<div class="i-octicon:cloud-16 color-[#4B9E5F] text-16px"></div>
{{ site.onlineCount }}
</div>
</div>
<div class="info-item">
<div class="info-item-label">离线设备</div>
<div class="info-item-value">
<div class="i-octicon:cloud-offline-16 color-[#F44336] text-16px"></div>
{{ site.offlineCount }}
</div>
</div>
</div> </div>
</div> </div>
<div class="info-item"> <div class="footer row-end-0">
<div class="info-item-label">离线设备</div> <div class="m-l-auto p-b-8">
<div class="info-item-value"> <el-button type="primary" style="height: 28px; padding: 0 12px" color="#4B9E5F"
<div class="i-octicon:cloud-offline-16 color-[#F44336] text-16px"></div> @click="onTransferData(site)">设备详情
{{ offlineCount }} </el-button>
</div> </div>
</div> </div>
</div> </div>
</div> </template>
<div class="footer row-end-0">
<div class="m-l-auto p-b-8">
<el-button type="primary" style="height: 28px; padding: 0 12px" color="#4B9E5F"
@click="onTransferData">设备详情
</el-button>
</div>
</div>
</div> </div>
</div> </el-scrollbar>
</EdfsWrap> </EdfsWrap>
</template> </template>
<EdfsWrap :title="`${env.VITE_APP_ENV == 'local' ? '数据迁移历史' : '数据导入历史'}`" <EdfsWrap :title="`${env.VITE_APP_ENV == 'local' ? '数据迁移历史' : '数据导入历史'}`"
shape="circle" shapeColor="#F1BF63" shape="circle" shapeColor="#F1BF63"
class="flex-1" useScrollBar> class="flex-1" useScrollBar>
<div class="station-list-flow"> <el-scrollbar>
<div class="station-item" v-for="item in siteList" :key="item.id"> <div class="station-list-flow">
<div class="title bg-[#F1BF63]"> <div class="station-item" v-for="item in siteList" :key="item.id">
{{ item.name }} <div class="title bg-[#F1BF63]">
<div class="flex items-center gap-col-2"> {{ item.name }}
<el-tooltip content="详情"> <div class="flex items-center gap-col-2">
<div <el-tooltip content="详情">
class="i-material-symbols:info-outline :hover:color-[#ddd] color-[#FFFFFF] cursor-pointer text-20px" <div
@click="onSiteDetails(item)"></div> class="i-material-symbols:info-outline :hover:color-[#ddd] color-[#FFFFFF] cursor-pointer text-20px"
</el-tooltip> @click="onSiteDetails(item)"></div>
</el-tooltip>
</div>
</div> </div>
</div> <template v-if="env.VITE_APP_ENV == 'local'">
<template v-if="env.VITE_APP_ENV == 'local'"> <div class="body">
<div class="body"> <div class="info">
<div class="info"> <div class="info-item">
<div class="info-item"> <div class="info-item-label">导出路径</div>
<div class="info-item-label">导出路径</div> <el-tooltip :content="item.export_root_path">
<el-tooltip :content="item.export_root_path"> <div class="info-item-path">{{ item.export_root_path }}</div>
<div class="info-item-path">{{ item.export_root_path }}</div> </el-tooltip>
</el-tooltip> </div>
</div> </div>
</div> </div>
</div> <div class="footer">
<div class="footer"> <div class="item">
<div class="item"> <div class="label">迁移时间:</div>
<div class="label">迁移时间:</div> <div class="value">
<div class="value"> {{ item.create_time }}
{{ item.create_time }} </div>
</div> </div>
</div> </div>
</div> </template>
</template> <template v-else>
<template v-else> <div class="body">
<div class="body"> <div class="info">
<div class="info"> <div class="info-item">
<div class="info-item"> <div class="info-item-label">创建时间</div>
<div class="info-item-label">创建时间</div> <div class="info-item-value">{{ item.create_time }}</div>
<div class="info-item-value">{{ item.create_time }}</div> </div>
</div> </div>
</div> </div>
</div> </template>
</template> </div>
</div> </div>
</div> </el-scrollbar>
</EdfsWrap> </EdfsWrap>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useTransferDataStore } from '@/stores/transferData' import { type SiteInfo, useTransferDataStore } from '@/stores/transferData'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { getSiteList, type ISite } from '@/api/module/transfer' import { getSiteList, type ISite } from '@/api/module/transfer'
import EdfsWrap from "@/components/Edfs-wrap.vue"; import EdfsWrap from "@/components/Edfs-wrap.vue";
@ -101,20 +112,15 @@ const env = import.meta.env
const router = useRouter() const router = useRouter()
const transferDataStore = useTransferDataStore() const transferDataStore = useTransferDataStore()
const { isConnected, connectSite, onlineCount, offlineCount } = const { connectSite, siteMap, onlineCount, offlineCount } =
storeToRefs(transferDataStore) storeToRefs(transferDataStore)
const { initConnectSite } = transferDataStore function onTransferData(site: SiteInfo) {
watch(isConnected, val => {
val ? initConnectSite() : (connectSite.value = null)
})
function onTransferData() {
router.push({ router.push({
path: '/station/data-transfer', path: '/station/data-transfer',
query: { query: {
type: 'export', type: 'export',
site: JSON.stringify(site),
}, },
}) })
} }
@ -139,7 +145,6 @@ async function loadSiteList() {
} }
onMounted(() => { onMounted(() => {
initConnectSite()
loadSiteList() loadSiteList()
}) })
</script> </script>

2
src/views/stationData/transfer/index.vue

@ -181,7 +181,7 @@ const transferStatus = ref<
const devices = computed(() => { const devices = computed(() => {
return isonLineTransfer.value return isonLineTransfer.value
? Array.from(devicesMap.value.values()) ? Array.from(devicesMap.value.values()).filter(r => r.site_id === siteInfo.value.id)
: offLineDeviceList.value : offLineDeviceList.value
}) as Ref<any[]> }) as Ref<any[]>

Loading…
Cancel
Save