From 0c60347d7bdae891997f298ac726610d558cdbb3 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 27 八月 2026 21:43:18 +0800
Subject: [PATCH] fix(test): preserve existing creation page

---
 src/views/TestCreate.vue |  614 ++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 357 insertions(+), 257 deletions(-)

diff --git a/src/views/TestCreate.vue b/src/views/TestCreate.vue
index 47b0579..4d22b64 100644
--- a/src/views/TestCreate.vue
+++ b/src/views/TestCreate.vue
@@ -1,17 +1,16 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { useRoute, useRouter } from 'vue-router'
 import {
   createTest,
   getTestConfigurationOptions,
   getTestDetail,
   updateTest,
+  type TestConfigurationDetail,
   type TestConfigurationOptions,
-  type TestConfigurationPayload,
+  type TestPlanStepPayload,
 } from '@/api/tests'
-
-type ThresholdUnit = 'A' | 'V' | 'Ah' | 'Wh' | '℃'
 
 interface BatteryRow {
   key: number
@@ -21,7 +20,21 @@
   cabinet: number | null
   cabinetChannel: number | null
   collector: number | null
-  expectedBarcode: string
+  expectedBarcode: string | null
+  temperature1: string
+  temperature2: string
+  temperature3: string
+  positive1: string
+  positive2: string
+  negative1: string
+  negative2: string
+  customChannels: Record<string, string>
+}
+
+interface CustomChannelColumn {
+  key: string
+  label: string
+  fieldName: string
 }
 
 interface BatteryGroupOption {
@@ -36,17 +49,7 @@
   type: string
   min: string
   max: string
-  unit: ThresholdUnit
-}
-
-interface PlanStepRow {
-  key: number
-  selected: boolean
-  cycleNo: number
-  stepNo: number
-  stepType: string
-  stepName: string
-  parametersJson: string
+  unit: 'A' | 'V' | 'Ah' | 'Wh' | '℃'
 }
 
 const router = useRouter()
@@ -58,9 +61,12 @@
 const batteryDialogVisible = ref(false)
 const selectedBatteryGroup = ref<number | null>(null)
 const selectedBatteryNos = ref<string[]>([])
+const configurationOptions = ref<TestConfigurationOptions | null>(null)
+const preservedPlanSteps = ref<TestPlanStepPayload[]>([])
+const monitorTimeoutMinutes = ref(30)
 let batteryKey = 1
 let thresholdKey = 1
-let planStepKey = 1
+let customChannelKey = 1
 
 const form = reactive({
   name: '',
@@ -68,29 +74,53 @@
   testTypeId: null as number | null,
   purpose: '',
   remark: '',
-  monitorTimeoutMinutes: 30,
+  syncMode: '默认同步检测柜',
 })
 
 const owners = ref<Array<{ id: number; name: string; userName: string }>>([])
 const testTypes = ref<Array<{ id: number; name: string }>>([])
 const ownersLoading = ref(false)
 const testTypesLoading = ref(false)
-const configurationOptions = ref<TestConfigurationOptions | null>(null)
-const cabinetOptions = ref<Array<{ id: number; label: string }>>([])
-const collectorOptions = ref<Array<{ id: number; label: string }>>([])
-const thresholdTypes = [
+const sensorChannels = Array.from({ length: 12 }, (_, index) => `S-${String(index + 1).padStart(2, '0')}`)
+const thresholdTypes: Array<{
+  label: string
+  value: string
+  unit: 'A' | 'V' | 'Ah' | 'Wh' | '℃'
+  disabled?: boolean
+}> = [
   { label: '温度', value: 'TEMPERATURE', unit: '℃' as const },
   { label: '电压', value: 'VOLTAGE', unit: 'V' as const },
   { label: '电流', value: 'CURRENT', unit: 'A' as const },
   { label: '容量', value: 'CAPACITY', unit: 'Ah' as const },
-  { label: '能量', value: 'ENERGY', unit: 'Wh' as const },
+  { label: '检测柜返回项(待现场验证)', value: 'CABINET_RETURN', unit: 'V' as const, disabled: true },
 ]
+const fixedSensorFields = ['temperature1', 'temperature2', 'temperature3', 'positive1', 'positive2', 'negative1', 'negative2'] as const
+type FixedSensorField = typeof fixedSensorFields[number]
+const reservedChannelLabels = ['主温度通道', '温度通道 2', '温度通道 3', '正电位主通道', '正电位通道 2', '负电位主通道', '负电位通道 2']
+const customChannelColumns = ref<CustomChannelColumn[]>([])
+const fixedFieldNames: Record<FixedSensorField, string> = {
+  temperature1: 'temperature1', temperature2: 'temperature2', temperature3: 'temperature3',
+  positive1: 'positive1', positive2: 'positive2', negative1: 'negative1', negative2: 'negative2',
+}
 const batteryGroupOptions = ref<BatteryGroupOption[]>([])
+const cabinetOptions = computed(() => {
+  const cabinets = new Map<number, string>()
+  configurationOptions.value?.channels.forEach(channel => {
+    cabinets.set(channel.cabinetId, [channel.cabinetCode, channel.cabinetName].filter(Boolean).join(' '))
+  })
+  return [...cabinets].map(([id, label]) => ({ id, label }))
+})
+const collectorOptions = computed(() => (configurationOptions.value?.collectors || []).map(item => ({
+  id: item.collectorId,
+  label: item.collectorName || item.collectorCode,
+})))
 
 function createBatteryRow(batteryNo = '', groupId: number | null = null): BatteryRow {
   return {
     key: batteryKey++, selected: false, groupId, batteryNo, cabinet: null, cabinetChannel: null, collector: null,
-    expectedBarcode: '',
+    expectedBarcode: null,
+    temperature1: '', temperature2: '', temperature3: '', positive1: '', positive2: '', negative1: '', negative2: '',
+    customChannels: Object.fromEntries(customChannelColumns.value.map(column => [column.key, ''])),
   }
 }
 
@@ -101,13 +131,8 @@
   }
 }
 
-function createPlanStepRow(): PlanStepRow {
-  return { key: planStepKey++, selected: false, cycleNo: 1, stepNo: 1, stepType: '', stepName: '', parametersJson: '{}' }
-}
-
 const batteries = ref<BatteryRow[]>([])
