732 lines
30 KiB
TypeScript
732 lines
30 KiB
TypeScript
'use client';
|
||
import { useState } from 'react';
|
||
import { AgGridReact } from '@ag-grid-community/react';
|
||
import { ColDef, ColGroupDef } from '@ag-grid-community/core';
|
||
import { SetFilterModule } from '@ag-grid-enterprise/set-filter';
|
||
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, read } from 'xlsx';
|
||
import { Modal, Input, Button, Switch, Upload, message } from 'antd';
|
||
import { api } from '@nice/client';
|
||
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, refetch } = api.staff.findMany.useQuery({
|
||
where: { deletedAt: null },
|
||
include: { // 添加关联查询
|
||
department: true
|
||
}
|
||
});
|
||
const [gridApi, setGridApi] = useState<any>(null); // 添加gridApi状态
|
||
const [fileNameVisible, setFileNameVisible] = useState(false);
|
||
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);
|
||
if (!gridApi || typeof gridApi.getRenderedNodes !== 'function') {
|
||
console.error('Grid API 未正确初始化');
|
||
return;
|
||
}
|
||
|
||
// 修改获取节点方式(使用更可靠的 getRenderedNodes)
|
||
const rowNodes = gridApi.getRenderedNodes();
|
||
|
||
// 获取所有列定义
|
||
const flattenColumns = (cols: any[]): any[] => cols.flatMap(col => col.children ? flattenColumns(col.children) : col);
|
||
const allColDefs = flattenColumns(gridApi.getColumnDefs());
|
||
|
||
// 获取数据(兼容分页状态)
|
||
|
||
// 处理数据格式
|
||
const processRowData = (node: any) => {
|
||
const row: Record<string, any> = {};
|
||
allColDefs.forEach((colDef: any) => {
|
||
if (!colDef.field || !colDef.headerName) return;
|
||
|
||
// 修改字段访问方式,支持嵌套对象
|
||
const value = colDef.field.includes('.')
|
||
? colDef.field.split('.').reduce((obj: any, key: string) => (obj || {})[key], node.data)
|
||
: node.data[colDef.field];
|
||
|
||
let renderedValue = value;
|
||
|
||
// 应用列格式化
|
||
if (colDef.valueFormatter) {
|
||
renderedValue = colDef.valueFormatter({ value });
|
||
}
|
||
|
||
// 处理特殊数据类型
|
||
if (colDef.cellRenderer) {
|
||
const renderResult = colDef.cellRenderer({ value });
|
||
if (typeof renderResult === 'string') {
|
||
renderedValue = renderResult;
|
||
} else if (renderResult?.props?.dangerouslySetInnerHTML?.__html) {
|
||
renderedValue = renderResult.props.dangerouslySetInnerHTML.__html.replace(/<br\s*\/?>/gi, '\n');
|
||
}
|
||
}
|
||
|
||
// 统一布尔值显示
|
||
if (typeof renderedValue === 'boolean') {
|
||
renderedValue = renderedValue ? '是' : '否';
|
||
}
|
||
|
||
// 日期字段处理
|
||
if (['hireDate', 'rankDate', 'seniority'].includes(colDef.field) && value) {
|
||
renderedValue = new Date(value).toLocaleDateString('zh-CN');
|
||
}
|
||
|
||
// 特别处理部门名称显示
|
||
if (colDef.field === 'department.name' && renderedValue === undefined) {
|
||
renderedValue = node.data.department?.name || '';
|
||
}
|
||
|
||
row[colDef.headerName] = renderedValue;
|
||
});
|
||
return row;
|
||
};
|
||
|
||
try {
|
||
// 生成工作表
|
||
const rowData = rowNodes.map(processRowData);
|
||
const ws = utils.json_to_sheet(rowData);
|
||
const wb = utils.book_new();
|
||
utils.book_append_sheet(wb, ws, "员工数据");
|
||
|
||
// 生成文件名
|
||
const finalFileName = fileName || `${defaultFileName}_${paginationEnabled ? '当前页' : '全部'}`;
|
||
writeFile(wb, `${finalFileName}.xlsx`);
|
||
} catch (error) {
|
||
console.error('导出失败:', error);
|
||
}
|
||
};
|
||
const handleResetFilters = () => {
|
||
if (gridApi) {
|
||
gridApi.setFilterModel(null);
|
||
gridApi.onFilterChanged(); // 触发筛选更新
|
||
}
|
||
};
|
||
|
||
const columnDefs: (ColDef | ColGroupDef)[] = [
|
||
{
|
||
field: 'username',
|
||
headerName: '姓名',
|
||
pinned: 'left',
|
||
// floatingFilter: true // 确保启用浮动过滤器
|
||
},
|
||
{
|
||
headerName: '个人基本信息',
|
||
children: [
|
||
{
|
||
field: 'idNumber',
|
||
headerName: '身份证号',
|
||
},
|
||
{
|
||
field: 'type',
|
||
headerName: '人员类型',
|
||
},
|
||
{ field: 'officerId', headerName: '警号'},
|
||
{ field: 'phoneNumber', headerName: '手机号' },
|
||
{ field: 'age', headerName: '年龄'},
|
||
{
|
||
field: 'sex', headerName: '性别',
|
||
cellRenderer: (params: any) => {
|
||
switch (params.value) {
|
||
case true:
|
||
return '男';
|
||
case false:
|
||
return '女';
|
||
default:
|
||
return '未知';
|
||
}
|
||
}
|
||
|
||
},
|
||
{ field: 'bloodType', headerName: '血型',},
|
||
{
|
||
field: 'birthplace',
|
||
headerName: '籍贯',
|
||
valueFormatter: (params) => params.value ? getAreaName(params.value.split('/')) : '',
|
||
},
|
||
{ field: 'source', headerName: '来源'},
|
||
]
|
||
},
|
||
{
|
||
headerName: '政治信息',
|
||
children: [
|
||
{ field: 'politicalStatus', headerName: '政治面貌', },
|
||
{ field: 'partyPosition', headerName: '党内职务', }
|
||
]
|
||
},
|
||
{
|
||
headerName: '职务信息',
|
||
children: [
|
||
{ field: 'department.name', headerName: '所属部门', },
|
||
{ field: 'rank', headerName: '衔职级别', },
|
||
{
|
||
field: 'rankDate', headerName: '衔职时间',
|
||
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
||
},
|
||
{ field: 'proxyPosition', headerName: '代理职务', },
|
||
{ field: 'post', headerName: '岗位', }
|
||
]
|
||
},
|
||
{
|
||
headerName: '入职信息',
|
||
children: [
|
||
{
|
||
field: 'hireDate', headerName: '入职时间',
|
||
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
||
},
|
||
{
|
||
field: 'seniority', headerName: '工龄认定时间',
|
||
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
||
},
|
||
{ field: 'sourceType', headerName: '来源类型', },
|
||
{
|
||
field: 'isReentry', headerName: '是否二次入职',
|
||
cellRenderer: (params: any) => params.value ? '是' : '否'
|
||
},
|
||
{
|
||
field: 'isExtended', headerName: '是否延期服役',
|
||
cellRenderer: (params: any) => params.value ? '是' : '否'
|
||
},
|
||
{
|
||
field: 'currentPositionDate', headerName: '现岗位开始时间',
|
||
valueFormatter: (params: any) => params.value ? new Date(params.value).toLocaleDateString() : ''
|
||
}
|
||
]
|
||
},
|
||
{
|
||
headerName: '教育背景',
|
||
children: [
|
||
{ field: 'education', headerName: '学历', },
|
||
{ field: 'educationType', headerName: '学历形式', },
|
||
{
|
||
field: 'isGraduated', headerName: '是否毕业',
|
||
cellRenderer: (params: any) => params.value ? '是' : '否'
|
||
},
|
||
{ field: 'major', headerName: '专业', },
|
||
{ field: 'foreignLang', headerName: '外语能力', }
|
||
]
|
||
},
|
||
{
|
||
headerName: '培训信息',
|
||
children: [
|
||
{ field: 'trainType', headerName: '培训类型', },
|
||
{ field: 'trainInstitute', headerName: '培训机构', },
|
||
{ field: 'trainMajor', headerName: '培训专业', },
|
||
{
|
||
field: 'hasTrain', headerName: '是否参加培训',
|
||
cellRenderer: (params: any) => params.value ? '是' : '否'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
headerName: '鉴定信息',
|
||
children: [
|
||
{ field: 'certRank', headerName: '鉴定等级', },
|
||
{ field: 'certWork', headerName: '鉴定工种', },
|
||
{
|
||
field: 'hasCert', headerName: '是否参加鉴定',
|
||
cellRenderer: (params: any) => params.value ? '是' : '否'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
headerName: '工作信息',
|
||
children: [
|
||
{
|
||
field: 'equipment',
|
||
headerName: '操作维护装备',
|
||
|
||
cellRenderer: (params: any) => (
|
||
<div
|
||
style={{ lineHeight: '24px' }}
|
||
dangerouslySetInnerHTML={{ __html: params.value?.replace(/,/g, '<br/>') || '' }}
|
||
/>
|
||
),
|
||
autoHeight: true
|
||
},
|
||
{
|
||
field: 'projects',
|
||
headerName: '演训任务经历',
|
||
|
||
cellRenderer: (params: any) => (
|
||
<div
|
||
style={{ lineHeight: '24px' }}
|
||
dangerouslySetInnerHTML={{ __html: params.value?.replace(/,/g, '<br/>') || '' }}
|
||
/>
|
||
),
|
||
autoHeight: true
|
||
},
|
||
// 修改剩余两个字段的cellRenderer为相同结构
|
||
{
|
||
field: 'awards',
|
||
headerName: '奖励信息',
|
||
|
||
cellRenderer: (params: any) => (
|
||
<div
|
||
style={{ lineHeight: '24px' }}
|
||
dangerouslySetInnerHTML={{ __html: params.value?.replace(/,/g, '<br/>') || '' }}
|
||
/>
|
||
),
|
||
autoHeight: true
|
||
},
|
||
{
|
||
field: 'punishments',
|
||
headerName: '处分信息',
|
||
|
||
cellRenderer: (params: any) => (
|
||
<div
|
||
style={{ lineHeight: '24px' }}
|
||
dangerouslySetInnerHTML={{ __html: params.value?.replace(/,/g, '<br/>') || '' }}
|
||
/>
|
||
),
|
||
autoHeight: true
|
||
}
|
||
]
|
||
}
|
||
];
|
||
|
||
|
||
const defaultColDef: ColDef = {
|
||
sortable: true,
|
||
filter: 'agSetColumnFilter',
|
||
resizable: false,
|
||
flex: 1,
|
||
minWidth: 200,
|
||
maxWidth: 600,
|
||
suppressMovable: true,
|
||
cellStyle: {
|
||
whiteSpace: 'normal',
|
||
overflowWrap: 'break-word'
|
||
},
|
||
wrapText: true,
|
||
autoHeight: true
|
||
};
|
||
// 修改导出模板处理函数
|
||
const handleExportTemplate = () => {
|
||
const headerNames = extractHeaders(columnDefs);
|
||
|
||
// 创建一个空白行对象,键为列名,值为空字符串
|
||
const emptyRow = headerNames.reduce((obj, header) => {
|
||
obj[header] = '';
|
||
return obj;
|
||
}, {} as Record<string, string>);
|
||
|
||
// 创建示例数据行
|
||
const exampleRow = headerNames.reduce((obj, header) => {
|
||
// 根据不同的表头设置不同的示例值
|
||
switch(header) {
|
||
// 基本信息示例
|
||
case '姓名': obj[header] = '张三'; break;
|
||
case '身份证号': obj[header] = '110101199001011234'; break;
|
||
case '人员类型': obj[header] = '在职'; break;
|
||
case '警号': obj[header] = '012345'; break;
|
||
case '手机号': obj[header] = '13800138000'; break;
|
||
case '年龄': obj[header] = '30'; break;
|
||
case '性别': obj[header] = '男'; break;
|
||
case '血型': obj[header] = 'A型'; break;
|
||
case '籍贯': obj[header] = '北京市海淀区'; break;
|
||
case '来源': obj[header] = '社会招聘'; break;
|
||
|
||
// 政治信息示例
|
||
case '政治面貌': obj[header] = '党员'; break;
|
||
case '党内职务': obj[header] = '支部书记'; break;
|
||
|
||
// 职务信息示例
|
||
case '所属部门': obj[header] = '技术部'; break;
|
||
case '衔职级别': obj[header] = '三级警司'; break;
|
||
case '衔职时间': obj[header] = '2020-01-01'; break;
|
||
case '代理职务': obj[header] = '技术组长'; break;
|
||
case '岗位': obj[header] = '技术开发'; break;
|
||
|
||
// 入职信息示例
|
||
case '入职时间': obj[header] = '2015-07-01'; break;
|
||
case '工龄认定时间': obj[header] = '2015-07-01'; break;
|
||
case '来源类型': obj[header] = '招聘'; break;
|
||
case '是否二次入职': obj[header] = '否'; break;
|
||
case '是否延期服役': obj[header] = '否'; break;
|
||
case '现岗位开始时间': obj[header] = '2018-05-01'; break;
|
||
|
||
// 教育背景示例
|
||
case '学历': obj[header] = '本科'; break;
|
||
case '学历形式': obj[header] = '全日制'; break;
|
||
case '是否毕业': obj[header] = '是'; break;
|
||
case '专业': obj[header] = '计算机科学与技术'; break;
|
||
case '外语能力': obj[header] = '英语四级'; break;
|
||
|
||
// 培训信息示例
|
||
case '培训类型': obj[header] = '专业技能'; break;
|
||
case '培训机构': obj[header] = '公安大学'; break;
|
||
case '培训专业': obj[header] = '网络安全'; break;
|
||
case '是否参加培训': obj[header] = '是'; break;
|
||
|
||
// 鉴定信息示例
|
||
case '鉴定等级': obj[header] = '高级'; break;
|
||
case '鉴定工种': obj[header] = '信息安全'; break;
|
||
case '是否参加鉴定': obj[header] = '是'; break;
|
||
|
||
// 工作信息示例
|
||
case '操作维护装备': obj[header] = '服务器,网络设备,安全设备'; break;
|
||
case '演训任务经历': obj[header] = '2019年网络安全演习,2020年数据恢复演练'; break;
|
||
case '奖励信息': obj[header] = '2018年度优秀员工,2020年技术创新奖'; break;
|
||
case '处分信息': obj[header] = ''; break;
|
||
|
||
default: obj[header] = `示例${header}`; break;
|
||
}
|
||
return obj;
|
||
}, {} as Record<string, string>);
|
||
|
||
// 创建工作簿和工作表,包含示例行和空白行
|
||
const wb = utils.book_new();
|
||
const ws = utils.json_to_sheet([exampleRow, emptyRow], { header: headerNames });
|
||
|
||
// 设置列宽
|
||
const colWidth = headerNames.map(() => ({ wch: 20 }));
|
||
ws['!cols'] = colWidth;
|
||
|
||
// 添加一些样式表示示例数据行
|
||
// XLSX.js 不直接支持样式,但我们可以添加注释
|
||
const note = { t: 's', v: '以上为示例数据,请在下方行填写实际数据' };
|
||
ws['A3'] = note;
|
||
|
||
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 {
|
||
// 根据字段类型进行处理
|
||
processFieldValue(staff, childCol.field, value);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
} else if ('field' in colDef && colDef.headerName) {
|
||
const value = row[colDef.headerName];
|
||
if (value !== undefined) {
|
||
processFieldValue(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('没有可导入的有效数据');
|
||
}
|
||
};
|
||
|
||
// 字段值处理函数
|
||
const processFieldValue = (staff: any, field: string, value: any) => {
|
||
// 跳过空值
|
||
if (value === null || value === undefined || value === '') {
|
||
return;
|
||
}
|
||
|
||
// 根据字段类型分别处理
|
||
switch (field) {
|
||
// 字符串字段 - 确保转为字符串
|
||
case 'idNumber':
|
||
case 'officerId':
|
||
case 'phoneNumber':
|
||
case 'username':
|
||
case 'password':
|
||
case 'showname':
|
||
case 'avatar':
|
||
case 'type':
|
||
case 'bloodType':
|
||
case 'birthplace':
|
||
case 'source':
|
||
case 'politicalStatus':
|
||
case 'partyPosition':
|
||
case 'rank':
|
||
case 'proxyPosition':
|
||
case 'post':
|
||
case 'sourceType':
|
||
case 'education':
|
||
case 'educationType':
|
||
case 'major':
|
||
case 'foreignLang':
|
||
case 'trainType':
|
||
case 'trainInstitute':
|
||
case 'trainMajor':
|
||
case 'certRank':
|
||
case 'certWork':
|
||
case 'equipment':
|
||
case 'projects':
|
||
case 'awards':
|
||
case 'punishments':
|
||
case 'domainId':
|
||
case 'deptId':
|
||
staff[field] = String(value);
|
||
break;
|
||
|
||
// 布尔字段 - 转为布尔值
|
||
case 'sex':
|
||
staff[field] = value === '男' ? true : value === '女' ? false : null;
|
||
break;
|
||
case 'enabled':
|
||
case 'isReentry':
|
||
case 'isExtended':
|
||
case 'isGraduated':
|
||
case 'hasTrain':
|
||
case 'hasCert':
|
||
if (typeof value === 'string') {
|
||
staff[field] = value === '是' || value === '√' || value === 'true' || value === '1';
|
||
} else {
|
||
staff[field] = Boolean(value);
|
||
}
|
||
break;
|
||
|
||
// 数值字段 - 转为数字
|
||
case 'age':
|
||
staff[field] = parseInt(value, 10);
|
||
break;
|
||
case 'order':
|
||
staff[field] = parseFloat(value);
|
||
break;
|
||
|
||
// 日期字段 - 转为日期格式
|
||
case 'rankDate':
|
||
case 'hireDate':
|
||
case 'seniority':
|
||
case 'currentPositionDate':
|
||
// 尝试将日期字符串转换为日期对象
|
||
try {
|
||
// Excel日期可能以不同格式导出
|
||
let dateValue = value;
|
||
|
||
// 如果是Excel序列号格式的日期
|
||
if (typeof value === 'number') {
|
||
// Excel日期是从1900年1月1日开始的天数
|
||
// 需要转换为JavaScript日期
|
||
const excelEpoch = new Date(1899, 11, 30);
|
||
dateValue = new Date(excelEpoch.getTime() + value * 86400000);
|
||
}
|
||
// 如果是字符串格式的日期
|
||
else if (typeof value === 'string') {
|
||
// 尝试解析常见日期格式
|
||
dateValue = new Date(value);
|
||
}
|
||
|
||
// 检查日期是否有效
|
||
if (dateValue instanceof Date && !isNaN(dateValue.getTime())) {
|
||
staff[field] = dateValue.toISOString();
|
||
} else {
|
||
console.warn(`无效的日期格式: ${field} = ${value}`);
|
||
}
|
||
} catch (e) {
|
||
console.error(`日期转换错误 (${field}): ${e}`);
|
||
}
|
||
break;
|
||
|
||
// 默认情况下保持原值
|
||
default:
|
||
staff[field] = value;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="ag-theme-alpine w-full h-[calc(100vh-100px)]"
|
||
style={{
|
||
width: '100%',
|
||
borderRadius: '12px',
|
||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||
}}
|
||
>
|
||
{!isLoading && (
|
||
<div className="flex items-center gap-4 mb-2">
|
||
<Button
|
||
onClick={handleConfirm}
|
||
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||
>
|
||
导出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"
|
||
>
|
||
重置筛选
|
||
</Button>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-gray-600">启用分页:</span>
|
||
<Switch
|
||
checked={paginationEnabled}
|
||
onChange={(checked) => setPaginationEnabled(checked)}
|
||
checkedChildren="开"
|
||
unCheckedChildren="关"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<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>
|
||
{isLoading ? (
|
||
<div className="h-full flex items-center justify-center">
|
||
<div className="text-gray-600 text-xl">加载中...</div>
|
||
</div>
|
||
) : (
|
||
<AgGridReact
|
||
modules={[SetFilterModule, ClientSideRowModelModule]}
|
||
onGridReady={(params) => setGridApi(params.api)} // 添加gridApi回调
|
||
rowData={staffs}
|
||
columnDefs={columnDefs}
|
||
defaultColDef={{
|
||
...defaultColDef,
|
||
filterParams: {
|
||
textCustomComparator: (_, value) => value !== '',
|
||
}
|
||
}}
|
||
enableCellTextSelection={true}
|
||
pagination={paginationEnabled}
|
||
paginationAutoPageSize={true}
|
||
cacheQuickFilter={true}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
} |