fix: 非列表页面姓名显示处也加上身份证号

后端:
- employee.routes.ts: checkPhone 返回 idCardNumber
- risk.service.ts: 风险项查询加上 idCardNumber 并解密
- dashboard.routes.ts: 风险中心返回 employeeIdCardNumber

前端:
- Termination.tsx: 离职管理所有姓名显示处加上身份证号(员工概况、通知书、PDF导出、下拉选项)
- Roster.tsx: 弹窗标题加上身份证号,signChoice 传递 idCardNumber
- SignMethodChoice.tsx: 签署方式弹窗显示身份证号
- Contracts.tsx: 手机查重提示加上身份证号
- RiskCenter.tsx: 风险卡片员工姓名后显示身份证号
This commit is contained in:
freedakgmail
2026-08-18 07:46:43 +08:00
parent a58cc3f6f9
commit 5c016914dd
8 changed files with 28 additions and 14 deletions
+1 -1
View File
@@ -546,7 +546,7 @@ router.get('/risks', authMiddleware, async (req: AuthRequest, res: Response, nex
actionUrl: t.actionUrl || '',
estimatedLoss: t.estimatedLoss || 0,
deadline: t.deadline || null,
employee: t.employeeName ? { name: t.employeeName, department: t.employeeDepartment || '' } : null,
employee: t.employeeName ? { name: t.employeeName, department: t.employeeDepartment || '', idCardNumber: t.employeeIdCardNumber || null } : null,
}))
res.json({ success: true, data: risks })
} catch (err) {
+2 -2
View File
@@ -141,12 +141,12 @@ router.get('/check-phone', authMiddleware, async (req: AuthRequest, res, next) =
// 手机号加密存储,需遍历匹配(量小可接受)
const employees = await prisma.employee.findMany({
where: { orgId: req.user!.orgId },
select: { id: true, name: true, department: true, status: true, phone: true },
select: { id: true, name: true, department: true, status: true, phone: true, idCardNumber: true },
})
const matched = employees.find(e => {
try { return e.phone ? decrypt(e.phone) === phone : false } catch { return false }
})
res.json({ success: true, data: { exists: !!matched, employee: matched ? { id: matched.id, name: matched.name, department: matched.department, status: matched.status } : undefined } })
res.json({ success: true, data: { exists: !!matched, employee: matched ? { id: matched.id, name: matched.name, department: matched.department, status: matched.status, idCardNumber: (() => { try { return matched.idCardNumber ? (matched.idCardNumber.includes(':') ? decrypt(matched.idCardNumber) : matched.idCardNumber) : null } catch { return null } })() } : undefined } })
} catch (err) {
next(err)
}
+2 -1
View File
@@ -699,7 +699,7 @@ export async function getDashboardData(orgId: string) {
prisma.riskItem.count({ where: { orgId, status: 'PENDING' } }),
prisma.riskItem.findMany({
where: { orgId, status: 'PENDING' },
include: { employee: { select: { id: true, name: true, department: true, idCardHash: true } } },
include: { employee: { select: { id: true, name: true, department: true, idCardHash: true, idCardNumber: true } } },
orderBy: [{ level: 'asc' }, { estimatedLoss: 'desc' }],
}),
prisma.riskItem.findMany({
@@ -930,6 +930,7 @@ export async function getDashboardData(orgId: string) {
employeeName: r.employee?.name || null,
employeeDepartment: r.employee?.department || null,
employeeIdCardHash: r.employee?.idCardHash || null,
employeeIdCardNumber: r.employee?.idCardNumber ? (() => { try { return r.employee.idCardNumber.includes(':') ? decrypt(r.employee.idCardNumber) : r.employee.idCardNumber } catch { return null } })() : null,
}
})
@@ -23,6 +23,8 @@ interface SignMethodChoiceProps {
employeeId: string
/** 员工姓名(用于显示) */
employeeName: string
/** 员工身份证号(用于显示) */
employeeIdCardNumber?: string
/** 签署场景 */
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
/** 文件标题 */
@@ -48,6 +50,7 @@ export default function SignMethodChoice({
onClose,
employeeId,
employeeName,
employeeIdCardNumber,
scene,
documentTitle,
contractId,
@@ -107,7 +110,7 @@ export default function SignMethodChoice({
<Modal open={open} onClose={onClose} title={`${actionName}成功 — 选择签署方式`} size="sm">
<div className="space-y-4">
<div className="text-sm text-gray-600 bg-gray-50 rounded-md p-3">
<div className="font-medium text-gray-700">{employeeName} · {SCENE_LABELS[scene] || scene}</div>
<div className="font-medium text-gray-700">{employeeName} · {SCENE_LABELS[scene] || scene}{employeeIdCardNumber && <span className="ml-1 text-gray-400 font-mono text-xs">{employeeIdCardNumber}</span>}</div>
<div className="text-xs text-gray-400 mt-1">{documentTitle}</div>
</div>
+1 -1
View File
@@ -371,7 +371,7 @@ function AddEmployeeModal({ open, onClose, onSubmit, loading, error }: {
{phoneDuplicate?.exists && (
<div className="px-3 py-2 rounded-md bg-amber-50 text-amber-700 text-xs flex items-center gap-2">
<AlertTriangle className="w-4 h-4 shrink-0" />
<span>{phoneDuplicate.employee?.name}{phoneDuplicate.employee?.department}</span>
<span>{phoneDuplicate.employee?.name}{phoneDuplicate.employee?.department}{phoneDuplicate.employee?.idCardNumber && `${phoneDuplicate.employee.idCardNumber}` }</span>
</div>
)}
+9 -2
View File
@@ -101,6 +101,7 @@ export default function Roster() {
open: boolean
employeeId: string
employeeName: string
employeeIdCardNumber?: string
scene: 'CONTRACT' | 'RESIGNATION' | 'POLICY' | 'PAYSLIP' | 'ONBOARDING'
documentTitle: string
contractId?: string
@@ -226,6 +227,7 @@ export default function Roster() {
open: true,
employeeId: rehireEmployee?.id || '',
employeeName: rehireEmployee?.name || '',
employeeIdCardNumber: rehireEmployee?.idCardNumber,
scene: 'CONTRACT',
documentTitle: `${rehireEmployee?.name || ''}的劳动合同`,
remark: '重新入职时发起',
@@ -247,6 +249,7 @@ export default function Roster() {
open: true,
employeeId: salaryEmployee?.id || '',
employeeName: salaryEmployee?.name || '',
employeeIdCardNumber: salaryEmployee?.idCardNumber,
scene: 'POLICY',
documentTitle: `${salaryEmployee?.name || ''}的薪酬调整确认书`,
remark: '薪酬变更时发起',
@@ -268,6 +271,7 @@ export default function Roster() {
open: true,
employeeId: deptEmployee?.id || '',
employeeName: deptEmployee?.name || '',
employeeIdCardNumber: deptEmployee?.idCardNumber,
scene: 'POLICY',
documentTitle: `${deptEmployee?.name || ''}的调岗确认书`,
remark: '部门/岗位调动时发起',
@@ -294,6 +298,7 @@ export default function Roster() {
open: true,
employeeId: variables.employeeId,
employeeName: confirmEmployee?.name || '',
employeeIdCardNumber: confirmEmployee?.idCardNumber,
scene: 'POLICY',
documentTitle: `${confirmEmployee?.name || ''}的转正薪酬调整确认书`,
remark: `转正薪资调整:¥${originalSalary || 0} → ¥${newSalary}`,
@@ -404,6 +409,7 @@ export default function Roster() {
open: true,
employeeId: renewEmployee?.id || '',
employeeName: renewEmployee?.name || '',
employeeIdCardNumber: renewEmployee?.idCardNumber,
scene: 'CONTRACT',
documentTitle: `${renewEmployee?.name || ''}的续签劳动合同`,
remark: '合同续签时发起',
@@ -1186,7 +1192,7 @@ export default function Roster() {
{/* 开具证明弹窗 */}
{showCertModal && certEmployee && (
<Modal open={showCertModal} onClose={() => { setShowCertModal(false); setCertEmployee(null) }} title={`${certEmployee.name} 开具证明`}>
<Modal open={showCertModal} onClose={() => { setShowCertModal(false); setCertEmployee(null) }} title={`${certEmployee.name}${certEmployee.idCardNumber ? `${certEmployee.idCardNumber}` : ''} 开具证明`}>
<div className="space-y-3">
<div>
<Label></Label>
@@ -1228,7 +1234,7 @@ export default function Roster() {
{/* 续签合同弹窗 */}
{showRenewModal && renewEmployee && (
<Modal open={showRenewModal} onClose={() => { setShowRenewModal(false); setRenewEmployee(null) }} title={`${renewEmployee.name} 续签合同`}>
<Modal open={showRenewModal} onClose={() => { setShowRenewModal(false); setRenewEmployee(null) }} title={`${renewEmployee.name}${renewEmployee.idCardNumber ? `${renewEmployee.idCardNumber}` : ''} 续签合同`}>
<div className="space-y-3">
<div className="text-xs text-gray-500">
{renewEmployee.latestContract?.endDate ? new Date(renewEmployee.latestContract.endDate).toISOString().slice(0, 10) : '无'}
@@ -1523,6 +1529,7 @@ export default function Roster() {
onClose={() => setSignChoice(null)}
employeeId={signChoice.employeeId}
employeeName={signChoice.employeeName}
employeeIdCardNumber={signChoice.employeeIdCardNumber}
scene={signChoice.scene}
documentTitle={signChoice.documentTitle}
contractId={signChoice.contractId}
+8 -6
View File
@@ -230,6 +230,7 @@ export default function Termination() {
id: string
employeeId: string
name: string
idCardNumber?: string
department: string
reason: string
reasonLabel: string
@@ -758,6 +759,7 @@ export default function Termination() {
id: `saved-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
employeeId,
name: selectedEmployee.name,
idCardNumber: selectedEmployee.idCardNumber,
department: selectedEmployee.department,
reason,
reasonLabel,
@@ -1284,13 +1286,13 @@ ${items}
<Select value={employeeId} onChange={(e) => setEmployeeId(e.target.value)}>
<option value=""></option>
{employees?.map((emp) => (
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}</option>
<option key={emp.id} value={emp.id}>{emp.name} - {emp.department}{emp.idCardNumber ? ` (${emp.idCardNumber})` : ''}</option>
))}
</Select>
</div>
{selectedEmployee && (
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="font-medium">{selectedEmployee.name}{selectedEmployee.department}</div>
<div className="font-medium">{selectedEmployee.name}{selectedEmployee.department}{selectedEmployee.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
<div>{selectedEmployee.hireDate?.toString().slice(0, 10)}</div>
<div>¥{fmt(selectedEmployee.monthlySalary)}</div>
{selectedEmployee.hasTermination && selectedEmployee.latestTerminationStatus !== 'CANCELLED' && selectedEmployee.latestTerminationStatus !== 'COMPLETED' && (
@@ -1667,7 +1669,7 @@ ${items}
<div className="space-y-3">
{/* 员工概况 */}
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="font-medium">{selectedEmployee?.name}{selectedEmployee?.department}</div>
<div className="font-medium">{selectedEmployee?.name}{selectedEmployee?.department}{selectedEmployee?.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
<div>{costResult.years}{costResult.remainingMonths}</div>
<div>¥{fmt(costResult.wage)}/</div>
{costResult.capped && (
@@ -1988,7 +1990,7 @@ ${items}
<div className="space-y-3">
{/* 汇总信息 */}
<div className="text-xs text-gray-600 bg-gray-50 p-3 rounded-md space-y-1">
<div className="font-medium">{selectedEmployee?.name}{selectedEmployee?.department}</div>
<div className="font-medium">{selectedEmployee?.name}{selectedEmployee?.department}{selectedEmployee?.idCardNumber && <span className="text-gray-400 font-mono ml-1">{selectedEmployee.idCardNumber}</span>}</div>
<div>{reasonLabel}</div>
<div>{terminationDate}</div>
<div>{socialInsEndMonth || terminationDate.slice(0, 7)}</div>
@@ -2100,7 +2102,7 @@ ${items}
<h2 className="text-base font-bold"></h2>
</div>
<div className="text-xs text-gray-700 space-y-3">
<p><strong>{selectedEmployee?.name}</strong> /</p>
<p><strong>{selectedEmployee?.name}</strong> /{selectedEmployee?.idCardNumber || '—'}</p>
<p>
<strong>{selectedEmployee?.hireDate?.toString().slice(0, 10)}</strong> {selectedEmployee?.department}
<strong>{reasonLabel}</strong> <strong>{terminationDate}</strong>
@@ -2293,7 +2295,7 @@ ${items}
savedItems.forEach((item, i) => {
if (y > 270) { doc.addPage(); y = 20 }
doc.setFontSize(11)
doc.text(`${i + 1}. ${item.name} (${item.department})`, 14, y)
doc.text(`${i + 1}. ${item.name} (${item.department}) ${item.idCardNumber || ''}`, 14, y)
y += 6
doc.setFontSize(9)
doc.text(`解聘原因: ${item.reason || '-'}`, 20, y); y += 5
@@ -282,6 +282,7 @@ export default function RiskCenter() {
{r.employee && (
<div className="text-xs text-gray-400 mt-0.5">
{r.employee.name} · {r.employee.department || ''}
{r.employee.idCardNumber && <span className="ml-1 font-mono">{r.employee.idCardNumber}</span>}
</div>
)}
</div>