0df8aa77d9
- 员工花名册管理(加密存储、导入导出) - 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条) - 社保公积金(多城市配置、版本管理、基数调整) - 解聘管理(6步流程、证据链、工作交接) - AI 助手(合同审查、风险预测、RAG 知识库) - Dashboard 仪表盘 - 设置与通知
162 lines
6.4 KiB
TypeScript
162 lines
6.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import { useSearchParams } from 'react-router-dom'
|
||
import { PenTool, Check, AlertCircle } from 'lucide-react'
|
||
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') || ''
|
||
const [data, setData] = useState<any>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState('')
|
||
const [agreed, setAgreed] = useState(false)
|
||
const [submitting, setSubmitting] = useState(false)
|
||
const [confirmed, setConfirmed] = useState(false)
|
||
const [verifyCode, setVerifyCode] = useState('')
|
||
const [sendingCode, setSendingCode] = useState(false)
|
||
const [codeSent, setCodeSent] = useState(false)
|
||
const [devCode, setDevCode] = useState('')
|
||
|
||
useEffect(() => {
|
||
if (token) {
|
||
api.get(`/portal/contract-confirm/${token}`).then((res: any) => {
|
||
setData(res.data)
|
||
}).catch((err: any) => {
|
||
setError(err.response?.data?.error?.message || '链接无效或已过期')
|
||
}).finally(() => setLoading(false))
|
||
} else {
|
||
setError('缺少 token 参数')
|
||
setLoading(false)
|
||
}
|
||
}, [token])
|
||
|
||
const handleSendCode = async () => {
|
||
setSendingCode(true)
|
||
setError('')
|
||
try {
|
||
const res = await api.post('/portal/contract-confirm/send-code', { token }) as any
|
||
setCodeSent(true)
|
||
setDevCode(res.data?.data?.code || '')
|
||
} catch (err: any) {
|
||
setError(err.response?.data?.error?.message || '验证码发送失败')
|
||
} finally {
|
||
setSendingCode(false)
|
||
}
|
||
}
|
||
|
||
const handleConfirm = async () => {
|
||
setSubmitting(true)
|
||
try {
|
||
await api.post('/portal/contract-confirm', { token, agreed: true, verifyCode })
|
||
setConfirmed(true)
|
||
} catch (err: any) {
|
||
setError(err.response?.data?.error?.message || '确认失败')
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
if (confirmed) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||
<div className="max-w-sm w-full text-center">
|
||
<Check className="w-16 h-16 text-safe mx-auto mb-4" />
|
||
<h1 className="text-sm font-semibold mb-2">合同签署确认成功</h1>
|
||
<p className="text-sm text-gray-500">已记录您的签署确认时间和 IP 地址。</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50 px-4 py-6">
|
||
<div className="max-w-md mx-auto">
|
||
<div className="flex items-center gap-2 mb-6">
|
||
<PenTool className="w-6 h-6 text-primary" />
|
||
<h1 className="text-sm font-semibold">合同签署确认</h1>
|
||
</div>
|
||
|
||
{loading ? (
|
||
<div className="text-center py-8 text-gray-400">加载中...</div>
|
||
) : error ? (
|
||
<Card>
|
||
<div className="flex items-center gap-2 text-danger">
|
||
<AlertCircle className="w-5 h-5" />
|
||
<span>{error}</span>
|
||
</div>
|
||
</Card>
|
||
) : data ? (
|
||
<Card>
|
||
<div className="space-y-3">
|
||
<div className="text-sm text-gray-600">
|
||
{data.orgName} 与 {data.employeeName} 的劳动合同
|
||
</div>
|
||
|
||
{data.contract && (
|
||
<div className="space-y-2 text-sm">
|
||
<Row label="合同类型" value={data.contract.contractType === 'FIXED' ? '固定期限' : data.contract.contractType === 'UNFIXED' ? '无固定期限' : '未签订'} />
|
||
{data.contract.contractYears > 0 && <Row label="合同期限" value={`${data.contract.contractYears}年`} />}
|
||
<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={`¥${fmt(Number(data.contract.probationSalary))}`} />}
|
||
</div>
|
||
)}
|
||
|
||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||
<input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
|
||
我已阅读合同内容,确认签署
|
||
</label>
|
||
|
||
{/* 验证码区域 */}
|
||
{agreed && (
|
||
<div className="space-y-2">
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
value={verifyCode}
|
||
onChange={(e) => setVerifyCode(e.target.value)}
|
||
placeholder="请输入6位验证码"
|
||
maxLength={6}
|
||
className="flex-1 px-3 py-2 rounded-md border border-gray-300 text-sm"
|
||
/>
|
||
<button
|
||
onClick={handleSendCode}
|
||
disabled={sendingCode || codeSent}
|
||
className="px-3 py-2 rounded-md bg-gray-100 text-xs font-medium disabled:opacity-50 whitespace-nowrap"
|
||
>
|
||
{sendingCode ? '发送中' : codeSent ? '已发送' : '发送验证码'}
|
||
</button>
|
||
</div>
|
||
{devCode && (
|
||
<div className="text-xs text-blue-500">开发模式验证码:{devCode}</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<Button className="w-full" onClick={handleConfirm} disabled={!agreed || submitting || !verifyCode}>
|
||
{submitting ? '确认中...' : '确认签署'}
|
||
</Button>
|
||
<div className="text-xs text-gray-400 text-center">📌 确认后将记录签署时间、IP 地址和设备信息</div>
|
||
</div>
|
||
</Card>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function Row({ label, value }: { label: string; value: string }) {
|
||
return (
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-500">{label}</span>
|
||
<span className="font-medium">{value}</span>
|
||
</div>
|
||
)
|
||
}
|