feat(waveform): 添加全通道波形数据显示功能
- 实现全通道模式下的波形数据展示 - 添加通道选择器支持全部/单个通道切换 - 新增全通道趋势分组数据结构 - 重构波形数据获取逻辑支持多通道模式 - 更新图表配置支持动态图例显示控制 - 完善波形数据导出功能支持全通道数据 - 优化工具栏界面适配新的通道选择功能
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<div class="file-select-row">
|
<div class="file-select-row">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -31,7 +30,12 @@
|
|||||||
placeholder="选择通道"
|
placeholder="选择通道"
|
||||||
@update:model-value="handleChannelChange"
|
@update:model-value="handleChannelChange"
|
||||||
>
|
>
|
||||||
<el-option v-for="item in channelOptions" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option
|
||||||
|
v-for="item in channelOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,7 +58,11 @@
|
|||||||
|
|
||||||
<div class="toolbar-item">
|
<div class="toolbar-item">
|
||||||
<div class="toolbar-label">数值类型</div>
|
<div class="toolbar-label">数值类型</div>
|
||||||
<el-radio-group :model-value="activeValueMode" class="value-mode-switch" @update:model-value="handleValueModeChange">
|
<el-radio-group
|
||||||
|
:model-value="activeValueMode"
|
||||||
|
class="value-mode-switch"
|
||||||
|
@update:model-value="handleValueModeChange"
|
||||||
|
>
|
||||||
<el-radio-button v-for="item in valueModeOptions" :key="item.value" :label="item.value">
|
<el-radio-button v-for="item in valueModeOptions" :key="item.value" :label="item.value">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
@@ -72,14 +80,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Download, FolderOpened } from '@element-plus/icons-vue'
|
import { Download, FolderOpened } from '@element-plus/icons-vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import type { DisplayMode, LabelValueOption, ValueMode, WaveformDetailOption } from './types'
|
import type { ChannelSelectValue, DisplayMode, LabelValueOption, ValueMode, WaveformDetailOption } from './types'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
selectedWaveformFileName: string
|
selectedWaveformFileName: string
|
||||||
isParsing: boolean
|
isParsing: boolean
|
||||||
waveformFileAccept: string
|
waveformFileAccept: string
|
||||||
channelOptions: WaveformDetailOption[]
|
channelOptions: WaveformDetailOption[]
|
||||||
activeChannelIndex: number
|
activeChannelIndex: ChannelSelectValue
|
||||||
displayModeOptions: LabelValueOption<DisplayMode>[]
|
displayModeOptions: LabelValueOption<DisplayMode>[]
|
||||||
activeDisplayMode: DisplayMode
|
activeDisplayMode: DisplayMode
|
||||||
valueModeOptions: LabelValueOption<ValueMode>[]
|
valueModeOptions: LabelValueOption<ValueMode>[]
|
||||||
@@ -88,7 +96,7 @@ defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:activeChannelIndex': [value: number]
|
'update:activeChannelIndex': [value: ChannelSelectValue]
|
||||||
'update:activeDisplayMode': [value: DisplayMode]
|
'update:activeDisplayMode': [value: DisplayMode]
|
||||||
'update:activeValueMode': [value: ValueMode]
|
'update:activeValueMode': [value: ValueMode]
|
||||||
'waveform-file-change': [event: Event]
|
'waveform-file-change': [event: Event]
|
||||||
@@ -108,7 +116,7 @@ const handleWaveformFileChange = (event: Event) => {
|
|||||||
emit('waveform-file-change', event)
|
emit('waveform-file-change', event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChannelChange = (value: number) => {
|
const handleChannelChange = (value: ChannelSelectValue) => {
|
||||||
emit('update:activeChannelIndex', value)
|
emit('update:activeChannelIndex', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +210,4 @@ const handleValueModeChange = (value: string | number | boolean | undefined) =>
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="waveform-panel">
|
<section class="waveform-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<el-tabs :model-value="activeTrendTab" class="trend-tabs" @update:model-value="handleTrendTabChange">
|
<el-tabs :model-value="activeTrendTab" class="trend-tabs" @update:model-value="handleTrendTabChange">
|
||||||
@@ -7,7 +7,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div v-if="hasWaveformData && activeDisplayMode === 'multi-channel'" class="chart-container">
|
<div v-if="hasWaveformData && isAllChannelsActive" class="all-channel-list">
|
||||||
|
<template v-for="group in allChannelTrendGroups" :key="group.key">
|
||||||
|
<div v-if="activeDisplayMode === 'multi-channel'" class="all-channel-chart">
|
||||||
|
<LineChart :options="group.multiChannelOptions" />
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="item in group.singleChannelOptionsList"
|
||||||
|
:key="item.key"
|
||||||
|
class="single-channel-card"
|
||||||
|
:class="{ 'single-channel-card--with-axis': item.isLastChart }"
|
||||||
|
>
|
||||||
|
<div class="single-channel-chart">
|
||||||
|
<LineChart :options="item.options" :group="item.group" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hasWaveformData && activeDisplayMode === 'multi-channel'" class="chart-container">
|
||||||
<LineChart :options="activeTrendOptions" />
|
<LineChart :options="activeTrendOptions" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="hasWaveformData" class="single-channel-list">
|
<div v-else-if="hasWaveformData" class="single-channel-list">
|
||||||
@@ -25,7 +44,9 @@
|
|||||||
<div v-else class="empty-block">
|
<div v-else class="empty-block">
|
||||||
<div class="empty-title">暂无波形数据</div>
|
<div class="empty-title">暂无波形数据</div>
|
||||||
<div class="empty-text">请选择同一组 `.cfg` 和 `.dat` 文件后自动解析并展示。</div>
|
<div class="empty-text">请选择同一组 `.cfg` 和 `.dat` 文件后自动解析并展示。</div>
|
||||||
<div v-if="lastParseErrorMessage" class="empty-text error-text">最近一次解析失败:{{ lastParseErrorMessage }}</div>
|
<div v-if="lastParseErrorMessage" class="empty-text error-text">
|
||||||
|
最近一次解析失败:{{ lastParseErrorMessage }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -33,15 +54,23 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LineChart from '@/components/echarts/line/index.vue'
|
import LineChart from '@/components/echarts/line/index.vue'
|
||||||
import type { DisplayMode, LabelValueOption, SingleChannelTrendOption, TrendTabValue } from './types'
|
import type {
|
||||||
|
AllChannelTrendGroup,
|
||||||
|
DisplayMode,
|
||||||
|
LabelValueOption,
|
||||||
|
SingleChannelTrendOption,
|
||||||
|
TrendTabValue
|
||||||
|
} from './types'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
hasWaveformData: boolean
|
hasWaveformData: boolean
|
||||||
|
isAllChannelsActive: boolean
|
||||||
activeDisplayMode: DisplayMode
|
activeDisplayMode: DisplayMode
|
||||||
activeTrendTab: TrendTabValue
|
activeTrendTab: TrendTabValue
|
||||||
trendTabs: LabelValueOption<TrendTabValue>[]
|
trendTabs: LabelValueOption<TrendTabValue>[]
|
||||||
activeTrendOptions: Record<string, unknown>
|
activeTrendOptions: Record<string, unknown>
|
||||||
singleChannelTrendOptionsList: SingleChannelTrendOption[]
|
singleChannelTrendOptionsList: SingleChannelTrendOption[]
|
||||||
|
allChannelTrendGroups: AllChannelTrendGroup[]
|
||||||
lastParseErrorMessage: string
|
lastParseErrorMessage: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -125,6 +154,26 @@ const handleTrendTabChange = (value: string | number) => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.all-channel-list {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--cn-color-canvas-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-channel-chart {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.single-channel-card {
|
.single-channel-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type TrendTabValue = 'instant' | 'rms'
|
export type TrendTabValue = 'instant' | 'rms'
|
||||||
export type ValueMode = 'primary' | 'secondary'
|
export type ValueMode = 'primary' | 'secondary'
|
||||||
export type DisplayMode = 'single-channel' | 'multi-channel'
|
export type DisplayMode = 'single-channel' | 'multi-channel'
|
||||||
|
export type ChannelSelectValue = number | 'all'
|
||||||
|
|
||||||
export interface LabelValueOption<T extends string | number = string | number> {
|
export interface LabelValueOption<T extends string | number = string | number> {
|
||||||
label: string
|
label: string
|
||||||
@@ -9,7 +10,7 @@ export interface LabelValueOption<T extends string | number = string | number> {
|
|||||||
|
|
||||||
export interface WaveformDetailOption {
|
export interface WaveformDetailOption {
|
||||||
label: string
|
label: string
|
||||||
value: number
|
value: ChannelSelectValue
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SingleChannelTrendOption {
|
export interface SingleChannelTrendOption {
|
||||||
@@ -19,6 +20,13 @@ export interface SingleChannelTrendOption {
|
|||||||
options: Record<string, unknown>
|
options: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AllChannelTrendGroup {
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
singleChannelOptionsList: SingleChannelTrendOption[]
|
||||||
|
multiChannelOptions: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export interface SummaryItem {
|
export interface SummaryItem {
|
||||||
label: string
|
label: string
|
||||||
value: string | number
|
value: string | number
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="table-box waveform-page">
|
<div class="table-box waveform-page">
|
||||||
<WaveformToolbar
|
<WaveformToolbar
|
||||||
:selected-waveform-file-name="selectedWaveformFileName"
|
:selected-waveform-file-name="selectedWaveformFileName"
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
:trend-tabs="trendTabs"
|
:trend-tabs="trendTabs"
|
||||||
:active-trend-options="activeTrendOptions"
|
:active-trend-options="activeTrendOptions"
|
||||||
:single-channel-trend-options-list="singleChannelTrendOptionsList"
|
:single-channel-trend-options-list="singleChannelTrendOptionsList"
|
||||||
|
:all-channel-trend-groups="allChannelTrendGroups"
|
||||||
|
:is-all-channels-active="isAllChannelsActive"
|
||||||
:last-parse-error-message="lastParseErrorMessage"
|
:last-parse-error-message="lastParseErrorMessage"
|
||||||
@update:active-trend-tab="activeTrendTab = $event"
|
@update:active-trend-tab="activeTrendTab = $event"
|
||||||
/>
|
/>
|
||||||
@@ -52,6 +54,8 @@ import WaveformInfoPanel from './components/WaveformInfoPanel.vue'
|
|||||||
import WaveformToolbar from './components/WaveformToolbar.vue'
|
import WaveformToolbar from './components/WaveformToolbar.vue'
|
||||||
import WaveformTrendPanel from './components/WaveformTrendPanel.vue'
|
import WaveformTrendPanel from './components/WaveformTrendPanel.vue'
|
||||||
import type {
|
import type {
|
||||||
|
AllChannelTrendGroup,
|
||||||
|
ChannelSelectValue,
|
||||||
DisplayMode,
|
DisplayMode,
|
||||||
LabelValueOption,
|
LabelValueOption,
|
||||||
SingleChannelTrendOption,
|
SingleChannelTrendOption,
|
||||||
@@ -80,13 +84,15 @@ interface WaveformTrendPayload {
|
|||||||
|
|
||||||
interface TrendChartLayoutOptions {
|
interface TrendChartLayoutOptions {
|
||||||
showTimeAxis?: boolean
|
showTimeAxis?: boolean
|
||||||
|
showLegend?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeTrendTab = ref<TrendTabValue>('instant')
|
const activeTrendTab = ref<TrendTabValue>('instant')
|
||||||
const activeValueMode = ref<ValueMode>('primary')
|
const activeValueMode = ref<ValueMode>('primary')
|
||||||
const activeDisplayMode = ref<DisplayMode>('single-channel')
|
const activeDisplayMode = ref<DisplayMode>('single-channel')
|
||||||
const activeChannelIndex = ref(0)
|
const activeChannelIndex = ref<ChannelSelectValue>('all')
|
||||||
const singleChannelTrendChartGroup = 'waveform-single-channel-sync'
|
const singleChannelTrendChartGroup = 'waveform-single-channel-sync'
|
||||||
|
const allChannelTrendChartGroup = 'waveform-all-channel-sync'
|
||||||
const isParsing = ref(false)
|
const isParsing = ref(false)
|
||||||
const selectedCfgFile = ref<File | null>(null)
|
const selectedCfgFile = ref<File | null>(null)
|
||||||
const selectedDatFile = ref<File | null>(null)
|
const selectedDatFile = ref<File | null>(null)
|
||||||
@@ -205,13 +211,19 @@ const normalizedWaveDetails = computed<Waveform.WaveDataDetail[]>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const channelOptions = computed<WaveformDetailOption[]>(() => {
|
const channelOptions = computed<WaveformDetailOption[]>(() => {
|
||||||
return normalizedWaveDetails.value.map((item, index) => ({
|
const detailOptions = normalizedWaveDetails.value.map((item, index) => ({
|
||||||
label: buildChannelLabel(item, index),
|
label: buildChannelLabel(item, index),
|
||||||
value: index
|
value: index
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
return detailOptions.length ? [{ label: '全部', value: 'all' }, ...detailOptions] : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isAllChannelsActive = computed(() => activeChannelIndex.value === 'all')
|
||||||
|
|
||||||
const activeWaveDetail = computed(() => {
|
const activeWaveDetail = computed(() => {
|
||||||
|
if (typeof activeChannelIndex.value !== 'number') return null
|
||||||
|
|
||||||
return normalizedWaveDetails.value[activeChannelIndex.value] || normalizedWaveDetails.value[0] || null
|
return normalizedWaveDetails.value[activeChannelIndex.value] || normalizedWaveDetails.value[0] || null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -226,13 +238,15 @@ const isCurrentChannel = (channelName?: string) => {
|
|||||||
return (channelName || '').toUpperCase().startsWith('I')
|
return (channelName || '').toUpperCase().startsWith('I')
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeValueScale = computed(() => {
|
const getValueScale = (detail: Waveform.WaveDataDetail | null) => {
|
||||||
if (activeValueMode.value === 'secondary') return 1
|
if (activeValueMode.value === 'secondary') return 1
|
||||||
|
|
||||||
const waveData = activeWaveData.value
|
const waveData = activeWaveData.value
|
||||||
const ratio = isCurrentChannel(activeWaveDetail.value?.channelName) ? waveData?.ct : waveData?.pt
|
const ratio = isCurrentChannel(detail?.channelName) ? waveData?.ct : waveData?.pt
|
||||||
return normalizeRatio(ratio)
|
return normalizeRatio(ratio)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const activeValueScale = computed(() => getValueScale(activeWaveDetail.value))
|
||||||
|
|
||||||
const safeNumber = (value: unknown) => {
|
const safeNumber = (value: unknown) => {
|
||||||
const numberValue = Number(value)
|
const numberValue = Number(value)
|
||||||
@@ -262,7 +276,11 @@ const buildChannelLabel = (detail: Waveform.WaveDataDetail, index: number) => {
|
|||||||
return `通道 ${index + 1}`
|
return `通道 ${index + 1}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildTrendPayload = (detail: Waveform.WaveDataDetail | null, trendTab: TrendTabValue, scale: number): WaveformTrendPayload => {
|
const buildTrendPayload = (
|
||||||
|
detail: Waveform.WaveDataDetail | null,
|
||||||
|
trendTab: TrendTabValue,
|
||||||
|
scale: number
|
||||||
|
): WaveformTrendPayload => {
|
||||||
const trendData = trendTab === 'instant' ? detail?.instantData : detail?.rmsData
|
const trendData = trendTab === 'instant' ? detail?.instantData : detail?.rmsData
|
||||||
const aName = detail?.a || 'A相'
|
const aName = detail?.a || 'A相'
|
||||||
const bName = detail?.b || 'B相'
|
const bName = detail?.b || 'B相'
|
||||||
@@ -301,12 +319,18 @@ const activeTrendPayload = computed(() => {
|
|||||||
return buildTrendPayload(activeWaveDetail.value, activeTrendTab.value, activeValueScale.value)
|
return buildTrendPayload(activeWaveDetail.value, activeTrendTab.value, activeValueScale.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentTrendColors = computed(() => {
|
const getTrendColors = (detail: Waveform.WaveDataDetail | null) => {
|
||||||
const customColors = activeWaveDetail.value?.colors?.filter(Boolean) || []
|
const customColors = detail?.colors?.filter(Boolean) || []
|
||||||
return customColors.length >= 3 ? customColors.slice(0, 3) : defaultPhaseColors
|
return customColors.length >= 3 ? customColors.slice(0, 3) : defaultPhaseColors
|
||||||
})
|
}
|
||||||
|
|
||||||
const hasWaveformData = computed(() => activeTrendPayload.value.series.length > 0)
|
const currentTrendColors = computed(() => getTrendColors(activeWaveDetail.value))
|
||||||
|
|
||||||
|
const hasWaveformData = computed(() => {
|
||||||
|
if (isAllChannelsActive.value) return allChannelTrendGroups.value.length > 0
|
||||||
|
|
||||||
|
return activeTrendPayload.value.series.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
const SYMMETRIC_AXIS_SPLIT_COUNT = 6
|
const SYMMETRIC_AXIS_SPLIT_COUNT = 6
|
||||||
const REGULAR_AXIS_SPLIT_COUNT = 5
|
const REGULAR_AXIS_SPLIT_COUNT = 5
|
||||||
@@ -408,13 +432,40 @@ const buildTimeAxisLabelFormatter = (timeLabels: string[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TrendTooltipParam {
|
||||||
|
axisValue?: string | number
|
||||||
|
marker?: string
|
||||||
|
seriesName?: string
|
||||||
|
value?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTrendTooltipFormatter = (unit: string) => {
|
||||||
|
return (params: TrendTooltipParam | TrendTooltipParam[]) => {
|
||||||
|
const paramList = Array.isArray(params) ? params : [params]
|
||||||
|
const firstParam = paramList[0]
|
||||||
|
const timeValue = firstParam?.axisValue
|
||||||
|
const valueRows = paramList
|
||||||
|
.map(item => {
|
||||||
|
const marker = item.marker || ''
|
||||||
|
const seriesName = item.seriesName || ''
|
||||||
|
const valueText = unit ? `${formatNumber(item.value)} ${unit}` : formatNumber(item.value)
|
||||||
|
|
||||||
|
return `<div>${marker}${seriesName}<span style="float:right;margin-left:12px;font-weight:600;">${valueText}</span></div>`
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
const timeText = timeValue === undefined ? '--' : `${formatNumber(timeValue, 2)} ms`
|
||||||
|
|
||||||
|
return `${valueRows}<div style="margin-top:4px;">时间<span style="float:right;margin-left:12px;">${timeText}</span></div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buildTrendChartOptions = (
|
const buildTrendChartOptions = (
|
||||||
trendPayload: WaveformTrendPayload,
|
trendPayload: WaveformTrendPayload,
|
||||||
seriesList: WaveformSeriesItem[],
|
seriesList: WaveformSeriesItem[],
|
||||||
chartColors = currentTrendColors.value,
|
chartColors = currentTrendColors.value,
|
||||||
layoutOptions: TrendChartLayoutOptions = {}
|
layoutOptions: TrendChartLayoutOptions = {}
|
||||||
) => {
|
) => {
|
||||||
const { showTimeAxis = true } = layoutOptions
|
const { showTimeAxis = true, showLegend = true } = layoutOptions
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -427,16 +478,15 @@ const buildTrendChartOptions = (
|
|||||||
width: 1
|
width: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
valueFormatter: (value: number) => {
|
formatter: buildTrendTooltipFormatter(trendPayload.unit)
|
||||||
return trendPayload.unit ? `${formatNumber(value)} ${trendPayload.unit}` : formatNumber(value)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
|
show: showLegend,
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 12
|
right: 12
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: '18px',
|
top: showLegend ? '18px' : '6px',
|
||||||
left: '24px',
|
left: '24px',
|
||||||
right: showTimeAxis ? '32px' : '24px',
|
right: showTimeAxis ? '32px' : '24px',
|
||||||
bottom: showTimeAxis ? '16px' : '6px'
|
bottom: showTimeAxis ? '16px' : '6px'
|
||||||
@@ -493,24 +543,68 @@ const activeTrendOptions = computed<Record<string, unknown>>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 单通道模式按相别拆成多个图,便于分别观察各通道波形。
|
// 单通道模式按相别拆成多个图,便于分别观察各通道波形。
|
||||||
const singleChannelTrendOptionsList = computed<SingleChannelTrendOption[]>(() => {
|
const buildSingleChannelTrendOptionsList = (
|
||||||
const trendPayload = activeTrendPayload.value
|
detail: Waveform.WaveDataDetail | null,
|
||||||
|
detailIndex: number,
|
||||||
|
chartGroup: string,
|
||||||
|
resolveShowTimeAxis = (seriesIndex: number, seriesCount: number) => seriesIndex === seriesCount - 1
|
||||||
|
): SingleChannelTrendOption[] => {
|
||||||
|
const trendPayload = buildTrendPayload(detail, activeTrendTab.value, getValueScale(detail))
|
||||||
|
const trendColors = getTrendColors(detail)
|
||||||
|
|
||||||
// 单通道下每张图只保留一个 series,需要单独指定对应相色。
|
// 单通道下每张图只保留一个 series,需要单独指定对应相色。
|
||||||
return trendPayload.series.map((item, index) => ({
|
return trendPayload.series.map((item, index) => {
|
||||||
key: item.name,
|
const showTimeAxis = resolveShowTimeAxis(index, trendPayload.series.length)
|
||||||
group: singleChannelTrendChartGroup,
|
|
||||||
isLastChart: index === trendPayload.series.length - 1,
|
return {
|
||||||
options: buildTrendChartOptions(
|
key: `${detailIndex}-${item.name}`,
|
||||||
trendPayload,
|
group: chartGroup,
|
||||||
[item],
|
isLastChart: showTimeAxis,
|
||||||
[currentTrendColors.value[index] || defaultPhaseColors[index]],
|
options: buildTrendChartOptions(trendPayload, [item], [trendColors[index] || defaultPhaseColors[index]], {
|
||||||
{
|
// 单通道下每张图只有一条曲线,图例信息与图表标题重复。
|
||||||
// 仅最后一张图显示时间轴,并通过卡片高度微调补偿其额外占用空间。
|
showTimeAxis,
|
||||||
showTimeAxis: index === trendPayload.series.length - 1
|
showLegend: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleChannelTrendOptionsList = computed<SingleChannelTrendOption[]>(() => {
|
||||||
|
return buildSingleChannelTrendOptionsList(activeWaveDetail.value, -1, singleChannelTrendChartGroup)
|
||||||
|
})
|
||||||
|
|
||||||
|
const allChannelTrendGroups = computed<AllChannelTrendGroup[]>(() => {
|
||||||
|
const availableDetails = normalizedWaveDetails.value
|
||||||
|
.map((detail, index) => {
|
||||||
|
const trendPayload = buildTrendPayload(detail, activeTrendTab.value, getValueScale(detail))
|
||||||
|
|
||||||
|
return {
|
||||||
|
detail,
|
||||||
|
index,
|
||||||
|
trendPayload
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}))
|
.filter(item => item.trendPayload.series.length > 0)
|
||||||
|
|
||||||
|
return availableDetails.map((item, groupIndex) => {
|
||||||
|
const isLastGroup = groupIndex === availableDetails.length - 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: `${item.index}-${buildChannelLabel(item.detail, item.index)}`,
|
||||||
|
title: buildChannelLabel(item.detail, item.index),
|
||||||
|
singleChannelOptionsList: buildSingleChannelTrendOptionsList(
|
||||||
|
item.detail,
|
||||||
|
item.index,
|
||||||
|
allChannelTrendChartGroup,
|
||||||
|
(seriesIndex, seriesCount) => isLastGroup && seriesIndex === seriesCount - 1
|
||||||
|
),
|
||||||
|
multiChannelOptions: buildTrendChartOptions(
|
||||||
|
item.trendPayload,
|
||||||
|
item.trendPayload.series,
|
||||||
|
getTrendColors(item.detail)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const summaryItems = computed<SummaryItem[]>(() => {
|
const summaryItems = computed<SummaryItem[]>(() => {
|
||||||
@@ -523,7 +617,14 @@ const summaryItems = computed<SummaryItem[]>(() => {
|
|||||||
{ label: '触发时间', value: formatWaveformTime(cfgData?.timeTrige) },
|
{ label: '触发时间', value: formatWaveformTime(cfgData?.timeTrige) },
|
||||||
{ label: '采样率', value: cfgData?.finalSampleRate ? `${cfgData.finalSampleRate} Hz` : '--' },
|
{ label: '采样率', value: cfgData?.finalSampleRate ? `${cfgData.finalSampleRate} Hz` : '--' },
|
||||||
{ label: '总通道数', value: cfgData?.nChannelNum ?? '--' },
|
{ label: '总通道数', value: cfgData?.nChannelNum ?? '--' },
|
||||||
{ label: '当前通道', value: detail ? buildChannelLabel(detail, activeChannelIndex.value) : '--' },
|
{
|
||||||
|
label: '当前通道',
|
||||||
|
value: isAllChannelsActive.value
|
||||||
|
? '全部'
|
||||||
|
: detail
|
||||||
|
? buildChannelLabel(detail, activeChannelIndex.value as number)
|
||||||
|
: '--'
|
||||||
|
},
|
||||||
{ label: '单位', value: detail?.unit || '--' },
|
{ label: '单位', value: detail?.unit || '--' },
|
||||||
{ label: '相别数量', value: waveData?.iPhasic ?? '--' },
|
{ label: '相别数量', value: waveData?.iPhasic ?? '--' },
|
||||||
{ label: 'PT / CT', value: `${formatNumber(waveData?.pt, 2)} / ${formatNumber(waveData?.ct, 2)}` },
|
{ label: 'PT / CT', value: `${formatNumber(waveData?.pt, 2)} / ${formatNumber(waveData?.ct, 2)}` },
|
||||||
@@ -583,7 +684,7 @@ const handleWaveformFileChange = async (event: Event) => {
|
|||||||
const loadWaveformData = async (cfgFile: File, datFile: File) => {
|
const loadWaveformData = async (cfgFile: File, datFile: File) => {
|
||||||
try {
|
try {
|
||||||
isParsing.value = true
|
isParsing.value = true
|
||||||
activeChannelIndex.value = 0
|
activeChannelIndex.value = 'all'
|
||||||
lastParseErrorMessage.value = ''
|
lastParseErrorMessage.value = ''
|
||||||
lastVectorParseErrorMessage.value = ''
|
lastVectorParseErrorMessage.value = ''
|
||||||
|
|
||||||
@@ -641,16 +742,46 @@ const downloadTrendData = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trendPayload = activeTrendPayload.value
|
const trendPayload = activeTrendPayload.value
|
||||||
|
const allChannelPayloads = normalizedWaveDetails.value
|
||||||
|
.map((detail, index) => ({
|
||||||
|
label: buildChannelLabel(detail, index),
|
||||||
|
payload: buildTrendPayload(detail, activeTrendTab.value, getValueScale(detail))
|
||||||
|
}))
|
||||||
|
.filter(item => item.payload.series.length > 0)
|
||||||
|
const exportPayload = isAllChannelsActive.value ? allChannelPayloads[0]?.payload : trendPayload
|
||||||
|
|
||||||
|
if (!exportPayload) {
|
||||||
|
ElMessage.warning('暂无可导出的波形数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const header = ['时间', ...trendPayload.series.map(item => item.name)]
|
const header = ['时间', ...trendPayload.series.map(item => item.name)]
|
||||||
const rows = trendPayload.timeLabels.map((time, index) => {
|
const allChannelHeader = [
|
||||||
|
'时间',
|
||||||
|
...allChannelPayloads.flatMap(item => item.payload.series.map(series => `${item.label}-${series.name}`))
|
||||||
|
]
|
||||||
|
const rows = exportPayload.timeLabels.map((time, index) => {
|
||||||
|
if (isAllChannelsActive.value) {
|
||||||
|
return [
|
||||||
|
time,
|
||||||
|
...allChannelPayloads.flatMap(item => item.payload.series.map(series => series.data[index] ?? ''))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return [time, ...trendPayload.series.map(item => item.data[index] ?? '')]
|
return [time, ...trendPayload.series.map(item => item.data[index] ?? '')]
|
||||||
})
|
})
|
||||||
|
|
||||||
const csvContent = [header, ...rows].map(row => row.join(',')).join('\n')
|
const csvContent = [isAllChannelsActive.value ? allChannelHeader : header, ...rows]
|
||||||
|
.map(row => row.join(','))
|
||||||
|
.join('\n')
|
||||||
const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' })
|
const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' })
|
||||||
const blobUrl = URL.createObjectURL(blob)
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
const exportFile = document.createElement('a')
|
const exportFile = document.createElement('a')
|
||||||
const channelLabel = activeWaveDetail.value ? buildChannelLabel(activeWaveDetail.value, activeChannelIndex.value) : '波形'
|
const channelLabel = isAllChannelsActive.value
|
||||||
|
? '全部'
|
||||||
|
: activeWaveDetail.value
|
||||||
|
? buildChannelLabel(activeWaveDetail.value, activeChannelIndex.value as number)
|
||||||
|
: '波形'
|
||||||
const fileName = `波形查看_${channelLabel}_${valueModeLabelMap[activeValueMode.value]}_${trendLabelMap[activeTrendTab.value]}.csv`
|
const fileName = `波形查看_${channelLabel}_${valueModeLabelMap[activeValueMode.value]}_${trendLabelMap[activeTrendTab.value]}.csv`
|
||||||
|
|
||||||
exportFile.style.display = 'none'
|
exportFile.style.display = 'none'
|
||||||
@@ -686,4 +817,3 @@ const downloadTrendData = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user