| src/components/ReportTestDetail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| tests/report-test-detail.ui.test.mjs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/components/ReportTestDetail.vue
@@ -1,14 +1,16 @@ <script setup lang="ts"> import { computed, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { useBatteryGroupFields } from '@/stores/batteryGroupFields' import { getBatteryGroupFields, type BatteryGroupField } from '@/api/batteryGroupFields' import { reportDetailFields, snapshotReportDetails, type ReportDetailColumn, type ReportTestDetails } from '@/utils/reportTestDetails' const props = defineProps<{ columns: ReportDetailColumn[]; modelValue?: ReportTestDetails; readonly?: boolean; disabled?: boolean }>() const emit = defineEmits<{ 'update:modelValue': [ReportTestDetails] }>() const details = computed(() => snapshotReportDetails(props.columns, props.modelValue)) const fields = computed(() => reportDetailFields(details.value)) const { fields: managedFields } = useBatteryGroupFields() const managedFields = ref<BatteryGroupField[]>([]) const fieldsLoading = ref(false) const fieldsError = ref('') const batteryOptions = computed(() => { const enabled = managedFields.value.filter(field => field.status === '启用').map(field => field.name) return [...new Set([...(enabled.length ? enabled : ['额定容量', '电流', '内阻', '质量', '材料批次']), @@ -23,6 +25,7 @@ const selectedBatteryFields = ref<string[]>([]) const selectedSpecialTemplate = ref('循环寿命模板') const locked = computed(() => props.readonly || props.disabled || !props.columns.length) const templateLocked = computed(() => locked.value || (templateMode.value === 'battery' && (fieldsLoading.value || Boolean(fieldsError.value)))) function updateValue(column: ReportDetailColumn, field: typeof fields.value[number], value: string) { if (locked.value) return @@ -64,13 +67,27 @@ templateMode.value = mode selectedBatteryFields.value = fields.value.filter(field => field.category === 'battery').map(field => field.name) templateVisible.value = true if (mode === 'battery') void loadBatteryFields() } async function loadBatteryFields() { fieldsLoading.value = true fieldsError.value = '' try { managedFields.value = await getBatteryGroupFields() } catch (error) { fieldsError.value = error instanceof Error ? error.message : '电池组字段加载失败' } finally { fieldsLoading.value = false } } function applyTemplate() { if (locked.value) return if (templateLocked.value) return const next = snapshotReportDetails(props.columns, details.value) for (const detail of Object.values(next)) { if (templateMode.value === 'battery') { // 与控制台一致:同名特殊字段转为电池组字段时,保留本列原值及明确空字符串。 for (const name of selectedBatteryFields.value) { const value = detail.fieldValues![`special|${name}`] if (!detail.batteryFields!.includes(name) && value !== undefined) detail.fieldValues![`battery|${name}`] = value } detail.batteryFields = [...selectedBatteryFields.value] detail.specialFields = detail.specialFields!.filter(name => !detail.batteryFields!.includes(name)) } else detail.specialFields = [...specialTemplates[selectedSpecialTemplate.value]!].filter(name => !detail.batteryFields!.includes(name)) @@ -102,9 +119,13 @@ </div> <div v-if="!readonly" class="detail-actions"><button class="secondary-button" type="button" :disabled="locked" @click="addField"><el-icon><Plus /></el-icon>添加项</button><button class="secondary-button" type="button" :disabled="locked" @click="openTemplate('battery')">电池组字段模板</button><button class="secondary-button" type="button" :disabled="locked" @click="openTemplate('special')">选择特殊字段模板</button></div> <el-dialog v-model="templateVisible" :title="templateMode === 'battery' ? '选择电池组字段模板' : '选择特殊字段模板'" width="620px" align-center> <el-checkbox-group v-if="templateMode === 'battery'" v-model="selectedBatteryFields" class="template-field-options"><el-checkbox v-for="field in batteryOptions" :key="field" :value="field">{{ field }}</el-checkbox></el-checkbox-group> <template v-if="templateMode === 'battery'"> <p v-if="fieldsLoading" role="status">正在加载电池组字段</p> <div v-else-if="fieldsError" role="alert">{{ fieldsError }}<button class="secondary-button" type="button" @click="loadBatteryFields">重试</button></div> <el-checkbox-group v-else v-model="selectedBatteryFields" class="template-field-options"><el-checkbox v-for="field in batteryOptions" :key="field" :value="field">{{ field }}</el-checkbox></el-checkbox-group> </template> <el-radio-group v-else v-model="selectedSpecialTemplate" class="template-field-options"><el-radio v-for="(names, name) in specialTemplates" :key="name" :value="name" border>{{ name }}({{ names.join('、') }})</el-radio></el-radio-group> <template #footer><button class="secondary-button" type="button" @click="templateVisible=false">取消</button><button class="primary-button" type="button" :disabled="locked" @click="applyTemplate"><el-icon><Check /></el-icon>应用模板</button></template> <template #footer><button class="secondary-button" type="button" @click="templateVisible=false">取消</button><button class="primary-button" type="button" :disabled="templateLocked" @click="applyTemplate"><el-icon><Check /></el-icon>应用模板</button></template> </el-dialog> </section> </template> tests/report-test-detail.ui.test.mjs
@@ -29,7 +29,7 @@ for (const type of ['compare', 'formation', 'splice']) { const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } }) await context.addInitScript(() => { localStorage.setItem('battery_detection_auth_token', 'ui-fixture'); window.print = () => {} }) let saved = fixture(type), submitted, failSave = true let saved = fixture(type), submitted, failSave = true, failFieldLoad = true const requests = [], errors = [] await context.route('http://report.test/**', async route => { const url = new URL(route.request().url()), path = url.pathname @@ -37,7 +37,10 @@ requests.push({ path, method: route.request().method() }) let response = { code: 200, msg: '成功', rows: [], total: 0 } if (path.endsWith('/getInfo')) Object.assign(response, { user: { userName: 'admin', nickName: '操作员' }, roles: ['admin'], permissions: ['*:*:*'] }) else if (path.includes('/batteryGroupField/')) response.rows = [{ fieldId: 1, fieldName: '额定容量', fieldType: 'text', status: '0' }, { fieldId: 2, fieldName: '质量', fieldType: 'text', status: '0' }] else if (path.includes('/batteryGroupField/')) { if (failFieldLoad) { failFieldLoad = false; response = { code: 500, msg: '字段加载失败' } } else response.rows = [{ fieldId: 1, fieldName: '额定容量', fieldType: 'text', status: '0' }, { fieldId: 2, fieldName: '质量', fieldType: 'text', status: '0' }] } else if (path.includes(`/${type}-report/7`)) { if (route.request().method() === 'POST') { if (failSave) { failSave = false; response = { code: 500, msg: '测试保存失败' } } @@ -68,8 +71,15 @@ await page.getByRole('button', { name: '添加', exact: true }).click() await row('新字段').locator('input').nth(0).fill('甲填写') await row('新字段').locator('input').nth(1).fill('乙填写') await detail.getByRole('button', { name: '添加项', exact: true }).click() await page.getByRole('dialog', { name: '添加项' }).getByRole('textbox').fill('质量') await page.getByRole('button', { name: '添加', exact: true }).click() await row('质量').locator('input').nth(0).fill('同名保留') await detail.getByRole('button', { name: '电池组字段模板', exact: true }).click() let dialog = page.getByRole('dialog', { name: '选择电池组字段模板' }) await expect(dialog.getByRole('alert')).toContainText('字段加载失败') await expect(dialog.getByRole('button', { name: '应用模板', exact: true })).toBeDisabled() await dialog.getByRole('button', { name: '重试', exact: true }).click() await dialog.locator('.el-checkbox').filter({ hasText: '额定容量' }).click() await dialog.getByRole('button', { name: '取消', exact: true }).click() await expect(row('额定容量')).toHaveCount(1) @@ -79,6 +89,8 @@ await expect(dialog).not.toBeVisible() await expect(row('额定容量').locator('input').nth(1)).toHaveValue('报表乙') await expect(row('新字段').locator('input').nth(0)).toHaveValue('甲填写') await expect(row('质量').locator('input').nth(0)).toHaveValue('同名保留') await expect(row('质量').locator('input').nth(1)).toHaveValue('') await detail.getByRole('button', { name: '选择特殊字段模板', exact: true }).click() dialog = page.getByRole('dialog', { name: '选择特殊字段模板' }) await dialog.locator('.el-radio').filter({ hasText: '热特性模板' }).click() @@ -102,7 +114,8 @@ await expect(row('最高温度').locator('input').nth(0)).toHaveValue('报表最高温度') await save.click() await expect.poll(() => submitted).toBeTruthy() assert.deepEqual(submitted.settings.sourceDetailOverrides['1|1'], { batteryFields: ['额定容量', '质量'], specialFields: ['最高温度', '温差', '温升速率'], fieldValues: { 'battery|额定容量': '', 'special|最高温度': '报表最高温度' } }) assert.deepEqual(submitted.settings.sourceDetailOverrides['1|1'], { batteryFields: ['额定容量', '质量'], specialFields: ['最高温度', '温差', '温升速率'], fieldValues: { 'battery|额定容量': '', 'battery|质量': '同名保留', 'special|最高温度': '报表最高温度' } }) assert.equal(submitted.settings.sourceDetailOverrides['2|2'].fieldValues['battery|质量'], '') assert.equal(submitted.settings.sourceDetailOverrides['2|2'].fieldValues['special|最高温度'], '') if (type === 'formation') assert.ok(submitted.settings.fields.every(field => field.scope === 'cycle')) else assert.deepEqual(submitted.settings.fieldStructure, [])