| | |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { getUserInfo } from '@/api/auth' |
| | | import { getSpliceSources, previewSpliceReport, createSpliceReport, editSpliceReport, refreshSpliceSource, |
| | | getSpliceReport, getSpliceVersion, listSpliceVersions, spliceFields, spliceFieldValue, spliceStatisticKey, spliceSourceKey, spliceSourceValue, spliceExperiment, |
| | | type SpliceCandidate, type SpliceMatrix, type SpliceField, type SpliceSettings, type SavedSpliceReport } from '@/api/spliceReports' |
| | | getSpliceReport, getSpliceVersion, listSpliceVersions, spliceFields, spliceFieldValue, spliceStatisticKey, spliceSourceKey, spliceSourceValue, spliceExperiment, spliceSourceFields, spliceDetailValue, |
| | | type SpliceCandidate, type SpliceMatrix, type SpliceField, type SpliceSettings, type SpliceSegment, type SavedSpliceReport } from '@/api/spliceReports' |
| | | import { openSpliceSavedReportPrint } from '@/utils/spliceSavedReport' |
| | | import SpliceSavedReport from '@/components/SpliceSavedReport.vue' |
| | | import SpliceFieldControls from '@/components/SpliceFieldControls.vue' |
| | |
| | | const detailFieldValues = ref<Record<string, Record<string, string>>>({}) |
| | | const statisticValues = ref<Record<string, string>>({}) |
| | | const sourceExperimentNames = ref<Record<string,string>>({}) |
| | | const sourceDetailOverrides = ref<SpliceSettings['sourceDetailOverrides']>({}) |
| | | const hiddenSeries = ref(false) |
| | | const statisticMetrics = [{ key: 'finalVoltage1', title: '最终电压1', unit: 'V' }, { key: 'energyEfficiency', title: '能量效率', unit: '%' }] as const |
| | | const statisticVisible = reactive({ finalVoltage1: true, energyEfficiency: true }) |
| | |
| | | const matrix = computed(() => savedReport.value || draftMatrix.value) |
| | | const segmentMode = computed(() => !savedReport.value || savedReport.value.spliceMode === 'battery-segments') |
| | | const detailColumns = computed(() => matrix.value?.segments || []) |
| | | const sourceFields = computed(() => spliceSourceFields(detailColumns.value, sourceDetailOverrides.value)) |
| | | const independentDetails = computed(() => segmentMode.value && sourceDetailOverrides.value !== undefined) |
| | | const statisticSettings = computed<SpliceSettings>(() => ({ |
| | | ...(sourceDetailOverrides.value === undefined ? {} : { sourceDetailOverrides: sourceDetailOverrides.value }), |
| | | sourceExperimentNames: sourceExperimentNames.value, |
| | | hiddenSeries: hiddenSeries.value, |
| | | fieldStructure: fields.value, |
| | |
| | | detailFieldValues.value = structuredClone(saved.settings.detailFieldValues || {}) |
| | | statisticValues.value = { ...saved.settings.statisticValues } |
| | | sourceExperimentNames.value = { ...saved.settings.sourceExperimentNames } |
| | | sourceDetailOverrides.value = saved.settings.sourceDetailOverrides === undefined ? undefined : structuredClone(saved.settings.sourceDetailOverrides) |
| | | hiddenSeries.value = saved.settings.hiddenSeries === true |
| | | seriesColors.value = [...(saved.settings.seriesColors || ['#3978f6', '#109854', '#d25b45', '#7754a8'])] |
| | | selectedTests.value = saved.segments.map(segment => segment.source) |
| | |
| | | selectedTests.value = []; samples.value = []; reportName.value = ''; experimentName.value = '' |
| | | fields.value = []; detailFieldValues.value = {}; statisticValues.value = {}; reordered = false |
| | | sourceExperimentNames.value = {}; hiddenSeries.value = false |
| | | sourceDetailOverrides.value = {} |
| | | sources.value = await getSpliceSources() |
| | | savedContent.value = formContent() |
| | | } |
| | |
| | | // 来源移除后同步移除其人工值,仍在范围内的修改继续保留。 |
| | | statisticValues.value = Object.fromEntries(Object.entries(statisticValues.value).filter(([key]) => |
| | | selected.some(selection => key.startsWith(`${selection.testId}|${selection.testBatteryId}|`)))) |
| | | sourceDetailOverrides.value = Object.fromEntries(Object.entries(sourceDetailOverrides.value || {}).filter(([key]) => |
| | | selected.some(selection => key === `${selection.testId}|${selection.testBatteryId}`))) |
| | | if (selected.length < 2) { previewing.value = false; return } |
| | | previewing.value = true |
| | | try { |
| | |
| | | statisticMetrics.map(metric => spliceStatisticKey(segment, cycle, metric.key))))) |
| | | statisticValues.value = Object.fromEntries(Object.entries(statisticValues.value).filter(([key]) => allowed.has(key))) |
| | | draftMatrix.value = result |
| | | // 控制台只提供字段名,带入时不猜单位;已有人工结构与值保持不变。 |
| | | for (const segment of result.segments) for (const category of ['battery', 'special'] as const) { |
| | | for (const name of segment.consoleDetail?.[category === 'battery' ? 'batteryFields' : 'specialFields'] || []) { |
| | | if (!fields.value.some(field => field.name === name)) fields.value.push({ name, category, displayName: name, unit: '' }) |
| | | } |
| | | } |
| | | } |
| | | } catch (error) { |
| | | if (request === previewRequest) previewError.value = error instanceof Error ? error.message : '统计预览失败' |
| | |
| | | function setFieldValue(field: SpliceField, battery: string, value: string) { |
| | | detailFieldValues.value[field.name] ||= {} |
| | | detailFieldValues.value[field.name][battery] = value |
| | | } |
| | | function setDetailValue(segment: SpliceSegment, value: string, field?: typeof sourceFields.value[number]) { |
| | | sourceDetailOverrides.value ||= {} |
| | | const key = spliceSourceKey(segment.source) |
| | | const current = sourceDetailOverrides.value[key] || {} |
| | | sourceDetailOverrides.value[key] = field ? { ...current, fieldValues: { ...current.fieldValues, [field.key]: value } } : { ...current, experimentName: value } |
| | | } |
| | | function applyFields(selected: SpliceField[]) { |
| | | // 模板不携值;同名但身份或单位不同的字段不能继承原模板人工值。来源详情覆盖完全独立。 |
| | | for (const field of selected) { |
| | | const previous = fields.value.find(item => item.name === field.name) |
| | | if (!previous || (previous.fieldDefinitionId ?? null) !== (field.fieldDefinitionId ?? null) || previous.category !== field.category || previous.unit !== field.unit) delete detailFieldValues.value[field.name] |
| | | } |
| | | fields.value = selected |
| | | } |
| | | async function addSpecialField() { |
| | | try { |
| | |
| | | <section class="detail-panel"> |
| | | <header class="panel-heading"><div><strong>测试详情</strong></div></header> |
| | | <div class="detail-meta"><label><span>报表说明</span><el-input v-model="experimentName" /></label><div><span>电池组</span><strong>{{ batteryGroup }}</strong></div></div> |
| | | <div v-if="segmentMode" class="detail-table-wrap"><table class="detail-table" :style="{ minWidth: `${Math.max(760, detailColumns.length * 190 + 170)}px` }"><thead><tr><th>字段</th><th v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)">{{ segment.source.name }}<br>{{ segment.source.group }} · {{ segment.source.batteryNo }}</th></tr></thead><tbody> |
| | | <tr><td>实验</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceExperiment(segment, statisticSettings)" placeholder="缺失" @update:model-value="sourceExperimentNames[spliceSourceKey(segment.source)] = $event" /></td></tr> |
| | | <tr v-for="field in fields" :key="field.name"><td>{{ field.displayName }}{{ field.unit ? ` (${field.unit})` : '' }}</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceSourceValue(segment, statisticSettings, field)" size="small" placeholder="缺失" @update:model-value="setFieldValue(field, spliceSourceKey(segment.source), $event)" /></td></tr> |
| | | <div v-if="independentDetails" class="detail-table-wrap"><table class="detail-table source-detail-table" :style="{ minWidth: `${Math.max(760, detailColumns.length * 190 + 170)}px` }"><thead><tr><th>来源详情字段</th><th v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)">{{ segment.source.name }}<br>{{ segment.source.group }} · {{ segment.source.batteryNo }}</th></tr></thead><tbody> |
| | | <tr><td>实验</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceDetailValue(segment, sourceDetailOverrides)" :aria-label="`${segment.source.name} ${segment.source.batteryNo} 实验名称`" placeholder="缺失" @update:model-value="setDetailValue(segment, $event)" /></td></tr> |
| | | <tr v-for="field in sourceFields" :key="field.key"><td>{{ field.name }}({{ field.category === 'battery' ? '电池字段' : '特殊字段' }})</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceDetailValue(segment, sourceDetailOverrides, field)" :aria-label="`${segment.source.name} ${segment.source.batteryNo} ${field.category === 'battery' ? '电池字段' : '特殊字段'} ${field.name}`" size="small" placeholder="缺失" @update:model-value="setDetailValue(segment, $event, field)" /></td></tr> |
| | | </tbody></table></div> |
| | | <div v-if="segmentMode" class="detail-table-wrap"><table class="detail-table report-field-table" :style="{ minWidth: `${Math.max(760, detailColumns.length * 190 + 170)}px` }"><thead><tr><th>{{ independentDetails ? '报表字段' : '字段' }}</th><th v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)">{{ segment.source.name }}<br>{{ segment.source.group }} · {{ segment.source.batteryNo }}</th></tr></thead><tbody> |
| | | <tr v-if="!independentDetails"><td>实验</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceExperiment(segment, statisticSettings)" placeholder="缺失" @update:model-value="sourceExperimentNames[spliceSourceKey(segment.source)] = $event" /></td></tr> |
| | | <tr v-for="field in fields" :key="field.name"><td>{{ field.displayName }}{{ field.unit ? ` (${field.unit})` : '' }}</td><td v-for="segment in detailColumns" :key="spliceSourceKey(segment.source)"><el-input :model-value="spliceSourceValue(segment, statisticSettings, field)" :aria-label="`${segment.source.name} ${segment.source.batteryNo} 报表字段 ${field.displayName}`" size="small" placeholder="缺失" @update:model-value="setFieldValue(field, spliceSourceKey(segment.source), $event)" /></td></tr> |
| | | </tbody></table></div> |
| | | <div v-else class="detail-table-wrap"><table class="detail-table" :style="{ minWidth: `${Math.max(760, samples.length * 190 + 170)}px` }"><thead><tr><th>字段</th><th v-for="sample in samples" :key="sample">{{ sample }}</th></tr></thead><tbody> |
| | | <tr v-for="field in fields" :key="field.name"><td>{{ field.displayName }}{{ field.unit ? ` (${field.unit})` : '' }}</td><td v-for="sample in samples" :key="sample"><el-input :model-value="fieldValue(field, sample)" size="small" placeholder="缺失" @update:model-value="setFieldValue(field, sample, $event)" /></td></tr> |
| | | </tbody></table></div> |
| | | <div class="detail-actions"><button class="primary-button" type="button" @click="addSpecialField"><el-icon><Plus /></el-icon>添加项</button></div> |
| | | <SpliceFieldControls :fields="fields" :segments="matrix?.segments || []" :can-create="canCreate" :can-edit="canEdit" @apply="fields = $event" /> |
| | | <SpliceFieldControls :fields="fields" :segments="matrix?.segments || []" :can-create="canCreate" :can-edit="canEdit" @apply="applyFields" /> |
| | | </section> |
| | | |
| | | <section v-if="statisticPreview" class="chart-panel"> |