解析 CSV 文件

304 阅读2分钟

解析 CSV 文件, 并解决对应中文编码问题

正确配置 encoding 值,即可解决文件编码问题

import * as jschardet from "jschardet";
import { parse } from "papaparse";

/**
 * 检查编码
 * @param base64Str Base64 字符串
 * @returns 编码类型
 */
const detectEncoding = (base64Str: string): string => {
	const str = atob(base64Str.split(";base64,")[1]);
	let encoding = jschardet.detect(str).encoding;
	if (encoding === "windows-1252") {
		encoding = "ANSI";
	}
	return encoding;
};

/**
 * 解析 CSV 文件
 * @param file CSV 文件
 * @returns 解析后的二维数组
 */
export const parseCsvFile = (file: File): Promise<any[][]> => {
	return new Promise((resolve, reject) => {
		const fReader = new FileReader();
		fReader.readAsDataURL(file);
		fReader.onload = (evt: ProgressEvent<FileReader>) => {
			if (evt.target?.result) {
				const data = evt.target.result as string;
				const encoding = detectEncoding(data);

				parse(file, {
					encoding: encoding,
					complete: (results: any) => {
						let res = results.data;
						if (res[res.length - 1] === "") {
							res.pop();
						}
						console.log("res", res);
						resolve(res);
					},
					error: (error: any) => {
						reject(error);
					},
				});
			} else {
				reject(new Error("文件读取失败"));
			}
		};
		fReader.onerror = (error) => {
			reject(error);
		};
	});
};

使用

import { Category } from "@/services/types/aiVideoCreation";
import { UploadOutlined } from "@ant-design/icons";
import { Button, Modal, Table, Upload } from "antd";
import React, { useEffect, useMemo, useState } from "react";
import { parseCsvFile } from "./tool";
import "./index.less"; 

interface CsvUploadModalProps {
	visible: boolean;
	categories: Category[];
	onOk: (data: any[]) => void;
	onCancel: () => void;
}

const CsvUploadModal: React.FC<CsvUploadModalProps> = ({
	visible,
	onOk,
	onCancel,
	categories,
}) => {
	const [csvData, setCsvData] = useState<any[]>([]);

	useEffect(() => {
		visible && setCsvData([]);
	}, [visible]);

	const handleUpload = (info: any) => {
		parseCsvFile(info.file)
			.then((res: any) => {
				console.log("我的数据", res);
				const parsedData = res
					?.slice(1)
					?.map((row: any) => {
						const [
							desc,
							category_name,
							sub_category_name,
							expect_num,
							cp_id,
							sync_title,
							folder_id,
							urls,
						] = row;
						return {
							desc,
							category_name,
							category2_name: sub_category_name,
							category_id: getCategoryIdByName(category_name) || "",
							category2_id:
								getCategoryIdByName(category_name, sub_category_name) || "",
							expect_num: Number(expect_num),
							cp_id: Number(cp_id) || 0,
							sync_title: sync_title || "",
							folder_id,
							urls: urls?.split("\n") || [],
						};
					});

				setCsvData(parsedData);
			})
			.catch((error) => {
				console.error("解析 CSV 文件时出错:", error);
			});
	};

	const columns = [
		{
			title: "任务描述",
			dataIndex: "desc",
			key: "desc",
			width: 250,
			render: (desc: string) => (
				<div>
					{desc?.split("\n")?.map((el: string, index: number) => (
						<div key={index}>{el}</div>
					))}
				</div>
			),
		},
		{
			title: "一级分类",
			dataIndex: "category_name",
			key: "category_name",
		},
		{
			title: "二级分类",
			dataIndex: "category2_name",
			key: "category2_name",
		},
		{
			title: "建议生成条数",
			dataIndex: "expect_num",
			key: "expect_num",
		},
		{
			title: "发布账号 ID",
			dataIndex: "cp_id",
			key: "cp_id",
		},
		{
			title: "同步标题",
			dataIndex: "sync_title",
			key: "sync_title",
			width: 200,
		},
		{
			title: "同步到视频库的位置",
			dataIndex: "folder_id",
			key: "folder_id",
		},
		{
			title: "素材地址(可多链接,换行有效)",
			dataIndex: "urls",
			key: "urls",
			width: 300,
			render: (urls: string[]) => (
				<div className="urls-container">
					{urls.map((url, index) => (
						<div>
							<a
								key={index}
								href={url}
								target="_blank"
								rel="noopener noreferrer"
							>
								{url}
							</a>
						</div>
					))}
				</div>
			),
		},
	];

	const dataSource = useMemo(() => {
		return csvData.map((item, index) => ({
			key: index,
			...item,
		}));
	}, [csvData]);

	return (
		<Modal
			title="上传 CSV 文件"
			visible={visible}
			centered
			className="csv-upload-modal"
			width="calc(100vw - 200px)"
			onCancel={onCancel}
			footer={
				<div className="csv-upload__btns">
					<Button
						key="download-template"
						type="link"
						href={""}
					>
						下载 CSV 模版
					</Button>
					<div>
						<Button key="cancel" onClick={onCancel}>
							取消
						</Button>
						<Button key="submit" type="primary" onClick={() => onOk(csvData)}>
							确认
						</Button>
					</div>
				</div>
			}
		>
			{csvData.length > 0 ? (
				<Table
					columns={columns}
					dataSource={dataSource}
					pagination={false}
					style={{ marginTop: 20, width: "100%" }}
					scroll={{ y: 300 }} // 固定表格高度
				/>
			) : (
				<Upload
					accept=".csv"
					showUploadList={false}
					beforeUpload={() => false}
					onChange={handleUpload}
					className="csv-upload__wrapper"
				>
					<Button  icon={<UploadOutlined />}>
						点击上传 CSV 文件
					</Button>
				</Upload>
			)}
		</Modal>
	);
};

export default CsvUploadModal;