Compare commits

..

10 Commits

Author SHA1 Message Date
yangsy
2abfa8f2f7 format: DataTable funcation 2025-11-14 16:42:30 +08:00
yangsy
686fdc8724 refactor(query): remove unnecessary query calls and optimize global loading 2025-11-14 16:36:07 +08:00
yangsy
1ed091f54d refactor(diag-info): make all fields optional 2025-11-14 16:06:24 +08:00
yangsy
8a38da53d7 style(device-status-history-diag-card): timeline height 2025-11-14 15:48:05 +08:00
yangsy
098a315153 fix(device-common-card): render condition 2025-11-12 15:37:53 +08:00
yangsy
a5a6ff9dbe refactor(ndm-security-box-diag-info): make all fields optional 2025-11-12 15:37:27 +08:00
yangsy
be88c0b31a refactor(device-tree): UX of tree node 2025-11-12 14:22:13 +08:00
yangsy
d2791ad093 feat(device-tree): expand/fold station node when click it 2025-11-11 12:21:12 +08:00
yangsy
a583fa2a4b chore: update dependencies 2025-11-11 10:37:36 +08:00
yangsy
8f3c8b7992 refactor: extract isNvrCluster 2025-11-10 20:38:40 +08:00
34 changed files with 236 additions and 258 deletions

View File

@@ -20,15 +20,15 @@
"@tanstack/vue-query": "^5.83.1", "@tanstack/vue-query": "^5.83.1",
"@tanstack/vue-query-devtools": "^5.84.0", "@tanstack/vue-query-devtools": "^5.84.0",
"@vueuse/core": "^13.6.0", "@vueuse/core": "^13.6.0",
"axios": "^1.11.0", "axios": "^1.13.2",
"compressing": "^2.0.0", "compressing": "^2.0.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.19",
"destr": "^2.0.5", "destr": "^2.0.5",
"echarts": "^6.0.0", "echarts": "^6.0.0",
"es-toolkit": "^1.39.9", "es-toolkit": "^1.41.0",
"naive-ui": "^2.42.0", "naive-ui": "^2.43.1",
"nanoid": "^5.1.5", "nanoid": "^5.1.6",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0", "pinia-plugin-persistedstate": "^4.5.0",
"sass": "^1.90.0", "sass": "^1.90.0",

60
pnpm-lock.yaml generated
View File

@@ -21,8 +21,8 @@ importers:
specifier: ^13.6.0 specifier: ^13.6.0
version: 13.6.0(vue@3.5.18(typescript@5.8.3)) version: 13.6.0(vue@3.5.18(typescript@5.8.3))
axios: axios:
specifier: ^1.11.0 specifier: ^1.13.2
version: 1.11.0 version: 1.13.2
compressing: compressing:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0 version: 2.0.0
@@ -30,8 +30,8 @@ importers:
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
dayjs: dayjs:
specifier: ^1.11.13 specifier: ^1.11.19
version: 1.11.13 version: 1.11.19
destr: destr:
specifier: ^2.0.5 specifier: ^2.0.5
version: 2.0.5 version: 2.0.5
@@ -39,14 +39,14 @@ importers:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
es-toolkit: es-toolkit:
specifier: ^1.39.9 specifier: ^1.41.0
version: 1.39.9 version: 1.41.0
naive-ui: naive-ui:
specifier: ^2.42.0 specifier: ^2.43.1
version: 2.42.0(vue@3.5.18(typescript@5.8.3)) version: 2.43.1(vue@3.5.18(typescript@5.8.3))
nanoid: nanoid:
specifier: ^5.1.5 specifier: ^5.1.6
version: 5.1.5 version: 5.1.6
pinia: pinia:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3)) version: 3.0.3(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3))
@@ -1049,8 +1049,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
axios@1.11.0: axios@1.13.2:
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1179,8 +1179,8 @@ packages:
date-fns@3.6.0: date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
dayjs@1.11.13: dayjs@1.11.19:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
de-indent@1.0.2: de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -1267,8 +1267,8 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
es-toolkit@1.39.9: es-toolkit@1.41.0:
resolution: {integrity: sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==} resolution: {integrity: sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==}
esbuild@0.25.8: esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
@@ -1722,8 +1722,8 @@ packages:
muggle-string@0.4.1: muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
naive-ui@2.42.0: naive-ui@2.43.1:
resolution: {integrity: sha512-c7cXR2YgOjgtBadXHwiWL4Y0tpGLAI5W5QzzHksOi22iuHXoSGMAzdkVTGVPE/PM0MSGQ/JtUIzCx2Y0hU0vTQ==} resolution: {integrity: sha512-w52W0mOhdOGt4uucFSZmP0DI44PCsFyuxeLSs9aoUThfIuxms90MYjv46Qrr7xprjyJRw5RU6vYpCx4o9ind3A==}
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
@@ -1732,8 +1732,8 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
nanoid@5.1.5: nanoid@5.1.6:
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
hasBin: true hasBin: true
@@ -2222,8 +2222,8 @@ packages:
typescript: typescript:
optional: true optional: true
vueuc@0.4.64: vueuc@0.4.65:
resolution: {integrity: sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==} resolution: {integrity: sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==}
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
@@ -3049,7 +3049,7 @@ snapshots:
'@vue/devtools-kit': 8.0.0 '@vue/devtools-kit': 8.0.0
'@vue/devtools-shared': 8.0.0 '@vue/devtools-shared': 8.0.0
mitt: 3.0.1 mitt: 3.0.1
nanoid: 5.1.5 nanoid: 5.1.6
pathe: 2.0.3 pathe: 2.0.3
vite-hot-client: 2.1.0(vite@7.1.12(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)(tsx@4.20.5)) vite-hot-client: 2.1.0(vite@7.1.12(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)(tsx@4.20.5))
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
@@ -3194,7 +3194,7 @@ snapshots:
dependencies: dependencies:
possible-typed-array-names: 1.1.0 possible-typed-array-names: 1.1.0
axios@1.11.0: axios@1.13.2:
dependencies: dependencies:
follow-redirects: 1.15.11 follow-redirects: 1.15.11
form-data: 4.0.4 form-data: 4.0.4
@@ -3333,7 +3333,7 @@ snapshots:
date-fns@3.6.0: {} date-fns@3.6.0: {}
dayjs@1.11.13: {} dayjs@1.11.19: {}
de-indent@1.0.2: {} de-indent@1.0.2: {}
@@ -3405,7 +3405,7 @@ snapshots:
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
hasown: 2.0.2 hasown: 2.0.2
es-toolkit@1.39.9: {} es-toolkit@1.41.0: {}
esbuild@0.25.8: esbuild@0.25.8:
optionalDependencies: optionalDependencies:
@@ -3839,7 +3839,7 @@ snapshots:
muggle-string@0.4.1: {} muggle-string@0.4.1: {}
naive-ui@2.42.0(vue@3.5.18(typescript@5.8.3)): naive-ui@2.43.1(vue@3.5.18(typescript@5.8.3)):
dependencies: dependencies:
'@css-render/plugin-bem': 0.15.14(css-render@0.15.14) '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
'@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.8.3)) '@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.8.3))
@@ -3860,11 +3860,11 @@ snapshots:
vdirs: 0.1.8(vue@3.5.18(typescript@5.8.3)) vdirs: 0.1.8(vue@3.5.18(typescript@5.8.3))
vooks: 0.2.12(vue@3.5.18(typescript@5.8.3)) vooks: 0.2.12(vue@3.5.18(typescript@5.8.3))
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
vueuc: 0.4.64(vue@3.5.18(typescript@5.8.3)) vueuc: 0.4.65(vue@3.5.18(typescript@5.8.3))
nanoid@3.3.11: {} nanoid@3.3.11: {}
nanoid@5.1.5: {} nanoid@5.1.6: {}
natural-compare@1.4.0: {} natural-compare@1.4.0: {}
@@ -4336,7 +4336,7 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
vueuc@0.4.64(vue@3.5.18(typescript@5.8.3)): vueuc@0.4.65(vue@3.5.18(typescript@5.8.3)):
dependencies: dependencies:
'@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.8.3)) '@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.8.3))
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0

