Merge branch 'main' of http://113.45.157.195:3003/linfeng/staff_data
This commit is contained in:
commit
c6b59483de
|
@ -11,12 +11,15 @@ export class ShareCodeService {
|
|||
// 生成8位分享码,使用易读的字符
|
||||
private readonly generateCode = customAlphabet(
|
||||
'23456789ABCDEFGHJKLMNPQRSTUVWXYZ',
|
||||
8
|
||||
8,
|
||||
);
|
||||
|
||||
constructor(private readonly resourceService: ResourceService) {}
|
||||
|
||||
async generateShareCode(fileId: string, fileName?: string): Promise<GenerateShareCodeResponse> {
|
||||
async generateShareCode(
|
||||
fileId: string,
|
||||
fileName?: string,
|
||||
): Promise<GenerateShareCodeResponse> {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
const resource = await this.resourceService.findUnique({
|
||||
|
@ -46,9 +49,7 @@ export class ShareCodeService {
|
|||
expiresAt,
|
||||
isUsed: false,
|
||||
// 只在没有现有文件名且提供了新文件名时才更新文件名
|
||||
...(fileName && !existingShareCode.fileName
|
||||
? { fileName }
|
||||
: {})
|
||||
...(fileName && !existingShareCode.fileName ? { fileName } : {}),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
@ -117,10 +118,7 @@ export class ShareCodeService {
|
|||
try {
|
||||
const result = await db.shareCode.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ expiresAt: { lt: new Date() } },
|
||||
{ isUsed: true },
|
||||
],
|
||||
OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -28,8 +28,22 @@ export default function DeptSettingPage() {
|
|||
}
|
||||
});
|
||||
|
||||
// 清除已上传文件
|
||||
const handleClearFile = () => {
|
||||
setUploadedFileId('');
|
||||
setUploadedFileName('');
|
||||
setUploadedFiles([]);
|
||||
setFileNameMap({});
|
||||
};
|
||||
|
||||
// 处理文件上传
|
||||
const handleFileSelect = async (file: File) => {
|
||||
// 限制:如果已有上传文件,则提示用户
|
||||
if (uploadedFiles.length > 0) {
|
||||
message.warning('只能上传一个文件,请先删除已上传的文件');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileKey = `file-${Date.now()}`; // 生成唯一的文件标识
|
||||
|
||||
handleFileUpload(
|
||||
|
@ -38,15 +52,13 @@ export default function DeptSettingPage() {
|
|||
setUploadedFileId(result.fileId);
|
||||
setUploadedFileName(result.fileName);
|
||||
|
||||
|
||||
// 添加到已上传文件列表
|
||||
setUploadedFiles(prev => [...prev, {id: result.fileId, name: file.name}]);
|
||||
setUploadedFiles([{ id: result.fileId, name: file.name }]);
|
||||
|
||||
// 在前端保存文件名映射(用于当前会话)
|
||||
setFileNameMap(prev => ({
|
||||
...prev,
|
||||
setFileNameMap({
|
||||
[result.fileId]: file.name
|
||||
}));
|
||||
});
|
||||
|
||||
// 上传成功后保存原始文件名到数据库
|
||||
try {
|
||||
|
@ -63,7 +75,6 @@ export default function DeptSettingPage() {
|
|||
}),
|
||||
});
|
||||
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('保存文件名响应:', response.status, responseText);
|
||||
|
||||
|
@ -87,11 +98,36 @@ export default function DeptSettingPage() {
|
|||
);
|
||||
};
|
||||
|
||||
// 处理多个文件上传
|
||||
// 处理多个文件上传 - 已移除
|
||||
// const handleFilesUpload = (file: File) => {
|
||||
// handleFileSelect(file);
|
||||
// };
|
||||
|
||||
// 处理文件删除
|
||||
const handleDeleteFile = async (fileId: string) => {
|
||||
try {
|
||||
// 可以添加删除文件的API调用
|
||||
// const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/delete/${fileId}`, {
|
||||
// method: 'DELETE'
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error('删除文件失败');
|
||||
// }
|
||||
|
||||
// 无论服务器删除是否成功,前端都需要更新状态
|
||||
setUploadedFiles([]);
|
||||
setUploadedFileId('');
|
||||
setUploadedFileName('');
|
||||
setFileNameMap({});
|
||||
|
||||
message.success('文件已删除');
|
||||
} catch (error) {
|
||||
console.error('删除文件错误:', error);
|
||||
message.error('删除文件失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 拖拽相关处理函数
|
||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
|
@ -168,6 +204,9 @@ export default function DeptSettingPage() {
|
|||
{/* 文件上传区域 */}
|
||||
<div style={{ marginBottom: '40px' }}>
|
||||
<h3>第一步:上传文件</h3>
|
||||
|
||||
{/* 如果没有已上传文件,显示上传区域 */}
|
||||
{uploadedFiles.length === 0 ? (
|
||||
<div
|
||||
ref={dropRef}
|
||||
onDragEnter={handleDragEnter}
|
||||
|
@ -186,20 +225,19 @@ export default function DeptSettingPage() {
|
|||
>
|
||||
<InboxOutlined style={{ fontSize: '48px', color: isDragging ? '#1890ff' : '#d9d9d9' }} />
|
||||
<p>点击或拖拽文件到此区域进行上传</p>
|
||||
<p style={{ fontSize: '12px', color: '#888' }}>支持单个上传文件</p>
|
||||
<p style={{ fontSize: '12px', color: '#888' }}>只能上传单个文件</p>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
id="file-input"
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files[0];
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
handleFileSelect(file);
|
||||
}
|
||||
}}
|
||||
disabled={isUploading}
|
||||
|
||||
/>
|
||||
<label
|
||||
htmlFor="file-input"
|
||||
|
@ -216,6 +254,21 @@ export default function DeptSettingPage() {
|
|||
<UploadOutlined /> 选择文件
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
backgroundColor: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '10px'
|
||||
}}>
|
||||
<p style={{ color: '#52c41a', margin: 0 }}>
|
||||
您已上传文件,请继续下一步生成分享码
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 已上传文件列表 */}
|
||||
{uploadedFiles.length > 0 && (
|
||||
|
@ -224,13 +277,12 @@ export default function DeptSettingPage() {
|
|||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{uploadedFiles.map((file, index) => (
|
||||
{uploadedFiles.map((file) => (
|
||||
<div key={file.id} style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '10px 15px',
|
||||
borderBottom: index < uploadedFiles.length - 1 ? '1px solid #f0f0f0' : 'none',
|
||||
backgroundColor: index % 2 === 0 ? '#fafafa' : 'white'
|
||||
backgroundColor: '#fafafa'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
|
@ -254,6 +306,8 @@ export default function DeptSettingPage() {
|
|||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined style={{ color: '#ff4d4f' }} />}
|
||||
onClick={() => handleDeleteFile(file.id)}
|
||||
title="删除此文件"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import { api, useStaff } from "@nice/client";
|
||||
import { Button, CascaderProps, message, Modal } from 'antd';
|
||||
import { Button, CascaderProps, message, Modal, Input, Upload } from 'antd';
|
||||
import { areaOptions } from '@web/src/app/main/staffinfo_write/area-options';
|
||||
import StaffInfoWrite from '@web/src/app/main/staffinfo_write/staffinfo_write.page';
|
||||
import { utils, writeFile, read } from 'xlsx';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
function getAreaName(codes: string[], level?: number): string {
|
||||
const result: string[] = [];
|
||||
|
@ -18,6 +20,24 @@ function getAreaName(codes: string[], level?: number): string {
|
|||
return level ? result[level - 1] || '' : result.join(' / ') || codes.join('/');
|
||||
}
|
||||
|
||||
// 添加表头提取工具函数
|
||||
function extractHeaders(columns: any[]): string[] {
|
||||
const result: string[] = [];
|
||||
|
||||
const extractHeadersRecursive = (cols: any[]) => {
|
||||
cols.forEach(col => {
|
||||
if ('children' in col && col.children) {
|
||||
extractHeadersRecursive(col.children);
|
||||
} else if (col.headerName) {
|
||||
result.push(col.headerName);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
extractHeadersRecursive(columns);
|
||||
return result;
|
||||
}
|
||||
|
||||
export default function StaffMessage() {
|
||||
const [rowData, setRowData] = useState<any[]>([]);
|
||||
const [columnDefs, setColumnDefs] = useState<any[]>([]);
|
||||
|
@ -26,6 +46,11 @@ export default function StaffMessage() {
|
|||
const fields = useCustomFields();
|
||||
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
|
||||
const [currentEditStaff, setCurrentEditStaff] = useState<any>(null);
|
||||
const [gridApi, setGridApi] = useState<any>(null);
|
||||
const [fileNameVisible, setFileNameVisible] = useState(false);
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`);
|
||||
const [importVisible, setImportVisible] = useState(false);
|
||||
|
||||
// 获取数据
|
||||
const { data: staffData } = api.staff.findMany.useQuery({
|
||||
|
@ -61,7 +86,6 @@ export default function StaffMessage() {
|
|||
setCurrentEditStaff(selectedRows[0]);
|
||||
setIsEditModalVisible(true);
|
||||
}, [selectedRows]);
|
||||
console.log('选中行',currentEditStaff);
|
||||
// 处理编辑完成
|
||||
const handleEditComplete = useCallback(() => {
|
||||
setIsEditModalVisible(false);
|
||||
|
@ -150,6 +174,241 @@ export default function StaffMessage() {
|
|||
staffData && setRowData(staffData);
|
||||
}, [staffData]);
|
||||
|
||||
// 修改导出模板处理函数
|
||||
const handleExportTemplate = useCallback(() => {
|
||||
const headerNames = extractHeaders(columnDefs);
|
||||
|
||||
// 创建示例数据行
|
||||
let exampleRow: Record<string, string> = {};
|
||||
|
||||
// 检查是否有选中行
|
||||
if (selectedRows.length > 0) {
|
||||
// 使用第一条选中的记录作为模板数据
|
||||
const templateData = selectedRows[0];
|
||||
|
||||
// 基础字段
|
||||
exampleRow['姓名'] = templateData.showname || '';
|
||||
exampleRow['所属部门'] = templateData.department?.name || '';
|
||||
|
||||
// 处理自定义字段
|
||||
const fieldsList = Array.isArray(fields?.data) ? fields.data : [];
|
||||
fieldsList.forEach((field: any) => {
|
||||
const fieldValue = templateData.fieldValues?.find(
|
||||
(fv: any) => fv.fieldId === field.id
|
||||
)?.value;
|
||||
|
||||
let displayValue = fieldValue;
|
||||
|
||||
// 根据字段类型处理值
|
||||
if (field.type === 'cascader' && fieldValue) {
|
||||
displayValue = getAreaName(fieldValue.split('/'));
|
||||
} else if (field.type === 'date' && fieldValue) {
|
||||
displayValue = new Date(fieldValue).toLocaleDateString();
|
||||
} else if (field.type === 'textarea' && fieldValue) {
|
||||
displayValue = fieldValue.replace(/,/g, '\n');
|
||||
}
|
||||
|
||||
exampleRow[field.label || field.name] = displayValue || '';
|
||||
});
|
||||
} else {
|
||||
// 如果没有选中行,使用默认示例数据
|
||||
exampleRow['姓名'] = '张三';
|
||||
exampleRow['所属部门'] = '技术部';
|
||||
|
||||
// 添加所有自定义字段的空值
|
||||
fieldsList.forEach((field: any) => {
|
||||
exampleRow[field.label || field.name] = '';
|
||||
});
|
||||
}
|
||||
|
||||
// 创建空白行供用户填写
|
||||
const emptyRow = headerNames.reduce((obj, header) => {
|
||||
obj[header] = '';
|
||||
return obj;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
// 创建工作簿和工作表
|
||||
const wb = utils.book_new();
|
||||
const ws = utils.json_to_sheet([exampleRow], { header: headerNames });
|
||||
|
||||
// 设置列宽
|
||||
const colWidth = headerNames.map(() => ({ wch: 20 }));
|
||||
ws['!cols'] = colWidth;
|
||||
|
||||
// 在第二行添加提示文字
|
||||
const rowIdx = 2; // 第二行索引
|
||||
const cellRef = utils.encode_cell({ r: rowIdx, c: 0 }); // A3单元格
|
||||
|
||||
const tipText = selectedRows.length > 0
|
||||
? '以上为选中人员数据,请在下方行填写实际数据'
|
||||
: '以上为示例数据,请在下方行填写实际数据';
|
||||
|
||||
ws[cellRef] = { t: 's', v: tipText };
|
||||
|
||||
// 手动添加空白行
|
||||
utils.sheet_add_json(ws, [emptyRow], { skipHeader: true, origin: rowIdx + 1 });
|
||||
|
||||
// 合并提示文字单元格
|
||||
if (!ws['!merges']) ws['!merges'] = [];
|
||||
ws['!merges'].push({
|
||||
s: { r: rowIdx, c: 0 }, // 起始单元格 A3
|
||||
e: { r: rowIdx, c: Math.min(5, headerNames.length - 1) } // 结束单元格,跨越多列
|
||||
});
|
||||
|
||||
utils.book_append_sheet(wb, ws, "员工模板");
|
||||
writeFile(wb, `员工数据模板_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
}, [columnDefs, selectedRows, fields.data]);
|
||||
|
||||
// 导出数据处理函数
|
||||
const handleConfirm = useCallback(() => {
|
||||
setFileNameVisible(true);
|
||||
}, []);
|
||||
|
||||
// 处理文件名确认
|
||||
const handleFileNameConfirm = useCallback(() => {
|
||||
setFileNameVisible(false);
|
||||
if (!gridApi) {
|
||||
console.error('Grid API 未正确初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取所有数据
|
||||
const allData = selectedRows.length > 0 ? selectedRows : rowData;
|
||||
|
||||
// 格式化数据
|
||||
const exportData = allData.map(row => {
|
||||
const formattedRow: Record<string, any> = {};
|
||||
|
||||
// 基础字段
|
||||
formattedRow['姓名'] = row.showname || '';
|
||||
formattedRow['所属部门'] = row.department?.name || '';
|
||||
|
||||
// 动态字段
|
||||
const fieldsList = Array.isArray(fields?.data) ? fields.data : [];
|
||||
fieldsList.forEach((field: any) => {
|
||||
const fieldValue = row.fieldValues?.find((fv: any) => fv.fieldId === field.id)?.value;
|
||||
let displayValue = fieldValue;
|
||||
|
||||
// 根据字段类型处理值
|
||||
if (field.type === 'cascader' && fieldValue) {
|
||||
displayValue = getAreaName(fieldValue.split('/'));
|
||||
} else if (field.type === 'date' && fieldValue) {
|
||||
displayValue = new Date(fieldValue).toLocaleDateString();
|
||||
} else if (field.type === 'textarea' && fieldValue) {
|
||||
displayValue = fieldValue.replace(/,/g, '\n');
|
||||
}
|
||||
|
||||
formattedRow[field.label || field.name] = displayValue || '';
|
||||
});
|
||||
|
||||
return formattedRow;
|
||||
});
|
||||
|
||||
// 生成工作表
|
||||
const ws = utils.json_to_sheet(exportData);
|
||||
const wb = utils.book_new();
|
||||
utils.book_append_sheet(wb, ws, "员工数据");
|
||||
|
||||
// 生成文件名
|
||||
const finalFileName = fileName || defaultFileName;
|
||||
writeFile(wb, `${finalFileName}.xlsx`);
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
message.error('导出失败,请稍后重试');
|
||||
}
|
||||
}, [fileName, defaultFileName, rowData, selectedRows, fields.data, gridApi]);
|
||||
|
||||
// 处理导入数据
|
||||
const createMany = api.staff.create.useMutation({
|
||||
onSuccess: () => {
|
||||
message.success('员工数据导入成功');
|
||||
// 刷新数据
|
||||
api.staff.findMany.useQuery();
|
||||
setImportVisible(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(`导入失败: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 处理Excel导入数据
|
||||
const handleImportData = useCallback((excelData: any[]) => {
|
||||
if (excelData.length === 0) {
|
||||
message.warning('没有可导入的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 将Excel数据转换为API需要的格式
|
||||
const staffData = excelData.map(row => {
|
||||
const staff: any = { fieldValues: [] };
|
||||
|
||||
// 处理基础字段
|
||||
if (row['姓名']) staff.showname = row['姓名'];
|
||||
|
||||
// 处理部门
|
||||
if (row['所属部门']) {
|
||||
// 简单存储部门名称,后续可能需要查询匹配
|
||||
staff.departmentName = row['所属部门'];
|
||||
}
|
||||
|
||||
// 处理自定义字段
|
||||
const fieldsList = Array.isArray(fields?.data) ? fields.data : [];
|
||||
fieldsList.forEach((field: any) => {
|
||||
let value = row[field.label || field.name];
|
||||
|
||||
// 跳过空值
|
||||
if (value === undefined || value === '') return;
|
||||
|
||||
// 根据字段类型处理输入值
|
||||
switch (field.type) {
|
||||
case 'cascader':
|
||||
// 级联选择器可能需要将显示名称转回代码
|
||||
// 这里简单保留原值,实际可能需要查询转换
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
// 尝试将日期字符串转换为ISO格式
|
||||
try {
|
||||
const dateObj = new Date(value);
|
||||
if (!isNaN(dateObj.getTime())) {
|
||||
value = dateObj.toISOString();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`日期格式转换错误: ${value}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'textarea':
|
||||
// 将换行符替换回逗号进行存储
|
||||
if (typeof value === 'string') {
|
||||
value = value.replace(/\n/g, ',');
|
||||
}
|
||||
break;
|
||||
|
||||
// 可以根据需要添加其他字段类型的处理
|
||||
}
|
||||
|
||||
// 添加到fieldValues数组
|
||||
staff.fieldValues.push({
|
||||
fieldId: field.id,
|
||||
value: String(value)
|
||||
});
|
||||
});
|
||||
|
||||
return staff;
|
||||
});
|
||||
|
||||
// 提交数据
|
||||
createMany.mutate({ data: staffData });
|
||||
message.info(`正在导入${staffData.length}条员工数据...`);
|
||||
} catch (error) {
|
||||
console.error('处理导入数据失败:', error);
|
||||
message.error('数据格式错误,导入失败');
|
||||
}
|
||||
}, [fields.data, createMany]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
|
@ -158,6 +417,15 @@ export default function StaffMessage() {
|
|||
onClick={handleDelete}>批量删除</Button>
|
||||
<Button className="bg-blue-500 hover:bg-blue-600 border-blue-500 text-white rounded-md px-4 py-2"
|
||||
onClick={handleEdit}>编辑</Button>
|
||||
<Button onClick={() => setImportVisible(true)} className="bg-orange-500 text-white px-4 py-2 rounded hover:bg-orange-600">
|
||||
导入Excel
|
||||
</Button>
|
||||
<Button onClick={handleExportTemplate} className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||
导出模板
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||
导出所有数据
|
||||
</Button>
|
||||
</div>
|
||||
<Button className="bg-gray-100 hover:bg-gray-200 border-gray-300 text-gray-700 rounded-md px-4 py-2"
|
||||
onClick={() => {
|
||||
|
@ -180,6 +448,7 @@ export default function StaffMessage() {
|
|||
headerHeight={40}
|
||||
rowHeight={40}
|
||||
domLayout="autoHeight"
|
||||
onGridReady={(params) => setGridApi(params.api)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -203,6 +472,61 @@ export default function StaffMessage() {
|
|||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 导出文件名对话框 */}
|
||||
<Modal
|
||||
title="输入文件名"
|
||||
open={fileNameVisible}
|
||||
onOk={handleFileNameConfirm}
|
||||
onCancel={() => setFileNameVisible(false)}
|
||||
okText="导出"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Input
|
||||
placeholder={`默认名称: ${defaultFileName}`}
|
||||
value={fileName}
|
||||
onChange={(e) => setFileName(e.target.value)}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 导入对话框 */}
|
||||
<Modal
|
||||
title="导入员工数据"
|
||||
open={importVisible}
|
||||
onCancel={() => setImportVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<Upload
|
||||
accept=".xlsx,.xls"
|
||||
beforeUpload={file => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const wb = read(e.target?.result);
|
||||
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
|
||||
|
||||
if (data.length === 0) {
|
||||
message.warning('Excel文件中没有数据');
|
||||
return;
|
||||
}
|
||||
|
||||
message.info(`读取到${data.length}条数据,正在处理...`);
|
||||
handleImportData(data);
|
||||
} catch (error) {
|
||||
console.error('解析Excel文件失败:', error);
|
||||
message.error('Excel文件格式错误,请确保使用正确的模板');
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>选择Excel文件</Button>
|
||||
</Upload>
|
||||
<div className="mt-4 text-gray-500 text-sm">
|
||||
提示:请使用导出模板功能获取标准模板,按格式填写数据后导入
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue