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,2 @@
export * from './use-alarm-action-column';
export * from './use-camera-snap-column';

View File

@@ -0,0 +1,149 @@
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, saveCameraIgnoreApi, updateDeviceAlarmLogApi, type NdmDeviceAlarmLogResultVO } from '@/apis';
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import { NButton, NFlex, NPopconfirm, type DataTableColumn, type DataTableRowData } from 'naive-ui';
import { h, type Ref } from 'vue';
export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
const { mutate: confirmAlarm } = useMutation({
mutationFn: async (params: { id: string | null }) => {
const { id } = params;
if (!id) return;
const alarmLog = tableData.value.find((item) => item.id === id);
if (alarmLog) {
alarmLog['alarmConfirm'] = '1';
}
await updateDeviceAlarmLogApi({ id, alarmConfirm: '1' });
},
onError: (error, variables) => {
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
const { id } = variables;
if (id) {
const alarmLog = tableData.value.find((item) => item.id === id);
if (alarmLog) {
alarmLog['alarmConfirm'] = '2';
}
}
},
});
const { mutate: ignoreCamera } = useMutation({
mutationFn: async (params: { id: string | null }) => {
const { id } = params;
if (!id) return;
const alarmLog = tableData.value.find((item) => item.id === id);
if (!alarmLog) return;
const { records } = await pageCameraIgnoreApi({
model: { deviceId: alarmLog.deviceId },
extra: {},
current: 1,
size: 10,
sort: 'id',
order: 'descending',
});
const ignoredCamera = records.at(0);
if (ignoredCamera) {
window.$message.info('设备已被忽略');
return;
}
await saveCameraIgnoreApi({ deviceId: alarmLog.deviceId });
window.$message.success('忽略设备成功');
},
onError: (error) => {
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
// const { mutate: noticeCamera } = useMutation({
// mutationFn: async (params: { id: string | null }) => {
// const { id } = params;
// if (!id) return;
// const alarmLog = tableData.value.find((item) => item.id === id);
// if (!alarmLog) return;
// const { records } = await pageCameraIgnoreApi({
// model: { deviceId: alarmLog.deviceId },
// extra: {},
// current: 1,
// size: 10,
// sort: 'id',
// order: 'descending',
// });
// if (records.length === 0) {
// window.$message.info('设备未被忽略');
// return;
// }
// await deleteCameraIgnoreApi([...records.map((record) => record.id ?? '')]);
// window.$message.success('取消忽略设备成功');
// },
// onError: (error) => {
// console.error(error);
// const errorFeedback = parseErrorFeedback(error);
// window.$message.error(errorFeedback);
// },
// });
const alarmActionColumn: DataTableColumn<NdmDeviceAlarmLogResultVO> = {
title: '操作',
key: 'action',
width: 120,
align: 'center',
render: (rowData) => {
const { id } = rowData;
return h(
NFlex,
{
size: 'small',
justify: 'center',
align: 'center',
},
{
default: () => [
rowData.alarmConfirm === '1'
? h(NButton, { disabled: true, secondary: true, type: 'info', size: 'tiny' }, { default: () => '确认' })
: h(
NPopconfirm,
{
onPositiveClick: () => confirmAlarm({ id }),
},
{
trigger: () => h(NButton, { secondary: true, type: 'info', size: 'tiny' }, { default: () => '确认' }),
default: () => '确认告警?',
},
),
tryGetDeviceType(rowData.deviceType) === DEVICE_TYPE_LITERALS.ndmCamera && [
h(
NPopconfirm,
{
onPositiveClick: () => ignoreCamera({ id }),
},
{
trigger: () => h(NButton, { tertiary: true, type: 'info', size: 'tiny' }, { default: () => '忽略' }),
default: () => '忽略设备?',
},
),
// h(
// NPopconfirm,
// {
// onPositiveClick: () => noticeCamera({ id }),
// },
// {
// trigger: () => h(NButton, { text: true, type: 'info', size: 'small' }, { icon: () => h(EyeOutlined) }),
// default: () => '取消忽略设备?',
// },
// ),
],
],
},
);
},
};
return {
alarmActionColumn,
};
};

View File

@@ -0,0 +1,66 @@
import { getCameraSnapApi, type NdmDeviceAlarmLogResultVO } from '@/apis';
import { tryGetDeviceType, DEVICE_TYPE_LITERALS } from '@/enums';
import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import { NButton, NImage, type DataTableColumn, type DataTableRowData } from 'naive-ui';
import { h, ref, watch, type Ref } from 'vue';
export const useCameraSnapColumn = (tableData: Ref<DataTableRowData[]>) => {
const { mutateAsync: getSnapByDeviceId } = useMutation({
mutationFn: async (params: { deviceAlarmLog: NdmDeviceAlarmLogResultVO }) => {
const { deviceAlarmLog } = params;
const { deviceId } = deviceAlarmLog;
if (!deviceId) throw new Error('设备ID不能为空');
const snap = await getCameraSnapApi(deviceId);
return snap;
},
onError: (error) => {
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
// 控制每一行的查看按钮loading状态
const loadingMap = ref<Record<string, boolean>>({});
watch(tableData, () => {
loadingMap.value = {};
});
const cameraSnapColumn: DataTableColumn<NdmDeviceAlarmLogResultVO & { snapUrl?: string }> = {
title: '实时画面截图',
key: 'snapUrl',
align: 'center',
render: (rowData) => {
const { deviceType: deviceTypeCode, snapUrl } = rowData;
const deviceType = tryGetDeviceType(deviceTypeCode);
if (deviceType !== DEVICE_TYPE_LITERALS.ndmCamera) return null;
if (!snapUrl) {
const id = rowData.id ?? '';
return h(
NButton,
{
type: 'info',
size: 'small',
loading: !!loadingMap.value[id],
onClick: async () => {
loadingMap.value[id] = true;
try {
const snap = await getSnapByDeviceId({ deviceAlarmLog: rowData });
rowData.snapUrl = snap.url;
} finally {
loadingMap.value[id] = false;
}
},
},
{ default: () => '查看' },
);
} else {
return h(NImage, { src: snapUrl, previewDisabled: false, showToolbar: false });
}
},
};
return {
cameraSnapColumn,
};
};