feat(data-tools): 新增入库类型选择功能并优化数据工具界面

- 在补数任务面板中添加入库类型单选按钮组,支持 MySQL 和 InfluxDB
- 更新 AddData 接口定义,添加 StorageType 相关类型和选项接口
- 修改补数 API 请求逻辑,根据入库类型动态调整接口路径前缀
- 重构台账设备表单,统一使用装置网络参数作为 MAC 和 NDID 的单一数据源
- 优化台账线路表单,仅当存在 ID 时才设置 lineId 字段,避免空值传递
- 添加入库类型列表获取接口和相关数据处理逻辑
- 更新台账字典代码常量,新增终端型号字典码
- 优化台账树节点添加逻辑,增加前置条件验证和禁用原因提示
- 添加 InfluxDB 配置文件到额外资源目录
- 更新稳定数据分析视图,优化台账树数据结构处理和样式布局
- 完善 API 调试契约检查,确保设备和线路数据映射正确性
- 优化趋势查询性能,禁用全局加载状态提升用户体验
This commit is contained in:
2026-05-21 14:07:10 +08:00
parent f1eaabae0e
commit b9ddfb5275
23 changed files with 2462 additions and 180 deletions

View File

@@ -2,6 +2,7 @@
<aside class="indicator-floating-panel" :class="{ 'is-collapsed': collapsed }">
<el-button
class="indicator-toggle"
type="primary"
:icon="collapsed ? ArrowLeft : ArrowRight"
circle
@click="emit('update:collapsed', !collapsed)"
@@ -61,7 +62,7 @@ const emit = defineEmits<{
.indicator-toggle {
position: absolute;
top: 12px;
left: -18px;
left: -28px;
z-index: 3;
}

View File

@@ -1,54 +1,67 @@
<template>
<section class="card steady-tree-card">
<div class="panel-header">
<span class="panel-title">台账监测点</span>
<el-button :icon="Refresh" circle :loading="loading" @click="emit('refresh')" />
<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>
<el-input
:model-value="keyword"
clearable
placeholder="搜索工程、项目、设备、监测点"
@update:model-value="handleKeywordChange"
/>
<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>
<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 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 { Folder, Location, Monitor, OfficeBuilding, Refresh } from '@element-plus/icons-vue'
import { ArrowLeft, ArrowRight, Folder, Location, Monitor, OfficeBuilding, Refresh } from '@element-plus/icons-vue'
import type { TreeInstance } from 'element-plus'
import type { SteadyDataView } from '@/api/steady/steadyDataView/interface'
@@ -61,12 +74,14 @@ const props = defineProps<{
loading: boolean
keyword: string
defaultCheckedKeys: string[]
collapsed: boolean
}>()
const emit = defineEmits<{
refresh: []
search: [value: string]
change: [nodes: SteadyDataView.SteadyLedgerNode[]]
toggle: []
}>()
const treeRef = ref<TreeInstance>()
@@ -134,6 +149,46 @@ watch(
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;
@@ -148,6 +203,21 @@ watch(
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;

View File

@@ -1,23 +1,79 @@
<template>
<section class="card trend-chart-panel" v-loading="loading">
<div v-if="trendResult" class="panel-header">
<span class="panel-meta">
{{ trendResult.bucket || '-' }} / {{ trendResult.displayPointCount || 0 }}
</span>
<div class="panel-header">
<span v-if="trendResult" class="panel-meta">{{ trendResult.displayPointCount || 0 }} </span>
<span v-else class="panel-meta">趋势图</span>
<div class="trend-tool-groups">
<div v-for="group in trendToolGroups" :key="group.key" class="trend-tool-group">
<el-tooltip
v-for="item in group.items"
:key="item.action"
:content="getTrendToolTooltip(item)"
placement="top"
>
<el-button
:type="isTrendToolActive(item.action) ? 'primary' : 'default'"
:icon="item.icon"
:disabled="isTrendToolDisabled(item.action)"
circle
@click="handleTrendToolAction(item.action)"
/>
</el-tooltip>
</div>
</div>
</div>
<div v-if="hasSeries" class="chart-body">
<LineChart :options="chartOptions" />
<div v-if="hasSeries" ref="chartExportTargetRef" class="chart-list steady-trend-export-target">
<div v-for="group in chartGroups" :key="group.key" class="chart-group">
<div class="chart-body">
<LineChart :options="group.options" :group="group.group" @chart-data-zoom="handleChartDataZoom" />
</div>
</div>
</div>
<el-empty v-else class="chart-empty" description="请选择监测点和指标后查询趋势" />
<el-dialog v-model="fullscreenVisible" title="趋势图全屏展示" fullscreen append-to-body destroy-on-close>
<div v-if="hasSeries" class="fullscreen-chart-body">
<div v-for="group in chartGroups" :key="group.key" class="chart-group">
<div class="chart-body">
<LineChart
:options="group.options"
:group="group.group"
@chart-data-zoom="handleChartDataZoom"
/>
</div>
</div>
</div>
<el-empty v-else class="chart-empty" description="请选择监测点和指标后查询趋势" />
</el-dialog>
<SteadyTrendDataTableDialog v-model="dataTableVisible" :trend-result="trendResult" />
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import {
ArrowDownBold,
ArrowLeftBold,
ArrowRightBold,
ArrowUpBold,
Crop,
DataAnalysis,
FullScreen,
Mouse,
Picture,
Pointer,
RefreshLeft
} from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import html2canvas from 'html2canvas'
import { computed, nextTick, ref, watch } from 'vue'
import LineChart from '@/components/echarts/line/index.vue'
import type { SteadyDataView } from '@/api/steady/steadyDataView/interface'
import { buildSteadyTrendChartOptions } from '../utils/trendOptions'
import { buildSteadyTrendChartGroups, type SteadyTrendZoomRange } from '../utils/trendOptions'
import SteadyTrendDataTableDialog from './SteadyTrendDataTableDialog.vue'
import type { Component } from 'vue'
defineOptions({
name: 'SteadyTrendChartPanel'
@@ -28,8 +84,235 @@ const props = defineProps<{
loading: boolean
}>()
type SteadyTrendInteractionMode = 'none' | 'box-zoom' | 'pan'
type SteadyTrendToolAction =
| 'x-zoom-in'
| 'x-zoom-out'
| 'y-zoom-in'
| 'y-zoom-out'
| 'box-zoom'
| 'wheel-zoom'
| 'reset'
| 'pan'
| 'fullscreen'
| 'download-image'
| 'query-data'
const trendToolGroups: Array<{
key: string
items: Array<{
action: SteadyTrendToolAction
label: string
icon: Component
}>
}> = [
{
key: 'viewport',
items: [
{ action: 'wheel-zoom', label: '滚轮缩放', icon: Mouse },
{ action: 'x-zoom-in', label: 'X坐标放大', icon: ArrowRightBold },
{ action: 'x-zoom-out', label: 'X坐标缩小', icon: ArrowLeftBold },
{ action: 'y-zoom-in', label: 'Y坐标放大', icon: ArrowUpBold },
{ action: 'y-zoom-out', label: 'Y坐标缩小', icon: ArrowDownBold },
{ action: 'box-zoom', label: '框选放大', icon: Crop },
{ action: 'reset', label: '恢复', icon: RefreshLeft },
{ action: 'pan', label: '平移', icon: Pointer }
]
},
{
key: 'view',
items: [
{ action: 'fullscreen', label: '全屏展示', icon: FullScreen },
{ action: 'download-image', label: '下载图片', icon: Picture },
{ action: 'query-data', label: '数据查询', icon: DataAnalysis }
]
}
]
const clampPercent = (value: number) => Math.min(Math.max(value, 0), 100)
const DEFAULT_STEADY_TREND_X_ZOOM_RANGE: SteadyTrendZoomRange = { start: 0, end: 10 }
const trendXZoomRange = ref<SteadyTrendZoomRange>({ ...DEFAULT_STEADY_TREND_X_ZOOM_RANGE })
const trendYZoomScale = ref(1)
const activeTrendInteractionMode = ref<SteadyTrendInteractionMode>('none')
const wheelZoomEnabled = ref(false)
const fullscreenVisible = ref(false)
const dataTableVisible = ref(false)
const chartExportTargetRef = ref<HTMLElement>()
const hasSeries = computed(() => Boolean(props.trendResult?.series?.length))
const chartOptions = computed(() => buildSteadyTrendChartOptions(props.trendResult?.series || []))
const chartGroups = computed(() =>
buildSteadyTrendChartGroups(props.trendResult?.series || [], trendXZoomRange.value, {
activeTool: activeTrendInteractionMode.value,
wheelZoomEnabled: wheelZoomEnabled.value,
yZoomScale: trendYZoomScale.value
})
)
const canPanTrendChart = computed(() => {
const { start, end } = trendXZoomRange.value
return hasSeries.value && (start > 0 || end < 100)
})
const isDefaultTrendXZoomRange = computed(() => {
const { start, end } = trendXZoomRange.value
return start === DEFAULT_STEADY_TREND_X_ZOOM_RANGE.start && end === DEFAULT_STEADY_TREND_X_ZOOM_RANGE.end
})
const canResetTrendChart = computed(() => {
const changedYZoom = trendYZoomScale.value !== 1
const changedInteractionMode = activeTrendInteractionMode.value !== 'none'
const changedWheelZoom = wheelZoomEnabled.value
return hasSeries.value && (!isDefaultTrendXZoomRange.value || changedYZoom || changedInteractionMode || changedWheelZoom)
})
const resetTrendToolState = () => {
trendXZoomRange.value = { ...DEFAULT_STEADY_TREND_X_ZOOM_RANGE }
trendYZoomScale.value = 1
activeTrendInteractionMode.value = 'none'
wheelZoomEnabled.value = false
}
const zoomTrendXAxis = (ratio: number) => {
const { start, end } = trendXZoomRange.value
const center = (start + end) / 2
const nextWidth = Math.min(Math.max((end - start) * ratio, 1), 100)
const nextStart = clampPercent(center - nextWidth / 2)
const nextEnd = clampPercent(center + nextWidth / 2)
if (nextStart === 0) {
trendXZoomRange.value = { start: 0, end: nextWidth }
return
}
if (nextEnd === 100) {
trendXZoomRange.value = { start: 100 - nextWidth, end: 100 }
return
}
trendXZoomRange.value = { start: nextStart, end: nextEnd }
}
const isTrendToolActive = (action: SteadyTrendToolAction) => {
if (action === 'fullscreen') return fullscreenVisible.value
if (action === 'wheel-zoom') return wheelZoomEnabled.value
if (action === 'box-zoom' || action === 'pan') return activeTrendInteractionMode.value === action
return false
}
const isTrendToolDisabled = (action: SteadyTrendToolAction) => {
if (action === 'query-data') return false
if (!hasSeries.value) return true
if (action === 'pan') return !canPanTrendChart.value
if (action === 'reset') return !canResetTrendChart.value
return false
}
const getTrendToolTooltip = (item: { action: SteadyTrendToolAction; label: string }) => {
if (item.action === 'pan' && isTrendToolDisabled(item.action) && hasSeries.value) {
return '请先放大 X 轴或框选局部区域后再平移'
}
return item.label
}
const downloadSteadyTrendImage = async () => {
await nextTick()
const targetElement = chartExportTargetRef.value
if (!targetElement) {
ElMessage.warning('暂无可下载的趋势图')
return
}
const canvas = await html2canvas(targetElement, {
backgroundColor: '#ffffff',
scale: window.devicePixelRatio || 1,
useCORS: true
})
const imageUrl = canvas.toDataURL('image/png')
const exportFile = document.createElement('a')
exportFile.style.display = 'none'
exportFile.download = `steady-trend-${Date.now()}.png`
exportFile.href = imageUrl
document.body.appendChild(exportFile)
exportFile.click()
document.body.removeChild(exportFile)
ElMessage.success('趋势图图片下载成功')
}
const handleTrendToolAction = async (action: SteadyTrendToolAction) => {
if (isTrendToolDisabled(action)) return
switch (action) {
case 'x-zoom-in':
zoomTrendXAxis(0.8)
break
case 'x-zoom-out':
zoomTrendXAxis(1.25)
break
case 'y-zoom-in':
trendYZoomScale.value = Math.max(trendYZoomScale.value * 0.8, 0.1)
break
case 'y-zoom-out':
trendYZoomScale.value = Math.min(trendYZoomScale.value * 1.25, 10)
break
case 'box-zoom':
activeTrendInteractionMode.value = activeTrendInteractionMode.value === 'box-zoom' ? 'none' : 'box-zoom'
break
case 'wheel-zoom':
wheelZoomEnabled.value = !wheelZoomEnabled.value
break
case 'pan':
if (!canPanTrendChart.value) {
ElMessage.info('请先放大 X 轴或框选局部区域后再平移')
activeTrendInteractionMode.value = 'none'
break
}
activeTrendInteractionMode.value = activeTrendInteractionMode.value === 'pan' ? 'none' : 'pan'
break
case 'reset':
resetTrendToolState()
break
case 'fullscreen':
fullscreenVisible.value = true
break
case 'download-image':
await downloadSteadyTrendImage()
break
case 'query-data':
if (!hasSeries.value) {
ElMessage.warning('请先查询趋势数据')
break
}
dataTableVisible.value = true
break
default:
break
}
}
const handleChartDataZoom = (value: SteadyTrendZoomRange) => {
trendXZoomRange.value = {
start: clampPercent(value.start),
end: clampPercent(value.end)
}
if (!canPanTrendChart.value && activeTrendInteractionMode.value === 'pan') {
activeTrendInteractionMode.value = 'none'
}
}
watch(
() => props.trendResult,
() => {
// 新查询结果应回到完整时间范围,避免沿用上一批数据的局部缩放窗口。
resetTrendToolState()
}
)
</script>
<style scoped lang="scss">
@@ -46,8 +329,8 @@ const chartOptions = computed(() => buildSteadyTrendChartOptions(props.trendResu
display: flex;
flex: none;
align-items: center;
justify-content: flex-end;
gap: 10px;
justify-content: flex-start;
gap: 12px;
margin-bottom: 10px;
}
@@ -56,9 +339,64 @@ const chartOptions = computed(() => buildSteadyTrendChartOptions(props.trendResu
font-size: 12px;
}
.trend-tool-groups {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
min-width: 0;
}
.trend-tool-group {
display: flex;
align-items: center;
gap: 4px;
}
.trend-tool-group + .trend-tool-group {
padding-left: 8px;
border-left: 1px dashed var(--el-border-color);
}
.trend-tool-group :deep(.el-button.is-circle) {
width: 28px;
height: 28px;
padding: 6px;
}
.chart-list {
display: flex;
flex: 1;
flex-direction: column;
gap: 8px;
min-height: 0;
overflow: auto;
}
.fullscreen-chart-body {
display: flex;
flex-direction: column;
gap: 8px;
height: calc(100vh - 96px);
min-height: 0;
overflow: auto;
}
.chart-group {
display: flex;
flex: 1 0 240px;
flex-direction: column;
min-height: 220px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
background: var(--cn-color-canvas-bg);
}
.chart-body {
flex: 1;
min-height: 0;
padding: 0 8px 8px;
}
.chart-empty {

View File

@@ -0,0 +1,199 @@
<template>
<el-dialog
v-model="visibleProxy"
class="steady-trend-data-dialog"
title="数据查询"
width="86vw"
top="7vh"
append-to-body
destroy-on-close
>
<div class="table-main card steady-trend-data-table">
<div class="table-header">
<div class="header-button-lf">
<el-button
type="primary"
:icon="Download"
plain
:loading="downloading"
:disabled="!tableModel.timeValues.length"
@click="downloadSteadyTrendData"
>
下载数据
</el-button>
</div>
<div class="header-button-ri"></div>
</div>
<el-table :data="pagedRows" border stripe height="100%">
<el-table-column prop="time" label="时间" min-width="170" fixed="left" align="center" />
<el-table-column
v-for="lineGroup in tableModel.lineGroups"
:key="lineGroup.key"
:label="lineGroup.label"
align="center"
>
<el-table-column
v-for="indicatorGroup in lineGroup.indicatorGroups"
:key="indicatorGroup.key"
:label="indicatorGroup.label"
align="center"
>
<el-table-column
v-for="column in indicatorGroup.columns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
min-width="110"
align="center"
>
<template #default="{ row }">
{{ row[column.prop] ?? '-' }}
</template>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
background
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[500, 1000, 2000, 5000]"
:total="tableModel.timeValues.length"
@size-change="currentPage = 1"
/>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { Download } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import type { SteadyDataView } from '@/api/steady/steadyDataView/interface'
import {
buildSteadyTrendExcelHtml,
buildSteadyTrendTableModel,
buildSteadyTrendTableRows,
createEmptySteadyTrendTableModel
} from '../utils/trendTable'
defineOptions({
name: 'SteadyTrendDataTableDialog'
})
const props = defineProps<{
modelValue: boolean
trendResult: SteadyDataView.SteadyTrendQueryResult | null
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
const currentPage = ref(1)
const pageSize = ref(500)
const downloading = ref(false)
const tableModel = shallowRef(createEmptySteadyTrendTableModel())
const visibleProxy = computed({
get: () => props.modelValue,
set: value => emit('update:modelValue', value)
})
const pagedRows = computed(() =>
buildSteadyTrendTableRows(tableModel.value, (currentPage.value - 1) * pageSize.value, pageSize.value)
)
const downloadSteadyTrendData = async () => {
if (!tableModel.value.timeValues.length || !tableModel.value.columns.length) {
ElMessage.warning('暂无可下载的数据')
return
}
downloading.value = true
try {
await nextTick()
const excelContent = buildSteadyTrendExcelHtml(tableModel.value)
const blob = new Blob([excelContent], { type: 'application/vnd.ms-excel;charset=utf-8;' })
const blobUrl = URL.createObjectURL(blob)
const exportFile = document.createElement('a')
exportFile.style.display = 'none'
exportFile.download = `steady-trend-data-${Date.now()}.xls`
exportFile.href = blobUrl
document.body.appendChild(exportFile)
exportFile.click()
document.body.removeChild(exportFile)
URL.revokeObjectURL(blobUrl)
ElMessage.success('数据下载成功')
} finally {
downloading.value = false
}
}
watch(
() => props.modelValue,
visible => {
if (visible) {
tableModel.value = buildSteadyTrendTableModel(props.trendResult?.series || [])
currentPage.value = 1
return
}
// 弹窗关闭后释放当前页表格模型,避免大数据量结果长期占用额外内存。
tableModel.value = createEmptySteadyTrendTableModel()
}
)
</script>
<style scoped lang="scss">
.steady-trend-data-table {
display: flex;
flex-direction: column;
height: 70vh;
min-height: 0;
padding: 12px;
overflow: hidden;
}
.table-header {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.header-button-lf,
.header-button-ri {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.header-button-ri {
flex-shrink: 0;
}
.steady-trend-data-table :deep(.el-table) {
flex: 1;
min-height: 0;
}
.steady-trend-data-table :deep(.el-table__inner-wrapper) {
height: 100%;
}
.table-footer {
display: flex;
flex: none;
justify-content: flex-end;
padding-top: 10px;
}
</style>

View File

@@ -14,12 +14,9 @@
<div class="toolbar-field">
<span class="toolbar-field__label">统计</span>
<el-select
:model-value="modelValue.statTypes"
multiple
collapse-tags
collapse-tags-tooltip
:model-value="modelValue.statType"
placeholder="选择统计类型"
@update:model-value="updateField('statTypes', $event)"
@update:model-value="updateField('statType', $event)"
>
<el-option v-for="item in statOptions" :key="item" :label="statLabelMap[item]" :value="item" />
</el-select>
@@ -118,7 +115,7 @@ const handleTimeBaseDateChange = (value: Date) => {
<style scoped lang="scss">
.trend-toolbar {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
grid-template-columns: minmax(360px, 1.35fr) repeat(3, minmax(0, 1fr)) auto;
gap: 10px;
align-items: center;
padding: 12px;
@@ -152,6 +149,16 @@ const handleTimeBaseDateChange = (value: Date) => {
min-width: 0;
}
.trend-toolbar__time :deep(.time-period-search__unit) {
width: 72px;
flex: 0 0 72px;
}
.trend-toolbar__time :deep(.time-period-search__picker) {
width: 136px;
flex: 0 0 136px;
}
.harmonic-select {
grid-column: auto;
}

View File

@@ -1,16 +1,20 @@
<template>
<div class="steady-trend-layout">
<div class="steady-trend-layout" :class="{ 'is-ledger-collapsed': ledgerPanelCollapsedProxy }">
<aside class="selector-column">
<SteadyLedgerTree
:key="selectorResetKey"
:tree-data="ledgerTree"
:loading="loading.ledger"
:keyword="ledgerKeyword"
:default-checked-keys="defaultLedgerCheckedKeys"
@refresh="emit('refreshLedger')"
@search="emit('ledgerSearch', $event)"
@change="emit('ledgerChange', $event)"
/>
<div class="ledger-panel-body">
<SteadyLedgerTree
:key="selectorResetKey"
:tree-data="ledgerTree"
:loading="loading.ledger"
:keyword="ledgerKeyword"
:default-checked-keys="defaultLedgerCheckedKeys"
:collapsed="ledgerPanelCollapsedProxy"
@refresh="emit('refreshLedger')"
@search="emit('ledgerSearch', $event)"
@change="emit('ledgerChange', $event)"
@toggle="emit('update:ledgerPanelCollapsed', !ledgerPanelCollapsedProxy)"
/>
</div>
</aside>
<main class="trend-main">
@@ -67,12 +71,14 @@ const props = defineProps<{
ledgerKeyword: string
defaultLedgerCheckedKeys: string[]
defaultIndicatorCheckedKeys: string[]
ledgerPanelCollapsed: boolean
indicatorPanelCollapsed: boolean
selectorResetKey: number
}>()
const emit = defineEmits<{
'update:trendForm': [value: SteadyTrendFormState]
'update:ledgerPanelCollapsed': [value: boolean]
'update:indicatorPanelCollapsed': [value: boolean]
refreshLedger: []
ledgerSearch: [value: string]
@@ -88,6 +94,11 @@ const trendFormProxy = computed({
set: value => emit('update:trendForm', value)
})
const ledgerPanelCollapsedProxy = computed({
get: () => props.ledgerPanelCollapsed,
set: value => emit('update:ledgerPanelCollapsed', value)
})
const indicatorPanelCollapsedProxy = computed({
get: () => props.indicatorPanelCollapsed,
set: value => emit('update:indicatorPanelCollapsed', value)
@@ -104,12 +115,37 @@ const indicatorPanelCollapsedProxy = computed({
min-height: 0;
}
.steady-trend-layout.is-ledger-collapsed {
grid-template-columns: 0 minmax(0, 1fr);
}
.selector-column {
display: grid;
position: relative;
height: 100%;
min-height: 0;
overflow: hidden;
}
.steady-trend-layout.is-ledger-collapsed .selector-column {
z-index: 4;
overflow: visible;
}
.ledger-panel-body {
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.steady-trend-layout.is-ledger-collapsed .ledger-panel-body {
overflow: visible;
}
.ledger-panel-body :deep(.steady-tree-card) {
height: 100%;
}
.trend-main {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
@@ -131,7 +167,7 @@ const indicatorPanelCollapsedProxy = computed({
}
@media (max-width: 1360px) {
.steady-trend-layout {
.steady-trend-layout:not(.is-ledger-collapsed) {
grid-template-columns: 280px minmax(0, 1fr);
}
}