This commit is contained in:
linfeng 2025-03-20 23:09:41 +08:00
parent e290a9d51c
commit 305ecf78ed
6 changed files with 153 additions and 130 deletions

View File

@ -0,0 +1,7 @@
export default function DeptSettingPage() {
return (
<div>
<h1></h1>
</div>
);
}

View File

@ -44,6 +44,15 @@ const items = [
null,
null,
),
getItem(
"系统设置",
"/admin",
<i className="iconfont icon-icon-user" />,
[
getItem("部门设置", "/admin/department", null, null, null),
],
null,
),
getItem(
"训练计划",
"/plan",
@ -83,7 +92,8 @@ const NavigationMenu: React.FC = () => {
// 添加考核成绩子路径的匹配规则
"^/assessment/positionassessment": ["/assessment"],
"^/assessment/commonassessment": ["/assessment"],
"^/assessment/sportsassessment": ["/assessment"]
"^/assessment/sportsassessment": ["/assessment"],
"^/admin/department": ["/admin"],
};
// 选中的菜单
@ -109,7 +119,10 @@ const NavigationMenu: React.FC = () => {
setOpenKeys(["/staff"]);
} else if (path.startsWith("/assessment/") || path.startsWith("/plan/")) {
setOpenKeys([path.split('/').slice(0, 2).join('/')]);
} else {
} else if(path.startsWith("/admin/")){
setOpenKeys(["/admin"]);
}
else {
setOpenKeys(openKeyMerge(path));
}

View File

@ -1,6 +1,6 @@
"use client";
import { Button, Form, Input, Select, DatePicker, Radio, message, Modal, Cascader } from "antd";
import { Button, Form, Input, Select, DatePicker, Radio, message, Modal, Cascader, InputNumber } from "antd";
import { useState } from "react";
import dayjs from "dayjs";
import { useStaff } from "@nice/client";
@ -144,7 +144,7 @@ const StaffInformation = () => {
</Radio.Group>
</Form.Item>
<Form.Item label='年龄' name='age'>
<Input />
<InputNumber />
</Form.Item>
<Form.Item label="血型" name="bloodType">
<Select>
@ -218,7 +218,9 @@ const StaffInformation = () => {
<Form.Item label="代理职务" name="proxyPosition">
<Input />
</Form.Item>
<Form.Item label="岗位" name="post">
<Input />
</Form.Item>
</div>
</div>

View File

@ -8,10 +8,10 @@ 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 } from 'antd';
import { Modal, Input, Button, Switch } from 'antd';
import { api } from '@nice/client';
import { StaffDto } from 'packages/common/dist';
import DepartmentSelect from '@web/src/components/models/department/department-select';
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
// 修改函数类型定义
@ -40,107 +40,100 @@ export default function StaffTable() {
}
});
const [gridApi, setGridApi] = useState<any>(null); // 添加gridApi状态
const [confirmVisible, setConfirmVisible] = useState(false);
const [fileNameVisible, setFileNameVisible] = useState(false);
const [fileName, setFileName] = useState('');
const [defaultFileName] = useState(`员工数据_${new Date().toISOString().slice(0, 10)}`);
const [exporting, setExporting] = useState(false);
const [selectedDepartment, setSelectedDepartment] = useState('');
const [tempExportData, setTempExportData] = useState<any | null>(null);
const [exportScope, setExportScope] = useState<'current' | 'department'>('current');
const handleExport = async () => {
setConfirmVisible(true);
};
const [paginationEnabled, setPaginationEnabled] = useState(true);
const handleConfirm = async () => {
setConfirmVisible(false);
setExporting(true);
if (exportScope === 'current') {
setExporting(false);
try {
const allStaffs = await api.staff.findMany.useQuery({
where: {
deletedAt: null,
department: { name: selectedDepartment } // 添加部门过滤条件
},
include: { department: true }
});
setTempExportData(allStaffs);
} finally {
setExporting(false);
}
}
setFileNameVisible(true);
};
// 添加导出处理函数
const handleFileNameConfirm = () => {
setFileNameVisible(false)
if (!tempExportData) return;
if (!gridApi) return;
const finalFileName = fileName || defaultFileName;
const flattenColumns = (cols: any[]): any[] => {
return cols.flatMap(col =>
col.children ? flattenColumns(col.children) : col
);
};
const allColDefs = flattenColumns(gridApi.getColumnDefs());
let rowData;
if (exportScope === 'current') {
rowData = gridApi.getDisplayedRowNodes().map((node: any) => node.data) || [];
} else {
rowData = tempExportData || [];
setFileNameVisible(false);
if (!gridApi || typeof gridApi.getRenderedNodes !== 'function') {
console.error('Grid API 未正确初始化');
return;
}
rowData.map((node: any) => {
// 修改获取节点方式(使用更可靠的 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) => {
const field = colDef.field;
if (field) {
const value = node.data[field];
const formatter = colDef.valueFormatter;
const renderer = colDef.cellRenderer;
if (!colDef.field || !colDef.headerName) return;
let renderedValue = value;
if (formatter) {
renderedValue = formatter({ value });
} else if (renderer) {
const renderResult = renderer({ value });
// 改进渲染结果处理
if (typeof renderResult === 'string') {
renderedValue = renderResult;
} else if (renderResult?.props?.children) { // 处理React元素文本内容
renderedValue = String(renderResult.props.children);
} else if (renderResult?.props?.dangerouslySetInnerHTML?.__html) {
const html = renderResult.props.dangerouslySetInnerHTML.__html;
renderedValue = html.replace(/<br\s*\/?>/gi, '\n');
}
}
// 修改字段访问方式,支持嵌套对象
const value = colDef.field.includes('.')
? colDef.field.split('.').reduce((obj: any, key: string) => (obj || {})[key], node.data)
: node.data[colDef.field];
// 增强布尔值处理逻辑
if (typeof renderedValue === 'boolean' ||
(typeof renderedValue === 'string' && ['true', 'false'].includes(renderedValue.toLowerCase()))) {
const boolValue = typeof renderedValue === 'boolean' ? renderedValue : renderedValue.toLowerCase() === 'true';
renderedValue = boolValue ? '是' : '否';
}
let renderedValue = value;
row[colDef.headerName] = renderedValue;
// 应用列格式化
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;
});
};
// 创建工作表并导出
const ws = utils.json_to_sheet(rowData);
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Sheet1");
// 修改导出文件名生成方式
writeFile(wb, `${fileName || '未命名数据'}.xlsx`);
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)[] = [
@ -169,7 +162,17 @@ export default function StaffTable() {
{ field: 'age', headerName: '年龄', minWidth: 80 },
{
field: 'sex', headerName: '性别', minWidth: 80,
cellRenderer: (params: any) => params.value ? '男' : '女'
cellRenderer: (params: any) => {
switch (params.value) {
case true:
return '男';
case false:
return '女';
default:
return '未知';
}
}
},
{ field: 'bloodType', headerName: '血型', minWidth: 80 },
{
@ -197,7 +200,8 @@ export default function StaffTable() {
field: 'rankDate', headerName: '衔职时间', minWidth: 120,
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}
]
},
{
@ -344,45 +348,30 @@ export default function StaffTable() {
}}
>
{!isLoading && (
<Button
onClick={handleExport}
className="mb-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Excel
</Button>
)}
<Modal
title="确认导出"
open={confirmVisible}
onOk={handleConfirm}
onCancel={() => setConfirmVisible(false)}
okText="确认"
cancelText="取消"
>
<div className="mb-4">
<span></span>
<select
value={exportScope}
onChange={(e) => setExportScope(e.target.value as any)}
className="ml-2 p-1 border rounded"
<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"
>
<option value="current"></option>
<option value="department"></option>
</select>
</div>
{exportScope === 'department' && (
<div>
<p></p>
<DepartmentSelect
value={selectedDepartment}
// onChange={(value) => setSelectedDepartment(value)}
placeholder="请选择部门"
Excel
</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>
)}
</Modal>
</div>
)}
<Modal
title="输入文件名"
@ -404,7 +393,7 @@ export default function StaffTable() {
</div>
) : (
<AgGridReact
modules={[SetFilterModule]}
modules={[SetFilterModule, ClientSideRowModelModule]}
onGridReady={(params) => setGridApi(params.api)} // 添加gridApi回调
rowData={staffs}
columnDefs={columnDefs}
@ -415,7 +404,7 @@ export default function StaffTable() {
}
}}
enableCellTextSelection={true}
pagination={true}
pagination={paginationEnabled}
paginationAutoPageSize={true}
cacheQuickFilter={true}
/>

