refactor(vimp-alarm): 重构告警树数据构建逻辑并优化节点显示
- 统一区域数据处理流程,使用compiledCodeAreas替代原有的四个独立区域数组 - 使用Map索引替代线性查找,提升数据构建性能 - 将节点的后缀/前缀渲染从节点属性迁移至Tree组件的renderPrefix和renderSuffix方法 - 为区域节点添加areaLevel字段以适配不同层级的样式 - 为告警节点添加警笛图标前缀,为各层级节点添加统计信息后缀
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type TreeProps } from 'naive-ui';
|
import { NIcon, NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type TreeProps } from 'naive-ui';
|
||||||
import { h, type CSSProperties } from 'vue';
|
import { h, type CSSProperties } from 'vue';
|
||||||
import { useAlarmStore, useResourcePanelStore } from '../stores';
|
import { useAlarmStore, useResourcePanelStore } from '../stores';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useDeviceCenterQuery } from '../composables';
|
import { useDeviceCenterQuery } from '../composables';
|
||||||
import { isAlarmNode, isAlarmSiteNode, isAlarmAreaNode } from '../types';
|
import { isAlarmNode, isAlarmSiteNode, isAlarmAreaNode } from '../types';
|
||||||
|
import { SirenIcon } from 'lucide-vue-next';
|
||||||
|
|
||||||
const { isLoading } = useDeviceCenterQuery();
|
const { isLoading } = useDeviceCenterQuery();
|
||||||
|
|
||||||
@@ -77,6 +78,26 @@ const renderNodeLabel: TreeProps['renderLabel'] = ({ option }) => {
|
|||||||
return option.label;
|
return option.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderNodePrefix: TreeProps['renderPrefix'] = ({ option }) => {
|
||||||
|
if (!isAlarmNode(option)) return null;
|
||||||
|
return h(NIcon, () => h(SirenIcon));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNodeSuffix: TreeProps['renderSuffix'] = ({ option }) => {
|
||||||
|
if (isAlarmSiteNode(option)) {
|
||||||
|
const { online, offline, total } = option.stats;
|
||||||
|
return `(${online}/${offline}/${total})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAlarmAreaNode(option)) {
|
||||||
|
const { online, offline, total } = option.stats;
|
||||||
|
const suffixStyle: CSSProperties = option.areaLevel === 1 ? { marginRight: '8px', opacity: 0.6 } : { marginRight: '16px', opacity: 0.4 };
|
||||||
|
return h('div', { style: suffixStyle }, `(${online}/${offline}/${total})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const resourcePanelStore = useResourcePanelStore();
|
const resourcePanelStore = useResourcePanelStore();
|
||||||
const { searchPattern } = storeToRefs(resourcePanelStore);
|
const { searchPattern } = storeToRefs(resourcePanelStore);
|
||||||
|
|
||||||
@@ -98,6 +119,8 @@ const searchFilter: TreeProps['filter'] = (pattern, node) => {
|
|||||||
virtual-scroll
|
virtual-scroll
|
||||||
style="height: 100%"
|
style="height: 100%"
|
||||||
:render-label="renderNodeLabel"
|
:render-label="renderNodeLabel"
|
||||||
|
:render-prefix="renderNodePrefix"
|
||||||
|
:render-suffix="renderNodeSuffix"
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
||||||
:default-expand-all="searchPattern.trim().length > 0"
|
:default-expand-all="searchPattern.trim().length > 0"
|
||||||
:show-irrelevant-nodes="false"
|
:show-irrelevant-nodes="false"
|
||||||
@@ -116,6 +139,8 @@ const searchFilter: TreeProps['filter'] = (pattern, node) => {
|
|||||||
virtual-scroll
|
virtual-scroll
|
||||||
style="height: 100%"
|
style="height: 100%"
|
||||||
:render-label="renderNodeLabel"
|
:render-label="renderNodeLabel"
|
||||||
|
:render-prefix="renderNodePrefix"
|
||||||
|
:render-suffix="renderNodeSuffix"
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
||||||
:default-expand-all="false"
|
:default-expand-all="false"
|
||||||
:show-irrelevant-nodes="false"
|
:show-irrelevant-nodes="false"
|
||||||
|
|||||||
@@ -139,10 +139,7 @@ export const useDeviceCenterQuery = () => {
|
|||||||
siteCodeToAlarmsMap,
|
siteCodeToAlarmsMap,
|
||||||
codeLines,
|
codeLines,
|
||||||
codeSites,
|
codeSites,
|
||||||
codeStationAreas,
|
compiledCodeAreas,
|
||||||
codeParkingAreas,
|
|
||||||
codeOccAreas,
|
|
||||||
codeTrainAreas,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,45 +1,52 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import type { VimpChannel, VimpStation } from '../apis';
|
import type { VimpChannel, VimpStation } from '../apis';
|
||||||
import { h, shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
import type { AlarmMainAreaNodeOption, AlarmNodeOption, CodeArea, CodeLines, CodeSites, AlarmLineTabPane, AlarmSiteNodeOption, AlarmSubAreaNodeOption } from '../types';
|
import type { AlarmMainAreaNodeOption, AlarmNodeOption, CodeLines, CodeSites, AlarmLineTabPane, AlarmSiteNodeOption, AlarmSubAreaNodeOption, CompiledCodeAreas } from '../types';
|
||||||
import { NIcon } from 'naive-ui';
|
|
||||||
import { SirenIcon } from 'lucide-vue-next';
|
|
||||||
|
|
||||||
interface BuildLineTabPanesParams {
|
interface BuildLineTabPanesParams {
|
||||||
sites: VimpStation[];
|
sites: VimpStation[];
|
||||||
siteCodeToAlarmsMap: Map<string, VimpChannel[]>;
|
siteCodeToAlarmsMap: Map<string, VimpChannel[]>;
|
||||||
codeLines: CodeLines;
|
codeLines: CodeLines;
|
||||||
codeSites: CodeSites;
|
codeSites: CodeSites;
|
||||||
codeStationAreas: CodeArea[];
|
compiledCodeAreas: CompiledCodeAreas;
|
||||||
codeParkingAreas: CodeArea[];
|
|
||||||
codeOccAreas: CodeArea[];
|
|
||||||
codeTrainAreas: CodeArea[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildMainAreaNodeKey = (siteCode: string, mainAreaCode: string) => `${siteCode}${mainAreaCode}`;
|
||||||
|
const buildSubAreaNodeKey = (siteCode: string, areaCode: string) => `${siteCode}${areaCode}`;
|
||||||
|
|
||||||
export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
||||||
const lineTabPanes = shallowRef<AlarmLineTabPane[]>([]);
|
const lineTabPanes = shallowRef<AlarmLineTabPane[]>([]);
|
||||||
|
|
||||||
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
||||||
const { sites, siteCodeToAlarmsMap, codeLines, codeSites, codeStationAreas, codeParkingAreas, codeOccAreas, codeTrainAreas } = params;
|
const { sites, siteCodeToAlarmsMap, codeLines, codeSites, compiledCodeAreas } = params;
|
||||||
|
|
||||||
// 线路TabPane
|
const result: AlarmLineTabPane[] = [];
|
||||||
const _lineTabPanes: AlarmLineTabPane[] = [];
|
|
||||||
|
// 1. 线路索引 lineCode -> AlarmLineTabPane
|
||||||
|
const linePaneMap = new Map<string, AlarmLineTabPane>();
|
||||||
|
|
||||||
// 遍历所有站点
|
// 遍历所有站点
|
||||||
for (const site of sites) {
|
for (const site of sites) {
|
||||||
|
// 2. 站点节点 siteNode 只在当前轮次中顺序创建,不需要建立索引
|
||||||
|
|
||||||
const lineCode = site.code.substring(0, 3);
|
const lineCode = site.code.substring(0, 3);
|
||||||
const lineName = codeLines[lineCode]?.name ?? '';
|
const lineName = codeLines[lineCode]?.name ?? '';
|
||||||
if (!_lineTabPanes.some((lineNode) => lineNode.lineCode === lineCode)) {
|
|
||||||
_lineTabPanes.push({
|
let linePane = linePaneMap.get(lineCode);
|
||||||
lineCode,
|
if (!linePane) {
|
||||||
lineName,
|
linePane = { lineCode, lineName, alarmTree: [] };
|
||||||
alarmTree: [],
|
linePaneMap.set(lineCode, linePane);
|
||||||
});
|
result.push(linePane);
|
||||||
}
|
}
|
||||||
|
|
||||||
const siteCode = site.code;
|
const siteCode = site.code;
|
||||||
const siteName = codeSites[siteCode]?.name;
|
const siteMeta = codeSites[siteCode];
|
||||||
if (!siteName) continue;
|
if (!siteMeta) continue;
|
||||||
|
const siteName = siteMeta.name;
|
||||||
|
const siteType = siteMeta.type;
|
||||||
|
|
||||||
|
const compiledCodeAreaMaps = compiledCodeAreas[siteType];
|
||||||
|
const mainAreaCodeLength = siteType === 'train' ? 3 : 2;
|
||||||
|
|
||||||
// 构造站点节点
|
// 构造站点节点
|
||||||
const siteNode: AlarmSiteNodeOption = {
|
const siteNode: AlarmSiteNodeOption = {
|
||||||
@@ -49,79 +56,76 @@ export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
|||||||
stats: { online: 0, offline: 0, total: 0 },
|
stats: { online: 0, offline: 0, total: 0 },
|
||||||
online: site.online,
|
online: site.online,
|
||||||
};
|
};
|
||||||
_lineTabPanes.find((lineTabPane) => lineTabPane.lineCode === lineCode)?.alarmTree.push(siteNode);
|
linePane.alarmTree.push(siteNode);
|
||||||
|
|
||||||
// 获取所有警报器
|
// 获取所有警报器
|
||||||
const alarms = siteCodeToAlarmsMap.get(siteCode);
|
const alarms = siteCodeToAlarmsMap.get(siteCode);
|
||||||
if (!alarms || alarms.length === 0) continue;
|
if (!alarms) continue;
|
||||||
|
|
||||||
|
// 3. 1级区域索引 mainAreaNodeKey -> AlarmMainAreaNodeOption
|
||||||
|
// mainAreaNodeKey = ${siteCode}${alarmMainAreaCode}
|
||||||
|
const mainAreaNodeMap = new Map<string, AlarmMainAreaNodeOption>();
|
||||||
|
// 4. 2级区域索引 subAreaNodeKey -> AlarmSubAreaNodeOption
|
||||||
|
// subAreaNodeKey = ${siteCode}${alarmAreaCode}
|
||||||
|
const subAreaNodeMap = new Map<string, AlarmSubAreaNodeOption>();
|
||||||
|
// 5. 警报器索引 subAreaNodeKey -> Set<AlarmGbCode>
|
||||||
|
const subAreaNodeKeyToAlarmGbCodeSetMap = new Map<string, Set<string>>();
|
||||||
|
|
||||||
// 遍历警报器
|
// 遍历警报器
|
||||||
for (const alarm of alarms) {
|
for (const alarm of alarms) {
|
||||||
// 计算相关编码
|
// 计算相关编码
|
||||||
const { code: alarmGbCode, name: alarmName } = alarm;
|
const { code: alarmGbCode, name: alarmName } = alarm;
|
||||||
const alarmSiteCode = alarmGbCode.substring(0, 6);
|
|
||||||
const alarmSiteType = codeSites[alarmSiteCode]?.type;
|
|
||||||
const alarmAreaCode = alarmGbCode.substring(6, 11);
|
const alarmAreaCode = alarmGbCode.substring(6, 11);
|
||||||
const alarmMainAreaCode = alarmAreaCode.slice(0, alarmSiteType === 'train' ? 3 : 2);
|
const alarmMainAreaCode = alarmAreaCode.slice(0, mainAreaCodeLength);
|
||||||
|
|
||||||
// 构造车站/基地/OCC/车次区域
|
// 查找1级区域,如果未找到则跳过该警报器
|
||||||
let siteArea: CodeArea | undefined = undefined;
|
const mainArea = compiledCodeAreaMaps.mainAreaMap.get(alarmMainAreaCode);
|
||||||
if (alarmSiteType === 'station') {
|
if (!mainArea) continue;
|
||||||
siteArea = codeStationAreas.find((area) => area.code === alarmMainAreaCode);
|
|
||||||
} else if (alarmSiteType === 'parking') {
|
|
||||||
siteArea = codeParkingAreas.find((area) => area.code === alarmMainAreaCode);
|
|
||||||
} else if (alarmSiteType === 'occ') {
|
|
||||||
siteArea = codeOccAreas.find((area) => area.code === alarmMainAreaCode);
|
|
||||||
} else if (alarmSiteType === 'train') {
|
|
||||||
siteArea = codeTrainAreas.find((area) => area.code === alarmMainAreaCode);
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!siteArea) continue; // 如果还是未找到区域,则跳过该警报器
|
|
||||||
|
|
||||||
// 构造1级区域节点
|
// 尝试从索引中获取1级区域节点,若不存在则创建
|
||||||
if (!siteNode.children?.find((areaNode) => areaNode.key === `${alarmSiteCode}${alarmMainAreaCode}`)) {
|
const mainAreaNodeKey = buildMainAreaNodeKey(siteCode, alarmMainAreaCode);
|
||||||
const mainAreaNode: AlarmMainAreaNodeOption = {
|
let mainAreaNode = mainAreaNodeMap.get(mainAreaNodeKey);
|
||||||
key: `${alarmSiteCode}${alarmMainAreaCode}`,
|
if (!mainAreaNode) {
|
||||||
label: siteArea.name,
|
mainAreaNode = {
|
||||||
|
key: mainAreaNodeKey,
|
||||||
|
label: mainArea.name,
|
||||||
children: [],
|
children: [],
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
stats: { online: 0, offline: 0, total: 0 },
|
||||||
site: site,
|
site: site,
|
||||||
|
areaLevel: 1,
|
||||||
};
|
};
|
||||||
|
mainAreaNodeMap.set(mainAreaNodeKey, mainAreaNode);
|
||||||
siteNode.children?.push(mainAreaNode);
|
siteNode.children?.push(mainAreaNode);
|
||||||
}
|
}
|
||||||
const targetMainAreaNode = siteNode.children?.find((areaNode) => areaNode.key === `${alarmSiteCode}${alarmMainAreaCode}`);
|
|
||||||
if (!targetMainAreaNode) continue; // 如果1级区域节点不存在,则跳过该警报器
|
|
||||||
|
|
||||||
// 构造2级区域节点
|
// 查找2级区域,如果未找到则跳过该警报器
|
||||||
if (!targetMainAreaNode.children?.find((subAreaNode) => subAreaNode.key === `${alarmSiteCode}${alarmAreaCode}`)) {
|
const subArea = compiledCodeAreaMaps.subAreaMap.get(alarmAreaCode);
|
||||||
let subArea: CodeArea['subs'][number] | undefined = undefined;
|
if (!subArea) continue;
|
||||||
if (alarmSiteType === 'station') {
|
|
||||||
subArea = codeStationAreas.find((area) => area.code === alarmMainAreaCode)?.subs.find((subArea) => subArea.code === alarmAreaCode);
|
|
||||||
} else if (alarmSiteType === 'parking') {
|
|
||||||
subArea = codeParkingAreas.find((area) => area.code === alarmMainAreaCode)?.subs.find((subArea) => subArea.code === alarmAreaCode);
|
|
||||||
} else if (alarmSiteType === 'occ') {
|
|
||||||
subArea = codeOccAreas.find((area) => area.code === alarmMainAreaCode)?.subs.find((subArea) => subArea.code === alarmAreaCode);
|
|
||||||
} else if (alarmSiteType === 'train') {
|
|
||||||
subArea = codeTrainAreas.find((area) => area.code === alarmMainAreaCode)?.subs.find((subArea) => subArea.code === alarmAreaCode);
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!subArea) continue; // 如果还是未找到2级区域,则跳过该警报器
|
|
||||||
|
|
||||||
const subAreaNode: AlarmSubAreaNodeOption = {
|
// 尝试从索引中获取2级区域节点,若不存在则创建
|
||||||
key: `${alarmSiteCode}${alarmAreaCode}`,
|
const subAreaNodeKey = buildSubAreaNodeKey(siteCode, alarmAreaCode);
|
||||||
|
let subAreaNode = subAreaNodeMap.get(subAreaNodeKey);
|
||||||
|
if (!subAreaNode) {
|
||||||
|
subAreaNode = {
|
||||||
|
key: subAreaNodeKey,
|
||||||
label: subArea.name,
|
label: subArea.name,
|
||||||
children: [],
|
children: [],
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
stats: { online: 0, offline: 0, total: 0 },
|
||||||
site: site,
|
site: site,
|
||||||
|
areaLevel: 2,
|
||||||
};
|
};
|
||||||
targetMainAreaNode.children?.push(subAreaNode);
|
subAreaNodeMap.set(subAreaNodeKey, subAreaNode);
|
||||||
|
mainAreaNode.children?.push(subAreaNode);
|
||||||
}
|
}
|
||||||
const subAreaNode = targetMainAreaNode.children?.find((subAreaNode) => subAreaNode.key === `${alarmSiteCode}${alarmAreaCode}`);
|
|
||||||
if (!subAreaNode) continue; // 如果2级区域节点不存在,则跳过该警报器
|
|
||||||
|
|
||||||
// 构造警报器节点
|
// 构造警报器节点
|
||||||
|
let alarmGbCodeSet = subAreaNodeKeyToAlarmGbCodeSetMap.get(subAreaNodeKey);
|
||||||
|
if (!alarmGbCodeSet) {
|
||||||
|
alarmGbCodeSet = new Set<string>();
|
||||||
|
subAreaNodeKeyToAlarmGbCodeSetMap.set(subAreaNodeKey, alarmGbCodeSet);
|
||||||
|
}
|
||||||
|
if (alarmGbCodeSet.has(alarmGbCode)) continue;
|
||||||
|
alarmGbCodeSet.add(alarmGbCode);
|
||||||
const alarmType = alarm.code.substring(11, 14);
|
const alarmType = alarm.code.substring(11, 14);
|
||||||
const alarmNode: AlarmNodeOption = {
|
const alarmNode: AlarmNodeOption = {
|
||||||
key: alarmGbCode,
|
key: alarmGbCode,
|
||||||
@@ -129,49 +133,25 @@ export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
|||||||
type: alarmType,
|
type: alarmType,
|
||||||
alarm: alarm,
|
alarm: alarm,
|
||||||
site: site,
|
site: site,
|
||||||
prefix: () => {
|
|
||||||
return h(NIcon, () => h(SirenIcon));
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加警报器节点到子区域节点
|
|
||||||
if (!subAreaNode.children?.find((alarmNode) => alarmNode.key === alarmGbCode)) {
|
|
||||||
subAreaNode.children?.push(alarmNode);
|
subAreaNode.children?.push(alarmNode);
|
||||||
}
|
|
||||||
|
|
||||||
// 统计站点、区域、子区域的在线/离线/总警报器数量
|
// 统计站点、区域、子区域的在线/离线/总警报器数量
|
||||||
siteNode.stats.total++;
|
siteNode.stats.total++;
|
||||||
targetMainAreaNode.stats.total++;
|
mainAreaNode.stats.total++;
|
||||||
subAreaNode.stats.total++;
|
subAreaNode.stats.total++;
|
||||||
if (alarm.status === 1) {
|
if (alarm.status === 1) {
|
||||||
siteNode.stats.online++;
|
siteNode.stats.online++;
|
||||||
targetMainAreaNode.stats.online++;
|
mainAreaNode.stats.online++;
|
||||||
subAreaNode.stats.online++;
|
subAreaNode.stats.online++;
|
||||||
}
|
} else if (alarm.status === 0) {
|
||||||
if (alarm.status === 0) {
|
|
||||||
siteNode.stats.offline++;
|
siteNode.stats.offline++;
|
||||||
targetMainAreaNode.stats.offline++;
|
mainAreaNode.stats.offline++;
|
||||||
subAreaNode.stats.offline++;
|
subAreaNode.stats.offline++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
siteNode.suffix = () => {
|
|
||||||
const { online, offline, total } = siteNode.stats;
|
|
||||||
return `(${online}/${offline}/${total})`;
|
|
||||||
};
|
|
||||||
siteNode.children?.forEach((areaNode) => {
|
|
||||||
areaNode.suffix = () => {
|
|
||||||
const { online, offline, total } = areaNode.stats;
|
|
||||||
return h('div', { style: { marginRight: '8px', opacity: 0.6 } }, `(${online}/${offline}/${total})`);
|
|
||||||
};
|
|
||||||
areaNode.children?.forEach((subAreaNode) => {
|
|
||||||
subAreaNode.suffix = () => {
|
|
||||||
const { online, offline, total } = subAreaNode.stats;
|
|
||||||
return h('div', { style: { marginRight: '16px', opacity: 0.4 } }, `(${online}/${offline}/${total})`);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
lineTabPanes.value = _lineTabPanes;
|
lineTabPanes.value = result;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -111,12 +111,14 @@ export interface AlarmSubAreaNodeOption extends TreeOption {
|
|||||||
children?: AlarmNodeOption[];
|
children?: AlarmNodeOption[];
|
||||||
stats: CountStats;
|
stats: CountStats;
|
||||||
site: VimpStation;
|
site: VimpStation;
|
||||||
|
areaLevel: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AlarmMainAreaNodeOption extends TreeOption {
|
export interface AlarmMainAreaNodeOption extends TreeOption {
|
||||||
children?: AlarmSubAreaNodeOption[];
|
children?: AlarmSubAreaNodeOption[];
|
||||||
stats: CountStats;
|
stats: CountStats;
|
||||||
site: VimpStation;
|
site: VimpStation;
|
||||||
|
areaLevel: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AlarmSiteNodeOption extends TreeOption {
|
export interface AlarmSiteNodeOption extends TreeOption {
|
||||||
|
|||||||
Reference in New Issue
Block a user