波形解析相关20260417
This commit is contained in:
@@ -1,24 +1,31 @@
|
||||
<template>
|
||||
<section class="waveform-panel">
|
||||
<div class="panel-header">
|
||||
<div class="section-title">波形信息</div>
|
||||
<div class="section-title">解析信息</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasParsedWaveform" class="panel-body info-body">
|
||||
<div class="summary-grid">
|
||||
<div v-for="item in summaryItems" :key="item.label" class="summary-item">
|
||||
<div class="summary-label">{{ item.label }}</div>
|
||||
<div class="summary-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<WaveformVectorInfo
|
||||
:vector-parse-result="vectorParseResult"
|
||||
:last-vector-parse-error-message="lastVectorParseErrorMessage"
|
||||
:active-vector-channel-name="activeVectorChannelName"
|
||||
/>
|
||||
</div>
|
||||
<el-tabs v-model="activeInfoTab" class="info-tabs">
|
||||
<el-tab-pane label="波形信息" name="waveform">
|
||||
<div class="info-tab-panel">
|
||||
<div class="summary-grid">
|
||||
<div v-for="item in summaryItems" :key="item.label" class="summary-item">
|
||||
<div class="summary-label">{{ item.label }}</div>
|
||||
<div class="summary-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="向量信息" name="vector">
|
||||
<div class="info-tab-panel">
|
||||
<WaveformVectorInfo
|
||||
:vector-parse-result="vectorParseResult"
|
||||
:last-vector-parse-error-message="lastVectorParseErrorMessage"
|
||||
:active-vector-channel-name="activeVectorChannelName"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="feature-header">特征值</div>
|
||||
<div v-if="featureCards.length" class="feature-grid">
|
||||
@@ -44,6 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { Waveform } from '@/api/tools/waveform/interface'
|
||||
import type { FeatureCardItem, SummaryItem } from './types'
|
||||
import WaveformVectorInfo from './WaveformVectorInfo.vue'
|
||||
@@ -57,6 +65,9 @@ defineProps<{
|
||||
lastVectorParseErrorMessage: string
|
||||
activeVectorChannelName: string
|
||||
}>()
|
||||
|
||||
// 右侧信息区仅切换展示内容,不影响波形与向量解析结果的联动状态。
|
||||
const activeInfoTab = ref('waveform')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -125,7 +136,7 @@ defineProps<{
|
||||
}
|
||||
|
||||
.info-body,
|
||||
.info-section {
|
||||
.info-tab-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -135,6 +146,19 @@ defineProps<{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.info-tabs {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-tabs :deep(.el-tabs__header) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-tabs :deep(.el-tabs__item) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div class="waveform-vector-info">
|
||||
<div class="feature-header">向量信息</div>
|
||||
<div v-if="hasVectorData" class="vector-content">
|
||||
<div class="vector-summary-grid">
|
||||
<div v-for="item in vectorSummaryItems" :key="item.label" class="summary-item">
|
||||
@@ -29,25 +28,48 @@
|
||||
</el-tabs>
|
||||
|
||||
<div v-if="activeCycle" class="vector-tab-content">
|
||||
<div v-if="phaseVectorCards.length" class="vector-card-grid">
|
||||
<div v-for="item in phaseVectorCards" :key="item.title" class="feature-card">
|
||||
<div class="feature-card-title">{{ item.title }}</div>
|
||||
<div v-for="row in item.rows" :key="row.label" class="feature-row">
|
||||
<span>{{ row.label }}</span>
|
||||
<span>{{ row.value }}</span>
|
||||
</div>
|
||||
<div v-if="phaseMetricColumns.length" class="phase-metric-table">
|
||||
<div class="phase-metric-row phase-metric-row--header" :style="phaseMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">指标</span>
|
||||
<span v-for="column in phaseMetricColumns" :key="column.key" class="phase-metric-cell">
|
||||
{{ column.label }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-for="row in phaseMetricRows" :key="row.label" class="phase-metric-row" :style="phaseMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">{{ row.label }}</span>
|
||||
<span v-for="column in phaseMetricColumns" :key="column.key" class="phase-metric-cell">
|
||||
{{ row.values[column.key] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-inline">当前周波未返回相量结果。</div>
|
||||
|
||||
<div class="feature-header feature-header--nested">序分量与不平衡度</div>
|
||||
<div class="vector-card-grid sequence-grid">
|
||||
<div v-for="item in sequenceCards" :key="item.title" class="feature-card">
|
||||
<div class="feature-card-title">{{ item.title }}</div>
|
||||
<div v-for="row in item.rows" :key="row.label" class="feature-row">
|
||||
<span>{{ row.label }}</span>
|
||||
<span>{{ row.value }}</span>
|
||||
</div>
|
||||
<div class="feature-header feature-header--nested">序分量</div>
|
||||
<div v-if="sequenceMetricRows.length" class="phase-metric-table phase-metric-table--fit">
|
||||
<div class="phase-metric-row phase-metric-row--header" :style="sequenceMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">指标</span>
|
||||
<span v-for="column in sequenceMetricColumns" :key="column.key" class="phase-metric-cell">
|
||||
{{ column.label }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-for="row in sequenceMetricRows" :key="row.label" class="phase-metric-row" :style="sequenceMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">{{ row.label }}</span>
|
||||
<span v-for="column in sequenceMetricColumns" :key="column.key" class="phase-metric-cell">
|
||||
{{ row.values[column.key] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-inline">当前周波未返回序分量结果。</div>
|
||||
|
||||
<div class="feature-header feature-header--nested">不平衡度</div>
|
||||
<div class="phase-metric-table phase-metric-table--fit">
|
||||
<div class="phase-metric-row phase-metric-row--header" :style="unbalanceMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">指标</span>
|
||||
<span class="phase-metric-cell">数值</span>
|
||||
</div>
|
||||
<div v-for="row in unbalanceMetricRows" :key="row.label" class="phase-metric-row" :style="unbalanceMetricGridStyle">
|
||||
<span class="phase-metric-cell phase-metric-cell--label">{{ row.label }}</span>
|
||||
<span class="phase-metric-cell">{{ row.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,10 +112,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref, watch, type CSSProperties } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import type { Waveform } from '@/api/tools/waveform/interface'
|
||||
import type { FeatureCardItem, SummaryItem } from './types'
|
||||
import type { SummaryItem } from './types'
|
||||
|
||||
defineOptions({
|
||||
name: 'WaveformVectorInfo'
|
||||
@@ -105,6 +127,22 @@ const props = defineProps<{
|
||||
activeVectorChannelName: string
|
||||
}>()
|
||||
|
||||
interface PhaseMetricColumn {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface PhaseMetricRow {
|
||||
label: string
|
||||
values: Record<string, string>
|
||||
}
|
||||
|
||||
|
||||
interface MetricRow {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const activeVectorGroupKey = ref('')
|
||||
const activeCycleKey = ref('')
|
||||
const activePhaseKey = ref('')
|
||||
@@ -146,6 +184,21 @@ const formatCycleTime = (value: unknown) => {
|
||||
return formattedValue === '--' ? '--' : `${formattedValue} ms`
|
||||
}
|
||||
|
||||
const buildMetricLabel = (label: string, unit?: string) => {
|
||||
return unit ? `${label} (${unit})` : label
|
||||
}
|
||||
|
||||
const phaseMetricConfigs: Array<{
|
||||
label: string
|
||||
getValue: (phase: Waveform.WavePhaseVectorDTO, unit?: string) => string
|
||||
}> = [
|
||||
{ label: '总有效值', getValue: (phase, unit) => formatWaveValue(phase.totalRms, unit) },
|
||||
{ label: '基波幅值', getValue: (phase, unit) => formatWaveValue(phase.fundamentalAmplitude, unit) },
|
||||
{ label: '基波有效值', getValue: (phase, unit) => formatWaveValue(phase.fundamentalRms, unit) },
|
||||
{ label: '基波相角', getValue: phase => formatPhaseAngle(phase.fundamentalPhaseAngle) },
|
||||
{ label: '谐波畸变率', getValue: phase => formatPercentValue(phase.harmonicDistortionRate) }
|
||||
]
|
||||
|
||||
const buildVectorGroupKey = (group: Waveform.WaveVectorGroupDTO, index: number) => {
|
||||
return group.channelName || `group-${index}`
|
||||
}
|
||||
@@ -227,59 +280,103 @@ const activePhaseVector = computed(() => {
|
||||
})
|
||||
|
||||
const vectorSummaryItems = computed<SummaryItem[]>(() => {
|
||||
const activeGroup = activeVectorGroup.value
|
||||
const currentCycle = activeCycle.value
|
||||
|
||||
// 向量摘要区仅保留参照波形信息,并按每行三组信息展示。
|
||||
return [
|
||||
{ label: '测点名称', value: props.vectorParseResult?.monitorName || '--' },
|
||||
{ label: '事件时间', value: formatWaveformTime(props.vectorParseResult?.time) },
|
||||
{ label: '每周波采样点数', value: props.vectorParseResult?.samplePerCycle ?? '--' },
|
||||
{ label: '可计算周波数', value: props.vectorParseResult?.cycleCount ?? '--' },
|
||||
{ label: '当前分组', value: activeGroup?.channelName || '--' },
|
||||
{ label: '当前周波', value: currentCycle ? buildCycleLabel(currentCycle, Number(currentCycle.cycleIndex ?? 0)) : '--' }
|
||||
]
|
||||
].slice(1)
|
||||
})
|
||||
|
||||
const phaseVectorCards = computed<FeatureCardItem[]>(() => {
|
||||
const activeGroup = activeVectorGroup.value
|
||||
|
||||
return activePhaseVectors.value.map(item => ({
|
||||
title: item.phaseName || '相量',
|
||||
rows: [
|
||||
{ label: '总有效值', value: formatWaveValue(item.totalRms, activeGroup?.unit) },
|
||||
{ label: '基波幅值', value: formatWaveValue(item.fundamentalAmplitude, activeGroup?.unit) },
|
||||
{ label: '基波有效值', value: formatWaveValue(item.fundamentalRms, activeGroup?.unit) },
|
||||
{ label: '基波相角', value: formatPhaseAngle(item.fundamentalPhaseAngle) },
|
||||
{ label: '谐波畸变率', value: formatPercentValue(item.harmonicDistortionRate) }
|
||||
]
|
||||
const phaseMetricColumns = computed<PhaseMetricColumn[]>(() => {
|
||||
return activePhaseVectors.value.map((item, index) => ({
|
||||
key: `${buildPhaseKey(item, index)}-${index}`,
|
||||
label: item.phaseName || `相别 ${index + 1}`
|
||||
}))
|
||||
})
|
||||
|
||||
const sequenceCards = computed<FeatureCardItem[]>(() => {
|
||||
const phaseMetricGridStyle = computed<CSSProperties>(() => ({
|
||||
gridTemplateColumns: `96px repeat(${Math.max(phaseMetricColumns.value.length, 1)}, minmax(96px, 1fr))`
|
||||
}))
|
||||
|
||||
const sequenceMetricGridStyle = computed<CSSProperties>(() => ({
|
||||
gridTemplateColumns: `132px repeat(${Math.max(sequenceMetricColumns.value.length, 1)}, minmax(0, 1fr))`
|
||||
}))
|
||||
|
||||
const unbalanceMetricGridStyle = computed<CSSProperties>(() => ({
|
||||
gridTemplateColumns: '156px minmax(0, 1fr)'
|
||||
}))
|
||||
|
||||
const phaseMetricRows = computed<PhaseMetricRow[]>(() => {
|
||||
const activeGroup = activeVectorGroup.value
|
||||
|
||||
// 相量基础指标按“指标为行、相别为列”转置,减少 A/B/C 三相重复标签。
|
||||
return phaseMetricConfigs.map(config => ({
|
||||
label: config.label,
|
||||
values: activePhaseVectors.value.reduce<Record<string, string>>((result, phase, index) => {
|
||||
const column = phaseMetricColumns.value[index]
|
||||
if (column) {
|
||||
result[column.key] = config.getValue(phase, activeGroup?.unit)
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
}))
|
||||
})
|
||||
|
||||
const sequenceMetrics = computed<Waveform.WaveSequenceVectorDTO[]>(() => {
|
||||
const currentCycle = activeCycle.value
|
||||
|
||||
const sequenceList = [currentCycle?.positiveSequence, currentCycle?.negativeSequence, currentCycle?.zeroSequence]
|
||||
.filter(Boolean)
|
||||
.map(item => ({
|
||||
title: item?.sequenceName || '序分量',
|
||||
rows: [
|
||||
{ label: '幅值', value: formatWaveValue(item?.amplitude, activeGroup?.unit) },
|
||||
{ label: '有效值', value: formatWaveValue(item?.rms, activeGroup?.unit) },
|
||||
{ label: '相角', value: formatPhaseAngle(item?.phaseAngle) }
|
||||
]
|
||||
}))
|
||||
return [currentCycle?.positiveSequence, currentCycle?.negativeSequence, currentCycle?.zeroSequence].filter(
|
||||
(item): item is Waveform.WaveSequenceVectorDTO => Boolean(item)
|
||||
)
|
||||
})
|
||||
|
||||
const sequenceMetricColumns = computed<PhaseMetricColumn[]>(() => {
|
||||
return sequenceMetrics.value.map((item, index) => ({
|
||||
key: `sequence-${index}`,
|
||||
label: item.sequenceName || `序分量 ${index + 1}`
|
||||
}))
|
||||
})
|
||||
|
||||
const sequenceMetricRows = computed<PhaseMetricRow[]>(() => {
|
||||
const activeGroup = activeVectorGroup.value
|
||||
|
||||
// 序分量区域改为“指标为行、序分量为列”,并把单位统一收敛到第一列,避免横向滚动。
|
||||
return [
|
||||
{
|
||||
label: buildMetricLabel('幅值', activeGroup?.unit),
|
||||
getValue: (item: Waveform.WaveSequenceVectorDTO) => formatNumber(item.amplitude)
|
||||
},
|
||||
{
|
||||
label: buildMetricLabel('有效值', activeGroup?.unit),
|
||||
getValue: (item: Waveform.WaveSequenceVectorDTO) => formatNumber(item.rms)
|
||||
},
|
||||
{
|
||||
label: buildMetricLabel('相角', '°'),
|
||||
getValue: (item: Waveform.WaveSequenceVectorDTO) => formatNumber(item.phaseAngle)
|
||||
}
|
||||
].map(config => ({
|
||||
label: config.label,
|
||||
values: sequenceMetrics.value.reduce<Record<string, string>>((result, item, index) => {
|
||||
const column = sequenceMetricColumns.value[index]
|
||||
if (column) {
|
||||
result[column.key] = config.getValue(item)
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
}))
|
||||
})
|
||||
|
||||
const unbalanceMetricRows = computed<MetricRow[]>(() => {
|
||||
const currentCycle = activeCycle.value
|
||||
|
||||
return [
|
||||
...sequenceList,
|
||||
{
|
||||
title: '不平衡度',
|
||||
rows: [
|
||||
{ label: '负序不平衡度', value: formatPercentValue(currentCycle?.unbalance?.negativeUnbalanceRate) },
|
||||
{ label: '零序不平衡度', value: formatPercentValue(currentCycle?.unbalance?.zeroUnbalanceRate) },
|
||||
{ label: '周波中点', value: formatCycleTime(currentCycle?.time) }
|
||||
]
|
||||
}
|
||||
{ label: buildMetricLabel('负序不平衡度', '%'), value: formatNumber(currentCycle?.unbalance?.negativeUnbalanceRate) },
|
||||
{ label: buildMetricLabel('零序不平衡度', '%'), value: formatNumber(currentCycle?.unbalance?.zeroUnbalanceRate) },
|
||||
{ label: buildMetricLabel('周波中点', 'ms'), value: formatNumber(currentCycle?.time) }
|
||||
]
|
||||
})
|
||||
|
||||
@@ -321,6 +418,10 @@ const activeHarmonicRows = computed(() => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vector-summary-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.summary-item,
|
||||
.feature-card,
|
||||
.vector-placeholder,
|
||||
@@ -370,6 +471,62 @@ const activeHarmonicRows = computed(() => {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.phase-metric-table {
|
||||
overflow: auto;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 4px;
|
||||
background: var(--cn-color-canvas-bg);
|
||||
}
|
||||
|
||||
.phase-metric-table--fit {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.phase-metric-row {
|
||||
display: grid;
|
||||
min-width: 384px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.phase-metric-table--fit .phase-metric-row {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.phase-metric-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.phase-metric-row--header {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.phase-metric-cell {
|
||||
padding: 8px 10px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--el-text-color-regular);
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-right: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.phase-metric-row--header .phase-metric-cell {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.phase-metric-cell:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.phase-metric-cell--label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vector-tabs {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
@@ -419,6 +576,49 @@ const activeHarmonicRows = computed(() => {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.vector-summary-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 993px) {
|
||||
.vector-tab-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.vector-tab-content > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vector-tab-content > :first-child,
|
||||
.vector-tab-content > :nth-child(n + 6) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.vector-tab-content > :nth-child(2) {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.vector-tab-content > :nth-child(3) {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.vector-tab-content > :nth-child(4) {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.vector-tab-content > :nth-child(5) {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.vector-summary-grid,
|
||||
.vector-card-grid,
|
||||
@@ -430,4 +630,4 @@ const activeHarmonicRows = computed(() => {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user