fix: portal端token过期后自动跳转登录页,避免显示空数据

- portalAxios 401 响应拦截器:清除 token 并跳转 /portal/login
- PortalLayoutWrapper:检查 token 是否存在,不存在则跳转登录页
- 排除 /portal/login 和 /portal/auto-login 页面避免重复跳转

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-17 15:06:46 +08:00
parent c66ea290eb
commit 5196d7c80a
23 changed files with 812 additions and 74 deletions
@@ -48,6 +48,7 @@ export async function batchCreateAttendanceConfirmations(orgId: string, userId:
weekendHours: number
holidayHours: number
overtimePay: number
deductionAmount?: number
}>) {
const results: Array<{ employeeId: string; success: boolean; error?: string }> = []
@@ -66,6 +67,7 @@ export async function batchCreateAttendanceConfirmations(orgId: string, userId:
weekendHours: item.weekendHours,
holidayHours: item.holidayHours,
overtimePay: item.overtimePay,
deductionAmount: item.deductionAmount || 0,
status: 'PENDING',
},
})
@@ -80,6 +82,7 @@ export async function batchCreateAttendanceConfirmations(orgId: string, userId:
weekendHours: item.weekendHours,
holidayHours: item.holidayHours,
overtimePay: item.overtimePay,
deductionAmount: item.deductionAmount || 0,
createdBy: userId,
},
})
+46
View File
@@ -14,6 +14,15 @@ function dateToMonth(date: Date): string {
return `${y}-${m}`
}
/** 安全解密数字(工资字段) */
function safeDecryptNum(encrypted: string): number {
try {
return Number(decrypt(encrypted)) || 0
} catch {
return Number(encrypted) || 0
}
}
async function clampSocialInsBase(orgId: string, base: number, city?: string, accountId?: string): Promise<number> {
// 优先按账户查年度标准
if (accountId) {
@@ -271,6 +280,9 @@ export async function getEmployees(orgId: string, params: { page?: number; pageS
} catch {
decryptedSalary = Number(emp.monthlySalary) || 0
}
// 解密工资结构
const decryptedBaseSalary = emp.baseSalary ? safeDecryptNum(emp.baseSalary) : null
const decryptedPerformanceSalary = emp.performanceSalary ? safeDecryptNum(emp.performanceSalary) : null
return {
id: emp.id,
@@ -279,6 +291,8 @@ export async function getEmployees(orgId: string, params: { page?: number; pageS
hireDate: emp.hireDate.toISOString().slice(0, 10),
status: emp.status,
monthlySalary: decryptedSalary,
baseSalary: decryptedBaseSalary,
performanceSalary: decryptedPerformanceSalary,
contractStatus: contractInfo.status,
contractStatusText: contractInfo.statusText,
riskLevel: contractInfo.riskLevel,
@@ -315,6 +329,8 @@ export async function getEmployeeDetail(orgId: string, id: string) {
return {
...employee,
monthlySalary: decryptedSalary,
baseSalary: employee.baseSalary ? safeDecryptNum(employee.baseSalary) : null,
performanceSalary: employee.performanceSalary ? safeDecryptNum(employee.performanceSalary) : null,
}
}
@@ -368,6 +384,9 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
const hireDate = new Date(data.hireDate)
const hireMonth = dateToMonth(hireDate)
const salaryNum = Number(data.monthlySalary) || 0
// 工资结构:基本工资 + 绩效工资 = 月薪总额
const baseSalaryNum = Number(data.baseSalary) || 0
const performanceSalaryNum = Number(data.performanceSalary) || 0
const city = data.city || '北京'
// 劳务协议/实习协议:不缴纳社保公积金,基数强制 0
const isNoSocialContract = data.contract && ['LABOR', 'INTERNSHIP'].includes(data.contract.contractType)
@@ -389,6 +408,8 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
department: data.department,
hireDate,
monthlySalary: encrypt(data.monthlySalary),
baseSalary: encrypt(String(baseSalaryNum)),
performanceSalary: encrypt(String(performanceSalaryNum)),
gender: data.gender,
femaleWorkerType: data.femaleWorkerType,
phone: data.phone,
@@ -488,6 +509,8 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
contractYears: data.contract.contractYears || 3,
probationMonths: data.contract.probationMonths || 0,
probationSalary: data.contract.probationSalary || 0,
baseSalary: baseSalaryNum,
performanceSalary: performanceSalaryNum,
createdBy: userId,
},
})
@@ -526,6 +549,10 @@ export async function rehireEmployee(orgId: string, userId: string, id: string,
const newHireMonth = dateToMonth(newHireDate)
const salaryNum = Number(decrypt(employee.monthlySalary)) || 0
// 重新入职时支持更新工资结构
const baseSalaryNum = data.baseSalary != null ? Number(data.baseSalary) : (employee.baseSalary ? Number(decrypt(employee.baseSalary)) || 0 : 0)
const performanceSalaryNum = data.performanceSalary != null ? Number(data.performanceSalary) : (employee.performanceSalary ? Number(decrypt(employee.performanceSalary)) || 0 : 0)
const newMonthlySalary = data.baseSalary != null ? baseSalaryNum + performanceSalaryNum : salaryNum
const city = data.city || employee.city || '北京'
const rawSocialInsBase = data.socialInsBase != null ? Number(data.socialInsBase) : salaryNum
const rawHousingFundBase = data.housingFundBase != null ? Number(data.housingFundBase) : salaryNum
@@ -575,6 +602,12 @@ export async function rehireEmployee(orgId: string, userId: string, id: string,
housingFundStartMonth,
housingFundEndMonth: null,
city: data.city || employee.city || '北京',
// 更新工资结构
...(data.baseSalary != null ? {
monthlySalary: encrypt(String(newMonthlySalary)),
baseSalary: encrypt(String(baseSalaryNum)),
performanceSalary: encrypt(String(performanceSalaryNum)),
} : {}),
},
})
@@ -657,6 +690,8 @@ export async function rehireEmployee(orgId: string, userId: string, id: string,
contractYears: data.contract.contractYears || 3,
probationMonths: data.contract.probationMonths || 0,
probationSalary: data.contract.probationSalary || 0,
baseSalary: baseSalaryNum,
performanceSalary: performanceSalaryNum,
createdBy: userId,
},
})
@@ -702,6 +737,13 @@ export async function updateEmployee(orgId: string, id: string, data: any) {
const oldSalary = Number(decrypt(employee.monthlySalary)) || 0
const newSalary = Number(data.monthlySalary) || 0
updateData.monthlySalary = encrypt(data.monthlySalary)
// 同步更新工资结构
if (data.baseSalary !== undefined) {
updateData.baseSalary = encrypt(String(Number(data.baseSalary) || 0))
}
if (data.performanceSalary !== undefined) {
updateData.performanceSalary = encrypt(String(Number(data.performanceSalary) || 0))
}
// 记录薪资变更
if (oldSalary !== newSalary) {
const now = new Date()
@@ -912,6 +954,8 @@ export async function batchRenew(orgId: string, userId: string, contractIds: str
contractYears: years,
probationMonths: 0,
probationSalary: 0,
baseSalary: contract.baseSalary || 0,
performanceSalary: contract.performanceSalary || 0,
renewalCount: contract.renewalCount + 1,
createdBy: userId,
},
@@ -963,6 +1007,8 @@ export async function addContract(orgId: string, userId: string, data: any) {
contractYears: data.contractYears || 3,
probationMonths: data.probationMonths || 0,
probationSalary: data.probationSalary || 0,
baseSalary: data.baseSalary || 0,
performanceSalary: data.performanceSalary || 0,
attachmentName: data.attachmentUrl ? '合同扫描件' : null,
attachmentUrl: data.attachmentUrl || null,
electronicContractNo: data.electronicContractNo || null,
+2
View File
@@ -638,6 +638,7 @@ export async function generatePayslipFromBatches(orgId: string, month: string) {
where: { employeeId_month: { employeeId, month } },
update: {
baseSalary: Math.round(summary.baseSalary * 100) / 100,
performanceSalary: Math.round(summary.performanceSalary * 100) / 100,
overtimePay: Math.round(summary.overtimePay * 100) / 100,
allowance: Math.round(summary.allowance * 100) / 100,
deduction: Math.round(summary.deduction * 100) / 100,
@@ -659,6 +660,7 @@ export async function generatePayslipFromBatches(orgId: string, month: string) {
employeeId,
month,
baseSalary: Math.round(summary.baseSalary * 100) / 100,
performanceSalary: Math.round(summary.performanceSalary * 100) / 100,
overtimePay: Math.round(summary.overtimePay * 100) / 100,
allowance: Math.round(summary.allowance * 100) / 100,
deduction: Math.round(summary.deduction * 100) / 100,
+6 -6
View File
@@ -18,7 +18,7 @@ export const documentTemplates: DocumentTemplate[] = [
name: '固定期限劳动合同',
category: 'CONTRACT',
description: '标准固定期限劳动合同模板,适用于大多数正式员工',
variables: ['companyName', 'employeeName', 'idCard', 'address', 'phone', 'startDate', 'endDate', 'position', 'workplace', 'probationMonths', 'monthlySalary', 'socialInsBase'],
variables: ['companyName', 'employeeName', 'idCard', 'address', 'phone', 'startDate', 'endDate', 'position', 'workplace', 'probationMonths', 'baseSalary', 'performanceSalary', 'probationSalary', 'socialInsBase'],
content: `劳动合同书
{{companyName}}
@@ -36,7 +36,7 @@ export const documentTemplates: DocumentTemplate[] = [
840
{{monthlySalary}}{{monthlySalary}}15
{{baseSalary}}/{{performanceSalary}}/{{probationSalary}}15
{{socialInsBase}}
@@ -64,7 +64,7 @@ export const documentTemplates: DocumentTemplate[] = [
name: '无固定期限劳动合同',
category: 'CONTRACT',
description: '无固定期限劳动合同模板,适用于符合签订条件的情况',
variables: ['companyName', 'employeeName', 'startDate', 'position', 'monthlySalary'],
variables: ['companyName', 'employeeName', 'startDate', 'position', 'baseSalary', 'performanceSalary'],
content: `无固定期限劳动合同书
{{companyName}}
@@ -79,7 +79,7 @@ export const documentTemplates: DocumentTemplate[] = [
{{position}}
{{monthlySalary}}
{{baseSalary}}/{{performanceSalary}}/
@@ -241,7 +241,7 @@ ____年__月__日
name: '试用期转正通知书',
category: 'NOTICE',
description: '试用期考核合格转正通知',
variables: ['employeeName', 'position', 'probationEndDate', 'regularDate', 'monthlySalary', 'companyName'],
variables: ['employeeName', 'position', 'probationEndDate', 'regularDate', 'baseSalary', 'performanceSalary', 'companyName'],
content: `试用期转正通知书
{{employeeName}}
@@ -249,7 +249,7 @@ ____年__月__日
{{regularDate}}{{position}}
{{monthlySalary}}
{{baseSalary}}/{{performanceSalary}}/
{{probationEndDate}}
+6 -2
View File
@@ -66,15 +66,19 @@ export async function executeWorkProcess(processId: string, type: string, formDa
return { employeeId }
}
case 'CONFIRM': {
const { employeeId, regularSalary } = formData
const { employeeId, regularSalary, baseSalary, performanceSalary } = formData
if (employeeId) {
if (regularSalary) {
const employee = await prisma.employee.findFirst({ where: { id: employeeId, orgId } })
const oldSalary = employee ? Number(decrypt(employee.monthlySalary)) || 0 : 0
const newSalary = Number(regularSalary) || 0
// 同步更新工资结构
const updateData: any = { monthlySalary: encrypt(String(regularSalary)) }
if (baseSalary !== undefined) updateData.baseSalary = encrypt(String(Number(baseSalary) || 0))
if (performanceSalary !== undefined) updateData.performanceSalary = encrypt(String(Number(performanceSalary) || 0))
await prisma.employee.update({
where: { id: employeeId },
data: { monthlySalary: encrypt(String(regularSalary)) },
data: updateData,
})
// 记录薪资变更(试用期薪资 → 转正薪资)
if (oldSalary !== newSalary) {