Files
ndm-web-platform/src/pages/vimp/components/alarm-tree.vue
T
yangsy 65603d469d refactor(vimp): 抽离并重构vimp的摄像机、告警store与树形类型
- 新增camera-store.ts与alarm-store.ts,封装摄像机、告警业务逻辑为独立Pinia store
- 重构tree.ts中的树形节点类型命名与关联判断函数
- 更新stores/index.ts的导出文件路径
- 移除alarm-tree.vue中的冗余类型导入
2026-05-27 21:29:59 +08:00

121 lines
3.6 KiB
Vue

<script setup lang="ts">
import { NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type TreeProps } from 'naive-ui';
import { h, type CSSProperties } from 'vue';
import { useAlarmStore } from '../stores';
import { storeToRefs } from 'pinia';
import { useDeviceCenterQuery } from '../apis';
import { isAlarmNode, isAlarmSiteNode, isAlarmAreaNode } from '../types';
const { isLoading } = useDeviceCenterQuery();
const alarmStore = useAlarmStore();
const { lineTabPanes } = storeToRefs(alarmStore);
const selectedDeviceGbCode = defineModel<[string]>('selectedDeviceGbCode', { default: () => [''] });
const overrideNodeClickBehavior: TreeOverrideNodeClickBehavior = ({ option }) => {
const hasChildren = (option.children?.length ?? 0) > 0;
if (hasChildren) {
return 'toggleExpand';
} else {
return 'none';
}
};
const renderNodeLabel: TreeProps['renderLabel'] = ({ option }) => {
// 是车站节点
if (isAlarmSiteNode(option)) {
const siteOnline = option.online;
const siteNodeStyle: CSSProperties = {
opacity: siteOnline ? 1 : 0.5,
};
return h('div', { style: siteNodeStyle }, option.label);
}
// 是中间节点(一级/二级区域)
if (isAlarmAreaNode(option)) {
const site = option.site;
const nodeStyle: CSSProperties = {
opacity: site.online ? 1 : 0.5,
};
return h('div', { style: nodeStyle }, option.label);
}
// 是警报器节点
if (isAlarmNode(option)) {
const alarm = option.alarm;
const site = option.site;
const alarmOnline = () => {
return alarm.status === 1 && site.online;
};
const alarmNodeStyle: CSSProperties = {
opacity: alarmOnline() ? 1 : 0.5,
cursor: alarmOnline() ? 'pointer' : 'not-allowed',
};
return h(
'div',
{
style: alarmNodeStyle,
draggable: alarm.status === 1,
onDblclick() {
if (alarm.status === 0) return;
selectedDeviceGbCode.value = [alarm.code];
window.$message.info(`查看警报器:${JSON.stringify({ code: alarm.code, name: alarm.name })}`);
},
onDragstart(event) {
if (alarm.status === 0) return;
console.log(event);
event.dataTransfer?.setData('type', 'alarm');
event.dataTransfer?.setData('code', alarm.code);
event.dataTransfer?.setData('name', alarm.name);
},
},
alarm.name,
);
}
// 其他节点(兜底,理论上不会走到这里)
return option.label;
};
</script>
<template>
<template v-if="isLoading">
<div>loading...</div>
</template>
<template v-else-if="lineTabPanes.length === 1">
<NTree
block-line
block-node
show-line
virtual-scroll
style="height: 100%"
v-model:selected-keys="selectedDeviceGbCode"
:data="lineTabPanes.at(0)?.alarmTree"
:override-default-node-click-behavior="overrideNodeClickBehavior"
:render-label="renderNodeLabel"
/>
</template>
<template v-if="lineTabPanes.length > 1">
<NTabs :type="'card'" :placement="'left'" style="height: 100%">
<NTabPane v-for="{ lineCode, lineName, alarmTree } in lineTabPanes" :key="lineCode" :name="lineName" :tab="lineName">
<NTree
block-line
block-node
show-line
virtual-scroll
style="height: 100%"
v-model:selected-keys="selectedDeviceGbCode"
:data="alarmTree"
:override-default-node-click-behavior="overrideNodeClickBehavior"
:render-label="renderNodeLabel"
/>
</NTabPane>
</NTabs>
</template>
</template>
<style scoped></style>