# HRMS前端 **Repository Path**: nepan/hrms-frontend ## Basic Information - **Project Name**: HRMS前端 - **Description**: HRMS前端开发仓库 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-14 - **Last Updated**: 2026-07-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HRMS 前端项目使用说明 > **项目名称:** 浮岛星际 · HRMS Frontend > **技术栈:** React 18 + TypeScript 5 + Umi 4 + Ant Design 5 + Zustand 5 > **设计风格:** 浮岛星际(毛玻璃 + 星空背景 + 低频微动效 + 双主题) --- ## 目录 - [1. 快速开始](#1-快速开始) - [2. 项目结构](#2-项目结构) - [3. 核心概念](#3-核心概念) - [3.1 浮岛星际设计体系](#31-浮岛星际设计体系) - [3.2 双主题(晨曦云海 / 幽邃深空)](#32-双主题晨曦云海--幽邃深空) - [3.3 五角色体系](#33-五角色体系) - [3.4 路由与约定](#34-路由与约定) - [4. 开发流程](#4-开发流程) - [4.1 新增菜单/页面(3 步)](#41-新增菜单页面3-步) - [4.2 开发业务模块](#42-开发业务模块) - [4.3 使用 API 层](#43-使用-api-层) - [4.4 使用共享组件](#44-使用共享组件) - [4.5 角色差异化](#45-角色差异化不同角色不同页面内容) - [4.6 页面加载态处理](#46-页面加载态处理) - [5. 鉴权说明](#5-鉴权说明) - [6. 主题与样式](#6-主题与样式) - [6.1 CSS 变量速查](#61-css-变量速查) - [6.2 预置 CSS 类](#62-预置-css-类) - [6.3 动画关键帧](#63-动画关键帧) - [7. 模块与负责人](#7-模块与负责人) - [8. 构建与部署](#8-构建与部署) - [9. 常见问题](#9-常见问题) --- ## 1. 快速开始 ```bash # 安装依赖 npm install # 启动开发服务器(默认 http://localhost:8000) npm run dev # 生产构建 npm run build # 预览生产构建 npm run preview ``` **开发阶段登录:** 打开 `http://localhost:8000`,任意输入账号密码即可登录(Mock 模式)。输入 `admin` 将以"陈思远 / 管理员"身份登录;输入其他任意用户名将以该名称 + admin 角色登录。 **角色切换:** 顶栏右侧有角色下拉选择器,可在开发阶段自由切换 admin / hr / manager / employee / finance 五种角色,实时预览不同角色的菜单和仪表盘内容。 --- ## 2. 项目结构 ``` hrms-frontend-umi/ ├── .umirc.ts # 🔧 Umi 主配置(插件、代理、拆包、构建) ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── tailwind.config.js # Tailwind CSS 配置(preflight 关闭,避免与 antd 冲突) ├── postcss.config.js # PostCSS 配置 ├── tsconfig.json # TypeScript 配置(路径别名 @/ → src/) ├── 使用说明.md # ← 本文件 │ ├── public/ │ └── favicon.svg # 🪐 站点图标 │ └── src/ ├── app.tsx # 🔑 Umi 运行时配置(主题包裹 + request 拦截器) ├── global.css # 🎨 全局样式 + CSS 变量 + 关键帧动画 │ ├── api/ # 🔌 API 层 │ ├── request.ts # Umi request 封装(get/post/put/del/upload/download) │ └── mock.ts # Mock 工具(模拟延迟、构造响应、构造分页) │ ├── components/ # 🧩 共享组件(所有模块通用) │ ├── AppHeader.tsx # 顶栏(搜索 + 角色切换 + 主题切换 + 通知 + 用户菜单) │ ├── Sidebar.tsx # 侧边栏菜单(按角色过滤 + 折叠) │ ├── StarCanvas.tsx # 星空背景(Canvas 粒子 + 暗黑模式流星) │ ├── AgentOrb.tsx # AI 助手浮球(M9 入口 + 引力搜索联动) │ ├── FloatingCard.tsx # 浮岛卡片容器(毛玻璃 + 悬停上浮 + 入场动画) │ ├── KpiIsland.tsx # KPI 卡片(数字滚动 + 趋势箭头) │ ├── SafeText.tsx # 毛玻璃脱敏文本(hover 变清晰) │ ├── StellarTimeline.tsx # 审批时间线(星座连线风格) │ ├── DotSpinner.tsx # 多点绕圈加载动画(纯 CSS 驱动) │ └── Placeholder.tsx # 占位页(模块未开发时显示) │ ├── config/ # ⚙️ 配置文件 │ ├── menu.ts # 🔑 侧边栏菜单树 + 快捷入口(改菜单改这里) │ └── dashboard.ts # 🔑 仪表盘配置(每个角色展示哪些区域 + KPI 数据) │ ├── store/ # 📦 状态管理(Zustand + persist 持久化) │ ├── useAppStore.ts # 主题 / 侧边栏折叠 / 角色 / 搜索焦点 │ └── useAuthStore.ts # 登录态 / Token / RefreshToken / 用户信息 │ ├── theme/ │ └── antdTheme.ts # Ant Design 双主题令牌(20px 超圆角 + 浮岛配色) │ ├── types/ │ └── index.ts # 全局类型(Employee / Approval / KPI / Result / PageResult 等) │ ├── lib/ │ └── utils.ts # cn() 工具(clsx + tailwind-merge) │ ├── layouts/ │ └── index.tsx # 全局布局(侧边栏 + 顶栏 + 内容区 + 鉴权守卫 + Suspense) │ ├── pages/ # 📄 顶层页面(每个文件 = 一条路由) │ ├── index.tsx # 仪表盘主页(按角色组合不同 widget) │ ├── login.tsx # 登录页(Mock 模式) │ ├── 404.tsx # 404 页面 │ ├── employees.tsx # 员工档案 → 指向 modules/employee │ ├── notification.tsx # 消息中心 → 指向 modules/system │ ├── dashboard/ # 5 个角色的仪表盘组件(预留扩展) │ │ ├── AdminDashboard.tsx │ │ ├── HRDashboard.tsx │ │ ├── ManagerDashboard.tsx │ │ ├── EmployeeDashboard.tsx │ │ └── FinanceDashboard.tsx │ ├── system/ # 系统管理路由页(各 1 行 re-export) │ ├── organization/ # 组织架构路由页 │ ├── hrchange/ # 入转调离路由页 │ ├── attendance/ # 考勤管理路由页 │ ├── salary/ # 薪资管理路由页 │ ├── approval/ # 审批中心路由页 │ ├── personal/ # 个人中心路由页 │ └── agent/ # AI 助手路由页 │ └── modules/ # 🚀 业务模块(各自独立开发,互不依赖) ├── system/ # M1 系统管理 ├── organization/ # M2 组织架构 ├── employee/ # M3 员工档案 ├── hrchange/ # M4 入转调离 ├── approval/ # M5 审批中心 ├── attendance/ # M6 考勤管理 ├── salary/ # M7 薪资管理 ├── personal/ # M8 个人中心 └── agent/ # M9 AI智能助手 ├── api/ # 本模块 API 接口 └── pages/ # 本模块页面文件 ``` > **架构说明:** `src/pages/` 下的文件由 Umi 自动识别为路由页面。业务代码实际写在 `src/modules/{模块}/pages/` 下,然后在 `src/pages/` 中用一行 `export { default } from '@/modules/...'` 做 re-export。这样 Umi 的路由约定和业务模块目录解耦,多人协作时各自只改自己模块的目录。 --- ## 3. 核心概念 ### 3.1 浮岛星际设计体系 整个项目围绕"浮岛星际"设计语言构建,核心设计令牌通过 CSS 变量统一管理: - **浮岛(Island):** 所有内容区块采用毛玻璃半透明背景 + 20px 超圆角,视觉上像悬浮在星空中的岛屿 - **内发光(Inner Glow):** 卡片顶部有一道微弱的高光线,模拟玻璃折射 - **低频动效(Low-frequency Motion):** 动画克制、缓慢,避免过度干扰。悬停上浮 4px 过渡 0.5s、数字滚动 1.2s、脉冲呼吸 2.8s - **星空背景:** Canvas 2D 粒子系统,日间淡蓝星点缓慢上浮,夜间增亮并随机出现流星 ### 3.2 双主题(晨曦云海 / 幽邃深空) 通过 `[data-theme]` 属性在 `` 上切换,所有 CSS 变量自动变化: | 属性 | 晨曦云海 (light) | 幽邃深空 (dark) | | ---- | ------------------------ | -------------------- | | 主色 | `#002FA7`(克莱因蓝) | `#4DD0FF`(荧光蓝) | | 背景 | 蓝紫渐变柔和云海 | 深蓝黑渐变星空 | | 表面 | `rgba(255,255,255,0.82)` | `rgba(28,28,45,0.7)` | | 文字 | 深色 `#1F2235` | 浅色 `#E8EAF6` | | 粒子 | 淡蓝半透明 | 荧光蓝 + 光晕 | 切换方式:顶栏右侧 ☀/🌙 按钮,或代码中调用 `useAppStore().toggleTheme()`。 ### 3.3 五角色体系 系统预置 5 种角色,在菜单可见性、仪表盘内容两个层面实现差异化: | 角色 | 标识 | 视角 | 典型菜单 | | ---------- | -------- | ------------ | -------------------------- | | `admin` | 管理员 | 全局管控 | 全部菜单可见 | | `hr` | HR | 人力资源管理 | 系统管理除外 | | `manager` | 部门主管 | 团队管理 | 入转调离、考勤、审批 | | `employee` | 员工 | 个人事务 | 仪表盘、个人中心、考勤打卡 | | `finance` | 财务 | 薪资核算 | 薪资管理 + 仪表盘 | - **菜单过滤:** `menu.ts` 中每个菜单项可设 `roles` 字段,Sidebar 自动递归过滤 - **仪表盘差异:** `dashboard.ts` 中 `dashboardRoleConfig` 定义每个角色看到的组件区域 - **开发阶段:** 顶栏下拉框可实时切换角色预览效果 ### 3.4 路由与约定 项目使用 Umi 4 的**约定式路由**:`src/pages/` 下的文件自动生成路由,无需手动注册。 ``` src/pages/index.tsx → / src/pages/login.tsx → /login src/pages/system/users.tsx → /system/users src/pages/attendance/clock.tsx → /attendance/clock src/pages/404.tsx → /* (兜底) ``` **约定:** - `src/pages/` 下的文件命名即路径名(不支持动态路由参数) - 如需让某个页面不使用全局布局,添加 `export const layout = false`(参见 `login.tsx`) - 路由页面本身很薄(通常只有 1 行 re-export),实际逻辑在 `modules/` 下 --- ## 4. 开发流程 ### 4.1 新增菜单/页面(3 步) 假设要在「AI智能助手」下新增「对话测试」页面,路径 `/agent/chat`: **步骤 ① — 新建业务页面** ```tsx // src/modules/agent/pages/Chat.tsx import Placeholder from "@/components/Placeholder"; export default function Chat() { // 开发阶段先用 Placeholder 占位,后续替换为真实页面 return ; } ``` **步骤 ② — 新建路由页面(re-export)** ```tsx // src/pages/agent/chat.tsx export { default } from "@/modules/agent/pages/Chat"; ``` > **为什么这样做?** `src/pages/` 是 Umi 约定式路由的扫描目录,`src/modules/` 是业务代码目录。通过一行 re-export 将两者连接,多人协作时各自只改自己模块的目录,路由文件不会产生冲突。 **步骤 ③ — 添加菜单项** 在 `src/config/menu.ts` 的 `agent` 菜单的 `children` 数组里追加: ```ts { key: "agent", label: "AI智能助手", icon: "🤖", children: [ // ... 已有菜单项 ... { key: "agent/chat", label: "对话测试", icon: "💬", path: "/agent/chat" }, ], }, ``` **完成!** 刷新页面即可在侧边栏看到新菜单。 > **无需修改其他任何文件** — Umi 自动扫描 `src/pages/` 生成路由,`Sidebar` 自动从 `menu.ts` 读取菜单树。 --- ### 4.2 开发业务模块 每个模块目录结构相同: ``` modules/{模块名}/ ├── api/ │ └── index.ts # 本模块的 API 接口函数 + 类型定义 └── pages/ └── XxxPage.tsx # 页面组件 ``` **开发步骤:** 1. **打开你的模块目录**,找到对应页面文件 2. **将 `Placeholder` 替换为真实页面代码** 3. **在 `api/index.ts` 中编写接口调用** 4. **在页面中调用 API,处理数据,渲染 UI** **示例:一个完整页面骨架** ```tsx // modules/system/pages/Users.tsx import { useState, useEffect } from "react"; import { Table, Button, Input, Space, message } from "antd"; import FloatingCard from "@/components/FloatingCard"; import DotSpinner from "@/components/DotSpinner"; import { getUserList, deleteUser } from "../api"; import type { UserItem } from "../api"; export default function Users() { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(""); const fetchData = async () => { setLoading(true); try { const res = await getUserList({ page: 1, size: 10, keyword }); setData(res.records); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); // 数据加载中 → 居中显示 DotSpinner if (loading) { return (
); } const columns = [ { title: "用户名", dataIndex: "username", key: "username" }, { title: "真实姓名", dataIndex: "realName", key: "realName" }, { title: "部门", dataIndex: "deptName", key: "deptName" }, { title: "职位", dataIndex: "position", key: "position" }, { title: "操作", key: "action", render: (_: unknown, record: UserItem) => ( ), }, ]; return (
setKeyword(e.target.value)} onSearch={fetchData} />
); } ``` --- ### 4.3 使用 API 层 #### 4.3.1 请求方法速查 API 层基于 Umi 原生 `request` 插件(底层 umi-request),封装了 6 个便捷方法: ```ts import { get, post, put, del, upload, download } from "@/api/request"; import type { Result, PageResult } from "@/types"; // GET —— 自动解包 data 字段 const users = await get>("/api/system/users", { page: 1, size: 10 }); // POST await post("/api/system/users", { username: "zhangsan", realName: "张三" }); // PUT await put("/api/system/users/123", { realName: "张三丰" }); // DELETE await del("/api/system/users/123"); // 文件上传 const formData = new FormData(); formData.append("file", file); await upload("/api/files/upload", formData); // 文件下载 const blob = await download("/api/files/export", { startDate: "2026-01-01" }); ``` #### 4.3.2 模块 API 文件规范 在模块的 `api/index.ts` 中,先定义类型,再定义接口函数: ```ts // modules/system/api/index.ts import { get, post, put, del } from "@/api/request"; import type { PageResult } from "@/types"; // ① 类型定义 export interface UserItem { id: string; username: string; realName: string; email: string; deptName: string; position: string; status: "active" | "disabled"; roles: string[]; } // ② 接口函数 export function getUserList(params: { page: number; size: number; keyword?: string }) { return get>("/api/system/users", params); } export function createUser(data: Partial) { return post("/api/system/users", data); } export function updateUser(id: string, data: Partial) { return put(`/api/system/users/${id}`, data); } export function deleteUser(id: string) { return del(`/api/system/users/${id}`); } ``` #### 4.3.3 请求拦截器(已内置) `src/app.tsx` 中配置了全局拦截器,无需各模块重复编写: | 功能 | 说明 | | --------------- | ------------------------------------------------------------ | | Token 注入 | 自动从 `useAuthStore` 读取 Token,添加到 `Authorization: Bearer xxx` 请求头 | | 业务码检查 | `code !== 0` 时自动 `message.error` 提示错误信息 | | 401 处理 | 自动清除登录态,跳转到 `/login` | | 403 / 404 / 500 | 自动弹出对应的错误提示 | #### 4.3.4 开发阶段 Mock 在模块 API 文件中使用 Mock 工具模拟数据,联调时替换为真实请求: ```ts import { mockDelay, mockResult, mockPageResult } from "@/api/mock"; export async function getUserList(params: { page: number; size: number }) { await mockDelay(300); // 模拟 300ms 网络延迟 const mockUsers = [ { id: "1", username: "admin", realName: "陈思远", deptName: "研发中心" }, { id: "2", username: "hr01", realName: "刘若曦", deptName: "人力资源部" }, ]; return mockPageResult(mockUsers, params.page, params.size, 100); // → { total: 100, page: 1, size: 10, records: [...] } } ``` **联调时**删除 `mockDelay` 等 Mock 调用,替换为 `get/post` 即可。 --- ### 4.4 使用共享组件 以下组件在 `@/components/` 下,所有模块可直接使用: | 组件 | 用途 | 典型用法 | | ------------------- | ------------------------ | ------------------------------------------------------------ | | `FloatingCard` | 毛玻璃浮岛卡片容器 | `
内容
` | | `KpiIsland` | KPI 统计卡片(数字滚动) | `` | | `SafeText` | 敏感数据毛玻璃脱敏 | `¥ 12,000` | | `StellarTimeline` | 审批节点时间线 | `` | | `DotSpinner` | 多点绕圈加载动画 | `` | | `DotSpinnerOverlay` | 全屏加载浮层 | ``(Suspense fallback 已内置) | | `Placeholder` | 未开发页面的占位 | `` | | `StarCanvas` | 星空背景粒子 | 已在布局中全局渲染,页面中无需重复使用 | | `AgentOrb` | AI 助手入口浮球 | 已在布局中全局渲染 | **`FloatingCard` 参数说明:** | 参数 | 类型 | 默认值 | 说明 | | ----------- | --------------- | ------- | ------------------------------------ | | `hover` | `boolean` | `true` | 是否启用悬停上浮效果 | | `float` | `boolean` | `false` | 是否启用低频浮动动画 | | `delay` | `number` | `0` | 入场动画延迟(秒) | | `className` | `string` | — | 额外 CSS 类名 | | `style` | `CSSProperties` | — | 额外内联样式 | | `onClick` | `() => void` | — | 点击回调(设置后 cursor 变 pointer) | **`DotSpinner` 参数说明:** | 参数 | 类型 | 默认值 | 说明 | | ---------- | -------- | ------------------ | ------------------------ | | `size` | `number` | `56` | 整体直径(px) | | `dotSize` | `number` | `5` | 每个圆点的直径(px) | | `dotCount` | `number` | `12` | 圆点数量 | | `color` | `string` | `"var(--primary)"` | 圆点颜色(自动跟随主题) | --- ### 4.5 角色差异化(不同角色不同页面内容) 框架在三个层面支持角色差异化:**菜单可见性**、**仪表盘内容**、**页面内元素控制**。 #### 4.5.1 菜单可见性 `menu.ts` 中每个菜单项都有可选的 `roles` 字段: ```ts // 整个大菜单仅 admin 可见 { key: "system", label: "系统管理", icon: "🛡️", roles: ["admin"], children: [...] } // 大菜单全员可见,但子菜单单独控制 { key: "attendance", label: "考勤管理", icon: "⏰", children: [ { key: "...leaves", label: "请假管理", path: "/attendance/leaves" }, // 全员 { key: "...groups", label: "考勤组管理", path: "...", roles: ["admin", "hr"] }, // 仅 admin/hr ] } ``` - `roles` 不写 → 全员可见 - Sidebar 自动递归过滤,无需改组件代码 - 顶栏角色切换器可实时预览 #### 4.5.2 仪表盘角色差异化 `dashboard.ts` 中 `dashboardRoleConfig` 定义每个角色看到的组件区域(从上到下顺序): ```ts // src/config/dashboard.ts export const dashboardRoleConfig: Record = { admin: ["welcome", "kpi-global", "quickEntries", "pendingApproval", "teamAttendance", "salarySummary", "timeline", "systemStatus"], hr: ["welcome", "kpi-global", "quickEntries", "pendingApproval", "teamAttendance", "timeline"], manager: ["welcome", "kpi-dept", "quickEntries", "pendingApproval", "teamAttendance", "timeline"], employee: ["welcome", "kpi-personal", "quickEntries", "pendingApproval", "myAttendance", "timeline"], finance: ["welcome", "kpi-global", "quickEntries", "salarySummary", "pendingApproval", "timeline"], }; ``` **增加新的仪表盘区域:** 1. 在 `dashboard.ts` 的类型 `DashboardWidget` 中新增一个 key 2. 在 `src/pages/index.tsx` 的 `widgetMap` 中添加对应的组件 3. 在 `dashboardRoleConfig` 中为需要的角色添加该 key #### 4.5.3 任意页面中的角色控制 同样的模式可以用于任何页面——不仅仪表盘: ```tsx import { useAppStore } from "@/store/useAppStore"; function MyPage() { const role = useAppStore((s) => s.role); const isMgmt = ["admin", "hr", "manager"].includes(role); return ( <> {/* 按钮级别:只有管理员能看到删除按钮 */} {role === "admin" && } {/* Tab 级别:不同角色看到不同 Tab */} }, ...(isMgmt ? [{ key: "manage", label: "管理", children: }] : []), ]} /> ); } ``` --- ### 4.6 页面加载态处理 路由懒加载的 chunk 下载阶段已由 `layouts/index.tsx` 中的 `}>` 统一处理,无需各页面重复配置。 页面内部的数据请求加载态需自行处理,推荐模式: ```tsx import DotSpinner from "@/components/DotSpinner"; import { Result, Button } from "antd"; export default function MyPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // 加载中 → DotSpinner if (loading) { return (
); } // 加载失败 → 错误提示 + 重试 if (error) { return ( 重试} /> ); } // 无数据 → 空状态 if (!data.length) { return ; } // 正常渲染 return
; } ``` > **DotSpinner vs Ant Design Spin:** 全屏/大面积加载推荐 `DotSpinner`(贴合浮岛星际风格);表格内加载推荐 Ant Design `
`(保留列头骨架,用户可提前看到结构)。 --- ## 5. 鉴权说明 ### 当前状态(开发阶段) - **登录:** 任意账号密码可登录,`admin` 用户角色为 `admin`,其他用户也为 `admin`(可在 `login.tsx` 中修改 Mock 逻辑) - **角色切换:** 顶栏右侧下拉框,可自由切换角色查看不同菜单 - **路由守卫:** `layouts/index.tsx` 中检查 `isLoggedIn`,未登录时自动重定向到 `/login` - **登录持久化:** Token 和用户信息通过 Zustand + `persist` 中间件存入 localStorage(key: `hrms-auth-store`) ### 相关文件 | 文件 | 作用 | | --------------------------- | ----------------------------------------------- | | `src/pages/login.tsx` | 登录页面(联调时替换 Mock 登录为真实 API 调用) | | `src/store/useAuthStore.ts` | Token / RefreshToken / 用户信息持久化存储 | | `src/layouts/index.tsx` | 登录态检查 + 未登录重定向 | | `src/app.tsx` | request 拦截器:自动注入 Token / 401 处理 | ### 联调时接入真实登录 修改 `src/pages/login.tsx` 中的 `handleLogin` 函数: ```ts // 删除这段 Mock 代码: // await new Promise((r) => setTimeout(r, 800)); // const mockToken = "..."; // login(mockToken, mockRefreshToken, { ... }); // 替换为真实 API 调用: import { login as loginApi } from "@/api/request"; const res = await loginApi({ username, password }); const { token, refreshToken, userInfo } = res; login(token, refreshToken, userInfo); ``` ### Token 刷新机制 `src/app.tsx` 的响应拦截器中,401 错误会自动清除登录态并跳转 `/login`。如需实现无感刷新 Token,在拦截器中添加 refresh 逻辑即可。 --- ## 6. 主题与样式 ### 6.1 CSS 变量速查 所有 CSS 变量定义在 `src/global.css` 的 `:root`、`[data-theme="light"]`、`[data-theme="dark"]` 中,可在任何组件中直接使用: ```css /* ---- 背景 ---- */ background: var(--island-bg); /* 页面渐变背景 */ background: var(--surface); /* 毛玻璃半透明表面 */ background: var(--surface-solid); /* 不透明表面 */ background: var(--primary-soft); /* 主题色淡化背景(10%) */ /* ---- 文字 ---- */ color: var(--text-primary); /* 主文字色 */ color: var(--text-secondary); /* 次要文字色 */ color: var(--text-tertiary); /* 辅助/占位文字色 */ /* ---- 边框 ---- */ border: 1px solid var(--border); /* 普通边框 */ border: 1px solid var(--border-strong); /* 强调边框 */ /* ---- 主题色 ---- */ color: var(--primary); /* 主色(日间蓝 #002FA7 / 夜间荧光蓝 #4DD0FF) */ box-shadow: 0 0 20px var(--glow); /* 主题色辉光 */ /* ---- 阴影 ---- */ box-shadow: var(--shadow-island); /* 浮岛投影 */ ``` ### 6.2 预置 CSS 类 ```html
悬停上浮 4px 的卡片
不上浮的静态卡片
衬线标题字体(Noto Serif SC)等宽数字字体(JetBrains Mono / tabular-nums)脉冲呼吸(2.8s 循环)流动连接线(3.5s 循环)
低频上下浮动(7s 循环)
路由切换淡入(0.5s)
退出淡出(0.8s)
毛玻璃模糊(hover 变清晰)
旋转容器
``` ### 6.3 动画关键帧 | 关键帧名称 | 时长 | 曲线 | 用途 | | -------------------- | ---- | ------------------------------ | --------------- | | `page-fade-in` | 0.5s | cubic-bezier(0.22, 1, 0.36, 1) | 路由切换淡入 | | `stellar-pulse` | 2.8s | ease-in-out | 脉冲呼吸 | | `stellar-flow` | 3.5s | linear | 边框流动 | | `gentle-float` | 7s | ease-in-out | 低频浮动 | | `sunset-fade` | 0.8s | cubic-bezier(0.22, 1, 0.36, 1) | 退出淡出 | | `dot-spinner-rotate` | 1.2s | linear | 加载点绕圈旋转 | | `dot-spinner-fade` | 1.2s | ease-in-out | 加载点淡入淡出 | | `orb-planet-spin` | 8s | linear | AI 浮球星球旋转 | | `orb-bot-float` | 3s | ease-in-out | AI 浮球上下浮动 | > 所有时长均为刻意放慢的"低频"节奏。如需调快/慢,搜索对应关键帧名称直接修改秒数即可。 --- ## 7. 模块与负责人 | 目录 | 模块 | 后端模块 | 页面数 | 菜单数 | | ----------------------- | ------------- | ------------------------ | ------ | ------ | | `modules/system/` | M1 系统管理 | hrms-module-system | 7 | 7 | | `modules/organization/` | M2 组织架构 | hrms-module-organization | 2 | 2 | | `modules/employee/` | M3 员工档案 | hrms-module-employee | 1 | 1 | | `modules/hrchange/` | M4 入转调离 | hrms-module-hrchange | 4 | 4 | | `modules/approval/` | M5 审批中心 | hrms-module-approval | 5 | 5 | | `modules/attendance/` | M6 考勤管理 | hrms-module-attendance | 6 | 5 | | `modules/salary/` | M7 薪资管理 | hrms-module-salary | 5 | 5 | | `modules/personal/` | M8 个人中心 | M8 PersonalController | 5 | 5 | | `modules/agent/` | M9 AI智能助手 | Python Agent (:8000) | 5 | 5 | **独立页面:** | 路径 | 说明 | | --------------- | -------------------------------- | | `/login` | 登录页 | | `/` | 仪表盘主页(按角色组合不同区域) | | `/notification` | 消息中心 | | `*` | 404 页面 | --- ## 8. 构建与部署 ### 8.1 环境变量 | 文件 | 用途 | Git 跟踪 | | ------------------ | ------------ | ------------------ | | `.env.development` | 开发环境变量 | ✅ 提交(团队共享) | | `.env.production` | 生产环境变量 | ✅ 提交 | | `.env` | 兜底默认值 | ✅ 提交 | **可用变量:** | 变量 | 开发环境 | 生产环境 | 说明 | | ------------------------ | --------------------- | --------------- | ----------------------------------------------------- | | `UMI_APP_API_BASE_URL` | `/api/v1` | `/api/v1` | 后端 API 前缀。开发时 Umi 自动代理到 `localhost:8080` | | `UMI_APP_AGENT_BASE_URL` | `/agent` | `/agent` | Agent 服务前缀。开发时代理到 `localhost:8000` | | `UMI_APP_TITLE` | `HRMS·浮岛星际(开发)` | `HRMS·浮岛星际` | 页面标题 | > **注意:** Umi 4 要求自定义环境变量以 `UMI_APP_` 开头,否则不会被注入到客户端代码中。 **在代码中访问环境变量:** ```ts // Umi 4 环境变量通过 process.env 访问 const apiUrl = process.env.UMI_APP_API_BASE_URL; const title = process.env.UMI_APP_TITLE; ``` **添加新变量:** 1. 在 `.env.development` / `.env.production` 中定义(必须以 `UMI_APP_` 开头) 2. 在 TypeScript 文件中通过 `process.env.UMI_APP_XXX` 访问 ### 8.2 开发代理 `.umirc.ts` 中配置了代理规则: ``` 浏览器请求 /api/xxx → 代理到 http://localhost:8080/api/xxx (后端 Java) 浏览器请求 /agent/xxx → 代理到 http://localhost:8000/agent/xxx (Python Agent) ``` 后端地址变更时,直接修改 `.umirc.ts` 的 `proxy` 配置即可。 ### 8.3 构建拆包策略 生产构建时按依赖来源拆分为独立 chunk,避免单一文件过大: | chunk | 内容 | 说明 | | --------------- | -------------------------------------------- | ---------- | | `vendor-react` | react + react-dom + react-router + scheduler | React 核心 | | `vendor-antd` | antd + @ant-design/icons | UI 组件库 | | `vendor-motion` | framer-motion | 动画库 | | `vendor-utils` | axios + dayjs + zustand + clsx + nprogress | 工具库 | | `umi` | Umi 框架运行时代码 | 框架核心 | 各业务页面以动态 import 的方式独立打包为 chunk,只在首次访问时下载。 ### 8.4 构建与预览 ```bash # 生产构建(输出到 dist/) npm run build # 本地预览生产构建 npm run preview ``` 构建产物在 `dist/` 目录下,直接部署到 Nginx / CDN 等静态服务器即可。部署时注意配置 SPA fallback(所有路由指向 `index.html`)。 --- ## 9. 常见问题 ### Q: 启动报错 / 白屏? A: 依次检查: 1. `npm install` 是否完整执行,有无报错 2. 后端是否已启动(虽然开发阶段是 Mock,但 Umi 的代理会尝试连接后端) 3. 清除 `node_modules/.cache` 和浏览器缓存后重试 ### Q: 我改了菜单但侧边栏没有变化? A: 检查 `menu.ts` 中新菜单的 `roles` 字段是否正确。如果设置了 `roles: ["admin"]`,只有管理员能看到(用顶栏角色选择器切换到 admin)。 ### Q: 路由跳转后页面空白? A: 检查三步是否都完成:① 业务页面文件已创建(`modules/` 下)② 路由页面 re-export 已添加(`pages/` 下)③ 菜单项已添加(`menu.ts`)。三个路径的命名必须一致(大小写敏感)。 ### Q: API 请求报 401? A: 开发阶段可能是 Token 过期或 localStorage 数据损坏。清除浏览器 localStorage 中的 `hrms-auth-store` 和 `hrms-app-store`,重新登录即可。 ### Q: 页面数据加载时一片空白很难看? A: 推荐使用 `DotSpinner` 组件填充加载态(参见 [4.6 页面加载态处理](#46-页面加载态处理))。路由懒加载的 chunk 下载阶段已由布局中的 `` 统一处理。 ### Q: 如何在页面中显示不同的 loading / error / empty 状态? A: 推荐使用"三段式"模式: ```tsx if (loading) return
; if (error) return 重试} />; if (!data.length) return } title="暂无数据" />; return
; ``` ### Q: 如何导入共享组件? A: 使用路径别名 `@/`(映射到 `src/`): ```ts import FloatingCard from "@/components/FloatingCard"; import DotSpinner from "@/components/DotSpinner"; import { useAppStore } from "@/store/useAppStore"; import { useAuthStore } from "@/store/useAuthStore"; import type { Employee, Approval } from "@/types"; import { cn } from "@/lib/utils"; ``` ### Q: 我的代码和其他人的冲突了怎么办? A: 每个人只改自己模块的目录(`modules/{模块名}/`)+ `pages/` 下对应的路由 re-export + `menu.ts` 中对应的菜单条目。只要不跨模块修改,冲突概率很低。跨模块的公共改动(`components/` / `store/` / `theme/` / `app.tsx` / `global.css`)需要团队沟通。 ### Q: 开发阶段如何看到 DotSpinner? A: 路由懒加载在本地几乎瞬间完成(chunk 已在内存中)。可以用以下方法模拟: 1. DevTools → Network → 选择 "Slow 3G" 节流 2. 强制刷新页面后切换到未访问过的路由 页面内的数据加载态(手动 `loading` state 控制的 DotSpinner)正常开发就能看到。 ### Q: Tailwind 的 `preflight: false` 是什么意思? A: Tailwind 默认会重置所有浏览器默认样式(类似 normalize.css),这会覆盖 Ant Design 组件的样式。设置 `preflight: false` 关闭重置,让 Tailwind 的工具类(如 `flex`、`text-center`)和 Ant Design 和平共存。 ### Q: `src/pages/` 和 `src/modules/` 的区别是什么? A: - `src/pages/` = Umi 约定式路由的扫描目录(文件路径即 URL 路径),每个文件只有一行 re-export - `src/modules/` = 业务代码的实际存放目录(组件 + API + 类型),按模块划分目录 两者之间通过 `export { default } from '@/modules/...'` 连接。这样做的目的是让路由和业务代码解耦——多人协作时各自只改自己模块的目录。 --- > **版本:** 1.0 > **最后更新:** 2026-07-14 > **配套配置文件:** `.umirc.ts` · `.env.development` · `.env.production` · `tsconfig.json` · `tailwind.config.js`