import { useState, useRef } from 'react'; import type { Word } from '../types/vocabulary'; interface ImportModalProps { isOpen: boolean; onClose: () => void; onImport: (words: Word[]) => void; } const normalizeWord = (input: Partial, index: number): Word | null => { const french = input.french?.trim(); const english = input.english?.trim(); if (!french || !english) { return null; } return { id: input.id || `imported-${Date.now()}-${index}`, french, english, pronunciation: input.pronunciation?.trim() || '', ttsText: input.ttsText?.trim() || french, chinese: input.chinese?.trim() || '', partOfSpeech: input.partOfSpeech?.trim() || '', example: input.example?.trim() || '', exampleTranslation: input.exampleTranslation?.trim() || '', examplePronunciation: input.examplePronunciation?.trim() || '', notes: input.notes?.trim() || '', audioUrl: input.audioUrl?.trim() || '', category: input.category?.trim() || 'General', difficulty: input.difficulty || 'beginner', tags: input.tags || [], }; }; const parseCsvLine = (line: string) => line.split(',').map(part => part.trim().replace(/^"|"$/g, '')); export function ImportModal({ isOpen, onClose, onImport }: ImportModalProps) { const [content, setContent] = useState(''); const [error, setError] = useState(''); const fileInputRef = useRef(null); if (!isOpen) return null; const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { const text = event.target?.result as string; setContent(text); parseContent(text); }; reader.readAsText(file); }; const parseContent = (text: string) => { setError(''); try { const json = JSON.parse(text); if (Array.isArray(json)) { const words = json.map((item, index) => normalizeWord(item, index)).filter(Boolean) as Word[]; if (words.length > 0) { onImport(words); onClose(); return; } } if (json.words && Array.isArray(json.words)) { const words = json.words.map((item: Partial, index: number) => normalizeWord(item, index)).filter(Boolean) as Word[]; if (words.length > 0) { onImport(words); onClose(); return; } } } catch { // fallthrough to CSV } const lines = text .trim() .split('\n') .map(line => line.trim()) .filter(Boolean); if (lines.length === 0) { setError('没有可导入的内容'); return; } const firstLine = parseCsvLine(lines[0]).map(v => v.toLowerCase()); const hasHeader = firstLine.includes('french') || firstLine.includes('english'); const dataLines = hasHeader ? lines.slice(1) : lines; const headerIndex = hasHeader ? firstLine : []; const words = dataLines .map((line, index) => { const parts = parseCsvLine(line); if (hasHeader) { const item: Partial = { french: parts[headerIndex.indexOf('french')] || '', english: parts[headerIndex.indexOf('english')] || '', pronunciation: parts[headerIndex.indexOf('pronunciation')] || '', chinese: parts[headerIndex.indexOf('chinese')] || '', partOfSpeech: parts[headerIndex.indexOf('partofspeech')] || '', example: parts[headerIndex.indexOf('example')] || '', exampleTranslation: parts[headerIndex.indexOf('exampletranslation')] || '', category: parts[headerIndex.indexOf('category')] || 'General', difficulty: (parts[headerIndex.indexOf('difficulty')] as Word['difficulty']) || 'beginner', }; return normalizeWord(item, index); } return normalizeWord( { french: parts[0] || '', english: parts[1] || '', pronunciation: parts[2] || '', category: parts[3] || 'General', example: parts[4] || '', exampleTranslation: parts[5] || '', }, index ); }) .filter(Boolean) as Word[]; if (words.length > 0) { onImport(words); onClose(); } else { setError('无法解析文件格式,请检查 french 和 english 字段是否存在'); } }; const handleSubmit = () => { if (!content.trim()) { setError('请输入内容'); return; } parseContent(content); }; return (

导入词库

支持更完整的法语词条字段:单词、英文、中文、音标、词性、例句、例句翻译、分类、难度。

或粘贴内容