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 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||||
import { areaOptions } from '@web/src/app/main/staffinformation/area-options';
|
import { areaOptions } from '@web/src/app/main/staffinformation/area-options';
|
||||||
import type { CascaderProps } from 'antd/es/cascader';
|
import type { CascaderProps } from 'antd/es/cascader';
|
||||||
import { utils, writeFile } from 'xlsx';
|
import { utils, writeFile, read } from 'xlsx';
|
||||||
import { Modal, Input, Button, Switch } from 'antd';
|
import { Modal, Input, Button, Switch, Upload, message } from 'antd';
|
||||||
import { api } from '@nice/client';
|
import { api } from '@nice/client';
|
||||||
import DepartmentSelect from '@web/src/components/models/department/department-select';
|
import DepartmentSelect from '@web/src/components/models/department/department-select';
|
||||||
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
|
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 {
|
function getAreaName(codes: string[], level?: number): string {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
let currentLevel: CascaderProps['options'] = areaOptions;
|
let currentLevel: CascaderProps['options'] = areaOptions;
|
||||||
|
|
||||||
for (const code of codes) {
|
for (const code of codes) {
|
||||||
const found = currentLevel?.find(opt => opt.value === code);
|
const found = currentLevel?.find(opt => opt.value === code);
|
||||||
if (!found) break;
|
if (!found) break;
|
||||||
|
|
||||||
result.push(String(found.label));
|
result.push(String(found.label));
|
||||||
currentLevel = found.children || [];
|
currentLevel = found.children || [];
|
||||||
if (level && result.length >= level) break; // 添加层级控制
|
if (level && result.length >= level) break; // 添加层级控制
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return level ? result[level - 1] || '' : result.join(' / ') || codes.join('/');
|
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() {
|
export default function StaffTable() {
|
||||||
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
|
const { data: staffs, isLoading, refetch } = api.staff.findMany.useQuery({
|
||||||
where: { deletedAt: null },
|
where: { deletedAt: null },
|
||||||
include: { // 添加关联查询
|
include: { // 添加关联查询
|
||||||
department: true
|
department: true
|
||||||
|
@ -44,12 +56,12 @@ export default function StaffTable() {
|
||||||
const [fileName, setFileName] = useState('');
|
const [fileName, setFileName] = useState('');
|
||||||
const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`);
|
const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`);
|
||||||
const [paginationEnabled, setPaginationEnabled] = useState(true);
|
const [paginationEnabled, setPaginationEnabled] = useState(true);
|
||||||
|
const [importVisible, setImportVisible] = useState(false);
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
setFileNameVisible(true);
|
setFileNameVisible(true);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加导出处理函数
|
// 添加导出处理函数
|
||||||
const handleFileNameConfirm = () => {
|
const handleFileNameConfirm = () => {
|
||||||
setFileNameVisible(false);
|
setFileNameVisible(false);
|
||||||
|
@ -201,7 +213,7 @@ export default function StaffTable() {
|
||||||
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
||||||
},
|
},
|
||||||
{ field: 'proxyPosition', headerName: '代理职务', minWidth: 120 },
|
{ 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,
|
wrapText: true,
|
||||||
autoHeight: 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 (
|
return (
|
||||||
<div className="ag-theme-alpine w-full h-[calc(100vh-100px)]"
|
<div className="ag-theme-alpine w-full h-[calc(100vh-100px)]"
|
||||||
|
@ -355,6 +460,12 @@ export default function StaffTable() {
|
||||||
>
|
>
|
||||||
导出Excel
|
导出Excel
|
||||||
</Button>
|
</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
|
<Button
|
||||||
onClick={handleResetFilters}
|
onClick={handleResetFilters}
|
||||||
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
|
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)}
|
onChange={(e) => setFileName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</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 ? (
|
{isLoading ? (
|
||||||
<div className="h-full flex items-center justify-center">
|
<div className="h-full flex items-center justify-center">
|
||||||
<div className="text-gray-600 text-xl">加载中...</div>
|
<div className="text-gray-600 text-xl">加载中...</div>
|
||||||
|
|
|
@ -53,7 +53,6 @@ export default function DepartmentSelect({
|
||||||
},
|
},
|
||||||
[utils]
|
[utils]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchDepts = useCallback(async () => {
|
const fetchDepts = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const rootDepts =
|
const rootDepts =
|
||||||
|
|
Loading…
Reference in New Issue