From 66fc63c6823979120c7e13752bd74bdee354c5b5 Mon Sep 17 00:00:00 2001
From: linfeng <2819853134@qq.com>
Date: Wed, 19 Mar 2025 15:57:48 +0800
Subject: [PATCH] lin
---
.../app/main/{dailyPage => daily}/page.tsx | 0
apps/web/src/app/main/home/page.tsx | 4 +-
apps/web/src/app/main/layout/MainHeader.tsx | 51 +-
.../src/app/main/layout/NavigationMenu.tsx | 349 ++++++++++--
apps/web/src/app/main/plan/monthplan/page.tsx | 0
apps/web/src/app/main/plan/page.tsx | 0
apps/web/src/app/main/plan/weekplan/page.tsx | 227 ++++++++
.../app/main/staffinformation/area-options.ts | 33 ++
.../src/app/main/staffinformation/page.tsx | 517 ++++++++++++++++++
apps/web/src/app/main/staffpage/page.tsx | 69 +--
.../app/main/staffpage/staffmodal/page.tsx | 145 ++---
.../app/main/staffpage/stafftable/page.tsx | 436 ++++++++++-----
.../department/department-children-select.tsx | 15 +-
.../train-content-tree-select.tsx | 8 +-
.../models/trainPlan/TrainPlanLayout.tsx | 5 +-
.../models/trainPlan/TrainPlanWrite.tsx | 3 +
apps/web/src/data/area-options.ts | 31 ++
apps/web/src/routes/index.tsx | 44 +-
config/nginx/conf.d/web.conf | 4 +-
packages/common/prisma/schema.prisma | 113 ++--
20 files changed, 1666 insertions(+), 388 deletions(-)
rename apps/web/src/app/main/{dailyPage => daily}/page.tsx (100%)
create mode 100644 apps/web/src/app/main/plan/monthplan/page.tsx
create mode 100644 apps/web/src/app/main/plan/page.tsx
create mode 100644 apps/web/src/app/main/plan/weekplan/page.tsx
create mode 100644 apps/web/src/app/main/staffinformation/area-options.ts
create mode 100644 apps/web/src/app/main/staffinformation/page.tsx
create mode 100644 apps/web/src/components/models/trainPlan/TrainPlanWrite.tsx
create mode 100644 apps/web/src/data/area-options.ts
diff --git a/apps/web/src/app/main/dailyPage/page.tsx b/apps/web/src/app/main/daily/page.tsx
similarity index 100%
rename from apps/web/src/app/main/dailyPage/page.tsx
rename to apps/web/src/app/main/daily/page.tsx
diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx
index 729e69d..d6c0312 100755
--- a/apps/web/src/app/main/home/page.tsx
+++ b/apps/web/src/app/main/home/page.tsx
@@ -1,9 +1,9 @@
import React from "react"
-export default function HomePage() {
+export default function Dashboard() {
return (
- 首页
+ 数据看板(待开发)
)
}
\ No newline at end of file
diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx
index fee3ad5..737fe8f 100644
--- a/apps/web/src/app/main/layout/MainHeader.tsx
+++ b/apps/web/src/app/main/layout/MainHeader.tsx
@@ -1,43 +1,30 @@
-import { Layout, Menu, Avatar, Button } from 'antd';
-import { UserOutlined, SettingOutlined } from '@ant-design/icons';
+import { Layout, Avatar, Dropdown, Menu } from 'antd';
+import { UserOutlined } from '@ant-design/icons';
import { useNavigate, Outlet, useLocation } from 'react-router-dom';
import NavigationMenu from './NavigationMenu';
+import { Header } from 'antd/es/layout/layout';
+import { useState } from 'react';
const { Sider, Content } = Layout;
export default function MainHeader() {
- const navigate = useNavigate();
-
-
return (
-
- {/* 左侧导航栏 */}
-
- {/* 用户头像区域 */}
-
-
-
}
- className=" transition-all duration-300"
- />
-
}
- className="!absolute bottom-0 right-6 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
- onClick={() => navigate('/profile')}
- />
-
-
-
-
- {/* 新增可滚动内容区域 */}
-
-
+
+ {/* 顶部Header */}
+
+
+ {/* 主体布局 */}
+
+ {/* 左侧导航栏 */}
+
+
+
+
+ {/* 内容区域 */}
+
diff --git a/apps/web/src/app/main/layout/NavigationMenu.tsx b/apps/web/src/app/main/layout/NavigationMenu.tsx
index b8c2ef8..490cea3 100644
--- a/apps/web/src/app/main/layout/NavigationMenu.tsx
+++ b/apps/web/src/app/main/layout/NavigationMenu.tsx
@@ -1,40 +1,325 @@
+import React, { useEffect, useState } from "react";
import { Menu } from "antd";
+// import { useSelector } from "react-redux";
import { useNavigate, useLocation } from "react-router-dom";
+// import styles from "./index.module.less";
+// import logo from "../../assets/logo.png";
-export default function NavigationMenu() {
- const navigate = useNavigate();
- const location = useLocation();
- console.log(location.pathname);
- // 导航菜单项配置
- const menuItems = [
- { key: 'staff', label: '人员总览', path: '/' }, // 将path改为根路径
- { key: 'plan', label: '培训计划', path: '/plan' },
- { key: 'day', label: '每日填报', path: '/daily' },
- { key: 'exam', label: '考核成绩', path: '/exam' },
- ];
+function getItem(
+ label: any,
+ key: any,
+ icon: any,
+ children: any,
+ type: any,
+ // permission: any
+) {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ // permission,
+ };
+}
+const items = [
+ getItem(
+ "首页概览",
+ "/",
+ ,
+ null,
+ null,
+ ),
+ getItem(
+ "人员信息",
+ "/staffinformation",
+ ,
+ null,
+ null,
+ ),
+ getItem(
+ "人员总览",
+ "/staff",
+ ,
+ null,
+ null,
+ ),
+ getItem(
+ "训练计划",
+ "/plan",
+ ,
+ [
+ getItem("周训练计划", "/plan/weekplan", null, null, null),
+ getItem("月训练计划", "/plan/monthplan", null, null, null),
+ ],
+ null,
+ ),
+ getItem(
+ "每日填报",
+ "/daily",
+ ,
+ null,
+ null,
+ ),
+ getItem(
+ "考核成绩",
+ "/assessment",
+ ,
+ [
+ getItem("岗位", "/assessment/positionassessment", null, null, null),
+ getItem("共同", "/assessment/commonassessment", null, null, null),
+ getItem("体育", "/assessment/sportsassessment", null, null, null),
+ ],
+ null,
+ )
+];
+
+const NavigationMenu: React.FC = () => {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const children2Parent: any = {
+ "^/plan/weekplan": ["/plan"],
+ "^/plan/monthplan": ["/plan"],
+ // 添加考核成绩子路径的匹配规则
+ "^/assessment/positionassessment": ["/assessment"],
+ "^/assessment/commonassessment": ["/assessment"],
+ "^/assessment/sportsassessment": ["/assessment"]
+ };
+
+ // 选中的菜单
+ const [selectedKeys, setSelectedKeys] = useState([
+ location.pathname,
+ ]);
+ // 展开菜单
+ const [openKeys, setOpenKeys] = useState([]);
+ // const permissions = useSelector(
+ // (state: any) => state.loginUser.value.permissions
+ // );
+ const [activeMenus, setActiveMenus] = useState(items);
+
+ const onClick = (e: any) => {
+ const path = e.key;
+ // 立即更新选中状态
+ setSelectedKeys([path]);
+
+ // 根据不同路径设置展开状态
+ if (path === "/staffinformation") {
+ setOpenKeys(["/staffinformation"]);
+ } else if (path === "/staff") {
+ setOpenKeys(["/staff"]);
+ } else if (path.startsWith("/assessment/") || path.startsWith("/plan/")) {
+ setOpenKeys([path.split('/').slice(0, 2).join('/')]);
+ } else {
+ setOpenKeys(openKeyMerge(path));
+ }
+
+ // 执行导航
+ navigate(path);
+ };
+ // console.log(items)
+ useEffect(() => {
+ setActiveMenus(items);
+ // console.log(activeMenus)
+ },[items])
+ // useEffect(() => {
+ // checkMenuPermissions(items, permissions);
+ // }, [items, permissions]);
+
+ // const checkMenuPermissions = (items: any, permissions: any) => {
+ // let menus: any = [];
+ // if (permissions.length === 0) {
+ // setActiveMenus(menus);
+ // return;
+ // }
+
+ // for (let i in items) {
+ // let menuItem = items[i];
+ // // 一级菜单=>没有子菜单&配置了权限
+ // if (menuItem.children === null) {
+ // if (
+ // menuItem.permission !== null &&
+ // typeof permissions[menuItem.permission] === "undefined"
+ // ) {
+ // continue;
+ // }
+ // menus.push(menuItem);
+ // continue;
+ // }
+ // let children = [];
+
+ // for (let j in menuItem.children) {
+ // let childrenItem = menuItem.children[j];
+
+ // if (
+ // typeof permissions[childrenItem.permission] !== "undefined" ||
+ // !childrenItem.permission
+ // ) {
+ // // 存在权限
+ // children.push(childrenItem);
+ // }
+ // }
+
+ // if (children.length > 0) {
+ // menus.push(Object.assign({}, menuItem, { children: children }));
+ // }
+ // }
+ // setActiveMenus(menus);
+ // };
+
+ const hit = (pathname: string): string[] => {
+ // 使用精确的路径匹配
+ const exactPaths = {
+ "/staffinformation": ["/staffinformation"],
+ "/staff": ["/staff"],
+ "/": ["/"]
+ };
+
+ // 先检查精确匹配
+ if (exactPaths[pathname]) {
+ return exactPaths[pathname];
+ }
+
+ // 再检查正则匹配的子路径
+ for (let p in children2Parent) {
+ const regex = new RegExp(p);
+ if (regex.test(pathname)) {
+ return children2Parent[p];
+ }
+ }
+ return [];
+ };
+
+ const openKeyMerge = (pathname: string): string[] => {
+ let newOpenKeys = hit(pathname);
+ for (let i = 0; i < openKeys.length; i++) {
+ let isIn = false;
+ for (let j = 0; j < newOpenKeys.length; j++) {
+ if (newOpenKeys[j] === openKeys[i]) {
+ isIn = true;
+ break;
+ }
+ }
+ if (isIn) {
+ continue;
+ }
+ newOpenKeys.push(openKeys[i]);
+ }
+ return newOpenKeys;
+ };
+
+ // 修改 useEffect 中的路径判断逻辑
+ useEffect(() => {
+ const currentPath = location.pathname;
+ setSelectedKeys([currentPath]);
+
+ // 对于根路径特殊处理
+ if (currentPath === "/") {
+ setOpenKeys([]);
+ return;
+ }
+
+ // 使用修改后的 hit 函数获取正确的 openKeys
+ const newOpenKeys = openKeyMerge(currentPath);
+ setOpenKeys(newOpenKeys);
+ }, [location.pathname]);
return (
- <>
+
+
{
+ window.location.href = "/";
+ }}
+ >
+ {/* 此处为版权标识,严禁删改
+

*/}
+
+
- >
+ items={activeMenus}
+ onSelect={(data: any) => {
+ setSelectedKeys(data.selectedKeys);
+ }}
+ onOpenChange={(keys: any) => {
+ setOpenKeys(keys);
+ }}
+ />
+
+
);
-}
\ No newline at end of file
+};
+
+export default NavigationMenu;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// import { Menu } from "antd";
+// import { useNavigate, useLocation } from "react-router-dom";
+
+// export default function NavigationMenu() {
+// const navigate = useNavigate();
+// const location = useLocation();
+// console.log(location.pathname);
+// // 导航菜单项配置
+// const menuItems = [
+// { key: 'staff', label: '人员总览', path: '/' }, // 将path改为根路径
+// { key: 'plan', label: '培训计划', path: '/plan' },
+// { key: 'day', label: '每日填报', path: '/daily' },
+// { key: 'exam', label: '考核成绩', path: '/exam' },
+// ];
+
+// return (
+// <>
+//
+// >
+// );
+// }
\ No newline at end of file
diff --git a/apps/web/src/app/main/plan/monthplan/page.tsx b/apps/web/src/app/main/plan/monthplan/page.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/apps/web/src/app/main/plan/page.tsx b/apps/web/src/app/main/plan/page.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/apps/web/src/app/main/plan/weekplan/page.tsx b/apps/web/src/app/main/plan/weekplan/page.tsx
new file mode 100644
index 0000000..2d870a5
--- /dev/null
+++ b/apps/web/src/app/main/plan/weekplan/page.tsx
@@ -0,0 +1,227 @@
+import { useState } from 'react'
+import * as XLSX from 'xlsx'
+import { Table, Select, Pagination } from 'antd'
+import type { ColumnsType, ColumnType } from 'antd/es/table'
+import { UploadOutlined } from '@ant-design/icons'
+
+interface TableData {
+ key: string
+ [key: string]: any
+}
+
+export default function WeekPlanPage() {
+ const [data, setData] = useState([])
+ const [columns, setColumns] = useState>([])
+ const [currentPage, setCurrentPage] = useState(1)
+ const [trainingStatus, setTrainingStatus] = useState>({})
+ const pageSize = 1 // 每页显示一个第一列的唯一值
+
+ const handleFileUpload = (event: React.ChangeEvent) => {
+ const file = event.target.files?.[0]
+ if (!file) return
+
+ const reader = new FileReader()
+ reader.onload = (e) => {
+ const data = new Uint8Array(e.target?.result as ArrayBuffer)
+ const workbook = XLSX.read(data, { type: 'array' })
+ const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
+
+ // 处理合并单元格
+ if (firstSheet['!merges']) {
+ firstSheet['!merges'].forEach(merge => {
+ // 获取合并区域的起始单元格的值
+ const firstCell = XLSX.utils.encode_cell({ r: merge.s.r, c: merge.s.c })
+ const firstCellValue = firstSheet[firstCell]?.v
+
+ // 将合并区域内的所有单元格设置为相同的值
+ for (let row = merge.s.r; row <= merge.e.r; row++) {
+ for (let col = merge.s.c; col <= merge.e.c; col++) {
+ const cell = XLSX.utils.encode_cell({ r: row, c: col })
+ if (!firstSheet[cell]) {
+ firstSheet[cell] = { t: 's', v: firstCellValue }
+ }
+ }
+ }
+ })
+ }
+
+ const jsonData: any[] = XLSX.utils.sheet_to_json(firstSheet, {
+ header: 1,
+ defval: '',
+ blankrows: false,
+ raw: false
+ })
+
+ // 处理表头和数据
+ const headers = jsonData[0]
+ const tableData: TableData[] = jsonData.slice(1).map((row, index) => ({
+ key: index.toString(),
+ ...headers.reduce((acc: any, header: string, idx: number) => {
+ acc[header] = row[idx]
+ return acc
+ }, {})
+ }))
+
+ // 创建列配置
+ const tableColumns: ColumnsType = headers.map((header: string, index: number) => ({
+ title: header,
+ dataIndex: header,
+ key: header,
+ width: 150,
+ align: 'center',
+ filterMultiple: true,
+ filters: Array.from(new Set(tableData.map(item => item[header])))
+ .filter(Boolean)
+ .map(value => ({ text: String(value), value: String(value) })),
+ onFilter: (value, record) => String(record[header]) === value,
+ ...(index < headers.length - 1 && {
+ render: (text, record, index) => ({
+ children: text,
+ props: {
+ rowSpan: calculateRowSpan(tableData, index, header),
+ style: {
+ border: '1px solid #f0f0f0',
+ borderBottom: '1px solid #f0f0f0'
+ }
+ }
+ })
+ })
+ }))
+ // 添加是否参训列
+ tableColumns.push({
+ title: '是否参训',
+ dataIndex: 'isTraining',
+ key: 'isTraining',
+ width: 120,
+ align: 'center',
+ render: (_, record) => (
+