From 127ed073830f336012785cdefa70678e9c2bd6d5 Mon Sep 17 00:00:00 2001
From: linfeng <2819853134@qq.com>
Date: Mon, 24 Mar 2025 20:16:37 +0800
Subject: [PATCH] lin
---
apps/web/package.json | 8 +-
.../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 | 331 ++++++++++++++++--
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 ++++++++++++
apps/web/src/app/main/staffpage/page.tsx | 5 +-
.../app/main/staffpage/staffmodal/page.tsx | 128 +++----
.../app/main/staffpage/stafftable/page.tsx | 196 +++++++----
.../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/routes/index.tsx | 39 ++-
config/nginx/conf.d/web.conf | 4 +-
pnpm-lock.yaml | 169 +++++++--
18 files changed, 959 insertions(+), 234 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/components/models/trainPlan/TrainPlanWrite.tsx
diff --git a/apps/web/package.json b/apps/web/package.json
index b484901..7649726 100755
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -23,7 +23,7 @@
"@ag-grid-enterprise/server-side-row-model": "~32.3.2",
"@ag-grid-enterprise/set-filter": "~32.3.2",
"@ag-grid-enterprise/status-bar": "~32.3.2",
- "@ant-design/icons": "^5.4.0",
+ "@ant-design/icons": "^5.5.2",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -41,11 +41,12 @@
"@trpc/client": "11.0.0-rc.456",
"@trpc/react-query": "11.0.0-rc.456",
"@trpc/server": "11.0.0-rc.456",
+ "@types/react-redux": "^7.1.34",
"@xyflow/react": "^12.3.6",
"ag-grid-community": "~32.3.2",
"ag-grid-enterprise": "~32.3.2",
"ag-grid-react": "~32.3.2",
- "antd": "^5.19.3",
+ "antd": "^5.23.0",
"axios": "^1.7.2",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.1",
@@ -64,12 +65,14 @@
"react-dom": "18.2.0",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.4.1",
+ "react-redux": "^9.2.0",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.24.1",
"superjson": "^2.2.1",
"tailwind-merge": "^2.6.0",
"use-debounce": "^10.0.4",
"uuid": "^10.0.0",
+ "xlsx": "^0.18.5",
"yjs": "^13.6.20",
"zod": "^3.23.8"
},
@@ -77,6 +80,7 @@
"@eslint/js": "^9.9.0",
"@types/react": "18.2.38",
"@types/react-dom": "18.2.15",
+ "@types/xlsx": "^0.0.36",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
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..b645dbc 100644
--- a/apps/web/src/app/main/layout/NavigationMenu.tsx
+++ b/apps/web/src/app/main/layout/NavigationMenu.tsx
@@ -1,40 +1,307 @@
+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(
+ "人员总览",
+ "/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"]
+ };
+
+ // 同时在 useEffect 中更新路径判断逻辑
+ useEffect(() => {
+ if (location.pathname.indexOf("/staff") !== -1) {
+ setSelectedKeys(["/staff"]);
+ setOpenKeys(openKeyMerge("/staff"));
+ } else if (
+ // 添加考核成绩路径的判断
+ location.pathname.startsWith("/assessment/") ||
+ location.pathname === "/plan/weekplan" ||
+ location.pathname === "/plan/monthplan"
+ ) {
+ setSelectedKeys([location.pathname]);
+ setOpenKeys([location.pathname.split('/').slice(0, 2).join('/')]);
+ } else {
+ setSelectedKeys([location.pathname]);
+ setOpenKeys(openKeyMerge(location.pathname));
+ }
+ }, [location.pathname]);
+
+ const hit = (pathname: string): string[] => {
+ for (let p in children2Parent) {
+ if (pathname.search(p) >= 0) {
+ 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;
+ };
+
+ // 选中的菜单
+ const [selectedKeys, setSelectedKeys] = useState([
+ location.pathname,
+ ]);
+ // 展开菜单
+ const [openKeys, setOpenKeys] = useState(hit(location.pathname));
+ // const permissions = useSelector(
+ // (state: any) => state.loginUser.value.permissions
+ // );
+ const [activeMenus, setActiveMenus] = useState(items);
+
+ const onClick = (e: any) => {
+ navigate(e.key);
+ };
+ // 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);
+ // };
+
+ useEffect(() => {
+ if (location.pathname.indexOf("/staff") !== -1) {
+ setSelectedKeys(["/staff"]);
+ setOpenKeys(openKeyMerge("/staff")); // 修正为当前路径的父级
+ } else if (
+ location.pathname === "/weekplan" ||
+ location.pathname === "/monthplan"
+ ) {
+ setSelectedKeys([location.pathname]);
+ setOpenKeys(["/plan"]); // 直接展开训练计划父菜单
+ } else {
+ setSelectedKeys([location.pathname]);
+ setOpenKeys(openKeyMerge(location.pathname));
+ }
+ }, [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) => (
+