This commit is contained in:
linfeng 2025-03-25 12:54:27 +08:00
commit fdb3e1a163
2 changed files with 160 additions and 13 deletions

View File

@ -7,33 +7,45 @@ import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { areaOptions } from '@web/src/app/main/staffinformation/area-options';
import type { CascaderProps } from 'antd/es/cascader';
import { utils, writeFile } from 'xlsx';
import { Modal, Input, Button, Switch } from 'antd';
import { utils, writeFile, read } from 'xlsx';
import { Modal, Input, Button, Switch, Upload, message } from 'antd';
import { api } from '@nice/client';
import DepartmentSelect from '@web/src/components/models/department/department-select';
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
import { UploadOutlined } from '@ant-design/icons';
// 修改函数类型定义
type ExcelColumn = { header: string; key: string };
function getAreaName(codes: string[], level?: number): string {
const result: string[] = [];
let currentLevel: CascaderProps['options'] = areaOptions;
for (const code of codes) {
const found = currentLevel?.find(opt => opt.value === code);
if (!found) break;
result.push(String(found.label));
currentLevel = found.children || [];
if (level && result.length >= level) break; // 添加层级控制
}
return level ? result[level - 1] || '' : result.join(' / ') || codes.join('/');
}
// 修改表头提取工具函数
function extractHeaders(columns: (ColDef | ColGroupDef)[]): string[] {
const result: string[] = [];
const extractHeadersRecursive = (cols: (ColDef | ColGroupDef)[]) => {
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 StaffTable() {
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
const { data: staffs, isLoading, refetch } = api.staff.findMany.useQuery({
where: { deletedAt: null },
include: { // 添加关联查询
department: true
@ -44,12 +56,12 @@ export default function StaffTable() {
const [fileName, setFileName] = useState('');
const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`);
const [paginationEnabled, setPaginationEnabled] = useState(true);
const [importVisible, setImportVisible] = useState(false);
const handleConfirm = async () => {
setFileNameVisible(true);
};
// 添加导出处理函数
const handleFileNameConfirm = () => {
setFileNameVisible(false);
@ -201,7 +213,7 @@ export default function StaffTable() {
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
},
{ field: 'proxyPosition', headerName: '代理职务', minWidth: 120 },
{field: 'post', headerName: '岗位', minWidth: 120}
{ field: 'post', headerName: '岗位', minWidth: 120 }
]
},
{
@ -338,6 +350,99 @@ export default function StaffTable() {
wrapText: true,
autoHeight: true
};
// 修改导出模板处理函数
const handleExportTemplate = () => {
const headerNames = extractHeaders(columnDefs);
// 创建一个对象,键为列名,值为空字符串
const templateRow = headerNames.reduce((obj, header) => {
obj[header] = '';
return obj;
}, {} as Record<string, string>);
// 创建工作簿和工作表
const wb = utils.book_new();
const ws = utils.json_to_sheet([templateRow], { header: headerNames });
// 设置列宽
const colWidth = headerNames.map(() => ({ wch: 20 }));
ws['!cols'] = colWidth;
utils.book_append_sheet(wb, ws, "员工模板");
writeFile(wb, `员工数据模板_${new Date().toISOString().slice(0, 10)}.xlsx`);
};
// 添加导入API钩子
const createManyMutation = api.staff.create.useMutation({
onSuccess: () => {
message.success('员工数据导入成功');
refetch(); // 刷新表格数据
setImportVisible(false);
},
onError: (error) => {
message.error(`导入失败: ${error.message}`);
}
});
// 处理Excel导入数据
const handleImportData = (excelData: any[]) => {
// 转换Excel数据为后端接受的格式
const staffData = excelData.map(row => {
// 创建一个标准的员工对象
const staff: any = {};
// 遍历列定义匹配Excel中的数据
columnDefs.forEach(colDef => {
if ('children' in colDef && colDef.children) {
colDef.children.forEach(childCol => {
if ('field' in childCol && childCol.headerName) {
// 使用表头名称查找Excel数据
const value = row[childCol.headerName];
if (value !== undefined) {
// 处理嵌套属性 (如 department.name)
if (childCol.field.includes('.')) {
const [parent, child] = childCol.field.split('.');
// 对于department.name特殊处理
if (parent === 'department' && child === 'name') {
// 仅存储部门名称,后续可处理
staff.departmentName = value;
}
} else {
// 性别特殊处理
if (childCol.field === 'sex') {
staff[childCol.field] = value === '男' ? true : value === '女' ? false : null;
} else {
staff[childCol.field] = value;
}
}
}
}
});
} else if ('field' in colDef && colDef.headerName) {
const value = row[colDef.headerName];
if (value !== undefined) {
staff[colDef.field] = value;
}
}
});
return staff;
});
// 调用后端API保存数据
if (staffData.length > 0) {
// 逐个创建员工记录
staffData.forEach(staff => {
createManyMutation.mutate({
data: staff // 直接传递正确格式的员工数据对象
});
});
message.success(`已提交${staffData.length}条员工数据导入请求`);
} else {
message.warning('没有可导入的有效数据');
}
};
return (
<div className="ag-theme-alpine w-full h-[calc(100vh-100px)]"
@ -355,6 +460,12 @@ export default function StaffTable() {
>
Excel
</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-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
</Button>
<Button
onClick={handleResetFilters}
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
@ -387,6 +498,43 @@ export default function StaffTable() {
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>
{isLoading ? (
<div className="h-full flex items-center justify-center">
<div className="text-gray-600 text-xl">...</div>

View File

@ -53,7 +53,6 @@ export default function DepartmentSelect({
},
[utils]
);
const fetchDepts = useCallback(async () => {
try {
const rootDepts =