yeg
2026-08-29 08398ca802378b14caa16d122f030abf6a8a3b46
支持列设置展示共享字段

从后端加载电池组字段定义,动态生成列设置和表格列,并展示各电池组对应字段值。
3个文件已修改
51 ■■■■■ 已修改文件
src/api/batteryGroups.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/BatteryGroup.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tests/battery-group-persistence.contract.test.mjs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/batteryGroups.ts
@@ -60,6 +60,7 @@
  createdAt: string
  status: GroupStatus
  creator: string
  templateFields: BatteryGroupTemplateField[]
}
export interface BatteryGroupListResult {
@@ -116,6 +117,15 @@
    createdAt: item.createTime || '-',
    status: item.status === '1' ? '禁用' : '启用',
    creator: item.createBy || '-',
    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 || 'text',
        fieldValue: field.fieldValue || '',
      })),
  }
}
src/views/BatteryGroup.vue
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { getBatteryGroupFields, type BatteryGroupField } from '@/api/batteryGroupFields'
import { changeBatteryGroupStatus, deleteBatteryGroup, getBatteryGroupCreatorOptions, getBatteryGroupList, type BatteryGroupCreatorOption, type BatteryGroupListItem, type GroupStatus } from '@/api/batteryGroups'
interface Filters {
@@ -13,16 +14,21 @@
  creator: string
}
type ColumnKey = 'id' | 'name' | 'model' | 'purpose' | 'batteryCount' | 'createdAt' | 'status' | 'creator'
type ColumnKey = 'id' | 'name' | 'model' | 'purpose' | 'batteryCount' | 'createdAt' | 'status' | 'creator' | `field-${number}`
const router = useRouter()
const creators = ref<BatteryGroupCreatorOption[]>([])
const customFields = ref<BatteryGroupField[]>([])
const statuses: GroupStatus[] = ['启用', '禁用']
const columnOptions: Array<{ key: ColumnKey, label: string }> = [
const fixedColumnOptions: Array<{ key: ColumnKey, label: string }> = [
  { key: 'id', label: '电池组ID' }, { key: 'name', label: '电池组名称' }, { key: 'model', label: '型号' },
  { key: 'purpose', label: '目的' }, { key: 'batteryCount', label: '电池数量' }, { key: 'createdAt', label: '添加时间' },
  { key: 'status', label: '状态' }, { key: 'creator', label: '创建人' },
]
const columnOptions = computed(() => [
  ...fixedColumnOptions,
  ...customFields.value.map(field => ({ key: `field-${field.id}` as ColumnKey, label: field.name })),
])
const filters = reactive<Filters>({ id: '', name: '', model: '', dateRange: null, status: '', creator: '' })
const appliedFilters = ref<Filters>({ id: '', name: '', model: '', dateRange: null, status: '', creator: '' })
@@ -32,11 +38,15 @@
const currentPage = ref(1)
const pageSize = ref(10)
const columnDialogVisible = ref(false)
const visibleColumns = ref<ColumnKey[]>(columnOptions.map(item => item.key))
const visibleColumns = ref<ColumnKey[]>(fixedColumnOptions.map(item => item.key))
let requestId = 0
function isColumnVisible(key: ColumnKey) {
  return visibleColumns.value.includes(key)
}
function customFieldValue(item: BatteryGroupListItem, fieldId: number) {
  return item.templateFields.find(field => field.batteryGroupFieldId === fieldId)?.fieldValue || '-'
}
async function loadGroups() {
@@ -146,8 +156,20 @@
  }
}
async function loadCustomFields() {
  try {
    customFields.value = await getBatteryGroupFields()
    const existing = new Set(visibleColumns.value)
    customFields.value.forEach(field => existing.add(`field-${field.id}` as ColumnKey))
    visibleColumns.value = [...existing]
  } catch {
    customFields.value = []
  }
}
onMounted(() => {
  void loadCreatorOptions()
  void loadCustomFields()
  void loadGroups()
})
</script>
@@ -175,10 +197,10 @@
      <div class="table-wrap">
        <table>
          <thead><tr><th v-if="isColumnVisible('id')">电池组ID</th><th v-if="isColumnVisible('name')">电池组名称</th><th v-if="isColumnVisible('model')">型号</th><th v-if="isColumnVisible('purpose')">目的</th><th v-if="isColumnVisible('batteryCount')">电池数量</th><th v-if="isColumnVisible('createdAt')">添加时间</th><th v-if="isColumnVisible('status')">状态</th><th v-if="isColumnVisible('creator')">创建人</th><th class="action-column">操作</th></tr></thead>
          <thead><tr><th v-if="isColumnVisible('id')">电池组ID</th><th v-if="isColumnVisible('name')">电池组名称</th><th v-if="isColumnVisible('model')">型号</th><th v-if="isColumnVisible('purpose')">目的</th><th v-if="isColumnVisible('batteryCount')">电池数量</th><th v-if="isColumnVisible('createdAt')">添加时间</th><th v-if="isColumnVisible('status')">状态</th><th v-if="isColumnVisible('creator')">创建人</th><template v-for="field in customFields" :key="`header-${field.id}`"><th v-if="isColumnVisible(`field-${field.id}`)">{{ field.name }}</th></template><th class="action-column">操作</th></tr></thead>
          <tbody>
            <tr v-for="item in groups" :key="item.id">
              <td v-if="isColumnVisible('id')" class="id-cell">#{{ item.id }}</td><td v-if="isColumnVisible('name')"><strong>{{ item.name }}</strong></td><td v-if="isColumnVisible('model')" class="code-cell">{{ item.model }}</td><td v-if="isColumnVisible('purpose')" :title="item.purpose">{{ item.purpose }}</td><td v-if="isColumnVisible('batteryCount')">{{ item.batteryCount }} 块</td><td v-if="isColumnVisible('createdAt')">{{ item.createdAt }}</td><td v-if="isColumnVisible('status')"><span class="status-tag" :class="item.status === '启用' ? 'enabled' : 'disabled'"><i></i>{{ item.status }}</span></td><td v-if="isColumnVisible('creator')">{{ item.creator }}</td>
              <td v-if="isColumnVisible('id')" class="id-cell">#{{ item.id }}</td><td v-if="isColumnVisible('name')"><strong>{{ item.name }}</strong></td><td v-if="isColumnVisible('model')" class="code-cell">{{ item.model }}</td><td v-if="isColumnVisible('purpose')" :title="item.purpose">{{ item.purpose }}</td><td v-if="isColumnVisible('batteryCount')">{{ item.batteryCount }} 块</td><td v-if="isColumnVisible('createdAt')">{{ item.createdAt }}</td><td v-if="isColumnVisible('status')"><span class="status-tag" :class="item.status === '启用' ? 'enabled' : 'disabled'"><i></i>{{ item.status }}</span></td><td v-if="isColumnVisible('creator')">{{ item.creator }}</td><template v-for="field in customFields" :key="`value-${item.id}-${field.id}`"><td v-if="isColumnVisible(`field-${field.id}`)">{{ customFieldValue(item, field.id) }}</td></template>
              <td class="action-column"><div class="row-actions"><button type="button" @click="editGroup(item)">编辑</button><button type="button" :class="item.status === '启用' ? 'disable-action' : 'enable-action'" @click="toggleStatus(item)">{{ item.status === '启用' ? '禁用' : '启用' }}</button><button class="delete-action" type="button" @click="removeGroup(item)">删除</button></div></td>
            </tr>
            <tr v-if="!groups.length"><td :colspan="visibleColumns.length + 1" class="empty-cell">{{ loading ? '正在加载电池组...' : '暂无符合条件的电池组' }}</td></tr>
tests/battery-group-persistence.contract.test.mjs
@@ -47,3 +47,10 @@
  assert.doesNotMatch(source, /localStorage\./)
  assert.match(source, /getBatteryGroupFields/)
})
test('battery group columns include backend-defined shared fields', () => {
  const source = fs.readFileSync(new URL('../src/views/BatteryGroup.vue', import.meta.url), 'utf8')
  assert.match(source, /getBatteryGroupFields/)
  assert.match(source, /field-\$\{field\.id\}/)
  assert.match(source, /customFieldValue/)
})