| | |
| | | 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)) |
| | |
| | | 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) |
| | | } |
| | | }) |
| | | } |
| | |
| | | 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 })) |
| | |
| | | |
| | | 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) |
| | |
| | | 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 = '' |
| | |
| | | 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() |
| | | } |
| | |
| | | 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) |
| | |
| | | |
| | | <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"> |
| | |
| | | .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); } |