View File

@ -14,6 +14,8 @@ import DailyPage from "../app/main/daily/page";
import Dashboard from "../app/main/home/page";
import WeekPlanPage from "../app/main/plan/weekplan/page";
import StaffInformation from "../app/main/staffinformation/page";
import SettingPage from "../app/main/admin/deptsettingpage/settingpage";
import DeptSettingPage from "../app/main/admin/deptsettingpage/settingpage";
interface CustomIndexRouteObject extends IndexRouteObject {
name?: string;
breadcrumb?: string;
@ -59,6 +61,15 @@ export const routes: CustomRouteObject[] = [
path: "/staff",
element: <StaffMessage></StaffMessage>,
},
{
path: "/admin",
children: [
{
path: "department",
element: <DeptSettingPage></DeptSettingPage>,
},
]
},
{
path:"/plan",
children:[

View File

@ -449,8 +449,8 @@ model Staff {
rank String? @map("rank") // 衔职级别
rankDate DateTime? @map("rank_date") // 衔职时间
proxyPosition String? @map("proxy_position") // 代理职务
position Position? @relation("StaffPosition", fields: [positionId], references: [id]) // 岗位
positionId String? @map("position_id")
post String? @map("post") // 岗位
// 入职相关信息
hireDate DateTime? @map("hire_date") // 入职时间
@ -501,7 +501,8 @@ model Staff {
enrollments Enrollment[]
teachedPosts PostInstructor[]
ownedResources Resource[]
position Position? @relation("StaffPosition", fields: [positionId], references: [id])
positionId String? @map("position_id")
// 系统信息
registerToken String?
createdAt DateTime @default(now()) @map("created_at")