feat: 统一页面布局与字体样式

This commit is contained in:
selfrelease
2026-07-24 17:30:36 +08:00
parent 4ae24990a7
commit a28b065ab8
25 changed files with 2032 additions and 259 deletions
@@ -0,0 +1,31 @@
/*
Warnings:
- Added the required column `updatedAt` to the `TerminationRecord` table without a default value. This is not possible if the table is not empty.
*/
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "ContractType" ADD VALUE 'LABOR';
ALTER TYPE "ContractType" ADD VALUE 'INTERNSHIP';
-- AlterTable
ALTER TABLE "TerminationRecord" ADD COLUMN "approvalComment" TEXT,
ADD COLUMN "approvedAt" TIMESTAMP(3),
ADD COLUMN "approvedBy" TEXT,
ADD COLUMN "checklistOverrides" JSONB,
ADD COLUMN "compensationBreakdown" JSONB,
ADD COLUMN "currentStep" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "handoverItems" JSONB,
ADD COLUMN "status" TEXT NOT NULL DEFAULT 'DRAFT',
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
ADD COLUMN "updatedBy" TEXT;
-- CreateIndex
CREATE INDEX "TerminationRecord_orgId_status_idx" ON "TerminationRecord"("orgId", "status");
+2
View File
@@ -30,6 +30,8 @@ enum ContractType {
FIXED
UNFIXED
UNSIGNED
LABOR
INTERNSHIP
}
enum SignMethod {
+9 -1
View File
@@ -9,7 +9,15 @@ import { apiLimiter } from './middleware/rateLimit'
const app = express()
app.use(helmet())
app.use(compression())
app.use(compression({
filter: (req, res) => {
// SSE 流式响应不压缩,避免缓冲导致前端一次性收到所有数据
if (req.headers['accept'] === 'text/event-stream' || res.getHeader('Content-Type') === 'text/event-stream') {
return false
}
return compression.filter(req, res)
},
}))
app.use(
cors({
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
+3 -3
View File
@@ -1,7 +1,7 @@
import app from './app'
const PORT = process.env.PORT || 3000
const PORT = Number(process.env.PORT) || 3000
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
app.listen(PORT, '::', () => {
console.log(`Server running on http://[::]:${PORT}`)
})
+6
View File
@@ -109,12 +109,18 @@ router.post('/chat-stream', authMiddleware, async (req: AuthRequest, res, next)
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
res.setHeader('X-Accel-Buffering', 'no')
res.flushHeaders()
let usageRecorded = false
try {
for await (const delta of chatStream(messages, orgContext)) {
res.write(`data: ${JSON.stringify({ delta })}\n\n`)
if (typeof (res as any).flush === 'function') (res as any).flush()
}
res.write('data: [DONE]\n\n')
} catch (streamErr: any) {
res.write(`data: ${JSON.stringify({ error: streamErr.message || 'AI 服务异常' })}\n\n`)
res.write('data: [DONE]\n\n')
} finally {
if (!usageRecorded) {
await recordUsage(req.user!.orgId, req.user!.id, 'chat')
+13 -1
View File
@@ -205,7 +205,7 @@ router.get('/:id/evidence-chain', authMiddleware, async (req: AuthRequest, res,
employee.contracts.forEach((c) => {
evidence.push({
category: '劳动关系',
title: `劳动合同(${c.contractType === 'FIXED' ? '固定期限' : c.contractType === 'UNFIXED' ? '无固定期限' : '未签订'}`,
title: `合同(${({ FIXED: '固定期限', UNFIXED: '固定期限', LABOR: '劳务协议', INTERNSHIP: '实习协议', UNSIGNED: '未签订' } as Record<string, string>)[c.contractType] || '未签订'}`,
date: c.signDate ? c.signDate.toISOString().slice(0, 10) : c.startDate.toISOString().slice(0, 10),
description: `合同期限:${c.startDate.toISOString().slice(0, 10)}${c.endDate ? c.endDate.toISOString().slice(0, 10) : '无固定期限'},试用期${c.probationMonths}个月,试用期工资¥${c.probationSalary}`,
evidenceType: 'CONTRACT',
@@ -788,4 +788,16 @@ router.get('/contracts/expiring', authMiddleware, async (req: AuthRequest, res,
} catch (err) { next(err) }
})
// 合同类型列表(供前端动态获取)
router.get('/contract-types', authMiddleware, (_req: AuthRequest, res) => {
const types = [
{ value: 'FIXED', label: '劳动合同-固定期', hasEndDate: true },
{ value: 'UNFIXED', label: '劳动合同-无固定期', hasEndDate: false },
{ value: 'LABOR', label: '劳务协议', hasEndDate: false },
{ value: 'INTERNSHIP', label: '实习协议', hasEndDate: false },
{ value: 'UNSIGNED', label: '未签合同', hasEndDate: false },
]
res.json({ success: true, data: types })
})
export default router
+8 -1
View File
@@ -26,7 +26,14 @@ export function getContractStatus(contract: {
hireDate: Date
}): { status: string; statusText: string; riskLevel: 'high' | 'medium' | 'low' | 'safe' } {
const today = new Date()
const typeLabel = contract.contractType === 'FIXED' ? '固定期限' : contract.contractType === 'UNFIXED' ? '无固定期限' : ''
const typeLabelMap: Record<string, string> = {
FIXED: '固定期限',
UNFIXED: '无固定期限',
LABOR: '劳务协议',
INTERNSHIP: '实习协议',
UNSIGNED: '',
}
const typeLabel = typeLabelMap[contract.contractType] || ''
if (!contract.signDate || contract.contractType === 'UNSIGNED') {
const days = daysBetween(today, contract.hireDate)