This commit is contained in:
fuenmao
2026-08-26 18:17:36 +08:00
parent ba023f2af4
commit ec7e889edc
2 changed files with 163 additions and 7 deletions
+14 -2
View File
@@ -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<OnboardingPreparedContract>("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}` : "";
+149 -5
View File
@@ -572,6 +572,16 @@
<p class="section-desc">系统已为您代入真实登记信息生成专属合同,请认真查阅条款并勾选确认</p>
</div>
<el-alert
v-if="preparingContract"
class="contract-preparing-alert"
title="正在生成您的专属合同"
:description="contractPreparingDescription"
type="info"
show-icon
:closable="false"
/>
<el-empty v-if="!contracts.length" description="暂无待签署入职合同" />
<div v-else class="contracts-list">
@@ -593,20 +603,32 @@
</div>
</div>
<el-alert
v-if="getContractPrepareError(c.id)"
class="contract-prepare-error"
:title="getContractPrepareError(c.id)"
type="error"
show-icon
:closable="false"
/>
<div class="contract-card-bottom">
<el-button
type="primary"
:type="isContractPrepareFailed(c.id) ? 'danger' : 'primary'"
plain
icon="Download"
@click="previewAndDownloadContract(c)"
:icon="isContractPrepareFailed(c.id) ? 'Refresh' : 'Download'"
:loading="isContractPreparing(c.id)"
:disabled="!isContractPrepared(c.id) && !isContractPrepareFailed(c.id)"
@click="handleContractAction(c)"
>
下载并查看合同
{{ getContractActionText(c.id) }}
</el-button>
<el-checkbox
v-if="canEdit"
v-model="c.isAgreed"
class="agree-checkbox"
:disabled="!isContractPrepared(c.id)"
:label="`我已完整阅读并同意《${c.contractName}》全部条款`"
/>
<span v-else class="confirmed-text">✓ 候选人已确认同意</span>
@@ -662,6 +684,9 @@ const submitting = ref(false);
const error = ref("");
const currentStep = ref(0);
const uploadingMaterials = reactive<Record<string, boolean>>({});
type ContractPrepareStatus = "idle" | "preparing" | "ready" | "error";
const contractPrepareStatus = reactive<Record<string, ContractPrepareStatus>>({});
const contractPrepareErrors = reactive<Record<string, string>>({});
const task = ref<OnboardingTask | null>(null);
const contracts = ref<any[]>([]);
@@ -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<unknown>) {
}
}
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;