From a7d40577c9095a3574651d7c4e26bb5368b53a01 Mon Sep 17 00:00:00 2001 From: Kai Date: Sun, 6 Jul 2025 20:34:29 -0700 Subject: [PATCH] feat: add export all words --- src/pages/ErrorBook/DropdownExport.tsx | 104 ++++++++++++++++++------- src/pages/ErrorBook/ErrorRow.tsx | 14 +--- src/pages/ErrorBook/index.tsx | 8 +- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/pages/ErrorBook/DropdownExport.tsx b/src/pages/ErrorBook/DropdownExport.tsx index 1a62baad..5d6fdd04 100644 --- a/src/pages/ErrorBook/DropdownExport.tsx +++ b/src/pages/ErrorBook/DropdownExport.tsx @@ -1,14 +1,18 @@ +import { idDictionaryMap } from '@/resources/dictionary' +import { wordListFetcher } from '@/utils/wordListFetcher' import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import { saveAs } from 'file-saver' import type { FC } from 'react' +import { useState } from 'react' import * as XLSX from 'xlsx' type DropdownProps = { renderRecords: any - paraphrases: any } -const DropdownExport: FC = ({ renderRecords, paraphrases }) => { +const DropdownExport: FC = ({ renderRecords }) => { + const [isExporting, setIsExporting] = useState(false) + const formatTimestamp = (date: any) => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') // 月份从0开始 @@ -20,37 +24,77 @@ const DropdownExport: FC = ({ renderRecords, paraphrases }) => { return `${year}-${month}-${day} ${hours}-${minutes}-${seconds}` } - const handleExport = (bookType: string) => { - const ExportData: Array<{ 单词: string; 释义: string; 错误次数: number; 词典: string }> = [] + const handleExport = async (bookType: string) => { + setIsExporting(true) - renderRecords.forEach((item: any) => { - const word = paraphrases.find((w: any) => w.name === item.word) - ExportData.push({ - 单词: item.word, - 释义: word ? word.trans.join(';') : '', - 错误次数: item.wrongCount, - 词典: item.dict, + try { + // 获取所有需要的词典数据 + const dictUrls: string[] = [] + renderRecords.forEach((item: any) => { + const dictInfo = idDictionaryMap[item.dict] + if (dictInfo?.url && !dictUrls.includes(dictInfo.url)) { + dictUrls.push(dictInfo.url) + } }) - }) - let blob: Blob + // 并行获取所有词典数据 + const dictDataPromises = dictUrls.map(async (url) => { + try { + const data = await wordListFetcher(url) + return { url, data } + } catch (error) { + console.error(`Failed to fetch dictionary data from ${url}:`, error) + return { url, data: [] } + } + }) - if (bookType === 'txt') { - const content = ExportData.map((item: any) => `${item.单词}: ${item.释义}`).join('\n') - blob = new Blob([content], { type: 'text/plain' }) - } else { - const worksheet = XLSX.utils.json_to_sheet(ExportData) - const workbook = XLSX.utils.book_new() - XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') - const excelBuffer = XLSX.write(workbook, { bookType: bookType as XLSX.BookType, type: 'array' }) - blob = new Blob([excelBuffer], { type: 'application/octet-stream' }) - } + const dictDataResults = await Promise.all(dictDataPromises) + const dictDataMap = new Map(dictDataResults.map((result) => [result.url, result.data])) - const timestamp = formatTimestamp(new Date()) - const fileName = `ErrorBook_${timestamp}.${bookType}` + const ExportData: Array<{ 单词: string; 释义: string; 错误次数: number; 词典: string }> = [] - if (blob && fileName) { - saveAs(blob, fileName) + renderRecords.forEach((item: any) => { + const dictInfo = idDictionaryMap[item.dict] + let translation = '' + + if (dictInfo?.url && dictDataMap.has(dictInfo.url)) { + const wordList = dictDataMap.get(dictInfo.url) || [] + const word = wordList.find((w: any) => w.name === item.word) + translation = word ? word.trans.join(';') : '' + } + + ExportData.push({ + 单词: item.word, + 释义: translation, + 错误次数: item.wrongCount, + 词典: dictInfo?.name || item.dict, + }) + }) + + let blob: Blob + + if (bookType === 'txt') { + const content = ExportData.map((item: any) => `${item.单词}: ${item.释义}`).join('\n') + blob = new Blob([content], { type: 'text/plain' }) + } else { + const worksheet = XLSX.utils.json_to_sheet(ExportData) + const workbook = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') + const excelBuffer = XLSX.write(workbook, { bookType: bookType as XLSX.BookType, type: 'array' }) + blob = new Blob([excelBuffer], { type: 'application/octet-stream' }) + } + + const timestamp = formatTimestamp(new Date()) + const fileName = `ErrorBook_${timestamp}.${bookType}` + + if (blob && fileName) { + saveAs(blob, fileName) + } + } catch (error) { + console.error('Export failed:', error) + alert('导出失败,请重试') + } finally { + setIsExporting(false) } } @@ -58,18 +102,22 @@ const DropdownExport: FC = ({ renderRecords, paraphrases }) => {
- + handleExport('xlsx')} + disabled={isExporting} > .xlsx handleExport('csv')} + disabled={isExporting} > .csv diff --git a/src/pages/ErrorBook/ErrorRow.tsx b/src/pages/ErrorBook/ErrorRow.tsx index 4382702f..2c2707c8 100644 --- a/src/pages/ErrorBook/ErrorRow.tsx +++ b/src/pages/ErrorBook/ErrorRow.tsx @@ -7,34 +7,24 @@ import { idDictionaryMap } from '@/resources/dictionary' import { recordErrorBookAction } from '@/utils' import { useSetAtom } from 'jotai' import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useRef } from 'react' +import { useCallback } from 'react' import DeleteIcon from '~icons/weui/delete-filled' type IErrorRowProps = { record: groupedWordRecords onDelete: () => void - onWordUpdate: (word: any) => void } -const ErrorRow: FC = ({ record, onDelete, onWordUpdate }) => { +const ErrorRow: FC = ({ record, onDelete }) => { const setCurrentRowDetail = useSetAtom(currentRowDetailAtom) const dictInfo = idDictionaryMap[record.dict] const { word, isLoading, hasError } = useGetWord(record.word, dictInfo) - const prevWordRef = useRef() - const stableWord = useMemo(() => word, [word]) const onClick = useCallback(() => { setCurrentRowDetail(record) recordErrorBookAction('detail') }, [record, setCurrentRowDetail]) - useEffect(() => { - if (stableWord && stableWord !== prevWordRef.current) { - onWordUpdate(stableWord) - prevWordRef.current = stableWord - } - }, [stableWord, onWordUpdate]) - return (
  • ([]) const onBack = useCallback(() => { navigate('/') @@ -95,10 +94,6 @@ export function ErrorBook() { setReload((prev) => !prev) } - const handleWordUpdate = (paraphrases: object) => { - setParaphrases((prevWords) => [...prevWords, paraphrases]) - } - return ( <>
    @@ -114,7 +109,7 @@ export function ErrorBook() { 释义 词典 - +
    @@ -124,7 +119,6 @@ export function ErrorBook() { key={`${record.dict}-${record.word}`} record={record} onDelete={() => handleDelete(record.word, record.dict)} - onWordUpdate={handleWordUpdate} /> ))}