yeg
4 天以前 f4d7007b89cf565746af4623623a294b82666ff1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listSpliceTemplates, createSpliceTemplate, updateSpliceTemplate,
  type SpliceField, type SpliceSegment, type SpliceTemplate } from '@/api/spliceReports'
 
const props = defineProps<{ fields: SpliceField[]; segments: SpliceSegment[]; canCreate: boolean; canEdit: boolean }>()
const emit = defineEmits<{ apply: [fields: SpliceField[]] }>()
const settingsVisible = ref(false)
const draft = ref<Array<SpliceField & { enabled: boolean }>>([])
const templatesVisible = ref(false)
const templates = ref<SpliceTemplate[]>([])
const selectedTemplate = ref<number | null>(null)
const loading = ref(false)
const saving = ref(false)
const error = ref('')
const identity = (field: SpliceField) => field.name
const fieldName = (field: SpliceField) => field.name
function openSettings() {
  const available: SpliceField[] = props.segments.flatMap(segment => (segment.businessFields || []).map(field => ({
    name: field.fieldName, displayName: field.fieldName, unit: '', category: 'battery', fieldDefinitionId: field.fieldDefinitionId,
  })))
  const combined = new Map<string, SpliceField>()
  for (const field of [...props.fields, ...available]) if (!combined.has(identity(field))) combined.set(identity(field), field)
  draft.value = [...combined.values()].map(field => ({ ...field, enabled: props.fields.some(selected => identity(selected) === identity(field)) }))
  settingsVisible.value = true
}
function move(index: number, direction: number) {
  const row = draft.value[index]
  if (!row || index + direction < 0 || index + direction >= draft.value.length) return
  draft.value.splice(index, 1); draft.value.splice(index + direction, 0, row)
}
function applySettings() {
  const selected = draft.value.filter(field => field.enabled).map(({ enabled: _enabled, ...field }) => field)
  if (!selected.length || selected.some(field => !field.displayName.trim())) { ElMessage.warning('至少选择一项并填写展示名称'); return }
  emit('apply', selected); settingsVisible.value = false
}
async function addField() {
  try {
    const { value } = await ElMessageBox.prompt('请输入字段名称', '添加人工项', { confirmButtonText: '添加', cancelButtonText: '取消', inputValidator: value => Boolean(value?.trim()) || '请输入字段名称' })
    const key = value.trim()
    if (key.includes('|') || key.length > 100 || draft.value.some(field => field.name === key)) { ElMessage.warning('字段名重复或无效'); return }
    draft.value.push({ name: key, displayName: key, unit: '', category: 'special', enabled: true })
  } catch (cause) { if (cause !== 'cancel' && cause !== 'close') ElMessage.error('添加字段失败') }
}
async function openTemplates() {
  templatesVisible.value = true; loading.value = true; error.value = ''; selectedTemplate.value = null
  try { templates.value = await listSpliceTemplates() }
  catch (cause) { error.value = cause instanceof Error ? cause.message : '字段模板加载失败' }
  finally { loading.value = false }
}
function applyTemplate() {
  const template = templates.value.find(item => item.id === selectedTemplate.value)
  if (!template) { ElMessage.warning('请选择字段模板'); return }
  // 来源详情独立于模板结构维护,不参与模板字段身份、单位和唯一性校验。
  emit('apply', template.fields.map(field => ({ ...field })))
  templatesVisible.value = false; ElMessage.success('字段模板已应用')
}
async function saveTemplate() {
  try {
    const selected = templates.value.find(item => item.id === selectedTemplate.value)
    const { value } = await ElMessageBox.prompt('请输入模板名称', '保存拼接字段模板', { inputValue: selected?.name || '', confirmButtonText: '保存', cancelButtonText: '取消', inputValidator: value => Boolean(value?.trim()) || '请输入模板名称' })
    const name = value.trim()
    const existing = templates.value.find(item => item.name === name)
    if (existing) {
      if (!props.canEdit) { ElMessage.warning('无编辑拼接模板权限'); return }
      await ElMessageBox.confirm(`更新“${name}”的字段结构?`, '更新字段模板', { confirmButtonText: '更新', cancelButtonText: '取消' })
    } else if (!props.canCreate) { ElMessage.warning('无新增拼接模板权限'); return }
    saving.value = true
    if (existing) await updateSpliceTemplate(existing.id, name, props.fields)
    else await createSpliceTemplate(name, props.fields)
    templates.value = await listSpliceTemplates()
    selectedTemplate.value = templates.value.find(item => item.name === name)?.id ?? null
    ElMessage.success('字段模板已保存')
  } catch (cause) { if (cause !== 'cancel' && cause !== 'close') ElMessage.error(cause instanceof Error ? cause.message : '模板保存失败') }
  finally { saving.value = false }
}
</script>
 
