feat(steady): 完善稳态数据视图功能
- 更新纵坐标刻度算法,优化小数趋势图范围显示 - 添加稳态趋势图全屏模式和共享工具组件 - 实现多图联动的鼠标悬停竖线同步功能 - 调整主线线宽分档策略,降低最大线宽限制 - 重构稳态趋势工具栏,优化谐波次数选择逻辑 - 添加周时间周期搜索支持和自定义时间范围选择 - 完善稳态数据表格和指示器浮动面板功能 - 优化稳态趋势图性能,添加LTB采样和动画控制 - 修复数据表格打开前的趋势数据验证问题 - 统一时间轴标签格式化和网格对齐处理
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user