View File

@@ -1,5 +1,5 @@
export interface NdmCameraDiagInfo { export interface NdmCameraDiagInfo {
[key: string]: any; [key: string]: any;
logTime: string; logTime?: string;
info: string; info?: string;
} }

View File

@@ -1,14 +1,14 @@
export interface NdmDecoderDiagInfo { export interface NdmDecoderDiagInfo {
[key: string]: any; [key: string]: any;
logTime: string; logTime?: string;
stCommonInfo: { stCommonInfo?: {
设备ID: string; 设备ID?: string;
软件版本: string; 软件版本?: string;
生产厂商: string; 生产厂商?: string;
设备别名: string; 设备别名?: string;
设备型号: string; 设备型号?: string;
硬件版本: string; 硬件版本?: string;
内存使用率: string; 内存使用率?: string;
CPU使用率: string; CPU使用率?: string;
}; };
} }

View File

@@ -1,31 +1,31 @@
export interface NdmNvrDiagInfo { export interface NdmNvrDiagInfo {
[key: string]: any; [key: string]: any;
logTime: string; logTime?: string;
info: { info?: {
diskHealth: number[]; diskHealth?: number[];
groupInfoList: { groupInfoList?: {
freeSize: number; freeSize?: number;
state: number; state?: number;
stateValue: string; stateValue?: string;
totalSize: number; totalSize?: number;
}[]; }[];
}; };
stCommonInfo: { stCommonInfo?: {
设备ID: string; 设备ID?: string;
软件版本: string; 软件版本?: string;
生产厂商: string; 生产厂商?: string;
设备别名: string; 设备别名?: string;
设备型号: string; 设备型号?: string;
硬件版本: string; 硬件版本?: string;
内存使用率: string; 内存使用率?: string;
CPU使用率: string; CPU使用率?: string;
}; };
cdFanInfo: { cdFanInfo?: {
索引号: string; 索引号?: string;
'风扇转速(rpm)': string; '风扇转速(rpm)'?: string;
}[]; }[];
cdPowerSupplyInfo: { cdPowerSupplyInfo?: {
索引号: string; 索引号?: string;
电源状态: string; 电源状态?: string;
}[]; }[];
} }

View File

@@ -1,18 +1,19 @@
export interface NdmSecurityBoxDiagInfo { export interface NdmSecurityBoxDiagInfo {
[key: string]: any; [key: string]: any;
info: [ info?: [
{ {
addrCode: number; addrCode?: number;
circuits: NdmSecurityBoxCircuit[]; circuits?: NdmSecurityBoxCircuit[];
fanSpeeds: number[]; fanSpeeds?: number[];
humidity: number; humidity?: number;
switches: number[]; switches?: number[];
temperature: number; temperature?: number;
}, },
]; ];
stCommonInfo: { stCommonInfo?: {
内存使用率: string; [key: string]: any;
CPU使用率: string; 内存使用率?: string;
CPU使用率?: string;
}; };
} }

View File

@@ -1,9 +1,9 @@
export interface NdmServerDiagInfo { export interface NdmServerDiagInfo {
[key: string]: any; [key: string]: any;
commInfo: { commInfo?: {
CPU使用率: string; CPU使用率?: string;
内存使用率: string; 内存使用率?: string;
磁盘使用率: string; 磁盘使用率?: string;
系统运行时间: string; 系统运行时间?: string;
}; };
} }

View File

@@ -2,10 +2,10 @@ export interface NdmSwitchDiagInfo {
[key: string]: any; [key: string]: any;
cpuRatio?: string; // 因环境不同可能不存在 cpuRatio?: string; // 因环境不同可能不存在
memoryRatio?: string; // 因环境不同可能不存在 memoryRatio?: string; // 因环境不同可能不存在
logTime: string; logTime?: string;
info: { info?: {
overFlowPorts: string[]; overFlowPorts?: string[];
portInfoList: NdmSwitchPortInfo[]; portInfoList?: NdmSwitchPortInfo[];
}; };
} }

View File

