/* === Data Import Page === */ function PageOperationDataImport() { const toast = useToast(); const [templates, setTemplates] = React.useState([]); const [selectedTemplate, setSelectedTemplate] = React.useState(""); const [dictCategories, setDictCategories] = React.useState([]); const [dictItems, setDictItems] = React.useState({}); const [productCodeType, setProductCodeType] = React.useState('fixed'); const [productCode, setProductCode] = React.useState(""); const [productCodeDictCategory, setProductCodeDictCategory] = React.useState(""); const [productCodeDictItem, setProductCodeDictItem] = React.useState(""); const [productCodeGetValue, setProductCodeGetValue] = React.useState(false); const [productNameType, setProductNameType] = React.useState('fixed'); const [productName, setProductName] = React.useState(""); const [productNameDictCategory, setProductNameDictCategory] = React.useState(""); const [productNameDictItem, setProductNameDictItem] = React.useState(""); const [productNameGetValue, setProductNameGetValue] = React.useState(false); const [selectedFile, setSelectedFile] = React.useState(null); const [previewData, setPreviewData] = React.useState(null); const [previewTemplate, setPreviewTemplate] = React.useState(null); const [loading, setLoading] = React.useState(false); const [importing, setImporting] = React.useState(false); const [productCardExpanded, setProductCardExpanded] = React.useState(false); const API_BASE = window.API_BASE; React.useEffect(() => { fetchTemplates(); 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 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 handleFileSelect = async (e) => { const file = e.target.files?.[0]; if (!file || !selectedTemplate) return; setSelectedFile(file); setLoading(true); setPreviewData(null); const formData = new FormData(); formData.append('template_code', selectedTemplate); formData.append('file', file); try { const res = await fetch(`${API_BASE}/api/import/preview`, { method: 'POST', body: formData }); const json = await res.json(); if (json.code === 200) { setPreviewData(json.data); // 保存模板信息用于渲染分组标题 const templateInfo = templates.find(t => t.template_code === selectedTemplate); setPreviewTemplate(templateInfo || null); } else { toast.push('预览失败: ' + json.msg, 'error'); } } catch (err) { console.error('Preview failed:', err); toast.push('预览失败', 'error'); } finally { setLoading(false); } }; const getProductCodeValue = () => { if (productCodeType === 'dict') { return productCodeGetValue ? productCodeDictItem : productCodeDictCategory; } return productCode; }; const getProductNameValue = () => { if (productNameType === 'dict') { return productNameGetValue ? productNameDictItem : productNameDictCategory; } return productName; }; const handleImport = async () => { if (!selectedFile) { toast.push('请选择文件', 'warn'); return; } const codeValue = getProductCodeValue(); const nameValue = getProductNameValue(); setImporting(true); const formData = new FormData(); formData.append('template_code', selectedTemplate); formData.append('product_code', codeValue); formData.append('product_name', nameValue); formData.append('file', selectedFile); try { const res = await fetch(`${API_BASE}/api/import/confirm`, { method: 'POST', body: formData }); const json = await res.json(); if (json.code === 200) { toast.push('导入成功!共导入 ' + json.data.imported_count + ' 条数据', 'success'); setPreviewData(null); setSelectedFile(null); } else { toast.push('导入失败: ' + json.msg, 'error'); } } catch (err) { console.error('Import failed:', err); toast.push('导入失败', 'error'); } finally { setImporting(false); } }; const handleCancel = () => { setPreviewData(null); setSelectedFile(null); }; const selectedTemplateInfo = templates.find(t => t.template_code === selectedTemplate); return (
{selectedTemplate && ( <> {/* 可折叠的产品信息卡片 */}
setProductCardExpanded(!productCardExpanded)} > 产品信息(可选)
{productCodeType === 'fixed' && ( setProductCode(e.target.value)} className="input" placeholder="请输入产品编码" /> )} {productCodeType === 'dict' && ( <> {productCodeDictCategory && dictItems[productCodeDictCategory] && ( )} )}
{productNameType === 'fixed' && ( setProductName(e.target.value)} className="input" placeholder="请输入产品名称" /> )} {productNameType === 'dict' && ( <> {productNameDictCategory && dictItems[productNameDictCategory] && ( )} )}
{selectedFile ? selectedFile.name : '点击或拖拽文件到这里'}
{selectedFile ? '已选择文件' : '支持 .xlsx, .xls 格式'}
{selectedFile ? '重新选择' : '选择文件'}
{selectedTemplateInfo && (
文件类型: {selectedTemplateInfo.file_type_name || selectedTemplateInfo.file_type}
列映射: {selectedTemplateInfo.column_mapping?.map(m => m.field_name).join('、')}
)} )}
{loading && (
正在解析文件...
)} {previewData && (

数据预览

共 {previewData.total} 条数据
{/* 1. 原始数据 */}

原始数据

{previewData.raw_headers?.map((header, idx) => ( ))} {previewData.raw_data?.slice(0, 10).map((row, rowIdx) => ( {row.map((cell, colIdx) => ( ))} ))}
{header}
{cell || '-'}
{/* 2. 解析预览 */}

解析预览

{previewData.parsed_data?.[0] && Object.keys(previewData.parsed_data[0]) .filter(key => key !== 'group_name') .map(key => { // 判断是公共字段还是分组字段 const isCommon = previewTemplate?.common_mappings?.some(m => m.target_dict_item === key); const groupIndex = previewTemplate?.groups?.findIndex(g => g.mappings?.some(m => m.target_dict_item === key) ); let colClass = 'preview-col'; if (isCommon) { colClass = 'common-col'; } else if (groupIndex >= 0) { colClass = `group-col group-${groupIndex + 1}`; } return ( ); })} {previewData.parsed_data?.slice(0, 10).map((row, idx) => ( {Object.keys(row) .filter(key => key !== 'group_name') .map(key => { const isCommon = previewTemplate?.common_mappings?.some(m => m.target_dict_item === key); const groupIndex = previewTemplate?.groups?.findIndex(g => g.mappings?.some(m => m.target_dict_item === key) ); let colClass = 'preview-col'; if (isCommon) { colClass = 'common-col'; } else if (groupIndex >= 0) { colClass = `group-col group-${groupIndex + 1}`; } return ( ); })} ))}
k !== 'group_name').length} key="header-all" className="group-header"> 数据预览
{previewData.parsed_data[0][key]?.field_name || key}
{row[key]?.value || '-'}
{/* 3. SQL预览 */}

SQL预览