feat: 员工端新增个人资料页面,数据同步到花名册

- 后端新增 GET/PUT /portal/profile 接口,员工可查看和编辑个人资料
- 员工可编辑:紧急联系人、住址、银行卡、学历、参保城市
- 只读字段:姓名、性别、手机号、证件号码、部门、岗位、入职日期
- 数据直接写入 Employee 表(花名册),HR 管理端可见
- 更新操作记录证据链(PROFILE_UPDATE)
- 前端新增 MyProfile.tsx 页面,PortalNav 添加导航入口
This commit is contained in:
freedakgmail
2026-08-17 18:56:42 +08:00
parent cdbfeb4f99
commit c4a929fc8e
6 changed files with 319 additions and 1 deletions
+86
View File
@@ -10,6 +10,7 @@ import { authMiddleware, AuthRequest } from '../middleware/auth'
import { portalLoginSchema, portalSendCodeSchema, portalVerifyCodeSchema, onboardingSchema, contractConfirmSchema, contractSendCodeSchema } from '../schemas/portal.schema'
import { setCode, getCode, deleteCode, updateCode, checkRateLimit } from '../lib/codeStore'
import { createEvidence, appendEvidence } from '../services/evidence.service'
import { encrypt, decrypt, sha256 } from '../lib/crypto'
const router = Router()
@@ -870,6 +871,91 @@ router.get('/onboarding/progress', portalAuth, async (req: any, res, next) => {
} catch (err) { next(err) }
})
// ========== 员工端:个人资料查看与编辑 ==========
// 查看个人资料
router.get('/profile', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const emp = await prisma.employee.findFirst({ where: { id: employeeId, orgId } })
if (!emp) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
// 解密敏感字段
let idCardNo = ''
let bankAccount = ''
try { if (emp.idCardNumber) idCardNo = decrypt(emp.idCardNumber) } catch {}
try { if (emp.bankAccount) bankAccount = decrypt(emp.bankAccount) } catch {}
res.json({
success: true,
data: {
name: emp.name,
gender: emp.gender || '',
phone: emp.phone || '',
idCardNo,
idType: emp.idType || 'ID_CARD',
department: emp.department,
position: emp.position || '',
hireDate: emp.hireDate ? emp.hireDate.toISOString().slice(0, 10) : '',
emergencyContact: emp.emergencyContact || '',
emergencyPhone: emp.emergencyPhone || '',
address: emp.address || '',
bankAccount,
bankName: emp.bankName || '',
education: emp.education || '',
city: emp.city || '',
birthDate: emp.birthDate ? emp.birthDate.toISOString().slice(0, 10) : '',
},
})
} catch (err) { next(err) }
})
// 更新个人资料(员工可编辑字段:紧急联系人、地址、银行卡、学历等)
router.put('/profile', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const emp = await prisma.employee.findFirst({ where: { id: employeeId, orgId } })
if (!emp) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
const {
emergencyContact, emergencyPhone, address,
bankAccount, bankName, education, city,
} = req.body
// 构建更新数据(仅允许员工自行编辑的字段)
const updateData: any = {}
if (emergencyContact !== undefined) updateData.emergencyContact = emergencyContact || null
if (emergencyPhone !== undefined) updateData.emergencyPhone = emergencyPhone || null
if (address !== undefined) updateData.address = address || null
if (bankName !== undefined) updateData.bankName = bankName || null
if (education !== undefined) updateData.education = education || null
if (city !== undefined) updateData.city = city || null
// 银行卡号需要加密
if (bankAccount !== undefined && bankAccount) {
updateData.bankAccount = encrypt(bankAccount)
}
await prisma.employee.update({ where: { id: employeeId }, data: updateData })
// 记录证据链
await createEvidence({
orgId,
category: 'PROFILE_UPDATE',
refId: employeeId,
employeeId,
events: [{
action: '员工更新个人资料',
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.headers['user-agent'],
location: `更新字段:${Object.keys(updateData).join(', ')}`,
}],
createdBy: employeeId,
}).catch(() => {})
res.json({ success: true, data: { message: '资料更新成功' } })
} catch (err) { next(err) }
})
// ========== 员工端:离职申请 ==========
// 提交离职申请
router.post('/resignation/submit', portalAuth, async (req: any, res, next) => {
+1
View File
@@ -45,6 +45,7 @@ export type EvidenceCategory =
| 'TERMINATION'
| 'TRAINING'
| 'PERFORMANCE'
| 'PROFILE_UPDATE'
/**
* 创建证据链记录