0df8aa77d9
- 员工花名册管理(加密存储、导入导出) - 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条) - 社保公积金(多城市配置、版本管理、基数调整) - 解聘管理(6步流程、证据链、工作交接) - AI 助手(合同审查、风险预测、RAG 知识库) - Dashboard 仪表盘 - 设置与通知
1148 lines
39 KiB
Markdown
1148 lines
39 KiB
Markdown
# AIHR 前端 UI/UX 梳理与优化方向
|
||
|
||
> 针对中小微企业人事/财务人员的使用场景,对现有前端进行全面梳理并提出优化建议。
|
||
>
|
||
> 日期:2026-07-24
|
||
|
||
---
|
||
|
||
## 〇、竞品参考标杆
|
||
|
||
### 0.1 选型逻辑
|
||
|
||
AIHR 定位为"中小微企业用工合规助手",核心用户是 1-2 人的 HR/财务兼管人员。选参考标杆时,排除北森(太重、实施 3-6 个月)、Rippling(HR+IT 一体化,偏离核心场景),聚焦以下 4 个标杆产品:
|
||
|
||
### 0.2 标杆产品矩阵
|
||
|
||
| 产品 | 定位 | 参考价值 | UI/UX 特点 |
|
||
|------|------|----------|------------|
|
||
| **BambooHR** | 中端市场 HRIS(50-500 人) | **UI/UX 黄金标准** | 46% 用户评价中专门提及界面质量;简洁现代,非 HR 人员也能快速上手;员工自助体验最佳 |
|
||
| **Gusto** | 美国小企业薪税合规(<50 人) | **薪税+合规体验** | "set it and forget it" 自动化薪税;界面极简,创始人自己就能跑薪税;$49+$6/人,性价比最高 |
|
||
| **飞书人事** | 中国协同办公生态 HR 模块 | **本土化+集成体验** | 与 IM/文档/审批原生打通;组织架构拖拉拽调整;员工点击头像即看全档案;零学习成本 |
|
||
| **薪人薪事** | 中国中小企业薪酬社保 SaaS | **薪酬社保深度** | 薪酬社保为核心优势;50-200 人企业首选;界面操作直观,部署速度快 |
|
||
|
||
### 0.3 各标杆关键设计借鉴
|
||
|
||
#### BambooHR — UI/UX 第一梯队
|
||
|
||
| 设计要点 | 借鉴方向 |
|
||
|----------|----------|
|
||
| 左侧导航栏(非顶部 tab) | 信息层级清晰,可折叠,不占垂直空间 |
|
||
| 员工档案卡片式布局 | 头照 + 基本信息 + 标签,视觉温度 |
|
||
| Onboarding Checklist | 新员工入职清单,进度条可视化 |
|
||
| PTO 申请日历视图 | 请假/考勤用日历而非表格 |
|
||
| 报表 + 人员分析 Dashboard | 图表驱动,非纯数字 |
|
||
| 移动端体验 | 员工自助(查工资条/请假/签合同)流畅 |
|
||
|
||
#### Gusto — 薪税合规极简体验
|
||
|
||
| 设计要点 | 借鉴方向 |
|
||
|----------|----------|
|
||
| 薪税运行向导(Run Payroll) | 3 步引导:确认人数 → 核对金额 → 一键发放 |
|
||
| 合规提醒卡片 | "你需要在本月 15 日前完成社保申报" 式主动提醒 |
|
||
| 自动报税 + 状态追踪 | 薪税计算后显示"已申报/待申报/已完成"状态 |
|
||
| 透明定价展示 | 套餐/额度在设置页面清晰展示 |
|
||
| 员工自助入职 | 员工自己填信息+传材料,HR 只需审核 |
|
||
|
||
#### 飞书人事 — 本土化集成
|
||
|
||
| 设计要点 | 借鉴方向 |
|
||
|----------|----------|
|
||
| 组织架构树 + 拖拽调整 | 部门管理可视化,非纯文本 |
|
||
| 人事流程无代码配置 | 审批/通知可自定义,适应不同企业 |
|
||
| 全生命周期数据汇聚 | 入职→在职→离职数据在一个档案页 |
|
||
| 与 IM 消息打通 | 审批/通知推送到微信/钉钉/飞书 |
|
||
| 移动端 = 桌面端功能对等 | 不阉割功能,移动端可完成所有操作 |
|
||
|
||
#### 薪人薪事 — 薪酬社保深度
|
||
|
||
| 设计要点 | 借鉴方向 |
|
||
|----------|----------|
|
||
| 薪酬结构可视化编辑 | 薪酬项拖拽排序,公式可视化 |
|
||
| 社保基数自动核定 | 按城市/基数上下限自动计算,非手动填 |
|
||
| 工资条一键群发 + 员工确认 | 批量发送 → 员工端查看确认 → 状态追踪 |
|
||
| 薪酬变更历史时间线 | 调薪记录以时间线展示,可追溯 |
|
||
| 社保公积金月度申报表 | 自动生成申报表,导出 Excel/PDF |
|
||
|
||
### 0.4 AIHR 对标差距与借鉴优先级
|
||
|
||
| 维度 | 标杆 | AIHR 现状 | 差距 | 借鉴优先级 |
|
||
|------|------|-----------|------|------------|
|
||
| **导航架构** | BambooHR(左侧栏) | 顶部 6 tab 平铺 | 无层级、认知负担重 | 高 |
|
||
| **字号/间距** | BambooHR / Gusto | text-xs 全局 | 过小,阅读疲劳 | 高 |
|
||
| **薪税向导** | Gusto(3 步引导) | 4 个子 tab 手动操作 | 缺少引导式流程 | 高 |
|
||
| **合规提醒** | Gusto(主动卡片) | Dashboard 待办列表 | 有但不够突出 | 中 |
|
||
| **员工档案** | 飞书人事(全生命周期) | 11 个 tab 平铺 | 信息分散,缺概览 | 中 |
|
||
| **组织架构** | 飞书人事(可视化树) | 无 | 完全缺失 | 低(中小微部门少) |
|
||
| **数据可视化** | BambooHR(图表驱动) | 纯数字+图标 | 无趋势/分布图 | 中 |
|
||
| **员工自助端** | BambooHR / 飞书 | 有但无导航 | 页面间无法跳转 | 中 |
|
||
| **Toast 反馈** | 所有标杆 | 无 | 操作无即时反馈 | 高 |
|
||
| **移动端体验** | BambooHR(功能对等) | 底部 6 tab 拥挤 | 表格不响应式 | 高 |
|
||
|
||
### 0.5 推荐设计方向总结
|
||
|
||
> **以 BambooHR 的 UI 质量为视觉标杆,以 Gusto 的薪税合规向导为交互范式,以飞书人事的全生命周期档案为信息架构参考,以薪人薪事的薪酬社保深度为功能标杆。**
|
||
|
||
具体而言:
|
||
1. **导航** → 参考 BambooHR 改为左侧栏(桌面端)+ 底部 4 tab(移动端)
|
||
2. **薪税流程** → 参考 Gusto 改为引导式向导,而非 4 个 tab 手动切换
|
||
3. **员工档案** → 参考飞书人事,增加"概览"首 tab,汇总关键信息
|
||
4. **合规提醒** → 参考 Gusto,在 Dashboard 突出"本月必做"卡片
|
||
5. **视觉风格** → 参考 BambooHR,提升字号/间距/色彩温度
|
||
|
||
---
|
||
|
||
## 一、现状概览
|
||
|
||
### 1.1 技术栈
|
||
|
||
| 层面 | 技术 |
|
||
|------|------|
|
||
| 框架 | React 18 + TypeScript |
|
||
| 构建 | Vite 5 |
|
||
| 样式 | Tailwind CSS 3(自定义 token:primary/danger/warning/safe/surface) |
|
||
| 路由 | React Router v6 |
|
||
| 状态 | Zustand(auth)+ TanStack Query(服务端数据) |
|
||
| 表单 | react-hook-form + zod |
|
||
| 图标 | lucide-react |
|
||
| 其他 | qrcode.react, jsPDF, xlsx |
|
||
|
||
### 1.2 页面结构
|
||
|
||
```
|
||
管理端(AdminLayout)
|
||
├── TopNav(顶部导航 h-14,6 个 tab)
|
||
├── PageContainer(max-w-content, px-4)
|
||
├── MobileTabBar(移动端底部 tab bar)
|
||
└── OnboardingGuide(首次引导遮罩)
|
||
|
||
路由页面:
|
||
├── / Dashboard(总览:概览/薪税/风险/任务 4 tab)
|
||
├── /roster Roster(花名册:员工列表 + 11 个详情 tab)
|
||
├── /money Money(薪税:发薪批次/薪酬模版/加班费/工资条 4 tab)
|
||
├── /social SocialInsurance(社保公积金:社保/公积金/月度 3 tab)
|
||
├── /termination Termination(解聘补偿:5 步向导)
|
||
├── /ai-assistant AIAssistant(AI 顾问:问答/预测/审查/案例/知识库 5 tab)
|
||
├── /settings Settings(设置:企业/用户/套餐/通知/导入 5 tab)
|
||
|
||
员工端(PortalLayout, max-w-md)
|
||
├── /portal/login 员工登录
|
||
├── /portal/payslip 工资条查看
|
||
├── /portal/contract 合同查看
|
||
├── /portal/onboarding 入职引导
|
||
└── /portal/contract-confirm 合同确认
|
||
|
||
未挂载页面(文件存在但未路由):
|
||
├── Compensation.tsx 薪酬调整
|
||
└── Contracts.tsx 合同管理
|
||
```
|
||
|
||
### 1.3 组件库
|
||
|
||
| 组件 | 说明 |
|
||
|------|------|
|
||
| Button | 3 variant(primary/secondary/danger)× 3 size |
|
||
| Card | 白底圆角阴影,p-3 |
|
||
| Input / Select / Label | 基础表单控件,text-xs |
|
||
| Modal | 4 size,居中弹窗 |
|
||
| Pagination | 分页 |
|
||
| EmptyState | 空状态 |
|
||
| Signal | 信号灯(风险指示) |
|
||
|
||
### 1.4 设计 Token
|
||
|
||
```js
|
||
colors: {
|
||
primary: { DEFAULT: '#2563EB', light: '#3B82F6', dark: '#1D4ED8' },
|
||
danger: '#DC2626',
|
||
warning: '#F59E0B',
|
||
safe: '#16A34A',
|
||
surface: '#F8FAFC',
|
||
}
|
||
maxWidth: { content: 'none' } // 宽屏不限制
|
||
```
|
||
|
||
---
|
||
|
||
## 二、问题诊断
|
||
|
||
### 2.1 信息架构
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **导航扁平,6 个 tab 平铺** | 中小微 HR 只有 1-2 人,6 个顶级 tab + 每页 3-5 个子 tab = 认知负担重 | 高 |
|
||
| **Contracts / Compensation 页面未挂载** | 合同管理和薪酬调整功能用户无法访问 | 高 |
|
||
| **Dashboard 内嵌薪税 tab** | 与 /money 页面功能重叠,用户困惑入口 | 中 |
|
||
| **设置入口隐蔽** | 仅在右上角下拉菜单,中小微用户不易发现 | 中 |
|
||
| **员工端无统一导航** | Portal 页面间无法跳转,只能通过链接进入 | 中 |
|
||
|
||
### 2.2 视觉设计
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **全局字号过小(text-xs 为主)** | 人事/财务人员长时间盯屏疲劳,35+ 用户阅读困难 | 高 |
|
||
| **卡片间距紧凑(p-3, gap-2)** | 信息密度过高,缺乏呼吸感,重要信息不突出 | 高 |
|
||
| **主色 #2563EB 偏冷** | B 端工具感强,缺乏温度,与"中小微企业友好"定位不符 | 中 |
|
||
| **max-w-content 为 none** | 超宽屏内容无限拉伸,1920px+ 屏幕阅读体验差 | 中 |
|
||
| **无数据可视化** | Dashboard 仅数字+图标卡片,无图表,趋势不直观 | 中 |
|
||
| **状态色未体系化** | 各页面自行定义颜色(blue-50/amber-50/cyan-50),未统一 token | 低 |
|
||
|
||
### 2.3 交互体验
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **无全局 Toast/通知** | 操作成功/失败无即时反馈,用户不确定操作是否生效 | 高 |
|
||
| **无 Loading 骨架屏** | 仅"加载中..."文字,长列表加载时白屏 | 中 |
|
||
| **Modal 无过渡动画** | 弹窗突现突消失,体验生硬 | 中 |
|
||
| **表单无防离开提示** | 填写一半误点导航数据丢失 | 中 |
|
||
| **批量操作无确认** | 批量解聘/续签缺少二次确认,误操作风险高 | 高 |
|
||
| **搜索无防抖** | 花名册搜索每次按键触发请求,浪费资源 | 低 |
|
||
| **无快捷键支持** | 高频操作(搜索/新建/保存)无键盘快捷方式 | 低 |
|
||
|
||
### 2.4 移动端适配
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **MobileTabBar 6 个 tab 拥挤** | iPhone SE 上每个 tab 仅 53px,图标+文字挤压 | 高 |
|
||
| **表格无响应式方案** | 花名册等列表页在手机上横向滚动体验差 | 高 |
|
||
| **Modal 在小屏溢出** | max-w-4xl 弹窗在手机上内容拥挤 | 中 |
|
||
| **员工端 max-w-md 偏窄** | 工资条信息在 tablet 上浪费空间 | 低 |
|
||
|
||
### 2.5 可访问性(a11y)
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **div onClick 模式** | TopNav 下拉菜单等用 div+onClick,键盘不可达 | 中 |
|
||
| **无 aria-label** | 图标按钮无语义标签,屏幕阅读器无法识别 | 中 |
|
||
| **对比度不足** | text-gray-400 on bg-white 对比度 ~2.5:1,低于 WCAG AA 4.5:1 | 中 |
|
||
| **无 focus-visible 样式** | 键盘导航时无法识别当前焦点元素 | 低 |
|
||
|
||
### 2.6 性能
|
||
|
||
| 问题 | 影响 | 严重度 |
|
||
|------|------|--------|
|
||
| **单文件过大** | Roster.tsx 140KB / Money.tsx 63KB / Termination.tsx 55KB,首屏加载慢 | 高 |
|
||
| **无代码分割** | 所有页面打包在一个 chunk,无 lazy loading | 高 |
|
||
| **无虚拟列表** | 花名册大量员工时 DOM 节点过多 | 中 |
|
||
|
||
---
|
||
|
||
## 三、优化方向
|
||
|
||
### 3.1 信息架构重组(优先级:高)
|
||
|
||
**目标:降低认知负担,让 HR 3 秒找到功能入口。**
|
||
|
||
#### 方案:二级导航 + 功能聚合
|
||
|
||
```
|
||
顶部导航(h-14)
|
||
├── Logo + 企业名
|
||
├── 主导航(4 个入口,合并相关功能)
|
||
│ ├── 总览 → Dashboard(保留概览+风险+任务,移除薪税 tab)
|
||
│ ├── 员工管理 → Roster + Contracts(合并,花名册内增加合同 tab)
|
||
│ ├── 薪税社保 → Money + SocialInsurance(合并为一个页面组)
|
||
│ └── AI 顾问 → AIAssistant
|
||
├── 右侧:通知铃铛(badge) + 设置齿轮 + 用户头像
|
||
|
||
移动端底部导航(4 个 tab)
|
||
├── 总览
|
||
├── 员工
|
||
├── 薪税
|
||
└── AI
|
||
```
|
||
|
||
#### 具体调整
|
||
|
||
1. **合并 Contracts → Roster**:花名册详情中已有合同 tab,Contracts.tsx 独立页面无入口,应删除或合并
|
||
2. **合并 Compensation → Money**:薪酬调整功能应作为薪税页面的一个子 tab
|
||
3. **Dashboard 去掉薪税 tab**:避免与 /money 重复,保留概览/风险/任务 3 个 tab
|
||
4. **设置入口可见化**:齿轮图标直接放在导航栏,而非藏在下拉菜单
|
||
|
||
### 3.2 视觉设计升级(优先级:高)
|
||
|
||
#### 3.2.1 字号体系
|
||
|
||
```
|
||
当前:全局 text-xs (12px) → 目标:text-sm (14px) 为正文基准
|
||
|
||
层级 当前 目标 用途
|
||
h1 (页面标题) text-xs text-base 页面顶部标题
|
||
h2 (区块标题) text-sm text-sm 卡片标题
|
||
正文/表格 text-xs text-sm 列表、表格、表单
|
||
辅助文字 text-xs text-xs 时间戳、备注(保持)
|
||
数字大屏 无 text-2xl Dashboard 统计数字
|
||
```
|
||
|
||
#### 3.2.2 间距体系
|
||
|
||
```
|
||
当前:p-3 / gap-2 → 目标:p-4 / gap-3(增加呼吸感)
|
||
|
||
组件 当前 目标
|
||
Card p-3 p-4
|
||
页面间距 gap-2 gap-4
|
||
表格行高 紧凑 py-2.5(增加行高)
|
||
```
|
||
|
||
#### 3.2.3 主色调暖
|
||
|
||
```js
|
||
// 当前:冷蓝 #2563EB
|
||
// 建议:indigo-600 #4F46E5,略带温度,专业且亲和
|
||
colors: {
|
||
primary: { DEFAULT: '#4F46E5', light: '#6366F1', dark: '#4338CA' },
|
||
}
|
||
```
|
||
|
||
#### 3.2.4 内容最大宽度
|
||
|
||
```js
|
||
// 当前:max-w-content = none(无限拉伸)
|
||
// 建议:限制 1280px,超宽屏居中
|
||
maxWidth: { content: '1280px' }
|
||
```
|
||
|
||
#### 3.2.5 数据可视化
|
||
|
||
- Dashboard 概览增加 **迷你折线图**(月度薪税趋势)
|
||
- 风险分布增加 **环形图**(高/中/低风险占比)
|
||
- 引入 `recharts`(轻量,~40KB gzip)
|
||
|
||
### 3.3 交互体验增强(优先级:高)
|
||
|
||
#### 3.3.1 全局 Toast
|
||
|
||
```tsx
|
||
// 引入 sonner(~5KB gzip)
|
||
import { toast } from 'sonner'
|
||
|
||
// 成功/失败/警告 统一调用
|
||
toast.success('员工已添加')
|
||
toast.error('删除失败,请重试')
|
||
toast.warning('该操作不可撤销')
|
||
```
|
||
|
||
#### 3.3.2 批量操作二次确认
|
||
|
||
```tsx
|
||
// 危险操作(批量解聘/删除)必须 Dialog 二次确认
|
||
<Dialog open={confirmOpen}>
|
||
确认对 {selectedIds.size} 名员工执行解聘操作?
|
||
此操作不可撤销。
|
||
</Dialog>
|
||
```
|
||
|
||
#### 3.3.3 骨架屏
|
||
|
||
```tsx
|
||
// 替换"加载中..."文字
|
||
function TableSkeleton() {
|
||
return Array.from({ length: 5 }).map(i => (
|
||
<div className="h-10 bg-gray-100 rounded animate-pulse mb-2" />
|
||
))
|
||
}
|
||
```
|
||
|
||
#### 3.3.4 搜索防抖
|
||
|
||
```tsx
|
||
// 使用 useDeferredValue 或 lodash.debounce
|
||
const debouncedSearch = useDeferredValue(search)
|
||
// queryKey 使用 debouncedSearch
|
||
```
|
||
|
||
#### 3.3.5 表单防离开
|
||
|
||
```tsx
|
||
// react-hook-form 已支持
|
||
const form = useForm({ mode: 'onChange' })
|
||
useEffect(() => {
|
||
const handler = (e: BeforeUnloadEvent) => {
|
||
if (form.formState.isDirty) {
|
||
e.preventDefault()
|
||
e.returnValue = ''
|
||
}
|
||
}
|
||
window.addEventListener('beforeunload', handler)
|
||
return () => window.removeEventListener('beforeunload', handler)
|
||
}, [form.formState.isDirty])
|
||
```
|
||
|
||
### 3.4 移动端优化(优先级:高)
|
||
|
||
#### 3.4.1 底部导航精简为 4 个
|
||
|
||
```
|
||
当前 6 个:总览 / 花名册 / 薪税 / 社保 / 解聘补偿 / AI
|
||
目标 4 个:总览 / 员工 / 薪税 / AI
|
||
(社保、解聘补偿归入对应主页面子功能)
|
||
```
|
||
|
||
#### 3.4.2 表格响应式
|
||
|
||
- 手机端:卡片列表模式(每行一个卡片,字段竖排)
|
||
- 平板端:紧凑表格(隐藏低优先级列)
|
||
- 桌面端:完整表格
|
||
|
||
#### 3.4.3 Modal 移动端全屏
|
||
|
||
```tsx
|
||
// 移动端 Modal 全屏展示
|
||
className={clsx(
|
||
'relative bg-white shadow-xl w-full max-h-[90vh] overflow-y-auto',
|
||
isMobile ? 'h-screen rounded-none' : 'rounded-lg',
|
||
sizeClass,
|
||
)}
|
||
```
|
||
|
||
### 3.5 可访问性(优先级:中)
|
||
|
||
- **替换 div onClick**:下拉菜单用 `<button>` + `aria-expanded`
|
||
- **添加 aria-label**:图标按钮统一添加 `aria-label="xxx"`
|
||
- **修复对比度**:`text-gray-400` → `text-gray-500`(对比度 ≥ 4.5:1)
|
||
- **focus-visible**:`focus-visible:ring-2 focus-visible:ring-primary`
|
||
|
||
### 3.6 性能优化(优先级:高)
|
||
|
||
#### 3.6.1 路由懒加载
|
||
|
||
```tsx
|
||
// App.tsx 改为 lazy import
|
||
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
||
const Roster = lazy(() => import('./pages/Roster'))
|
||
// ...
|
||
<Suspense fallback={<PageSkeleton />}>
|
||
<Routes>...</Routes>
|
||
</Suspense>
|
||
```
|
||
|
||
#### 3.6.2 大文件拆分
|
||
|
||
| 文件 | 当前大小 | 拆分方向 |
|
||
|------|----------|----------|
|
||
| Roster.tsx | 140KB | 拆为 RosterList + EmployeeDetail + 子 tab 组件 |
|
||
| Money.tsx | 63KB | 拆为 BatchManager + TemplateManager + OvertimeCalc + PayslipManager |
|
||
| Termination.tsx | 55KB | 拆为向导各步骤独立组件 |
|
||
| Settings.tsx | 46KB | 拆为各 section 独立组件 |
|
||
|
||
#### 3.6.3 虚拟列表
|
||
|
||
```tsx
|
||
// 花名册员工数 > 100 时使用虚拟列表
|
||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||
```
|
||
|
||
---
|
||
|
||
## 三-A、详细优化实施方案
|
||
|
||
> 以下为每个 Phase 的具体文件修改清单、代码示例和验收标准。
|
||
|
||
### Phase 1:基础体验修复(1-2 天)
|
||
|
||
#### P1.1 全局字号提升
|
||
|
||
| 文件 | 修改内容 | 行号参考 |
|
||
|------|----------|----------|
|
||
| `src/index.css` | `.btn` text-xs → text-sm;`.input` text-xs → text-sm;`.label` text-xs → text-sm | L24, L39, L42 |
|
||
| `src/index.css` | `h1` text-base → text-lg;`h2` text-sm → text-base | L17-18 |
|
||
| `src/components/ui/Button.tsx` | size sm: text-xs→text-xs(保持);md: text-xs→text-sm;lg: text-sm→text-base | L18-20 |
|
||
| `src/components/ui/Input.tsx` | Input/Select text-xs → text-sm | L10, L25 |
|
||
| `src/components/ui/Pagination.tsx` | text-xs → text-sm(页码、条数信息) | L42, L69, L74 |
|
||
| `src/components/ui/EmptyState.tsx` | title text-sm→text-base;description text-xs→text-sm | L19-20 |
|
||
| `src/components/layout/TopNav.tsx` | tab 文字 text-sm(保持);用户名 text-sm→text-sm | L42 |
|
||
| `src/components/layout/MobileTabBar.tsx` | tab label text-xs→text-xs(保持,移动端紧凑) | L33 |
|
||
|
||
**验收标准**:正文内容 14px,页面标题 18px,辅助文字 12px,按钮 14px。
|
||
|
||
#### P1.2 卡片间距增大
|
||
|
||
| 文件 | 当前 | 目标 |
|
||
|------|------|------|
|
||
| `src/components/ui/Card.tsx` | `p-3` | `p-4` |
|
||
| `src/index.css` `.card` | `p-3` | `p-4` |
|
||
| `src/pages/Dashboard.tsx` | `space-y-3` | `space-y-4`;`gap-2` → `gap-3` | L157, L197 |
|
||
| `src/pages/Money.tsx` | `space-y-3` | `space-y-4` | L28 |
|
||
| `src/pages/Roster.tsx` | 列表行 `py-1.5` → `py-2.5` | 表格行 |
|
||
| `src/pages/SocialInsurance.tsx` | `space-y-3` → `space-y-4` | |
|
||
| `src/pages/Settings.tsx` | `space-y-3` → `space-y-4` | L45 |
|
||
| `src/pages/AIAssistant.tsx` | `space-y-4`(保持) | L37 |
|
||
|
||
**验收标准**:卡片内边距 16px,页面模块间距 16px,表格行高 ≥ 40px。
|
||
|
||
#### P1.3 引入 Toast(sonner)
|
||
|
||
**安装**:`npm install sonner`
|
||
|
||
**修改文件清单**:
|
||
|
||
| 文件 | 修改 |
|
||
|------|------|
|
||
| `src/App.tsx` | 顶层添加 `<Toaster position="top-center" richColors />` |
|
||
| `src/pages/AIAssistant.tsx` | 6 处 `alert()` → `toast.success()` / `toast.error()` | L137, L433, L435, L585, L587, L605, L607 |
|
||
| `src/pages/Money.tsx` | 8 处 `alert()` → `toast.success()` / `toast.error()`;3 处 `confirm()` → ConfirmDialog | L284, L342, L352, L354, L358, L493, L506, L883, L1216, L1255 |
|
||
| `src/pages/Roster.tsx` | 4 处 `alert()` → `toast.error()`;1 处 `confirm()` → ConfirmDialog | L400, L507, L985, L993, L1930, L1937 |
|
||
| `src/pages/SocialInsurance.tsx` | 7 处 `alert()` → `toast.success()` / `toast.error()` | L122, L132, L168, L182, L191, L200, L294 |
|
||
| `src/pages/Settings.tsx` | 6 处 `alert()` → `toast` | 搜索结果 |
|
||
| `src/pages/portal/ContractConfirm.tsx` | 1 处 `alert()` → `toast` | |
|
||
|
||
**代码示例**:
|
||
|
||
```tsx
|
||
// App.tsx 顶层
|
||
import { Toaster } from 'sonner'
|
||
|
||
export default function App() {
|
||
return (
|
||
<>
|
||
<Routes>...</Routes>
|
||
<Toaster position="top-center" richColors closeButton />
|
||
</>
|
||
)
|
||
}
|
||
|
||
// 各页面替换
|
||
// 旧:alert('已保存到员工档案')
|
||
// 新:toast.success('已保存到员工档案')
|
||
// 旧:alert('保存失败:' + msg)
|
||
// 新:toast.error('保存失败:' + msg)
|
||
```
|
||
|
||
**验收标准**:全局 0 处 `alert()`,0 处 `confirm()`,所有操作反馈通过 toast 或 Dialog。
|
||
|
||
#### P1.4 批量操作二次确认组件
|
||
|
||
**新增文件**:`src/components/ui/ConfirmDialog.tsx`
|
||
|
||
```tsx
|
||
import Modal from './Modal'
|
||
import Button from './Button'
|
||
|
||
interface ConfirmDialogProps {
|
||
open: boolean
|
||
title: string
|
||
message: string
|
||
confirmLabel?: string
|
||
cancelLabel?: string
|
||
variant?: 'danger' | 'primary'
|
||
onConfirm: () => void
|
||
onCancel: () => void
|
||
}
|
||
|
||
export default function ConfirmDialog({
|
||
open, title, message,
|
||
confirmLabel = '确认', cancelLabel = '取消',
|
||
variant = 'danger', onConfirm, onCancel,
|
||
}: ConfirmDialogProps) {
|
||
return (
|
||
<Modal open={open} onClose={onCancel} title={title} size="sm">
|
||
<p className="text-sm text-gray-600 mb-4">{message}</p>
|
||
<div className="flex justify-end gap-2">
|
||
<Button variant="secondary" onClick={onCancel}>{cancelLabel}</Button>
|
||
<Button variant={variant} onClick={onConfirm}>{confirmLabel}</Button>
|
||
</div>
|
||
</Modal>
|
||
)
|
||
}
|
||
```
|
||
|
||
**替换清单**(所有 `confirm()` 调用):
|
||
|
||
| 文件 | 行号 | 当前代码 | 替换为 |
|
||
|------|------|----------|--------|
|
||
| Money.tsx | L284 | `confirm('确认删除批次?')` | `<ConfirmDialog>` |
|
||
| Money.tsx | L493 | `confirm('确认归档?')` | `<ConfirmDialog>` |
|
||
| Money.tsx | L506 | `confirm('确认删除批次?')` | `<ConfirmDialog>` |
|
||
| Money.tsx | L1255 | `confirm('确认生成工资条?')` | `<ConfirmDialog>` |
|
||
| Roster.tsx | L400 | `confirm('确认撤回离职记录?')` | `<ConfirmDialog>` |
|
||
|
||
**验收标准**:危险操作弹出 Dialog 而非浏览器原生 confirm,有明确文案说明后果。
|
||
|
||
#### P1.5 对比度修复
|
||
|
||
| 文件 | 当前 | 目标 | 说明 |
|
||
|------|------|------|------|
|
||
| 全局 `text-gray-400` | `text-gray-400` (#9CA3AF) | `text-gray-500` (#6B7280) | 对比度 2.5:1 → 4.6:1 |
|
||
| `src/components/ui/Pagination.tsx` | L61, L69 `text-gray-400` | `text-gray-500` | 翻页按钮 |
|
||
| `src/pages/Dashboard.tsx` | L229 `text-gray-400` | `text-gray-500` | "等人"文字 |
|
||
| `src/components/layout/TopNav.tsx` | L50 `text-gray-400` | `text-gray-500` | ChevronDown 图标 |
|
||
|
||
**验收标准**:所有文字对比度 ≥ 4.5:1(WCAG AA),使用 axe DevTools 验证。
|
||
|
||
#### P1.6 内容宽度限制
|
||
|
||
| 文件 | 修改 |
|
||
|------|------|
|
||
| `tailwind.config.js` | `maxWidth: { content: '1280px' }`(当前 `none`) |
|
||
|
||
**验收标准**:1920px 屏幕内容居中,最大宽度 1280px,两侧留白。
|
||
|
||
---
|
||
|
||
### Phase 2:信息架构调整(2-3 天)
|
||
|
||
#### P2.1 顶部导航精简为 4 入口
|
||
|
||
**修改文件**:`src/components/layout/TopNav.tsx`
|
||
|
||
```tsx
|
||
// 当前 6 个 tab
|
||
const tabs = [
|
||
{ path: '/', label: '总览' },
|
||
{ path: '/roster', label: '花名册' },
|
||
{ path: '/money', label: '薪税' },
|
||
{ path: '/social', label: '社保公积金' },
|
||
{ path: '/termination', label: '解聘补偿' },
|
||
{ path: '/ai-assistant', label: 'AI顾问' },
|
||
]
|
||
|
||
// 目标 4 个 tab
|
||
const tabs = [
|
||
{ path: '/', label: '总览' },
|
||
{ path: '/roster', label: '员工管理' },
|
||
{ path: '/money', label: '薪税社保' },
|
||
{ path: '/ai-assistant', label: 'AI顾问' },
|
||
]
|
||
```
|
||
|
||
**右侧操作区修改**:
|
||
|
||
```tsx
|
||
// 当前:仅用户下拉菜单
|
||
// 目标:通知铃铛(badge) + 设置齿轮 + 用户头像
|
||
<div className="flex items-center gap-2 shrink-0">
|
||
<Link to="/settings" className="p-1.5 rounded-md hover:bg-gray-100" aria-label="设置">
|
||
<Settings className="w-4 h-4 text-gray-600" />
|
||
</Link>
|
||
<button className="relative p-1.5 rounded-md hover:bg-gray-100" aria-label="通知">
|
||
<Bell className="w-4 h-4 text-gray-600" />
|
||
{riskCount > 0 && <span className="absolute -top-0.5 -right-0.5 ..." />}
|
||
</button>
|
||
{/* 用户菜单保持 */}
|
||
</div>
|
||
```
|
||
|
||
#### P2.2 移动端底部导航精简为 4
|
||
|
||
**修改文件**:`src/components/layout/MobileTabBar.tsx`
|
||
|
||
```tsx
|
||
// 当前 6 个 → 目标 4 个
|
||
const tabs = [
|
||
{ path: '/', label: '总览', icon: Home },
|
||
{ path: '/roster', label: '员工', icon: Users },
|
||
{ path: '/money', label: '薪税', icon: Calculator },
|
||
{ path: '/ai-assistant', label: 'AI', icon: Bot },
|
||
]
|
||
```
|
||
|
||
#### P2.3 路由调整
|
||
|
||
**修改文件**:`src/App.tsx`
|
||
|
||
| 变更 | 说明 |
|
||
|------|------|
|
||
| `/social` 路由保留但导航不直接暴露 | 作为 `/money` 的子 tab 或独立路由通过页面内跳转 |
|
||
| `/termination` 路由保留但导航不直接暴露 | 作为 `/roster` 内的功能入口(花名册 → 解聘操作) |
|
||
| Dashboard 移除薪税 tab | `activeTab` 类型从 `'overview' \| 'payroll' \| 'risk' \| 'task'` → `'overview' \| 'risk' \| 'task'` |
|
||
|
||
**Dashboard.tsx 修改**:
|
||
|
||
```tsx
|
||
// 移除 payroll tab
|
||
const tabs = [
|
||
{ key: 'overview' as const, label: '概览', icon: LayoutDashboard, badge: data.stats.todoCount },
|
||
{ key: 'risk' as const, label: '风险提醒', icon: AlertTriangle, badge: riskTodos.length },
|
||
{ key: 'task' as const, label: '月度任务', icon: ListTodo, badge: taskTodos.length },
|
||
]
|
||
// 删除 activeTab === 'payroll' 相关的所有 JSX 块
|
||
```
|
||
|
||
#### P2.4 合并未挂载页面
|
||
|
||
| 操作 | 文件 | 说明 |
|
||
|------|------|------|
|
||
| Compensation → Money | `src/pages/Money.tsx` | 在 tabs 数组增加 `{ key: 'adjust', label: '薪酬调整' }`,渲染 `<CompensationManager />` |
|
||
| Contracts → Roster | `src/pages/Contracts.tsx` | Roster 详情已有合同 tab,删除独立 Contracts.tsx 或将其逻辑合并到 Roster 详情的合同 tab |
|
||
|
||
**Money.tsx 修改**:
|
||
|
||
```tsx
|
||
type Tab = 'batch' | 'template' | 'overtime' | 'payslip' | 'adjust'
|
||
|
||
const tabs = [
|
||
{ key: 'batch', label: '发薪批次' },
|
||
{ key: 'template', label: '薪酬模版' },
|
||
{ key: 'overtime', label: '加班费计算' },
|
||
{ key: 'payslip', label: '工资条管理' },
|
||
{ key: 'adjust', label: '薪酬调整' }, // 新增
|
||
]
|
||
// {tab === 'adjust' && <CompensationManager />}
|
||
```
|
||
|
||
**验收标准**:顶部导航 4 个 tab,移动端 4 个 tab,无重复入口,Contracts/Compensation 功能可访问。
|
||
|
||
---
|
||
|
||
### Phase 3:性能与组件化(2-3 天)
|
||
|
||
#### P3.1 路由懒加载
|
||
|
||
**修改文件**:`src/App.tsx`
|
||
|
||
```tsx
|
||
import { lazy, Suspense } from 'react'
|
||
import { Loader2 } from 'lucide-react'
|
||
|
||
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
||
const Roster = lazy(() => import('./pages/Roster'))
|
||
const Money = lazy(() => import('./pages/Money'))
|
||
const SocialInsurance = lazy(() => import('./pages/SocialInsurance'))
|
||
const Termination = lazy(() => import('./pages/Termination'))
|
||
const AIAssistant = lazy(() => import('./pages/AIAssistant'))
|
||
const Settings = lazy(() => import('./pages/Settings'))
|
||
const Login = lazy(() => import('./pages/auth/Login'))
|
||
// ... 其他页面同理
|
||
|
||
function PageSkeleton() {
|
||
return (
|
||
<div className="flex items-center justify-center py-20">
|
||
<Loader2 className="w-6 h-6 text-primary animate-spin" />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function App() {
|
||
return (
|
||
<Suspense fallback={<PageSkeleton />}>
|
||
<Routes>...</Routes>
|
||
</Suspense>
|
||
)
|
||
}
|
||
```
|
||
|
||
**验收标准**:首屏仅加载 Dashboard chunk,其他页面按需加载,Network 面板可见独立 chunk。
|
||
|
||
#### P3.2 大文件拆分
|
||
|
||
**Roster.tsx(140KB)拆分方案**:
|
||
|
||
```
|
||
src/pages/roster/
|
||
├── Roster.tsx # 主页面:列表 + 搜索 + 筛选 + 分页
|
||
├── EmployeeDetail.tsx # 详情面板:tab 切换容器
|
||
├── tabs/
|
||
│ ├── BasicInfoTab.tsx # 基本信息
|
||
│ ├── ContractTab.tsx # 合同信息
|
||
│ ├── PayslipTab.tsx # 工资条
|
||
│ ├── OvertimeTab.tsx # 加班记录
|
||
│ ├── DisciplinaryTab.tsx # 违纪记录
|
||
│ ├── AttendanceTab.tsx # 考勤记录
|
||
│ ├── TrainingTab.tsx # 培训记录
|
||
│ ├── PerformanceTab.tsx # 绩效记录
|
||
│ ├── TerminationTab.tsx # 解聘记录
|
||
│ ├── AttachmentTab.tsx # 附件管理
|
||
│ └── EvidenceTab.tsx # 仲裁证据链
|
||
├── AddEmployeeModal.tsx # 新增员工弹窗
|
||
├── ResignModal.tsx # 离职弹窗
|
||
├── RehireModal.tsx # 重新入职弹窗
|
||
├── SalaryModal.tsx # 调薪弹窗
|
||
├── DeptModal.tsx # 调岗弹窗
|
||
└── BatchRenewModal.tsx # 批量续签弹窗
|
||
```
|
||
|
||
**Money.tsx(63KB)拆分方案**:
|
||
|
||
```
|
||
src/pages/money/
|
||
├── Money.tsx # 主页面:tab 切换
|
||
├── BatchManager.tsx # 发薪批次
|
||
├── TemplateManager.tsx # 薪酬模版
|
||
├── OvertimeCalculator.tsx # 加班费计算
|
||
├── PayslipManager.tsx # 工资条管理
|
||
└── CompensationManager.tsx # 薪酬调整(从 Compensation.tsx 合入)
|
||
```
|
||
|
||
**Termination.tsx(55KB)拆分方案**:
|
||
|
||
```
|
||
src/pages/termination/
|
||
├── Termination.tsx # 主页面:向导容器
|
||
├── StepSelectEmployee.tsx # 步骤1:选择员工
|
||
├── StepSelectReason.tsx # 步骤2:解聘方式
|
||
├── StepCompliance.tsx # 步骤3:合规检查
|
||
├── StepSettlement.tsx # 步骤4:费用结算
|
||
└── StepConfirm.tsx # 步骤5:确认完成
|
||
```
|
||
|
||
**Settings.tsx(46KB)拆分方案**:
|
||
|
||
```
|
||
src/pages/settings/
|
||
├── Settings.tsx # 主页面:section 切换
|
||
├── OrgSettings.tsx # 企业信息
|
||
├── UserSettings.tsx # 用户管理
|
||
├── PlanSettings.tsx # 套餐
|
||
├── NotificationSettings.tsx # 通知设置
|
||
└── ImportSettings.tsx # 数据导入
|
||
```
|
||
|
||
**验收标准**:单个文件不超过 500 行,每个子组件独立可测。
|
||
|
||
#### P3.3 骨架屏组件
|
||
|
||
**新增文件**:`src/components/ui/Skeleton.tsx`
|
||
|
||
```tsx
|
||
export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||
return (
|
||
<div className="space-y-2">
|
||
{Array.from({ length: rows }).map((_, i) => (
|
||
<div key={i} className="h-10 bg-gray-100 rounded animate-pulse" />
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export function CardSkeleton() {
|
||
return (
|
||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||
<div className="h-4 bg-gray-100 rounded w-1/3 mb-3 animate-pulse" />
|
||
<div className="h-8 bg-gray-100 rounded w-1/2 animate-pulse" />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export function DetailSkeleton() {
|
||
return (
|
||
<div className="space-y-3">
|
||
<div className="h-6 bg-gray-100 rounded w-1/4 animate-pulse" />
|
||
<div className="h-4 bg-gray-100 rounded w-full animate-pulse" />
|
||
<div className="h-4 bg-gray-100 rounded w-3/4 animate-pulse" />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**替换清单**(13 处 "加载中..."):
|
||
|
||
| 文件 | 行号 | 替换为 |
|
||
|------|------|--------|
|
||
| Dashboard.tsx | L112 | `<TableSkeleton rows={4} />` |
|
||
| Roster.tsx | L249, L702 | `<TableSkeleton />` |
|
||
| Money.tsx | L239, L371, L719, L933, L1268 | `<TableSkeleton />` |
|
||
| SocialInsurance.tsx | L314, L623 | `<TableSkeleton rows={3} />` |
|
||
| AIAssistant.tsx | L773 | `<TableSkeleton />` |
|
||
| portal/Payslip.tsx | L131 | `<CardSkeleton />` |
|
||
| portal/MyContract.tsx | L70 | `<CardSkeleton />` |
|
||
| portal/ContractConfirm.tsx | L85 | `<CardSkeleton />` |
|
||
| Contracts.tsx | L82 | `<TableSkeleton />` |
|
||
|
||
**验收标准**:加载时显示骨架屏动画,无白屏或纯文字"加载中..."。
|
||
|
||
#### P3.4 搜索防抖
|
||
|
||
**修改文件**:`src/pages/Roster.tsx`(及任何有搜索的页面)
|
||
|
||
```tsx
|
||
import { useDeferredValue } from 'react'
|
||
|
||
const [search, setSearch] = useState('')
|
||
const deferredSearch = useDeferredValue(search)
|
||
|
||
// queryKey 使用 deferredSearch 而非 search
|
||
const { data: rosterData } = useQuery({
|
||
queryKey: ['roster', page, pageSize, deferredSearch, filterStatus, filterContractStatus],
|
||
// ...
|
||
})
|
||
```
|
||
|
||
**验收标准**:快速输入时不会每次按键触发 API 请求,停止输入 ~200ms 后才发请求。
|
||
|
||
---
|
||
|
||
### Phase 4:视觉与数据可视化(2-3 天)
|
||
|
||
#### P4.1 主色调暖
|
||
|
||
**修改文件**:`tailwind.config.js`
|
||
|
||
```js
|
||
// 当前
|
||
primary: { DEFAULT: '#2563EB', light: '#3B82F6', dark: '#1D4ED8' }
|
||
// 目标(indigo-600,略带紫调,专业且亲和)
|
||
primary: { DEFAULT: '#4F46E5', light: '#6366F1', dark: '#4338CA' }
|
||
```
|
||
|
||
**影响范围**:所有使用 `text-primary`、`bg-primary`、`border-primary` 的组件自动生效,无需逐文件修改。
|
||
|
||
**验收标准**:主色从冷蓝变为 indigo,视觉感受更温暖,与 Tailwind indigo-600 色卡一致。
|
||
|
||
#### P4.2 Dashboard 数据可视化
|
||
|
||
**安装**:`npm install recharts`
|
||
|
||
**修改文件**:`src/pages/Dashboard.tsx`
|
||
|
||
在概览 tab 的统计卡片下方增加:
|
||
|
||
```tsx
|
||
import { LineChart, Line, ResponsiveContainer, XAxis, YAxis, Tooltip, PieChart, Pie, Cell } from 'recharts'
|
||
|
||
// 月度薪税趋势迷你折线图
|
||
<Card>
|
||
<h2 className="font-medium mb-3">月度薪税趋势</h2>
|
||
<ResponsiveContainer width="100%" height={120}>
|
||
<LineChart data={data.payrollHistory}>
|
||
<XAxis dataKey="month" tick={{ fontSize: 12 }} />
|
||
<YAxis tick={{ fontSize: 12 }} />
|
||
<Tooltip />
|
||
<Line type="monotone" dataKey="totalPay" stroke="#4F46E5" strokeWidth={2} dot={false} />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
|
||
// 风险分布环形图
|
||
<Card>
|
||
<h2 className="font-medium mb-3">风险分布</h2>
|
||
<ResponsiveContainer width="100%" height={160}>
|
||
<PieChart>
|
||
<Pie data={riskData} dataKey="count" nameKey="label" cx="50%" cy="50%" innerRadius={40} outerRadius={60}>
|
||
{riskData.map((entry, i) => <Cell key={i} fill={entry.color} />)}
|
||
</Pie>
|
||
<Tooltip />
|
||
</PieChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
```
|
||
|
||
**验收标准**:Dashboard 概览页有折线图和环形图,图表响应式,tooltip 正常显示。
|
||
|
||
#### P4.3 Modal 过渡动画
|
||
|
||
**修改文件**:`src/components/ui/Modal.tsx`
|
||
|
||
```tsx
|
||
// 添加 CSS transition
|
||
// 方案:利用 Tailwind 的 transition + opacity + scale
|
||
|
||
// 遮罩层:opacity 0 → 100
|
||
// 内容层:scale-95 opacity-0 → scale-100 opacity-100
|
||
|
||
// 新增 state 控制动画
|
||
const [show, setShow] = useState(false)
|
||
useEffect(() => {
|
||
if (open) {
|
||
setShow(true)
|
||
} else {
|
||
const timer = setTimeout(() => setShow(false), 200)
|
||
return () => clearTimeout(timer)
|
||
}
|
||
}, [open])
|
||
|
||
if (!show && !open) return null
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
<div className={clsx(
|
||
'fixed inset-0 bg-black/40 transition-opacity duration-200',
|
||
open ? 'opacity-100' : 'opacity-0'
|
||
)} onClick={onClose} />
|
||
<div className={clsx(
|
||
'relative bg-white rounded-lg shadow-xl w-full max-h-[90vh] overflow-y-auto transition-all duration-200',
|
||
open ? 'scale-100 opacity-100' : 'scale-95 opacity-0',
|
||
sizeClass,
|
||
)}>
|
||
{/* 内容不变 */}
|
||
</div>
|
||
</div>
|
||
)
|
||
```
|
||
|
||
**验收标准**:弹窗有淡入+缩放动画,关闭有淡出动画,~200ms。
|
||
|
||
#### P4.4 移动端表格响应式
|
||
|
||
**新增组件**:`src/components/ui/ResponsiveTable.tsx`
|
||
|
||
```tsx
|
||
interface Column<T> {
|
||
key: string
|
||
label: string
|
||
render?: (row: T) => React.ReactNode
|
||
priority: 'high' | 'medium' | 'low' // 高优先级在手机端显示
|
||
className?: string
|
||
}
|
||
|
||
interface ResponsiveTableProps<T> {
|
||
columns: Column<T>[]
|
||
data: T[]
|
||
rowKey: (row: T) => string
|
||
onRowClick?: (row: T) => void
|
||
}
|
||
|
||
// 桌面端:表格(显示所有列)
|
||
// 平板端:表格(隐藏 low priority 列,用 hidden md:table-cell)
|
||
// 手机端:卡片列表(仅显示 high priority 字段,竖排)
|
||
```
|
||
|
||
**应用页面**:Roster、Money(工资条列表)、SocialInsurance(月度申报表)
|
||
|
||
**验收标准**:iPhone SE 上列表为卡片模式,iPad 上为紧凑表格,桌面端完整表格。
|
||
|
||
---
|
||
|
||
### Phase 5:a11y 与细节打磨(1-2 天)
|
||
|
||
#### P5.1 div onClick → button + aria
|
||
|
||
**修改文件**:`src/components/layout/TopNav.tsx`
|
||
|
||
```tsx
|
||
// 当前(L46-50):div + onClick
|
||
<div className="relative shrink-0">
|
||
<button onClick={() => setMenuOpen(!menuOpen)} className="flex items-center gap-1 ...">
|
||
|
||
// 目标:添加 aria 属性
|
||
<button
|
||
onClick={() => setMenuOpen(!menuOpen)}
|
||
className="flex items-center gap-1 ..."
|
||
aria-expanded={menuOpen}
|
||
aria-haspopup="menu"
|
||
aria-label="用户菜单"
|
||
>
|
||
```
|
||
|
||
#### P5.2 aria-label 覆盖
|
||
|
||
| 文件 | 位置 | 添加 |
|
||
|------|------|------|
|
||
| TopNav.tsx | Logo Link | `aria-label="用工合规助手首页"` |
|
||
| TopNav.tsx | 设置齿轮 | `aria-label="设置"` |
|
||
| TopNav.tsx | 通知铃铛 | `aria-label="通知"` |
|
||
| MobileTabBar.tsx | 每个 Link | `aria-label={tab.label}` |
|
||
| Modal.tsx | 关闭按钮 | `aria-label="关闭"` |
|
||
| Pagination.tsx | 上下页按钮 | `aria-label="上一页"` / `aria-label="下一页"` |
|
||
|
||
#### P5.3 focus-visible 样式
|
||
|
||
**修改文件**:`src/index.css`
|
||
|
||
```css
|
||
@layer base {
|
||
*:focus-visible {
|
||
@apply outline-none ring-2 ring-primary ring-offset-1;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### P5.4 表单防离开
|
||
|
||
**新增 Hook**:`src/hooks/useUnsavedChanges.ts`
|
||
|
||
```tsx
|
||
import { useEffect } from 'react'
|
||
|
||
export function useUnsavedChanges(isDirty: boolean) {
|
||
useEffect(() => {
|
||
const handler = (e: BeforeUnloadEvent) => {
|
||
if (isDirty) {
|
||
e.preventDefault()
|
||
e.returnValue = ''
|
||
}
|
||
}
|
||
window.addEventListener('beforeunload', handler)
|
||
return () => window.removeEventListener('beforeunload', handler)
|
||
}, [isDirty])
|
||
}
|
||
```
|
||
|
||
**应用**:所有表单页面(AddEmployee、Settings OrgForm、Termination 向导等)
|
||
|
||
```tsx
|
||
const form = useForm({ mode: 'onChange' })
|
||
useUnsavedChanges(form.formState.isDirty)
|
||
```
|
||
|
||
#### P5.5 虚拟列表
|
||
|
||
**安装**:`npm install @tanstack/react-virtual`
|
||
|
||
**修改文件**:`src/pages/Roster.tsx`(当员工数 > 100 时)
|
||
|
||
```tsx
|
||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||
|
||
// 在员工列表渲染中
|
||
const rowVirtualizer = useVirtualizer({
|
||
count: employees.length,
|
||
getScrollElement: () => parentRef.current,
|
||
estimateSize: () => 48,
|
||
overscan: 5,
|
||
})
|
||
```
|
||
|
||
**验收标准**:1000+ 员工时列表滚动流畅,DOM 节点数 < 30。
|
||
|
||
---
|
||
|
||
## 四、实施路线图
|
||
|
||
### Phase 1:基础体验修复(1-2 天)
|
||
|
||
- [ ] 全局字号提升 text-xs → text-sm
|
||
- [ ] 卡片间距 p-3 → p-4,页面 gap-2 → gap-4
|
||
- [ ] 引入 sonner Toast,替换所有 alert/console
|
||
- [ ] 批量操作增加二次确认 Dialog
|
||
- [ ] 修复 text-gray-400 对比度
|
||
- [ ] max-w-content 限制为 1280px
|
||
|
||
### Phase 2:信息架构调整(2-3 天)
|
||
|
||
- [ ] 顶部导航精简为 4 个入口
|
||
- [ ] 移动端底部导航精简为 4 个
|
||
- [ ] Dashboard 移除薪税 tab
|
||
- [ ] 合并 Contracts → Roster,Compensation → Money
|
||
- [ ] 设置入口可见化(齿轮图标)
|
||
|
||
### Phase 3:性能与组件化(2-3 天)
|
||
|
||
- [ ] 路由懒加载 + Suspense
|
||
- [ ] Roster / Money / Settings 大文件拆分
|
||
- [ ] 骨架屏替换"加载中..."
|
||
- [ ] 搜索防抖
|
||
|
||
### Phase 4:视觉与数据可视化(2-3 天)
|
||
|
||
- [ ] 主色调 indigo-600
|
||
- [ ] Dashboard 增加迷你折线图 + 环形图(recharts)
|
||
- [ ] Modal 过渡动画
|
||
- [ ] 移动端表格卡片模式
|
||
|
||
### Phase 5:a11y 与细节打磨(1-2 天)
|
||
|
||
- [ ] div onClick → button + aria
|
||
- [ ] aria-label 覆盖所有图标按钮
|
||
- [ ] focus-visible 样式
|
||
- [ ] 表单 beforeunload 防离开
|
||
- [ ] 虚拟列表(如员工数 > 100)
|
||
|
||
---
|
||
|
||
## 五、目标用户画像参考
|
||
|
||
```
|
||
中小微企业 HR/财务(1-2 人兼管)
|
||
├── 年龄:28-45 岁,非技术背景
|
||
├── 设备:笔记本为主(1366×768 或 1920×1080),偶尔手机查看
|
||
├── 使用频率:每月集中使用 2-3 次(发薪/社保/合同续签)
|
||
├── 痛点:不懂法规、怕出错、需要快速完成
|
||
├── 期望:操作简单 → 结果准确 → 有提醒不遗漏
|
||
└── 设计原则:
|
||
├── 信息层级清晰(一眼看到"该做什么")
|
||
├── 操作有反馈(每步都确认成功/失败)
|
||
├── 容错性强(危险操作二次确认,可撤销)
|
||
└── 移动端可用(出差/居家时手机审批)
|
||
```
|