refactor(WpTool): 替换 CSV 解析为 XLSX 库以支持 Excel 文件

移除 papaparse 依赖,改用 xlsx 库处理文件上传和导出
支持 CSV 和 Excel 格式文件,提升用户兼容性
This commit is contained in:
tikkhun 2025-11-26 15:23:43 +08:00
parent 867b7f74d4
commit 4b01742dcd
2 changed files with 52 additions and 48 deletions

View File

@ -16,7 +16,6 @@
"@ant-design/icons": "^5.0.1", "@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.4.4", "@ant-design/pro-components": "^2.4.4",
"@fingerprintjs/fingerprintjs": "^4.6.2", "@fingerprintjs/fingerprintjs": "^4.6.2",
"@umijs/max": "^4.4.4", "@umijs/max": "^4.4.4",
"@umijs/max-plugin-openapi": "^2.0.3", "@umijs/max-plugin-openapi": "^2.0.3",
"@umijs/plugin-openapi": "^1.3.3", "@umijs/plugin-openapi": "^1.3.3",
@ -25,7 +24,6 @@
"echarts": "^5.6.0", "echarts": "^5.6.0",
"echarts-for-react": "^3.0.2", "echarts-for-react": "^3.0.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"papaparse": "^5.5.3",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"react-phone-input-2": "^2.15.1", "react-phone-input-2": "^2.15.1",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
@ -34,7 +32,6 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.33", "@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/papaparse": "^5.5.0",
"code-inspector-plugin": "^1.2.10", "code-inspector-plugin": "^1.2.10",
"husky": "^9", "husky": "^9",
"lint-staged": "^13.2.0", "lint-staged": "^13.2.0",

View File

@ -7,7 +7,7 @@ import {
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { Button, Card, Col, Input, message, Row, Upload } from 'antd'; import { Button, Card, Col, Input, message, Row, Upload } from 'antd';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import Papa from 'papaparse'; import * as XLSX from 'xlsx';
// 定义配置接口 // 定义配置接口
interface TagConfig { interface TagConfig {
@ -176,41 +176,53 @@ const WpToolPage: React.FC = () => {
* @param {File} uploadedFile - * @param {File} uploadedFile -
*/ */
const handleFileUpload = (uploadedFile: File) => { const handleFileUpload = (uploadedFile: File) => {
// 检查文件类型是否为 CSV // 检查文件类型,虽然 xlsx 库更宽容,但最好还是保留基本验证
if (uploadedFile.type !== 'text/csv') { if (!uploadedFile.name.match(/\.(csv|xlsx|xls)$/)) {
message.error('请上传 CSV 格式的文件!'); message.error('请上传 CSV 或 Excel 格式的文件!');
return false; return false;
} }
setFile(uploadedFile); setFile(uploadedFile);
// 使用 Papaparse 解析 CSV 文件
Papa.parse(uploadedFile, { const reader = new FileReader();
header: true, // 将第一行作为表头 reader.onload = (e) => {
skipEmptyLines: true, try {
// 简化配置,依赖解析器的自动检测能力,同时保持必要的兼容性 const data = e.target?.result;
quoteChar: '"', const workbook = XLSX.read(data, { type: 'binary' });
dynamicTyping: false, // 禁用动态类型转换 const sheetName = workbook.SheetNames[0];
relaxColumnCount: true, // 允许列数不匹配 const worksheet = workbook.Sheets[sheetName];
complete: (results) => { const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
// 如果解析过程中出现错误
if (results.errors.length > 0) { if (jsonData.length < 2) {
// 提取第一条错误信息用于展示 message.error('文件为空或缺少表头!');
const firstError = results.errors[0];
// 构造更详细的错误提示
const errorMsg = `CSV 解析失败 (行号: ${firstError.row}): ${firstError.message}`;
message.error(errorMsg);
console.error('CSV Parsing Errors:', results.errors);
setCsvData([]); setCsvData([]);
} else { return;
message.success(`成功解析 ${results.data.length} 条数据。`);
setCsvData(results.data);
setProcessedData([]); // 清空旧的处理结果
} }
},
error: (error) => { // 将数组转换为对象数组
message.error('文件读取失败!'); const headers = jsonData[0] as string[];
console.error('File Read Error:', error); const rows = jsonData.slice(1).map((rowArray: any) => {
}, const rowData: { [key: string]: any } = {};
}); headers.forEach((header, index) => {
rowData[header] = rowArray[index];
});
return rowData;
});
message.success(`成功解析 ${rows.length} 条数据。`);
setCsvData(rows);
setProcessedData([]); // 清空旧的处理结果
} catch (error) {
message.error('文件解析失败,请检查文件格式!');
console.error('File Parse Error:', error);
setCsvData([]);
}
};
reader.onerror = (error) => {
message.error('文件读取失败!');
console.error('File Read Error:', error);
};
reader.readAsBinaryString(uploadedFile);
return false; // 阻止 antd Upload 组件的默认上传行为 return false; // 阻止 antd Upload 组件的默认上传行为
}; };
@ -274,21 +286,16 @@ const WpToolPage: React.FC = () => {
return; return;
} }
// 使用 Papaparse 将 JSON 对象数组转换回 CSV 字符串 // 创建一个新的工作簿
const csvString = Papa.unparse(processedData); const workbook = XLSX.utils.book_new();
// 将 JSON 数据转换为工作表
const worksheet = XLSX.utils.json_to_sheet(processedData);
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(workbook, worksheet, 'Products with Tags');
// 创建 Blob 对象 // 生成文件名并触发下载
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); const fileName = `products_with_tags_${Date.now()}.xlsx`;
XLSX.writeFile(workbook, fileName);
// 创建一个临时的 a 标签用于下载
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `products_with_tags_${Date.now()}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
message.success('下载任务已开始!'); message.success('下载任务已开始!');
}; };