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,
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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:[
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue