- 更新纵坐标刻度算法,优化小数趋势图范围显示 - 添加稳态趋势图全屏模式和共享工具组件 - 实现多图联动的鼠标悬停竖线同步功能 - 调整主线线宽分档策略,降低最大线宽限制 - 重构稳态趋势工具栏,优化谐波次数选择逻辑 - 添加周时间周期搜索支持和自定义时间范围选择 - 完善稳态数据表格和指示器浮动面板功能 - 优化稳态趋势图性能,添加LTB采样和动画控制 - 修复数据表格打开前的趋势数据验证问题 - 统一时间轴标签格式化和网格对齐处理
276 lines
6.7 KiB
Vue
276 lines
6.7 KiB
Vue
<template>
|
|
<section class="card steady-tree-card" :class="{ 'is-collapsed': collapsed }">
|
|
<div v-show="collapsed" class="collapsed-panel">
|
|
<el-tooltip content="展开设备树" placement="right">
|
|
<el-button class="panel-toggle" type="primary" :icon="ArrowRight" circle @click="emit('toggle')" />
|
|
</el-tooltip>
|
|
</div>
|
|
|
|
<div v-show="!collapsed" class="expanded-panel">
|
|
<div class="panel-header">
|
|
<span class="panel-title">设备树</span>
|
|
<el-tooltip content="收缩设备树" placement="top">
|
|
<el-button class="panel-toggle" type="primary" :icon="ArrowLeft" circle @click="emit('toggle')" />
|
|
</el-tooltip>
|
|
</div>
|
|
|
|
<div class="tree-search-row">
|
|
<el-input
|
|
:model-value="keyword"
|
|
clearable
|
|
placeholder="搜索工程、项目、设备、监测点"
|
|
@update:model-value="handleKeywordChange"
|
|
></el-input>
|
|
<el-button :icon="Refresh" circle :loading="loading" @click="emit('refresh')" />
|
|
</div>
|
|
|
|
<el-scrollbar class="tree-scrollbar">
|
|
<el-tree
|
|
ref="treeRef"
|
|
class="ledger-tree"
|
|
:data="treeData"
|
|
node-key="id"
|
|
show-checkbox
|
|
default-expand-all
|
|
:default-checked-keys="defaultCheckedKeys"
|
|
:expand-on-click-node="false"
|
|
:props="{ label: 'name', children: 'children' }"
|
|
@check="handleCheck"
|
|
>
|
|
<template #default="{ data }">
|
|
<div class="tree-node">
|
|
<span class="node-main">
|
|
<el-icon :class="['node-icon', `is-level-${normalizeLedgerLevel(data.level)}`]">
|
|
<component :is="resolveLedgerIcon(data.level)" />
|
|
</el-icon>
|
|
<span class="node-name">{{ data.name }}</span>
|
|
</span>
|
|
<span class="node-count">
|
|
<template v-if="shouldShowLedgerCount(data)">
|
|
{{ resolveLedgerCountText(data) }}
|
|
</template>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</el-scrollbar>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { nextTick, ref, watch } from 'vue'
|
|
import type { Component } from 'vue'
|
|
import { ArrowLeft, ArrowRight, Folder, Location, Monitor, OfficeBuilding, Refresh } from '@element-plus/icons-vue'
|
|
import type { TreeInstance } from 'element-plus'
|
|
import type { SteadyTrend } from '@/api/steady/steadyTrend/interface'
|
|
|
|
defineOptions({
|
|
name: 'SteadyLedgerTree'
|
|
})
|
|
|
|
const props = defineProps<{
|
|
treeData: SteadyTrend.SteadyLedgerNode[]
|
|
loading: boolean
|
|
keyword: string
|
|
defaultCheckedKeys: string[]
|
|
collapsed: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
refresh: []
|
|
search: [value: string]
|
|
change: [nodes: SteadyTrend.SteadyLedgerNode[]]
|
|
toggle: []
|
|
}>()
|
|
|
|
const treeRef = ref<TreeInstance>()
|
|
type LedgerLevel = SteadyTrend.SteadyLedgerNode['level']
|
|
const ledgerIcons: Record<LedgerLevel, Component> = {
|
|
0: OfficeBuilding,
|
|
1: Folder,
|
|
2: Monitor,
|
|
3: Location
|
|
}
|
|
|
|
const normalizeLedgerLevel = (value: unknown): LedgerLevel => {
|
|
const level = Number(value)
|
|
if (level === 0 || level === 1 || level === 2 || level === 3) {
|
|
return level
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
const resolveLedgerIcon = (value: unknown) => {
|
|
return ledgerIcons[normalizeLedgerLevel(value)]
|
|
}
|
|
|
|
const shouldShowLedgerCount = (data: SteadyTrend.SteadyLedgerNode) => {
|
|
return Number(data.level) < 3 && (Number(data.deviceCount) > 0 || Number(data.lineCount) > 0)
|
|
}
|
|
|
|
const resolveLedgerCountText = (data: SteadyTrend.SteadyLedgerNode) => {
|
|
if (normalizeLedgerLevel(data.level) === 2) {
|
|
return String(Number(data.lineCount || 0))
|
|
}
|
|
|
|
return `${Number(data.deviceCount || 0)} / ${Number(data.lineCount || 0)}`
|
|
}
|
|
|
|
const handleKeywordChange = (value: string) => {
|
|
emit('search', value)
|
|
}
|
|
|
|
const handleCheck = () => {
|
|
emit('change', (treeRef.value?.getCheckedNodes(false, false) || []) as SteadyTrend.SteadyLedgerNode[])
|
|
}
|
|
|
|
const applyDefaultCheckedKeys = async () => {
|
|
await nextTick()
|
|
treeRef.value?.setCheckedKeys(props.defaultCheckedKeys, false)
|
|
}
|
|
|
|
watch(
|
|
() => [props.treeData, props.defaultCheckedKeys],
|
|
() => {
|
|
applyDefaultCheckedKeys()
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.steady-tree-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
min-height: 0;
|
|
padding: 12px;
|
|
}
|
|
|
|
.steady-tree-card:not(.is-collapsed) {
|
|
height: 100%;
|
|
}
|
|
|
|
.steady-tree-card.is-collapsed {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 4;
|
|
align-items: center;
|
|
width: 36px;
|
|
height: 36px;
|
|
min-height: 36px;
|
|
padding: 0;
|
|
overflow: visible;
|
|
background: transparent;
|
|
border: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.collapsed-panel,
|
|
.expanded-panel {
|
|
min-height: 0;
|
|
}
|
|
|
|
.collapsed-panel {
|
|
display: flex;
|
|
width: 36px;
|
|
height: 36px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.expanded-panel {
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.panel-header,
|
|
.tree-node {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
.panel-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
|
|
.panel-toggle {
|
|
flex: none;
|
|
}
|
|
|
|
.tree-search-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.tree-search-row :deep(.el-input) {
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
}
|
|
|
|
.tree-scrollbar {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.tree-node {
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
.node-main {
|
|
display: inline-flex;
|
|
min-width: 0;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.node-icon {
|
|
flex: none;
|
|
font-size: 15px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
|
|
.node-icon.is-level-0 {
|
|
color: var(--el-color-primary);
|
|
}
|
|
|
|
.node-icon.is-level-1 {
|
|
color: var(--el-color-success);
|
|
}
|
|
|
|
.node-icon.is-level-2 {
|
|
color: var(--el-color-warning);
|
|
}
|
|
|
|
.node-icon.is-level-3 {
|
|
color: var(--el-color-info);
|
|
}
|
|
|
|
.node-name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.node-count {
|
|
flex: none;
|
|
color: var(--el-text-color-secondary);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.ledger-tree :deep(.el-tree-node__content) {
|
|
min-width: 0;
|
|
}
|
|
</style>
|