From 754900d0b8e3883912bb8d60d193edd711d6aa7e Mon Sep 17 00:00:00 2001 From: Li1304553726 <1304553726@qq.com> Date: Tue, 8 Apr 2025 16:48:40 +0800 Subject: [PATCH] add --- apps/server/.eslintrc.js | 14 +- apps/web/eslint.config.js | 74 +- .../main/staffinfo_show/staffmessage_page.tsx | 1048 +++++++++-------- packages/common/prisma/schema.prisma | 3 +- 4 files changed, 601 insertions(+), 538 deletions(-) diff --git a/apps/server/.eslintrc.js b/apps/server/.eslintrc.js index 259975e..79cf5b8 100755 --- a/apps/server/.eslintrc.js +++ b/apps/server/.eslintrc.js @@ -21,20 +21,22 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', + // 允许使用短路表达式 + 'no-unused-expressions': 'off', + // 允许使用 let 声明后不重新赋值的变量 + 'prefer-const': 'off', // 允许使用 any 类型 '@typescript-eslint/no-explicit-any': 'off', - // 允许声明但未使用的变量 '@typescript-eslint/no-unused-vars': [ 'warn', { - vars: 'all', // 检查所有变量 - args: 'none', // 不检查函数参数 + vars: 'all', + args: 'none', ignoreRestSiblings: true, }, ], - - // 禁止使用未声明的变量 - 'no-undef': 'error', + // 可选:关闭未定义变量检查 + 'no-undef': 'off', }, }; diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js index 592790b..884648c 100755 --- a/apps/web/eslint.config.js +++ b/apps/web/eslint.config.js @@ -5,39 +5,47 @@ import reactRefresh from "eslint-plugin-react-refresh"; import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ["dist"] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ["**/*.{ts,tsx}"], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - "react-hooks": reactHooks, - "react-refresh": reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - // 允许使用 any 类型 - "@typescript-eslint/no-explicit-any": "off", + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + // 允许使用 any 类型 + "@typescript-eslint/no-explicit-any": "off", + // 允许使用 let 声明后不重新赋值的变量 + "no-unused-expressions": "off", + // 允许使用 let 声明后不重新赋值的变量 + "prefer-const": "off", - // 允许声明但未使用的变量 - "@typescript-eslint/no-unused-vars": [ - "warn", - { - vars: "all", // 检查所有变量 - args: "none", // 不检查函数参数 - ignoreRestSiblings: true, - }, - ], + // 允许声明但未使用的变量 + "@typescript-eslint/no-unused-vars": [ + "warn", + { + vars: "all", // 检查所有变量 + args: "none", // 不检查函数参数 + ignoreRestSiblings: true, + }, + ], - // 禁止使用未声明的变量 - "no-undef": "error", - }, - } + // 禁止使用未声明的变量 + "no-undef": "error", + }, + } ); diff --git a/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx b/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx index 547d7bc..40e9317 100644 --- a/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx +++ b/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx @@ -1,532 +1,584 @@ -import { useState, useEffect, useMemo, useCallback } from 'react'; -import { AgGridReact } from 'ag-grid-react'; +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable no-unused-expressions */ +import { useState, useEffect, useMemo, useCallback } from "react"; +import { AgGridReact } from "ag-grid-react"; import { api, useStaff } from "@nice/client"; -import { Button, CascaderProps, message, Modal, Input, Upload } from 'antd'; -import { areaOptions } from '@web/src/app/main/staffinfo_write/area-options'; -import StaffInfoWrite from '@web/src/app/main/staffinfo_write/staffinfo_write.page'; -import { utils, writeFile, read } from 'xlsx'; -import { UploadOutlined } from '@ant-design/icons'; +import { Button, CascaderProps, message, Modal, Input, Upload } from "antd"; +import { areaOptions } from "@web/src/app/main/staffinfo_write/area-options"; +import StaffInfoWrite from "@web/src/app/main/staffinfo_write/staffinfo_write.page"; +import { utils, writeFile, read } from "xlsx"; +import { UploadOutlined } from "@ant-design/icons"; 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('/'); + 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: any[]): string[] { - const result: string[] = []; + const result: string[] = []; - const extractHeadersRecursive = (cols: any[]) => { - cols.forEach(col => { - if ('children' in col && col.children) { - extractHeadersRecursive(col.children); - } else if (col.headerName) { - result.push(col.headerName); - } - }); - }; + const extractHeadersRecursive = (cols: any[]) => { + cols.forEach((col) => { + if ("children" in col && col.children) { + extractHeadersRecursive(col.children); + } else if (col.headerName) { + result.push(col.headerName); + } + }); + }; - extractHeadersRecursive(columns); - return result; + extractHeadersRecursive(columns); + return result; } export default function StaffMessage() { - const [rowData, setRowData] = useState([]); - const [columnDefs, setColumnDefs] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const { useCustomFields, softDeleteByIds } = useStaff(); - const fields = useCustomFields(); - const [isEditModalVisible, setIsEditModalVisible] = useState(false); - const [currentEditStaff, setCurrentEditStaff] = useState(null); - const [gridApi, setGridApi] = useState(null); - const [fileNameVisible, setFileNameVisible] = useState(false); - const [fileName, setFileName] = useState(''); - const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`); - const [importVisible, setImportVisible] = useState(false); + const [rowData, setRowData] = useState([]); + const [columnDefs, setColumnDefs] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); + const { useCustomFields, softDeleteByIds } = useStaff(); + const fields = useCustomFields(); + const [isEditModalVisible, setIsEditModalVisible] = useState(false); + const [currentEditStaff, setCurrentEditStaff] = useState(null); + const [gridApi, setGridApi] = useState(null); + const [fileNameVisible, setFileNameVisible] = useState(false); + const [fileName, setFileName] = useState(""); + const [defaultFileName] = useState( + `员工数据_${new Date().toISOString().slice(0, 10)}` + ); + const [importVisible, setImportVisible] = useState(false); - // 获取数据 - const { data: staffData } = api.staff.findMany.useQuery({ - where: { - deletedAt: null - }, + // 获取数据 + const { data: staffData } = api.staff.findMany.useQuery({ + where: { + deletedAt: null, + }, + include: { + fieldValues: { include: { - fieldValues: { - include: { - // 添加这两个关联字段 - staff: { select: { id: true } }, // 关联员工ID - field: { select: { id: true } } // 关联字段ID - } - }, - department: true - } - } as any); - // console.log(staffData); - const actionColumns = [{ - field: "action", - width: 50, - checkboxSelection: true, - headerCheckboxSelection: true, - pinned: 'left' - }] - // 新增编辑处理函数 - const handleEdit = useCallback(async () => { - if (selectedRows.length === 0) return; - if (selectedRows.length > 1) { - message.error('只能选择一个员工进行编辑'); - return; - } - setCurrentEditStaff(selectedRows[0]); - setIsEditModalVisible(true); - }, [selectedRows]); - // 处理编辑完成 - const handleEditComplete = useCallback(() => { - setIsEditModalVisible(false); - setCurrentEditStaff(null); - // 刷新表格数据 - api.staff.findMany.useQuery(); - }, []); - - // 新增删除处理函数 - const handleDelete = useCallback(async () => { - if (selectedRows.length === 0) return; - console.log('待删除的选中行数据:', selectedRows); // 新增调试语句 - try { - await softDeleteByIds.mutateAsync({ - ids: selectedRows?.map(row => { - console.log('当前行ID:', row.id); // 检查每个ID - return row.id - }) - }); - message.success('删除成功'); - } catch (error) { - message.error('删除失败'); - console.error('详细错误信息:', error); // 输出完整错误堆栈 - } - // 重新获取数据或本地过滤已删除项 - }, [selectedRows]); - - // 缓存基础列定义 - const baseColumns = useMemo(() => [ - { - field: 'showname', - headerName: '姓名', - filter: 'agSetColumnFilter', - pinned: 'left' + // 添加这两个关联字段 + staff: { select: { id: true } }, // 关联员工ID + field: { select: { id: true } }, // 关联字段ID }, - { - field: 'deptId', - headerName: '所属部门', - valueGetter: params => params.data.department?.name, - filter: 'agSetColumnFilter' + }, + department: true, + }, + } as any); + // console.log(staffData); + const actionColumns = [ + { + field: "action", + width: 50, + checkboxSelection: true, + headerCheckboxSelection: true, + pinned: "left", + }, + ]; + // 新增编辑处理函数 + const handleEdit = useCallback(async () => { + if (selectedRows.length === 0) return; + if (selectedRows.length > 1) { + message.error("只能选择一个员工进行编辑"); + return; + } + setCurrentEditStaff(selectedRows[0]); + setIsEditModalVisible(true); + }, [selectedRows]); + // 处理编辑完成 + const handleEditComplete = useCallback(() => { + setIsEditModalVisible(false); + setCurrentEditStaff(null); + // 刷新表格数据 + api.staff.findMany.useQuery(); + }, []); + + // 新增删除处理函数 + const handleDelete = useCallback(async () => { + if (selectedRows.length === 0) return; + console.log("待删除的选中行数据:", selectedRows); // 新增调试语句 + try { + await softDeleteByIds.mutateAsync({ + ids: selectedRows?.map((row) => { + console.log("当前行ID:", row.id); // 检查每个ID + return row.id; + }), + }); + message.success("删除成功"); + } catch (error) { + message.error("删除失败"); + console.error("详细错误信息:", error); // 输出完整错误堆栈 + } + // 重新获取数据或本地过滤已删除项 + }, [selectedRows]); + + // 缓存基础列定义 + const baseColumns = useMemo( + () => [ + { + field: "showname", + headerName: "姓名", + filter: "agSetColumnFilter", + pinned: "left", + }, + { + field: "deptId", + headerName: "所属部门", + valueGetter: (params) => params.data.department?.name, + filter: "agSetColumnFilter", + }, + ], + [] + ); + + // 缓存动态列定义 + const dynamicColumns = useMemo( + () => + (fields.data || ([] as any)).map((field) => ({ + field: field.name, + headerName: field.label || field.name, + filter: "agSetColumnFilter", + cellStyle: { whiteSpace: "pre-line" }, + autoHeight: true, + valueGetter: (params) => { + // 获取原始值 + const rawValue = params.data.fieldValues?.find( + (fv: any) => fv.fieldId === field.id + )?.value; + + // 根据字段类型格式化 + switch (field.type) { + case "cascader": + return rawValue ? getAreaName(rawValue.split("/")) : ""; + + case "date": + // 格式化日期(假设存储的是ISO字符串) + return rawValue ? new Date(rawValue).toLocaleDateString() : ""; + + case "textarea": + // 换行处理 + + return rawValue?.replace(/,/g, "\n"); + + default: + return rawValue; + } + }, + })), + [fields.data] + ); + // 合并列定义 + useEffect(() => { + setColumnDefs([...actionColumns, ...baseColumns, ...dynamicColumns]); + }, [baseColumns, dynamicColumns]); + + // 更新行数据 + useEffect(() => { + staffData && setRowData(staffData); + }, [staffData]); + + // 修改导出模板处理函数 + const handleExportTemplate = useCallback(() => { + const headerNames = extractHeaders(columnDefs); + + // 创建示例数据行 + const exampleRow: Record = {}; + // 定义 fieldsList(移到这里) + const fieldsList = Array.isArray(fields?.data) ? fields.data : []; + + // 检查是否有选中行 + if (selectedRows.length > 0) { + // 使用第一条选中的记录作为模板数据 + const templateData = selectedRows[0]; + + // 基础字段 + exampleRow["姓名"] = templateData.showname || ""; + exampleRow["所属部门"] = templateData.department?.name || ""; + + // 处理自定义字段 + fieldsList.forEach((field: any) => { + const fieldValue = templateData.fieldValues?.find( + (fv: any) => fv.fieldId === field.id + )?.value; + + let displayValue = fieldValue; + + // 根据字段类型处理值 + if (field.type === "cascader" && fieldValue) { + displayValue = getAreaName(fieldValue.split("/")); + } else if (field.type === "date" && fieldValue) { + displayValue = new Date(fieldValue).toLocaleDateString(); + } else if (field.type === "textarea" && fieldValue) { + displayValue = fieldValue.replace(/,/g, "\n"); } - ], []); - // 缓存动态列定义 - const dynamicColumns = useMemo(() => - (fields.data || [] as any).map(field => ({ - field: field.name, - headerName: field.label || field.name, - filter: 'agSetColumnFilter', - cellStyle: { whiteSpace: 'pre-line' }, - autoHeight: true, - valueGetter: params => { - // 获取原始值 - const rawValue = params.data.fieldValues?.find( - (fv: any) => fv.fieldId === field.id - )?.value; + exampleRow[field.label || field.name] = displayValue || ""; + }); + } else { + // 如果没有选中行,使用默认示例数据 + exampleRow["姓名"] = "张三"; + exampleRow["所属部门"] = "技术部"; - // 根据字段类型格式化 - switch (field.type) { - case 'cascader': - return rawValue ? getAreaName(rawValue.split('/')) : ''; + // 添加所有自定义字段的空值 + fieldsList.forEach((field: any) => { + exampleRow[field.label || field.name] = ""; + }); + } - case 'date': - // 格式化日期(假设存储的是ISO字符串) - return rawValue ? new Date(rawValue).toLocaleDateString() : ''; - - case 'textarea': - // 换行处理 - - return rawValue?.replace(/,/g, '\n'); - - default: - return rawValue; - } - } - })), - [fields.data] + // 创建空白行供用户填写 + const emptyRow = headerNames.reduce( + (obj, header) => { + obj[header] = ""; + return obj; + }, + {} as Record ); - // 合并列定义 - useEffect(() => { - setColumnDefs([...actionColumns, ...baseColumns, ...dynamicColumns]); - }, [baseColumns, dynamicColumns]); - // 更新行数据 - useEffect(() => { - staffData && setRowData(staffData); - }, [staffData]); + // 创建工作簿和工作表 + const wb = utils.book_new(); + const ws = utils.json_to_sheet([exampleRow], { header: headerNames }); - // 修改导出模板处理函数 - const handleExportTemplate = useCallback(() => { - const headerNames = extractHeaders(columnDefs); + // 设置列宽 + const colWidth = headerNames.map(() => ({ wch: 20 })); + ws["!cols"] = colWidth; - // 创建示例数据行 - let exampleRow: Record = {}; + // 在第二行添加提示文字 + const rowIdx = 2; // 第二行索引 + const cellRef = utils.encode_cell({ r: rowIdx, c: 0 }); // A3单元格 - // 检查是否有选中行 - if (selectedRows.length > 0) { - // 使用第一条选中的记录作为模板数据 - const templateData = selectedRows[0]; + const tipText = + selectedRows.length > 0 + ? "以上为选中人员数据,请在下方行填写实际数据" + : "以上为示例数据,请在下方行填写实际数据"; - // 基础字段 - exampleRow['姓名'] = templateData.showname || ''; - exampleRow['所属部门'] = templateData.department?.name || ''; + ws[cellRef] = { t: "s", v: tipText }; - // 处理自定义字段 - const fieldsList = Array.isArray(fields?.data) ? fields.data : []; - fieldsList.forEach((field: any) => { - const fieldValue = templateData.fieldValues?.find( - (fv: any) => fv.fieldId === field.id - )?.value; - - let displayValue = fieldValue; - - // 根据字段类型处理值 - if (field.type === 'cascader' && fieldValue) { - displayValue = getAreaName(fieldValue.split('/')); - } else if (field.type === 'date' && fieldValue) { - displayValue = new Date(fieldValue).toLocaleDateString(); - } else if (field.type === 'textarea' && fieldValue) { - displayValue = fieldValue.replace(/,/g, '\n'); - } - - exampleRow[field.label || field.name] = displayValue || ''; - }); - } else { - // 如果没有选中行,使用默认示例数据 - exampleRow['姓名'] = '张三'; - exampleRow['所属部门'] = '技术部'; - - // 添加所有自定义字段的空值 - fieldsList.forEach((field: any) => { - exampleRow[field.label || field.name] = ''; - }); - } - - // 创建空白行供用户填写 - const emptyRow = headerNames.reduce((obj, header) => { - obj[header] = ''; - return obj; - }, {} as Record); - - // 创建工作簿和工作表 - const wb = utils.book_new(); - const ws = utils.json_to_sheet([exampleRow], { header: headerNames }); - - // 设置列宽 - const colWidth = headerNames.map(() => ({ wch: 20 })); - ws['!cols'] = colWidth; - - // 在第二行添加提示文字 - const rowIdx = 2; // 第二行索引 - const cellRef = utils.encode_cell({ r: rowIdx, c: 0 }); // A3单元格 - - const tipText = selectedRows.length > 0 - ? '以上为选中人员数据,请在下方行填写实际数据' - : '以上为示例数据,请在下方行填写实际数据'; - - ws[cellRef] = { t: 's', v: tipText }; - - // 手动添加空白行 - utils.sheet_add_json(ws, [emptyRow], { skipHeader: true, origin: rowIdx + 1 }); - - // 合并提示文字单元格 - if (!ws['!merges']) ws['!merges'] = []; - ws['!merges'].push({ - s: { r: rowIdx, c: 0 }, // 起始单元格 A3 - e: { r: rowIdx, c: Math.min(5, headerNames.length - 1) } // 结束单元格,跨越多列 - }); - - utils.book_append_sheet(wb, ws, "员工模板"); - writeFile(wb, `员工数据模板_${new Date().toISOString().slice(0, 10)}.xlsx`); - }, [columnDefs, selectedRows, fields.data]); - - // 导出数据处理函数 - const handleConfirm = useCallback(() => { - setFileNameVisible(true); - }, []); - - // 处理文件名确认 - const handleFileNameConfirm = useCallback(() => { - setFileNameVisible(false); - if (!gridApi) { - console.error('Grid API 未正确初始化'); - return; - } - - try { - // 获取所有数据 - const allData = selectedRows.length > 0 ? selectedRows : rowData; - - // 格式化数据 - const exportData = allData.map(row => { - const formattedRow: Record = {}; - - // 基础字段 - formattedRow['姓名'] = row.showname || ''; - formattedRow['所属部门'] = row.department?.name || ''; - - // 动态字段 - const fieldsList = Array.isArray(fields?.data) ? fields.data : []; - fieldsList.forEach((field: any) => { - const fieldValue = row.fieldValues?.find((fv: any) => fv.fieldId === field.id)?.value; - let displayValue = fieldValue; - - // 根据字段类型处理值 - if (field.type === 'cascader' && fieldValue) { - displayValue = getAreaName(fieldValue.split('/')); - } else if (field.type === 'date' && fieldValue) { - displayValue = new Date(fieldValue).toLocaleDateString(); - } else if (field.type === 'textarea' && fieldValue) { - displayValue = fieldValue.replace(/,/g, '\n'); - } - - formattedRow[field.label || field.name] = displayValue || ''; - }); - - return formattedRow; - }); - - // 生成工作表 - const ws = utils.json_to_sheet(exportData); - const wb = utils.book_new(); - utils.book_append_sheet(wb, ws, "员工数据"); - - // 生成文件名 - const finalFileName = fileName || defaultFileName; - writeFile(wb, `${finalFileName}.xlsx`); - } catch (error) { - console.error('导出失败:', error); - message.error('导出失败,请稍后重试'); - } - }, [fileName, defaultFileName, rowData, selectedRows, fields.data, gridApi]); - - // 处理导入数据 - const createMany = api.staff.create.useMutation({ - onSuccess: () => { - message.success('员工数据导入成功'); - // 刷新数据 - api.staff.findMany.useQuery(); - setImportVisible(false); - }, - onError: (error) => { - message.error(`导入失败: ${error.message}`); - } + // 手动添加空白行 + utils.sheet_add_json(ws, [emptyRow], { + skipHeader: true, + origin: rowIdx + 1, }); - // 处理Excel导入数据 - const handleImportData = useCallback((excelData: any[]) => { - if (excelData.length === 0) { - message.warning('没有可导入的数据'); - return; + // 合并提示文字单元格 + if (!ws["!merges"]) ws["!merges"] = []; + ws["!merges"].push({ + s: { r: rowIdx, c: 0 }, // 起始单元格 A3 + e: { r: rowIdx, c: Math.min(5, headerNames.length - 1) }, // 结束单元格,跨越多列 + }); + + utils.book_append_sheet(wb, ws, "员工模板"); + writeFile(wb, `员工数据模板_${new Date().toISOString().slice(0, 10)}.xlsx`); + }, [columnDefs, selectedRows, fields.data]); + + // 导出数据处理函数 + const handleConfirm = useCallback(() => { + setFileNameVisible(true); + }, []); + + // 处理文件名确认 + const handleFileNameConfirm = useCallback(() => { + setFileNameVisible(false); + if (!gridApi) { + console.error("Grid API 未正确初始化"); + return; + } + + try { + // 获取所有数据 + const allData = selectedRows.length > 0 ? selectedRows : rowData; + + // 格式化数据 + const exportData = allData.map((row) => { + const formattedRow: Record = {}; + + // 基础字段 + formattedRow["姓名"] = row.showname || ""; + formattedRow["所属部门"] = row.department?.name || ""; + + // 动态字段 + const fieldsList = Array.isArray(fields?.data) ? fields.data : []; + fieldsList.forEach((field: any) => { + const fieldValue = row.fieldValues?.find( + (fv: any) => fv.fieldId === field.id + )?.value; + let displayValue = fieldValue; + + // 根据字段类型处理值 + if (field.type === "cascader" && fieldValue) { + displayValue = getAreaName(fieldValue.split("/")); + } else if (field.type === "date" && fieldValue) { + displayValue = new Date(fieldValue).toLocaleDateString(); + } else if (field.type === "textarea" && fieldValue) { + displayValue = fieldValue.replace(/,/g, "\n"); + } + + formattedRow[field.label || field.name] = displayValue || ""; + }); + + return formattedRow; + }); + + // 生成工作表 + const ws = utils.json_to_sheet(exportData); + const wb = utils.book_new(); + utils.book_append_sheet(wb, ws, "员工数据"); + + // 生成文件名 + const finalFileName = fileName || defaultFileName; + writeFile(wb, `${finalFileName}.xlsx`); + } catch (error) { + console.error("导出失败:", error); + message.error("导出失败,请稍后重试"); + } + }, [fileName, defaultFileName, rowData, selectedRows, fields.data, gridApi]); + + // 处理导入数据 + const createMany = api.staff.create.useMutation({ + onSuccess: () => { + message.success("员工数据导入成功"); + // 刷新数据 + api.staff.findMany.useQuery(); + setImportVisible(false); + }, + onError: (error) => { + message.error(`导入失败: ${error.message}`); + }, + }); + + // 处理Excel导入数据 + const handleImportData = useCallback( + (excelData: any[]) => { + if (excelData.length === 0) { + message.warning("没有可导入的数据"); + return; + } + + try { + // 将Excel数据转换为API需要的格式 + const staffData = excelData.map((row) => { + const staff: any = { fieldValues: [] }; + + // 处理基础字段 + if (row["姓名"]) staff.showname = row["姓名"]; + + // 处理部门 + if (row["所属部门"]) { + // 简单存储部门名称,后续可能需要查询匹配 + staff.departmentName = row["所属部门"]; + } + + // 处理自定义字段 + const fieldsList = Array.isArray(fields?.data) ? fields.data : []; + fieldsList.forEach((field: any) => { + let value = row[field.label || field.name]; + + // 跳过空值 + if (value === undefined || value === "") return; + + // 根据字段类型处理输入值 + switch (field.type) { + case "cascader": + // 级联选择器可能需要将显示名称转回代码 + // 这里简单保留原值,实际可能需要查询转换 + break; + + case "date": + // 尝试将日期字符串转换为ISO格式 + try { + const dateObj = new Date(value); + if (!isNaN(dateObj.getTime())) { + value = dateObj.toISOString(); + } + } catch (e) { + console.error(`日期格式转换错误: ${value}`); + } + break; + + case "textarea": + // 将换行符替换回逗号进行存储 + if (typeof value === "string") { + value = value.replace(/\n/g, ","); + } + break; + + // 可以根据需要添加其他字段类型的处理 + } + + // 添加到fieldValues数组 + staff.fieldValues.push({ + fieldId: field.id, + value: String(value), + }); + }); + + return staff; + }); + + // 提交数据 + createMany.mutate({ + data: staffData[0], // 由于类型限制,这里只能一条一条导入 + }); + // 如果有多条数据,需要循环处理 + for (let i = 1; i < staffData.length; i++) { + createMany.mutate({ data: staffData[i] }); } + message.info(`正在导入${staffData.length}条员工数据...`); + } catch (error) { + console.error("处理导入数据失败:", error); + message.error("数据格式错误,导入失败"); + } + }, + [fields.data, createMany] + ); - try { - // 将Excel数据转换为API需要的格式 - const staffData = excelData.map(row => { - const staff: any = { fieldValues: [] }; + return ( + <> +
+
+ + + + + +
+ +
- // 处理基础字段 - if (row['姓名']) staff.showname = row['姓名']; +
+

