yeg
2026-09-03 0e0d22fc13fcc81b39292010a31906644a4dfd16
调整工步合并即时保存与图表展示

合并选中后立即持久化规则,解除合并增加确认提示并即时保存。存在逻辑工步时图表仅展示逻辑工步名称,隐藏实际工步和循环编号前缀。
2个文件已修改
63 ■■■■■ 已修改文件
src/components/ConsoleChartPanel.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/TestConsole.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ConsoleChartPanel.vue
@@ -155,9 +155,9 @@
  const logical: StepOption[] = []
  for (const mapping of props.logicStepMappings || []) {
    const hit = snapshotRecords.value.some(record => Number(record.cycleNo) === cycleNo && logicalStepMatches(record, mapping))
    if (hit) logical.push({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: `${cycleNo}-逻辑${mapping.logicalNo} ${mapping.logicalName}`, cycleNo, logical: mapping })
    if (hit) logical.push({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: mapping.logicalName, cycleNo, logical: mapping })
  }
  return [...actual.values(), ...logical]
  return logical.length ? logical : [...actual.values()]
})
function ensureDetailFieldValues() {
@@ -298,8 +298,8 @@
    })
  const logical = (props.logicStepMappings || []).filter(mapping => snapshotRecords.value.some(record => String(record.batteryNo) === cellNo
    && Number(record.cycleNo) === cycleNo && logicalStepMatches(record, mapping)))
    .map(mapping => ({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: `${cycleNo}-逻辑${mapping.logicalNo} ${mapping.logicalName}`, cycleNo, logical: mapping }))
  return [...actual.values(), ...logical]
    .map(mapping => ({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: mapping.logicalName, cycleNo, logical: mapping }))
  return logical.length ? logical : [...actual.values()]
}
function cellUsesOverride(cellNo: string) {
src/views/TestConsole.vue
@@ -207,12 +207,55 @@
function openStepSettings() { stepDraft.value = stepSettings.value.map(item => ({ ...item })); selectedPlanSteps.value = []; stepEditing.value = true }
function togglePlanStep(position: number, checked: string | number | boolean) { const next = new Set(selectedPlanSteps.value); checked ? next.add(position) : next.delete(position); selectedPlanSteps.value = [...next].sort((a, b) => a - b) }
async function addStepMapping() { const positions = [...selectedPlanSteps.value].sort((a, b) => a - b); if (positions.length < 2) { ElMessage.warning('请先勾选至少两个连续工步'); return }; const contiguous = positions.every((value, index) => index === 0 || value === positions[index - 1] + 1); if (!contiguous) { ElMessage.warning('只能合并连续工步'); return }; if (stepDraft.value.some(item => positions[0] <= item.endStepNo && positions[positions.length - 1] >= item.startStepNo)) { ElMessage.warning('选中的工步已在其他合并组中'); return }; try { const result = await ElMessageBox.prompt('请输入逻辑工步名称', '合并工步', { inputPlaceholder: '例如:充电阶段', inputValidator: value => value.trim() ? true : '逻辑工步名称不能为空' }); const next = stepDraft.value.length + 1; stepDraft.value.push({ logicalNo: next, logicalName: result.value.trim(), startStepNo: positions[0], endStepNo: positions[positions.length - 1] }); selectedPlanSteps.value = [] } catch { /* 用户取消命名 */ } }
async function persistStepMappings(mappings: TestLogicStepMapping[]) {
  const detail = await saveLogicStepMappings(testId.value, mappings)
  stepSettings.value = detail.logicStepMappings || []
  backendDetail.value = detail
  stepDraft.value = stepSettings.value.map(item => ({ ...item }))
}
async function addStepMapping() {
  const positions = [...selectedPlanSteps.value].sort((a, b) => a - b)
  if (positions.length < 2) { ElMessage.warning('请先勾选至少两个连续工步'); return }
  const contiguous = positions.every((value, index) => index === 0 || value === positions[index - 1] + 1)
  if (!contiguous) { ElMessage.warning('只能合并连续工步'); return }
  if (stepDraft.value.some(item => positions[0] <= item.endStepNo && positions[positions.length - 1] >= item.startStepNo)) {
    ElMessage.warning('选中的工步已在其他合并组中'); return
  }
  try {
    const result = await ElMessageBox.prompt('请输入逻辑工步名称', '合并工步', {
      inputPlaceholder: '例如:充电阶段', inputValidator: value => value.trim() ? true : '逻辑工步名称不能为空',
    })
    const previous = stepDraft.value.map(item => ({ ...item }))
    const next = stepDraft.value.length + 1
    stepDraft.value.push({ logicalNo: next, logicalName: result.value.trim(), startStepNo: positions[0], endStepNo: positions[positions.length - 1] })
    normalizeStepDraft()
    try {
      await persistStepMappings(stepDraft.value)
      selectedPlanSteps.value = []
      ElMessage.success('工步合并规则已保存')
    } catch (error) {
      stepDraft.value = previous
      ElMessage.error(error instanceof Error ? error.message : '工步合并保存失败')
    }
  } catch { /* 用户取消命名或确认 */ }
}
function normalizeStepDraft() { stepDraft.value.sort((a, b) => a.startStepNo - b.startStepNo); stepDraft.value.forEach((item, i) => { item.logicalNo = i + 1 }) }
function removeStepMapping(index: number) { stepDraft.value.splice(index, 1); normalizeStepDraft() }
async function saveSteps() {
  try { normalizeStepDraft(); const detail = await saveLogicStepMappings(testId.value, stepDraft.value); stepSettings.value = detail.logicStepMappings || []; backendDetail.value = detail; stepEditing.value = false; selectedPlanSteps.value = []; ElMessage.success('工步合并规则已保存') }
  catch (error) { ElMessage.error(error instanceof Error ? error.message : '工步设置保存失败') }
async function removeStepMapping(index: number) {
  try {
    await ElMessageBox.confirm('解除后将立即保存该变更,确定解除合并吗?', '解除合并', {
      confirmButtonText: '解除合并', cancelButtonText: '取消', type: 'warning',
    })
    const previous = stepDraft.value.map(item => ({ ...item }))
    stepDraft.value.splice(index, 1)
    normalizeStepDraft()
    try {
      await persistStepMappings(stepDraft.value)
      ElMessage.success('合并规则已解除')
    } catch (error) {
      stepDraft.value = previous
      ElMessage.error(error instanceof Error ? error.message : '解除合并保存失败')
    }
  } catch { /* 用户取消解除 */ }
}
function cancelStepSettings() { stepEditing.value = false; selectedPlanSteps.value = [] }
@@ -396,7 +439,7 @@
    <div class="console-layout">
      <main>
    <section class="panel step-panel"><header class="panel-heading"><div><i></i><strong>项目参数</strong><span>STEP PARAMETERS</span></div><div class="step-actions"><template v-if="stepEditing"><button class="primary-button compact-button" type="button" @click="addStepMapping"><el-icon><Plus /></el-icon>合并选中</button><button class="primary-button compact-button" type="button" @click="saveSteps"><el-icon><Check /></el-icon>保存</button><button class="secondary-button compact-button" type="button" @click="cancelStepSettings">取消</button></template><button v-else class="primary-button compact-button" type="button" @click="openStepSettings"><el-icon><Setting /></el-icon>工步设置</button></div></header><div class="project-meta"><span>测试项目 <strong>{{ profile.purpose }}</strong></span><span>当前循环 <strong>{{ currentCycleNo || '-' }} / 计划 {{ plannedCycleCount || '-' }}</strong></span><span>当前工步 <strong>{{ currentStepNo || '-' }}</strong></span><span>执行状态 <strong>{{ testStatusLabel }}</strong></span></div><div class="step-table-wrap"><table><thead><tr><th v-if="stepEditing">选择</th><th>工步号</th><th>工步名称</th><th>时间(h:min:s.ms)</th><th>电压(V)</th><th>执行状态</th></tr></thead><tbody><template v-if="stepEditing"><tr v-for="step in actualPlanRows" :key="`edit-${step.cyclePosition}`" :class="{ active: step.executionStatus === '进行中' }"><td><el-checkbox :model-value="selectedPlanSteps.includes(step.cyclePosition)" @change="(checked: string | number | boolean) => togglePlanStep(step.cyclePosition, checked)" /></td><td>{{ step.stepNo }}</td><td><strong>{{ step.stepName }}</strong></td><td>{{ formatDuration(step.durationSeconds) }}</td><td>{{ step.voltageV == null ? '-' : `${Number(step.voltageV).toFixed(4)} V` }}</td><td><span class="step-state" :class="step.executionStatus">{{ step.executionStatus }}</span></td></tr></template><template v-else><tr v-for="(step, index) in planRows" :key="`${index}-${step.no}-${step.name}`" :class="{ active: step.state === '进行中' }"><td>{{ step.no }}</td><td><strong>{{ step.name }}</strong></td><td>{{ step.time }}</td><td>{{ step.voltage }}</td><td><span class="step-state" :class="step.state">{{ step.state }}</span></td></tr></template><tr v-if="!planRows.length && !actualPlanRows.length"><td :colspan="stepEditing ? 6 : 5" class="empty-cell">暂无后端检测柜工步方案</td></tr></tbody></table></div><div v-if="stepEditing && stepDraft.length" class="mapping-list"><div v-for="(item, index) in stepDraft" :key="index" class="mapping-row"><strong>逻辑工步 {{ item.logicalNo }}</strong><span>第 {{ item.startStepNo }} 至 {{ item.endStepNo }} 个工步</span><button class="secondary-button compact-button" type="button" aria-label="解除合并" @click="removeStepMapping(index)"><el-icon><Delete /></el-icon>解除合并</button></div></div></section>
    <section class="panel step-panel"><header class="panel-heading"><div><i></i><strong>项目参数</strong><span>STEP PARAMETERS</span></div><div class="step-actions"><template v-if="stepEditing"><button class="primary-button compact-button" type="button" @click="addStepMapping"><el-icon><Plus /></el-icon>合并选中</button><button class="secondary-button compact-button" type="button" @click="cancelStepSettings">取消</button></template><button v-else class="primary-button compact-button" type="button" @click="openStepSettings"><el-icon><Setting /></el-icon>工步设置</button></div></header><div class="project-meta"><span>测试项目 <strong>{{ profile.purpose }}</strong></span><span>当前循环 <strong>{{ currentCycleNo || '-' }} / 计划 {{ plannedCycleCount || '-' }}</strong></span><span>当前工步 <strong>{{ currentStepNo || '-' }}</strong></span><span>执行状态 <strong>{{ testStatusLabel }}</strong></span></div><div class="step-table-wrap"><table><thead><tr><th v-if="stepEditing">选择</th><th>工步号</th><th>工步名称</th><th>时间(h:min:s.ms)</th><th>电压(V)</th><th>执行状态</th></tr></thead><tbody><template v-if="stepEditing"><tr v-for="step in actualPlanRows" :key="`edit-${step.cyclePosition}`" :class="{ active: step.executionStatus === '进行中' }"><td><el-checkbox :model-value="selectedPlanSteps.includes(step.cyclePosition)" @change="(checked: string | number | boolean) => togglePlanStep(step.cyclePosition, checked)" /></td><td>{{ step.stepNo }}</td><td><strong>{{ step.stepName }}</strong></td><td>{{ formatDuration(step.durationSeconds) }}</td><td>{{ step.voltageV == null ? '-' : `${Number(step.voltageV).toFixed(4)} V` }}</td><td><span class="step-state" :class="step.executionStatus">{{ step.executionStatus }}</span></td></tr></template><template v-else><tr v-for="(step, index) in planRows" :key="`${index}-${step.no}-${step.name}`" :class="{ active: step.state === '进行中' }"><td>{{ step.no }}</td><td><strong>{{ step.name }}</strong></td><td>{{ step.time }}</td><td>{{ step.voltage }}</td><td><span class="step-state" :class="step.state">{{ step.state }}</span></td></tr></template><tr v-if="!planRows.length && !actualPlanRows.length"><td :colspan="stepEditing ? 6 : 5" class="empty-cell">暂无后端检测柜工步方案</td></tr></tbody></table></div><div v-if="stepEditing && stepDraft.length" class="mapping-list"><div v-for="(item, index) in stepDraft" :key="index" class="mapping-row"><strong>逻辑工步 {{ item.logicalNo }}</strong><span>第 {{ item.startStepNo }} 至 {{ item.endStepNo }} 个工步</span><button class="secondary-button compact-button" type="button" aria-label="解除合并" @click="removeStepMapping(index)"><el-icon><Delete /></el-icon>解除合并</button></div></div></section>
        <section class="panel alarm-panel"><header class="panel-heading"><div><i></i><strong>异常警告</strong><span>ALARM</span></div></header><div v-if="currentExceptions.length" class="alarm-list"><div v-for="(item, index) in currentExceptions" :key="`${item.sourceId || item.sourceType}-${item.channelCode || index}-${index}`"><el-icon><WarningFilled /></el-icon><span><strong>{{ item.sourceType === 'COLLECTOR' ? `采集器${item.collectorName || item.collectorCode || `#${item.sourceId}`}${item.status === 'OPEN' ? '离线' : '异常已处理'} · ${item.channelCode || '来源'}` : (item.message || '数据来源当前不可观测') }}</strong><small>{{ item.measurementName ? `${item.measurementName}受影响 · ` : '' }}{{ item.message || (item.status === 'OPEN' ? '当前异常' : '异常已处理') }}</small></span></div></div><div v-else class="alarm-empty"><el-icon><CircleCheckFilled /></el-icon>暂无异常</div></section>
         <section class="panel channel-panel"><header class="panel-heading"><div><i></i><strong>通道</strong><span>CHANNEL STATUS</span></div></header><div class="channel-table-wrap"><table :style="{ minWidth: `${1120 + collectorColumns.length * 120}px` }"><thead><tr><th>电池号</th><th>测试柜</th><th>柜状态</th><th>检测通道</th><th>采集器</th><th>采集器状态</th><th v-for="column in collectorColumns" :key="column.key">{{ column.label }}</th></tr></thead><tbody><tr v-for="item in channels" :key="item.no"><td><strong>{{ item.no }}</strong></td><td>{{ item.cabinet }}</td><td><span class="device-state" :class="item.cabinetState">{{ item.cabinetState }}</span></td><td>{{ item.channel }}</td><td>{{ item.collector }}</td><td><span class="device-state" :class="{ 在线: item.collectorState === '在线', 异常: item.collectorState !== '在线' }">{{ item.collectorState }}</span></td><td v-for="column in collectorColumns" :key="column.key">{{ item.measurements[column.measurementCode] || '-' }}</td></tr><tr v-if="!channels.length"><td :colspan="6 + collectorColumns.length" class="empty-cell">暂无通道配置</td></tr></tbody></table></div></section>
      </main>