@@ -155,10 +155,6 @@ const tablePagination = reactive<PaginationProps>({
const tableData = ref<DataTableRowData[]>([]); const tableData = ref<DataTableRowData[]>([]);
const exportTableData = () => {
downloadTableData();
};
const onAfterModalEnter = () => { const onAfterModalEnter = () => {
getStaionAlarmList(); getStaionAlarmList();
}; };
@@ -183,7 +179,7 @@ const onUpdateFilters: DataTableProps['onUpdateFilters'] = (filterState) => {
getStaionAlarmList(); getStaionAlarmList();
}; };
const { mutate: getStaionAlarmList, isPending: isTableLoading } = useMutation({ const { mutate: getStaionAlarmList, isPending: tableLoading } = useMutation({
mutationFn: async () => { mutationFn: async () => {
const now = dayjs(); const now = dayjs();
const res = await postNdmDeviceAlarmLogPage(station.value?.code ?? '', { const res = await postNdmDeviceAlarmLogPage(station.value?.code ?? '', {
@@ -218,7 +214,7 @@ const { mutate: getStaionAlarmList, isPending: isTableLoading } = useMutation({
}, },
}); });
const { mutate: downloadTableData, isPending: isDownloading } = useMutation({ const { mutate: exportTableData, isPending: exporting } = useMutation({
mutationFn: async () => { mutationFn: async () => {
const now = dayjs(); const now = dayjs();
const data = await ndmDeviceAlarmLogDefaultExportByTemplate(station.value?.code ?? '', { const data = await ndmDeviceAlarmLogDefaultExportByTemplate(station.value?.code ?? '', {
@@ -275,12 +271,12 @@ const { mutate: downloadTableData, isPending: isDownloading } = useMutation({
<div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px 0"> <div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px 0">
<div style="font-size: medium">今日设备告警列表</div> <div style="font-size: medium">今日设备告警列表</div>
<NSpace style="margin-left: auto"> <NSpace style="margin-left: auto">
<NButton type="primary" :loading="isDownloading" @click="exportTableData">导出</NButton> <NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton>
</NSpace> </NSpace>
</div> </div>
<div style="flex: 1 1 auto; min-height: 0"> <div style="flex: 1 1 auto; min-height: 0">
<NDataTable <NDataTable
:loading="isTableLoading" :loading="tableLoading"
:columns="tableColumns" :columns="tableColumns"
:data="tableData" :data="tableData"
:pagination="tablePagination" :pagination="tablePagination"

View File

@@ -8,6 +8,7 @@ import { computed, ref, toRefs } from 'vue';
import DeviceHeaderCard from './current-diag-card/device-header-card.vue'; import DeviceHeaderCard from './current-diag-card/device-header-card.vue';
import CameraHistoryDiagCard from './history-diag-card/camera-history-diag-card.vue'; import CameraHistoryDiagCard from './history-diag-card/camera-history-diag-card.vue';
import DeviceCommonCard from './current-diag-card/device-common-card.vue'; import DeviceCommonCard from './current-diag-card/device-common-card.vue';
import type { NdmCameraDiagInfo } from '@/apis/domains';
const props = defineProps<{ const props = defineProps<{
stationCode: string; stationCode: string;
@@ -22,7 +23,7 @@ const lastDiagInfo = computed(() => {
const result = destr<any>(ndmCamera.value.lastDiagInfo); const result = destr<any>(ndmCamera.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmCameraDiagInfo;
}); });
const commonInfo = computed(() => { const commonInfo = computed(() => {

View File

@@ -8,11 +8,13 @@ const props = defineProps<{
const { commonInfo } = toRefs(props); const { commonInfo } = toRefs(props);
const cardShow = computed(() => !!commonInfo.value && Object.keys(commonInfo.value).length > 0);
const commonInfoEntries = computed(() => Object.entries(commonInfo.value ?? {})); const commonInfoEntries = computed(() => Object.entries(commonInfo.value ?? {}));
</script> </script>
<template> <template>
<NCard v-if="commonInfo" size="small" hoverable> <NCard v-if="cardShow" size="small" hoverable>
<template #header> <template #header>
<div>设备信息</div> <div>设备信息</div>
</template> </template>

View File

@@ -21,10 +21,10 @@ const debugModeStore = useDebugModeStore();
const { stationCode, ndmDecoder } = toRefs(props); const { stationCode, ndmDecoder } = toRefs(props);
const lastDiagInfo = computed(() => { const lastDiagInfo = computed(() => {
const result = destr<NdmDecoderDiagInfo>(ndmDecoder.value.lastDiagInfo); const result = destr<any>(ndmDecoder.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmDecoderDiagInfo;
}); });
const commonInfo = computed<Record<string, string> | undefined>(() => { const commonInfo = computed<Record<string, string> | undefined>(() => {
@@ -37,8 +37,8 @@ const commonInfo = computed<Record<string, string> | undefined>(() => {
return info; return info;
}); });
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo.CPU使用率); const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo.内存使用率); const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用率);
const selectedTab = ref('设备状态'); const selectedTab = ref('设备状态');
</script> </script>

View File

@@ -119,6 +119,9 @@ defineExpose({
<div v-if="timelineItems.length === 0" style="width: 100%; display: flex; color: #666"> <div v-if="timelineItems.length === 0" style="width: 100%; display: flex; color: #666">
<div style="margin: auto">暂无记录</div> <div style="margin: auto">暂无记录</div>
</div> </div>
<NTimeline v-else-if="timelineItems.length <= DEFAULT_PAGE_SIZE">
<NTimelineItem v-for="{ type, title, content, time } in timelineItems" :key="time" :type="type" :title="title" :content="content" :time="time"></NTimelineItem>
</NTimeline>
<NScrollbar v-else x-scrollable style="height: 500px"> <NScrollbar v-else x-scrollable style="height: 500px">
<NTimeline> <NTimeline>
<NTimelineItem v-for="{ type, title, content, time } in timelineItems" :key="time" :type="type" :title="title" :content="content" :time="time"></NTimelineItem> <NTimelineItem v-for="{ type, title, content, time } in timelineItems" :key="time" :type="type" :title="title" :content="content" :time="time"></NTimelineItem>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NdmNvrResultVO } from '@/apis/models'; import type { NdmNvrResultVO } from '@/apis/models';
import { isNvrCluster } from '@/components/helper';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { NButton, NCard, NDatePicker, NFlex, NGi, NGrid, NSelect, type DatePickerProps, type SelectOption } from 'naive-ui'; import { NButton, NCard, NDatePicker, NFlex, NGi, NGrid, NSelect, type DatePickerProps, type SelectOption } from 'naive-ui';
import { computed, onMounted, reactive, ref, toRefs, useTemplateRef } from 'vue'; import { computed, onMounted, reactive, ref, toRefs, useTemplateRef } from 'vue';
@@ -66,12 +67,11 @@ onMounted(() => {
}); });
const diagCards = computed<SelectOption[]>(() => { const diagCards = computed<SelectOption[]>(() => {
const isCluster = (maybeNvrCluster: NdmNvrResultVO) => !!maybeNvrCluster.clusterList?.trim() && maybeNvrCluster.clusterList !== maybeNvrCluster.ipAddress;
const baseOptions: SelectOption[] = [ const baseOptions: SelectOption[] = [
{ label: '设备状态', value: 'status' }, { label: '设备状态', value: 'status' },
{ label: '设备告警', value: 'alarm' }, { label: '设备告警', value: 'alarm' },
]; ];
if (!isCluster(ndmNvr.value)) { if (!isNvrCluster(ndmNvr.value)) {
baseOptions.push({ label: '硬件使用率', value: 'usage' }, { label: '硬盘健康', value: 'health' }); baseOptions.push({ label: '硬件使用率', value: 'usage' }, { label: '硬盘健康', value: 'health' });
} }
return baseOptions; return baseOptions;

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NdmNvrDiagInfo } from '@/apis/domains'; import type { NdmNvrDiagInfo } from '@/apis/domains';
import type { NdmNvrResultVO } from '@/apis/models'; import type { NdmNvrResultVO } from '@/apis/models';
import { isNvrCluster } from '@/components/helper';
import { useDebugModeStore } from '@/stores/debug-mode'; import { useDebugModeStore } from '@/stores/debug-mode';
import { destr } from 'destr'; import { destr } from 'destr';
import { NCard, NFlex, NTabPane, NTabs } from 'naive-ui'; import { NCard, NFlex, NTabPane, NTabs } from 'naive-ui';
@@ -23,14 +24,14 @@ const debugModeStore = useDebugModeStore();
const { stationCode, ndmNvr } = toRefs(props); const { stationCode, ndmNvr } = toRefs(props);
const lastDiagInfo = computed(() => { const lastDiagInfo = computed(() => {
const result = destr<NdmNvrDiagInfo>(ndmNvr.value.lastDiagInfo); const result = destr<any>(ndmNvr.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmNvrDiagInfo;
}); });
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo.CPU使用率); const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.['CPU使用率']);
const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo.内存使用率); const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.['内存使用率']);
const commonInfo = computed<Record<string, string> | undefined>(() => { const commonInfo = computed<Record<string, string> | undefined>(() => {
const info = lastDiagInfo.value?.stCommonInfo; const info = lastDiagInfo.value?.stCommonInfo;
@@ -42,14 +43,11 @@ const commonInfo = computed<Record<string, string> | undefined>(() => {
return info; return info;
}); });
const diskHealth = computed(() => lastDiagInfo.value?.info.diskHealth); const diskHealth = computed(() => lastDiagInfo.value?.info?.diskHealth);
const groupInfoList = computed(() => lastDiagInfo.value?.info.groupInfoList); const groupInfoList = computed(() => lastDiagInfo.value?.info?.groupInfoList);
const isCluster = computed(() => { const isCluster = computed(() => {
const { ipAddress, clusterList } = ndmNvr.value; return isNvrCluster(ndmNvr.value);
if (!clusterList?.trim()) return false;
if (clusterList === ipAddress) return false;
return true;
}); });
const selectedTab = ref('设备状态'); const selectedTab = ref('设备状态');

View File

@@ -23,10 +23,10 @@ const debugModeStore = useDebugModeStore();
const { stationCode, ndmSecurityBox } = toRefs(props); const { stationCode, ndmSecurityBox } = toRefs(props);
const lastDiagInfo = computed(() => { const lastDiagInfo = computed(() => {
const result = destr<NdmSecurityBoxDiagInfo>(ndmSecurityBox.value.lastDiagInfo); const result = destr<any>(ndmSecurityBox.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmSecurityBoxDiagInfo;
}); });
const commonInfo = computed<Record<string, string> | undefined>(() => { const commonInfo = computed<Record<string, string> | undefined>(() => {
@@ -39,15 +39,15 @@ const commonInfo = computed<Record<string, string> | undefined>(() => {
return info; return info;
}); });
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo.CPU使用率); const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.['CPU使用率']);
const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo.内存使用率); const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.['内存使用率']);
const fanSpeeds = computed(() => lastDiagInfo.value?.info.at(0)?.fanSpeeds); const fanSpeeds = computed(() => lastDiagInfo.value?.info?.at(0)?.fanSpeeds);
const temperature = computed(() => lastDiagInfo.value?.info.at(0)?.temperature); const temperature = computed(() => lastDiagInfo.value?.info?.at(0)?.temperature);
const humidity = computed(() => lastDiagInfo.value?.info.at(0)?.humidity); const humidity = computed(() => lastDiagInfo.value?.info?.at(0)?.humidity);
const switches = computed(() => lastDiagInfo.value?.info.at(0)?.switches); const switches = computed(() => lastDiagInfo.value?.info?.at(0)?.switches);
const circuits = computed(() => lastDiagInfo.value?.info.at(0)?.circuits); const circuits = computed(() => lastDiagInfo.value?.info?.at(0)?.circuits);
const selectedTab = ref('设备状态'); const selectedTab = ref('设备状态');
</script> </script>

View File

@@ -20,16 +20,16 @@ const debugModeStore = useDebugModeStore();
const { stationCode, ndmServer } = toRefs(props); const { stationCode, ndmServer } = toRefs(props);
const lastDiagInfo = computed(() => { const lastDiagInfo = computed(() => {
const result = destr<NdmServerDiagInfo>(ndmServer.value.lastDiagInfo); const result = destr<any>(ndmServer.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmServerDiagInfo;
}); });
const cpuUsage = computed(() => lastDiagInfo.value?.commInfo.CPU使用率); const cpuUsage = computed(() => lastDiagInfo.value?.commInfo?.['CPU使用率']);
const memUsage = computed(() => lastDiagInfo.value?.commInfo.内存使用率); const memUsage = computed(() => lastDiagInfo.value?.commInfo?.['内存使用率']);
const diskUsage = computed(() => lastDiagInfo.value?.commInfo.磁盘使用率); const diskUsage = computed(() => lastDiagInfo.value?.commInfo?.['磁盘使用率']);
const runningTime = computed(() => lastDiagInfo.value?.commInfo.系统运行时间); const runningTime = computed(() => lastDiagInfo.value?.commInfo?.['系统运行时间']);
const selectedTab = ref('设备状态'); const selectedTab = ref('设备状态');
</script> </script>

View File

@@ -21,16 +21,16 @@ const debugModeStore = useDebugModeStore();
const { stationCode, ndmSwitch } = toRefs(props); const { stationCode, ndmSwitch } = toRefs(props);
const lastDiagInfo = computed(() => { const lastDiagInfo = computed(() => {
const result = destr<NdmSwitchDiagInfo>(ndmSwitch.value.lastDiagInfo); const result = destr<any>(ndmSwitch.value.lastDiagInfo);
if (!result) return null; if (!result) return null;
if (typeof result !== 'object') return null; if (typeof result !== 'object') return null;
return result; return result as NdmSwitchDiagInfo;
}); });
const cpuUsage = computed(() => lastDiagInfo.value?.cpuRatio); const cpuUsage = computed(() => lastDiagInfo.value?.cpuRatio);
const memUsage = computed(() => lastDiagInfo.value?.memoryRatio); const memUsage = computed(() => lastDiagInfo.value?.memoryRatio);
const portInfoList = computed(() => lastDiagInfo.value?.info.portInfoList ?? []); const portInfoList = computed(() => lastDiagInfo.value?.info?.portInfoList ?? []);
const selectedTab = ref('设备状态'); const selectedTab = ref('设备状态');
</script> </script>

View File

@@ -11,6 +11,7 @@ const deviceTabPanes = Object.keys(DeviceType).map((key) => {
<script setup lang="ts"> <script setup lang="ts">
import type { Station } from '@/apis/domains'; import type { Station } from '@/apis/domains';
import type { NdmDeviceResultVO, NdmNvrResultVO } from '@/apis/models'; import type { NdmDeviceResultVO, NdmNvrResultVO } from '@/apis/models';
import { isNvrCluster } from '@/components/helper';
import type { LineDevices } from '@/composables/query'; import type { LineDevices } from '@/composables/query';
import { DeviceType, DeviceTypeName, tryGetDeviceTypeVal, type DeviceTypeKey, type DeviceTypeVal } from '@/enums/device-type'; import { DeviceType, DeviceTypeName, tryGetDeviceTypeVal, type DeviceTypeKey, type DeviceTypeVal } from '@/enums/device-type';
import { destr } from 'destr'; import { destr } from 'destr';
@@ -57,11 +58,19 @@ watch(
); );
const selectedKeys = computed(() => (selectedDevice.value?.id ? [selectedDevice.value.id] : undefined)); const selectedKeys = computed(() => (selectedDevice.value?.id ? [selectedDevice.value.id] : undefined));
// 选择设备 // 树节点交互
const onSelectDevice = (device: NdmDeviceResultVO, stationCode: string) => { const onSelectDevice = (device: NdmDeviceResultVO, stationCode: string) => {
emit('select-device', device, stationCode); emit('select-device', device, stationCode);
}; };
const override: TreeOverrideNodeClickBehavior = () => 'none'; const override: TreeOverrideNodeClickBehavior = ({ option }) => {
const hasChildren = (option.children?.length ?? 0) > 0;
const isDeviceNode = !!option['device'];
if (hasChildren || !isDeviceNode) {
return 'toggleExpand';
} else {
return 'none';
}
};
const nodeProps: TreeProps['nodeProps'] = ({ option }) => { const nodeProps: TreeProps['nodeProps'] = ({ option }) => {
return { return {
onDblclick: (payload: MouseEvent) => { onDblclick: (payload: MouseEvent) => {
@@ -85,13 +94,12 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
const onlineDevices = devices?.filter((device) => device.deviceStatus === '10'); const onlineDevices = devices?.filter((device) => device.deviceStatus === '10');
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20'); const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
// 对于录像机需要根据clusterList字段以分号分隔设备IP进一步形成子树结构 // 对于录像机需要根据clusterList字段以分号分隔设备IP进一步形成子树结构
const isCluster = (maybeNvrCluster: NdmNvrResultVO) => !!maybeNvrCluster.clusterList?.trim() && maybeNvrCluster.clusterList !== maybeNvrCluster.ipAddress;
if (paneName === DeviceType.Nvr) { if (paneName === DeviceType.Nvr) {
const nvrs = devices as NdmNvrResultVO[]; const nvrs = devices as NdmNvrResultVO[];
const nvrClusters: NdmNvrResultVO[] = []; const nvrClusters: NdmNvrResultVO[] = [];
const nvrSingletons: NdmNvrResultVO[] = []; const nvrSingletons: NdmNvrResultVO[] = [];
for (const device of nvrs) { for (const device of nvrs) {
if (isCluster(device)) { if (isNvrCluster(device)) {
nvrClusters.push(device); nvrClusters.push(device);
} else { } else {
nvrSingletons.push(device); nvrSingletons.push(device);
@@ -194,7 +202,7 @@ const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: string)
() => '查看', () => '查看',
); );
}; };
return h('div', [/* renderViewDeviceButton(device, stationCode), */ renderDeviceStatusTag(device)]); return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
}; };
// ========== 设备树搜索 ========== // ========== 设备树搜索 ==========
@@ -257,7 +265,7 @@ const scrollDeviceTreeToSelectedDevice = () => {
<template> <template>
<div style="height: 100%; display: flex; flex-direction: column"> <div style="height: 100%; display: flex; flex-direction: column">
<div style="flex-shrink: 0; padding: 12px"> <div style="flex-shrink: 0; padding: 12px">
<NInput v-model:value="searchInput" placeholder="搜索设备、设备ID或IP地址" clearable /> <NInput v-model:value="searchInput" placeholder="搜索设备名称、设备ID或IP地址" clearable />
<NFlex justify="space-between" align="center"> <NFlex justify="space-between" align="center">
<NRadioGroup v-model:value="statusInput"> <NRadioGroup v-model:value="statusInput">
<NRadio value="">全部</NRadio> <NRadio value="">全部</NRadio>

View File

@@ -1,2 +1,3 @@
export * from './device-alarm'; export * from './device-alarm';
export * from './nvr-cluster';
export * from './switch-port'; export * from './switch-port';

View File

@@ -0,0 +1,8 @@
import type { NdmNvrResultVO } from '@/apis/models';
export const isNvrCluster = (maybeNvrCluster: NdmNvrResultVO) => {
const { ipAddress, clusterList } = maybeNvrCluster;
if (!clusterList?.trim()) return false;
if (clusterList === ipAddress) return false;
return true;
};

View File

@@ -1,3 +1,3 @@
export * from './domains'; export * from './domains';
export * from './use-line-alarm-counts-query'; export * from './use-line-alarms-query';

View File

@@ -1,16 +1,16 @@
import { LINE_ALARM_COUNTS_QUERY_KEY } from '@/constants'; import type { StationAlarmCounts } from './domains';
import { useLineAlarmCountsStore } from '@/stores/line-alarm-counts'; import type { Station } from '@/apis/domains';
import { postNdmDeviceAlarmLogPage } from '@/apis/requests';
import { LINE_ALARMS_QUERY_KEY } from '@/constants';
import { DeviceType, tryGetDeviceTypeVal } from '@/enums/device-type';
import { useLineAlarmsStore } from '@/stores/line-alarms';
import { useQueryControlStore } from '@/stores/query-control'; import { useQueryControlStore } from '@/stores/query-control';
import { useStationStore } from '@/stores/station'; import { useStationStore } from '@/stores/station';
import { isCancelledError, useMutation, useQuery } from '@tanstack/vue-query'; import { isCancelledError, useMutation, useQuery } from '@tanstack/vue-query';
import { runTask } from '@/utils/run-task';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed } from 'vue'; import { computed } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { postNdmDeviceAlarmLogPage } from '@/apis/requests';
import type { Station } from '@/apis/domains';
import { DeviceType, tryGetDeviceTypeVal } from '@/enums/device-type';
import type { StationAlarmCounts } from './domains';
import { runTask } from '@/utils/run-task';
const createEmptyStationAlarmCounts = () => { const createEmptyStationAlarmCounts = () => {
return { return {
@@ -26,7 +26,7 @@ const createEmptyStationAlarmCounts = () => {
}; };
}; };
export function useLineAlarmCountsQuery() { export function useLineAlarmsQuery() {
const stationStore = useStationStore(); const stationStore = useStationStore();
const { stationList } = storeToRefs(stationStore); const { stationList } = storeToRefs(stationStore);
const queryControlStore = useQueryControlStore(); const queryControlStore = useQueryControlStore();
@@ -34,7 +34,7 @@ export function useLineAlarmCountsQuery() {
const { mutateAsync: getStationAlarmCounts } = useStationAlarmCountsMutation(); const { mutateAsync: getStationAlarmCounts } = useStationAlarmCountsMutation();
return useQuery({ return useQuery({
queryKey: [LINE_ALARM_COUNTS_QUERY_KEY, alarmQueryStamp], queryKey: [LINE_ALARMS_QUERY_KEY, alarmQueryStamp],
enabled: computed(() => alarmQueryStamp.value > 0), enabled: computed(() => alarmQueryStamp.value > 0),
staleTime: Infinity, staleTime: Infinity,
refetchOnMount: false, refetchOnMount: false,
@@ -58,8 +58,8 @@ interface StationAlarmCountsMutationParams {
} }
function useStationAlarmCountsMutation() { function useStationAlarmCountsMutation() {
const lineAlarmCountsStore = useLineAlarmCountsStore(); const lineAlarmsStore = useLineAlarmsStore();
const { lineAlarmCounts } = storeToRefs(lineAlarmCountsStore); const { lineAlarmCounts } = storeToRefs(lineAlarmsStore);
return useMutation<StationAlarmCounts, Error, StationAlarmCountsMutationParams>({ return useMutation<StationAlarmCounts, Error, StationAlarmCountsMutationParams>({
mutationFn: async ({ station, signal }) => { mutationFn: async ({ station, signal }) => {

View File

@@ -1,15 +1,15 @@
import type { StationDevices } from './domains';
import { ndmClient } from '@/apis/client';
import type { Station } from '@/apis/domains';
import { LINE_DEVICES_QUERY_KEY } from '@/constants';
import { DeviceType } from '@/enums/device-type'; import { DeviceType } from '@/enums/device-type';
import { useLineDevicesStore } from '@/stores/line-devices';
import { useQueryControlStore } from '@/stores/query-control'; import { useQueryControlStore } from '@/stores/query-control';
import { useStationStore } from '@/stores/station'; import { useStationStore } from '@/stores/station';
import { isCancelledError, useMutation, useQuery } from '@tanstack/vue-query'; import { isCancelledError, useMutation, useQuery } from '@tanstack/vue-query';
import { runTask } from '@/utils/run-task';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed } from 'vue'; import { computed } from 'vue';
import type { StationDevices } from './domains';
import { useLineDevicesStore } from '@/stores/line-devices';
import { LINE_DEVICES_QUERY_KEY } from '@/constants';
import { ndmClient } from '@/apis/client';
import type { Station } from '@/apis/domains';
import { runTask } from '@/utils/run-task';
const createEmptyStationDevices = (): StationDevices => { const createEmptyStationDevices = (): StationDevices => {
return { return {

View File

@@ -1,4 +1,4 @@
export const STATION_LIST_QUERY_KEY = 'station-list'; export const STATION_LIST_QUERY_KEY = 'station-list';
export const LINE_DEVICES_QUERY_KEY = 'line-devices'; export const LINE_DEVICES_QUERY_KEY = 'line-devices';
export const LINE_ALARM_COUNTS_QUERY_KEY = 'line-alarm-counts'; export const LINE_ALARMS_QUERY_KEY = 'line-alarms';
export const DEVICE_SNMP_LOGS_QUERY_KEY = 'device-snmp-logs'; export const DEVICE_SNMP_LOGS_QUERY_KEY = 'device-snmp-logs';

View File

@@ -10,16 +10,18 @@ function renderIcon(icon: Component): () => VNode {
import type { NdmDeviceAlarmLogResultVO } from '@/apis/models'; import type { NdmDeviceAlarmLogResultVO } from '@/apis/models';
import SettingsDrawer from '@/components/global/settings-drawer.vue'; import SettingsDrawer from '@/components/global/settings-drawer.vue';
import { useStationListQuery } from '@/composables/query'; import { useStationListQuery } from '@/composables/query';
import { STATION_LIST_QUERY_KEY, LINE_DEVICES_QUERY_KEY, LINE_ALARMS_QUERY_KEY } from '@/constants';
import { useCurrentAlarmsStore } from '@/stores/current-alarms'; import { useCurrentAlarmsStore } from '@/stores/current-alarms';
import { useUserStore } from '@/stores/user'; import { useUserStore } from '@/stores/user';
import { Client as StompClient } from '@stomp/stompjs'; import { Client as StompClient } from '@stomp/stompjs';
import { useIsFetching } from '@tanstack/vue-query';
import { AlertFilled, /* AreaChartOutlined, */ FileTextFilled, HomeFilled, LogoutOutlined, SettingOutlined, VideoCameraFilled } from '@vicons/antd'; import { AlertFilled, /* AreaChartOutlined, */ FileTextFilled, HomeFilled, LogoutOutlined, SettingOutlined, VideoCameraFilled } from '@vicons/antd';
import { ChevronDown, Debug } from '@vicons/carbon'; import { ChevronDown, Debug } from '@vicons/carbon';
import type { AxiosError } from 'axios'; import type { AxiosError } from 'axios';
import { destr } from 'destr'; import { destr } from 'destr';
import { NBadge, NButton, NDropdown, NFlex, NIcon, NLayout, NLayoutContent, NLayoutFooter, NLayoutHeader, NLayoutSider, NMenu, NScrollbar, type DropdownOption, type MenuOption } from 'naive-ui'; import { NBadge, NButton, NDropdown, NFlex, NIcon, NLayout, NLayoutContent, NLayoutFooter, NLayoutHeader, NLayoutSider, NMenu, NScrollbar, type DropdownOption, type MenuOption } from 'naive-ui';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { h, onBeforeMount, onBeforeUnmount, onMounted, ref, watch, type Component, type VNode } from 'vue'; import { computed, h, onBeforeMount, onBeforeUnmount, onMounted, ref, watch, type Component, type VNode } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router'; import { RouterLink, useRoute, useRouter } from 'vue-router';
const userStore = useUserStore(); const userStore = useUserStore();
@@ -38,6 +40,13 @@ watch(stationListQueryError, (newStationListQueryError) => {
} }
}); });
const stationListFetchingCount = useIsFetching({ queryKey: [STATION_LIST_QUERY_KEY] });
const lineDevicesFetchingCount = useIsFetching({ queryKey: [LINE_DEVICES_QUERY_KEY] });
const lineAlarmsFetchingCount = useIsFetching({ queryKey: [LINE_ALARMS_QUERY_KEY] });
const fetchingCount = computed(() => {
return stationListFetchingCount.value + lineDevicesFetchingCount.value + lineAlarmsFetchingCount.value;
});
onBeforeMount(() => { onBeforeMount(() => {
userStore.userGetInfo().catch((err) => window.$message.error((err as AxiosError).message)); userStore.userGetInfo().catch((err) => window.$message.error((err as AxiosError).message));
}); });
@@ -104,7 +113,7 @@ const menuOptions = ref<MenuOption[]>([
icon: renderIcon(AlertFilled), icon: renderIcon(AlertFilled),
}, },
// { // {
// label: () => h(RouterLink, { to: '/statistics' }, { default: () => '设备性能统计' }), // label: () => h(RouterLink, { to: '/statistics' }, { default: () => '设备数据统计' }),
// key: '/statistics', // key: '/statistics',
// icon: renderIcon(AreaChartOutlined), // icon: renderIcon(AreaChartOutlined),
// }, // },
@@ -133,13 +142,12 @@ const dropdownOptions = ref<DropdownOption[]>([
key: 'logout', key: 'logout',
icon: renderIcon(LogoutOutlined), icon: renderIcon(LogoutOutlined),
onClick: async () => { onClick: async () => {
await userStore try {
.userLogout() await userStore.userLogout();
.then(() => { router.push('/login');
window.$loadingBar.finish(); } catch (_) {
router.push('/login'); window.$message.error('退出登录失败');
}) }
.catch(() => window.$message.error('退出登录失败'));
}, },
}, },
]); ]);
@@ -175,7 +183,10 @@ const openSettingsDrawer = () => {
<NLayout :native-scrollbar="false"> <NLayout :native-scrollbar="false">
<NLayoutHeader bordered class="app-layout-header"> <NLayoutHeader bordered class="app-layout-header">
<NFlex justify="space-between" align="center" :size="8" style="width: 100%; height: 100%"> <NFlex justify="space-between" align="center" :size="8" style="width: 100%; height: 100%">
<span style="font-size: 16px; font-weight: 500; margin-left: 16px; cursor: pointer" @click="toDashboardPage">网络设备管理平台</span> <NFlex>
<div style="font-size: 16px; font-weight: 500; margin-left: 16px; cursor: pointer" @click="toDashboardPage">网络设备管理平台</div>
<NButton text size="tiny" :loading="fetchingCount > 0" />
</NFlex>
<NFlex align="center" :size="0" style="height: 100%"> <NFlex align="center" :size="0" style="height: 100%">
<!-- <ThemeSwitch /> --> <!-- <ThemeSwitch /> -->
<NDropdown trigger="hover" show-arrow :options="dropdownOptions" @select="selectDropdownOption"> <NDropdown trigger="hover" show-arrow :options="dropdownOptions" @select="selectDropdownOption">

View File

@@ -2,9 +2,9 @@
import type { NdmDeviceAlarmLogResultVO } from '@/apis/models'; import type { NdmDeviceAlarmLogResultVO } from '@/apis/models';
import { ndmDeviceAlarmLogDefaultExportByTemplate, postNdmDeviceAlarmLogPage, putNdmDeviceAlarmLog } from '@/apis/requests'; import { ndmDeviceAlarmLogDefaultExportByTemplate, postNdmDeviceAlarmLogPage, putNdmDeviceAlarmLog } from '@/apis/requests';
import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/components/helper'; import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/components/helper';
import { FaultLevel } from '@/enums/fault-level';
import { AlarmType } from '@/enums/alarm-type'; import { AlarmType } from '@/enums/alarm-type';
import { DeviceType, DeviceTypeCode, DeviceTypeName, type DeviceTypeVal } from '@/enums/device-type'; import { DeviceType, DeviceTypeCode, DeviceTypeName, type DeviceTypeVal } from '@/enums/device-type';
import { FaultLevel } from '@/enums/fault-level';
import { useCurrentAlarmsStore } from '@/stores/current-alarms'; import { useCurrentAlarmsStore } from '@/stores/current-alarms';
import { useStationStore } from '@/stores/station'; import { useStationStore } from '@/stores/station';
import { downloadByData } from '@/utils/download'; import { downloadByData } from '@/utils/download';
@@ -192,7 +192,7 @@ const tablePagination = reactive<PaginationProps>({
}, },
}); });
const { mutate: getAlarmList, isPending: isTableLoading } = useMutation({ const { mutate: getAlarmList, isPending: tableLoading } = useMutation({
mutationFn: async () => { mutationFn: async () => {
const res = await postNdmDeviceAlarmLogPage('', { const res = await postNdmDeviceAlarmLogPage('', {
model: {}, model: {},
@@ -252,7 +252,7 @@ const { mutate: confirmAlarm } = useMutation({
}, },
}); });
const { mutate: downloadTableData, isPending: isDownloading } = useMutation({ const { mutate: exportTableData, isPending: exporting } = useMutation({
mutationFn: async () => { mutationFn: async () => {
const data = await ndmDeviceAlarmLogDefaultExportByTemplate('', { const data = await ndmDeviceAlarmLogDefaultExportByTemplate('', {
model: {}, model: {},
@@ -274,8 +274,6 @@ const { mutate: downloadTableData, isPending: isDownloading } = useMutation({
}, },
}); });
const exportTableData = () => downloadTableData();
onBeforeMount(() => getAlarmList()); onBeforeMount(() => getAlarmList());
</script> </script>
@@ -349,7 +347,7 @@ onBeforeMount(() => getAlarmList());
<NGridItem> <NGridItem>
<NSpace> <NSpace>
<NButton @click="onClickReset">重置</NButton> <NButton @click="onClickReset">重置</NButton>
<NButton type="primary" :loading="isTableLoading" @click="onClickQuery">查询</NButton> <NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
</NSpace> </NSpace>
</NGridItem> </NGridItem>
</NGrid> </NGrid>
@@ -360,13 +358,13 @@ onBeforeMount(() => getAlarmList());
<div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px"> <div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px">
<div style="font-size: medium">设备告警列表</div> <div style="font-size: medium">设备告警列表</div>
<NSpace style="margin-left: auto"> <NSpace style="margin-left: auto">
<NButton type="primary" :loading="isDownloading" @click="exportTableData">导出</NButton> <NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton>
</NSpace> </NSpace>
</div> </div>
<!-- 表格区域填满剩余空间 --> <!-- 表格区域填满剩余空间 -->
<div style="flex: 1 1 auto; min-height: 0; padding: 8px"> <div style="flex: 1 1 auto; min-height: 0; padding: 8px">
<NDataTable :loading="isTableLoading" :columns="tableColumns" :data="tableData" :pagination="tablePagination" :single-line="false" remote flex-height style="height: 100%" /> <NDataTable :loading="tableLoading" :columns="tableColumns" :data="tableData" :pagination="tablePagination" :single-line="false" remote flex-height style="height: 100%" />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,56 +1,42 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Station } from '@/apis/domains'; import type { Station } from '@/apis/domains';
import DeviceAlarmDetailModal from '@/components/dashboard-page/device-alarm-detail-modal.vue'; import { ndmExportDevices } from '@/apis/requests';
import DeviceParamsConfigModal from '@/components/dashboard-page/device-params-config-modal.vue'; import { useLineAlarmsQuery, useLineDevicesQuery } from '@/composables/query';
import OfflineDeviceDetailModal from '@/components/dashboard-page/offline-device-detail-modal.vue'; import { useLayoutStore } from '@/stores/layout';
import StationCard from '@/components/dashboard-page/station-card.vue'; import { useLineAlarmsStore } from '@/stores/line-alarms';
import { useLineAlarmCountsQuery, useLineDevicesQuery } from '@/composables/query';
import { useStationStore } from '@/stores/station';
import { useLineDevicesStore } from '@/stores/line-devices'; import { useLineDevicesStore } from '@/stores/line-devices';
import { useStationStore } from '@/stores/station';
import { downloadByData } from '@/utils/download';
import { useMutation } from '@tanstack/vue-query';
import { NGrid, NGi } from 'naive-ui'; import { NGrid, NGi } from 'naive-ui';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { useLineAlarmCountsStore } from '@/stores/line-alarm-counts';
import { useLayoutStore } from '@/stores/layout';
import DeviceStatistic from '@/components/dashboard-page/device-statistic.vue';
import { useMutation } from '@tanstack/vue-query';
import { ndmExportDevices } from '@/apis/requests';
import { downloadByData } from '@/utils/download';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import DeviceAlarmDetailModal from '@/components/dashboard-page/device-alarm-detail-modal.vue';
import DeviceParamsConfigModal from '@/components/dashboard-page/device-params-config-modal.vue';
import DeviceStatisticCard from '@/components/dashboard-page/device-statistic-card.vue';
import OfflineDeviceDetailModal from '@/components/dashboard-page/offline-device-detail-modal.vue';
import StationCard from '@/components/dashboard-page/station-card.vue';
const stationStore = useStationStore(); const stationStore = useStationStore();
const { stationList } = storeToRefs(stationStore); const { stationList } = storeToRefs(stationStore);
const lineDevicesStore = useLineDevicesStore(); const lineDevicesStore = useLineDevicesStore();
const { lineDevices } = storeToRefs(lineDevicesStore); const { lineDevices } = storeToRefs(lineDevicesStore);
const lineAlarmCountsStore = useLineAlarmCountsStore(); const lineAlarmsStore = useLineAlarmsStore();
const { lineAlarmCounts } = storeToRefs(lineAlarmCountsStore); const { lineAlarmCounts } = storeToRefs(lineAlarmsStore);
const { isFetching: lineDevicesFetching, error: lineDevicesQueryError } = useLineDevicesQuery(); const { error: lineDevicesQueryError } = useLineDevicesQuery();
const { isFetching: lineAlarmCountsFetching, error: lineAlarmCountsQueryError } = useLineAlarmCountsQuery(); const { error: lineAlarmsQueryError } = useLineAlarmsQuery();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const { stationLayoutGridCols } = storeToRefs(layoutStore); const { stationLayoutGridCols } = storeToRefs(layoutStore);
// 当设备查询或告警查询正在进行时,显示加载条 watch([lineDevicesQueryError, lineAlarmsQueryError], ([newLineDevicesQueryError, newLineAlarmsQueryError]) => {
watch(
[lineDevicesFetching, lineAlarmCountsFetching],
([devicesFetching, alarmCountsFetching]) => {
if (devicesFetching || alarmCountsFetching) {
window.$loadingBar.start();
} else {
window.$loadingBar.finish();
}
},
{
immediate: true,
},
);
watch([lineDevicesQueryError, lineAlarmCountsQueryError], ([newLineDevicesQueryError, newLineAlarmCountsQueryError]) => {
if (newLineDevicesQueryError) { if (newLineDevicesQueryError) {
window.$message.error(newLineDevicesQueryError.message); window.$message.error(newLineDevicesQueryError.message);
} }
if (newLineAlarmCountsQueryError) { if (newLineAlarmsQueryError) {
window.$message.error(newLineAlarmCountsQueryError.message); window.$message.error(newLineAlarmsQueryError.message);
} }
}); });
@@ -95,7 +81,7 @@ const openDeviceParamsConfigModal = (station: Station) => {
</script> </script>
<template> <template>
<DeviceStatistic <DeviceStatisticCard
:station-list="stationList" :station-list="stationList"
:line-devices="lineDevices" :line-devices="lineDevices"
:button-loading="exporting" :button-loading="exporting"

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useQueryControlStore } from '@/stores/query-control';
import { NLayout, NLayoutContent } from 'naive-ui'; import { NLayout, NLayoutContent } from 'naive-ui';
import { onBeforeUnmount } from 'vue'; import { onBeforeUnmount } from 'vue';
import { useQueryControlStore } from '@/stores/query-control';
const queryControlStore = useQueryControlStore(); const queryControlStore = useQueryControlStore();
queryControlStore.disablePolling(); queryControlStore.disablePolling();

View File

@@ -1,20 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { useDeviceSelection } from '@/composables/device'; import { useDeviceSelection } from '@/composables/device';
import { useLineDevicesQuery, type LineDevices } from '@/composables/query'; import { useLineDevicesQuery } from '@/composables/query';
import { useLineDevicesStore } from '@/stores/line-devices'; import { useLineDevicesStore } from '@/stores/line-devices';
import { useStationStore } from '@/stores/station'; import { useStationStore } from '@/stores/station';
import { ChevronBack } from '@vicons/ionicons5'; import { ChevronBack } from '@vicons/ionicons5';
import { watchDebounced } from '@vueuse/core'; import { watchDebounced } from '@vueuse/core';
import { NEmpty, NIcon, NLayout, NLayoutContent, NLayoutSider, NPageHeader, NScrollbar, NSpin } from 'naive-ui'; import { NEmpty, NIcon, NLayout, NLayoutContent, NLayoutSider, NPageHeader, NScrollbar, NSpin } from 'naive-ui';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onMounted, ref, watch } from 'vue'; import { onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import DeviceTree from '@/components/device-page/device-tree.vue'; import DeviceTree from '@/components/device-page/device-tree.vue';
import DeviceRenderer from '@/components/device-page/device-renderer.vue'; import DeviceRenderer from '@/components/device-page/device-renderer.vue';
// 数据获取 // 数据获取
const { isFetching: lineDevicesFetching, error: lineDevicesQueryError } = useLineDevicesQuery(); const { error: lineDevicesQueryError } = useLineDevicesQuery();
const stationStore = useStationStore(); const stationStore = useStationStore();
const { stationList } = storeToRefs(stationStore); const { stationList } = storeToRefs(stationStore);
const lineDevicesStore = useLineDevicesStore(); const lineDevicesStore = useLineDevicesStore();
@@ -32,21 +32,6 @@ onMounted(() => {
initFromRoute(lineDevices.value); initFromRoute(lineDevices.value);
}); });
// 加载条控制
watch(
lineDevicesFetching,
(fetching) => {
if (fetching) {
window.$loadingBar.start();
} else {
window.$loadingBar.finish();
}
},
{
immediate: true,
},
);
// 当设备数据更新时,需要重新配置设备树 // 当设备数据更新时,需要重新配置设备树
watchDebounced( watchDebounced(
lineDevices, lineDevices,
@@ -98,11 +83,6 @@ watch(lineDevicesQueryError, (newLineDevicesQueryError) => {
<DeviceRenderer :station-code="selectedStationCode" :device="selectedDevice" /> <DeviceRenderer :station-code="selectedStationCode" :device="selectedDevice" />
</NScrollbar> </NScrollbar>
</template> </template>
<template v-else-if="lineDevicesFetching">
<div style="width: 100%; height: 100%; display: flex">
<NSpin style="margin: auto" />
</div>
</template>
<template v-else> <template v-else>
<div style="width: 100%; height: 100%; display: flex"> <div style="width: 100%; height: 100%; display: flex">
<NEmpty description="选择设备查看诊断数据" style="margin: auto" /> <NEmpty description="选择设备查看诊断数据" style="margin: auto" />

View File

@@ -30,7 +30,6 @@ const vimpOperationTypeOptions: SelectOption[] = [
<script setup lang="ts"> <script setup lang="ts">
import type { NdmVimpLogResultVO } from '@/apis/models/device'; import type { NdmVimpLogResultVO } from '@/apis/models/device';
import { ndmVimpLogDefaultExportByTemplate, postNdmVimpLogPage } from '@/apis/requests'; import { ndmVimpLogDefaultExportByTemplate, postNdmVimpLogPage } from '@/apis/requests';
import { useStationListQuery } from '@/composables/query';
import { useStationStore } from '@/stores/station'; import { useStationStore } from '@/stores/station';
import { downloadByData } from '@/utils/download'; import { downloadByData } from '@/utils/download';
import { useMutation } from '@tanstack/vue-query'; import { useMutation } from '@tanstack/vue-query';
@@ -57,7 +56,6 @@ import {
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed, h, onMounted, reactive, ref, watch, watchEffect } from 'vue'; import { computed, h, onMounted, reactive, ref, watch, watchEffect } from 'vue';
useStationListQuery();
const stationStore = useStationStore(); const stationStore = useStationStore();
const { stationList, onlineStationList } = storeToRefs(stationStore); const { stationList, onlineStationList } = storeToRefs(stationStore);
@@ -187,7 +185,7 @@ const tablePagination = reactive<PaginationProps>({
}, },
}); });
const { mutate: getVimpLogList, isPending: isTableLoading } = useMutation({ const { mutate: getVimpLogList, isPending: tableLoading } = useMutation({
mutationFn: async () => { mutationFn: async () => {
if (!searchFields.stationCode) throw Error('请选择车站'); if (!searchFields.stationCode) throw Error('请选择车站');
const res = await postNdmVimpLogPage(searchFields.stationCode, { const res = await postNdmVimpLogPage(searchFields.stationCode, {
@@ -228,7 +226,7 @@ const onClickQuery = () => {
getVimpLogList(); getVimpLogList();
}; };
const { mutate: downloadTableData, isPending: isDownloading } = useMutation({ const { mutate: exportTableData, isPending: exporting } = useMutation({
mutationFn: async () => { mutationFn: async () => {
if (!searchFields.stationCode) throw Error('请选择车站'); if (!searchFields.stationCode) throw Error('请选择车站');
const data = await ndmVimpLogDefaultExportByTemplate(searchFields.stationCode, { const data = await ndmVimpLogDefaultExportByTemplate(searchFields.stationCode, {
@@ -251,8 +249,6 @@ const { mutate: downloadTableData, isPending: isDownloading } = useMutation({
}, },
}); });
const exportTableData = () => downloadTableData();
// 进入页面时选择首个在线的车站 // 进入页面时选择首个在线的车站
onMounted(() => { onMounted(() => {
// if (onlineStationList.value.length > 0) { // if (onlineStationList.value.length > 0) {
@@ -316,7 +312,7 @@ watchEffect(() => {
<NGridItem> <NGridItem>
<NSpace> <NSpace>
<NButton @click="onClickReset">重置</NButton> <NButton @click="onClickReset">重置</NButton>
<NButton type="primary" :loading="isTableLoading" @click="onClickQuery">查询</NButton> <NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
</NSpace> </NSpace>
</NGridItem> </NGridItem>
</NGrid> </NGrid>
@@ -327,13 +323,13 @@ watchEffect(() => {
<div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px"> <div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px">
<div style="font-size: medium">视频平台日志</div> <div style="font-size: medium">视频平台日志</div>
<NSpace style="margin-left: auto"> <NSpace style="margin-left: auto">
<NButton type="primary" :loading="isDownloading" @click="exportTableData">导出</NButton> <NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton>
</NSpace> </NSpace>
</div> </div>
<!-- 表格区域:填满剩余空间 --> <!-- 表格区域:填满剩余空间 -->
<div style="flex: 1 1 auto; min-height: 0; padding: 8px"> <div style="flex: 1 1 auto; min-height: 0; padding: 8px">
<NDataTable remote :columns="tableColumns" :data="tableData" :pagination="tablePagination" :loading="isTableLoading" :single-line="false" flex-height style="height: 100%" /> <NDataTable remote :columns="tableColumns" :data="tableData" :pagination="tablePagination" :loading="tableLoading" :single-line="false" flex-height style="height: 100%" />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,11 +0,0 @@
import type { LineAlarmCounts } from '@/composables/query';
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useLineAlarmCountsStore = defineStore('ndm-line-alarm-counts-store', () => {
const lineAlarmCounts = ref<LineAlarmCounts>({});
return {
lineAlarmCounts,
};
});

View File

@@ -1,11 +1,11 @@
import type { LineAlarms } from '@/composables/query'; import type { LineAlarmCounts } from '@/composables/query';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ref } from 'vue'; import { ref } from 'vue';
export const useLineAlarmsStore = defineStore('ndm-line-alarms-store', () => { export const useLineAlarmsStore = defineStore('ndm-line-alarms-store', () => {
const lineAlarms = ref<LineAlarms>({}); const lineAlarmCounts = ref<LineAlarmCounts>({});
return { return {
lineAlarms, lineAlarmCounts,
}; };
}); });