人员总览

- // 处理部门 - if (row['所属部门']) { - // 简单存储部门名称,后续可能需要查询匹配 - staff.departmentName = row['所属部门']; +
+ setSelectedRows(e.api.getSelectedRows())} + rowSelection="multiple" + className="rounded border border-gray-200" + headerHeight={40} + rowHeight={40} + domLayout="autoHeight" + onGridReady={(params) => setGridApi(params.api)} + /> +
+
+ + {/* 编辑弹窗 */} + setIsEditModalVisible(false)} + footer={null} + width={1000} + destroyOnClose={true} + > + {currentEditStaff && ( + + )} + + + {/* 导出文件名对话框 */} + 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; } - // 处理自定义字段 - const fieldsList = Array.isArray(fields?.data) ? fields.data : []; - fieldsList.forEach((field: any) => { - let value = row[field.label || field.name]; - - // 跳过空值 - if (value === undefined || value === '') return; - - // 根据字段类型处理输入值 - switch (field.type) { - case 'cascader': - // 级联选择器可能需要将显示名称转回代码 - // 这里简单保留原值,实际可能需要查询转换 - break; - - case 'date': - // 尝试将日期字符串转换为ISO格式 - try { - const dateObj = new Date(value); - if (!isNaN(dateObj.getTime())) { - value = dateObj.toISOString(); - } - } catch (e) { - console.error(`日期格式转换错误: ${value}`); - } - break; - - case 'textarea': - // 将换行符替换回逗号进行存储 - if (typeof value === 'string') { - value = value.replace(/\n/g, ','); - } - break; - - // 可以根据需要添加其他字段类型的处理 - } - - // 添加到fieldValues数组 - staff.fieldValues.push({ - fieldId: field.id, - value: String(value) - }); - }); - - return staff; - }); - - // 提交数据 - createMany.mutate({ data: staffData }); - message.info(`正在导入${staffData.length}条员工数据...`); - } catch (error) { - console.error('处理导入数据失败:', error); - message.error('数据格式错误,导入失败'); - } - }, [fields.data, createMany]); - - return ( - <> -
-
- - - - - -
- -
- -
-

