yeg
2026-09-08 dc1c47ea58be0c321c7a62d4435c3bbd498c2948
src/views/ReportCompareForm.vue
@@ -86,6 +86,7 @@
  temperature: true,
})
const seriesColors = ref(['#3978f6', '#7d42d8', '#f08a3e'])
const hiddenSeriesKeys = ref<string[]>([])
const visibleMetrics = computed(() => metricDefinitions.filter(metric => metricVisible[metric.key]))
const selectedTemplate = computed(() => persistedTemplates.value.find(item => `remote:${item.id}` === selectedTemplateKey.value))
@@ -149,8 +150,12 @@
    const key = detailKey(item)
    // 未填写才读取当前预览或保存快照的业务值,显式清空不被后续预览覆盖。
    if (detailFieldValues[field][key] === undefined) {
      const source = sourceItems.value.find(value => detailKey(toSelectedTest(value.source)) === key)
      detailFieldValues[field][key] = compareBusinessFieldValue(fieldDescriptor(field), source)
      const source = sourceItems.value.find(value => compareSelectionKey(value.source.selection) === key)
      const sourceDetail = source?.source.consoleDetail
      const sourceValue = sourceDetail?.fieldValues?.[field]?.[item.batteryNo]
      detailFieldValues[field][key] = sourceValue !== undefined
        ? sourceValue
        : compareBusinessFieldValue(fieldDescriptor(field), source)
    }
  })
}
@@ -168,6 +173,7 @@
  selectedTests.value = record.items.map(item => toSelectedTest(item.source))
  metricDefinitions.forEach(metric => { metricVisible[metric.key] = record.settings.metrics.includes(metric.key) })
  seriesColors.value = [...record.settings.seriesColors]
  hiddenSeriesKeys.value = [...(record.settings.hiddenSeriesKeys || [])]
  batteryFields.value = [...record.settings.batteryTemplateFields]
  specialFields.value = [...record.settings.specialFields]
  fieldStructure.value = (record.settings.fieldStructure || []).map(field => ({ ...field }))
@@ -183,6 +189,13 @@
function updateSeriesColor(index: number, color: string | null) {
  if (color) seriesColors.value[index] = color
}
function toggleSeries(item: SelectedTest) {
  if (readonly.value) return
  const key = detailKey(item)
  hiddenSeriesKeys.value = hiddenSeriesKeys.value.includes(key)
    ? hiddenSeriesKeys.value.filter(value => value !== key) : [...hiddenSeriesKeys.value, key]
}
detailFields.value.forEach(ensureDetailFieldValues)
@@ -207,6 +220,7 @@
  selectedTemplateKey.value = ''
  selectedBatteryTemplateName.value = '自定义'
  seriesColors.value = ['#3978f6', '#7d42d8', '#f08a3e']
  hiddenSeriesKeys.value = []
  metricDefinitions.forEach(metric => { metricVisible[metric.key] = true })
  Object.keys(detailFieldValues).forEach(field => delete detailFieldValues[field])
  loadError.value = ''
