"use client" import { userImport } from "@/components/label/api/user" import { Alert, Button, FileInput, Group, Modal, Paper, Stack, Text, } from "@mantine/core" import { notifications } from "@mantine/notifications" import { IconAlertCircle, IconDownload, IconUpload } from "@tabler/icons-react" import { useState } from "react" import * as XLSX from "xlsx-js-style" interface ComponentProps { opened: boolean onCloseAction: (refresh?: boolean) => void } type ImportItem = { user_name: string group_name: string base_city: string } export default function ImportModal({ opened, onCloseAction }: ComponentProps) { const [file, setFile] = useState(null) const [sheetList, setSheetList] = useState([]) const [failedDataTips, setFailedDataTips] = useState([]) const [submitting, setSubmitting] = useState(false) const clearState = () => { setFile(null) setSheetList([]) setFailedDataTips([]) } const parseFile = async (nextFile: File | null) => { setFile(nextFile) setSheetList([]) setFailedDataTips([]) if (!nextFile) return if (!nextFile.name.endsWith(".xlsx")) { notifications.show({ color: "red", title: "文件格式错误", message: "仅支持 .xlsx 文件", }) setFile(null) return } try { const data = await nextFile.arrayBuffer() const workbook = XLSX.read(data, { type: "array" }) const sheetName = workbook.SheetNames[0] const ws = workbook.Sheets[sheetName] const arr = XLSX.utils.sheet_to_json(ws, { header: 1, raw: false }) const tableData = arr.slice(4).filter((row) => row.length > 0) if (tableData.length === 0) { setSheetList([]) return } const checkList: Record = { 0: "用户名", 1: "组织", 2: "所在地", } const tipRows = 4 const errors: string[] = [] const list: ImportItem[] = tableData.map((item: any[], index: number) => { Object.entries(checkList).forEach(([key, fieldName]) => { const value = item[Number(key)] if (!value || (typeof value === "string" && !value.trim())) { errors.push( `第${index + 1 + tipRows}行数据缺少必填项【${fieldName}】或该必填项全为空格` ) } }) return { user_name: item[0] ? String(item[0]).trim() : "", group_name: item[1] ? String(item[1]).trim() : "", base_city: item[2] ? String(item[2]).trim() : "", } }) if (errors.length > 0) { setFailedDataTips(errors) setSheetList([]) return } setSheetList(list) } catch (e) { notifications.show({ color: "red", title: "解析失败", message: e instanceof Error ? e.message : "读取文件失败", }) setFile(null) } } const exportTemplateFile = () => { const tips = [ [`提示:\n1.下列字段均为必填项\n2.【组织】仅可以填入一个组织名称`], [""], [""], ] const col = [["用户名", "组织", "所在地"]] const wb = XLSX.utils.book_new() const ws = XLSX.utils.aoa_to_sheet([...tips, ...col]) XLSX.utils.book_append_sheet(wb, ws, "用户信息") wb.Sheets["用户信息"]["!rows"] = [{ hpx: 20 }, { hpx: 20 }, { hpx: 20 }] wb.Sheets["用户信息"]["!cols"] = [{ wch: 20 }, { wch: 20 }, { wch: 20 }] wb.Sheets["用户信息"]["!merges"] = [ { s: { c: 0, r: 0 }, e: { c: 2, r: 2 } }, ] if (wb.Sheets["用户信息"]["A1"]) { wb.Sheets["用户信息"]["A1"].s = { alignment: { wrapText: true } } } XLSX.writeFile(wb, "用户信息批量导入模板.xlsx") } const submit = async () => { if (sheetList.length === 0) { notifications.show({ color: "yellow", title: "无导入数据", message: "请先上传并校验 Excel 文件", }) return } try { setSubmitting(true) const res = await userImport({ user_infos: sheetList }) if (Array.isArray(res) && res.length > 0) { setFailedDataTips(res) notifications.show({ color: "red", title: "导入失败", message: "请根据错误提示修正后重试", }) return } notifications.show({ color: "green", title: "导入成功", message: "批量新增用户数据成功", }) clearState() onCloseAction(true) } catch (e) { notifications.show({ color: "red", title: "导入失败", message: e instanceof Error ? e.message : "请求失败", }) } finally { setSubmitting(false) } } return ( { clearState() onCloseAction() }} title="批量导入用户信息" centered size={760} closeOnClickOutside={false}> 点击下载 后,填写上传 } value={file} onChange={parseFile} clearable /> 待导入数据:{sheetList.length} 条 {failedDataTips.length > 0 ? ( } color="red" radius="sm" title="错误信息提示"> {failedDataTips.map((item) => ( {item} ))} ) : null} ) }