'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(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 = {}; 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(//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) => (
') || '' }} /> ), autoHeight: true }, { field: 'projects', headerName: '演训任务经历', cellRenderer: (params: any) => (
') || '' }} /> ), autoHeight: true }, // 修改剩余两个字段的cellRenderer为相同结构 { field: 'awards', headerName: '奖励信息', cellRenderer: (params: any) => (
') || '' }} /> ), autoHeight: true }, { field: 'punishments', headerName: '处分信息', cellRenderer: (params: any) => (
') || '' }} /> ), 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); // 创建示例数据行 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); // 创建工作簿和工作表,包含示例行和空白行 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 (
{!isLoading && (
启用分页: setPaginationEnabled(checked)} checkedChildren="开" unCheckedChildren="关" />
)} setFileNameVisible(false)} okText="导出" cancelText="取消" > setFileName(e.target.value)} /> setImportVisible(false)} footer={null} > { 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; }} >
提示:请使用导出模板功能获取标准模板,按格式填写数据后导入
{isLoading ? (
加载中...
) : ( setGridApi(params.api)} // 添加gridApi回调 rowData={staffs} columnDefs={columnDefs} defaultColDef={{ ...defaultColDef, filterParams: { textCustomComparator: (_, value) => value !== '', } }} enableCellTextSelection={true} pagination={paginationEnabled} paginationAutoPageSize={true} cacheQuickFilter={true} /> )}
); }