feat: 分页组件、Dashboard待办图标、归档与工资条解耦、总览数据优化

- 新增公用 Pagination 组件,Roster/Money/Dashboard 列表加分页
- Dashboard 待办按类型显示不同图标(合同/薪资/解聘/月度)
- 待办分为「风险提醒」「月度任务」两个顶层 tab
- 归档与工资条生成解耦:归档只锁定批次,工资条单独生成
- 工资条管理新增「从批次汇总生成」按钮
- Dashboard 总览优先从已归档批次 BatchEntry 汇总数据
- 新增工资条生成待办提醒,生成后自动标记完成
- 修复高风险统计只含 CONTRACT/TERMINATION 类型
- 修复月度任务去重逻辑覆盖 SALARY 类型
This commit is contained in:
freedakgmail
2026-07-23 13:42:28 +08:00
parent 820579e98d
commit 2a09d31ccc
20 changed files with 2300 additions and 235 deletions
@@ -5,6 +5,9 @@ import api from '../../lib/api'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
// 金额格式化:保留两位小数 + 千分位
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
export default function ContractConfirm() {
const [params] = useSearchParams()
const token = params.get('token') || ''
@@ -83,7 +86,7 @@ export default function ContractConfirm() {
<Row label="合同开始" value={new Date(data.contract.startDate).toISOString().slice(0, 10)} />
{data.contract.endDate && <Row label="合同结束" value={new Date(data.contract.endDate).toISOString().slice(0, 10)} />}
{data.contract.probationMonths > 0 && <Row label="试用期" value={`${data.contract.probationMonths}个月`} />}
{data.contract.probationSalary > 0 && <Row label="试用期工资" value={`¥${Number(data.contract.probationSalary).toLocaleString()}`} />}
{data.contract.probationSalary > 0 && <Row label="试用期工资" value={`¥${fmt(Number(data.contract.probationSalary))}`} />}
</div>
)}
+4 -1
View File
@@ -5,6 +5,9 @@ import api from '../../lib/api'
import Card from '../../components/ui/Card'
import EmptyState from '../../components/ui/EmptyState'
// 金额格式化:保留两位小数 + 千分位
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const portalApi = api.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
@@ -65,7 +68,7 @@ export default function MyContract() {
{contract.endDate && <Row label="合同结束" value={new Date(contract.endDate).toISOString().slice(0, 10)} />}
{contract.contractYears > 0 && <Row label="合同期限" value={`${contract.contractYears}`} />}
{contract.probationMonths > 0 && <Row label="试用期" value={`${contract.probationMonths}个月`} />}
{contract.probationSalary > 0 && <Row label="试用期工资" value={`¥${Number(contract.probationSalary).toLocaleString()}`} />}
{contract.probationSalary > 0 && <Row label="试用期工资" value={`¥${fmt(Number(contract.probationSalary))}`} />}
</div>
{/* 签署确认记录 */}
+8 -5
View File
@@ -7,6 +7,9 @@ import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import EmptyState from '../../components/ui/EmptyState'
// 金额格式化:保留两位小数 + 千分位
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const portalApi = api.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
@@ -65,32 +68,32 @@ export default function Payslip() {
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">¥{Number(data.baseSalary).toLocaleString()}</span>
<span className="font-medium">¥{fmt(Number(data.baseSalary))}</span>
</div>
{data.overtimePay > 0 && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">¥{Number(data.overtimePay).toLocaleString()}</span>
<span className="font-medium">¥{fmt(Number(data.overtimePay))}</span>
</div>
</div>
)}
{data.allowance > 0 && (
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">¥{Number(data.allowance).toLocaleString()}</span>
<span className="font-medium">¥{fmt(Number(data.allowance))}</span>
</div>
)}
{data.deduction > 0 && (
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium text-danger">-¥{Number(data.deduction).toLocaleString()}</span>
<span className="font-medium text-danger">-¥{fmt(Number(data.deduction))}</span>
</div>
)}
<div className="border-t pt-3">
<div className="flex justify-between">
<span className="font-medium"></span>
<span className="text-xl font-bold text-primary">¥{Number(data.totalPay).toLocaleString()}</span>
<span className="text-xl font-bold text-primary">¥{fmt(Number(data.totalPay))}</span>
</div>
</div>