From ec7e889edc055533a5f67af72c6815cbf34d53ca Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Wed, 26 Aug 2026 18:17:36 +0800 Subject: [PATCH] 11 --- src/api/modules/biz/onboarding.ts | 16 ++- src/views/onboarding/fill/index.vue | 154 +++++++++++++++++++++++++++- 2 files changed, 163 insertions(+), 7 deletions(-) diff --git a/src/api/modules/biz/onboarding.ts b/src/api/modules/biz/onboarding.ts index cedf589..2b852d0 100644 --- a/src/api/modules/biz/onboarding.ts +++ b/src/api/modules/biz/onboarding.ts @@ -21,6 +21,14 @@ export interface OnboardingTask { createTime: string; } +/** 员工端完成合同准备后返回的当前文件版本。 */ +export interface OnboardingPreparedContract { + /** 当前生成合同对应的系统文件 Id,同时作为下载缓存版本。 */ + fileId: number; + /** 当前合同文件生成时间。 */ + generatedAt?: string; +} + export interface OnboardingCompanyOption { id: number; companyName: string; @@ -556,8 +564,12 @@ export const onboardingPublicApi = { submit(params: { token: string }) { return publicHttp.post("submit", params); }, - contractSourceUrl(params: { token: string; taskContractId: number }) { - return `/api/open/onboarding/contract-source?token=${encodeURIComponent(params.token)}&taskContractId=${params.taskContractId}`; + prepareContract(params: { token: string; taskContractId: number }) { + return publicHttp.post("prepare-contract", params); + }, + contractSourceUrl(params: { token: string; taskContractId: number; fileVersion?: number }) { + const version = params.fileVersion ? `&version=${params.fileVersion}` : `&_t=${Date.now()}`; + return `/api/open/onboarding/contract-source?token=${encodeURIComponent(params.token)}&taskContractId=${params.taskContractId}${version}`; }, materialSourceUrl(params: { token: string; taskMaterialId: number; fileId?: number }) { const version = params.fileId ? `&v=${params.fileId}` : ""; diff --git a/src/views/onboarding/fill/index.vue b/src/views/onboarding/fill/index.vue index e853397..b633b66 100644 --- a/src/views/onboarding/fill/index.vue +++ b/src/views/onboarding/fill/index.vue @@ -572,6 +572,16 @@

系统已为您代入真实登记信息生成专属合同,请认真查阅条款并勾选确认

+ +
@@ -593,20 +603,32 @@
+ +
- 下载并查看合同 + {{ getContractActionText(c.id) }} ✓ 候选人已确认同意 @@ -662,6 +684,9 @@ const submitting = ref(false); const error = ref(""); const currentStep = ref(0); const uploadingMaterials = reactive>({}); +type ContractPrepareStatus = "idle" | "preparing" | "ready" | "error"; +const contractPrepareStatus = reactive>({}); +const contractPrepareErrors = reactive>({}); const task = ref(null); const contracts = ref([]); @@ -727,6 +752,22 @@ const canEdit = computed(() => { return task.value?.taskStatus === "pending_fill" || task.value?.taskStatus === "rejected"; }); +/** 当前正在校验变量并生成文件的合同。 */ +const preparingContract = computed(() => { + return contracts.value.find(contract => isContractPreparing(contract.id)); +}); + +/** 合同准备提示,明确告诉员工当前进度及正在处理的合同。 */ +const contractPreparingDescription = computed(() => { + const contract = preparingContract.value; + if (!contract) { + return ""; + } + + const index = contracts.value.findIndex(item => item.id === contract.id); + return `正在校验合同信息并生成第 ${index + 1}/${contracts.value.length} 份《${contract.contractName}》,请稍候,不要关闭页面。`; +}); + // 计算差集增量变量 (合同需要的 employee. 变量中,登记表没有的字段) const registrationStandardFields = [ "employeename", "name", "username", "gender", "nation", "birthday", "registeredresidence", @@ -858,6 +899,11 @@ async function load(silent = false) { ...c, isAgreed: c.contractStatus === "confirmed" })); + contracts.value.forEach((contract: any) => { + const key = String(contract.id); + contractPrepareStatus[key] = contract.generatedDocxFileId ? "ready" : "idle"; + contractPrepareErrors[key] = ""; + }); materialRequirements.value = data.materialRequirements || []; uploadedMaterials.value = data.materials || []; contractVariables.value = data.contractVariables || []; @@ -919,6 +965,9 @@ async function nextStep() { } currentStep.value++; window.scrollTo({ top: 0, behavior: "smooth" }); + if (currentStep.value === 3) { + await prepareContracts(); + } } function getMaterialUploadUrl(materialTypeId: number, taskMaterialId?: number) { @@ -1011,10 +1060,92 @@ async function isMessageBoxConfirmed(confirmPromise: Promise) { } } +function getContractPrepareStatus(taskContractId: number) { + return contractPrepareStatus[String(taskContractId)] || "idle"; +} + +function getContractPrepareError(taskContractId: number) { + return contractPrepareErrors[String(taskContractId)] || ""; +} + +function isContractPreparing(taskContractId: number) { + return getContractPrepareStatus(taskContractId) === "preparing"; +} + +function isContractPrepared(taskContractId: number) { + return getContractPrepareStatus(taskContractId) === "ready"; +} + +function isContractPrepareFailed(taskContractId: number) { + return getContractPrepareStatus(taskContractId) === "error"; +} + +function getContractActionText(taskContractId: number) { + const status = getContractPrepareStatus(taskContractId); + if (status === "preparing") { + return "正在准备合同"; + } + if (status === "error") { + return "重新准备合同"; + } + if (status === "ready") { + return "下载并查看合同"; + } + return "等待准备合同"; +} + +/** + * 提前校验变量并生成合同,让业务异常能够在当前页面直接展示。 + */ +async function prepareContract(contract: any) { + const key = String(contract.id); + contractPrepareStatus[key] = "preparing"; + contractPrepareErrors[key] = ""; + + try { + const response = await onboardingPublicApi.prepareContract({ + token: token.value, + taskContractId: contract.id + }); + // 使用后端返回的新文件 Id 作为下载版本,避免手机浏览器复用旧合同缓存。 + contract.generatedDocxFileId = response.data.fileId; + contract.generatedAt = response.data.generatedAt; + contractPrepareStatus[key] = "ready"; + return true; + } catch (err: any) { + const message = err?.message || "合同准备失败,请稍后重试"; + contractPrepareStatus[key] = "error"; + contractPrepareErrors[key] = message; + ElMessage.error(`${contract.contractName}:${message}`); + return false; + } +} + +/** + * 进入合同确认步骤时逐份准备,避免同时生成多个大文件造成服务端瞬时压力。 + */ +async function prepareContracts() { + for (const contract of contracts.value) { + await prepareContract(contract); + } +} + +async function handleContractAction(contract: any) { + if (isContractPrepareFailed(contract.id)) { + await prepareContract(contract); + return; + } + + if (isContractPrepared(contract.id)) { + previewAndDownloadContract(contract); + } +} + function previewAndDownloadContract(c: any) { const url = onboardingPublicApi.contractSourceUrl({ token: token.value, - taskContractId: c.id + taskContractId: c.id, + fileVersion: c.generatedDocxFileId }); // 下载必须直接发生在用户点击事件中,避免手机浏览器拦截异步打开或二次跳转。 @@ -1025,6 +1156,11 @@ function previewAndDownloadContract(c: any) { } async function submitAll() { + const unprepared = contracts.value.filter(c => !isContractPrepared(c.id)); + if (unprepared.length) { + return ElMessage.warning(`请先等待合同准备完成:${unprepared.map(c => c.contractName).join("、")}`); + } + const unconfirmed = contracts.value.filter(c => !c.isAgreed); if (unconfirmed.length) { return ElMessage.warning(`请先勾选同意全部合同(未勾选:${unconfirmed.map(c => c.contractName).join("、")})`); @@ -1413,6 +1549,14 @@ onBeforeUnmount(() => { margin-bottom: 14px; } +.contract-prepare-error { + margin-bottom: 14px; +} + +.contract-preparing-alert { + margin-bottom: 16px; +} + .contract-card-bottom { display: flex; align-items: center;