<template>
  <div class="field-controls"><el-button @click="openSettings"><el-icon><Setting /></el-icon>字段设置</el-button><el-button @click="openTemplates"><el-icon><DocumentCopy /></el-icon>选择字段模板</el-button></div>
  <el-dialog v-model="settingsVisible" title="字段设置" width="min(850px, calc(100vw - 28px))">
    <div class="field-settings-table"><table><thead><tr><th>选择</th><th>字段</th><th>分类</th><th>展示名称</th><th>单位</th><th>顺序</th></tr></thead><tbody>
      <tr v-for="(field, index) in draft" :key="identity(field)"><td><el-checkbox v-model="field.enabled" :aria-label="`选择${fieldName(field)}`" /></td><td>{{ fieldName(field) }}</td>
        <td>{{ field.category === 'battery' ? '电池组字段' : '人工字段' }}</td>
        <td><el-input v-model="field.displayName" aria-label="展示名称" maxlength="100" /></td><td><el-input v-model="field.unit" aria-label="单位" maxlength="30" /></td>
        <td class="move-buttons"><el-tooltip content="上移"><el-button aria-label="上移" :disabled="index === 0" @click="move(index, -1)"><el-icon><ArrowUp /></el-icon></el-button></el-tooltip><el-tooltip content="下移"><el-button aria-label="下移" :disabled="index === draft.length - 1" @click="move(index, 1)"><el-icon><ArrowDown /></el-icon></el-button></el-tooltip></td>
      </tr></tbody></table></div>
    <template #footer><el-button @click="addField"><el-icon><Plus /></el-icon>添加人工项</el-button><el-button @click="settingsVisible = false">取消</el-button><el-button type="primary" @click="applySettings">确认</el-button></template>
  </el-dialog>
  <el-dialog v-model="templatesVisible" title="选择拼接字段模板" width="min(600px, calc(100vw - 28px))">
    <div v-loading="loading"><el-alert v-if="error" :title="error" type="error" :closable="false" /><el-empty v-else-if="!templates.length && !loading" description="暂无已保存模板" :image-size="60" />
      <el-radio-group v-model="selectedTemplate" class="template-options"><el-radio v-for="template in templates" :key="template.id" :value="template.id">{{ template.name }}</el-radio></el-radio-group>
    </div>
    <template #footer><el-button v-if="canCreate || canEdit" :disabled="saving || loading || !!error" @click="saveTemplate">保存为模板</el-button><el-button @click="templatesVisible = false">取消</el-button><el-button type="primary" :disabled="loading || !selectedTemplate" @click="applyTemplate">应用模板</el-button></template>
  </el-dialog>
</template>
 
<style scoped>
.field-controls{display:flex;flex-wrap:wrap;gap:8px;padding:16px 24px}.field-controls .el-button{margin:0;gap:5px}.field-settings-table{max-height:55vh;overflow:auto}table{border-collapse:collapse;min-width:730px;width:100%;table-layout:fixed;font-size:12px}th,td{padding:8px;border-bottom:1px solid #d8e1e9;text-align:left}th{background:#f2f7fb}th:first-child{width:48px}th:nth-child(2){width:150px}th:nth-child(3){width:90px}th:nth-child(4){width:150px}th:nth-child(5){width:85px}td{overflow-wrap:anywhere}.move-buttons{white-space:nowrap}.move-buttons .el-button{width:28px;height:28px;padding:0;margin-left:4px}.template-options{display:flex;align-items:flex-start;flex-direction:column;gap:8px}.template-options :deep(.el-radio__label){white-space:normal;overflow-wrap:anywhere}
</style>