电子签基础功能

This commit is contained in:
fuenmao
2026-09-07 16:18:01 +08:00
parent 7e74fb3b4b
commit 802d9cde89
5 changed files with 1392 additions and 116 deletions
+166 -39
View File
@@ -4,8 +4,59 @@ import type { ReqId, ResPage } from "@/api/interface";
const taskHttp = moduleRequest("/biz/onboarding/task/");
const auditHttp = moduleRequest("/biz/onboarding/audit/");
const contractDocumentHttp = moduleRequest("/biz/onboarding/contract-document/");
const electronicSignHttp = moduleRequest("/biz/onboarding/electronic-sign/");
const fileHttp = moduleRequest("/sys/dev/file/");
/**
* 下载创建任务前的合同预览,并将统一响应中的 JSON 错误转换成可读异常。
*/
async function requestContractPreview(
action: string,
params: Record<string, unknown>,
fallbackMessage: string
) {
let response: any;
try {
response = await contractDocumentHttp.post<any>(
action,
params,
{ responseType: "blob", loading: false, cancel: false }
);
} catch (error: any) {
const errorBlob = error?.response?.data;
if (errorBlob instanceof Blob) {
try {
const result = JSON.parse(await errorBlob.text());
throw new Error(result?.msg || fallbackMessage);
} catch (parseError) {
if (!(parseError instanceof SyntaxError)) throw parseError;
}
}
throw error;
}
const blob = response?.data;
const contentType = String(
response?.headers?.["content-type"] || blob?.type || ""
);
if (
blob instanceof Blob &&
contentType.toLowerCase().includes("application/json")
) {
try {
const result = JSON.parse(await blob.text());
throw new Error(result?.msg || fallbackMessage);
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error(fallbackMessage);
}
throw error;
}
}
return response;
}
export interface OnboardingTask {
id: number;
taskNo: string;
@@ -59,6 +110,62 @@ export interface OnboardingPreparedContract {
generatedAt?: string;
}
/** 一次电子签流程中的单份合同文件。 */
export interface OnboardingElectronicSignDocument {
id: number;
taskContractId: number;
providerDocumentId?: string;
documentName: string;
documentSort: number;
originalDocxFileId?: number;
signedPdfFileId?: number;
}
/** 管理端查看的入职电子签流程详情。 */
export interface OnboardingElectronicSignDetail {
taskId: number;
taskNo: string;
canInitiate: boolean;
cannotInitiateReason?: string;
canReinitiate: boolean;
cannotReinitiateReason?: string;
hasProcess: boolean;
processId?: number;
providerContractId?: string;
bizId?: string;
subject?: string;
signStatus?: string;
providerStatus?: string;
employeeName?: string;
employeeMobile?: string;
signUrl?: string;
signUrlExpireAt?: string;
signUrlValid: boolean;
startedAt?: string;
sentAt?: string;
completedAt?: string;
archivedAt?: string;
archiveFileId?: number;
lastError?: string;
documents: OnboardingElectronicSignDocument[];
historyProcesses: OnboardingElectronicSignHistory[];
}
/** 已被新流程替代的电子签历史记录。 */
export interface OnboardingElectronicSignHistory {
processId: number;
bizId: string;
providerContractId?: string;
signStatus?: string;
providerStatus?: string;
employeeName?: string;
employeeMobile?: string;
startedAt?: string;
reissuedAt?: string;
reissueReason?: string;
lastError?: string;
}
export interface OnboardingCompanyOption {
id: number;
companyName: string;
@@ -282,48 +389,22 @@ export const onboardingTaskApi = {
return response;
},
/** 创建任务前生成并下载单份临时 DOCX 合同预览。 */
/** 创建任务前生成并下载指定的一份临时 DOCX 合同预览。 */
async previewContractBeforeCreate(params: Record<string, unknown>) {
let response: any;
try {
response = await contractDocumentHttp.post<any>(
"preview-before-create",
params,
{ responseType: "blob", loading: false, cancel: false }
);
} catch (error: any) {
const errorBlob = error?.response?.data;
if (errorBlob instanceof Blob) {
try {
const result = JSON.parse(await errorBlob.text());
throw new Error(result?.msg || "合同预览生成失败");
} catch (parseError) {
if (!(parseError instanceof SyntaxError)) throw parseError;
}
}
throw error;
}
const blob = response?.data;
const contentType = String(
response?.headers?.["content-type"] || blob?.type || ""
return requestContractPreview(
"preview-before-create",
params,
"合同预览生成失败"
);
if (
blob instanceof Blob &&
contentType.toLowerCase().includes("application/json")
) {
try {
const result = JSON.parse(await blob.text());
throw new Error(result?.msg || "合同预览生成失败");
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error("合同预览生成失败");
}
throw error;
}
}
},
return response;
/** 创建任务前生成全部临时 DOCX 合同预览,并以 ZIP 下载。 */
async previewAllContractsBeforeCreate(params: Record<string, unknown>) {
return requestContractPreview(
"preview-all-before-create",
params,
"合同预览打包生成失败"
);
},
create(params: Record<string, unknown>) {
@@ -359,6 +440,52 @@ export const onboardingTaskApi = {
);
},
/** 查询任务当前电子签流程和全部合同文件。 */
electronicSignDetail(taskId: number) {
return electronicSignHttp.get<OnboardingElectronicSignDetail>("detail", {
id: taskId
});
},
/** 手动发起;失败流程会从已保存的本地断点继续。 */
initiateElectronicSign(taskId: number) {
return electronicSignHttp.post<OnboardingElectronicSignDetail>("initiate", {
taskId
});
},
/** 保留旧流程记录,并以新的业务编号重新发起一次签约。 */
reinitiateElectronicSign(taskId: number, reason: string) {
return electronicSignHttp.post<OnboardingElectronicSignDetail>("reinitiate", {
taskId,
reason
});
},
/** 主动同步签署状态,完成时后端自动执行归档。 */
syncElectronicSign(taskId: number) {
return electronicSignHttp.post<OnboardingElectronicSignDetail>("sync", {
taskId
});
},
/** 获取有效的员工签署链接,旧链接失效时由后端重新生成。 */
getElectronicSignLink(taskId: number) {
return electronicSignHttp.post<OnboardingElectronicSignDetail>("get-link", {
taskId
});
},
/** 催办当前待签员工。 */
noticeElectronicSign(taskId: number) {
return electronicSignHttp.post("notice", { taskId });
},
/** 作废尚未完成的电子签流程。 */
invalidElectronicSign(taskId: number, reason: string) {
return electronicSignHttp.post("invalid", { taskId, reason });
},
archive(params: {
taskId: number;
auditOpinion?: string;