refactor: 重构项目结构

- 优化 `车站-设备-告警`  轮询机制
- 改进设备卡片的布局
- 支持修改设备
- 告警轮询中获取完整告警数据
- 车站告警详情支持导出完整的 `今日告警列表`
- 支持将状态持久化到 `IndexedDB`
- 新增轮询控制 (调试模式)
- 新增离线开发模式 (调试模式)
- 新增 `IndexedDB` 数据控制 (调试模式)
This commit is contained in:
yangsy
2025-12-11 13:42:22 +08:00
commit 37781216b2
278 changed files with 17988 additions and 0 deletions

View File

@@ -0,0 +1 @@
export * from './use-stomp-client';

View File

@@ -0,0 +1,123 @@
import type { NdmDeviceAlarmLogResultVO, Station, SyncCameraResult } from '@/apis';
import { ALARM_TOPIC, SYNC_CAMERA_STATUS_TOPIC } from '@/constants';
import { useAlarmStore, useSettingStore, useStationStore } from '@/stores';
import { Client } from '@stomp/stompjs';
import { watchDebounced } from '@vueuse/core';
import destr from 'destr';
import { storeToRefs } from 'pinia';
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useStationAlarmsMutation } from '../query';
const getBrokerUrl = () => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const { host } = window.location;
const endpoint = '/ws';
const brokerURL = `${protocol}//${host}${endpoint}`;
return brokerURL;
};
export const useStompClient = () => {
const stationStore = useStationStore();
const { stations } = storeToRefs(stationStore);
const alarmStore = useAlarmStore();
const { unreadLineAlarms } = storeToRefs(alarmStore);
const settingStore = useSettingStore();
const { offlineDev } = storeToRefs(settingStore);
const { mutate: refreshStationAlarms } = useStationAlarmsMutation();
const stompClient = ref<Client | null>(null);
const syncCameraResult = ref<Record<Station['code'], SyncCameraResult>>({});
onMounted(() => {
stompClient.value = new Client({
brokerURL: getBrokerUrl(),
reconnectDelay: 5000,
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
onConnect: () => {
console.log('Stomp连接成功');
stompClient.value?.subscribe(ALARM_TOPIC, (message) => {
const alarm = destr<NdmDeviceAlarmLogResultVO>(message.body);
if (alarm.alarmCategory === '1') {
alarmStore.pushUnreadAlarm(alarm);
}
});
stompClient.value?.subscribe(SYNC_CAMERA_STATUS_TOPIC, (message) => {
const { stationCode, startTime, endTime, insertList, updateList, deleteList } = destr<SyncCameraResult>(message.body);
syncCameraResult.value[stationCode] = { stationCode, startTime, endTime, insertList, updateList, deleteList };
});
},
onDisconnect: () => {
console.log('Stomp连接断开');
stompClient.value?.unsubscribe(ALARM_TOPIC);
stompClient.value?.unsubscribe(SYNC_CAMERA_STATUS_TOPIC);
},
onStompError: (frame) => {
console.log('Stomp错误', frame);
window.$message.error('Stomp错误');
},
onWebSocketError: (event: Event) => {
console.log('WebSocket错误', event);
window.$message.error('WebSocket错误');
},
});
if (!offlineDev.value) {
stompClient.value.activate();
}
});
onBeforeUnmount(() => {
stompClient.value?.deactivate();
stompClient.value = null;
});
watch(offlineDev, (offline) => {
if (offline) {
stompClient.value?.deactivate();
} else {
stompClient.value?.activate();
}
});
// 当有车站的未读报警变化,即新收到告警时,需要同步告警数据,
// 但告警可能非常频繁,所以需要防抖处理
const abortControllerMap = ref(new Map<Station['code'], AbortController>());
watchDebounced(
() => Object.entries(unreadLineAlarms.value).map(([stationCode, stationAlarms]) => ({ stationCode, count: stationAlarms['unclassified'].length })),
(newValue, oldValue) => {
// 启用离线模式时,跳过处理
if (offlineDev.value) return;
if (newValue.length === 0) return;
const codes: Station['code'][] = [];
newValue.forEach(({ stationCode, count }) => {
const prevState = oldValue.find((stat) => stat.stationCode === stationCode);
if (!prevState || count !== prevState.count) {
codes.push(stationCode);
}
});
// console.log('以下车站收到新告警:', codes);
for (const stationCode of codes) {
const station = stations.value.find((station) => station.code === stationCode);
if (!station) continue;
abortControllerMap.value.get(stationCode)?.abort();
abortControllerMap.value.set(stationCode, new AbortController());
refreshStationAlarms({ station, signal: abortControllerMap.value.get(stationCode)?.signal });
}
},
{
debounce: 2500,
maxWait: 5000,
},
);
return {
stompClient,
syncCameraResult,
afterCheckSyncCameraResult: () => {
syncCameraResult.value = {};
},
};
};