-const thresholds = ref<ThresholdRow[]>([createThresholdRow('VOLTAGE')])
-const planSteps = ref<PlanStepRow[]>([createPlanStepRow()])
+const thresholds = ref<ThresholdRow[]>([createThresholdRow('TEMPERATURE'), createThresholdRow('VOLTAGE')])
 const groupBatteryNos = computed(() => batteryGroupOptions.value.find(group => group.id === selectedBatteryGroup.value)?.batteries || [])
 const availableGroupBatteryNos = computed(() => groupBatteryNos.value.filter(batteryNo => !isBatteryAdded(batteryNo)))
 const selectedBatteryGroupName = computed(() => batteryGroupOptions.value.find(group => group.id === selectedBatteryGroup.value)?.name || '-')
@@ -116,18 +141,18 @@
   get: () => batteries.value.length > 0 && batteries.value.every(item => item.selected),
   set: (checked: boolean) => batteries.value.forEach(item => { item.selected = checked }),
 })
+
 const allThresholdsSelected = computed({
   get: () => thresholds.value.length > 0 && thresholds.value.every(item => item.selected),
   set: (checked: boolean) => thresholds.value.forEach(item => { item.selected = checked }),
 })
-const allPlanStepsSelected = computed({
-  get: () => planSteps.value.length > 0 && planSteps.value.every(item => item.selected),
-  set: (checked: boolean) => planSteps.value.forEach(item => { item.selected = checked }),
-})
+
 const allGroupBatteriesSelected = computed({
   get: () => availableGroupBatteryNos.value.length > 0
     && availableGroupBatteryNos.value.every(batteryNo => selectedBatteryNos.value.includes(batteryNo)),
-  set: (checked: boolean) => { selectedBatteryNos.value = checked ? [...availableGroupBatteryNos.value] : [] },
+  set: (checked: boolean) => {
+    selectedBatteryNos.value = checked ? [...availableGroupBatteryNos.value] : []
+  },
 })
 
 function isBatteryAdded(batteryNo: string) {
@@ -145,11 +170,18 @@
 }
 
 function confirmAddBatteries() {
-  if (!selectedBatteryGroup.value) return void ElMessage.warning('请先选择电池组')
-  if (batteries.value.length && batteries.value[0].groupId !== selectedBatteryGroup.value) {
-    return void ElMessage.warning('同一测试只能选择一个电池组')
+  if (!selectedBatteryGroup.value) {
+    ElMessage.warning('请先选择电池组')
+    return
   }
-  if (!selectedBatteryNos.value.length) return void ElMessage.warning('请至少选择一块电池')
+  if (batteries.value.length && batteries.value[0].groupId !== selectedBatteryGroup.value) {
+    ElMessage.warning('同一测试只能选择一个电池组')
+    return
+  }
+  if (!selectedBatteryNos.value.length) {
+    ElMessage.warning('请至少选择一块电池')
+    return
+  }
   const newBatteryNos = selectedBatteryNos.value.filter(batteryNo => !isBatteryAdded(batteryNo))
   batteries.value.push(...newBatteryNos.map(batteryNo => createBatteryRow(batteryNo, selectedBatteryGroup.value)))
   batteryDialogVisible.value = false
@@ -157,54 +189,172 @@
 }
 
 function removeBatteries() {
-  if (!batteries.value.some(item => item.selected)) return void ElMessage.warning('请先选择需要删除的电池')
+  if (!batteries.value.some(item => item.selected)) {
+    ElMessage.warning('请先选择需要删除的电池')
+    return
+  }
   batteries.value = batteries.value.filter(item => !item.selected)
 }
 
-function isCabinetChannelUsed(item: BatteryRow, channel: number) {
-  return batteries.value.some(other => other.key !== item.key && other.cabinetChannel === channel)
+function getSensorSelections(item: BatteryRow) {
+  return [
+    ...fixedSensorFields.map(field => ({ id: field, value: item[field] })),
+    ...customChannelColumns.value.map(column => ({ id: `custom:${column.key}`, value: item.customChannels[column.key] || '' })),
+  ]
 }
 
-function handleCabinetChange(item: BatteryRow) {
-  if (item.cabinetChannel && !channelsForCabinet(item.cabinet).some(channel => channel.channelId === item.cabinetChannel)) {
-    item.cabinetChannel = null
+function clearSensorSelection(item: BatteryRow, fieldId: string) {
+  if (fieldId.startsWith('custom:')) {
+    item.customChannels[fieldId.slice(7)] = ''
+    return
   }
+  item[fieldId as FixedSensorField] = ''
 }
 
 function channelsForCabinet(cabinetId: number | null) {
-  return configurationOptions.value?.channels.filter(item => item.cabinetId === cabinetId) || []
+  if (!cabinetId) return []
+  return (configurationOptions.value?.channels || []).filter(channel => channel.cabinetId === cabinetId)
 }
 
-function handleThresholdTypeChange(item: ThresholdRow) {
-  item.unit = thresholdTypes.find(option => option.value === item.type)?.unit || item.unit
+function isCabinetChannelUsed(item: BatteryRow, channelId: number) {
+  return batteries.value.some(other => other.key !== item.key && other.cabinetChannel === channelId)
 }
 
-async function loadConfiguration() {
+function isSensorChannelUsed(item: BatteryRow, fieldId: string, channel: string) {
+  if (!item.collector) return false
+  return batteries.value.some(other => {
+    if (other.key !== item.key && other.collector !== item.collector) return false
+    return getSensorSelections(other).some(selection => {
+      if (other.key === item.key && selection.id === fieldId) return false
+      return selection.value === channel
+    })
+  })
+}
+
+function handleCabinetChange(item: BatteryRow) {
+  item.cabinetChannel = null
+}
+
+function handleCollectorChange(item: BatteryRow) {
+  if (!item.collector) {
+    getSensorSelections(item).forEach(selection => clearSensorSelection(item, selection.id))
+    return
+  }
+  const occupiedChannels = new Set(
+    batteries.value
+      .filter(other => other.key !== item.key && other.collector === item.collector)
+      .flatMap(other => getSensorSelections(other).map(selection => selection.value).filter(Boolean)),
+  )
+  const currentChannels = new Set<string>()
+  let clearedCount = 0
+  getSensorSelections(item).forEach(selection => {
+    if (!selection.value) return
+    if (occupiedChannels.has(selection.value) || currentChannels.has(selection.value)) {
+      clearSensorSelection(item, selection.id)
+      clearedCount += 1
+      return
+    }
+    currentChannels.add(selection.value)
+  })
+  if (clearedCount) ElMessage.warning(`已清空 ${clearedCount} 个重复通道,请重新选择`)
+}
+
+async function addChannelField() {
+  try {
+    const result = await ElMessageBox.prompt('输入字段名称后,将在表格最后新增一个可选择采集器通道的列。', '添加采集器字段', {
+      confirmButtonText: '添加',
+      cancelButtonText: '取消',
+      inputPlaceholder: '例如:壳体温度通道',
+      inputValidator: (value: string) => {
+        const label = value.trim()
+        if (!label) return '请输入字段名称'
+        if (label.length > 16) return '字段名称不能超过 16 个字'
+        const normalizedLabel = label.toLocaleLowerCase()
+        const labelExists = [...reservedChannelLabels, ...customChannelColumns.value.map(column => column.label)]
+          .some(item => item.toLocaleLowerCase() === normalizedLabel)
+        return labelExists ? '字段名称已存在' : true
+      },
+    })
+    const label = result.value.trim()
+    const key = `custom-channel-${customChannelKey++}`
+    customChannelColumns.value.push({ key, label, fieldName: `custom:${label}` })
+    batteries.value.forEach(item => { item.customChannels[key] = '' })
+    ElMessage.success(`已添加“${label}”通道列`)
+  } catch {
+    // 用户取消输入时保持当前配置。
+  }
+}
+
+function applyOptions(options: TestConfigurationOptions) {
+  configurationOptions.value = options
+  owners.value = options.users.map(item => ({
+    id: item.userId,
+    name: item.nickName || item.userName,
+    userName: item.userName,
+  }))
+  testTypes.value = options.testTypes.map(item => ({ id: item.testTypeId, name: item.testTypeName }))
+  const groups = new Map<number, BatteryGroupOption>()
+  options.batteries.forEach(item => {
+    const group = groups.get(item.groupId) || { id: item.groupId, name: item.groupName, batteries: [] }
+    group.batteries.push(item.batteryNo)
+    groups.set(item.groupId, group)
+  })
+  batteryGroupOptions.value = [...groups.values()]
+}
+
+function applyDetail(detail: TestConfigurationDetail) {
+  if (detail.monitoringStatus !== 'NOT_MONITORED') {
+    throw new Error('该测试已经开始监测,不能修改配置')
+  }
+  form.name = detail.testName
+  form.principalId = detail.principalId
+  form.testTypeId = detail.testTypeId
+  form.purpose = detail.testPurpose
+  form.remark = detail.remark || ''
+  monitorTimeoutMinutes.value = detail.monitorTimeoutMinutes || 30
+  preservedPlanSteps.value = detail.planSteps || []
+
+  const fixedNames = new Set(Object.values(fixedFieldNames))
+  const customFieldNames = new Set<string>()
+  detail.batteries.forEach(battery => battery.fields.forEach(field => {
+    if (!fixedNames.has(field.fieldName)) customFieldNames.add(field.fieldName)
+  }))
+  customChannelColumns.value = [...customFieldNames].map(fieldName => ({
+    key: `custom-channel-${customChannelKey++}`,
+    label: fieldName.startsWith('custom:') ? fieldName.slice(7) : fieldName,
+    fieldName,
+  }))
+
+  batteries.value = detail.batteries.map(battery => {
+    const row = createBatteryRow(battery.batteryNo, battery.groupId)
+    const channel = configurationOptions.value?.channels.find(item => item.channelId === battery.binding.channelId)
+    row.cabinet = channel?.cabinetId || null
+    row.cabinetChannel = battery.binding.channelId
+    row.collector = battery.collectorId
+    row.expectedBarcode = battery.binding.expectedBarcode
+    const values = new Map(battery.fields.map(field => [field.fieldName, field.fieldValue]))
+    fixedSensorFields.forEach(field => { row[field] = values.get(fixedFieldNames[field]) || '' })
+    customChannelColumns.value.forEach(column => { row.customChannels[column.key] = values.get(column.fieldName) || '' })
+    return row
+  })
+  thresholds.value = detail.thresholds.map(item => ({
+    key: thresholdKey++, selected: false, type: item.metricCode,
+    min: item.minValue === null ? '' : String(item.minValue),
+    max: item.maxValue === null ? '' : String(item.maxValue),
+    unit: item.unit,
+  }))
+}
+
+async function loadPage() {
   loading.value = true
   ownersLoading.value = true
   testTypesLoading.value = true
   try {
-    const options = await getTestConfigurationOptions()
-    configurationOptions.value = options
-    owners.value = options.users.map(item => ({ id: item.userId, name: item.nickName || item.userName, userName: item.userName }))
-    testTypes.value = options.testTypes.map(item => ({ id: item.testTypeId, name: item.testTypeName }))
-    cabinetOptions.value = [...new Map(options.channels.map(item => [item.cabinetId, {
-      id: item.cabinetId, label: `${item.cabinetName}(${item.cabinetCode})`,
-    }])).values()]
-    collectorOptions.value = options.collectors.map(item => ({
-      id: item.collectorId, label: `${item.collectorName}(${item.collectorCode})`,
-    }))
-    const groups = new Map<number, BatteryGroupOption>()
-    options.batteries.forEach(item => {
-      const group = groups.get(item.groupId) || { id: item.groupId, name: item.groupName, batteries: [] }
-      group.batteries.push(item.batteryNo)
-      groups.set(item.groupId, group)
-    })
-    batteryGroupOptions.value = [...groups.values()]
-    if (testId.value) await loadDetail(testId.value)
+    applyOptions(await getTestConfigurationOptions())
+    if (testId.value) applyDetail(await getTestDetail(testId.value))
   } catch (error) {
     ElMessage.error(error instanceof Error ? error.message : '测试配置加载失败')
-    if (testId.value) await router.push('/test-list')
+    if (isEdit.value) await router.push('/test-list')
   } finally {
     loading.value = false
     ownersLoading.value = false
@@ -212,69 +362,20 @@
   }
 }
 
-async function loadDetail(id: number) {
-  const detail = await getTestDetail(id)
-  if (detail.monitoringStatus !== 'NOT_MONITORED') throw new Error('开始监测后不能修改测试配置')
-  form.name = detail.testName
-  form.principalId = detail.principalId
-  form.testTypeId = detail.testTypeId
-  form.purpose = detail.testPurpose
-  form.remark = detail.remark || ''
-  form.monitorTimeoutMinutes = detail.monitorTimeoutMinutes
-
-  batteries.value = detail.batteries.map(item => {
-    const channel = configurationOptions.value?.channels.find(option => option.channelId === item.binding.channelId)
-    const row = createBatteryRow(item.batteryNo, item.groupId)
-    row.cabinet = channel?.cabinetId || null
-    row.cabinetChannel = item.binding.channelId
-    row.collector = item.collectorId
-    row.expectedBarcode = item.binding.expectedBarcode || ''
-    return row
-  })
-  planSteps.value = detail.planSteps.map(item => ({
-    key: planStepKey++, selected: false, cycleNo: item.cycleNo, stepNo: item.stepNo,
-    stepType: item.stepType, stepName: item.stepName, parametersJson: JSON.stringify(item.parameters),
-  }))
-  thresholds.value = detail.thresholds.map(item => ({
-    key: thresholdKey++, selected: false, type: item.metricCode,
-    min: item.minValue === null ? '' : String(item.minValue), max: item.maxValue === null ? '' : String(item.maxValue),
-    unit: item.unit,
-  }))
-}
-
-function addPlanStep() {
-  const row = createPlanStepRow()
-  const previous = planSteps.value.at(-1)
-  if (previous) {
-    row.cycleNo = previous.cycleNo
-    row.stepNo = previous.stepNo + 1
-  }
-  planSteps.value.push(row)
-}
-
-function removePlanSteps() {
-  if (!planSteps.value.some(item => item.selected)) return void ElMessage.warning('请先选择需要删除的计划工步')
-  planSteps.value = planSteps.value.filter(item => !item.selected)
-}
-
 function addThreshold() {
   thresholds.value.push(createThresholdRow())
 }
 
-function removeThresholds() {
-  if (!thresholds.value.some(item => item.selected)) return void ElMessage.warning('请先选择需要删除的阈值')
-  thresholds.value = thresholds.value.filter(item => !item.selected)
+function handleThresholdTypeChange(item: ThresholdRow) {
+  item.unit = thresholdTypes.find(option => option.value === item.type)?.unit || item.unit
 }
 
-function parseParameters(value: string): Record<string, unknown> | null {
-  try {
-    const parsed: unknown = JSON.parse(value)
-    return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
-      ? parsed as Record<string, unknown>
-      : null
-  } catch {
-    return null
+function removeThresholds() {
+  if (!thresholds.value.some(item => item.selected)) {
+    ElMessage.warning('请先选择需要删除的阈值')
+    return
   }
+  thresholds.value = thresholds.value.filter(item => !item.selected)
 }
 
 function validate() {
@@ -282,189 +383,174 @@
   if (!form.principalId) return '请选择测试负责人'
   if (!form.testTypeId) return '请选择测试类型'
   if (!form.purpose.trim()) return '请输入测试目的'
-  if (!Number.isInteger(form.monitorTimeoutMinutes) || form.monitorTimeoutMinutes < 1) return '监测超时必须为正整数'
   if (!batteries.value.length) return '至少需要配置一块电池'
 
-  const groupIds = new Set(batteries.value.map(item => item.groupId))
-  if (groupIds.has(null) || groupIds.size !== 1) return '同一测试只能选择一个电池组'
+  const requiredFields: Array<[keyof BatteryRow, string]> = [
+    ['batteryNo', '电池号'], ['groupId', '电池组'], ['cabinet', '检测柜'], ['cabinetChannel', '检测柜通道'],
+  ]
   for (let index = 0; index < batteries.value.length; index += 1) {
-    const item = batteries.value[index]
-    if (!item.batteryNo.trim()) return `第 ${index + 1} 行缺少电池号`
-    if (!item.cabinet) return `第 ${index + 1} 行请选择检测柜`
-    if (!item.cabinetChannel) return `第 ${index + 1} 行请选择主要检测柜通道`
+    for (const [field, label] of requiredFields) {
+      const value = batteries.value[index][field]
+      if (value === null || value === undefined || !String(value).trim()) return `第 ${index + 1} 行请选择${label}`
+    }
+    if (!batteries.value[index].collector && getSensorSelections(batteries.value[index]).some(item => item.value)) {
+      return `第 ${index + 1} 行配置采集器通道前请先选择采集器`
+    }
   }
 
-  const batteryNos = batteries.value.map(item => item.batteryNo.trim())
-  if (new Set(batteryNos).size !== batteryNos.length) return '测试内电池号不可重复'
+  const groupIds = new Set(batteries.value.map(item => item.groupId))
+  if (groupIds.size !== 1) return '同一测试只能选择一个电池组'
   const cabinetChannels = batteries.value.map(item => item.cabinetChannel)
   if (new Set(cabinetChannels).size !== cabinetChannels.length) return '同一检测柜通道不可重复绑定'
 
-  if (!planSteps.value.length) return '至少需要配置一个计划工步'
-  const stepIdentities = new Set<string>()
-  for (let index = 0; index < planSteps.value.length; index += 1) {
-    const item = planSteps.value[index]
-    if (!Number.isInteger(item.cycleNo) || item.cycleNo < 1 || !Number.isInteger(item.stepNo) || item.stepNo < 1) {
-      return `第 ${index + 1} 行计划工步序号必须为正整数`
-    }
-    if (!item.stepType.trim() || !item.stepName.trim()) return `请完整填写第 ${index + 1} 行计划工步`
-    if (!parseParameters(item.parametersJson)) return `第 ${index + 1} 行工步参数必须是 JSON 对象`
-    const identity = `${item.cycleNo}/${item.stepNo}`
-    if (stepIdentities.has(identity)) return '计划工步的循环号和工步号不可重复'
-    stepIdentities.add(identity)
-  }
+  const collectorChannels = batteries.value.flatMap(item => [
+    ...fixedSensorFields.map(field => item[field]),
+    ...customChannelColumns.value.map(column => item.customChannels[column.key]),
+  ].filter(Boolean).map(channel => `${item.collector}/${channel}`))
+  if (new Set(collectorChannels).size !== collectorChannels.length) return '同一采集器的采集通道不可重复'
 
   if (!thresholds.value.length) return '至少需要配置一项预警阈值'
-  const metricCodes = new Set<string>()
   for (let index = 0; index < thresholds.value.length; index += 1) {
     const item = thresholds.value[index]
-    if (!item.type) return `请选择第 ${index + 1} 行阈值类型`
-    if (metricCodes.has(item.type)) return '同一阈值类型不可重复配置'
-    metricCodes.add(item.type)
-    if (item.min === '' && item.max === '') return `第 ${index + 1} 行阈值至少填写最小值或最大值`
-    if (item.min !== '' && item.max !== '' && Number(item.min) > Number(item.max)) {
-      return `第 ${index + 1} 行阈值最小值不能大于最大值`
-    }
+    if (!item.type || item.min === '' || item.max === '') return `请完整填写第 ${index + 1} 行预警阈值`
+    if (Number(item.min) >= Number(item.max)) return `第 ${index + 1} 行阈值最小值必须小于最大值`
   }
   return ''
 }
 
-function buildPayload(): TestConfigurationPayload {
-  return {
-    testName: form.name.trim(),
-    principalId: form.principalId as number,
-    testTypeId: form.testTypeId as number,
-    testPurpose: form.purpose.trim(),
-    monitorTimeoutMinutes: form.monitorTimeoutMinutes,
-    remark: form.remark.trim() || null,
-    batteries: batteries.value.map(item => ({
-      groupId: item.groupId as number,
-      batteryNo: item.batteryNo.trim(),
-      collectorId: item.collector,
-      binding: { channelId: item.cabinetChannel as number, expectedBarcode: item.expectedBarcode.trim() || null },
-      fields: [],
-    })),
-    planSteps: planSteps.value.map((item, index) => ({
-      cycleNo: item.cycleNo, stepNo: item.stepNo, stepType: item.stepType.trim(), stepName: item.stepName.trim(),
-      parameters: parseParameters(item.parametersJson) as Record<string, unknown>, sortOrder: index + 1,
-    })),
-    thresholds: thresholds.value.map(item => ({
-      metricCode: item.type,
-      minValue: item.min === '' ? null : Number(item.min),
-      maxValue: item.max === '' ? null : Number(item.max),
-      unit: item.unit,
-      severity: 'WARNING',
-    })),
-  }
+function buildBatteryFields(item: BatteryRow) {
+  const fixedFields = fixedSensorFields
+    .filter(field => item[field])
+    .map(field => ({ fieldName: fixedFieldNames[field], fieldValue: item[field] }))
+  const customFields = customChannelColumns.value
+    .filter(column => item.customChannels[column.key])
+    .map(column => ({ fieldName: column.fieldName, fieldValue: item.customChannels[column.key] }))
+  return [...fixedFields, ...customFields]
 }
 
 async function submit() {
-  const validationError = validate()
-  if (validationError) return void ElMessage.warning(validationError)
+  const error = validate()
+  if (error) {
+    ElMessage.warning(error)
+    return
+  }
   submitting.value = true
   try {
-    const payload = buildPayload()
-    if (testId.value) {
-      await updateTest({ ...payload, testId: testId.value })
-      ElMessage.success('测试配置已更新')
-    } else {
-      await createTest(payload)
-      ElMessage.success('测试已创建并加入测试列表')
+    const payload = {
+      testName: form.name.trim(),
+      principalId: form.principalId!,
+      testTypeId: form.testTypeId!,
+      testPurpose: form.purpose.trim(),
+      monitorTimeoutMinutes: monitorTimeoutMinutes.value,
+      remark: form.remark.trim() || null,
+      batteries: batteries.value.map(item => ({
+        groupId: item.groupId!,
+        batteryNo: item.batteryNo.trim(),
+        collectorId: item.collector,
+        binding: {
+          channelId: item.cabinetChannel!,
+          expectedBarcode: item.expectedBarcode,
+        },
+        fields: buildBatteryFields(item),
+      })),
+      planSteps: preservedPlanSteps.value,
+      thresholds: thresholds.value.map(item => ({
+        metricCode: item.type,
+        minValue: item.min === '' ? null : Number(item.min),
+        maxValue: item.max === '' ? null : Number(item.max),
+        unit: item.unit,
+        severity: 'WARNING' as const,
+      })),
     }
+    if (testId.value) await updateTest({ ...payload, testId: testId.value })
+    else await createTest(payload)
+    ElMessage.success(isEdit.value ? '测试配置已保存' : '测试已创建并加入测试列表')
     await router.push('/test-list')
-  } catch (error) {
-    ElMessage.error(error instanceof Error ? error.message : '测试配置保存失败')
+  } catch (submitError) {
+    ElMessage.error(submitError instanceof Error ? submitError.message : '测试保存失败')
   } finally {
     submitting.value = false
   }
 }
 
-onMounted(loadConfiguration)
+onMounted(() => {
+  loadPage()
+})
 </script>
 
 <template>
   <div v-loading="loading" class="create-page">
     <div class="page-tools">
       <button class="back-button" type="button" @click="router.push('/test-list')"><el-icon><ArrowLeft /></el-icon>返回测试列表</button>
-      <span>{{ isEdit ? 'EDIT TEST CONFIGURATION' : 'NEW TEST CONFIGURATION' }}</span>
+      <span>NEW TEST CONFIGURATION</span>
     </div>
 
     <section class="form-panel">
       <header class="panel-heading">
-        <div><i></i><strong>{{ isEdit ? '编辑测试参数' : '测试参数' }}</strong><span>TEST PARAMETERS</span></div>
+        <div><i></i><strong>测试参数</strong><span>TEST PARAMETERS</span></div>
         <small><b>*</b> 为必填项</small>
       </header>
       <div class="parameter-grid">
-        <label><span><b>*</b>测试名称</span><el-input v-model="form.name" maxlength="100" show-word-limit placeholder="输入测试名称" /></label>
+        <label><span><b>*</b>测试名称</span><el-input v-model="form.name" maxlength="40" show-word-limit placeholder="输入测试名称" /></label>
         <label><span><b>*</b>测试负责人</span><el-select v-model="form.principalId" :loading="ownersLoading" filterable clearable placeholder="请选择负责人"><el-option v-for="item in owners" :key="item.id" :label="item.name" :value="item.id"><span>{{ item.name }}</span><small v-if="item.userName && item.userName !== item.name">({{ item.userName }})</small></el-option></el-select></label>
         <label><span><b>*</b>测试类型</span><el-select v-model="form.testTypeId" :loading="testTypesLoading" filterable clearable placeholder="请选择测试类型"><el-option v-for="item in testTypes" :key="item.id" :label="item.name" :value="item.id" /></el-select></label>
-        <label><span><b>*</b>监测超时(分钟)</span><el-input-number v-model="form.monitorTimeoutMinutes" :min="1" :max="1440" :step="1" controls-position="right" /></label>
-        <label class="textarea-field"><span><b>*</b>测试目的</span><el-input v-model="form.purpose" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="输入测试目的" /></label>
-        <label class="textarea-field"><span>备注</span><el-input v-model="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="输入备注(选填)" /></label>
+        <label class="textarea-field"><span><b>*</b>测试目的</span><el-input v-model="form.purpose" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="输入测试目的" /></label>
+        <label class="textarea-field"><span>备注</span><el-input v-model="form.remark" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="输入备注(选填)" /></label>
       </div>
     </section>
 
     <section class="form-panel">
-      <header class="panel-heading channel-heading"><div><i></i><strong>电池组和通道设置</strong><span>BATTERY & CHANNELS</span></div></header>
+      <header class="panel-heading channel-heading">
+        <div><i></i><strong>电池组和通道设置</strong><span>BATTERY & CHANNELS</span></div>
+        <label class="sync-setting"><span>同步方式</span><el-select v-model="form.syncMode"><el-option label="默认同步检测柜" value="默认同步检测柜" /></el-select></label>
+      </header>
       <div class="section-actions">
-        <div><button class="primary-button" type="button" @click="openBatteryDialog"><el-icon><Plus /></el-icon>添加电池</button><button class="secondary-button danger" type="button" @click="removeBatteries"><el-icon><Delete /></el-icon>删除所选</button></div>
-        <span>同一测试使用一个电池组;主要检测柜通道不可重复</span>
+        <div><button class="primary-button" type="button" @click="openBatteryDialog"><el-icon><Plus /></el-icon>添加电池</button><button class="secondary-button" type="button" @click="addChannelField"><el-icon><Plus /></el-icon>添加项</button><button class="secondary-button danger" type="button" @click="removeBatteries"><el-icon><Delete /></el-icon>删除所选</button></div>
+        <span>至少配置一块电池 · 同一采集器通道不可重复</span>
       </div>
       <div class="config-table-wrap">
-        <table class="config-table battery-table">
+        <table class="config-table battery-table" :style="{ minWidth: `${1700 + customChannelColumns.length * 150}px` }">
           <thead><tr>
-            <th><el-checkbox v-model="allBatteriesSelected" aria-label="选择全部电池" /></th><th>电池号 *</th><th>检测柜 *</th><th>主要检测柜通道 *</th><th>预期条码</th><th>采集器</th>
+            <th><el-checkbox v-model="allBatteriesSelected" aria-label="选择全部电池" /></th><th>电池号 *</th><th>检测柜 *</th><th>检测柜通道 *</th><th>采集器</th>
+            <th>主温度通道</th><th>温度通道 2</th><th>温度通道 3</th><th>正电位主通道</th><th>正电位通道 2</th><th>负电位主通道</th><th>负电位通道 2</th>
+            <th v-for="column in customChannelColumns" :key="column.key" class="custom-channel-header">{{ column.label }}</th>
           </tr></thead>
           <tbody>
             <tr v-for="item in batteries" :key="item.key">
               <td><el-checkbox v-model="item.selected" :aria-label="`选择电池 ${item.batteryNo || item.key}`" /></td>
-              <td><el-input v-model="item.batteryNo" disabled /></td>
+              <td><el-input v-model="item.batteryNo" disabled placeholder="电池号" /></td>
               <td><el-select v-model="item.cabinet" placeholder="选择" @change="handleCabinetChange(item)"><el-option v-for="option in cabinetOptions" :key="option.id" :label="option.label" :value="option.id" /></el-select></td>
               <td><el-select v-model="item.cabinetChannel" clearable :disabled="!item.cabinet" :placeholder="item.cabinet ? '选择' : '请先选择检测柜'"><el-option v-for="option in channelsForCabinet(item.cabinet)" :key="option.channelId" :label="isCabinetChannelUsed(item, option.channelId) ? `${option.displayNo || `CH-${option.chlid}`}(已占用)` : option.displayNo || `CH-${option.chlid}`" :value="option.channelId" :disabled="isCabinetChannelUsed(item, option.channelId)" /></el-select></td>
-              <td><el-input v-model="item.expectedBarcode" maxlength="100" placeholder="选填" /></td>
-              <td><el-select v-model="item.collector" clearable placeholder="选填"><el-option v-for="option in collectorOptions" :key="option.id" :label="option.label" :value="option.id" /></el-select></td>
+              <td><el-select v-model="item.collector" clearable placeholder="选择(选填)" @change="handleCollectorChange(item)"><el-option v-for="option in collectorOptions" :key="option.id" :label="option.label" :value="option.id" /></el-select></td>
+              <td v-for="field in fixedSensorFields" :key="field">
+                <el-select v-model="item[field]" clearable :disabled="!item.collector" :placeholder="item.collector ? '选择' : '请先选择采集器'"><el-option v-for="option in sensorChannels" :key="option" :label="isSensorChannelUsed(item, field, option) ? `${option}(已占用)` : option" :value="option" :disabled="isSensorChannelUsed(item, field, option)" /></el-select>
+              </td>
+              <td v-for="column in customChannelColumns" :key="column.key">
+                <el-select v-model="item.customChannels[column.key]" clearable :disabled="!item.collector" :placeholder="item.collector ? '选择' : '请先选择采集器'"><el-option v-for="option in sensorChannels" :key="option" :label="isSensorChannelUsed(item, `custom:${column.key}`, option) ? `${option}(已占用)` : option" :value="option" :disabled="isSensorChannelUsed(item, `custom:${column.key}`, option)" /></el-select>
+              </td>
             </tr>
-            <tr v-if="!batteries.length"><td colspan="6" class="empty-row">暂未配置电池,请点击“添加电池”。</td></tr>
-          </tbody>
-        </table>
-      </div>
-    </section>
-
-    <section class="form-panel plan-panel">
-      <header class="panel-heading"><div><i></i><strong>计划工步</strong><span>TEST PLAN STEPS</span></div></header>
-      <div class="section-actions"><div><button class="primary-button" type="button" @click="addPlanStep"><el-icon><Plus /></el-icon>添加工步</button><button class="secondary-button danger" type="button" @click="removePlanSteps"><el-icon><Delete /></el-icon>删除所选</button></div></div>
-      <div class="config-table-wrap compact-table-wrap">
-        <table class="config-table plan-table">
-          <thead><tr><th><el-checkbox v-model="allPlanStepsSelected" aria-label="选择全部计划工步" /></th><th>循环号 *</th><th>工步号 *</th><th>工步类型 *</th><th>工步名称 *</th><th>参数(JSON 对象)*</th></tr></thead>
-          <tbody>
-            <tr v-for="item in planSteps" :key="item.key">
-              <td><el-checkbox v-model="item.selected" :aria-label="`选择计划工步 ${item.key}`" /></td>
-              <td><el-input-number v-model="item.cycleNo" :min="1" :step="1" controls-position="right" /></td>
-              <td><el-input-number v-model="item.stepNo" :min="1" :step="1" controls-position="right" /></td>
-              <td><el-input v-model="item.stepType" maxlength="50" placeholder="例如 CHARGE" /></td>
-              <td><el-input v-model="item.stepName" maxlength="100" placeholder="输入工步名称" /></td>
-              <td><el-input v-model="item.parametersJson" placeholder="{}" /></td>
-            </tr>
-            <tr v-if="!planSteps.length"><td colspan="6" class="empty-row">暂未设置计划工步</td></tr>
+            <tr v-if="!batteries.length"><td :colspan="12 + customChannelColumns.length" class="empty-row">尚未配置电池,请点击“添加电池”</td></tr>
           </tbody>
         </table>
       </div>
     </section>
 
     <section class="form-panel threshold-panel">
-      <header class="panel-heading"><div><i></i><strong>预警阈值</strong><span>ALERT THRESHOLDS</span></div><small>单位按 SI 业务单位保存</small></header>
-      <div class="section-actions"><div><button class="primary-button" type="button" @click="addThreshold"><el-icon><Plus /></el-icon>添加阈值</button><button class="secondary-button danger" type="button" @click="removeThresholds"><el-icon><Delete /></el-icon>删除所选</button></div></div>
+      <header class="panel-heading"><div><i></i><strong>预警阈值</strong><span>ALERT THRESHOLDS</span></div><small>可配置温度、电压、电流、容量;检测柜返回项待现场验证</small></header>
+      <div class="section-actions">
+        <div><button class="primary-button" type="button" @click="addThreshold"><el-icon><Plus /></el-icon>添加阈值</button><button class="secondary-button danger" type="button" @click="removeThresholds"><el-icon><Delete /></el-icon>删除所选</button></div>
+      </div>
       <div class="config-table-wrap threshold-wrap">
         <table class="config-table threshold-table">
-          <thead><tr><th><el-checkbox v-model="allThresholdsSelected" aria-label="选择全部阈值" /></th><th>阈值类型 *</th><th>最小值</th><th>最大值</th><th>单位</th></tr></thead>
+          <thead><tr><th><el-checkbox v-model="allThresholdsSelected" aria-label="选择全部阈值" /></th><th>阈值类型</th><th>最小值</th><th>最大值</th></tr></thead>
           <tbody>
             <tr v-for="item in thresholds" :key="item.key">
               <td><el-checkbox v-model="item.selected" :aria-label="`选择阈值 ${item.key}`" /></td>
-              <td><el-select v-model="item.type" placeholder="选择类型" @change="handleThresholdTypeChange(item)"><el-option v-for="option in thresholdTypes" :key="option.value" :label="option.label" :value="option.value" /></el-select></td>
-              <td><el-input v-model="item.min" type="number" placeholder="选填" /></td>
-              <td><el-input v-model="item.max" type="number" placeholder="选填" /></td>
-              <td><span class="unit-value">{{ item.unit }}</span></td>
+              <td><el-select v-model="item.type" placeholder="选择类型" @change="handleThresholdTypeChange(item)"><el-option v-for="option in thresholdTypes" :key="option.value" :label="option.label" :value="option.value" :disabled="option.disabled" /></el-select></td>
+              <td><el-input v-model="item.min" type="number" placeholder="输入最小值" /></td>
+              <td><el-input v-model="item.max" type="number" placeholder="输入最大值" /></td>
             </tr>
-            <tr v-if="!thresholds.length"><td colspan="5" class="empty-row">暂未设置预警阈值</td></tr>
+            <tr v-if="!thresholds.length"><td colspan="4" class="empty-row">暂未设置预警阈值</td></tr>
           </tbody>
         </table>
       </div>
@@ -476,20 +562,35 @@
     </footer>
 
     <el-dialog v-model="batteryDialogVisible" class="battery-dialog" width="560px" align-center destroy-on-close>
-      <template #header><div class="dialog-title"><strong>添加电池组</strong><span>ADD BATTERY GROUP</span></div></template>
+      <template #header>
+        <div class="dialog-title"><strong>添加电池组</strong><span>ADD BATTERY GROUP</span></div>
+      </template>
       <div class="battery-dialog-body">
-        <label class="battery-group-search"><span>电池组</span><el-select v-model="selectedBatteryGroup" filterable clearable placeholder="输入或选择电池组" @change="handleBatteryGroupChange"><el-option v-for="group in batteryGroupOptions" :key="group.id" :label="group.name" :value="group.id" :disabled="batteries.length > 0 && batteries[0].groupId !== group.id" /></el-select></label>
+        <label class="battery-group-search">
+          <span>电池组</span>
+          <el-select v-model="selectedBatteryGroup" filterable clearable placeholder="输入电池组名称搜索" @change="handleBatteryGroupChange">
+            <el-option v-for="group in batteryGroupOptions" :key="group.id" :label="group.name" :value="group.id" :disabled="batteries.length > 0 && batteries[0].groupId !== group.id" />
+          </el-select>
+        </label>
+
         <div class="battery-picker-table">
           <table>
-            <thead><tr><th><el-checkbox v-model="allGroupBatteriesSelected" :disabled="!availableGroupBatteryNos.length" aria-label="选择全部可用电池" /></th><th>电池组</th><th>电池号</th><th>状态</th></tr></thead>
+            <thead><tr><th><el-checkbox v-model="allGroupBatteriesSelected" :disabled="!availableGroupBatteryNos.length" aria-label="选择全部可用电池" /></th><th>电池组</th><th>电池名</th><th>状态</th></tr></thead>
             <tbody>
-              <tr v-for="batteryNo in groupBatteryNos" :key="batteryNo" :class="{ added: isBatteryAdded(batteryNo) }"><td><el-checkbox v-model="selectedBatteryNos" :value="batteryNo" :disabled="isBatteryAdded(batteryNo)" :aria-label="`选择电池 ${batteryNo}`" /></td><td>{{ selectedBatteryGroupName }}</td><td>{{ batteryNo }}</td><td><span :class="['battery-state', { used: isBatteryAdded(batteryNo) }]">{{ isBatteryAdded(batteryNo) ? '已添加' : '可添加' }}</span></td></tr>
-              <tr v-if="!selectedBatteryGroup"><td colspan="4" class="picker-empty">请先选择一个电池组</td></tr>
+              <tr v-for="batteryNo in groupBatteryNos" :key="batteryNo" :class="{ added: isBatteryAdded(batteryNo) }">
+                <td><el-checkbox v-model="selectedBatteryNos" :value="batteryNo" :disabled="isBatteryAdded(batteryNo)" :aria-label="`选择电池 ${batteryNo}`" /></td>
+                <td>{{ selectedBatteryGroupName }}</td>
+                <td>{{ batteryNo }}</td>
+                <td><span :class="['battery-state', { used: isBatteryAdded(batteryNo) }]">{{ isBatteryAdded(batteryNo) ? '已添加' : '可添加' }}</span></td>
+              </tr>
+              <tr v-if="!selectedBatteryGroup"><td colspan="4" class="picker-empty">请选择或搜索电池组</td></tr>
             </tbody>
           </table>
         </div>
       </div>
-      <template #footer><div class="dialog-footer"><button class="secondary-button" type="button" @click="batteryDialogVisible = false">取消</button><button class="primary-button" type="button" @click="confirmAddBatteries"><el-icon><Check /></el-icon>提交</button></div></template>
+      <template #footer>
+        <div class="dialog-footer"><button class="secondary-button" type="button" @click="batteryDialogVisible = false">取消</button><button class="primary-button" type="button" @click="confirmAddBatteries"><el-icon><Check /></el-icon>提交</button></div>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -511,11 +612,14 @@
 .parameter-grid label { min-width: 0; display: flex; flex-direction: column; gap: 7px; }
 .parameter-grid label > span { color: #52606d; font-size: 12px; font-weight: 500; }
 .textarea-field { grid-column: span 1; }
-.parameter-grid :deep(.el-select), .parameter-grid :deep(.el-input-number) { width: 100%; }
+.parameter-grid :deep(.el-select), .sync-setting :deep(.el-select) { width: 100%; }
 .create-page :deep(.el-input__wrapper), .create-page :deep(.el-select__wrapper), .create-page :deep(.el-textarea__inner) { border-radius: 3px; box-shadow: 0 0 0 1px #d8e1e9 inset; background: #f8fafc; }
 .create-page :deep(.el-input__wrapper), .create-page :deep(.el-select__wrapper) { min-height: 34px; }
 .create-page :deep(.el-textarea__inner) { resize: vertical; }
 .channel-heading { min-height: 54px; }
+.sync-setting { display: flex; align-items: center; gap: 10px; }
+.sync-setting > span { color: #52606d; font-size: 12px; white-space: nowrap; }
+.sync-setting :deep(.el-select) { width: 190px; }
 .section-actions { min-height: 50px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 14px; }
 .section-actions > div { display: flex; gap: 8px; }
 .section-actions > span { color: #7b8794; font-size: 11px; }
@@ -527,27 +631,20 @@
 .secondary-button:hover { color: var(--blue); border-color: #9dc8f5; }
 .secondary-button.danger { color: #a84848; }
 .config-table-wrap { overflow-x: auto; border-top: 1px solid var(--line); }
-.config-table { width: 100%; table-layout: fixed; border-collapse: collapse; color: #52606d; font-size: 12px; }
+.config-table { width: 100%; min-width: 1700px; table-layout: fixed; border-collapse: collapse; color: #52606d; font-size: 12px; }
 .config-table th, .config-table td { height: 48px; padding: 6px; border-right: 1px solid #cbd9e5; border-bottom: 1px solid #d8e1e9; text-align: center; }
 .config-table th { height: 40px; color: #fff; background: #255884; font-size: 11px; font-weight: 600; }
 .config-table th:first-child { width: 46px; }
 .config-table th:nth-child(2) { width: 130px; }
+.config-table th.custom-channel-header { width: 150px; }
 .config-table th:last-child, .config-table td:last-child { border-right: 0; }
 .config-table tbody tr:last-child td { border-bottom: 0; }
-.config-table :deep(.el-select), .config-table :deep(.el-input), .config-table :deep(.el-input-number) { width: 100%; }
+.config-table :deep(.el-select), .config-table :deep(.el-input) { width: 100%; }
 .config-table :deep(.el-input__wrapper), .config-table :deep(.el-select__wrapper) { min-height: 32px; padding: 0 8px; background: #fff; }
-.battery-table { min-width: 980px; }
-.compact-table-wrap { width: calc(100% - 28px); margin: 0 14px 14px; border: 1px solid var(--line); }
-.plan-table { min-width: 980px; }
-.plan-table th:nth-child(2), .plan-table th:nth-child(3) { width: 110px; }
-.plan-table th:nth-child(4) { width: 170px; }
-.plan-table th:nth-child(5) { width: 220px; }
 .threshold-panel { padding-bottom: 14px; }
-.threshold-wrap { width: min(760px, calc(100% - 28px)); margin: 0 14px; border: 1px solid var(--line); }
-.threshold-table { min-width: 720px; }
+.threshold-wrap { width: min(620px, 100%); margin: 0 14px; border: 1px solid var(--line); }
+.threshold-table { min-width: 600px; }
 .threshold-table th:nth-child(2) { width: 210px; }
-.threshold-table th:last-child { width: 90px; }
-.unit-value { display: inline-flex; min-width: 40px; height: 26px; align-items: center; justify-content: center; color: #255884; background: #eaf2f8; border: 1px solid #c7dce9; }
 .empty-row { height: 88px !important; color: #8795a3; }
 .submit-bar { position: sticky; bottom: 0; z-index: 20; min-height: 62px; display: flex; align-items: center; justify-content: flex-end; gap: 10px; padding: 10px 16px; background: rgba(234, 240, 246, .96); border-top: 1px solid #cbd9e5; }
 .submit-button { min-width: 110px; }
@@ -583,6 +680,7 @@
   .parameter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
   .channel-heading { align-items: flex-start; flex-direction: column; padding-top: 12px; padding-bottom: 12px; }
 }
+
 @media (max-width: 680px) {
   .create-page { gap: 12px; }
   .page-tools > span { display: none; }
@@ -591,7 +689,9 @@
   .parameter-grid { grid-template-columns: 1fr; gap: 14px; padding: 14px 12px; }
   .section-actions { align-items: flex-start; flex-direction: column; padding: 10px 12px; }
   .section-actions > span { line-height: 1.5; }
-  .compact-table-wrap, .threshold-wrap { width: calc(100% - 24px); margin-right: 12px; margin-left: 12px; }
+  .sync-setting { width: 100%; }
+  .sync-setting :deep(.el-select) { flex: 1; width: auto; }
+  .threshold-wrap { width: calc(100% - 24px); margin: 0 12px; }
   .submit-bar { margin: 0 -14px -14px; padding-right: 14px; }
   .create-page :deep(.battery-dialog .el-dialog__body) { padding: 16px 12px; }
   .battery-group-search { grid-template-columns: 1fr; gap: 7px; }

--
Gitblit v1.7.1