yeg
2026-08-27 8bc9027e4614944c6d5ee4fa2d50d01ebb87f3e1
fix: persist battery group batteries through api
2个文件已添加
3个文件已修改
266 ■■■■■ 已修改文件
src/api/batteryGroupPayload.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/batteryGroups.ts 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/BatteryGroup.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/BatteryGroupForm.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tests/battery-group-persistence.contract.test.mjs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/batteryGroupPayload.ts
New file
@@ -0,0 +1,51 @@
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,
  }
}
src/api/batteryGroups.ts
@@ -1,5 +1,7 @@
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
@@ -13,6 +15,17 @@
  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
@@ -22,6 +35,14 @@
  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 {
@@ -49,6 +70,10 @@
  data: BatteryGroupCreatorDto[]
}
interface BatteryGroupDetailResponse extends ApiResponse {
  data: BatteryGroupDto
}
interface BatteryGroupCreatorDto {
  userName: string | null
  nickName: string | null
@@ -57,6 +82,26 @@
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 {
@@ -96,3 +141,41 @@
    })
    .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 }),
  })
}
src/views/BatteryGroup.vue
@@ -2,8 +2,7 @@
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
src/views/BatteryGroupForm.vue
@@ -1,8 +1,10 @@
<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 {
@@ -20,11 +22,13 @@
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)
@@ -74,35 +78,40 @@
    .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: '' })
@@ -155,8 +164,6 @@
  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 ''
}
@@ -167,19 +174,20 @@
    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>
@@ -190,7 +198,7 @@
    </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">
@@ -221,7 +229,7 @@
          <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">
tests/battery-group-persistence.contract.test.mjs
New file
@@ -0,0 +1,31 @@
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,
  })
})