From 802d9cde8957df5628e978b705087c3a128febd0 Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Mon, 7 Sep 2026 16:18:01 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=B5=E5=AD=90=E7=AD=BE=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 2 + src/api/modules/biz/onboarding.ts | 205 ++++- src/views/biz/onboarding/employee/index.vue | 381 ++++++++- src/views/biz/onboarding/task/index.vue | 882 ++++++++++++++++++-- src/views/onboarding/fill/index.vue | 38 + 5 files changed, 1392 insertions(+), 116 deletions(-) diff --git a/components.d.ts b/components.d.ts index 0fa6c69..1cff276 100644 --- a/components.d.ts +++ b/components.d.ts @@ -29,6 +29,8 @@ declare module 'vue' { ElCascader: typeof import('element-plus/es')['ElCascader'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCol: typeof import('element-plus/es')['ElCol'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] diff --git a/src/api/modules/biz/onboarding.ts b/src/api/modules/biz/onboarding.ts index e69608d..a2fa423 100644 --- a/src/api/modules/biz/onboarding.ts +++ b/src/api/modules/biz/onboarding.ts @@ -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, + fallbackMessage: string +) { + let response: any; + try { + response = await contractDocumentHttp.post( + 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) { - let response: any; - try { - response = await contractDocumentHttp.post( - "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) { + return requestContractPreview( + "preview-all-before-create", + params, + "合同预览打包生成失败" + ); }, create(params: Record) { @@ -359,6 +440,52 @@ export const onboardingTaskApi = { ); }, + /** 查询任务当前电子签流程和全部合同文件。 */ + electronicSignDetail(taskId: number) { + return electronicSignHttp.get("detail", { + id: taskId + }); + }, + + /** 手动发起;失败流程会从已保存的本地断点继续。 */ + initiateElectronicSign(taskId: number) { + return electronicSignHttp.post("initiate", { + taskId + }); + }, + + /** 保留旧流程记录,并以新的业务编号重新发起一次签约。 */ + reinitiateElectronicSign(taskId: number, reason: string) { + return electronicSignHttp.post("reinitiate", { + taskId, + reason + }); + }, + + /** 主动同步签署状态,完成时后端自动执行归档。 */ + syncElectronicSign(taskId: number) { + return electronicSignHttp.post("sync", { + taskId + }); + }, + + /** 获取有效的员工签署链接,旧链接失效时由后端重新生成。 */ + getElectronicSignLink(taskId: number) { + return electronicSignHttp.post("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; diff --git a/src/views/biz/onboarding/employee/index.vue b/src/views/biz/onboarding/employee/index.vue index 025bacf..546a774 100644 --- a/src/views/biz/onboarding/employee/index.vue +++ b/src/views/biz/onboarding/employee/index.vue @@ -152,38 +152,116 @@ - - - -
-
-
-
{{ contract.contractName }}
-
- 状态:{{ contract.contractStatus }} - · 线下签署于:{{ formatTime(contract.offlineSignedAt) }} + + + + + + +
+
+
+ +
+ + {{ materialPreviewText(item) }} +
+
+ +
+
+ {{ item.materialName || "员工资料" }} + + {{ materialReviewText(item.reviewStatus) }} + +
+
{{ materialFileName(item) }}
+
+ {{ item.fileSizeInfo || "大小未知" }} + 上传于 {{ formatTime(item.uploadedAt) }} +
+
审核备注:{{ item.reviewRemark }}
+
+ + {{ materialPreviewUrls[item.fileId] ? "点击图片可放大预览" : materialPreviewText(item) }} + + 下载原件 +
-
+
+ + + + + +
+
+
+
{{ contract.contractName }}
+
+ 状态:{{ contractStatusText(contract.contractStatus) }} + · 线下签署于:{{ formatTime(contract.offlineSignedAt) }} +
+
+
+ + 下载签后 PDF + + + 下载生成 DOCX + + 尚未生成电子文件 +
-
+
@@ -213,8 +291,8 @@ diff --git a/src/views/onboarding/fill/index.vue b/src/views/onboarding/fill/index.vue index 2f1a5e4..133cfe4 100644 --- a/src/views/onboarding/fill/index.vue +++ b/src/views/onboarding/fill/index.vue @@ -1976,18 +1976,25 @@ onBeforeUnmount(() => { padding: 18px; background: #f8fafc; margin-bottom: 16px; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .contract-meta { display: flex; align-items: center; + flex-wrap: wrap; gap: 10px; margin-bottom: 8px; + min-width: 0; } .contract-title { font-size: 16px; color: #0f172a; + min-width: 0; + overflow-wrap: anywhere; } .contract-desc { @@ -2012,10 +2019,32 @@ onBeforeUnmount(() => { gap: 12px; border-top: 1px dashed #cbd5e1; padding-top: 14px; + min-width: 0; + max-width: 100%; } .agree-checkbox { font-weight: 600; + min-width: 0; + max-width: 100%; + height: auto; + margin-right: 0; + white-space: normal; +} + +.agree-checkbox :deep(.el-checkbox__input) { + flex: 0 0 auto; + margin-top: 3px; +} + +.agree-checkbox :deep(.el-checkbox__label) { + min-width: 0; + max-width: 100%; + padding-left: 8px; + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; + line-height: 1.5; } .contract-view-hint { @@ -2170,5 +2199,14 @@ onBeforeUnmount(() => { flex-direction: column; align-items: stretch; } + .contract-sign-card { + padding: 14px; + } + .contract-card-bottom > .el-button, + .contract-card-bottom > .agree-checkbox { + width: 100%; + max-width: 100%; + box-sizing: border-box; + } }