/** * 批量导入用户弹窗 */ import React, { useState } from 'react'; import { Modal, Upload, Button, Select, Alert, Table, Space, Typography, message, Divider, } from 'antd'; import { InboxOutlined, DownloadOutlined } from '@ant-design/icons'; import type { UploadFile } from 'antd/es/upload/interface'; import * as XLSX from 'xlsx'; import * as userApi from '../api/userApi'; import type { TenantOption, ImportUserRow, ImportResult } from '../types/user'; const { Dragger } = Upload; const { Text } = Typography; interface ImportUserModalProps { visible: boolean; onClose: () => void; onSuccess: () => void; tenantOptions: TenantOption[]; } const ImportUserModal: React.FC = ({ visible, onClose, onSuccess, tenantOptions, }) => { const [step, setStep] = useState<'upload' | 'preview' | 'result'>('upload'); const [fileList, setFileList] = useState([]); const [parsedData, setParsedData] = useState([]); const [defaultTenantId, setDefaultTenantId] = useState(); const [importing, setImporting] = useState(false); const [importResult, setImportResult] = useState(null); // 重置状态 const resetState = () => { setStep('upload'); setFileList([]); setParsedData([]); setDefaultTenantId(undefined); setImportResult(null); }; // 关闭弹窗 const handleClose = () => { resetState(); onClose(); }; // 解析Excel文件 const parseExcel = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const data = e.target?.result; const workbook = XLSX.read(data, { type: 'binary' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); // 映射列名 const rows: ImportUserRow[] = jsonData.map((row) => ({ phone: String(row['手机号'] || row['phone'] || '').trim(), name: String(row['姓名'] || row['name'] || '').trim(), email: row['邮箱'] || row['email'] || undefined, role: row['角色'] || row['role'] || undefined, tenantCode: row['租户代码'] || row['tenantCode'] || undefined, departmentName: row['科室'] || row['departmentName'] || undefined, modules: row['模块'] || row['modules'] || undefined, })); resolve(rows); } catch (error) { reject(error); } }; reader.onerror = reject; reader.readAsBinaryString(file); }); }; // 处理文件上传 const handleUpload = async (file: File) => { try { const rows = await parseExcel(file); if (rows.length === 0) { message.error('文件中没有数据'); return false; } setParsedData(rows); setStep('preview'); } catch (error) { message.error('解析文件失败'); } return false; }; // 执行导入 const handleImport = async () => { if (!defaultTenantId) { message.warning('请选择默认租户'); return; } setImporting(true); try { const result = await userApi.importUsers(parsedData, defaultTenantId); setImportResult(result); setStep('result'); if (result.success > 0) { onSuccess(); } } catch (error) { message.error('导入失败'); } finally { setImporting(false); } }; // 下载模板 const downloadTemplate = () => { const template = [ { '手机号': '13800138000', '姓名': '张三', '邮箱': 'zhangsan@example.com', '角色': 'USER', '租户代码': '', '科室': '', '模块': 'AIA,PKB', }, ]; const ws = XLSX.utils.json_to_sheet(template); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '用户导入模板'); XLSX.writeFile(wb, '用户导入模板.xlsx'); }; // 预览表格列 const previewColumns = [ { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, { title: '姓名', dataIndex: 'name', key: 'name', width: 100 }, { title: '邮箱', dataIndex: 'email', key: 'email', width: 180 }, { title: '角色', dataIndex: 'role', key: 'role', width: 100 }, { title: '租户代码', dataIndex: 'tenantCode', key: 'tenantCode', width: 100 }, { title: '科室', dataIndex: 'departmentName', key: 'departmentName', width: 100 }, { title: '模块', dataIndex: 'modules', key: 'modules', width: 120 }, ]; // 错误表格列 const errorColumns = [ { title: '行号', dataIndex: 'row', key: 'row', width: 80 }, { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, { title: '错误原因', dataIndex: 'error', key: 'error' }, ]; return ( {step === 'upload' && ( <>
  • 支持 .xlsx 或 .xls 格式的Excel文件
  • 必填字段:手机号、姓名
  • 角色可选值:SUPER_ADMIN、HOSPITAL_ADMIN、PHARMA_ADMIN、DEPARTMENT_ADMIN、USER
  • 模块字段使用逗号分隔,如:AIA,PKB,RVW
  • } type="info" showIcon style={{ marginBottom: 16 }} />
    setFileList(fileList)} maxCount={1} >

    点击或拖拽文件到此区域上传

    支持 .xlsx 或 .xls 格式

    )} {step === 'preview' && ( <>
    默认租户: