feat: 电子签署集成到合同管理和离职流程

- 合同管理:选择电子签署时保存后自动创建ESignRecord并关联合同ID
- 离职管理:工作交接清单「离职文件签署」步骤增加「发起电子签署」按钮
- 电子签署页面:列表增加来源标识(劳动合同/离职协议)
- 合同卡片:电子签署合同显示提示而非手动填写编号/链接
This commit is contained in:
freedakgmail
2026-08-05 07:34:01 +08:00
parent cce0c936bd
commit b9240ffe9b
3 changed files with 64 additions and 21 deletions
+35 -19
View File
@@ -1,12 +1,12 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { employeeApi } from '../../lib/api-services'
import { employeeApi, esignApi } from '../../lib/api-services'
import { useConfirm } from '../../hooks/useConfirm'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
import { FileText, AlertTriangle, X, Paperclip, Trash2, Info, Download } from "lucide-react"
import { FileText, AlertTriangle, X, Paperclip, Trash2, Info, Download, PenTool } from "lucide-react"
export default function ContractInfo({ employeeId, contracts, hireDate }: { employeeId: string; contracts: any[]; hireDate: string }) {
const queryClient = useQueryClient()
@@ -17,8 +17,27 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
const [previewUrl, setPreviewUrl] = useState<string | null>(null)
const addContractMutation = useMutation({
mutationFn: (data: any) => employeeApi.addContract({ ...data, employeeId }),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
mutationFn: async (data: any) => {
const res = await employeeApi.addContract({ ...data, employeeId }) as any
const contractId = res?.data?.id
if (data.signMethod === 'ELECTRONIC' && contractId) {
try {
await esignApi.create({
contractId,
employeeId,
documentTitle: `${data.contractType === 'UNFIXED' ? '无固定期限' : '固定期限'}劳动合同`,
remark: '合同创建时自动发起',
})
toast.success('合同已保存,电子签署记录已创建')
} catch {
toast.success('合同已保存,电子签署记录创建失败(可稍后手动发起)')
}
} else {
toast.success('合同已保存')
}
return res
},
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); queryClient.invalidateQueries({ queryKey: ['esign-records'] }); setShowForm(false) },
})
const deleteContractMutation = useMutation({
@@ -193,10 +212,12 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
</div>
)}
{form.signMethod === 'ELECTRONIC' && (
<>
<div><Label> *</Label><Input value={form.electronicContractNo} onChange={(e) => setForm({ ...form, electronicContractNo: e.target.value })} placeholder="如 E-2026-001" /></div>
<div><Label> *</Label><Input value={form.electronicContractUrl} onChange={(e) => setForm({ ...form, electronicContractUrl: e.target.value })} placeholder="https://..." /></div>
</>
<div className="md:col-span-2">
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs flex items-start gap-2">
<PenTool className="w-4 h-4 mt-0.5 shrink-0" />
<span></span>
</div>
</div>
)}
<div className="md:col-span-2 flex gap-2">
<Button onClick={() => {
@@ -267,17 +288,12 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
</div>
)}
{c.signMethod === 'ELECTRONIC' && (
<>
{c.electronicContractNo && <div className="flex justify-between"><span className="text-gray-500"></span><span className="font-medium">{c.electronicContractNo}</span></div>}
{c.electronicContractUrl && (
<div className="flex justify-between md:col-span-3">
<span className="text-gray-500"></span>
<a href={c.electronicContractUrl} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline flex items-center gap-1">
<FileText className="w-3 h-3" />
</a>
</div>
)}
</>
<div className="flex justify-between md:col-span-3">
<span className="text-gray-500"></span>
<span className="text-primary flex items-center gap-1 text-xs">
<PenTool className="w-3 h-3" />
</span>
</div>
)}
</div>
<button