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

View File

@ -1,6 +1,6 @@
"use client"; "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 { useState } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useStaff } from "@nice/client"; import { useStaff } from "@nice/client";
@ -144,7 +144,7 @@ const StaffInformation = () => {
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label='年龄' name='age'> <Form.Item label='年龄' name='age'>
<Input /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item label="血型" name="bloodType"> <Form.Item label="血型" name="bloodType">
<Select> <Select>
@ -218,7 +218,9 @@ const StaffInformation = () => {
<Form.Item label="代理职务" name="proxyPosition"> <Form.Item label="代理职务" name="proxyPosition">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label="岗位" name="post">
<Input />
</Form.Item>
</div> </div>
</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 { 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 } from 'xlsx';
import { Modal, Input, Button } from 'antd'; import { Modal, Input, Button, Switch } from 'antd';
import { api } from '@nice/client'; import { api } from '@nice/client';
import { StaffDto } from 'packages/common/dist';
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';
// 修改函数类型定义 // 修改函数类型定义
@ -40,107 +40,100 @@ export default function StaffTable() {
} }
}); });
const [gridApi, setGridApi] = useState<any>(null); // 添加gridApi状态 const [gridApi, setGridApi] = useState<any>(null); // 添加gridApi状态
const [confirmVisible, setConfirmVisible] = useState(false);
const [fileNameVisible, setFileNameVisible] = useState(false); const [fileNameVisible, setFileNameVisible] = useState(false);
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 [exporting, setExporting] = useState(false); const [paginationEnabled, setPaginationEnabled] = useState(true);
const [selectedDepartment, setSelectedDepartment] = useState('');
const [tempExportData, setTempExportData] = useState<any | null>(null);
const [exportScope, setExportScope] = useState<'current' | 'department'>('current');
const handleExport = async () => {
setConfirmVisible(true);
};
const handleConfirm = async () => { 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); setFileNameVisible(true);
}; };
// 添加导出处理函数 // 添加导出处理函数
const handleFileNameConfirm = () => { const handleFileNameConfirm = () => {
setFileNameVisible(false) setFileNameVisible(false);
if (!tempExportData) return; if (!gridApi || typeof gridApi.getRenderedNodes !== 'function') {
console.error('Grid API 未正确初始化');
if (!gridApi) return; 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 || [];
} }
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> = {}; const row: Record<string, any> = {};
allColDefs.forEach((colDef: any) => { allColDefs.forEach((colDef: any) => {
const field = colDef.field; if (!colDef.field || !colDef.headerName) return;
if (field) {
const value = node.data[field]; // 修改字段访问方式,支持嵌套对象
const formatter = colDef.valueFormatter; const value = colDef.field.includes('.')
const renderer = colDef.cellRenderer; ? colDef.field.split('.').reduce((obj: any, key: string) => (obj || {})[key], node.data)
: node.data[colDef.field];
let renderedValue = value; let renderedValue = value;
if (formatter) {
renderedValue = formatter({ value }); // 应用列格式化
} else if (renderer) { if (colDef.valueFormatter) {
const renderResult = renderer({ value }); renderedValue = colDef.valueFormatter({ value });
// 改进渲染结果处理 }
// 处理特殊数据类型
if (colDef.cellRenderer) {
const renderResult = colDef.cellRenderer({ value });
if (typeof renderResult === 'string') { if (typeof renderResult === 'string') {
renderedValue = renderResult; renderedValue = renderResult;
} else if (renderResult?.props?.children) { // 处理React元素文本内容
renderedValue = String(renderResult.props.children);
} else if (renderResult?.props?.dangerouslySetInnerHTML?.__html) { } else if (renderResult?.props?.dangerouslySetInnerHTML?.__html) {
const html = renderResult.props.dangerouslySetInnerHTML.__html; renderedValue = renderResult.props.dangerouslySetInnerHTML.__html.replace(/<br\s*\/?>/gi, '\n');
renderedValue = html.replace(/<br\s*\/?>/gi, '\n');
} }
} }
// 增强布尔值处理逻辑 // 统一布尔值显示
if (typeof renderedValue === 'boolean' || if (typeof renderedValue === 'boolean') {
(typeof renderedValue === 'string' && ['true', 'false'].includes(renderedValue.toLowerCase()))) { renderedValue = renderedValue ? '是' : '否';
const boolValue = typeof renderedValue === 'boolean' ? renderedValue : renderedValue.toLowerCase() === 'true'; }
renderedValue = boolValue ? '是' : '否';
// 日期字段处理
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; row[colDef.headerName] = renderedValue;
}
}); });
return row; return row;
}); };
// 创建工作表并导出 try {
// 生成工作表
const rowData = rowNodes.map(processRowData);
const ws = utils.json_to_sheet(rowData); const ws = utils.json_to_sheet(rowData);
const wb = utils.book_new(); const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Sheet1"); utils.book_append_sheet(wb, ws, "员工数据");
// 修改导出文件名生成方式
writeFile(wb, `${fileName || '未命名数据'}.xlsx`); // 生成文件名
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)[] = [ const columnDefs: (ColDef | ColGroupDef)[] = [
@ -169,7 +162,17 @@ export default function StaffTable() {
{ field: 'age', headerName: '年龄', minWidth: 80 }, { field: 'age', headerName: '年龄', minWidth: 80 },
{ {
field: 'sex', 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 }, { field: 'bloodType', headerName: '血型', minWidth: 80 },
{ {
@ -197,7 +200,8 @@ export default function StaffTable() {
field: 'rankDate', headerName: '衔职时间', minWidth: 120, field: 'rankDate', headerName: '衔职时间', minWidth: 120,
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}
] ]
}, },
{ {
@ -344,45 +348,30 @@ export default function StaffTable() {
}} }}
> >
{!isLoading && ( {!isLoading && (
<div className="flex items-center gap-4 mb-2">
<Button <Button
onClick={handleExport} onClick={handleConfirm}
className="mb-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
> >
Excel Excel
</Button> </Button>
)} <Button
onClick={handleResetFilters}
<Modal className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
title="确认导出"
open={confirmVisible}
onOk={handleConfirm}
onCancel={() => setConfirmVisible(false)}
okText="确认"
cancelText="取消"
> >
<div className="mb-4">
<span></span> </Button>
<select <div className="flex items-center gap-2">
value={exportScope} <span className="text-gray-600"></span>
onChange={(e) => setExportScope(e.target.value as any)} <Switch
className="ml-2 p-1 border rounded" checked={paginationEnabled}
> onChange={(checked) => setPaginationEnabled(checked)}
<option value="current"></option> checkedChildren="开"
<option value="department"></option> unCheckedChildren="关"
</select>
</div>
{exportScope === 'department' && (
<div>
<p></p>
<DepartmentSelect
value={selectedDepartment}
// onChange={(value) => setSelectedDepartment(value)}
placeholder="请选择部门"
/> />
</div> </div>
</div>
)} )}
</Modal>
<Modal <Modal
title="输入文件名" title="输入文件名"
@ -404,7 +393,7 @@ export default function StaffTable() {
</div> </div>
) : ( ) : (
<AgGridReact <AgGridReact
modules={[SetFilterModule]} modules={[SetFilterModule, ClientSideRowModelModule]}
onGridReady={(params) => setGridApi(params.api)} // 添加gridApi回调 onGridReady={(params) => setGridApi(params.api)} // 添加gridApi回调
rowData={staffs} rowData={staffs}
columnDefs={columnDefs} columnDefs={columnDefs}
@ -415,7 +404,7 @@ export default function StaffTable() {
} }
}} }}
enableCellTextSelection={true} enableCellTextSelection={true}
pagination={true} pagination={paginationEnabled}
paginationAutoPageSize={true} paginationAutoPageSize={true}
cacheQuickFilter={true} cacheQuickFilter={true}
/> />

View File

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

View File

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