/* === Import Template Management Page v2 === */ function PageOperationDataTemplate() { const [templates, setTemplates] = React.useState([]); const [showDrawer, setShowDrawer] = React.useState(false); const [editingTemplate, setEditingTemplate] = React.useState(null); const [showViewModal, setShowViewModal] = React.useState(false); const [viewingTemplate, setViewingTemplate] = React.useState(null); const [fileTypes, setFileTypes] = React.useState([]); const [dictCategories, setDictCategories] = React.useState([]); const [dictItems, setDictItems] = React.useState({}); const [formData, setFormData] = React.useState({ template_name: '', template_code: '', target_table_type: 'fixed', target_table: '', target_table_dict_category: '', target_table_dict_item: '', target_table_get_value: false, file_type: '', common_mappings: [], groups: [{ name: '组一', mappings: [] }] }); const API_BASE = window.API_BASE; React.useEffect(() => { fetchTemplates(); fetchFileTypes(); fetchDictCategories(); }, []); const fetchTemplates = async () => { try { const res = await fetch(`${API_BASE}/api/import/templates`); const json = await res.json(); if (Array.isArray(json)) { setTemplates(json); } else if (json.data) { setTemplates(json.data); } } catch (err) { console.error('Failed to fetch templates:', err); } }; const fetchFileTypes = async () => { try { const res = await fetch(`${API_BASE}/api/dict/item/select?category_code=loadfile-type`); const json = await res.json(); console.log('File types response:', json); if (Array.isArray(json)) { const formattedTypes = json.map(item => ({ value: item.value || item.code || item.id, label: item.label || item.name || item.value })); setFileTypes(formattedTypes); } } catch (err) { console.error('Failed to fetch file types:', err); } }; const fetchDictCategories = async () => { try { const res = await fetch(`${API_BASE}/api/dict/category/tree`); const json = await res.json(); if (Array.isArray(json)) { setDictCategories(json); } } catch (err) { console.error('Failed to fetch dict categories:', err); } }; const flattenCategories = (nodes, level = 0, result = []) => { if (!Array.isArray(nodes)) return result; nodes.forEach(node => { result.push({ code: node.code, name: node.name, level: level, indent: ' '.repeat(level) }); if (node.children && node.children.length > 0) { flattenCategories(node.children, level + 1, result); } }); return result; }; const flatCategories = React.useMemo(() => { return flattenCategories(dictCategories); }, [dictCategories]); const fetchDictItems = async (categoryCode) => { if (dictItems[categoryCode]) return; try { const res = await fetch(`${API_BASE}/api/dict/item/select?category_code=${categoryCode}`); const json = await res.json(); if (Array.isArray(json)) { setDictItems(prev => ({ ...prev, [categoryCode]: json })); } } catch (err) { console.error('Failed to fetch dict items:', err); } }; const openCreateDrawer = () => { setEditingTemplate(null); setFormData({ template_name: '', template_code: '', target_table_type: 'fixed', target_table: '', target_table_dict_category: '', target_table_dict_item: '', target_table_get_value: false, file_type: '', common_mappings: [createEmptyMapping()], groups: [{ name: '组一', mappings: [createEmptyMapping()] }] }); setShowDrawer(true); }; const createEmptyMapping = () => ({ source_type: 'excel', source_value: '1', source_dict_category: '', source_dict_item: '', source_get_value: false, target_type: 'string', target_value: '', target_dict_category: '', target_dict_item: '', target_get_value: true, field_type: 'string', json_key: '' }); const openEditDrawer = (template) => { setEditingTemplate(template); const commonMappings = template.common_mappings || [createEmptyMapping()]; const groupsData = template.groups || [{ name: '组一', mappings: [createEmptyMapping()] }]; // 预加载所有字典项 const loadDictItems = () => { // 加载公共映射中的字典项 commonMappings.forEach(mapping => { if (mapping.source_type === 'dict' && mapping.source_dict_category) { fetchDictItems(mapping.source_dict_category); } if (mapping.target_type === 'dict' && mapping.target_dict_category) { fetchDictItems(mapping.target_dict_category); } }); // 加载分组映射中的字典项 groupsData.forEach(group => { group.mappings.forEach(mapping => { if (mapping.source_type === 'dict' && mapping.source_dict_category) { fetchDictItems(mapping.source_dict_category); } if (mapping.target_type === 'dict' && mapping.target_dict_category) { fetchDictItems(mapping.target_dict_category); } }); }); }; setFormData({ template_name: template.template_name, template_code: template.template_code, target_table_type: template.target_table_type || 'fixed', target_table: template.target_table || '', target_table_dict_category: template.target_table_dict_category || '', target_table_dict_item: template.target_table_dict_item || '', target_table_get_value: template.target_table_get_value || false, file_type: template.file_type || '', common_mappings: commonMappings, groups: groupsData }); setTimeout(loadDictItems, 0); setShowDrawer(true); }; const handleDelete = async (templateCode) => { if (!confirm('确定要删除此模板吗?')) return; try { const res = await fetch(`${API_BASE}/api/import/template/${templateCode}`, { method: 'DELETE' }); const json = await res.json(); if (json.code === 200) { fetchTemplates(); } else { alert('删除失败: ' + json.msg); } } catch (err) { console.error('Delete failed:', err); alert('删除失败'); } }; const handleView = (template) => { setViewingTemplate(template); setShowViewModal(true); }; const handleCopy = async (template) => { const newTemplateCode = template.template_code + '_copy'; const newTemplateName = template.template_name + '(副本)'; const templateData = { template_name: newTemplateName, template_code: newTemplateCode, target_table_type: template.target_table_type || 'fixed', target_table: template.target_table || '', target_table_dict_category: template.target_table_dict_category || '', target_table_dict_item: template.target_table_dict_item || '', target_table_get_value: template.target_table_get_value || false, file_type: template.file_type || '', common_mappings: template.common_mappings || [], groups: template.groups || [] }; try { const res = await fetch(`${API_BASE}/api/import/template`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(templateData) }); const json = await res.json(); if (json.code === 200 || json.code === 0) { fetchTemplates(); } else { alert('复制失败: ' + (json.msg || json.message || '未知错误')); } } catch (err) { console.error('Copy failed:', err); alert('复制失败'); } }; const handleRefresh = async (templateCode) => { if (!confirm('确定要刷新此模板的映射吗?这将根据当前字典数据重新验证映射配置。')) return; try { const res = await fetch(`${API_BASE}/api/import/template/${templateCode}/refresh`, { method: 'POST' }); const json = await res.json(); if (json.code === 200) { fetchTemplates(); const data = json.data || {}; const invalidList = data.invalid_mappings || []; if (invalidList.length > 0) { const details = invalidList.map((m, i) => { if (m.type === 'common') { return `${i + 1}. [公共映射] 类别: ${m.category}, 字典项: ${m.item}`; } else { return `${i + 1}. [${m.group}] 类别: ${m.category}, 字典项: ${m.item}`; } }).join('\n'); alert(`刷新完成!\n发现 ${invalidList.length} 个无效映射:\n\n${details}`); } else { alert('刷新成功:所有映射都有效!'); } } else { alert('刷新失败: ' + json.msg); } } catch (err) { console.error('Refresh failed:', err); alert('刷新失败'); } }; const addCommonMapping = () => { setFormData(prev => ({ ...prev, common_mappings: [...prev.common_mappings, createEmptyMapping()] })); }; const removeCommonMapping = (index) => { setFormData(prev => ({ ...prev, common_mappings: prev.common_mappings.filter((_, i) => i !== index) })); }; const updateCommonMapping = (index, field, value) => { setFormData(prev => { const newMappings = [...prev.common_mappings]; newMappings[index] = { ...newMappings[index], [field]: value }; return { ...prev, common_mappings: newMappings }; }); }; const addGroup = () => { const newGroupName = `组${formData.groups.length + 1}`; setFormData(prev => ({ ...prev, groups: [...prev.groups, { name: newGroupName, mappings: [createEmptyMapping()] }] })); }; const removeGroup = (index) => { if (formData.groups.length <= 1) { alert('至少保留一个分组'); return; } setFormData(prev => ({ ...prev, groups: prev.groups.filter((_, i) => i !== index) })); }; const copyGroup = (index) => { const groupToCopy = formData.groups[index]; const copiedGroup = { name: groupToCopy.name + '(副本)', mappings: groupToCopy.mappings.map(m => ({ ...m })) }; setFormData(prev => ({ ...prev, groups: [ ...prev.groups.slice(0, index + 1), copiedGroup, ...prev.groups.slice(index + 1) ] })); }; const moveGroup = (fromIndex, toIndex) => { setFormData(prev => { const newGroups = [...prev.groups]; const [removed] = newGroups.splice(fromIndex, 1); newGroups.splice(toIndex, 0, removed); return { ...prev, groups: newGroups }; }); }; const updateGroupName = (index, name) => { setFormData(prev => { const newGroups = [...prev.groups]; newGroups[index] = { ...newGroups[index], name }; return { ...prev, groups: newGroups }; }); }; const addGroupMapping = (groupIndex) => { setFormData(prev => { const newGroups = [...prev.groups]; newGroups[groupIndex] = { ...newGroups[groupIndex], mappings: [...newGroups[groupIndex].mappings, createEmptyMapping()] }; return { ...prev, groups: newGroups }; }); }; const removeGroupMapping = (groupIndex, mappingIndex) => { setFormData(prev => { const newGroups = [...prev.groups]; newGroups[groupIndex] = { ...newGroups[groupIndex], mappings: newGroups[groupIndex].mappings.filter((_, i) => i !== mappingIndex) }; return { ...prev, groups: newGroups }; }); }; const copyGroupMapping = (groupIndex, mappingIndex) => { setFormData(prev => { const newGroups = [...prev.groups]; const mappingToCopy = newGroups[groupIndex].mappings[mappingIndex]; const copiedMapping = { ...mappingToCopy }; newGroups[groupIndex] = { ...newGroups[groupIndex], mappings: [ ...newGroups[groupIndex].mappings.slice(0, mappingIndex + 1), copiedMapping, ...newGroups[groupIndex].mappings.slice(mappingIndex + 1) ] }; return { ...prev, groups: newGroups }; }); }; const moveGroupMapping = (groupIndex, fromIndex, toIndex) => { setFormData(prev => { const newGroups = [...prev.groups]; const mappings = [...newGroups[groupIndex].mappings]; const [removed] = mappings.splice(fromIndex, 1); mappings.splice(toIndex, 0, removed); newGroups[groupIndex] = { ...newGroups[groupIndex], mappings }; return { ...prev, groups: newGroups }; }); }; const copyCommonMapping = (index) => { setFormData(prev => { const mappingToCopy = prev.common_mappings[index]; const copiedMapping = { ...mappingToCopy }; return { ...prev, common_mappings: [ ...prev.common_mappings.slice(0, index + 1), copiedMapping, ...prev.common_mappings.slice(index + 1) ] }; }); }; const moveCommonMapping = (fromIndex, toIndex) => { setFormData(prev => { const mappings = [...prev.common_mappings]; const [removed] = mappings.splice(fromIndex, 1); mappings.splice(toIndex, 0, removed); return { ...prev, common_mappings: mappings }; }); }; const updateGroupMapping = (groupIndex, mappingIndex, field, value) => { setFormData(prev => { const newGroups = [...prev.groups]; newGroups[groupIndex] = { ...newGroups[groupIndex], mappings: newGroups[groupIndex].mappings.map((m, i) => i === mappingIndex ? { ...m, [field]: value } : m ) }; return { ...prev, groups: newGroups }; }); }; const handleSave = async () => { if (!formData.template_name || !formData.template_code) { alert('请填写模板名称和模板编码'); return; } if (!formData.file_type) { alert('请选择文件类型'); return; } try { const url = editingTemplate ? `${API_BASE}/api/import/template/${editingTemplate.template_code}` : `${API_BASE}/api/import/template`; const method = editingTemplate ? 'PUT' : 'POST'; const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const json = await res.json(); if (json.code === 200) { setShowDrawer(false); fetchTemplates(); } else { alert('保存失败: ' + json.msg); } } catch (err) { console.error('Save failed:', err); alert('保存失败'); } }; const renderMappingCard = (mapping, index, onUpdate, onRemove, onCopy, onMove, groupIndex = null) => (
{ e.dataTransfer.setData('index', index); e.dataTransfer.setData('groupIndex', groupIndex ?? 'common'); }} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { const fromIndex = parseInt(e.dataTransfer.getData('index')); const fromGroupIndex = e.dataTransfer.getData('groupIndex'); if ((fromGroupIndex === 'common' && groupIndex === null) || (fromGroupIndex !== 'common' && groupIndex !== null && fromGroupIndex === String(groupIndex))) { onMove?.(fromIndex, index); } }} >
{index + 1}
数据源
{mapping.source_type === 'excel' && (
onUpdate(index, 'source_value', e.target.value)} className="mapping-input" placeholder="列号" min="1" />
)} {mapping.source_type === 'string' && (
onUpdate(index, 'source_value', e.target.value)} className="mapping-input" placeholder="固定值" />
)} {mapping.source_type === 'dict' && ( <>
{mapping.source_dict_category && dictItems[mapping.source_dict_category] && (
)} )}
数据库
{mapping.target_type === 'string' && (
onUpdate(index, 'target_value', e.target.value)} className="mapping-input" placeholder="字段代码" />
)} {mapping.target_type === 'dict' && ( <>
{mapping.target_dict_category && dictItems[mapping.target_dict_category] && (
)} )}
类型
{mapping.field_type === 'json' && (
onUpdate(index, 'json_key', e.target.value)} className="mapping-input" placeholder="JSON Key" />
)}
); return (

模板管理

{templates.length > 0 ? (
{templates.map(template => (
{template.template_name}
{template.template_code} {template.file_type}
公共字段: {template.common_mappings?.length || 0} | 分组数: {template.groups?.length || 0}
))}
) : (
📋

暂无模板

点击上方按钮创建导入模板

)}
{showDrawer && (
setShowDrawer(false)}>
e.stopPropagation()}>

{editingTemplate ? '编辑模板' : '新建模板'}

配置导入数据的字段映射关系

基本信息

setFormData({...formData, template_name: e.target.value})} className="template-form-input" placeholder="请输入模板名称" />
setFormData({...formData, template_code: e.target.value})} className="template-form-input" placeholder="请输入模板编码" disabled={!!editingTemplate} />
{formData.target_table_type === 'fixed' && ( setFormData({...formData, target_table: e.target.value})} className="template-form-input" placeholder="请输入目标表名" /> )} {formData.target_table_type === 'dict' && ( <> {formData.target_table_dict_category && dictItems[formData.target_table_dict_category] && ( )} )}

