fix: persist battery group batteries through api
| New file |
| | |
| | | export type GroupStatus = '启用' | '禁用' |
| | | |
| | | export interface BatteryGroupPayload { |
| | | groupName: string |
| | | model: string |
| | | purpose: string |
| | | remark: string |
| | | status: '0' | '1' |
| | | z: string |
| | | h: string |
| | | f: string |
| | | a: string |
| | | b: string |
| | | batteryNos: string[] |
| | | templateId: number | null |
| | | templateFieldRelations?: Array<{ batteryGroupFieldId: number }> |
| | | } |
| | | |
| | | interface BatteryGroupFormPayloadInput { |
| | | name: string |
| | | model: string |
| | | purpose: string |
| | | remark: string |
| | | status: GroupStatus |
| | | specs: Record<string, string> |
| | | batteries: Array<{ value: string }> |
| | | } |
| | | |
| | | export function buildBatteryGroupPayload( |
| | | form: BatteryGroupFormPayloadInput, |
| | | templateId: number | null, |
| | | templateFieldIds: number[], |
| | | ): BatteryGroupPayload { |
| | | return { |
| | | groupName: form.name.trim(), |
| | | model: form.model.trim(), |
| | | purpose: form.purpose.trim(), |
| | | remark: form.remark.trim(), |
| | | status: form.status === '禁用' ? '1' : '0', |
| | | z: form.specs.z || '', |
| | | h: form.specs.h || '', |
| | | f: form.specs.f || '', |
| | | a: form.specs.a || '', |
| | | b: form.specs.b || '', |
| | | batteryNos: form.batteries.map(item => item.value.trim()).filter(Boolean), |
| | | templateId, |
| | | templateFieldRelations: templateId |
| | | ? templateFieldIds.map(batteryGroupFieldId => ({ batteryGroupFieldId })) |
| | | : undefined, |
| | | } |
| | | } |
| | |
| | | import type { GroupStatus } from '@/stores/batteryGroups' |
| | | import { request, type ApiResponse } from './request' |
| | | import type { BatteryGroupPayload, GroupStatus } from './batteryGroupPayload' |
| | | |
| | | export type { GroupStatus } from './batteryGroupPayload' |
| | | |
| | | export interface BatteryGroupListParams { |
| | | pageNum: number |
| | |
| | | endCreateTime?: string |
| | | } |
| | | |
| | | interface BatteryGroupBatteryDto { |
| | | batteryNo: string | null |
| | | } |
| | | |
| | | interface BatteryGroupTemplateFieldDto { |
| | | templateId: number | null |
| | | batteryGroupFieldId: number | null |
| | | fieldName: string | null |
| | | fieldType: string | null |
| | | } |
| | | |
| | | interface BatteryGroupDto { |
| | | groupId: number |
| | | groupName: string | null |
| | |
| | | createBy: string | null |
| | | createTime: string | null |
| | | batteryCount: number | null |
| | | remark?: string | null |
| | | z?: string | null |
| | | h?: string | null |
| | | f?: string | null |
| | | a?: string | null |
| | | b?: string | null |
| | | batteries?: BatteryGroupBatteryDto[] | null |
| | | templateFields?: BatteryGroupTemplateFieldDto[] | null |
| | | } |
| | | |
| | | interface BatteryGroupListResponse extends ApiResponse { |
| | |
| | | data: BatteryGroupCreatorDto[] |
| | | } |
| | | |
| | | interface BatteryGroupDetailResponse extends ApiResponse { |
| | | data: BatteryGroupDto |
| | | } |
| | | |
| | | interface BatteryGroupCreatorDto { |
| | | userName: string | null |
| | | nickName: string | null |
| | |
| | | export interface BatteryGroupCreatorOption { |
| | | label: string |
| | | value: string |
| | | } |
| | | |
| | | export interface BatteryGroupTemplateField { |
| | | templateId: number |
| | | batteryGroupFieldId: number |
| | | fieldName: string |
| | | fieldType: string |
| | | } |
| | | |
| | | export interface BatteryGroupDetail { |
| | | id: number |
| | | name: string |
| | | model: string |
| | | purpose: string |
| | | remark: string |
| | | status: GroupStatus |
| | | creator: string |
| | | specs: Record<'z' | 'h' | 'f' | 'a' | 'b', string> |
| | | batteries: string[] |
| | | templateFields: BatteryGroupTemplateField[] |
| | | } |
| | | |
| | | function mapBatteryGroup(item: BatteryGroupDto): BatteryGroupListItem { |
| | |
| | | }) |
| | | .filter(item => item.value) |
| | | } |
| | | |
| | | export async function getBatteryGroupDetail(groupId: number): Promise<BatteryGroupDetail> { |
| | | const result = await request<BatteryGroupDetailResponse>(`/system/batteryGroup/${groupId}`) |
| | | const item = result.data |
| | | return { |
| | | id: item.groupId, |
| | | name: item.groupName || '', |
| | | model: item.model || '', |
| | | purpose: item.purpose || '', |
| | | remark: item.remark || '', |
| | | status: item.status === '1' ? '禁用' : '启用', |
| | | creator: item.createBy || '', |
| | | specs: { z: item.z || '', h: item.h || '', f: item.f || '', a: item.a || '', b: item.b || '' }, |
| | | batteries: (item.batteries || []).map(battery => battery.batteryNo?.trim() || '').filter(Boolean), |
| | | templateFields: (item.templateFields || []) |
| | | .filter(field => field.templateId != null && field.batteryGroupFieldId != null) |
| | | .map(field => ({ |
| | | templateId: Number(field.templateId), |
| | | batteryGroupFieldId: Number(field.batteryGroupFieldId), |
| | | fieldName: field.fieldName || '', |
| | | fieldType: field.fieldType || '文本', |
| | | })), |
| | | } |
| | | } |
| | | |
| | | export function createBatteryGroup(payload: BatteryGroupPayload): Promise<ApiResponse> { |
| | | return request<ApiResponse>('/system/batteryGroup', { |
| | | method: 'POST', |
| | | body: JSON.stringify(payload), |
| | | }) |
| | | } |
| | | |
| | | export function updateBatteryGroup(groupId: number, payload: BatteryGroupPayload): Promise<ApiResponse> { |
| | | return request<ApiResponse>('/system/batteryGroup', { |
| | | method: 'PUT', |
| | | body: JSON.stringify({ groupId, ...payload }), |
| | | }) |
| | | } |
| | |
| | | import { onMounted, reactive, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useRouter } from 'vue-router' |
| | | import { getBatteryGroupCreatorOptions, getBatteryGroupList, type BatteryGroupCreatorOption, type BatteryGroupListItem } from '@/api/batteryGroups' |
| | | import type { GroupStatus } from '@/stores/batteryGroups' |
| | | import { getBatteryGroupCreatorOptions, getBatteryGroupList, type BatteryGroupCreatorOption, type BatteryGroupListItem, type GroupStatus } from '@/api/batteryGroups' |
| | | |
| | | interface Filters { |
| | | id: string |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, reactive, ref } from 'vue' |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { useBatteryGroups, type GroupStatus, type TemplateField } from '@/stores/batteryGroups' |
| | | import { createBatteryGroup, getBatteryGroupDetail, updateBatteryGroup } from '@/api/batteryGroups' |
| | | import { buildBatteryGroupPayload } from '@/api/batteryGroupPayload' |
| | | import type { GroupStatus, TemplateField } from '@/stores/batteryGroups' |
| | | import { useBatteryGroupFields, type BatteryGroupField } from '@/stores/batteryGroupFields' |
| | | |
| | | interface FormState { |
| | |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const { groups, createGroup, updateGroup } = useBatteryGroups() |
| | | const { fields: managedFields } = useBatteryGroupFields() |
| | | const creators = ['张工', '李工', '周工', '王工', '赵工', '陈工'] |
| | | const templatePanelOpen = ref(false) |
| | | const loading = ref(false) |
| | | const saving = ref(false) |
| | | const persistedTemplateId = ref<number | null>(null) |
| | | const persistedTemplateFieldIds = ref<number[]>([]) |
| | | let batteryKey = 1 |
| | | |
| | | const editingId = computed(() => Number(route.params.id) || null) |
| | |
| | | .map(field => ({ key: `managed-field-${field.id}`, label: field.name, type: field.type, value: previousValues.get(field.name) || '' })) |
| | | } |
| | | |
| | | function loadEditingGroup() { |
| | | async function loadEditingGroup() { |
| | | if (!editingId.value) return |
| | | const target = groups.value.find(item => item.id === editingId.value) |
| | | if (!target) { |
| | | ElMessage.warning('未找到需要编辑的电池组') |
| | | router.replace('/battery-group') |
| | | return |
| | | loading.value = true |
| | | try { |
| | | const target = await getBatteryGroupDetail(editingId.value) |
| | | const templateFields = target.templateFields.map(field => ({ |
| | | key: `persisted-field-${field.batteryGroupFieldId}`, |
| | | label: field.fieldName, |
| | | type: field.fieldType as TemplateField['type'], |
| | | value: '', |
| | | })) |
| | | persistedTemplateId.value = target.templateFields[0]?.templateId || null |
| | | persistedTemplateFieldIds.value = target.templateFields.map(field => field.batteryGroupFieldId) |
| | | Object.assign(form, { |
| | | name: target.name, |
| | | model: target.model, |
| | | purpose: target.purpose, |
| | | remark: target.remark, |
| | | creator: target.creator, |
| | | status: target.status, |
| | | templateName: '自定义', |
| | | specs: { ...target.specs }, |
| | | customFields: templateFields, |
| | | batteries: target.batteries.map(value => ({ key: batteryKey++, value })), |
| | | }) |
| | | if (!form.batteries.length) form.batteries.push({ key: batteryKey++, value: '' }) |
| | | selectedFieldIds.value = [...persistedTemplateFieldIds.value] |
| | | } catch (error) { |
| | | ElMessage.error(error instanceof Error ? error.message : '加载电池组失败') |
| | | await router.replace('/battery-group') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | Object.assign(form, { |
| | | name: target.name, |
| | | model: target.model, |
| | | purpose: target.purpose, |
| | | remark: target.remark, |
| | | creator: target.creator, |
| | | status: target.status, |
| | | templateName: target.templateName, |
| | | specs: { ...target.specs }, |
| | | customFields: target.customFields.map(field => { |
| | | const managedField = managedFields.value.find(item => item.name === field.label) |
| | | return { ...field, type: managedField?.type || field.type } |
| | | }), |
| | | batteries: target.batteries.map(value => ({ key: batteryKey++, value })), |
| | | }) |
| | | selectedFieldIds.value = target.customFields |
| | | .map(customField => managedFields.value.find(field => field.name === customField.label)?.id) |
| | | .filter((id): id is number => id !== undefined) |
| | | } |
| | | |
| | | loadEditingGroup() |
| | | |
| | | function addBattery() { |
| | | form.batteries.push({ key: batteryKey++, value: '' }) |
| | |
| | | if (form.customFields.some(field => !field.label.trim())) return '请填写全部模板字段名称' |
| | | const fieldLabels = form.customFields.map(field => field.label.trim().toLowerCase()) |
| | | if (new Set(fieldLabels).size !== fieldLabels.length) return '模板字段名称不能重复' |
| | | const duplicateGroup = groups.value.some(item => item.id !== editingId.value && item.name.trim().toLowerCase() === form.name.trim().toLowerCase()) |
| | | if (duplicateGroup) return '电池组名称已存在' |
| | | return '' |
| | | } |
| | | |
| | |
| | | return |
| | | } |
| | | saving.value = true |
| | | await new Promise(resolve => window.setTimeout(resolve, 250)) |
| | | const payload = { |
| | | name: form.name.trim(), model: form.model.trim(), purpose: form.purpose.trim(), remark: form.remark.trim(), |
| | | creator: form.creator, status: form.status, templateName: form.templateName, specs: { ...form.specs }, |
| | | customFields: form.customFields.map(field => ({ ...field, label: field.label.trim() })), |
| | | batteries: form.batteries.map(item => item.value.trim()).filter(Boolean), |
| | | try { |
| | | const payload = buildBatteryGroupPayload(form, persistedTemplateId.value, persistedTemplateFieldIds.value) |
| | | if (editingId.value) await updateBatteryGroup(editingId.value, payload) |
| | | else await createBatteryGroup(payload) |
| | | ElMessage.success(isEditing.value ? '电池组已更新' : '电池组已创建') |
| | | await router.push('/battery-group') |
| | | } catch (error) { |
| | | ElMessage.error(error instanceof Error ? error.message : '保存电池组失败') |
| | | } finally { |
| | | saving.value = false |
| | | } |
| | | if (editingId.value) updateGroup(editingId.value, payload) |
| | | else createGroup(payload) |
| | | saving.value = false |
| | | ElMessage.success(isEditing.value ? '电池组已更新' : '电池组已创建') |
| | | await router.push('/battery-group') |
| | | } |
| | | |
| | | onMounted(loadEditingGroup) |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | </div> |
| | | |
| | | <div class="form-layout"> |
| | | <div class="form-content"> |
| | | <div v-loading="loading" class="form-content"> |
| | | <section class="detail-panel"> |
| | | <header class="panel-heading"><div><i></i><strong>样品详情</strong><span>SAMPLE DETAILS</span></div><small><b>*</b> 为必填项</small></header> |
| | | <div class="sample-grid"> |
| | |
| | | <label><span>状态</span><el-radio-group v-model="form.status"><el-radio-button value="启用">启用</el-radio-button><el-radio-button value="禁用">禁用</el-radio-button></el-radio-group></label> |
| | | </section> |
| | | |
| | | <footer class="submit-bar"><button class="secondary-button" type="button" @click="router.push('/battery-group')">取消</button><button class="primary-button submit-button" type="button" :disabled="saving" @click="submit"><el-icon><Check /></el-icon>{{ saving ? '保存中' : `保存${pageTitle}` }}</button></footer> |
| | | <footer class="submit-bar"><button class="secondary-button" type="button" @click="router.push('/battery-group')">取消</button><button class="primary-button submit-button" type="button" :disabled="loading || saving" @click="submit"><el-icon><Check /></el-icon>{{ saving ? '保存中' : `保存${pageTitle}` }}</button></footer> |
| | | </div> |
| | | |
| | | <aside class="template-panel" :aria-hidden="!templatePanelOpen"> |
| New file |
| | |
| | | import assert from 'node:assert/strict' |
| | | import test from 'node:test' |
| | | import { buildBatteryGroupPayload } from '../src/api/batteryGroupPayload.ts' |
| | | |
| | | test('battery group payload contains the entered battery numbers', () => { |
| | | const payload = buildBatteryGroupPayload({ |
| | | name: ' 样品组 ', |
| | | model: ' M-01 ', |
| | | purpose: ' 容量验证 ', |
| | | remark: ' 首批 ', |
| | | status: '启用', |
| | | specs: { z: 'Z1', h: '', f: '', a: '', b: '' }, |
| | | batteries: [{ value: ' BAT-01 ' }, { value: '' }, { value: 'BAT-02' }], |
| | | }, null, []) |
| | | |
| | | assert.deepEqual(payload, { |
| | | groupName: '样品组', |
| | | model: 'M-01', |
| | | purpose: '容量验证', |
| | | remark: '首批', |
| | | status: '0', |
| | | z: 'Z1', |
| | | h: '', |
| | | f: '', |
| | | a: '', |
| | | b: '', |
| | | batteryNos: ['BAT-01', 'BAT-02'], |
| | | templateId: null, |
| | | templateFieldRelations: undefined, |
| | | }) |
| | | }) |