feat: 系统优化Phase2 - 面包屑导航/侧边栏间距/制度公示阅读签收/模板变量中文化/通知类型补全

- 面包屑导航组件,集成至TopNav header
- 侧边栏菜单分组间距增大,分组间分隔线
- 制度公示员工阅读签收:PolicyReadRecord模型、portal路由、管理端阅读统计
- 修复Policies.tsx民主程序推进bug(字段名/API路径/参数)
- 用工文本模板变量名英文转中文显示
- 通知类型TYPE_LABELS补全(RISK_ALERT/SOCIAL_INS/OVERTIME_ALERT/PAYSLIP_READY)
- 通知示例数据补充
- h2标题统一为text-sm font-medium
- 新增run.md
This commit is contained in:
selfrelease
2026-07-26 20:32:38 +08:00
parent 9cb0d1f63b
commit d79e3baa34
71 changed files with 18561 additions and 3230 deletions
+123
View File
@@ -0,0 +1,123 @@
import { Router, Response, NextFunction } from 'express'
import { authMiddleware, AuthRequest } from '../middleware/auth'
import { z } from 'zod'
import prisma from '../lib/prisma'
import {
createPolicy, getPolicies, getPolicyDetail, updatePolicy,
advanceDemocracyStep, deletePolicy,
} from '../services/policy.service'
const router = Router()
/** 获取制度列表 */
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const status = req.query.status as string | undefined
const policies = await getPolicies(req.user!.orgId, status)
res.json({ success: true, data: policies })
} catch (err) {
next(err)
}
})
/** 获取制度详情 */
router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const policy = await getPolicyDetail(req.user!.orgId, req.params.id)
res.json({ success: true, data: policy })
} catch (err) {
next(err)
}
})
/** 创建制度 */
router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({
title: z.string().min(1),
content: z.string().min(1),
type: z.string().optional(),
})
const data = schema.parse(req.body)
const policy = await createPolicy(req.user!.orgId, req.user!.id, data)
res.json({ success: true, data: { id: policy.id } })
} catch (err) {
next(err)
}
})
/** 更新制度 */
router.put('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({
title: z.string().optional(),
content: z.string().optional(),
type: z.string().optional(),
})
const data = schema.parse(req.body)
await updatePolicy(req.user!.orgId, req.params.id, data)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 推进民主程序步骤 */
router.post('/:id/advance-step', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({ step: z.number().int().min(1).max(4), note: z.string().optional() })
const { step, note } = schema.parse(req.body)
await advanceDemocracyStep(req.user!.orgId, req.params.id, step, note)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 删除制度 */
router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
await deletePolicy(req.user!.orgId, req.params.id)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 获取制度的阅读签收统计 */
router.get('/:id/read-stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const policy = await prisma.policyDocument.findFirst({ where: { id: req.params.id, orgId }, select: { id: true } })
if (!policy) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '制度不存在' } })
}
const [totalEmployees, readRecords] = await Promise.all([
prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }),
prisma.policyReadRecord.findMany({
where: { policyId: req.params.id, orgId },
include: { employee: { select: { id: true, name: true, department: true } } },
orderBy: { readAt: 'desc' },
}),
])
res.json({
success: true,
data: {
total: totalEmployees,
readCount: readRecords.length,
unreadCount: totalEmployees - readRecords.length,
records: readRecords.map(r => ({
employeeId: r.employeeId,
employeeName: r.employee.name,
department: r.employee.department,
readAt: r.readAt.toISOString(),
ip: r.ip,
})),
},
})
} catch (err) {
next(err)
}
})
export default router