Merge branch 'main' of http://113.45.157.195:3003/linfeng/staff_data
This commit is contained in:
commit
fdb3e1a163
|
@ -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>
|
||||
|
|
|
@ -53,7 +53,6 @@ export default function DepartmentSelect({
|
|||
},
|
||||
[utils]
|
||||
);
|
||||
|
||||
const fetchDepts = useCallback(async () => {
|
||||
try {
|
||||
const rootDepts =
|
||||
|
|
Loading…
Reference in New Issue