公共映射

所有分组共享的字段
{formData.common_mappings.map((mapping, index) => ( renderMappingCard( mapping, index, updateCommonMapping, removeCommonMapping, copyCommonMapping, moveCommonMapping ) ))}

明细映射

每组+公共信息生成一条记录
{formData.groups.map((group, groupIndex) => (
e.dataTransfer.setData('groupIndex', groupIndex)} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { const fromIndex = parseInt(e.dataTransfer.getData('groupIndex')); if (fromIndex !== groupIndex) { moveGroup(fromIndex, groupIndex); } }} >
updateGroupName(groupIndex, e.target.value)} className="template-group-name-input" />
{formData.groups.length > 1 && ( )}
{group.mappings.map((mapping, mappingIndex) => ( renderMappingCard( mapping, mappingIndex, (idx, field, val) => updateGroupMapping(groupIndex, idx, field, val), (idx) => removeGroupMapping(groupIndex, idx), (idx) => copyGroupMapping(groupIndex, idx), (fromIdx, toIdx) => moveGroupMapping(groupIndex, fromIdx, toIdx), groupIndex ) ))}
))}
)} {showViewModal && viewingTemplate && (
setShowViewModal(false)}>
e.stopPropagation()}>

模板详情

{viewingTemplate.template_name}

模板编码: {viewingTemplate.template_code}
目标表: {viewingTemplate.target_table}
文件类型: {viewingTemplate.file_type}

公共映射

                    {JSON.stringify(viewingTemplate.common_mappings || [], null, 2)}
                  

分组配置

{(viewingTemplate.groups || []).map((group, index) => (
分组 {index + 1}: {group.name || `组${index + 1}`}
                        {JSON.stringify(group, null, 2)}
                      
))}
)}
); }