lin
This commit is contained in:
parent
e290a9d51c
commit
305ecf78ed
|
@ -0,0 +1,7 @@
|
|||
export default function DeptSettingPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>部门设置</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
// 修改字段访问方式,支持嵌套对象
|
||||
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 (formatter) {
|
||||
renderedValue = formatter({ value });
|
||||
} else if (renderer) {
|
||||
const renderResult = renderer({ 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?.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');
|
||||
renderedValue = renderResult.props.dangerouslySetInnerHTML.__html.replace(/<br\s*\/?>/gi, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
// 增强布尔值处理逻辑
|
||||
if (typeof renderedValue === 'boolean' ||
|
||||
(typeof renderedValue === 'string' && ['true', 'false'].includes(renderedValue.toLowerCase()))) {
|
||||
const boolValue = typeof renderedValue === 'boolean' ? renderedValue : renderedValue.toLowerCase() === 'true';
|
||||
renderedValue = boolValue ? '是' : '否';
|
||||
// 统一布尔值显示
|
||||
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, "Sheet1");
|
||||
// 修改导出文件名生成方式
|
||||
writeFile(wb, `${fileName || '未命名数据'}.xlsx`);
|
||||
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 && (
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<Button
|
||||
onClick={handleExport}
|
||||
className="mb-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||
onClick={handleConfirm}
|
||||
className="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="取消"
|
||||
<Button
|
||||
onClick={handleResetFilters}
|
||||
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
|
||||
>
|
||||
<div className="mb-4">
|
||||
<span>导出范围:</span>
|
||||
<select
|
||||
value={exportScope}
|
||||
onChange={(e) => setExportScope(e.target.value as any)}
|
||||
className="ml-2 p-1 border rounded"
|
||||
>
|
||||
<option value="current">当前页数据</option>
|
||||
<option value="department">指定部门全量数据</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{exportScope === 'department' && (
|
||||
<div>
|
||||
<p>部门名称:</p>
|
||||
<DepartmentSelect
|
||||
value={selectedDepartment}
|
||||
// onChange={(value) => setSelectedDepartment(value)}
|
||||
placeholder="请选择部门"
|
||||
重置筛选
|
||||
</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>
|
||||
|
||||
<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}
|
||||
/>
|
||||
|
|
|
@ -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:[
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue