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