import React, { useRef, useState } from 'react'; import * as XLSX from 'xlsx'; import { ImportData, RecordRow } from '../types'; import { FileSpreadsheet, Upload, Download, Trash2 } from 'lucide-react'; import { useAppDialog } from './AppDialog'; export function downloadDataTemplate(templateName: string, templateVariables: string[]) { const columns = templateVariables.length > 0 ? templateVariables : ['数据']; const ws = XLSX.utils.aoa_to_sheet([columns, columns.map(() => '')]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '数据'); const safeName = templateName.replace(/[^\w\u4e00-\u9fa5-]+/g, '_').slice(0, 30) || '标签'; XLSX.writeFile(wb, `${safeName}_数据模板.xlsx`); } interface DataImporterProps { importData: ImportData | null; onDataLoaded: (data: ImportData) => void; onClear: () => void; templateName: string; templateVariables: string[]; variant?: 'default' | 'ps' | 'mobile'; } export const DataImporter: React.FC = ({ importData, onDataLoaded, onClear, templateName, templateVariables, variant = 'default', }) => { const { showAlert } = useAppDialog(); const isPs = variant === 'ps'; const isMobile = variant === 'mobile'; const fileInputRef = useRef(null); const [dragActive, setDragActive] = useState(false); const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true); } else if (e.type === 'dragleave') { setDragActive(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { processFile(e.dataTransfer.files[0]); } }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { processFile(e.target.files[0]); } }; const processFile = (file: File) => { const reader = new FileReader(); reader.onload = async (e) => { try { const data = new Uint8Array(e.target?.result as ArrayBuffer); const workbook = XLSX.read(data, { type: 'array' }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: '' }); if (jsonData.length === 0) { await showAlert('表格为空或格式不正确', { variant: 'warning' }); return; } const rawColumns = jsonData[0] as string[]; const columns = rawColumns.map((c, idx) => (c ? String(c).trim() : `列_${idx + 1}`)).filter(Boolean); const rows: RecordRow[] = []; for (let i = 1; i < jsonData.length; i++) { const rawRow = jsonData[i] as any[]; if (rawRow.every((cell) => cell === undefined || cell === null || String(cell).trim() === '')) { continue; } const row: RecordRow = {}; columns.forEach((col, colIdx) => { row[col] = rawRow[colIdx] !== undefined ? String(rawRow[colIdx]).trim() : ''; }); rows.push(row); } onDataLoaded({ fileName: file.name, sheets: workbook.SheetNames, currentSheet: firstSheetName, columns, rows, }); } catch (err) { console.error(err); await showAlert('解析文件出错,请确保是标准的 Excel 或 CSV 文件', { variant: 'error' }); } }; reader.readAsArrayBuffer(file); }; const handleSheetChange = (sheetName: string) => { if (!fileInputRef.current?.files?.[0]) return; const file = fileInputRef.current.files[0]; const reader = new FileReader(); reader.onload = async (e) => { try { const data = new Uint8Array(e.target?.result as ArrayBuffer); const workbook = XLSX.read(data, { type: 'array' }); const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: '' }); if (jsonData.length === 0) return; const rawColumns = jsonData[0] as string[]; const columns = rawColumns.map((c, idx) => (c ? String(c).trim() : `列_${idx + 1}`)).filter(Boolean); const rows: RecordRow[] = []; for (let i = 1; i < jsonData.length; i++) { const rawRow = jsonData[i] as any[]; if (rawRow.every((cell) => cell === undefined || cell === null || String(cell).trim() === '')) { continue; } const row: RecordRow = {}; columns.forEach((col, colIdx) => { row[col] = rawRow[colIdx] !== undefined ? String(rawRow[colIdx]).trim() : ''; }); rows.push(row); } onDataLoaded({ fileName: file.name, sheets: workbook.SheetNames, currentSheet: sheetName, columns, rows, }); } catch { await showAlert('切换工作表时出错', { variant: 'error' }); } }; reader.readAsArrayBuffer(file); }; const rows = importData?.rows || []; const columns = importData?.columns || []; const wrapperClass = isMobile ? '' : isPs ? 'space-y-3' : 'bg-white border border-gray-200 rounded-2xl p-6 shadow-sm flex flex-col gap-5'; return (
{/* {!isPs && (

批量数据源导入

上传 Excel 或 CSV,绑定当前模板变量

)} */} {!isMobile && (
)} {!importData ? (
fileInputRef.current?.click()} className={ isMobile ? `mobile-upload-zone ${dragActive ? 'active' : ''}` : isPs ? `border border-dashed rounded py-8 px-4 text-center cursor-pointer transition flex flex-col items-center gap-2 ${ dragActive ? 'border-[#31a8ff] bg-[#1a3a4f]/30' : 'border-[#4a4a4a] hover:border-[#31a8ff]/60 hover:bg-[#1e1e1e]' }` : `border-2 border-dashed rounded-2xl py-10 px-6 text-center cursor-pointer transition flex flex-col items-center gap-3 ${ dragActive ? 'border-indigo-500 bg-indigo-50/20' : 'border-gray-200 hover:border-indigo-400 hover:bg-gray-50/50' }` } >

{isMobile ? '点击选择 Excel / CSV' : '点击上传或拖拽文件到此处'}

{isMobile && (

支持 .xlsx、.xls、.csv

)}
) : (

{importData.fileName}

{rows.length} 条记录 · {columns.length} 列

{importData.sheets.length > 1 && ( )}
)}
); };