<template>
|
|
<aside class="portal-tree">
|
|
<div class="tree-title">
|
|
<span><i class="el-icon-s-platform" /> 设备列表</span>
|
|
<strong>{{ channelNodes.length }}</strong>
|
|
</div>
|
|
<div class="tree-filters">
|
|
<el-select v-model="onlineStatus" size="small" placeholder="状态" clearable>
|
|
<el-option label="全部" value="" />
|
|
<el-option label="在线" value="1" />
|
|
<el-option label="离线" value="0" />
|
|
</el-select>
|
|
<el-input
|
|
v-model.trim="keywords"
|
|
size="small"
|
|
clearable
|
|
prefix-icon="el-icon-search"
|
|
placeholder="搜索"
|
|
@input="refreshTree"
|
|
/>
|
|
<el-button size="small" icon="el-icon-refresh" title="刷新列表" @click="loadTree" />
|
|
</div>
|
|
<div v-loading="internalLoading" class="tree-content">
|
|
<el-tree
|
|
ref="treeRef"
|
|
:data="treeData"
|
|
:props="treeProps"
|
|
node-key="id"
|
|
highlight-current
|
|
:default-expanded-keys="expandedKeys"
|
|
@node-click="handleNodeClick"
|
|
>
|
|
<el-tooltip
|
|
slot-scope="{ data }"
|
|
:content="data.label || data.name || ''"
|
|
placement="top-start"
|
|
:offset="6"
|
|
:open-delay="300"
|
|
:append-to-body="true"
|
|
popper-class="portal-tree-tooltip"
|
|
effect="dark"
|
|
>
|
|
<span class="custom-tree-node">
|
|
<i :class="nodeIcon(data)" />
|
|
<span class="node-label">{{ data.label || data.name }}</span>
|
|
</span>
|
|
</el-tooltip>
|
|
</el-tree>
|
|
<div v-if="!internalLoading && !treeData.length" class="tree-empty">
|
|
<i class="el-icon-video-camera-solid" />
|
|
<span>没有匹配的设备通道</span>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</template>
|
|
|
|
<script>
|
|
import { getChannelTree } from "@/api/portal";
|
|
|
|
function flatten(nodes, result = []) {
|
|
(nodes || []).forEach(node => {
|
|
result.push(node);
|
|
if (Array.isArray(node.children)) flatten(node.children, result);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function filterTree(nodes, keyword, onlineStatus) {
|
|
return (nodes || []).reduce((result, node) => {
|
|
const children = filterTree(node.children, keyword, onlineStatus);
|
|
const text = `${node.label || ""} ${node.name || ""} ${node.stationName || ""} ${node.patroldeviceCode || ""} ${node.channelCode || ""}`.toLowerCase();
|
|
const matchesKeyword = !keyword || text.includes(keyword.toLowerCase());
|
|
const matchesStatus = node.areaFlag === "Yes" || !onlineStatus || String(node.patroldeviceOnlineStatus || "") === onlineStatus;
|
|
if ((matchesKeyword && matchesStatus) || children.length) result.push({ ...node, children });
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
export default {
|
|
name: "PortalDeviceTree",
|
|
props: {
|
|
selectedCode: { type: String, default: "" }
|
|
},
|
|
data() {
|
|
return {
|
|
treeNodes: [],
|
|
treeData: [],
|
|
expandedKeys: [],
|
|
keywords: "",
|
|
onlineStatus: "1",
|
|
internalLoading: false,
|
|
treeProps: { children: "children", label: "label" }
|
|
};
|
|
},
|
|
computed: {
|
|
channelNodes() {
|
|
return flatten(this.treeNodes).filter(node => node.channelFlag);
|
|
}
|
|
},
|
|
watch: {
|
|
onlineStatus: "refreshTree",
|
|
selectedCode(value) {
|
|
const node = this.channelNodes.find(item => this.toCameraCode(item) === value);
|
|
if (node && this.$refs.treeRef) this.$refs.treeRef.setCurrentKey(node.id);
|
|
}
|
|
},
|
|
mounted() {
|
|
this.loadTree();
|
|
},
|
|
methods: {
|
|
async loadTree() {
|
|
this.internalLoading = true;
|
|
try {
|
|
const response = await getChannelTree({ onlineStatus: this.onlineStatus, patroldeviceTypeFlag: "ipc" });
|
|
this.treeNodes = Array.isArray(response && response.data) ? response.data : [];
|
|
this.expandedKeys = [];
|
|
const walk = nodes => (nodes || []).forEach(node => {
|
|
if (node.children && node.children.length) {
|
|
this.expandedKeys.push(node.id);
|
|
walk(node.children);
|
|
}
|
|
});
|
|
walk(this.treeNodes);
|
|
this.refreshTree();
|
|
this.$emit("channels-loaded", this.channelNodes.map(this.normalizeChannel));
|
|
} catch (error) {
|
|
this.treeNodes = [];
|
|
this.treeData = [];
|
|
this.$message.error(error.message || "设备树加载失败");
|
|
} finally {
|
|
this.internalLoading = false;
|
|
}
|
|
},
|
|
refreshTree() {
|
|
this.treeData = filterTree(this.treeNodes, this.keywords, this.onlineStatus);
|
|
},
|
|
toCameraCode(node) {
|
|
return node.cameraCode || node.channelCode || "";
|
|
},
|
|
normalizeChannel(node) {
|
|
return {
|
|
...node,
|
|
cameraCode: this.toCameraCode(node),
|
|
cameraName: node.channelName || node.label || node.name,
|
|
name: node.channelName || node.label || node.name,
|
|
channel: node.channelCode
|
|
};
|
|
},
|
|
handleNodeClick(node) {
|
|
if (!node.channelFlag) return;
|
|
this.$emit("open", this.normalizeChannel(node));
|
|
},
|
|
nodeIcon(node) {
|
|
if (node.channelFlag) return "el-icon-video-camera";
|
|
if (node.patroldeviceType === 13) return "el-icon-position";
|
|
if (node.areaFlag === "Yes") return "el-icon-location-outline";
|
|
return "el-icon-connection";
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.portal-tree {
|
|
width: 100%;
|
|
min-width: 260px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid rgba(63, 127, 216, 0.72);
|
|
border-radius: 2px;
|
|
background: transparent;
|
|
color: #d7e9ff;
|
|
box-sizing: border-box;
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif !important;
|
|
}
|
|
|
|
.tree-title {
|
|
height: 46px;
|
|
padding: 0 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
border-bottom: 1px solid rgba(41, 139, 255, 0.35);
|
|
color: #54ffff;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.tree-title i {
|
|
margin-right: 6px;
|
|
}
|
|
|
|
.tree-title strong {
|
|
min-width: 28px;
|
|
text-align: center;
|
|
color: #b7d8ff;
|
|
}
|
|
|
|
.tree-filters {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 10px 8px;
|
|
}
|
|
|
|
.tree-filters .el-select {
|
|
width: 78px;
|
|
}
|
|
|
|
.tree-filters .el-input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.tree-filters .el-button {
|
|
flex: 0 0 32px;
|
|
padding: 0;
|
|
border-color: #2787ed;
|
|
color: #54ffff;
|
|
background: #0a2b67;
|
|
}
|
|
|
|
.tree-content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: auto;
|
|
padding: 8px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #2f7fe8 rgba(6, 27, 79, 0.7);
|
|
}
|
|
|
|
.tree-content::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
.tree-content::-webkit-scrollbar-track {
|
|
background: rgba(6, 27, 79, 0.7);
|
|
}
|
|
|
|
.tree-content::-webkit-scrollbar-thumb {
|
|
background: #2f7fe8;
|
|
border: 1px solid #0d327d;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.custom-tree-node {
|
|
display: flex;
|
|
align-items: center;
|
|
width: max-content;
|
|
min-width: 100%;
|
|
gap: 6px;
|
|
font-size: 14px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.node-label {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tree-empty {
|
|
padding: 40px 0;
|
|
text-align: center;
|
|
color: #7298c3;
|
|
}
|
|
|
|
.tree-empty i {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
font-size: 28px;
|
|
}
|
|
|
|
::v-deep .el-tree {
|
|
display: inline-block;
|
|
width: max-content;
|
|
min-width: 100%;
|
|
background: transparent;
|
|
color: inherit;
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif !important;
|
|
font-size: 14px;
|
|
}
|
|
|
|
::v-deep .el-tree-node__content,
|
|
::v-deep .custom-tree-node,
|
|
::v-deep .node-label {
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif !important;
|
|
font-weight: 400 !important;
|
|
}
|
|
|
|
::v-deep .el-tree-node__content {
|
|
display: flex;
|
|
height: 24px;
|
|
}
|
|
|
|
::v-deep .el-tree-node__expand-icon.is-leaf {
|
|
display: inline-block;
|
|
width: 12px;
|
|
}
|
|
|
|
::v-deep .el-tree-node__content:hover,
|
|
::v-deep .el-tree-node:focus > .el-tree-node__content {
|
|
background: transparent;
|
|
}
|
|
|
|
::v-deep .el-tree-node.is-current > .el-tree-node__content {
|
|
color: #54ffff;
|
|
background: transparent;
|
|
}
|
|
</style>
|
|
|
|
<style lang="scss">
|
|
.portal-tree-tooltip.el-tooltip__popper {
|
|
color: #eaf6ff;
|
|
background: #176bc2;
|
|
border: 1px solid #4ba3ff;
|
|
}
|
|
|
|
.portal-tree-tooltip.el-tooltip__popper[x-placement^="top"] .popper__arrow {
|
|
border-top-color: #176bc2;
|
|
}
|
|
</style>
|