feat: Sprint 1 — 设计Token系统 + 新导航5分组 + AppShell/PageHeader/FilterBar/DataTable组件 + 花名册导出扩充19字段 + 社保/公积金不缴纳选项 + 个税申报表导出 + 入离职/绩效统计看板

This commit is contained in:
selfrelease
2026-07-31 17:40:49 +08:00
parent c15e11ec22
commit 821a62e3f8
16 changed files with 3177 additions and 55 deletions
+138
View File
@@ -239,4 +239,142 @@ router.get('/workforce-stats', authMiddleware, async (req: AuthRequest, res: Res
}
})
// 入离职统计看板 — 按月聚合入职和离职人数
router.get('/turnover-stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const months = parseInt(req.query.months as string) || 12
// 计算最近 N 个月的月份列表
const now = new Date()
const monthList: string[] = []
for (let i = months - 1; i >= 0; i--) {
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
monthList.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`)
}
// 查询入职数据(按 hireDate 月份分组)
const startDate = new Date(monthList[0] + '-01')
const employees = await prisma.employee.findMany({
where: {
orgId,
OR: [
{ hireDate: { gte: startDate } },
{ status: 'RESIGNED' },
],
},
select: { id: true, name: true, hireDate: true, status: true, department: true },
})
// 查询离职记录
const terminations = await prisma.terminationRecord.findMany({
where: {
orgId,
terminationDate: { gte: startDate },
},
select: { employeeId: true, terminationDate: true, type: true, reason: true },
})
// 按月聚合
const monthlyData = monthList.map(month => {
const monthStart = new Date(month + '-01')
const monthEnd = new Date(monthStart.getFullYear(), monthStart.getMonth() + 1, 1)
const hired = employees.filter(e => e.hireDate >= monthStart && e.hireDate < monthEnd).length
const left = terminations.filter(t => {
const td = t.terminationDate
return td && td >= monthStart && td < monthEnd
}).length
return { month, hired, left, net: hired - left }
})
// 汇总
const totalHired = monthlyData.reduce((s, m) => s + m.hired, 0)
const totalLeft = monthlyData.reduce((s, m) => s + m.left, 0)
const currentHeadcount = employees.filter(e => e.status !== 'RESIGNED').length
const avgHeadcount = currentHeadcount // 简化:使用当前人数
const turnoverRate = avgHeadcount > 0 ? (totalLeft / avgHeadcount * 100).toFixed(1) : '0'
res.json({
success: true,
data: {
monthly: monthlyData,
summary: {
totalHired,
totalLeft,
currentHeadcount,
turnoverRate: parseFloat(turnoverRate),
},
},
})
} catch (err) {
next(err)
}
})
// 绩效统计看板 — 按周期聚合绩效分布
router.get('/performance-stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const period = (req.query.period as string) || new Date().toISOString().slice(0, 4) // 默认当前年
const records = await prisma.performanceRecord.findMany({
where: {
orgId,
period: { startsWith: period },
},
include: { employee: { select: { name: true, department: true } } },
orderBy: { createdAt: 'desc' },
})
// 按等级分布
const gradeDist: Record<string, number> = {}
// 按部门分布
const deptDist: Record<string, { count: number; avgScore: number; scores: number[] }> = {}
// 按周期分布
const periodDist: Record<string, { count: number; avgScore: number; scores: number[] }> = {}
for (const r of records) {
// 等级分布
const grade = r.grade || '未评级'
gradeDist[grade] = (gradeDist[grade] || 0) + 1
// 部门分布
const dept = r.employee?.department || '未分配'
if (!deptDist[dept]) deptDist[dept] = { count: 0, avgScore: 0, scores: [] }
deptDist[dept].count++
if (r.score) deptDist[dept].scores.push(r.score)
// 周期分布
if (!periodDist[r.period]) periodDist[r.period] = { count: 0, avgScore: 0, scores: [] }
periodDist[r.period].count++
if (r.score) periodDist[r.period].scores.push(r.score)
}
// 计算平均分
const calcAvg = (d: typeof deptDist) => Object.entries(d).map(([name, v]) => ({
name,
count: v.count,
avgScore: v.scores.length > 0 ? Math.round(v.scores.reduce((s, x) => s + x, 0) / v.scores.length * 10) / 10 : 0,
}))
const allScores = records.map(r => r.score).filter(Boolean) as number[]
const overallAvg = allScores.length > 0 ? Math.round(allScores.reduce((s, x) => s + x, 0) / allScores.length * 10) / 10 : 0
res.json({
success: true,
data: {
total: records.length,
overallAvgScore: overallAvg,
gradeDistribution: Object.entries(gradeDist).map(([name, value]) => ({ name, value })),
departmentDistribution: calcAvg(deptDist),
periodDistribution: calcAvg(periodDist),
},
})
} catch (err) {
next(err)
}
})
export default router
+141 -5
View File
@@ -282,26 +282,65 @@ router.get('/roster', authMiddleware, async (req: AuthRequest, res: Response, ne
const workbook = new ExcelJS.Workbook()
const ws = workbook.addWorksheet('花名册')
ws.columns = [
{ header: '姓名', key: 'name', width: 12 },
{ header: '部门', key: 'department', width: 15 },
{ header: '状态', key: 'status', width: 10 },
{ header: '姓名', key: 'name', width: 10 },
{ header: '部门', key: 'department', width: 12 },
{ header: '性别', key: 'gender', width: 6 },
{ header: '状态', key: 'status', width: 8 },
{ header: '入职日期', key: 'hireDate', width: 12 },
{ header: '手机号', key: 'phone', width: 13 },
{ header: '身份证号', key: 'idCardNumber', width: 20 },
{ header: '月工资', key: 'monthlySalary', width: 10 },
{ header: '社保基数', key: 'socialInsBase', width: 10 },
{ header: '公积金基数', key: 'housingFundBase', width: 10 },
{ header: '专项附加扣除', key: 'specialDeduction', width: 12 },
{ header: '参保城市', key: 'city', width: 10 },
{ header: '紧急联系人', key: 'emergencyContact', width: 10 },
{ header: '紧急联系电话', key: 'emergencyPhone', width: 13 },
{ header: '住址', key: 'address', width: 18 },
{ header: '开户行', key: 'bankName', width: 10 },
{ header: '银行账号', key: 'bankAccount', width: 18 },
{ header: '合同起始', key: 'contractStart', width: 12 },
{ header: '合同结束', key: 'contractEnd', width: 12 },
{ header: '联系方式', key: 'phone', width: 15 },
]
ws.getRow(1).font = { bold: true }
// 是否脱敏(非 ADMIN 用户强制脱敏)
const shouldMask = req.user!.role !== 'ADMIN'
for (const e of employees) {
const contract = e.contracts[0]
// 解密敏感字段
let salary = 0
try { salary = Number(decrypt(e.monthlySalary)) || 0 } catch { salary = Number(e.monthlySalary) || 0 }
let idCard: string | null = null
try { if (e.idCardNumber) idCard = decrypt(e.idCardNumber) } catch { idCard = e.idCardNumber }
let bankAccount: string | null = null
try { if (e.bankAccount) bankAccount = decrypt(e.bankAccount) } catch { bankAccount = e.bankAccount }
if (shouldMask) {
idCard = maskIdCard(idCard)
bankAccount = maskBankAccount(bankAccount)
if (salary) salary = 0
}
ws.addRow({
name: e.name,
department: e.department,
gender: e.gender || '',
status: e.status === 'ACTIVE' ? '在职' : e.status === 'RESIGNED' ? '离职' : '预入职',
hireDate: e.hireDate?.toISOString().slice(0, 10) || '',
phone: e.phone || '',
idCardNumber: idCard || '',
monthlySalary: salary,
socialInsBase: e.socialInsBase || 0,
housingFundBase: e.housingFundBase || 0,
specialDeduction: e.specialDeduction || 0,
city: e.city || '',
emergencyContact: e.emergencyContact || '',
emergencyPhone: e.emergencyPhone || '',
address: e.address || '',
bankName: e.bankName || '',
bankAccount: bankAccount || '',
contractStart: contract?.startDate?.toISOString().slice(0, 10) || '',
contractEnd: contract?.endDate?.toISOString().slice(0, 10) || '',
phone: e.phone || '',
})
}
@@ -386,4 +425,101 @@ router.get('/terminations', authMiddleware, async (req: AuthRequest, res: Respon
}
})
// 导出个税申报表 Excel(对齐自然人电子税务局格式)
router.get('/tax-declaration', authMiddleware, requireAdmin, async (req: AuthRequest, res: Response, next) => {
try {
const orgId = req.user!.orgId
const month = (req.query.month as string) || new Date().toISOString().slice(0, 7)
const entries = await prisma.batchEntry.findMany({
where: { orgId, batch: { month, status: 'ARCHIVED' } },
include: { employee: true, batch: true },
orderBy: { employee: { name: 'asc' } },
})
const workbook = new ExcelJS.Workbook()
const ws = workbook.addWorksheet('个税申报表')
// 个税申报表列定义(对齐自然人电子税务局模板)
ws.columns = [
{ header: '序号', key: 'seq', width: 6 },
{ header: '姓名', key: 'name', width: 10 },
{ header: '身份证件号码', key: 'idCardNumber', width: 22 },
{ header: '所得项目', key: 'incomeType', width: 16 },
{ header: '本期收入', key: 'totalPay', width: 12 },
{ header: '本期免税收入', key: 'taxFreeIncome', width: 14 },
{ header: '基本减除费用', key: 'basicDeduction', width: 14 },
{ header: '专项扣除合计', key: 'specialDeductionTotal', width: 14 },
{ header: '养老保险', key: 'pensionEmp', width: 10 },
{ header: '医疗保险', key: 'medicalEmp', width: 10 },
{ header: '失业保险', key: 'unemploymentEmp', width: 10 },
{ header: '住房公积金', key: 'housingEmp', width: 12 },
{ header: '专项附加扣除', key: 'specialAdditionalDeduction', width: 14 },
{ header: '其他扣除', key: 'otherDeduction', width: 10 },
{ header: '累计收入额', key: 'ytdIncome', width: 12 },
{ header: '累计减除费用', key: 'ytdBasicDeduction', width: 14 },
{ header: '累计专项扣除', key: 'ytdSpecialDeduction', width: 14 },
{ header: '累计专项附加扣除', key: 'ytdSpecialAdditional', width: 16 },
{ header: '累计应纳税所得额', key: 'ytdTaxableIncome', width: 16 },
{ header: '税率', key: 'taxRate', width: 8 },
{ header: '速算扣除数', key: 'quickDeduction', width: 12 },
{ header: '累计已预扣税额', key: 'ytdTaxDeducted', width: 14 },
{ header: '本期应预扣税额', key: 'tax', width: 14 },
{ header: '备注', key: 'remark', width: 20 },
]
ws.getRow(1).font = { bold: true }
let seq = 0
for (const e of entries) {
seq++
// 解密身份证号
let idCard: string = ''
try { if (e.employee.idCardNumber) idCard = decrypt(e.employee.idCardNumber) || '' } catch { idCard = e.employee.idCardNumber || '' }
// 从社保中拆分个人部分(简化:使用 socialEmp 总额按比例拆分)
const socialEmp = e.socialEmp || 0
const pensionEmp = Math.round(socialEmp * 0.56) // 养老约 56%
const medicalEmp = Math.round(socialEmp * 0.36) // 医疗约 36%
const unemploymentEmp = socialEmp - pensionEmp - medicalEmp // 剩余为失业
// 累计数据
const ytdTaxDeducted = (e as any).ytdTaxDeducted || e.tax || 0
ws.addRow({
seq,
name: e.employee.name,
idCardNumber: idCard,
incomeType: '工资薪金所得',
totalPay: e.totalPay || 0,
taxFreeIncome: 0,
basicDeduction: 5000, // 基本减除费用 5000/月
specialDeductionTotal: socialEmp + (e.housingEmp || 0),
pensionEmp,
medicalEmp,
unemploymentEmp,
housingEmp: e.housingEmp || 0,
specialAdditionalDeduction: e.employee.specialDeduction || 0,
otherDeduction: 0,
ytdIncome: e.totalPay || 0, // 简化:单月累计=本月
ytdBasicDeduction: 5000,
ytdSpecialDeduction: socialEmp + (e.housingEmp || 0),
ytdSpecialAdditional: e.employee.specialDeduction || 0,
ytdTaxableIncome: Math.max(0, (e.totalPay || 0) - 5000 - socialEmp - (e.housingEmp || 0) - (e.employee.specialDeduction || 0)),
taxRate: '',
quickDeduction: 0,
ytdTaxDeducted,
tax: e.tax || 0,
remark: '',
})
}
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
res.setHeader('Content-Disposition', contentDisposition(`个税申报表-${month}.xlsx`))
await workbook.xlsx.write(res)
res.end()
} catch (err) {
next(err)
}
})
export default router
+22 -6
View File
@@ -267,6 +267,16 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
if (idCheck.upgraded) idCard = idCheck.upgraded
}
// 社保基数:值为 0 或「无」表示不参保
const socialInsBaseVal = num(getField(r, '社保基数'))
const socialInsOptOut = val(getField(r, '社保基数')) === '无' || val(getField(r, '社保基数')) === '不缴'
const socialInsBase = socialInsOptOut ? 0 : (socialInsBaseVal || num(salary))
// 公积金基数:值为 0 或「无」表示不缴纳
const housingFundBaseVal = num(getField(r, '公积金基数'))
const housingFundOptOut = val(getField(r, '公积金基数')) === '无' || val(getField(r, '公积金基数')) === '不缴'
const housingFundBase = housingFundOptOut ? 0 : (housingFundBaseVal || num(salary))
const emp = await prisma.employee.create({
data: {
orgId, name, department: dept, hireDate,
@@ -283,21 +293,27 @@ router.post('/excel', authMiddleware, requireAdmin, upload.single('file'), async
address: val(getField(r, '住址')) || null,
bankName: val(getField(r, '开户行')) || null,
bankAccount: val(getField(r, '银行账号')) ? encrypt(val(getField(r, '银行账号'))) : null,
socialInsBase: num(getField(r, '社保基数')) || num(salary),
housingFundBase: num(getField(r, '公积金基数')) || num(salary),
socialInsBase,
housingFundBase,
specialDeduction: num(getField(r, '专项附加扣除')) || 0,
city: val(getField(r, '参保城市')) || null,
isPregnant: val(getField(r, '孕期')) === '是',
isInMedicalPeriod: val(getField(r, '医疗期')) === '是',
isWorkInjured: val(getField(r, '工伤')) === '是',
socialInsStartMonth: dateToMonth(hireDate),
housingFundStartMonth: dateToMonth(hireDate),
socialInsStartMonth: socialInsOptOut ? null : dateToMonth(hireDate),
housingFundStartMonth: housingFundOptOut ? null : dateToMonth(hireDate),
createdBy: userId,
},
})
await prisma.employeeSocialInsRecord.create({ data: { orgId, employeeId: emp.id, startMonth: dateToMonth(hireDate), endMonth: null, base: num(getField(r, '社保基数')) || num(salary), changeType: 'ONBOARDING', createdBy: userId } })
await prisma.employeeHousingFundRecord.create({ data: { orgId, employeeId: emp.id, startMonth: dateToMonth(hireDate), endMonth: null, base: num(getField(r, '公积金基数')) || num(salary), changeType: 'ONBOARDING', createdBy: userId } })
// 仅在未 opt-out 时创建社保记录
if (!socialInsOptOut) {
await prisma.employeeSocialInsRecord.create({ data: { orgId, employeeId: emp.id, startMonth: dateToMonth(hireDate), endMonth: null, base: socialInsBase, changeType: 'ONBOARDING', createdBy: userId } })
}
// 仅在未 opt-out 时创建公积金记录
if (!housingFundOptOut) {
await prisma.employeeHousingFundRecord.create({ data: { orgId, employeeId: emp.id, startMonth: dateToMonth(hireDate), endMonth: null, base: housingFundBase, changeType: 'ONBOARDING', createdBy: userId } })
}
await prisma.salaryChangeRecord.create({ data: { orgId, employeeId: emp.id, oldSalary: 0, newSalary: num(salary), effectiveDate: hireDate, effectiveMonth: dateToMonth(hireDate), endMonth: null, changeType: 'ONBOARDING', createdBy: userId } })
await prisma.employeeDepartmentRecord.create({ data: { orgId, employeeId: emp.id, oldDepartment: '', newDepartment: dept, effectiveMonth: dateToMonth(hireDate), endMonth: null, changeType: 'ONBOARDING', createdBy: userId } })
@@ -0,0 +1,406 @@
# 企业用工专家系统 — 操作问题与现有功能对照分析
> 文档日期:2026-07-31
> 整理人:用户反馈 + 系统代码核查
---
## 一、花名册模块
### 1.1 合同类型覆盖问题
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 只有标准劳动合同界面,劳务合同、承揽合同、临时工、非全日制等员工无法录入 | 数据库 `ContractType` 枚举支持 5 种:`FIXED`(固定期限)、`UNFIXED`(无固定期限)、`UNSIGNED`(未签)、`LABOR`(劳务)、`INTERNSHIP`(实习) | **后端已支持 LABOR 类型,但前端未暴露**。导入模板合同类型仅映射「固定期限/无固定期限/未签」3 种,缺少「劳务/实习」选项。前端合同添加界面也只展示标准劳动合同选项 |
| — | 前端 `Roster.tsx` 合同管理 Tab 和 `roster/ContractInfo.tsx` 中合同类型选择器未包含 LABOR、INTERNSHIP | **承揽合同、临时工、非全日制** 3 种类型在枚举中完全缺失,需要新增 |
### 1.2 花名册导入导出错位问题
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 导出只能导出姓名、合同期限等部分基本信息,无法导出全部信息 | `export.routes.ts` 花名册导出仅 7 列:姓名、部门、状态、入职日期、合同起始、合同结束、联系方式 | **导出字段严重不足**。缺少:性别、身份证号、手机号、社保基数、公积金基数、参保城市、银行账户、紧急联系人、学历、出生日期、专项附加扣除等 |
| 导入和导出系统错位 | 导入模板 `import.routes.ts` 支持 19 个字段(姓名、部门、性别、手机号、身份证号、入职日期、月工资、社保基数、公积金基数、专项附加扣除、参保城市、紧急联系人、紧急联系电话、住址、开户行、银行账号、孕期、医疗期、工伤) | **导入 19 字段 vs 导出 7 字段,严重不对齐**。导入的数据无法完整导出回溯 |
### 1.3 现有功能
| 功能项 | 支持情况 | 实现位置 |
|-------|---------|---------|
| 单个员工录入 | ✅ 支持 | `Roster.tsx``AddEmployeeModal` |
| 批量导入 | ✅ 支持 Excel 导入 | `import.routes.ts``/import/excel` |
| 花名册导出 | ⚠️ 仅 7 列基本信息 | `export.routes.ts``/export/roster` |
| 全量数据导出 | ✅ 支持 JSON/Excel 格式 | `export.routes.ts``/export/all`(管理员权限) |
| 员工详情 | ✅ 支持 10 个 Tab 页 | `roster/EmployeeProfile.tsx`(基本信息、合同、薪税社保、考勤加班、解聘、违纪、培训、绩效、证据链、变更历史) |
| 批量续签合同 | ✅ 支持 | `Roster.tsx``batchRenewMutation` |
| 批量解聘 | ✅ 支持 | `Roster.tsx``batchTerminateMutation` |
| 调薪记录 | ✅ 支持 | `roster/modals.tsx``SalaryChangeModal` |
| 部门变更 | ✅ 支持 | `roster/modals.tsx``DeptChangeModal` |
---
## 二、工资模块
### 2.1 计薪方式覆盖问题
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 只有标准工时工资模块,计件、计时方式无法录入 | `BatchEntry` 模型薪资项:`baseSalary`(基本工资)、`overtimePay`(加班费)、`allowance`(津贴)、`bonus`(奖金)、`deduction`(扣款)、`positionSalary`(岗位工资)、`performanceSalary`(绩效工资)、`senioritySalary`(工龄工资)及 4 类补贴 | **缺少计件工资字段**。无 `pieceworkCount`(件数)、`pieceworkUnitPrice`(单价)、`hourlyRate`(时薪)、`workHours`(工时) 等字段 |
| — | `PayrollBatchType` 枚举:`REGULAR`(常规)、`TERMINATION`(离职结算)、`BONUS`(年终奖/奖金)、`SEVERANCE`(补偿金按月) | **缺少计件/计时批次类型** |
### 2.2 现有功能
| 功能项 | 支持情况 | 实现位置 |
|-------|---------|---------|
| 发薪批次管理 | ✅ 支持创建/编辑/归档 | `Money.tsx` → batch tab |
| 薪酬模板 | ✅ 支持自定义薪资项 | `Money.tsx` → template tab |
| 加班费计算 | ✅ 支持工作日/休息日/法定节假日 | `Money.tsx` → overtime tab |
| 工资条管理 | ✅ 支持发布/确认/定时发送 | `Money.tsx` → payslip tab |
| Excel 导入工资 | ✅ 支持 | `import.routes.ts``/import/payroll` |
| 工资表导出 | ✅ 支持部门汇总 + 明细 | `Money.tsx` → 导出按钮 |
| 个税预览计算 | ✅ 支持累计预扣法 | `payroll.routes.ts``/payroll/tax-preview` |
| 年终奖单独计税 | ✅ 支持 | `payroll.service.ts``calcBonusTax` |
| 薪资项手工覆盖 | ✅ 社保/公积金可手动覆盖 | `payroll.service.ts``overrideSocial` |
---
## 三、社保模块
### 3.1 险种覆盖问题
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 全部是正规五险一金,无社保人员模块 | `SocialInsuranceConfig` 固定 5 险:养老、医疗、失业、工伤、生育 | **缺少「无社保」选项**。员工入职时自动创建社保记录,无法标记为「不参保」 |
| 纯商险或意外险员工无法录入 | `extraInsurances` JSON 字段支持附加险种配置(名称、企业/个人比例、基数类型) | **后端已支持附加险种,但前端仅展示已有配置**。无独立的「商险管理」模块,附加险种只能在社保配置中作为附加项录入 |
| 只有五险或三险且无公积金的员工,系统也会自动匹配公积金 | 入职导入时 `import.routes.ts` 第 300 行**无条件创建** `EmployeeHousingFundRecord`,基数默认等于月工资 | **缺少「是否缴纳公积金」开关**`EmployeeHousingFundRecord``status``optOut` 字段 |
| 基数自动为员工应发工资基数 | `import.routes.ts` 第 287 行:`housingFundBase: num(getField(r, '公积金基数')) || num(salary)` | 未填写公积金基数时自动使用月工资作为基数,**无法设为 0 或不缴** |
### 3.2 现有功能
| 功能项 | 支持情况 | 实现位置 |
|-------|---------|---------|
| 社保配置(五险比例) | ✅ 支持多城市多版本 | `SocialInsurance.tsx` → social tab |
| 公积金配置 | ✅ 支持基本/补充公积金 | `SocialInsurance.tsx` → housing tab |
| 附加险种配置 | ✅ 后端支持 JSON 字段 | `SocialInsuranceConfig.extraInsurances` |
| 社保基数调整 | ✅ 支持批量调基 | `SocialInsurance.tsx` → 调整按钮 |
| 月度社保办理 | ✅ 支持标记已办理 | `SocialInsurance.tsx` → monthly tab |
| 专项附加扣除 | ✅ 支持导入 | `import.routes.ts``/import/special-deduction` |
| 版本管理 | ✅ 支持历史版本 | `SocialInsurance.tsx` → 版本管理 |
| 社保导出 | ✅ 含在全量导出中 | `export.routes.ts``/export/all` |
---
## 四、考勤模块
### 4.1 打卡功能缺失
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 无法实现打卡模块,不如钉钉/企微好用 | 系统无打卡功能,`AttendanceRecord` 模型有 `checkInTime`/`checkOutTime` 字段但无打卡接口 | **完全未实现打卡功能**。系统定位为 HR 管理后台而非考勤终端 |
| 需要借助第三方平台导出数据再手工录入,增加工作量 | 支持 Excel 批量导入考勤记录(`import.routes.ts``/import/monthly` 考勤记录 sheet | **已有导入功能但无第三方系统对接**。缺少钉钉/企微/飞书 API 对接 |
### 4.2 现有功能
| 功能项 | 支持情况 | 实现位置 |
|-------|---------|---------|
| 考勤确认流程 | ✅ 支持员工端确认 | `Attendance.tsx` → confirm tab |
| 班次管理 | ✅ 支持创建/编辑班次 | `Attendance.tsx` → shifts tab |
| 排班管理 | ✅ 支持按日排班 | `Attendance.tsx` → schedule tab |
| 每日出勤记录 | ✅ 支持手动录入/查看 | `Attendance.tsx` → daily tab |
| 月度报表 | ✅ 支持汇总统计 | `Attendance.tsx` → monthly tab |
| 休假记录 | ✅ 支持请假类型管理 | `Attendance.tsx` → leaves tab |
| Excel 批量导入 | ✅ 支持月度导入 | `import.routes.ts``/import/monthly` |
| 员工端考勤查看 | ✅ 支持 | `portal/MyAttendance.tsx` |
---
## 五、发薪模块 — 个税自动算税
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 暂时无法实现个税自动算税 | **已实现累计预扣法个税计算**`payroll.service.ts``calcCumulativeTax()`,支持 7 级超额累进税率 | **用户反馈与系统现状不符**。系统已支持个税自动计算,可能在操作时未发现该功能 |
| — | `payroll.routes.ts``/payroll/tax-preview` 提供个税预览计算 | 前端 `Money.tsx` 有个税预览功能,支持输入薪资项后实时计算个税 |
| — | 归档批次时自动计算个税并写入 `BatchEntry.tax` | 归档时个税自动计算并锁定 |
| — | 支持年终奖单独计税:`calcBonusTax()` | 年终奖批次类型自动使用单独计税 |
| — | 支持专项附加扣除:`Employee.specialDeduction` | 个税计算时扣除专项附加扣除 |
| — | 个税跳档预警:`payroll.service.ts` 第 544 行 | 归档时检测个税异常波动并提示 |
### 现有功能
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 累计预扣法算税 | ✅ 已实现 | 7 级超额累进,年度累计预扣 |
| 个税实时预览 | ✅ 已实现 | 前端输入薪资项即可预览个税 |
| 年终奖单独计税 | ✅ 已实现 | 除以 12 找税率 + 速算扣除 |
| 专项附加扣除 | ✅ 已实现 | 支持导入和员工端填报 |
| 个税跳档预警 | ✅ 已实现 | 归档时自动检测 |
| YTD 累计数据 | ✅ 已实现 | 从已归档批次获取历史数据 |
---
## 六、发票模块和审批模块
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 系统暂时没有发票模块 | **完全未实现**。无 Invoice 模型、无发票路由、无发票页面 | 需从零设计开发 |
| 系统暂时没有审批模块 | **部分实现**。解聘记录有 `status` 字段(DRAFT/PENDING_APPROVAL/APPROVED/REJECTED/EXECUTING/COMPLETED/CANCELLED),但无通用审批流 | 仅有解聘审批状态流转,无通用审批引擎(请假审批、调薪审批、合同审批等) |
### 现有相关功能
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 解聘审批流 | ⚠️ 有状态字段但无审批界面 | `TerminationRecord.status` 支持 7 种状态,但前端无审批操作入口 |
| 操作日志审计 | ✅ 支持 | `AuditLog` 模型记录所有操作 |
| 合同确认链接 | ✅ 支持 | `ContractConfirmLink` 模型,员工端确认签署 |
---
## 七、功能对照总览
| 模块 | 用户反馈问题 | 系统实际状态 | 严重程度 | 修复难度 |
|------|------------|------------|---------|---------|
| 花名册 | 缺少劳务/承揽/临时工/非全合同类型 | 枚举缺 3 种类型,前端缺 2 种展示 | 🔴 高 | 中 |
| 花名册 | 导出字段不全、导入导出错位 | 导出 7 列 vs 导入 19 列 | 🔴 高 | 低 |
| 工资 | 缺少计件/计时计薪方式 | 无相关字段和批次类型 | 🟡 中 | 高 |
| 社保 | 无社保人员无法标记 | 入职自动创建社保记录,无 optOut | 🟡 中 | 中 |
| 社保 | 商险/意外险无独立模块 | 后端有 extraInsurances 字段 | 🟡 中 | 中 |
| 社保 | 无公积金员工自动匹配 | 无条件创建公积金记录 | 🟡 中 | 低 |
| 考勤 | 无打卡功能 | 系统无打卡能力 | 🟡 中 | 高 |
| 考勤 | 第三方数据需手工导入 | 有 Excel 导入但无 API 对接 | 🟢 低 | 中 |
| 发薪 | 个税无法自动算税 | **已实现**,用户可能未发现 | 🟢 低 | — |
| 发票 | 无发票模块 | 完全未实现 | 🟡 中 | 高 |
| 审批 | 无审批模块 | 仅解聘有状态流转 | 🟡 中 | 高 |
---
## 八、建议优先级
### P0 — 立即修复(影响日常使用)
1. **花名册导出字段扩充**:对齐导入的 19 个字段,增加性别、身份证号、社保基数、公积金基数、参保城市、银行账户等
2. **社保「无社保」选项**:入职时增加「是否参保」开关,不参保则不创建社保记录
3. **公积金「不缴纳」选项**:入职时增加「是否缴纳公积金」开关,不缴纳则不创建公积金记录
### P1 — 近期优化(提升覆盖面)
4. **合同类型扩充**:新增「承揽合同」「临时工」「非全日制」3 种枚举,前端添加对应选项
5. **导入模板合同类型对齐**:导入模板增加「劳务」「实习」选项映射
6. **商险/意外险独立管理**:在社保页面增加「商险管理」Tab,独立于五险一金
### P2 — 中期规划(功能扩展)
7. **计件/计时工资**:新增 `PayrollBatchType.PIECEWORK``TIMework``BatchEntry` 增加件数/单价/时薪字段
8. **考勤第三方对接**:钉钉/企微/飞书 API 数据同步
9. **发票模块**:新增 Invoice 模型,支持发票录入、查验、归档
10. **通用审批引擎**:支持请假、调薪、合同等多场景审批流
---
## 九、系统现有完整模块清单
| 分类 | 模块 | 路由 | 核心功能 |
|------|------|------|---------|
| 工作台 | 总览 | `/` | 数据看板、风险提醒、月度任务、价值报告 |
| 工作台 | 工作日历 | `/calendar` | 月度 HR 事项日历 |
| 员工管理 | 花名册 | `/roster` | 员工 CRUD、批量导入、导出、合同管理、调薪、部门变更 |
| 员工管理 | 用工办理 | `/work-process` | 入职/离职流程管理 |
| 员工管理 | 考勤确认 | `/attendance` | 班次/排班/出勤/月度报表/休假 |
| 员工管理 | 解聘补偿 | `/termination` | 解聘记录、补偿金计算 |
| 员工管理 | 特殊状态 | `/special-status` | 孕期/哺乳期/工伤/医疗期 |
| 薪税社保 | 薪税管理 | `/money` | 发薪批次、薪酬模板、加班费、工资条 |
| 薪税社保 | 社保公积金 | `/social` | 五险配置、公积金配置、基数调整、月度办理 |
| 合规风控 | 证据链 | `/evidence` | 入职/合同/培训/违纪证据管理 |
| 合规风控 | 规章制度 | `/policies` | 制度发布、员工签收 |
| 合规风控 | 用工体检 | `/tools/health-check` | 5 维度合规健康度评分 |
| 合规风控 | 医疗期计算 | `/tools/medical-period` | 医疗期期限计算器 |
| 合规风控 | 年度价值 | `/tools/annual-value` | 年度 HR 价值报告 |
| AI 辅助 | AI 顾问 | `/ai-assistant` | 劳动法 AI 问答 |
| AI 辅助 | 文本模板 | `/templates` | 合同/通知/制度模板管理 |
| 系统 | 通知管理 | `/notifications` | 风险提醒通知配置 |
| 系统 | 操作日志 | `/audit` | 操作审计日志 |
| 系统 | 设置 | `/settings` | 企业信息、导入配置 |
| 员工端 | 门户 | `/portal/*` | 工资条查看、合同确认、考勤确认、制度签收、入职办理 |
---
## 十、绩效分析与统计看板
### 10.1 现状
| 功能项 | 支持情况 | 实现位置 | 说明 |
|-------|---------|---------|------|
| 绩效记录录入 | ✅ 支持 | `roster/PerformanceInfo.tsx` | 在员工详情中手动新增绩效记录(考核周期、得分、等级 A/B/C/D、评语、改进计划) |
| 绩效员工确认 | ✅ 支持 | `PerformanceRecord.employeeAck` | 员工可确认考核结果 |
| 绩效看板 | ❌ 未实现 | — | 无独立的绩效统计看板页面 |
| 绩效趋势分析 | ❌ 未实现 | — | 无绩效得分趋势、部门对比、等级分布等分析 |
| 绩效与薪酬联动 | ❌ 未实现 | — | 绩效结果未与薪酬调整、奖金分配关联 |
### 10.2 差距分析与建议
| 需求 | 现状 | 建议方案 | 预算评估 |
|------|------|---------|---------|
| 绩效统计看板 | 无 | 新增 `/tools/performance-dashboard` 页面,展示:等级分布饼图、部门绩效对比柱状图、月度/季度趋势折线图、Top/Bottom 员工排名 | 🟢 低 — 前端页面 + 1 个聚合 API,复用现有 `PerformanceRecord` 数据 |
| 绩效与薪酬联动 | 无 | 在发薪批次中增加「绩效奖金」列,自动关联最近一期绩效等级,按规则计算奖金系数 | 🟡 中 — 需扩展 `BatchEntry` 增加绩效奖金字段 + 计算规则配置 |
| 绩效改进追踪 | 有改进计划字段但无追踪 | 在绩效记录中增加「改进目标」「完成度」「复评得分」字段,支持改进闭环 | 🟢 低 — 扩展 `PerformanceRecord` 模型 + 前端展示 |
### 10.3 现有可复用的看板基础设施
| 基础设施 | 实现位置 | 复用方向 |
|---------|---------|---------|
| Dashboard 数据看板 | `Dashboard.tsx` | 绩效看板可复用 Card/图表组件和布局模式 |
| 人员统计 API | `/dashboard/workforce-stats` | 绩效看板可复用聚合统计 API 模式 |
| 年度价值报告 | `/tools/annual-value` | 绩效年度汇总可嵌入年度价值报告 |
| 图表组件 | recharts(已引入) | 直接复用饼图/柱状图/折线图 |
---
## 十一、入离职合同签署线上化
### 11.1 现状
| 功能项 | 支持情况 | 实现位置 | 说明 |
|-------|---------|---------|------|
| 入职线上化 | ✅ 已实现 | `OnboardingLink` 模型 + `portal.routes.ts` | HR 生成入职链接 → 员工扫码/点击 → 填写个人信息 → 上传附件(身份证、银行卡等)→ HR 审核 |
| 入职附件上传 | ✅ 已实现 | `portal.routes.ts``/onboarding/:token/upload` | 支持文件上传到 `/uploads/onboarding/` |
| 入职链接管理 | ✅ 已实现 | `OnboardingLink.status` | PENDING → APPROVED/REJECTED/CANCELLED |
| 合同签署线上化 | ✅ 已实现 | `ContractConfirmLink` 模型 + `portal.routes.ts` | HR 发送合同确认链接 → 员工手机验证码验证 → 确认签署 → 记录 IP/时间/验证码为证据 |
| 合同签署证据链 | ✅ 已实现 | `portal.routes.ts` 第 295 行 | 签署确认时自动创建证据链(IP、UserAgent、短信验证码) |
| 合同电子签署 | ⚠️ 部分 | `LaborContract.signMethod` 支持 `ELECTRONIC` | 有字段标记但未对接第三方电子签平台(如 e签宝、法大大) |
| 离职线上化 | ⚠️ 部分 | `TerminationRecord` 模型 | HR 端可发起离职/解聘流程,但员工端无离职申请入口 |
| 离职确认 | ❌ 未实现 | — | 员工无法在线确认离职/解聘结果 |
### 11.2 差距分析与建议
| 需求 | 现状 | 建议方案 | 预算评估 |
|------|------|---------|---------|
| 入职流程完善 | 已有基础流程 | 补充:入职信息预览 PDF 导出、入职指南推送、入职材料清单勾选 | 🟢 低 — 前端增强 |
| 合同电子签对接 | 有签署确认但非真电子签 | 对接 e签宝/法大大 API,实现具有法律效力的电子签名 | 🟡 中 — API 对接费用 + 开发量 |
| 员工离职申请 | 无 | 员工端门户新增离职申请入口,HR 端审批 | 🟢 低 — 复用现有门户框架 |
| 离职交接清单 | 无 | 离职流程增加交接清单(设备归还、工作交接、文件移交) | 🟢 低 — 前端表单 + 后端模型扩展 |
| 入离职统计看板 | 无 | Dashboard 新增入离职趋势图、离职率分析、入职转化率 | 🟢 低 — 复用 `Employee.hireDate` + `TerminationRecord` 数据 |
### 11.3 现有入离职线上化流程
```
入职流程(已实现):
HR 创建员工 → 生成入职链接(OnboardingLink) → 员工扫码填写信息 → 上传附件 → HR 审核 → 创建员工档案
合同签署流程(已实现):
HR 创建合同(LaborContract) → 生成确认链接(ContractConfirmLink) → 员工手机验证码验证 → 确认签署 → 自动生成证据链
离职流程(部分实现):
HR 发起解聘/离职(TerminationRecord) → 计算补偿金 → 记录证据链 → [缺失:员工确认/交接清单]
```
---
## 十二、薪酬核算线上化
### 12.1 现状
| 功能项 | 支持情况 | 实现位置 | 说明 |
|-------|---------|---------|------|
| 薪酬核算 | ✅ 已实现 | `payroll.service.ts``calcBatchEntry()` | 自动计算:基本工资 + 岗位/绩效/工龄工资 + 各类补贴 + 加班费 - 扣款 → 社保公积金 → 个税 → 实发 |
| 社保公积金自动计算 | ✅ 已实现 | `calcSocialInsurance()` + `calcHousingFund()` | 按城市配置比例自动计算企业和个人部分 |
| 个税自动计算 | ✅ 已实现 | `calcCumulativeTax()` | 累计预扣法,7 级超额累进 |
| 年终奖单独计税 | ✅ 已实现 | `calcBonusTax()` | 除以 12 找税率 + 速算扣除 |
| 多批次发薪 | ✅ 已实现 | `PayrollBatchType` 4 种类型 | 常规/离职结算/年终奖/补偿金按月 |
| 薪酬模板 | ✅ 已实现 | `PayslipItem` 模型 | 支持自定义薪资项和计算公式 |
| 工资条在线发放 | ✅ 已实现 | `Payslip.status` PENDING→PUBLISHED | 支持定时发送、员工端确认 |
| 工资条员工确认 | ✅ 已实现 | `Payslip.confirmedAt` | 员工查看并确认工资条 |
| Excel 导入工资 | ✅ 已实现 | `/import/payroll` | 支持批量导入工资数据 |
| 薪税汇总导出 | ✅ 已实现 | `/export/payroll` | 导出月度薪税汇总 Excel |
| 个税预览 | ✅ 已实现 | `/payroll/tax-preview` | 输入薪资项实时预览个税 |
| 个税跳档预警 | ✅ 已实现 | `payroll.service.ts` 第 544 行 | 归档时检测个税异常波动 |
| 计件/计时工资 | ❌ 未实现 | — | 无相关字段和计算逻辑 |
### 12.2 差距分析
| 需求 | 现状 | 差距 |
|------|------|------|
| 薪酬核算线上化 | **已基本实现** | 用户可能未充分了解现有功能 |
| 计件/计时工资 | 未实现 | 需扩展数据模型和计算逻辑 |
| 薪酬分析看板 | 无 | 缺少薪酬分布、薪酬趋势、部门薪酬对比等分析 |
| 调薪记录追踪 | ✅ 有 `SalaryChangeRecord` | 但缺少调薪审批流和调薪建议 |
---
## 十三、个税申报线上化
### 13.1 现状
| 功能项 | 支持情况 | 实现位置 | 说明 |
|-------|---------|---------|------|
| 个税计算 | ✅ 已实现 | `calcCumulativeTax()` | 累计预扣法,支持 7 级超额累进 |
| 个税预览 | ✅ 已实现 | `/payroll/tax-preview` | 实时预览个税明细 |
| 个税累计数据 | ✅ 已实现 | `Payslip.ytdTaxDeducted` | 年度累计已扣税额 |
| 个税申报提醒 | ✅ 已实现 | `risk.service.ts` 第 408 行 | 每月个税申报日提醒 |
| 个税申报日配置 | ✅ 已实现 | `NotificationSetting.taxDay` | 可配置每月申报截止日 |
| 个税申报报表 | ❌ 未实现 | — | 无个税申报表导出(自然人电子税务局格式) |
| 个税批量申报 | ❌ 未实现 | — | 未对接税务系统 API |
| 专项附加扣除导入 | ✅ 已实现 | `/import/special-deduction` | 支持批量导入 |
### 13.2 差距分析与建议
| 需求 | 现状 | 建议方案 | 预算评估 |
|------|------|---------|---------|
| 个税申报表导出 | 无 | 生成「个人所得税申报表」Excel,格式对齐自然人电子税务局模板 | 🟢 低 — 后端新增 1 个导出接口 |
| 个税年度汇算提示 | 无 | 每年 3-6 月提示员工进行年度汇算清缴 | 🟢 低 — 定时任务 + 通知 |
| 个税申报数据校验 | 无 | 导出前自动校验:身份证号完整性、累计数据一致性 | 🟢 低 — 校验逻辑 |
| 对接税务系统 | 无 | 调用自然人电子税务局 API 实现在线申报 | 🔴 高 — 需税务系统授权 + API 对接,成本高 |
### 13.3 个税申报线上化实施路径
```
阶段一(低成本,立即可做):
✅ 个税自动计算 → 已实现
✅ 个税申报日提醒 → 已实现
📋 个税申报表 Excel 导出 → 新增导出接口,对齐税务局模板格式
阶段二(中等成本):
📋 个税申报数据校验 → 导出前自动校验数据完整性
📋 个税年度汇算提醒 → 每年 3 月推送汇算通知
阶段三(高成本,远期规划):
📋 对接自然人电子税务局 API → 在线批量申报(需获取税务系统授权)
```
---
## 十四、综合分析与实施建议
### 14.1 功能成熟度矩阵
| 功能领域 | 计算能力 | 线上化程度 | 分析看板 | 申报对接 | 综合评分 |
|---------|---------|-----------|---------|---------|---------|
| 薪酬核算 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | — | 4/5 |
| 个税 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | — | ⭐ | 3/5 |
| 入职 | ⭐⭐⭐ | ⭐⭐⭐⭐ | — | — | 3.5/5 |
| 合同签署 | ⭐⭐⭐ | ⭐⭐⭐⭐ | — | — | 3.5/5 |
| 离职 | ⭐⭐⭐⭐ | ⭐⭐ | — | — | 3/5 |
| 绩效 | ⭐⭐ | ⭐⭐ | ⭐ | — | 1.5/5 |
| 社保 | ⭐⭐⭐⭐ | ⭐⭐⭐ | — | ⭐ | 3/5 |
### 14.2 低预算优先实施清单
| 优先级 | 功能项 | 预算 | 预期效果 |
|-------|-------|------|---------|
| P0 | 个税申报表 Excel 导出 | 🟢 低 | HR 可直接用导出表去税务局申报,免去手工整理 |
| P0 | 入离职统计看板 | 🟢 低 | Dashboard 新增入离职趋势、离职率,管理层一目了然 |
| P0 | 绩效统计看板 | 🟢 低 | 新增绩效分布、部门对比、趋势分析页面 |
| P1 | 员工离职申请入口 | 🟢 低 | 员工端门户新增离职申请,完善离职线上化 |
| P1 | 薪酬分析看板 | 🟢 低 | 薪酬分布、部门薪酬对比、人均成本趋势 |
| P1 | 离职交接清单 | 🟢 低 | 离职流程增加交接确认步骤 |
| P2 | 合同电子签对接 | 🟡 中 | 对接 e签宝/法大大,实现法律效力电子签 |
| P2 | 个税申报数据校验 | 🟢 低 | 导出前校验数据完整性 |
| P3 | 对接税务系统在线申报 | 🔴 高 | 实现个税全流程线上化 |
### 14.3 实施建议总结
> **核心结论**:系统在薪酬核算和个税计算方面已具备完整的线上化能力,用户反馈"无法实现个税自动算税"可能是操作不熟练导致。当前最大的短板在于**缺乏统计分析看板**和**个税申报报表导出**。
**低成本高收益的 3 件事**
1. **个税申报表导出**1-2 天开发量)— 新增 `/export/tax-declaration?month=YYYY-MM` 接口,导出格式对齐自然人电子税务局模板,包含:员工姓名、身份证号、应发工资、社保扣除、公积金扣除、专项附加扣除、应纳税所得额、税率、速算扣除数、已预扣税额、本月应扣税额
2. **入离职统计看板**2-3 天开发量)— Dashboard 新增「人员流动」卡片:本月入职人数、离职人数、离职率趋势图(近 12 个月)、部门离职分布
3. **绩效统计看板**2-3 天开发量)— 新增 `/tools/performance-dashboard` 页面:绩效等级分布饼图、部门绩效均分对比柱状图、月度/季度趋势折线图、Top 10 / Bottom 10 员工排名
以上 3 项均复用现有数据和基础设施,无需新增数据模型,开发成本低,用户感知提升明显。
@@ -0,0 +1,979 @@
# TurboHR UI/UX 完整优化方案
> 版本:V1.0
> 日期:2026-07-31
> 适用范围:企业管理端、员工门户、平台管理端
> 参考方向:Gusto 的任务驱动体验、温暖可信的视觉语言、员工自助和低门槛业务流程
> 说明:本方案借鉴 Gusto 的产品原则,不复制其品牌、页面或视觉资产。
---
## 1. 项目目标
TurboHR 已具备员工、合同、考勤、薪酬、社保公积金、离职、制度、证据链、审批、通知、员工门户及 AI 顾问等较完整的后端能力。当前优化重点不应继续堆叠入口,而应将既有能力组织成更容易理解和完成的工作流。
本轮优化目标如下:
1. 将产品从“模块型管理后台”升级为“任务型 HR 工作台”。
2. 让非专业 HR 用户也能在清晰引导下完成发薪、入职、离职和月度社保等复杂业务。
3. 建立国际化、可信、温暖但不过度装饰的品牌与视觉体系。
4. 减少页面跳转、重复录入、操作犹豫和错误返工。
5. 提高员工自助率,减少 HR 重复答疑和资料收集工作。
6. 建立可扩展的设计系统,为中英文、多地区和多组织能力预留空间。
### 1.1 核心设计原则
- **任务优先**:首先告诉用户现在要做什么,而不是先展示系统有哪些模块。
- **渐进披露**:日常信息优先,高级配置在需要时出现。
- **一步一决策**:复杂流程每一步只解决一个主要问题。
- **状态透明**:所有流程都能看见当前状态、负责人、截止时间和下一步。
- **异常前置**:尽量在提交、归档、发布之前发现问题。
- **人物优先**:HR 系统中的主体是员工,不是数据表和字段。
- **一致可预测**:同一类动作、状态和反馈在所有模块中保持一致。
- **国际框架、本地语义**:交互采用国际通行方式,劳动用工文案保持中国法律语义准确。
---
## 2. 当前产品诊断
### 2.1 信息架构问题
当前侧边栏包含工作台、员工管理、薪税社保、合规风控、AI 辅助和系统等多个分组,并且所有分组默认展开。日常高频任务、高级配置、辅助工具和系统管理处于同一层级。
主要影响:
- 新用户无法迅速判断应该从哪里开始。
- 低频工具持续占用导航空间。
- 用户需要理解系统模块之间的关系,才能完成真实业务。
- “考勤确认—加班计算—发薪—工资条发布”等连续工作被人为拆散。
- 页面入口多,但缺乏统一的“我的工作”视角。
### 2.2 页面结构问题
- Dashboard 同时承担概览、薪税、风险、待办、成本和人力分析,第一屏焦点不清。
- Money、Termination、SocialInsurance、AIAssistant 等页面体量过大,局部交互难以保持一致。
- 页面标题、说明、工具栏、筛选区和数据区缺乏完全统一的结构。
- 大量操作按钮同时出现,主要动作与次要动作区分不足。
- 表格能够承载数据,但不够支持重复、高频和批量操作。
- 页面状态经常表现为模块状态,缺少负责人、截止时间、影响范围和下一步说明。
### 2.3 视觉问题
- 当前紫色主色与灰白卡片具有较强的通用后台模板感。
- 品牌名“企业用工专家”描述了功能,但品牌识别度不足。
- 大量 `text-xs` 导致信息虽然密集,但阅读压力较大。
- 页面主要依赖卡片和边框分区,缺少明确的视觉层级。
- 图标、状态标签、按钮密度和留白在不同页面存在差异。
- `max-w-content` 已配置,但主内容容器未实际限制超宽屏宽度。
### 2.4 交互问题
- 用户需要跨页面记忆上下文和上一步结果。
- 部分复杂流程虽有步骤,但缺少持续可见的摘要和风险提示。
- 批量操作入口常驻,未根据选择状态渐进出现。
- 错误反馈偏结果型,缺少问题定位与修复建议。
- AI 顾问与业务上下文分离,用户需要重新描述员工、批次或风险背景。
- 员工门户功能已覆盖多个场景,但仍更像功能集合,不像统一的员工工作台。
---
## 3. 目标产品框架
### 3.1 目标一级导航
| 一级入口 | 主要内容 | 核心用户任务 |
|---|---|---|
| 首页 | 今日任务、审批、风险、截止日、快捷创建、近期动态 | 判断今天先做什么 |
| 团队 | 员工目录、入职、异动、合同、特殊状态、离职 | 管理员工全生命周期 |
| 薪酬 | 发薪、工资条、加班、社保公积金、专项扣除 | 完成每月薪酬工作 |
| 时间 | 考勤、排班、休假、月度确认 | 管理工时和出勤 |
| 合规 | 风险、制度、证据链、用工体检、审计 | 发现并处理用工风险 |
全局右上区域保留:
- 全局搜索/命令面板。
- 快速创建按钮。
- 待审批入口。
- 通知入口。
- 帮助与 AI 助手。
- 组织及个人菜单。
以下内容移入“设置”或“更多”:
- 文本模板。
- 数据导入和导出。
- 用户与权限。
- 企业信息。
- 通知规则。
- 社保和公积金基础配置。
- 审计配置及平台级管理。
### 3.2 导航行为
- 默认只展开当前一级导航对应的二级菜单。
- 记住用户最近访问的页面和分组状态。
- 当前页面使用底色、左侧标识和文本颜色共同表达,不只依赖颜色。
- 导航底部提供“设置”和“帮助”,避免与日常模块混排。
- 桌面端侧边栏宽度建议为 224–240px,可折叠为 64px 图标栏。
- 移动端不复刻完整侧边栏,使用“首页、团队、待办、我的”四入口底栏。
### 3.3 全局搜索和命令面板
快捷键建议为 `Cmd/Ctrl + K`,搜索范围包括:
- 员工姓名、手机号、员工编号。
- 发薪批次和月份。
- 合同、制度和证据链。
- 页面与功能。
- 可执行命令,例如“新增员工”“创建发薪批次”“发起离职”。
搜索结果按“人员、任务、记录、功能”分组,最近使用项优先。敏感信息不应出现在未经授权的搜索结果摘要中。
---
## 4. 首页:从数据看板升级为任务中心
### 4.1 页面目标
首页第一屏必须回答:
1. 今天最需要处理什么?
2. 最近一个重要截止日是什么?
3. 哪些流程或员工存在异常?
### 4.2 推荐布局
#### 顶部区域
- 问候语:`早上好,李经理`
- 补充信息:`7 月 31 日,星期五 · 上海`
- 右侧主按钮:`+ 快速创建`
- 可选企业/组织切换器。
#### 主任务卡
只展示当前最重要的一项任务,例如:
> 7 月工资等待复核
> 63 名员工,预计应发 ¥428,560,计划 8 月 5 日发布
> 3 项异常需要处理
主操作:`继续复核`
次操作:`查看异常``稍后提醒`
主任务的选择规则:
1. 已逾期事项。
2. 高风险合规事项。
3. 需要当前用户审批的事项。
4. 未来三日内到期事项。
5. 当前月度流程的下一步。
#### 待办队列
使用分段控制器切换:
- 需要我处理。
- 等待他人。
- 即将到期。
- 已完成。
每条待办包含:事项、关联员工或批次、截止时间、风险级别、负责人和一个直接操作。
#### 人员动态
- 待入职。
- 本周入职。
- 合同即将到期。
- 试用期即将结束。
- 特殊状态提醒。
- 离职交接中。
#### 数据洞察
放在第二屏,避免与任务竞争注意力:
- 本月人力成本。
- 年度成本趋势。
- 部门人数变化。
- 风险分布。
- 员工完成率和自助率。
### 4.3 空状态
无待办时不显示空白卡片,应表达积极完成状态:
> 今天没有待处理事项
> 下一次发薪准备将在 8 月 1 日开始。
可提供 `查看日历``创建任务` 两个入口。
---
## 5. 团队与员工体验
### 5.1 员工目录
默认列表列建议:
| 列 | 内容 |
|---|---|
| 员工 | 头像、姓名、职位/部门 |
| 状态 | 在职、待入职、试用期、离职处理中、已离职 |
| 入职日期 | 本地化日期和在职时长 |
| 合同 | 合同类型、到期时间或风险 |
| 薪酬状态 | 已配置、缺失、变更待生效 |
| 下一项任务 | 最需要完成的员工相关事项 |
| 操作 | 查看详情和更多菜单 |
优化要求:
- 点击整行进入员工详情。
- 搜索与筛选始终位于同一工具栏。
- 筛选条件展示为可移除的条件标签。
- 支持保存视图,例如“合同 30 天内到期”“上海在职员工”。
- 选中员工后再出现批量操作栏。
- 批量离职、删除等危险操作必须显示影响范围和不可逆后果。
- 支持列显示管理、列冻结和舒适/紧凑密度切换。
### 5.2 员工 Profile Shell
员工详情采用统一框架,避免不同档案页面重复搭建标题和导航。
头部固定信息:
- 头像、姓名、职位、部门。
- 在职状态和关键风险。
- 联系方式。
- 入职日期和直属负责人。
- `编辑资料` 主按钮及更多操作菜单。
推荐 Tab
1. 概览。
2. 工作信息。
3. 薪酬福利。
4. 考勤休假。
5. 文件合同。
6. 绩效与事件。
7. 历史记录。
概览页内容:
- 待完成事项。
- 当前合同摘要。
- 薪酬与参保摘要。
- 最近考勤异常。
- 最近文件和记录。
- 生命周期时间线。
隐私要求:
- 身份证、银行卡、工资默认遮罩。
- 查看完整敏感信息需要具备权限。
- 查看和导出敏感数据写入审计日志。
- 在操作附近说明数据用途,避免用户不清楚为何收集。
### 5.3 入职流程
统一为五阶段:
`发送邀请 → 员工填写 → HR 审核 → 合同确认 → 完成配置`
每位待入职员工显示:
- 总完成度。
- 当前阶段。
- 缺失资料。
- 员工最后操作时间。
- 预计入职日期。
- 可执行动作,如提醒、重新发送、代为补录。
完成配置应包含合同、薪酬、社保、班次和系统访问权限,不应只以员工资料提交为终点。
### 5.4 离职流程
保留现有六步能力,并统一为:
1. 选择员工和离职类型。
2. 确认原因与日期。
3. 合规检查。
4. 费用结算。
5. 工作交接和材料。
6. 复核、审批与执行。
桌面端使用“主内容 + 右侧摘要”布局。右侧始终显示:
- 员工和离职类型。
- 生效日期。
- 补偿金额。
- 风险级别。
- 缺失材料。
- 当前审批状态。
底部固定操作栏:`保存草稿``上一步``继续`。最后一步根据权限显示 `提交审批``执行离职`,避免三个同等权重的终态按钮同时出现。
---
## 6. 薪酬与发薪体验
### 6.1 月度发薪中心
将发薪操作组织为一个连续流程:
`准备数据 → 异常检查 → 复核工资 → 归档 → 发布工资条 → 完成`
页面顶部显示:
- 月份和批次名称。
- 流程步骤。
- 当前负责人。
- 计划发布时间。
- 员工数量和预计总额。
- 自动保存状态。
### 6.2 准备数据
集中呈现以下数据源的完成情况:
- 员工和薪酬结构。
- 考勤。
- 加班。
- 请假。
- 社保公积金。
- 专项附加扣除。
- 奖金、补偿或其他一次性项目。
每个数据源显示“已同步、存在异常、尚未导入、无需处理”。支持直接进入问题位置,不要求用户自行寻找模块。
### 6.3 异常检查
使用后端现有 pre-check 能力构建发薪质量门禁。
异常按严重级别分为:
- 阻断:未配置薪资、关键身份信息缺失、计算失败。
- 警告:与上月相比变化显著、加班异常、实发为负数。
- 提示:新员工首月、离职结算、一次性奖金。
每条异常需要提供:
- 员工。
- 具体字段。
- 当前值和参考值。
- 影响。
- 建议操作。
- `立即修复` 入口。
修复后自动重新检查,并保留处理记录。
### 6.4 工资复核表
- 固定员工姓名列和实发工资列。
- 工资项目支持分组展开和折叠。
- 修改后的单元格明确标识,并支持撤销。
- 显示与上月的金额和百分比变化。
- 支持筛选“仅看异常”“仅看已修改”“仅看新增员工”。
- 表尾固定显示人数、应发、扣款、税额、公司成本和实发合计。
- 大数据量使用虚拟滚动或服务端分页,避免一次渲染全部员工。
### 6.5 归档与发布
归档前弹出复核摘要,而不是只询问是否确认:
- 员工数量。
- 总金额。
- 未解决警告。
- 后续影响。
- 归档后是否可撤销。
归档后将主要操作变为 `发布工资条`。支持立即发布和定时发布,发布完成后展示员工查看/确认进度。
---
## 7. 时间、考勤和排班
### 7.1 模块结构
建议二级导航:
- 月度概览。
- 每日出勤。
- 排班。
- 休假。
- 规则设置。
现有六个平级 Tab 应重新按使用频率分组,高级班次设置放入次级入口。
### 7.2 月度确认流程
`导入/同步 → 识别异常 → 员工确认 → HR 锁定 → 同步发薪`
页面显示:
- 应确认人数。
- 已确认人数。
- 未确认人员。
- 异常考勤数量。
- 加班总时长。
- 当前发布状态。
“取消发布”属于次要危险操作,应收进更多菜单并说明会对员工端及发薪数据造成的影响。
### 7.3 排班
- 周视图作为默认视图。
- 班次用色块与缩写共同表达,不能只依赖颜色。
- 支持复制上周、按部门批量排班和冲突检测。
- 在发布排班前检查重复班次、休假冲突和连续工作风险。
---
## 8. 社保公积金体验
### 8.1 日常办理与配置分离
日常用户默认进入“月度办理”,配置版本、费率试算和基数调整放入“规则与配置”。
月度办理流程:
`确认增减员 → 检查基数变化 → 生成申报名单 → 导出/办理 → 标记完成`
每一步显示:
- 待办数量。
- 关键变化。
- 是否存在阻断问题。
- 上一步完成时间和操作者。
### 8.2 配置版本
- 使用时间线展示历史版本。
- 清楚标记当前生效版本和未来版本。
- 新建版本时显示与当前版本的差异。
- 应用调基前展示影响人数和公司成本变化。
- 允许导出受影响员工名单。
---
## 9. 合规、制度与证据链
### 9.1 统一风险中心
将风险、用工体检、合同提醒、特殊状态提醒和相关证据入口汇总为统一风险中心。
风险列表字段:
- 风险事项。
- 关联员工/流程。
- 风险等级。
- 截止时间。
- 负责人。
- 建议动作。
- 证据完整度。
- 处理状态。
风险详情使用三栏结构:问题说明、法律/制度依据、处理动作与证据。
### 9.2 规章制度
制度发布流程:
`起草 → 审核 → 公示/征求意见 → 发布 → 员工阅读确认 → 归档`
制度列表优先展示状态、覆盖人数、已读率、未读人员和最近更新时间。员工已读统计应能直接发送提醒。
### 9.3 证据链
证据链采用时间线表达,按事件展示:
- 发生时间。
- 事件类型。
- 操作者和相关人员。
- 文件及来源。
- 是否完整、是否签署或确认。
- 与制度、审批和员工事件的关联。
支持按离职、违纪、考勤、合同等场景生成证据包,并在导出前展示包含内容和敏感信息提示。
---
## 10. AI 顾问优化
### 10.1 从独立聊天升级为上下文助手
保留完整 AI 工作区,同时在业务页面提供“询问 AI”入口。系统自动附带当前上下文,例如:
- 当前员工和已授权档案摘要。
- 当前发薪批次和异常。
- 当前离职原因、日期和风险评估。
- 当前制度文档或证据链。
用户不需要重新描述“这个员工是谁、现在在哪一步”。发送前应明确显示即将带入的上下文,并允许取消部分内容。
### 10.2 AI 输出结构
优先提供结构化结果:
- 结论。
- 风险级别。
- 依据。
- 建议步骤。
- 可生成材料。
- 需要人工确认的部分。
AI 建议不能直接执行高风险操作。生成合同、制度或离职材料后,需要进入预览、编辑和确认流程。
---
## 11. 员工门户
### 11.1 目标结构
移动端首页优先展示:
- 我的待办。
- 最新工资条。
- 考勤和休假。
- 合同与文件。
- 制度阅读确认。
- 个人资料。
底部导航建议:`首页|时间|文件|我的`
### 11.2 员工首页
第一屏显示需要员工完成的动作,例如:
- 确认本月考勤。
- 阅读新制度。
- 补充入职材料。
- 确认合同。
- 查看工资条。
不要首先展示功能说明或宣传内容。每项任务展示截止时间、完成要求和一个明确按钮。
### 11.3 自助数据维护
在权限允许的前提下支持员工更新:
- 手机号、地址和紧急联系人。
- 银行卡。
- 专项附加扣除。
- 个人证件和附件。
敏感变更进入 HR 审核,并向员工展示“已提交、审核中、已生效、被退回”的完整状态。
---
## 12. 视觉设计系统
### 12.1 品牌气质
关键词:可信、温暖、清晰、专业、有人情味、不过度严肃。
避免:
- 通用紫色 SaaS 模板感。
- 大面积单一米色或深蓝色主题。
- 过多装饰卡片、渐变球和营销式构图。
- 超大标题占据工作空间。
- 用插画替代真实业务信息。
### 12.2 建议色彩
以下为方向性 Token,最终需通过品牌设计和 WCAG 对比度测试:
| Token | 建议色值 | 用途 |
|---|---|---|
| `brand-600` | `#C7442E` | 主按钮、当前导航、品牌强调 |
| `brand-700` | `#A93625` | Hover、Active |
| `ink-900` | `#202622` | 主要文字 |
| `ink-600` | `#59615C` | 次要文字 |
| `surface-page` | `#F7F8F6` | 页面背景 |
| `surface-card` | `#FFFFFF` | 内容背景 |
| `border` | `#DDE1DD` | 边框和分隔线 |
| `success-600` | `#237A57` | 成功、安全、完成 |
| `warning-600` | `#A76113` | 警告、临期 |
| `danger-600` | `#B83232` | 高风险、危险操作 |
| `info-600` | `#356A8A` | 信息、进行中 |
颜色不能单独表达状态。状态组件必须同时包含文字,必要时增加图标。
### 12.3 字体
建议字体栈:
```css
font-family: Inter, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
font-variant-numeric: tabular-nums;
```
字体层级:
| 层级 | 字号 | 字重 | 用途 |
|---|---:|---:|---|
| Page title | 24px | 600 | 页面主标题 |
| Section title | 1618px | 600 | 页面分区 |
| Card title | 1416px | 600 | 小型内容块 |
| Body | 14px | 400 | 默认正文和表格 |
| Metadata | 1213px | 400 | 时间、辅助信息 |
| KPI | 2432px | 600 | 关键数字 |
不使用随视口宽度缩放字号,不使用负字距。
### 12.4 间距和尺寸
- 使用 4px 基础单位。
- 页面水平内边距:移动端 16px,桌面端 24–32px。
- 主内容最大宽度:1440px;数据密集表格可使用全可用宽度。
- 常规区块垂直间距:24px。
- 表单字段间距:16px。
- 按钮高度:小型 32px,默认 40px,大型 48px。
- 输入框默认高度:40px。
- 图标按钮保持稳定的 36×36px 或 40×40px。
- 卡片圆角不超过 8px。
### 12.5 页面标准结构
每个管理端页面统一为:
1. `PageHeader`:标题、说明、状态和主要动作。
2. `Tabs/SegmentedControl`:只在确有同级视图时使用。
3. `FilterBar`:搜索、筛选、视图和导出。
4. `Content`:列表、表格、流程或详情。
5. `BulkActionBar`:仅选择数据后出现。
6. `Pagination/Footer`:分页、总数和显示密度。
### 12.6 组件补齐
建议建立或完善:
- AppShell。
- PageHeader。
- GlobalSearchCommandPalette。
- FilterBar 和 FilterChip。
- DataTable。
- Stepper。
- TaskItem。
- Timeline。
- StatBlock。
- InlineAlert。
- Drawer。
- SplitPane。
- StickyActionBar。
- SensitiveValue。
- PermissionGate。
- FormSection。
- SaveStatus。
- UndoToast。
---
## 13. 表单、反馈和可访问性
### 13.1 表单
- 标签始终可见,placeholder 不代替标签。
- 必填项统一表达,不在每个标签后堆叠红色星号。
- 校验错误出现在字段附近,并说明如何修复。
- 长表单按语义分区,支持保存草稿和离开保护。
- 对身份证、手机号、银行卡、金额和日期提供格式化输入。
- 自动保存时显示“保存中/已保存/保存失败”。
### 13.2 操作反馈
- 成功:Toast,可提供“撤销”。
- 普通失败:Toast + 字段或区域内错误。
- 页面加载失败:区域级 Error State 和重试按钮。
- 危险操作:确认对话框,说明对象、数量、后果及可恢复性。
- 长耗时任务:显示进度和后台处理状态,不能只让按钮持续转圈。
### 13.3 可访问性
- 正文和交互文字达到 WCAG AA 对比度。
- 所有功能支持键盘访问和可见焦点。
- 图标按钮具有明确的 `aria-label` 和 tooltip。
- 表格表头、排序状态和选择状态可被辅助技术理解。
- Modal 打开时锁定焦点,关闭后返回触发元素。
- 动画遵循 `prefers-reduced-motion`
- 移动端触控目标至少 44×44px。
---
## 14. 国际化与本地化
### 14.1 工程要求
- 界面文案全部进入 i18n 资源文件。
- 日期、数字、百分比和货币统一通过 `Intl` 格式化。
- 内部存储使用 ISO 日期和明确时区。
- 不在业务组件内拼接中文句子。
- 布局支持英文文案比中文长 30%–80%。
- 表格列和按钮不依赖固定中文宽度。
### 14.2 语言策略
- 第一阶段:简体中文和英文框架。
- 第二阶段:繁体中文。
- 第三阶段:根据业务扩展国家/地区包。
劳动合同、解聘、社保公积金等功能需要区分“界面语言”和“适用法律地区”。英文界面不意味着将中国法概念简单替换成美国 HR 术语。
### 14.3 品牌与文案
- 品牌名应简短、可记忆、便于形成中英文组合。
- “企业用工专家”可保留为价值说明,而非唯一品牌名。
- 文案使用动作导向表达,例如“复核 7 月工资”,避免“薪税管理功能”。
- 按钮描述结果,例如“发布工资条”,避免笼统的“确定”。
- 空状态提供下一步,不使用无帮助的“暂无数据”。
---
## 15. 后端能力与前端映射
| 现有能力 | 推荐前端体验 | 优先级 |
|---|---|---|
| Dashboard todos、风险和合规评分 | 首页任务排序、统一风险中心 | P0 |
| Calendar 和提醒 | 首页截止日、员工时间线、月度日历 | P0 |
| Payroll batch pre-check | 发薪异常质量门禁 | P0 |
| 归档、撤销归档、定时发布 | 发薪 Stepper 和发布中心 | P0 |
| Onboarding link 和资料上传 | 入职进度面板、员工自助资料收集 | P1 |
| Contract confirm | 入职和合同确认闭环 | P1 |
| Termination draft、审批和执行 | 离职流程及审批收件箱 | P0 |
| WorkProcess | 跨模块统一审批和办理中心 | P1 |
| Policy read records | 制度已读率、未读提醒和证据 | P1 |
| Attendance publishconfirm | 月度确认流程和员工响应追踪 | P1 |
| Evidence chain | 员工详情时间线、风险处理证据包 | P1 |
| Audit log | 敏感字段查看和关键操作追踪 | P1 |
| RAGAI conversation | 上下文 AI、结构化风险建议 | P2 |
| Portal APIs | Employee Hub | P1 |
| Notification settings/logs | 统一通知中心和偏好设置 | P2 |
| Platform org/user APIs | 多组织管理工作台 | P2 |
### 15.1 建议补充的后端聚合能力
尽管现有接口已覆盖大部分功能,前端重构时建议增加少量聚合接口,避免首页和流程页并发请求过多:
- `GET /workspace/next-actions`:当前用户下一步任务。
- `GET /workspace/deadlines`:统一截止日和提醒。
- `GET /employees/:id/summary`:员工 Profile 首屏摘要。
- `GET /payroll2/batches/:id/workflow`:发薪步骤、异常和完成状态。
- `GET /approvals/inbox`:跨模块审批收件箱。
- `GET /search`:全局搜索。
聚合接口只负责编排已有业务数据,不重复实现核心计算逻辑。
---
## 16. 响应式策略
### 16.1 桌面端
- 适用于完整发薪表、社保办理、配置和批量管理。
- 1280px 以上使用侧边栏与主内容布局。
- 超宽屏限制主要内容宽度,数据表格场景可例外铺满。
### 16.2 平板
- 侧边栏默认折叠。
- 表格优先保留关键列,其他字段进入详情。
- 复杂流程保持步骤和摘要,但摘要可折叠。
### 16.3 手机端
管理员端只优先支持:
- 查看首页待办。
- 审批。
- 查看员工摘要。
- 处理通知。
- 查看发薪状态和异常摘要。
完整工资表编辑、复杂配置和批量导入应引导用户使用桌面端。员工门户则必须完整支持手机端。
---
## 17. 技术与组件重构建议
### 17.1 页面拆分
按领域拆分大型页面:
```text
pages/payroll/
PayrollHome.tsx
PayrollWorkflow.tsx
PayrollReviewTable.tsx
PayrollExceptions.tsx
PayrollPublish.tsx
pages/termination/
TerminationList.tsx
TerminationWorkflow.tsx
steps/
components/
pages/social/
MonthlyProcess.tsx
SocialConfig.tsx
HousingFundConfig.tsx
VersionHistory.tsx
```
拆分目标不是追求小文件,而是建立清晰的领域边界、复用状态组件和降低单页面变更风险。
### 17.2 状态与数据
- React Query 的 key 统一工厂化,明确失效规则。
- 复杂流程使用 reducer 或状态机表达,避免大量彼此关联的 `useState`
- 筛选、Tab 和分页写入 URL,支持刷新、分享和返回。
- 对乐观更新提供撤销机制。
- 大型表格考虑列配置持久化。
### 17.3 设计 Token
将颜色、圆角、阴影、字号、层级、宽度和动效统一定义为 Token,不继续在页面内直接使用 `bg-[#...]` 等任意值。Tailwind 配置与 CSS 变量应共用同一语义命名。
---
## 18. 分阶段实施计划
### Phase 0:基线和验证,35 天
- 建立核心任务的可用性基线。
- 记录发薪、入职和离职的点击数及完成时间。
- 整理角色和权限矩阵。
- 确认中英文品牌策略。
- 建立桌面、平板和手机测试视口。
交付物:体验基线、页面清单、角色矩阵、设计原则确认稿。
### Phase 1:全局框架,23 周
- 新导航和 AppShell。
- PageHeader、FilterBar、DataTable 等基础组件。
- 新色彩、字体、间距和状态体系。
- 首页任务中心。
- 全局宽度和响应式修复。
- WCAG AA 基础整改。
验收:一级导航不超过五项;首页第一屏存在唯一主任务;所有页面使用统一标题结构;1920px 下内容不无边界拉伸。
### Phase 2:核心业务流程,35 周
- 发薪工作流。
- 离职工作流。
- 月度社保办理。
- 月度考勤确认。
- 员工 Profile Shell。
验收:核心任务无需用户跨模块寻找下一步;所有流程能显示当前步骤、阻断项和负责人;危险操作明确说明影响。
### Phase 3:员工自助与协作,3–4 周
- Employee Hub。
- 入职进度中心。
- 审批收件箱。
- 制度阅读追踪。
- 通知中心。
验收:员工可在手机端完成主要自助任务;HR 能统一查看员工响应状态;审批不再分散在业务页面中。
### Phase 4:智能化和国际化,3–4 周
- 上下文 AI。
- 全局搜索和命令面板。
- 中英文资源化。
- Intl 日期、数字和货币格式化。
- 保存视图和个性化工作台。
验收:AI 自动识别当前业务上下文;核心页面完成中英文切换;英文文本不出现按钮溢出和布局破坏。
---
## 19. 成功指标
### 19.1 效率指标
- 首次创建员工到入职完成的时间。
- 创建发薪批次到发布工资条的时间。
- 每次发薪的点击数和页面跳转数。
- 异常发现到修复的平均时间。
- 离职流程平均完成时长。
- 批量操作使用率。
### 19.2 质量指标
- 发薪归档后撤销率。
- 工资条发布错误率。
- 入职资料一次通过率。
- 逾期合同和未处理高风险数量。
- 重复导入和数据冲突数量。
- 高风险操作误触和取消率。
### 19.3 体验指标
- 首页待办完成率。
- 员工自助完成率。
- 员工制度阅读确认率。
- 移动端任务完成率。
- 客服/内部咨询数量。
- SUS 或内部满意度评分。
建议目标:
- 发薪操作时间下降 30%。
- 核心流程页面跳转下降 40%。
- 入职资料一次通过率提升至 85% 以上。
- 员工自助完成率达到 80% 以上。
- 高风险事项逾期数量下降 50%。
---
## 20. 页面验收清单
每个页面发布前至少验证:
- 用户能否在 5 秒内理解页面目的。
- 是否存在唯一且明确的主要动作。
- 是否说明当前状态和下一步。
- 是否避免同时展示过多低频操作。
- 加载、空、错误、无权限和部分数据状态是否完整。
- 危险操作是否说明对象、数量、后果和可恢复性。
- 文字和状态对比度是否达到 WCAG AA。
- 仅使用键盘是否能完成主要操作。
- 360px、768px、1280px、1440px、1920px 是否无溢出和遮挡。
- 中英文最长文案是否不破坏布局。
- 敏感信息是否按权限遮罩并留下审计记录。
- 页面返回后筛选、分页和滚动位置是否合理保留。
---
## 21. 建议优先制作的高保真原型
为了尽快验证方向,第一轮只需设计以下八个关键页面:
1. 新首页任务中心。
2. 新导航和全局搜索。
3. 员工目录。
4. 员工概览详情。
5. 发薪流程总览。
6. 发薪异常和工资复核表。
7. 离职流程。
8. 员工门户首页。
上述页面覆盖导航、人物、任务、数据表、复杂流程、风险和移动端,可用于验证整个设计系统,无需一开始重画所有页面。
---
## 22. 参考资料
- Gusto 产品能力总览:<https://gusto.com/product>
- Gusto Employee Self-Serve<https://gusto.com/product/payroll/employee-self-serve>
- Gusto 2025 Feature Showcase<https://gusto.com/product/showcase/2025>
- Gusto 报表与自定义报告:<https://support.gusto.com/article/101334493100000/view-download-and-customize-reports-in-gusto-for-admins>
- Gusto Money Dashboard<https://gusto.com/product/money/dashboard>
这些参考资料体现的共同方向是:减少专业门槛、围绕下一步组织界面、让异常在提交前出现、强化员工自助,并使跨模块数据自然衔接。TurboHR 应保留自身在中国劳动用工合规、证据链和离职处理方面的差异化能力,在此基础上建立国际化产品体验。
@@ -0,0 +1,904 @@
# TurboHR 完整优化与功能对照方案
> 版本:V2.0(整合版)
> 日期:2026-07-31
> 整合来源:UI/UX 完整优化方案 + 系统操作问题与功能对照分析
> 参考方向:Gusto 的任务驱动体验、温暖可信的视觉语言、员工自助和低门槛业务流程
---
## 目录
- [Part 1:项目概述与诊断](#part-1)
- [Part 2:目标产品框架](#part-2)
- [Part 3:模块设计与功能对照](#part-3)
- [Part 4:视觉设计系统](#part-4)
- [Part 5:工程与技术](#part-5)
- [Part 6:实施计划与指标](#part-6)
---
<a id="part-1"></a>
# Part 1:项目概述与诊断
## 1. 项目目标
TurboHR 已具备员工、合同、考勤、薪酬、社保公积金、离职、制度、证据链、审批、通知、员工门户及 AI 顾问等较完整的后端能力。当前优化重点不应继续堆叠入口,而应将既有能力组织成更容易理解和完成的工作流,同时补齐功能短板。
本轮优化目标:
1. 将产品从"模块型管理后台"升级为"任务型 HR 工作台"。
2. 让非专业 HR 用户也能在清晰引导下完成发薪、入职、离职和月度社保等复杂业务。
3. 建立可信、温暖但不过度装饰的品牌与视觉体系。
4. 减少页面跳转、重复录入、操作犹豫和错误返工。
5. 提高员工自助率,减少 HR 重复答疑和资料收集工作。
6. 补齐用户反馈的功能短板:合同类型覆盖、导入导出对齐、社保灵活性、绩效分析看板、个税申报报表等。
### 1.1 核心设计原则
- **任务优先**:首先告诉用户现在要做什么,而不是先展示系统有哪些模块。
- **渐进披露**:日常信息优先,高级配置在需要时出现。
- **一步一决策**:复杂流程每一步只解决一个主要问题。
- **状态透明**:所有流程都能看见当前状态、负责人、截止时间和下一步。
- **异常前置**:尽量在提交、归档、发布之前发现问题。
- **人物优先**:HR 系统中的主体是员工,不是数据表和字段。
- **一致可预测**:同一类动作、状态和反馈在所有模块中保持一致。
- **国际框架、本地语义**:交互采用国际通行方式,劳动用工文案保持中国法律语义准确。
## 2. 当前产品诊断
### 2.1 信息架构问题
当前侧边栏包含工作台、员工管理、薪税社保、合规风控、AI 辅助和系统等多个分组,且默认展开。日常高频任务、高级配置、辅助工具和系统管理处于同一层级。
主要影响:新用户无法迅速判断从哪开始;低频工具占用导航空间;连续工作被拆散;缺乏统一的"我的工作"视角。
### 2.2 页面结构问题
- Dashboard 同时承担概览、薪税、风险、待办、成本和人力分析,第一屏焦点不清。
- Money、Termination、SocialInsurance 等页面体量过大,交互不一致。
- 主要动作与次要动作区分不足。
### 2.3 视觉问题
- 紫色主色有通用后台模板感;`text-xs` 密度高阅读压力大;缺少明确视觉层级。
### 2.4 交互问题
- 用户需跨页面记忆上下文;批量操作入口常驻;AI 顾问与业务上下文分离。
### 2.5 功能差距总览
| 模块 | 用户反馈问题 | 系统实际状态 | 严重程度 | 修复难度 |
|------|------------|------------|---------|---------|
| 花名册 | 缺少劳务/承揽/临时工/非全合同类型 | 枚举缺 3 种,前端缺 2 种展示 | 🔴 高 | 中 |
| 花名册 | 导出字段不全、导入导出错位 | 导出 7 列 vs 导入 19 列 | 🔴 高 | 低 |
| 工资 | 缺少计件/计时计薪方式 | 无相关字段和批次类型 | 🟡 中 | 高 |
| 社保 | 无社保人员无法标记 | 入职自动创建社保记录 | 🟡 中 | 中 |
| 社保 | 商险/意外险无独立模块 | 后端有 extraInsurances 字段 | 🟡 中 | 中 |
| 社保 | 无公积金员工自动匹配 | 无条件创建公积金记录 | 🟡 中 | 低 |
| 考勤 | 无打卡功能 | 系统无打卡能力 | 🟡 中 | 高 |
| 考勤 | 第三方数据需手工导入 | 有 Excel 导入但无 API 对接 | 🟢 低 | 中 |
| 发薪 | 个税无法自动算税 | **已实现**,用户可能未发现 | 🟢 低 | — |
| 发票 | 无发票模块 | 完全未实现 | 🟡 中 | 高 |
| 审批 | 无审批模块 | 仅解聘有状态流转 | 🟡 中 | 高 |
| 绩效 | 无绩效分析看板 | 有绩效记录但无统计分析 | 🟡 中 | 低 |
| 个税 | 无个税申报报表导出 | 有计算但无申报表 | 🟡 中 | 低 |
### 2.6 功能成熟度矩阵
| 功能领域 | 计算能力 | 线上化程度 | 分析看板 | 申报对接 | 综合评分 |
|---------|---------|-----------|---------|---------|---------|
| 薪酬核算 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | — | 4/5 |
| 个税 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | — | ⭐ | 3/5 |
| 入职 | ⭐⭐⭐ | ⭐⭐⭐⭐ | — | — | 3.5/5 |
| 合同签署 | ⭐⭐⭐ | ⭐⭐⭐⭐ | — | — | 3.5/5 |
| 离职 | ⭐⭐⭐⭐ | ⭐⭐ | — | — | 3/5 |
| 绩效 | ⭐⭐ | ⭐⭐ | ⭐ | — | 1.5/5 |
| 社保 | ⭐⭐⭐⭐ | ⭐⭐⭐ | — | ⭐ | 3/5 |
---
<a id="part-2"></a>
# Part 2:目标产品框架
## 3. 目标一级导航
| 一级入口 | 主要内容 | 核心用户任务 |
|---|---|---|
| 首页 | 今日任务、审批、风险、截止日、快捷创建、近期动态 | 判断今天先做什么 |
| 团队 | 员工目录、入职、异动、合同、特殊状态、离职 | 管理员工全生命周期 |
| 薪酬 | 发薪、工资条、加班、社保公积金、专项扣除 | 完成每月薪酬工作 |
| 时间 | 考勤、排班、休假、月度确认 | 管理工时和出勤 |
| 合规 | 风险、制度、证据链、用工体检、审计 | 发现并处理用工风险 |
全局右上区域保留:全局搜索/命令面板、快速创建按钮、待审批入口、通知入口、帮助与 AI 助手、组织及个人菜单。
以下内容移入"设置"或"更多":文本模板、数据导入导出、用户与权限、企业信息、通知规则、社保公积金基础配置、审计配置。
## 3.1 导航行为
- 默认只展开当前一级导航对应的二级菜单。
- 记住用户最近访问的页面和分组状态。
- 桌面端侧边栏 224–240px,可折叠为 64px 图标栏。
- 移动端使用"首页、团队、待办、我的"四入口底栏。
## 3.2 全局搜索和命令面板
快捷键 `Cmd/Ctrl + K`,搜索范围:员工、发薪批次、合同、页面与功能、可执行命令。结果按"人员、任务、记录、功能"分组,敏感信息不出现在未授权摘要中。
---
<a id="part-3"></a>
# Part 3:模块设计与功能对照
## 4. 首页:从数据看板升级为任务中心
### 4.1 页面目标
首页第一屏必须回答:今天最需要处理什么?最近一个重要截止日是什么?哪些流程或员工存在异常?
### 4.2 推荐布局
**顶部区域**:问候语 + 日期 + `+ 快速创建` 主按钮。
**主任务卡**:只展示当前最重要的一项任务,主操作 + 次操作。选择规则:已逾期 → 高风险 → 需审批 → 三日内到期 → 月度流程下一步。
**待办队列**:分段控制器切换"需要我处理 / 等待他人 / 即将到期 / 已完成"。每条包含事项、关联对象、截止时间、风险级别、负责人、直接操作。
**人员动态**:待入职、本周入职、合同即将到期、试用期即将结束、特殊状态、离职交接中。
**数据洞察(第二屏)**:本月人力成本、年度成本趋势、部门人数变化、风险分布、员工完成率。**新增**:入离职趋势图(近 12 个月离职率)、绩效概览(等级分布、部门均分)。
### 4.3 空状态
> 今天没有待处理事项。下一次发薪准备将在 8 月 1 日开始。
提供 `查看日历``创建任务` 入口。
---
## 5. 团队与员工管理
### 5.1 功能对照
#### 合同类型覆盖问题
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 只有标准劳动合同界面,劳务/承揽/临时工/非全日制无法录入 | `ContractType` 枚举 5 种:FIXED、UNFIXED、UNSIGNED、LABOR、INTERNSHIP | 后端已支持 LABOR/INTERNSHIP 但前端未暴露;**承揽合同、临时工、非全日制** 3 种完全缺失 |
#### 花名册导入导出错位
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 导出只能导出部分基本信息 | `export.routes.ts` 仅 7 列 | 导入 19 字段 vs 导出 7 字段,严重不对齐 |
#### 现有功能
| 功能项 | 支持情况 | 实现位置 |
|-------|---------|---------|
| 单个员工录入 | ✅ | `Roster.tsx``AddEmployeeModal` |
| 批量导入 | ✅ | `import.routes.ts``/import/excel` |
| 花名册导出 | ⚠️ 仅 7 列 | `export.routes.ts``/export/roster` |
| 全量数据导出 | ✅ | `export.routes.ts``/export/all` |
| 员工详情 | ✅ 10 个 Tab | `roster/EmployeeProfile.tsx` |
| 批量续签/解聘 | ✅ | `Roster.tsx` |
| 调薪/部门变更 | ✅ | `roster/modals.tsx` |
### 5.2 员工目录优化
默认列表列:员工(头像/姓名/职位)、状态、入职日期、合同、薪酬状态、下一项任务、操作。
优化要求:点击整行进入详情;搜索与筛选同一工具栏;筛选条件为可移除标签;支持保存视图;选中后才出现批量操作栏;危险操作显示影响范围。
### 5.3 员工 Profile Shell
头部固定:头像、姓名、职位、部门、在职状态、联系方式、入职日期、`编辑资料` 主按钮。
推荐 Tab:概览、工作信息、薪酬福利、考勤休假、文件合同、绩效与事件、历史记录。
隐私要求:身份证/银行卡/工资默认遮罩;查看敏感信息需权限并写审计日志。
### 5.4 入职流程
统一五阶段:`发送邀请 → 员工填写 → HR 审核 → 合同确认 → 完成配置`
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 入职线上化 | ✅ | `OnboardingLink` + `portal.routes.ts`,HR 生成链接 → 员工填写 → 上传附件 → HR 审核 |
| 入职附件上传 | ✅ | `/onboarding/:token/upload` |
| 入职信息 PDF 导出 | ❌ | 建议补充 |
| 入职材料清单勾选 | ❌ | 建议补充 |
每位待入职员工显示:总完成度、当前阶段、缺失资料、最后操作时间、预计入职日期、可执行动作。
### 5.5 合同签署线上化
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 合同签署线上化 | ✅ | `ContractConfirmLink`,HR 发送链接 → 员工验证码验证 → 确认签署 |
| 签署证据链 | ✅ | 自动记录 IP/时间/验证码 |
| 第三方电子签 | ⚠️ | 有 ELECTRONIC 字段但未对接 e签宝/法大大 |
现有流程:`HR 创建合同 → 生成确认链接 → 员工验证码验证 → 确认签署 → 自动生成证据链`
### 5.6 离职流程
统一六步:选择员工 → 确认原因日期 → 合规检查 → 费用结算 → 工作交接 → 复核审批执行。
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 离职线上化 | ⚠️ 部分 | HR 端可发起,员工端无离职申请入口 |
| 离职确认 | ❌ | 员工无法在线确认 |
| 离职交接清单 | ❌ | 建议增加 |
| 解聘审批流 | ⚠️ | 有 7 种状态字段但前端无审批界面 |
桌面端使用"主内容 + 右侧摘要"布局,底部固定操作栏。
---
## 6. 薪酬与发薪体验
### 6.1 功能对照
#### 计薪方式覆盖
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 只有标准工时工资,计件/计时无法录入 | `BatchEntry` 有基本工资/加班费/津贴/奖金等 | 缺少计件工资字段(件数/单价/时薪/工时)和对应批次类型 |
#### 个税自动算税
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 无法实现个税自动算税 | **已实现** `calcCumulativeTax()` 累计预扣法 + `calcBonusTax()` 年终奖单独计税 | 用户反馈与系统现状不符,可能未发现该功能 |
#### 现有功能清单
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 薪酬核算 | ✅ | `calcBatchEntry()` 自动计算全流程 |
| 社保公积金自动计算 | ✅ | 按城市配置比例 |
| 个税自动计算 | ✅ | 累计预扣法,7 级超额累进 |
| 年终奖单独计税 | ✅ | `calcBonusTax()` |
| 多批次发薪 | ✅ | 4 种批次类型 |
| 薪酬模板 | ✅ | `PayslipItem` 自定义薪资项 |
| 工资条在线发放 | ✅ | 支持定时发送、员工端确认 |
| Excel 导入/导出 | ✅ | `/import/payroll` + `/export/payroll` |
| 个税预览 | ✅ | `/payroll/tax-preview` |
| 个税跳档预警 | ✅ | 归档时检测 |
| 薪资项手工覆盖 | ✅ | 社保/公积金可 override |
| 计件/计时工资 | ❌ | 无相关字段 |
| 薪酬分析看板 | ❌ | 缺少薪酬分布/趋势/部门对比 |
### 6.2 月度发薪中心
连续流程:`准备数据 → 异常检查 → 复核工资 → 归档 → 发布工资条 → 完成`
页面顶部显示:月份、批次名称、流程步骤、当前负责人、计划发布时间、员工数量、预计总额、自动保存状态。
### 6.3 准备数据
集中呈现数据源完成情况:员工薪酬结构、考勤、加班、请假、社保公积金、专项附加扣除、奖金/补偿。每个数据源显示"已同步/存在异常/尚未导入/无需处理"。
### 6.4 异常检查
异常分三级:阻断(未配置薪资/计算失败)、警告(变化显著/实发为负)、提示(新员工/离职结算)。每条异常提供员工、字段、当前值/参考值、影响、建议操作、`立即修复` 入口。
### 6.5 工资复核表
固定姓名列和实发列;工资项目分组展开;修改单元格标识并支持撤销;显示与上月变化;支持筛选"仅看异常/已修改/新增";表尾固定合计;大数据量虚拟滚动。
### 6.6 归档与发布
归档前弹出复核摘要(人数/总额/未解决警告/后续影响/可撤销性)。归档后主操作变为 `发布工资条`,支持立即/定时发布,展示员工查看确认进度。
---
## 7. 个税申报线上化
### 7.1 现状
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 个税计算 | ✅ | 累计预扣法,7 级超额累进 |
| 个税预览 | ✅ | `/payroll/tax-preview` |
| 个税累计数据 | ✅ | `Payslip.ytdTaxDeducted` |
| 个税申报提醒 | ✅ | 每月申报日提醒 |
| 申报日配置 | ✅ | `NotificationSetting.taxDay` |
| 专项附加扣除导入 | ✅ | `/import/special-deduction` |
| 个税申报报表 | ❌ | 无导出功能 |
| 个税批量申报 | ❌ | 未对接税务系统 |
### 7.2 实施路径
```
阶段一(低成本):
✅ 个税自动计算 → 已实现
✅ 申报日提醒 → 已实现
📋 申报表 Excel 导出 → 新增 /export/tax-declaration 接口
阶段二(中等成本):
📋 申报数据校验 → 导出前校验完整性
📋 年度汇算提醒 → 每年 3 月推送
阶段三(高成本):
📋 对接自然人电子税务局 API → 在线批量申报
```
---
## 8. 时间、考勤和排班
### 8.1 功能对照
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 无法实现打卡 | 无打卡接口 | 系统定位为 HR 管理后台非考勤终端 |
| 第三方数据需手工导入 | 有 Excel 导入 | 缺少钉钉/企微/飞书 API 对接 |
现有功能:考勤确认、班次管理、排班、每日出勤、月度报表、休假记录、Excel 批量导入、员工端考勤查看 — 全部 ✅。
### 8.2 模块优化
二级导航建议:月度概览、每日出勤、排班、休假、规则设置。高级班次设置放入次级入口。
月度确认流程:`导入/同步 → 识别异常 → 员工确认 → HR 锁定 → 同步发薪`
排班:周视图默认;色块+缩写表达班次;支持复制上周/批量排班/冲突检测。
第三方对接建议:钉钉/企微/飞书 API(🟡 中预算)。
---
## 9. 社保公积金体验
### 9.1 功能对照
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 全部正规五险一金,无社保人员模块 | 固定 5 险 | 缺少「无社保」选项,入职自动创建 |
| 纯商险/意外险无法录入 | `extraInsurances` JSON 字段 | 后端支持但前端无独立商险模块 |
| 无公积金员工自动匹配 | 无条件创建公积金记录 | 缺少「是否缴纳公积金」开关 |
| 基数自动为应发工资 | 未填写时默认使用月工资 | 无法设为 0 或不缴 |
现有功能:社保配置(多城市多版本)、公积金配置、附加险种配置、基数调整、月度办理、专项附加扣除导入、版本管理、社保导出 — 全部 ✅。
### 9.2 体验优化
日常办理与配置分离:日常用户默认进入"月度办理",配置放入"规则与配置"。
月度办理流程:`确认增减员 → 检查基数变化 → 生成申报名单 → 导出/办理 → 标记完成`
配置版本:时间线展示历史版本;新建版本时显示差异;调基前展示影响人数和成本变化。
### 9.3 灵活性改进
| 需求 | 建议方案 | 预算 |
|------|---------|------|
| 「无社保」选项 | 入职增加「是否参保」开关 | 🟢 低 |
| 「不缴纳公积金」选项 | 入职增加开关 | 🟢 低 |
| 商险独立管理 | 社保页面增加「商险管理」Tab | 🟡 中 |
---
## 10. 合规、制度与证据链
### 10.1 统一风险中心
将风险、用工体检、合同提醒、特殊状态汇总为统一风险中心。风险列表字段:事项、关联对象、等级、截止时间、负责人、建议动作、证据完整度、处理状态。详情使用三栏结构:问题说明、法律依据、处理动作与证据。
### 10.2 规章制度
发布流程:`起草 → 审核 → 公示 → 发布 → 员工阅读确认 → 归档`。列表优先展示状态、覆盖人数、已读率、未读人员。
### 10.3 证据链
时间线表达,按事件展示时间、类型、操作者、文件、完整性。支持按离职/违纪/考勤/合同等场景生成证据包。
---
## 11. 绩效分析与统计看板
### 11.1 现状
| 功能项 | 支持情况 | 说明 |
|-------|---------|------|
| 绩效记录录入 | ✅ | `roster/PerformanceInfo.tsx`,考核周期/得分/等级/评语/改进计划 |
| 绩效员工确认 | ✅ | `PerformanceRecord.employeeAck` |
| 绩效看板 | ❌ | 无独立统计看板 |
| 绩效趋势分析 | ❌ | 无趋势/部门对比/等级分布 |
| 绩效与薪酬联动 | ❌ | 未与奖金分配关联 |
### 11.2 建议方案
| 需求 | 建议方案 | 预算 |
|------|---------|------|
| 绩效统计看板 | 新增 `/tools/performance-dashboard`,等级分布饼图/部门对比柱状图/趋势折线图/Top-Bottom 排名 | 🟢 低 |
| 绩效与薪酬联动 | 发薪批次增加「绩效奖金」列,关联绩效等级计算系数 | 🟡 中 |
| 绩效改进追踪 | 增加改进目标/完成度/复评得分字段 | 🟢 低 |
### 11.3 可复用基础设施
Dashboard 看板组件、`/dashboard/workforce-stats` 聚合 API 模式、年度价值报告、recharts 图表库。
---
## 12. AI 顾问优化
从独立聊天升级为上下文助手:业务页面提供"询问 AI"入口,自动附带当前员工/批次/风险上下文。发送前显示带入的上下文并允许取消。
AI 输出结构化:结论、风险级别、依据、建议步骤、可生成材料、需人工确认部分。AI 建议不能直接执行高风险操作。
---
## 13. 员工门户
移动端首页优先展示:我的待办、最新工资条、考勤休假、合同文件、制度阅读确认、个人资料。底部导航:`首页|时间|文件|我的`
第一屏显示需要员工完成的动作,每项展示截止时间、完成要求、明确按钮。
自助数据维护:手机号/地址/紧急联系人/银行卡/专项附加扣除/证件附件。敏感变更进入 HR 审核,展示完整状态。
---
## 14. 发票与审批模块
| 用户反馈 | 系统现状 | 差距分析 |
|---------|---------|---------|
| 无发票模块 | 完全未实现 | 需从零开发 |
| 无审批模块 | 仅解聘有 7 种状态字段,无通用审批引擎 | 需从零开发通用审批流 |
现有相关:解聘审批流(⚠️ 有状态无界面)、操作日志审计(✅)、合同确认链接(✅)。
建议:发票模块从零设计(🔴 高);通用审批引擎支持请假/调薪/合同等多场景(🔴 高);解聘审批界面前端补充(🟢 低)。
---
<a id="part-4"></a>
# Part 4:视觉设计系统
## 15. 品牌气质
关键词:可信、温暖、清晰、专业、有人情味。避免通用紫色 SaaS 模板感、大面积单一色调、过多装饰卡片。
## 16. 建议色彩
| Token | 建议色值 | 用途 |
|---|---|---|
| `brand-600` | `#C7442E` | 主按钮、当前导航、品牌强调 |
| `brand-700` | `#A93625` | Hover、Active |
| `ink-900` | `#202622` | 主要文字 |
| `ink-600` | `#59615C` | 次要文字 |
| `surface-page` | `#F7F8F6` | 页面背景 |
| `surface-card` | `#FFFFFF` | 内容背景 |
| `border` | `#DDE1DD` | 边框和分隔线 |
| `success-600` | `#237A57` | 成功、完成 |
| `warning-600` | `#A76113` | 警告、临期 |
| `danger-600` | `#B83232` | 高风险、危险操作 |
| `info-600` | `#356A8A` | 信息、进行中 |
颜色不能单独表达状态,必须同时包含文字。
## 17. 字体
```css
font-family: Inter, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
font-variant-numeric: tabular-nums;
```
| 层级 | 字号 | 字重 | 用途 |
|---|---:|---:|---|
| Page title | 24px | 600 | 页面主标题 |
| Section title | 1618px | 600 | 页面分区 |
| Card title | 1416px | 600 | 小型内容块 |
| Body | 14px | 400 | 默认正文和表格 |
| Metadata | 1213px | 400 | 时间、辅助信息 |
| KPI | 2432px | 600 | 关键数字 |
## 18. 间距和尺寸
4px 基础单位;页面水平内边距移动端 16px / 桌面端 24–32px;主内容最大宽度 1440px;常规区块垂直间距 24px;表单字段间距 16px;按钮高度 32/40/48px;卡片圆角不超过 8px。
## 19. 页面标准结构
1. `PageHeader`:标题、说明、状态、主要动作。
2. `Tabs/SegmentedControl`:只在确有同级视图时使用。
3. `FilterBar`:搜索、筛选、视图、导出。
4. `Content`:列表、表格、流程或详情。
5. `BulkActionBar`:仅选择数据后出现。
6. `Pagination/Footer`:分页、总数、显示密度。
## 20. 组件补齐
AppShell、PageHeader、GlobalSearch/CommandPalette、FilterBar/FilterChip、DataTable、Stepper、TaskItem、Timeline、StatBlock、InlineAlert、Drawer、SplitPane、StickyActionBar、SensitiveValue、PermissionGate、FormSection、SaveStatus、UndoToast。
## 21. 表单、反馈和可访问性
**表单**:标签始终可见;校验错误在字段附近说明修复方法;长表单按语义分区支持草稿;格式化输入(身份证/手机/银行卡/金额/日期);自动保存显示状态。
**操作反馈**:成功 Toast 可撤销;失败 Toast + 区域错误;页面加载失败 Error State + 重试;危险操作确认对话框说明后果;长耗时任务显示进度。
**可访问性**:WCAG AA 对比度;键盘访问和可见焦点;图标按钮 `aria-label`Modal 焦点锁定;`prefers-reduced-motion`;移动端触控目标 ≥ 44×44px。
---
<a id="part-5"></a>
# Part 5:工程与技术
## 22. 文案与表达规范
- 文案使用动作导向表达("复核 7 月工资"而非"薪税管理功能")。
- 按钮描述结果("发布工资条"而非"确定")。
- 空状态提供下一步,不使用无帮助的"暂无数据"。
- 日期/数字/百分比/货币通过 `Intl` 格式化。
- 内部存储使用 ISO 日期和明确时区。
## 23. 后端能力与前端映射
| 现有能力 | 推荐前端体验 | 优先级 |
|---|---|---|
| Dashboard todos、风险评分 | 首页任务排序、统一风险中心 | P0 |
| Calendar 和提醒 | 首页截止日、月度日历 | P0 |
| Payroll batch pre-check | 发薪异常质量门禁 | P0 |
| 归档/撤销/定时发布 | 发薪 Stepper 和发布中心 | P0 |
| Termination draft/审批 | 离职流程及审批收件箱 | P0 |
| 个税计算 + 申报提醒 | 个税申报表导出 | P0 |
| Onboarding link | 入职进度面板 | P1 |
| Contract confirm | 合同确认闭环 | P1 |
| WorkProcess | 跨模块统一审批中心 | P1 |
| Policy read records | 制度已读率、未读提醒 | P1 |
| Attendance publish/confirm | 月度确认流程 | P1 |
| Evidence chain | 员工时间线、证据包 | P1 |
| Audit log | 敏感字段查看追踪 | P1 |
| Portal APIs | Employee Hub | P1 |
| PerformanceRecord | 绩效统计看板 | P1 |
| RAG/AI conversation | 上下文 AI | P2 |
| Notification settings | 统一通知中心 | P2 |
| Platform org/user APIs | 多组织管理工作台 | P2 |
### 23.1 建议补充的聚合接口
- `GET /workspace/next-actions`:当前用户下一步任务。
- `GET /workspace/deadlines`:统一截止日。
- `GET /employees/:id/summary`:员工 Profile 首屏摘要。
- `GET /payroll2/batches/:id/workflow`:发薪步骤和异常。
- `GET /approvals/inbox`:跨模块审批收件箱。
- `GET /search`:全局搜索。
- `GET /performance/dashboard`:绩效统计聚合。
- `GET /dashboard/turnover`:入离职统计聚合。
- `GET /export/tax-declaration?month=YYYY-MM`:个税申报表导出。
## 24. 响应式策略
**桌面端**:1280px+ 侧边栏与主内容布局;超宽屏限制主要内容宽度。
**平板**:侧边栏默认折叠;表格保留关键列;复杂流程摘要可折叠。
**手机端**:管理员端仅支持查看待办/审批/员工摘要/通知/发薪状态;员工门户完整支持。
## 25. 技术重构建议
**页面拆分**:按领域拆分大型页面(payroll/termination/social 等子目录)。
**状态与数据**React Query key 工厂化;复杂流程用 reducer/状态机;筛选/Tab/分页写入 URL;乐观更新提供撤销。
**设计 Token**:颜色/圆角/阴影/字号/层级统一定义为 Token,不使用 `bg-[#...]` 任意值。
---
<a id="part-6"></a>
# Part 6:实施计划与指标
## 26. 综合优先级清单
### P0 — 立即修复(影响日常使用)
| 序号 | 功能项 | 预算 | 来源 |
|-----|-------|------|-----|
| 1 | 花名册导出字段扩充(对齐 19 字段) | 🟢 低 | 功能对照 |
| 2 | 社保「无社保」选项 | 🟢 低 | 功能对照 |
| 3 | 公积金「不缴纳」选项 | 🟢 低 | 功能对照 |
| 4 | 个税申报表 Excel 导出 | 🟢 低 | 功能对照 |
| 5 | 入离职统计看板 | 🟢 低 | 功能对照 |
| 6 | 绩效统计看板 | 🟢 低 | 功能对照 |
| 7 | 新导航和 AppShell | 🟡 中 | UI/UX |
| 8 | 首页任务中心 | 🟡 中 | UI/UX |
| 9 | 发薪工作流 Stepper | 🟡 中 | UI/UX |
| 10 | 离职流程及审批 | 🟡 中 | UI/UX |
### P1 — 近期优化
| 序号 | 功能项 | 预算 | 来源 |
|-----|-------|------|-----|
| 11 | 合同类型扩充(承揽/临时工/非全日制) | 🟡 中 | 功能对照 |
| 12 | 导入模板合同类型对齐 | 🟢 低 | 功能对照 |
| 13 | 商险/意外险独立管理 | 🟡 中 | 功能对照 |
| 14 | 员工离职申请入口 | 🟢 低 | 功能对照 |
| 15 | 离职交接清单 | 🟢 低 | 功能对照 |
| 16 | 薪酬分析看板 | 🟢 低 | 功能对照 |
| 17 | 员工 Profile Shell | 🟡 中 | UI/UX |
| 18 | 入职进度面板 | 🟡 中 | UI/UX |
| 19 | 月度社保办理流程 | 🟡 中 | UI/UX |
| 20 | 月度考勤确认流程 | 🟡 中 | UI/UX |
| 21 | 统一风险中心 | 🟡 中 | UI/UX |
| 22 | Employee Hub(员工门户) | 🟡 中 | UI/UX |
### P2 — 中期规划
| 序号 | 功能项 | 预算 | 来源 |
|-----|-------|------|-----|
| 23 | 计件/计时工资 | 🔴 高 | 功能对照 |
| 24 | 考勤第三方对接(钉钉/企微/飞书) | 🟡 中 | 功能对照 |
| 25 | 合同电子签对接(e签宝/法大大) | 🟡 中 | 功能对照 |
| 26 | 个税申报数据校验 | 🟢 低 | 功能对照 |
| 27 | 绩效与薪酬联动 | 🟡 中 | 功能对照 |
| 28 | 上下文 AI | 🟡 中 | UI/UX |
| 29 | 全局搜索和命令面板 | 🟡 中 | UI/UX |
### P3 — 远期规划
| 序号 | 功能项 | 预算 | 来源 |
|-----|-------|------|-----|
| 30 | 发票模块 | 🔴 高 | 功能对照 |
| 31 | 通用审批引擎 | 🔴 高 | 功能对照 |
| 32 | 对接税务系统在线申报 | 🔴 高 | 功能对照 |
## 27. 分阶段实施计划
### Phase 0:基线和验证(35 天)
- 建立核心任务可用性基线(发薪/入职/离职点击数及完成时间)。
- 整理角色和权限矩阵。
- 确认品牌策略。
- 建立桌面/平板/手机测试视口。
### Phase 1:全局框架 + P0 功能修复(2–3 周)
- 新导航和 AppShell。
- PageHeader、FilterBar、DataTable 基础组件。
- 新色彩/字体/间距/状态体系。
- 首页任务中心。
- 花名册导出扩充、社保/公积金开关、个税申报表导出。
- 入离职统计看板、绩效统计看板。
- WCAG AA 基础整改。
验收:一级导航不超过五项;首页第一屏存在唯一主任务;导出字段对齐;统计看板上线。
### Phase 2:核心业务流程(35 周)
- 发薪工作流(Stepper + 异常门禁 + 复核表 + 归档发布)。
- 离职工作流(六步 + 右侧摘要 + 审批)。
- 月度社保办理。
- 月度考勤确认。
- 员工 Profile Shell。
- 合同类型扩充、商险管理。
验收:核心任务无需跨模块寻找下一步;所有流程显示当前步骤/阻断项/负责人;危险操作说明影响。
### Phase 3:员工自助与协作(3–4 周)
- Employee Hub。
- 入职进度中心。
- 审批收件箱。
- 制度阅读追踪。
- 通知中心。
- 员工离职申请入口。
验收:员工可手机端完成主要自助任务;HR 统一查看员工响应状态;审批不再分散。
### Phase 4:智能化(34 周)
- 上下文 AI。
- 全局搜索和命令面板。
- 保存视图和个性化工作台。
验收:AI 自动识别业务上下文;全局搜索覆盖人员、任务、记录和功能。
## 27.5 详细 Sprint 实施安排
以下将上述 Phase 04 细化为 5 个 Sprint(每个 Sprint 2 周),明确每项工作的类型、工作量、涉及文件和执行策略。
### 现状判断
当前代码基础:
- **布局**`AdminLayout``SidebarNav` + `TopNav` + `PageContainer` + `MobileTabBar`)已存在但结构简单。
- **组件**:无 `AppShell``PageHeader``DataTable``FilterBar` 等抽象组件,各页面自行实现。
- **导航**:6 个分组全部默认展开,未按任务优先级组织。
- **色彩**:使用 Tailwind `primary`(紫色系),无设计 Token 系统。
- **页面**`Dashboard.tsx``Money.tsx``Roster.tsx` 等均为大文件单页面。
### Sprint 1:基础设施 + 快速修复(第 1–2 周)
**目标**:搭建新框架骨架,同时完成 6 项低成本功能修复,让用户立刻感知改善。
| 工作项 | 类型 | 工作量 | 涉及文件 |
|-------|------|-------|---------|
| 设计 Token 系统 | UI/UX | 1 天 | `tailwind.config.ts` + 新建 `src/styles/tokens.css` |
| 新导航 5 分组 | UI/UX | 1 天 | `SidebarNav.tsx` 改造 + `MobileTabBar.tsx` 调整 |
| `AppShell` + `PageHeader` 组件 | UI/UX | 2 天 | 新建 `src/components/layout/AppShell.tsx``PageHeader.tsx` |
| `FilterBar` + `DataTable` 组件 | UI/UX | 2 天 | 新建 `src/components/ui/FilterBar.tsx``DataTable.tsx` |
| 花名册导出字段扩充 | 功能修复 | 0.5 天 | `export.routes.ts` |
| 社保「无社保」选项 | 功能修复 | 1 天 | `schema.prisma` + `import.routes.ts` + `SocialInsurance.tsx` |
| 公积金「不缴纳」选项 | 功能修复 | 0.5 天 | `import.routes.ts` + `SocialInsurance.tsx` |
| 个税申报表 Excel 导出 | 功能修复 | 1 天 | 新增 `export.routes.ts` 路由 |
| 入离职统计看板 | 功能修复 | 1.5 天 | `Dashboard.tsx` + 新增后端聚合 API |
| 绩效统计看板 | 功能修复 | 1.5 天 | 新建 `PerformanceDashboard.tsx` + 后端 API |
**执行策略**:功能修复(后端为主)与 UI 基础设施(前端为主)可并行,互不阻塞。
**验收标准**
- 导航从 6 分组变为 5 分组(首页/团队/薪酬/时间/合规)。
- 花名册导出包含 19 字段。
- 社保/公积金可选择不缴纳。
- 个税申报表可导出 Excel。
- Dashboard 新增入离职趋势图和绩效分布图。
### Sprint 2:首页 + 薪酬流程(第 3–4 周)
**目标**:首页从数据看板升级为任务中心,发薪流程串联为 Stepper。
| 工作项 | 类型 | 工作量 | 涉及文件 |
|-------|------|-------|---------|
| 首页任务中心重构 | UI/UX | 3 天 | `Dashboard.tsx` 重写 + 新增 `/workspace/next-actions` API |
| 待办队列 + 人员动态 | UI/UX | 2 天 | 同上 |
| `Stepper` 组件 | UI/UX | 1 天 | 新建 `src/components/ui/Stepper.tsx` |
| 发薪工作流页面拆分 | UI/UX | 4 天 | `Money.tsx` 拆分为 `PayrollHome/Workflow/ReviewTable/Exceptions/Publish` |
| 发薪异常质量门禁 | UI/UX | 2 天 | 对接已有 `pre-check` API + `InlineAlert` 组件 |
**执行策略**:先做 `Stepper` 组件,再做首页,最后拆分发薪页面。`Money.tsx` 拆分时保持路由兼容(`/money` 仍可用)。
### Sprint 3:团队 + 离职 + 社保流程(第 5–6 周)
**目标**:员工全生命周期管理体验统一。
| 工作项 | 类型 | 工作量 | 涉及文件 |
|-------|------|-------|---------|
| 员工目录优化 | UI/UX | 2 天 | `Roster.tsx` 重构列表部分 |
| 员工 Profile Shell | UI/UX | 3 天 | 新建 `src/pages/roster/EmployeeProfileShell.tsx` |
| 离职工作流重构 | UI/UX | 3 天 | `Termination.tsx` 拆分 + 右侧摘要面板 |
| 合同类型扩充 | 功能 | 1 天 | `schema.prisma` 枚举 + 前端选择器 + 导入模板 |
| 月度社保办理流程 | UI/UX | 2 天 | `SocialInsurance.tsx` 重构月度办理部分 |
| 商险管理 Tab | 功能 | 1 天 | `SocialInsurance.tsx` 新增 Tab |
### Sprint 4:员工自助 + 考勤 + 合规(第 7–8 周)
| 工作项 | 类型 | 工作量 | 涉及文件 |
|-------|------|-------|---------|
| Employee Hub 首页 | UI/UX | 2 天 | 新建 `src/pages/portal/EmployeeHome.tsx` |
| 入职进度面板 | UI/UX | 2 天 | 新建 `src/pages/portal/OnboardingProgress.tsx` |
| 员工离职申请入口 | 功能 | 1.5 天 | `portal.routes.ts` + 前端表单 |
| 离职交接清单 | 功能 | 1 天 | `TerminationRecord` 模型扩展 + 前端 |
| 月度考勤确认流程 | UI/UX | 2 天 | `Attendance.tsx` 重构 |
| 统一风险中心 | UI/UX | 2.5 天 | 新建 `src/pages/compliance/RiskCenter.tsx` |
| 薪酬分析看板 | 功能 | 1 天 | 新建 `SalaryDashboard.tsx` + 后端 API |
### Sprint 5AI + 搜索 + 收尾(第 9–10 周)
| 工作项 | 类型 | 工作量 | 涉及文件 |
|-------|------|-------|---------|
| `CommandPalette` 全局搜索 | UI/UX | 3 天 | 新建 `src/components/ui/CommandPalette.tsx` + `/search` API |
| 上下文 AI | UI/UX | 3 天 | 业务页面嵌入"询问 AI"入口 + 上下文传递 |
| 保存视图 | UI/UX | 1.5 天 | FilterBar 扩展 + localStorage 持久化 |
| WCAG AA 整体审查 | UI/UX | 1 天 | 全局对比度/键盘/aria 审查 |
| 页面验收清单走查 | QA | 2 天 | 8 个关键页面逐一验证 |
### 关键执行原则
1. **先后端后前端**:功能修复项(社保开关、导出扩充、个税报表)先改后端 API,前端再对接,避免阻塞。
2. **大页面拆分保持路由兼容**`Money.tsx` 拆分后 `/money` 路由保留,内部用子路由导航。
3. **组件先行**:每个 Sprint 开始先完成该 Sprint 需要的新组件(`Stepper``PageHeader` 等),再做页面。
4. **并行策略**:后端改动(schema 迁移、新 API)与前端组件开发可并行,前端用 mock 数据先行。
5. **每个 Sprint 末尾部署验证**:在 `https://on.hr8ai.top` 上验证,使用 Playwright 跑关键路径。
### 人力建议
| 角色 | 人数 | 职责 |
|------|------|------|
| 全栈开发 | 12 | 后端 API + Prisma 迁移 + 前端页面 |
| 前端开发 | 1 | 组件库 + 页面重构 |
| 产品/设计 | 0.5 | 原型确认 + 验收 |
**最小可行配置**:1 名全栈开发 + 按需前端协助,10 周完成全部 P0–P1 内容。如果只做 P0(Sprint 1–2),4 周即可见效。
## 28. 成功指标
### 28.1 效率指标
- 首次创建员工到入职完成的时间。
- 创建发薪批次到发布工资条的时间。
- 每次发薪的点击数和页面跳转数。
- 异常发现到修复的平均时间。
- 离职流程平均完成时长。
### 28.2 质量指标
- 发薪归档后撤销率。
- 工资条发布错误率。
- 入职资料一次通过率。
- 逾期合同和未处理高风险数量。
- 高风险操作误触和取消率。
### 28.3 体验指标
- 首页待办完成率。
- 员工自助完成率。
- 员工制度阅读确认率。
- 移动端任务完成率。
### 28.4 建议目标
- 发薪操作时间下降 30%。
- 核心流程页面跳转下降 40%。
- 入职资料一次通过率提升至 85%+。
- 员工自助完成率达到 80%+。
- 高风险事项逾期数量下降 50%。
## 29. 页面验收清单
每个页面发布前至少验证:
- 用户能否在 5 秒内理解页面目的。
- 是否存在唯一且明确的主要动作。
- 是否说明当前状态和下一步。
- 加载/空/错误/无权限状态是否完整。
- 危险操作是否说明对象/数量/后果/可恢复性。
- 文字和状态对比度是否达到 WCAG AA。
- 仅使用键盘是否能完成主要操作。
- 360px/768px/1280px/1440px/1920px 是否无溢出。
- 敏感信息是否按权限遮罩并留审计记录。
## 30. 建议优先制作的高保真原型
第一轮只需设计以下八个关键页面:
1. 新首页任务中心。
2. 新导航和全局搜索。
3. 员工目录。
4. 员工概览详情。
5. 发薪流程总览。
6. 发薪异常和工资复核表。
7. 离职流程。
8. 员工门户首页。
---
## 31. 系统现有完整模块清单
| 分类 | 模块 | 路由 | 核心功能 |
|------|------|------|---------|
| 工作台 | 总览 | `/` | 数据看板、风险提醒、月度任务、价值报告 |
| 工作台 | 工作日历 | `/calendar` | 月度 HR 事项日历 |
| 员工管理 | 花名册 | `/roster` | 员工 CRUD、批量导入/导出、合同管理、调薪、部门变更 |
| 员工管理 | 用工办理 | `/work-process` | 入职/离职流程管理 |
| 员工管理 | 考勤确认 | `/attendance` | 班次/排班/出勤/月度报表/休假 |
| 员工管理 | 解聘补偿 | `/termination` | 解聘记录、补偿金计算 |
| 员工管理 | 特殊状态 | `/special-status` | 孕期/哺乳期/工伤/医疗期 |
| 薪税社保 | 薪税管理 | `/money` | 发薪批次、薪酬模板、加班费、工资条 |
| 薪税社保 | 社保公积金 | `/social` | 五险配置、公积金配置、基数调整、月度办理 |
| 合规风控 | 证据链 | `/evidence` | 入职/合同/培训/违纪证据管理 |
| 合规风控 | 规章制度 | `/policies` | 制度发布、员工签收 |
| 合规风控 | 用工体检 | `/tools/health-check` | 5 维度合规健康度评分 |
| 合规风控 | 医疗期计算 | `/tools/medical-period` | 医疗期期限计算器 |
| 合规风控 | 年度价值 | `/tools/annual-value` | 年度 HR 价值报告 |
| AI 辅助 | AI 顾问 | `/ai-assistant` | 劳动法 AI 问答 |
| AI 辅助 | 文本模板 | `/templates` | 合同/通知/制度模板管理 |
| 系统 | 通知管理 | `/notifications` | 风险提醒通知配置 |
| 系统 | 操作日志 | `/audit` | 操作审计日志 |
| 系统 | 设置 | `/settings` | 企业信息、导入配置 |
| 员工端 | 门户 | `/portal/*` | 工资条查看、合同确认、考勤确认、制度签收、入职办理 |
---
## 32. 参考资料
- Gusto 产品能力总览:<https://gusto.com/product>
- Gusto Employee Self-Serve<https://gusto.com/product/payroll/employee-self-serve>
- Gusto 2025 Feature Showcase<https://gusto.com/product/showcase/2025>
- Gusto 报表与自定义报告:<https://support.gusto.com/article/101334493100000/view-download-and-customize-reports-in-gusto-for-admins>
- Gusto Money Dashboard<https://gusto.com/product/money/dashboard>
这些参考体现的共同方向:减少专业门槛、围绕下一步组织界面、让异常在提交前出现、强化员工自助、跨模块数据自然衔接。TurboHR 应保留自身在中国劳动用工合规、证据链和离职处理方面的差异化能力,在此基础上建立优秀的产品体验。
@@ -0,0 +1,54 @@
/**
* 应用外壳组件 — 统一页面布局结构
* 包含 PageHeader 区域 + 内容区域,配合 SidebarNav 和 TopNav 使用
*/
import { ReactNode } from 'react'
import clsx from 'clsx'
interface AppShellProps {
children: ReactNode
className?: string
}
/**
* 应用外壳 — 限制内容最大宽度,统一内边距
*/
export function AppShell({ children, className }: AppShellProps) {
return (
<div className={clsx('w-full max-w-content mx-auto px-4 md:px-6', className)}>
{children}
</div>
)
}
interface PageHeaderProps {
title: string
description?: string
actions?: ReactNode
status?: ReactNode
className?: string
}
/**
* 页面头部 — 统一标题、说明、状态和操作按钮区域
*/
export function PageHeader({ title, description, actions, status, className }: PageHeaderProps) {
return (
<div className={clsx('flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-4', className)}>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<h1 className="text-xl font-semibold text-ink-900 truncate">{title}</h1>
{status}
</div>
{description && (
<p className="text-sm text-ink-600 mt-0.5">{description}</p>
)}
</div>
{actions && (
<div className="flex items-center gap-2 shrink-0">
{actions}
</div>
)}
</div>
)
}
@@ -1,14 +1,13 @@
import { Link, useLocation } from 'react-router-dom'
import { Home, Users, Calculator, UserX, Bot, Shield } from 'lucide-react'
import { Home, Users, Calculator, CalendarCheck, Shield } from 'lucide-react'
import clsx from 'clsx'
const tabs = [
{ path: '/', label: '总览', icon: Home },
{ path: '/roster', label: '员工', icon: Users },
{ path: '/money', label: '薪', icon: Calculator },
{ path: '/social', label: '社保', icon: Shield },
{ path: '/termination', label: '解聘', icon: UserX },
{ path: '/ai-assistant', label: 'AI', icon: Bot },
{ path: '/', label: '首页', icon: Home },
{ path: '/roster', label: '团队', icon: Users },
{ path: '/money', label: '薪', icon: Calculator },
{ path: '/attendance', label: '时间', icon: CalendarCheck },
{ path: '/evidence', label: '合规', icon: Shield },
]
export default function MobileTabBar() {
+21 -19
View File
@@ -30,31 +30,36 @@ interface NavGroup {
const navGroups: NavGroup[] = [
{
title: '工作台',
title: '首页',
items: [
{ path: '/', label: '总览', icon: LayoutDashboard },
{ path: '/', label: '工作台', icon: LayoutDashboard },
{ path: '/calendar', label: '工作日历', icon: CalendarDays },
],
},
{
title: '员工管理',
title: '团队',
items: [
{ path: '/roster', label: '花名册', icon: Users },
{ path: '/work-process', label: '用工办理', icon: ClipboardList },
{ path: '/attendance', label: '考勤确认', icon: CalendarCheck },
{ path: '/termination', label: '解聘补偿', icon: UserX },
{ path: '/termination', label: '离职管理', icon: UserX },
{ path: '/special-status', label: '特殊状态', icon: Heart },
],
},
{
title: '薪税社保',
title: '薪',
items: [
{ path: '/money', label: '薪税管理', icon: Calculator },
{ path: '/social', label: '社保公积金', icon: Shield },
],
},
{
title: '合规风控',
title: '时间',
items: [
{ path: '/attendance', label: '考勤排班', icon: CalendarCheck },
],
},
{
title: '合规',
items: [
{ path: '/evidence', label: '证据链', icon: FileSearch },
{ path: '/policies', label: '规章制度', icon: FileText },
@@ -64,15 +69,10 @@ const navGroups: NavGroup[] = [
],
},
{
title: 'AI 辅助',
title: '更多',
items: [
{ path: '/ai-assistant', label: 'AI 顾问', icon: Bot },
{ path: '/templates', label: '文本模板', icon: BookMarked },
],
},
{
title: '系统',
items: [
{ path: '/notifications', label: '通知管理', icon: Bell },
{ path: '/audit', label: '操作日志', icon: ScrollText },
{ path: '/settings', label: '设置', icon: Settings },
@@ -85,7 +85,14 @@ const navGroups: NavGroup[] = [
*/
export default function SidebarNav({ mobileOpen, onClose }: { mobileOpen: boolean; onClose: () => void }) {
const location = useLocation()
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set(navGroups.map(g => g.title)))
const isActive = (path: string) => {
if (path === '/') return location.pathname === '/'
return location.pathname.startsWith(path)
}
const activeGroup = navGroups.find(g => g.items.some(item => isActive(item.path)))
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
new Set(activeGroup ? [activeGroup.title] : ['首页'])
)
const toggleGroup = (title: string) => {
setExpandedGroups(prev => {
@@ -96,11 +103,6 @@ export default function SidebarNav({ mobileOpen, onClose }: { mobileOpen: boolea
})
}
const isActive = (path: string) => {
if (path === '/') return location.pathname === '/'
return location.pathname.startsWith(path)
}
return (
<>
{/* 移动端遮罩 */}
+127
View File
@@ -0,0 +1,127 @@
/**
* 数据表格组件 — 统一表头、行样式、排序状态和空状态
* 支持列定义、行点击、选中行、固定列、对齐和空状态
*/
import { ReactNode } from 'react'
import clsx from 'clsx'
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
export interface Column<T> {
key: string
header: string
render?: (row: T) => ReactNode
sortable?: boolean
align?: 'left' | 'center' | 'right'
width?: string
fixed?: 'left' | 'right'
}
interface DataTableProps<T> {
columns: Column<T>[]
data: T[]
rowKey: (row: T) => string
onRowClick?: (row: T) => void
sortBy?: string
sortOrder?: 'asc' | 'desc'
onSort?: (key: string) => void
emptyState?: ReactNode
dense?: boolean
className?: string
}
/**
* 数据表格 — 支持排序、行点击、空状态和密度切换
*/
export function DataTable<T>({
columns,
data,
rowKey,
onRowClick,
sortBy,
sortOrder,
onSort,
emptyState,
dense,
className,
}: DataTableProps<T>) {
const alignClass = (align?: string) =>
align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left'
return (
<div className={clsx('overflow-x-auto border border-border-default rounded-card bg-surface-card', className)}>
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border-default bg-surface-muted">
{columns.map((col) => (
<th
key={col.key}
className={clsx(
'px-3 font-medium text-ink-600 whitespace-nowrap select-none',
dense ? 'py-1.5' : 'py-2.5',
alignClass(col.align),
col.fixed === 'left' && 'sticky left-0 z-10 bg-surface-muted',
col.fixed === 'right' && 'sticky right-0 z-10 bg-surface-muted',
onSort && col.sortable && 'cursor-pointer hover:text-ink-900',
)}
style={col.width ? { width: col.width } : undefined}
onClick={col.sortable && onSort ? () => onSort(col.key) : undefined}
>
<span className="inline-flex items-center gap-1">
{col.header}
{col.sortable && onSort && (
<span className="inline-flex">
{sortBy === col.key ? (
sortOrder === 'asc' ? (
<ChevronUp className="w-3.5 h-3.5" />
) : (
<ChevronDown className="w-3.5 h-3.5" />
)
) : (
<ChevronsUpDown className="w-3.5 h-3.5 text-ink-400" />
)}
</span>
)}
</span>
</th>
))}
</tr>
</thead>
<tbody>
{data.length === 0 ? (
<tr>
<td colSpan={columns.length} className="text-center py-12 text-ink-400">
{emptyState || '暂无数据'}
</td>
</tr>
) : (
data.map((row) => (
<tr
key={rowKey(row)}
onClick={onRowClick ? () => onRowClick(row) : undefined}
className={clsx(
'border-b border-border-subtle transition-colors',
onRowClick && 'cursor-pointer hover:bg-surface-muted',
)}
>
{columns.map((col) => (
<td
key={col.key}
className={clsx(
'px-3 text-ink-900 whitespace-nowrap',
dense ? 'py-1.5' : 'py-2.5',
alignClass(col.align),
col.fixed === 'left' && 'sticky left-0 z-10 bg-surface-card',
col.fixed === 'right' && 'sticky right-0 z-10 bg-surface-card',
)}
>
{col.render ? col.render(row) : (row as any)[col.key]}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
)
}
+73
View File
@@ -0,0 +1,73 @@
/**
* 筛选栏组件 — 统一搜索、筛选条件展示和工具按钮
* 支持搜索框、可移除的筛选条件标签、右侧工具区
*/
import { ReactNode } from 'react'
import clsx from 'clsx'
import { Search, X } from 'lucide-react'
interface FilterBarProps {
searchValue?: string
searchPlaceholder?: string
onSearchChange?: (value: string) => void
filters?: ReactNode
activeChips?: Array<{ label: string; onRemove: () => void }>
tools?: ReactNode
className?: string
}
/**
* 筛选栏 — 搜索 + 筛选条件 + 工具按钮
*/
export function FilterBar({
searchValue,
searchPlaceholder = '搜索…',
onSearchChange,
filters,
activeChips,
tools,
className,
}: FilterBarProps) {
return (
<div className={clsx('flex flex-col gap-2 mb-3', className)}>
{/* 第一行:搜索 + 筛选器 + 工具 */}
<div className="flex flex-wrap items-center gap-2">
{onSearchChange !== undefined && (
<div className="relative flex-1 min-w-[180px] max-w-xs">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-ink-400" />
<input
type="text"
value={searchValue || ''}
onChange={(e) => onSearchChange(e.target.value)}
placeholder={searchPlaceholder}
className="w-full pl-8 pr-3 py-1.5 rounded-md border border-border-default bg-surface-card text-sm text-ink-900 focus:outline-none focus:ring-2 focus:ring-brand-600/30 focus:border-brand-600 transition-colors"
/>
</div>
)}
{filters && <div className="flex items-center gap-2 flex-wrap">{filters}</div>}
{tools && <div className="flex items-center gap-2 ml-auto shrink-0">{tools}</div>}
</div>
{/* 第二行:激活的筛选条件标签 */}
{activeChips && activeChips.length > 0 && (
<div className="flex items-center gap-1.5 flex-wrap">
{activeChips.map((chip, i) => (
<span
key={i}
className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-surface-muted text-ink-600"
>
{chip.label}
<button
onClick={chip.onRemove}
className="hover:text-danger transition-colors"
aria-label="移除筛选条件"
>
<X className="w-3 h-3" />
</button>
</span>
))}
</div>
)}
</div>
)
}
+10 -9
View File
@@ -4,9 +4,10 @@
@layer base {
body {
@apply bg-surface text-gray-900 antialiased;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
@apply bg-surface-page text-ink-900 antialiased;
font-family: Inter, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
font-variant-numeric: tabular-nums;
font-size: 14px;
line-height: 1.5;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
@@ -29,22 +30,22 @@
@apply inline-flex items-center justify-center px-3 py-1.5 rounded font-medium text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-primary {
@apply btn bg-primary text-white hover:bg-primary-dark;
@apply btn bg-brand-600 text-white hover:bg-brand-700;
}
.btn-secondary {
@apply btn bg-gray-100 text-gray-700 hover:bg-gray-200;
@apply btn bg-surface-muted text-ink-700 hover:bg-border-subtle;
}
.btn-danger {
@apply btn bg-danger text-white hover:bg-red-700;
@apply btn bg-danger text-white hover:bg-danger-light;
}
.card {
@apply bg-white rounded-lg shadow-sm border border-gray-200 p-4;
@apply bg-surface-card rounded-card border border-border-default;
}
.input {
@apply w-full px-2.5 py-1.5 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm;
@apply w-full px-2.5 py-2 rounded-md border border-border-default bg-surface-card text-sm text-ink-900 focus:outline-none focus:ring-2 focus:ring-brand-600/30 focus:border-brand-600 transition-colors;
}
.label {
@apply block text-sm font-medium text-gray-700 mb-1;
@apply block text-sm font-medium text-ink-700 mb-1;
}
}
+8
View File
@@ -11,6 +11,8 @@ import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
import type { DashboardData } from '../types'
import TurnoverStats from './dashboard/TurnoverStats'
import PerformanceStats from './dashboard/PerformanceStats'
function fmt(n: number) {
return `¥${(n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
@@ -679,6 +681,12 @@ export default function Dashboard() {
)}
</Card>
</div>
{/* 入离职统计 + 绩效统计 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 mt-3">
<TurnoverStats />
<PerformanceStats />
</div>
</div>
)}
@@ -0,0 +1,122 @@
/**
* 绩效统计看板 — 展示绩效等级分布和部门/周期对比
*/
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid, Legend } from 'recharts'
import { Award, TrendingUp } from 'lucide-react'
import api from '../../lib/api'
const GRADE_COLORS: Record<string, string> = {
'A': '#237A57',
'B': '#356A8A',
'C': '#A76113',
'D': '#B83232',
'S': '#C7442E',
'未评级': '#9CA39B',
}
export default function PerformanceStats() {
const [period, setPeriod] = useState(new Date().getFullYear().toString())
const { data, isLoading } = useQuery({
queryKey: ['performance-stats', period],
queryFn: async () => {
const res = await api.get(`/dashboard/performance-stats?period=${period}`)
return res.data.data
},
})
if (isLoading) {
return (
<div className="card p-4">
<div className="animate-pulse space-y-3">
<div className="h-4 bg-surface-muted rounded w-32" />
<div className="h-48 bg-surface-muted rounded" />
</div>
</div>
)
}
if (!data || data.total === 0) {
return (
<div className="card p-4">
<h3 className="text-sm font-medium text-ink-700 mb-2"></h3>
<div className="flex items-center justify-center h-32 text-ink-400 text-sm"></div>
</div>
)
}
return (
<div className="card p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-ink-700"></h3>
<select
value={period}
onChange={(e) => setPeriod(e.target.value)}
className="text-xs px-2 py-1 rounded border border-border-default bg-surface-card text-ink-700 focus:outline-none focus:ring-2 focus:ring-brand-600/30"
>
{[new Date().getFullYear(), new Date().getFullYear() - 1].map(y => (
<option key={y} value={y}>{y} </option>
))}
</select>
</div>
{/* 汇总 */}
<div className="grid grid-cols-2 gap-3 mb-4">
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-ink-900 flex items-center gap-1">
<Award className="w-4 h-4" />{data.total}
</span>
</div>
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-info flex items-center gap-1">
<TrendingUp className="w-4 h-4" />{data.overallAvgScore}
</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* 等级分布饼图 */}
<div>
<p className="text-xs text-ink-500 mb-2"></p>
<ResponsiveContainer width="100%" height={160}>
<PieChart>
<Pie
data={data.gradeDistribution}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
outerRadius={60}
label={({ name, value }: any) => `${name}: ${value}`}
labelLine={false}
>
{data.gradeDistribution.map((entry: any, i: number) => (
<Cell key={i} fill={GRADE_COLORS[entry.name] || '#9CA39B'} />
))}
</Pie>
<Tooltip contentStyle={{ fontSize: 12, borderRadius: 8, border: '1px solid #DDE1DD' }} />
</PieChart>
</ResponsiveContainer>
</div>
{/* 部门平均分柱状图 */}
<div>
<p className="text-xs text-ink-500 mb-2"></p>
<ResponsiveContainer width="100%" height={160}>
<BarChart data={data.departmentDistribution} layout="vertical" margin={{ top: 4, right: 8, bottom: 0, left: 20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#ECEEEC" horizontal={false} />
<XAxis type="number" tick={{ fontSize: 10, fill: '#7A8278' }} />
<YAxis type="category" dataKey="name" tick={{ fontSize: 10, fill: '#7A8278' }} width={60} />
<Tooltip contentStyle={{ fontSize: 12, borderRadius: 8, border: '1px solid #DDE1DD' }} />
<Bar dataKey="avgScore" name="平均分" fill="#356A8A" radius={[0, 3, 3, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
)
}
@@ -0,0 +1,93 @@
/**
* 入离职统计看板 — 展示按月入职/离职趋势和汇总数据
*/
import { useQuery } from '@tanstack/react-query'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'
import { UserPlus, UserMinus, Users, TrendingDown } from 'lucide-react'
import api from '../../lib/api'
export default function TurnoverStats() {
const { data, isLoading } = useQuery({
queryKey: ['turnover-stats'],
queryFn: async () => {
const res = await api.get('/dashboard/turnover-stats?months=12')
return res.data.data
},
})
if (isLoading) {
return (
<div className="card p-4">
<div className="animate-pulse space-y-3">
<div className="h-4 bg-surface-muted rounded w-32" />
<div className="h-48 bg-surface-muted rounded" />
</div>
</div>
)
}
if (!data || data.monthly.length === 0) {
return (
<div className="card p-4">
<h3 className="text-sm font-medium text-ink-700 mb-2"></h3>
<div className="flex items-center justify-center h-32 text-ink-400 text-sm"></div>
</div>
)
}
const { monthly, summary } = data
return (
<div className="card p-4">
<h3 className="text-sm font-medium text-ink-700 mb-3"> 12 </h3>
{/* 汇总卡片 */}
<div className="grid grid-cols-4 gap-3 mb-4">
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-success flex items-center gap-1">
<UserPlus className="w-4 h-4" />{summary.totalHired}
</span>
</div>
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-danger flex items-center gap-1">
<UserMinus className="w-4 h-4" />{summary.totalLeft}
</span>
</div>
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-ink-900 flex items-center gap-1">
<Users className="w-4 h-4" />{summary.currentHeadcount}
</span>
</div>
<div className="flex flex-col">
<span className="text-xs text-ink-500"></span>
<span className="text-lg font-semibold text-warning flex items-center gap-1">
<TrendingDown className="w-4 h-4" />{summary.turnoverRate}%
</span>
</div>
</div>
{/* 趋势图 */}
<ResponsiveContainer width="100%" height={200}>
<BarChart data={monthly} margin={{ top: 4, right: 8, bottom: 0, left: -16 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#ECEEEC" />
<XAxis
dataKey="month"
tick={{ fontSize: 10, fill: '#7A8278' }}
tickFormatter={(v: string) => v.slice(5)}
/>
<YAxis tick={{ fontSize: 10, fill: '#7A8278' }} allowDecimals={false} />
<Tooltip
contentStyle={{ fontSize: 12, borderRadius: 8, border: '1px solid #DDE1DD' }}
formatter={(v: any) => [v, ''] as [any, any]}
/>
<Legend wrapperStyle={{ fontSize: 12 }} />
<Bar dataKey="hired" name="入职" fill="#237A57" radius={[3, 3, 0, 0]} />
<Bar dataKey="left" name="离职" fill="#B83232" radius={[3, 3, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
)
}
+73 -9
View File
@@ -6,18 +6,82 @@ export default {
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#4F46E5',
light: '#6366F1',
dark: '#4338CA',
// 品牌色 — 暖红橙,可信温暖
brand: {
50: '#FBF0EE',
100: '#F5D9D4',
200: '#EBB3A9',
300: '#E08D7E',
400: '#D66853',
500: '#C7442E',
600: '#C7442E',
700: '#A93625',
800: '#8B2B1E',
900: '#6D2118',
},
danger: '#DC2626',
warning: '#F59E0B',
safe: '#16A34A',
surface: '#F8FAFC',
// 兼容旧 primary 引用,指向 brand-600
primary: {
DEFAULT: '#C7442E',
light: '#D66853',
dark: '#A93625',
},
// 文字色
ink: {
900: '#202622',
700: '#3A4239',
600: '#59615C',
500: '#7A8278',
400: '#9CA39B',
},
// 表面色
surface: {
page: '#F7F8F6',
card: '#FFFFFF',
muted: '#F0F2EF',
},
// 状态色
success: {
DEFAULT: '#237A57',
light: '#3A9B72',
bg: '#E6F4ED',
},
warning: {
DEFAULT: '#A76113',
light: '#C98A30',
bg: '#FBF0E0',
},
danger: {
DEFAULT: '#B83232',
light: '#D94848',
bg: '#FCEAEA',
},
info: {
DEFAULT: '#356A8A',
light: '#4F8AAB',
bg: '#E8F0F5',
},
// 边框
border: {
DEFAULT: '#DDE1DD',
strong: '#C4C9C4',
subtle: '#ECEEEC',
},
},
fontFamily: {
sans: ['Inter', '"Noto Sans SC"', '"PingFang SC"', '"Microsoft YaHei"', 'sans-serif'],
},
fontSize: {
// 使用 tabular-nums 对齐数字
'kpi': ['28px', { lineHeight: '36px', fontWeight: '600' }],
},
maxWidth: {
content: '1280px',
content: '1440px',
},
borderRadius: {
card: '8px',
},
spacing: {
'18': '72px',
},
},
},