<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>
|