<script setup lang="ts">
|
import { ref } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { defaultFormationFields } from '@/utils/formationReportFields'
|
import { listFormationTemplates, createFormationTemplate, updateFormationTemplate,
|
type FormationField, type FormationItem, type FormationTemplate } from '@/api/formationReports'
|
|
const props = defineProps<{ fields: FormationField[]; items: FormationItem[]; canCreate: boolean; canEdit: boolean }>()
|
const emit = defineEmits<{ apply: [fields: FormationField[]] }>()
|
const settingsVisible = ref(false)
|
const draft = ref<Array<FormationField & { enabled: boolean }>>([])
|
const templatesVisible = ref(false)
|
const templates = ref<FormationTemplate[]>([])
|
const selectedTemplate = ref<number | null>(null)
|
const loading = ref(false)
|
const saving = ref(false)
|
const error = ref('')
|
const identity = (field: FormationField) => `${field.scope}|${field.key}`
|
function openSettings() {
|
const available: FormationField[] = props.items.flatMap(item => item.businessFields.map(field => ({ key: field.fieldName, displayName: field.fieldName,
|
unit: '', kind: 'battery' as const, scope: 'detail' as const, fieldDefinitionId: field.fieldDefinitionId })))
|
const combined = new Map<string, FormationField>()
|
for (const field of [...props.fields, ...defaultFormationFields, ...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.key === key)) { ElMessage.warning('字段名重复或无效'); return }
|
draft.value.push({ key, displayName: key, unit: '', kind: 'manual', scope: 'detail', 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 listFormationTemplates() }
|
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 updateFormationTemplate(existing.id, name, props.fields)
|
else await createFormationTemplate(name, props.fields)
|
templates.value = await listFormationTemplates()
|
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="`选择${field.key}`" /></td><td>{{ field.key }}</td>
|
<td><el-select v-if="field.kind === 'manual'" v-model="field.scope" aria-label="字段范围"><el-option label="详情" value="detail" /><el-option label="循环" value="cycle" /></el-select><span v-else>{{ field.scope === 'detail' ? '详情' : '循环' }}</span></td>
|
<td><el-input v-model="field.displayName" aria-label="展示名称" maxlength="100" /></td><td><el-input v-model="field.unit" aria-label="单位" maxlength="30" :disabled="field.kind === 'metric'" /></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>
|