@@ -374,6 +388,7 @@
    return
  }
  const index = selectedTests.value.indexOf(itemToRemove)
  hiddenSeriesKeys.value = hiddenSeriesKeys.value.filter(key => key !== detailKey(itemToRemove))
  if (index >= 0) { selectedTests.value.splice(index, 1); seriesColors.value.splice(index, 1) }
  await refreshPreview()
}
@@ -468,6 +483,7 @@
    name: reportName.value, description: description.value, experimentName: experimentName.value,
    settings: {
      metrics: visibleMetrics.value.map(item => item.key), seriesColors: seriesColors.value.slice(0, selectedTests.value.length),
      hiddenSeriesKeys: [...hiddenSeriesKeys.value],
      batteryTemplateFields: [...batteryFields.value], specialFields: [...specialFields.value],
      fieldStructure: detailFields.value.map(fieldDescriptor),
      ...(sourceDetailOverrides.value === undefined ? {} : { sourceDetailOverrides: Object.fromEntries(Object.entries(sourceDetailOverrides.value)
@@ -614,9 +630,10 @@
    <section class="metrics-panel">
      <div v-if="!readonly" class="metric-switches"><label v-for="metric in metricDefinitions" :key="metric.key"><span>{{ metric.title }}</span><el-switch v-model="metricVisible[metric.key]" inline-prompt active-text="显" inactive-text="隐" /></label></div>
      <div v-if="chartSeries.length" class="chart-legend-bar"><strong>对比图例</strong><div v-for="(item, index) in chartSeries" :key="compareSelectionKey(item.selection)" class="chart-legend-item"><el-color-picker v-if="!readonly" class="legend-color-picker" :model-value="seriesColors[index]" :show-alpha="false" popper-class="legend-color-popper" :aria-label="`修改${item.name}颜色`" @change="updateSeriesColor(index, $event)" /><i v-else class="series-swatch" :style="{ background: seriesColors[index] }" /><span>{{ item.name }} · {{ item.batteryNo }} · 循环{{ item.cycle }} · {{ item.process }}</span></div></div>
      <div v-if="chartSeries.length" class="chart-legend-bar"><strong>对比图例</strong><div v-for="(item, index) in chartSeries" :key="detailKey(item)" class="chart-legend-item"><el-color-picker v-if="!readonly" class="legend-color-picker" :model-value="seriesColors[index]" :show-alpha="false" popper-class="legend-color-popper" :aria-label="`修改${item.name}颜色`" @change="updateSeriesColor(index, $event)" /><i v-else class="series-swatch" :style="{ background: seriesColors[index] }" /><button type="button" class="legend-name" :class="{ 'is-hidden': hiddenSeriesKeys.includes(detailKey(item)) }" :aria-pressed="!hiddenSeriesKeys.includes(detailKey(item))" :disabled="readonly" @click="toggleSeries(item)">{{ item.name }} (#{{ item.id }}) · {{ item.batteryGroup }} · {{ item.batteryNo }} · 循环{{ item.cycle }} · {{ item.process }}</button></div></div>
      <!-- 报表切换和比对项移除时完整替换配置,避免图表库将颜色数组缩短误判为组件合并。 -->
      <div class="charts-grid"><article v-for="metric in visibleMetrics" :key="metric.key" class="chart-card"><header><strong>{{ metric.title }}</strong></header><VChart class="metric-chart" :option="compareChartOption(metric, sourceItems, seriesColors)" :update-options="{ notMerge: true }" autoresize /></article></div>
      <div v-if="!visibleMetrics.length" class="chart-empty">当前未启用曲线指标</div>
      <div class="charts-grid"><article v-for="metric in visibleMetrics" :key="metric.key" class="chart-card"><header><strong>{{ metric.title }}</strong></header><VChart class="metric-chart" :option="compareChartOption(metric, sourceItems, seriesColors, hiddenSeriesKeys)" :update-options="{ notMerge: true }" autoresize /><div v-if="!sourceItems.some(item => !hiddenSeriesKeys.includes(compareSelectionKey(item.source.selection)) && item.records.some(row => (metric.key === 'voltage' || metric.key === 'current' ? row.deviceTime : row.collectorDeviceTime) != null && (metric.key === 'positiveVoltage' ? row.positivePotential : metric.key === 'negativeVoltage' ? row.negativePotential : row[metric.key]) != null))" class="chart-empty">{{ sourceItems.length && hiddenSeriesKeys.length === sourceItems.length ? '当前全部曲线已隐藏' : '所选范围暂无该指标数据' }}</div></article></div>
    </section>
    <footer class="form-actions">
@@ -720,6 +737,10 @@
.chart-legend-bar > strong { color: #26333f; font-size: 12px; font-weight: 600; }
.chart-legend-item { display: inline-flex; align-items: center; gap: 7px; min-width: 0; color: #52606d; font-size: 12px; }
.chart-legend-item span { max-width: 360px; overflow-wrap: anywhere; }
.legend-name { max-width: 100%; border: 0; padding: 4px 0; background: transparent; color: inherit; font: inherit; text-align: left; overflow-wrap: anywhere; cursor: pointer; }
.legend-name.is-hidden { color: #8795a3; text-decoration: line-through; }
.legend-name:disabled { cursor: default; }
.chart-empty { padding: 12px; color: #8795a3; font-size: 12px; text-align: center; }
.legend-color-picker { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; }
.legend-color-picker :deep(.el-color-picker__trigger) { width: 14px !important; height: 14px !important; padding: 0 !important; border: 0 !important; border-radius: 2px; background: transparent !important; box-shadow: none !important; overflow: hidden; }
.legend-color-picker :deep(.el-color-picker__color) { width: 14px; height: 14px; border: 0; border-radius: 2px; box-shadow: 0 0 0 1px rgba(36, 54, 71, .18); }