人员总览

- -
- setSelectedRows(e.api.getSelectedRows())} - rowSelection="multiple" - className="rounded border border-gray-200" - headerHeight={40} - rowHeight={40} - domLayout="autoHeight" - onGridReady={(params) => setGridApi(params.api)} - /> -
-
- - {/* 编辑弹窗 */} - setIsEditModalVisible(false)} - footer={null} - width={1000} - destroyOnClose={true} - > - {currentEditStaff && ( - - )} - - - {/* 导出文件名对话框 */} - 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; - }} - > - - -
- 提示:请使用导出模板功能获取标准模板,按格式填写数据后导入 -
-
- - ); -} \ No newline at end of file + message.info(`读取到${data.length}条数据,正在处理...`); + handleImportData(data); + } catch (error) { + console.error("解析Excel文件失败:", error); + message.error("Excel文件格式错误,请确保使用正确的模板"); + } + }; + reader.readAsArrayBuffer(file); + return false; + }} + > + +
+
+ 提示:请使用导出模板功能获取标准模板,按格式填写数据后导入 +
+
+ + ); +} diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index 58b41e7..480cf19 100755 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -466,7 +466,8 @@ model Staff { enabled Boolean? @default(true) officerId String? @map("officer_id") phoneNumber String? @map("phone_number") - + age Int?@map("age") + sex String?@map("sex") // 部门关系 domainId String? @map("domain_id") deptId String? @map("dept_id")