fix: 员工端姓名显示处加上身份证号

后端:
- portal.routes.ts: 密码登录/验证码登录/自动登录返回 idCardNumber
- portal.routes.ts: home/overview 返回 employeeIdCardNumber
- portal.routes.ts: 合同确认接口返回 employeeIdCardNumber

前端:
- EmployeeHome.tsx: 首页欢迎卡片显示身份证号
- PortalLayout.tsx: 顶栏姓名后显示身份证号
- Payslip.tsx: 下载文件名加上身份证号
- ContractConfirm.tsx: 合同确认页显示身份证号
This commit is contained in:
freedakgmail
2026-08-18 07:52:52 +08:00
parent 5c016914dd
commit 754dfd3116
5 changed files with 17 additions and 10 deletions
+13 -7
View File
@@ -40,7 +40,7 @@ router.post('/login', async (req, res, next) => {
// 查找所有匹配手机号的在职员工(可能跨组织) // 查找所有匹配手机号的在职员工(可能跨组织)
const employees = await prisma.employee.findMany({ const employees = await prisma.employee.findMany({
where: { phone: data.phone, status: 'ACTIVE' }, where: { phone: data.phone, status: 'ACTIVE' },
select: { id: true, name: true, department: true, orgId: true, passwordHash: true, org: { select: { name: true } } }, select: { id: true, name: true, department: true, orgId: true, passwordHash: true, idCardNumber: true, org: { select: { name: true } } },
}) })
if (employees.length === 0) { if (employees.length === 0) {
return res.status(400).json({ success: false, error: { code: 'AUTH_FAILED', message: '手机号或密码错误' } }) return res.status(400).json({ success: false, error: { code: 'AUTH_FAILED', message: '手机号或密码错误' } })
@@ -60,7 +60,7 @@ router.post('/login', async (req, res, next) => {
return res.status(400).json({ success: false, error: { code: 'AUTH_FAILED', message: '手机号或密码错误' } }) return res.status(400).json({ success: false, error: { code: 'AUTH_FAILED', message: '手机号或密码错误' } })
} }
const token = signAccessToken({ id: matchedEmployee.id, orgId: matchedEmployee.orgId, role: 'EMPLOYEE' }) const token = signAccessToken({ id: matchedEmployee.id, orgId: matchedEmployee.orgId, role: 'EMPLOYEE' })
res.json({ success: true, data: { token, employee: { id: matchedEmployee.id, name: matchedEmployee.name, department: matchedEmployee.department, orgName: (matchedEmployee as any).org?.name || '' } } }) res.json({ success: true, data: { token, employee: { id: matchedEmployee.id, name: matchedEmployee.name, department: matchedEmployee.department, orgName: (matchedEmployee as any).org?.name || '', idCardNumber: (() => { try { return matchedEmployee.idCardNumber ? (matchedEmployee.idCardNumber.includes(':') ? decrypt(matchedEmployee.idCardNumber) : matchedEmployee.idCardNumber) : null } catch { return null } })() } } })
} catch (err) { } catch (err) {
next(err) next(err)
} }
@@ -110,7 +110,7 @@ router.post('/verify-code', async (req, res, next) => {
// 查找所有匹配手机号的在职员工(可能跨组织) // 查找所有匹配手机号的在职员工(可能跨组织)
const employees = await prisma.employee.findMany({ const employees = await prisma.employee.findMany({
where: { phone: data.phone, status: 'ACTIVE' }, where: { phone: data.phone, status: 'ACTIVE' },
select: { id: true, name: true, department: true, orgId: true, org: { select: { name: true } } }, select: { id: true, name: true, department: true, orgId: true, idCardNumber: true, org: { select: { name: true } } },
}) })
if (employees.length === 0) { if (employees.length === 0) {
return res.status(400).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } }) return res.status(400).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
@@ -118,7 +118,7 @@ router.post('/verify-code', async (req, res, next) => {
// 如果只有一个匹配,直接登录 // 如果只有一个匹配,直接登录
const employee = employees[0] const employee = employees[0]
const token = signAccessToken({ id: employee.id, orgId: employee.orgId, role: 'EMPLOYEE' }) const token = signAccessToken({ id: employee.id, orgId: employee.orgId, role: 'EMPLOYEE' })
res.json({ success: true, data: { token, employee: { id: employee.id, name: employee.name, department: employee.department, orgName: (employee as any).org?.name || '' } } }) res.json({ success: true, data: { token, employee: { id: employee.id, name: employee.name, department: employee.department, orgName: (employee as any).org?.name || '', idCardNumber: (() => { try { return employee.idCardNumber ? (employee.idCardNumber.includes(':') ? decrypt(employee.idCardNumber) : employee.idCardNumber) : null } catch { return null } })() } } })
} catch (err) { } catch (err) {
next(err) next(err)
} }
@@ -421,7 +421,7 @@ router.get('/contract-confirm/:token', async (req, res, next) => {
include: { include: {
contract: { contract: {
include: { include: {
employee: { select: { name: true, org: { select: { name: true } } } }, employee: { select: { name: true, idCardNumber: true, org: { select: { name: true } } } },
}, },
}, },
}, },
@@ -434,6 +434,7 @@ router.get('/contract-confirm/:token', async (req, res, next) => {
data: { data: {
orgName: link.contract.employee.org.name, orgName: link.contract.employee.org.name,
employeeName: link.contract.employee.name, employeeName: link.contract.employee.name,
employeeIdCardNumber: (() => { try { const v = link.contract.employee.idCardNumber; return v ? (v.includes(':') ? decrypt(v) : v) : null } catch { return null } })(),
contract: link.contract, contract: link.contract,
}, },
}) })
@@ -694,13 +695,13 @@ router.get('/auto-login', async (req, res, next) => {
} }
const employee = await prisma.employee.findFirst({ const employee = await prisma.employee.findFirst({
where: { id: payload.id, orgId: payload.orgId, status: 'ACTIVE' }, where: { id: payload.id, orgId: payload.orgId, status: 'ACTIVE' },
select: { id: true, name: true, department: true, orgId: true, org: { select: { name: true } } }, select: { id: true, name: true, department: true, orgId: true, idCardNumber: true, org: { select: { name: true } } },
}) })
if (!employee) { if (!employee) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在或已离职' } }) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在或已离职' } })
} }
const accessToken = signAccessToken({ id: employee.id, orgId: employee.orgId, role: 'EMPLOYEE' }) const accessToken = signAccessToken({ id: employee.id, orgId: employee.orgId, role: 'EMPLOYEE' })
res.json({ success: true, data: { token: accessToken, employee: { id: employee.id, name: employee.name, department: employee.department, orgName: (employee as any).org?.name || '' } } }) res.json({ success: true, data: { token: accessToken, employee: { id: employee.id, name: employee.name, department: employee.department, orgName: (employee as any).org?.name || '', idCardNumber: (() => { try { return employee.idCardNumber ? (employee.idCardNumber.includes(':') ? decrypt(employee.idCardNumber) : employee.idCardNumber) : null } catch { return null } })() } } })
} catch (err) { } catch (err) {
next(err) next(err)
} }
@@ -742,6 +743,9 @@ router.get('/home/overview', portalAuth, async (req: any, res, next) => {
const employee = await prisma.employee.findFirst({ where: { id: employeeId, orgId } }) const employee = await prisma.employee.findFirst({ where: { id: employeeId, orgId } })
if (!employee) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } }) if (!employee) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
let employeeIdCardNumber: string | null = null
try { if (employee.idCardNumber) employeeIdCardNumber = employee.idCardNumber.includes(':') ? decrypt(employee.idCardNumber) : employee.idCardNumber } catch {}
// 最新工资条 // 最新工资条
const latestPayslip = await prisma.payslip.findFirst({ const latestPayslip = await prisma.payslip.findFirst({
where: { employeeId, orgId, publishStatus: 'PUBLISHED' }, where: { employeeId, orgId, publishStatus: 'PUBLISHED' },
@@ -808,6 +812,8 @@ router.get('/home/overview', portalAuth, async (req: any, res, next) => {
res.json({ res.json({
success: true, success: true,
data: { data: {
employeeName: employee.name,
employeeIdCardNumber,
latestPayslip, latestPayslip,
contract: contract ? { contract: contract ? {
typeLabel: typeLabels[contract.contractType] || contract.contractType, typeLabel: typeLabels[contract.contractType] || contract.contractType,
@@ -38,7 +38,7 @@ export default function PortalLayout({ children }: { children: React.ReactNode }
</div> </div>
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
{employee.name && ( {employee.name && (
<span className="text-xs text-gray-500 truncate max-w-[60px]">{employee.name}</span> <span className="text-xs text-gray-500 truncate max-w-[120px]">{employee.name}{employee.idCardNumber ? `${employee.idCardNumber}` : ''}</span>
)} )}
<button <button
onClick={handleLogout} onClick={handleLogout}
@@ -94,7 +94,7 @@ export default function ContractConfirm() {
<Card> <Card>
<div className="space-y-3"> <div className="space-y-3">
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
{data.orgName} {data.employeeName} {data.orgName} {data.employeeName}{data.employeeIdCardNumber ? `${data.employeeIdCardNumber}` : ''}
</div> </div>
{data.contract && ( {data.contract && (
@@ -62,6 +62,7 @@ export default function EmployeeHome() {
<div> <div>
<h1 className="text-lg font-bold">{employee.name || '同事'}</h1> <h1 className="text-lg font-bold">{employee.name || '同事'}</h1>
<p className="text-sm text-indigo-100 mt-1">{employee.department || ''} · {employee.position || ''}</p> <p className="text-sm text-indigo-100 mt-1">{employee.department || ''} · {employee.position || ''}</p>
{employee.idCardNumber && <p className="text-xs text-indigo-200 mt-0.5 font-mono">{employee.idCardNumber}</p>}
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-xs text-indigo-100"></div> <div className="text-xs text-indigo-100"></div>
+1 -1
View File
@@ -55,7 +55,7 @@ export default function Payslip() {
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = `工资条_${employee.name || '员工'}_${new Date().toISOString().slice(0, 10)}.csv` a.download = `工资条_${employee.name || '员工'}${employee.idCardNumber ? `_${employee.idCardNumber}` : ''}_${new Date().toISOString().slice(0, 10)}.csv`
a.click() a.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }