提示优化
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
/** 判断 Element Plus 弹窗是否由用户主动取消或关闭。 */
|
||||||
|
function isUserCancelled(action: unknown) {
|
||||||
|
return action === "cancel" || action === "close";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行确认弹窗。用户取消时返回 false,真正的异常继续抛出。
|
||||||
|
*/
|
||||||
|
export async function isMessageBoxConfirmed(confirmPromise: Promise<unknown>) {
|
||||||
|
try {
|
||||||
|
await confirmPromise;
|
||||||
|
return true;
|
||||||
|
} catch (action) {
|
||||||
|
if (isUserCancelled(action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行输入弹窗。用户取消时返回 null,真正的异常继续抛出。
|
||||||
|
*/
|
||||||
|
export async function getMessageBoxPromptResult<T>(promptPromise: Promise<T>) {
|
||||||
|
try {
|
||||||
|
return await promptPromise;
|
||||||
|
} catch (action) {
|
||||||
|
if (isUserCancelled(action)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw action;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -500,6 +500,7 @@ import {
|
|||||||
type OnboardingTemplateVariable
|
type OnboardingTemplateVariable
|
||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||||
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
|
|
||||||
const templateTable = ref<ProTableInstance>();
|
const templateTable = ref<ProTableInstance>();
|
||||||
const selectedTemplate = ref<OnboardingContractTemplate>();
|
const selectedTemplate = ref<OnboardingContractTemplate>();
|
||||||
@@ -811,7 +812,11 @@ async function saveTemplate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeTemplate(row: OnboardingContractTemplate) {
|
async function removeTemplate(row: OnboardingContractTemplate) {
|
||||||
await ElMessageBox.confirm(`确认删除签署材料模板“${row.templateName}”?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除签署材料模板“${row.templateName}”?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteTemplate([row.id]);
|
await onboardingTaskApi.deleteTemplate([row.id]);
|
||||||
templateTable.value?.refresh();
|
templateTable.value?.refresh();
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
@@ -860,7 +865,11 @@ async function changeVersionStatus(row: OnboardingContractVersion, status: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeVersion(row: OnboardingContractVersion) {
|
async function removeVersion(row: OnboardingContractVersion) {
|
||||||
await ElMessageBox.confirm(`确认删除签署材料版本“${row.versionNo}”?此操作不可撤销。`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除签署材料版本“${row.versionNo}”?此操作不可撤销。`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteVersion(row.id);
|
await onboardingTaskApi.deleteVersion(row.id);
|
||||||
await loadVersions();
|
await loadVersions();
|
||||||
ElMessage.success("版本删除成功");
|
ElMessage.success("版本删除成功");
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ import {
|
|||||||
OnboardingTemplate,
|
OnboardingTemplate,
|
||||||
onboardingTaskApi
|
onboardingTaskApi
|
||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
@@ -617,7 +618,11 @@ async function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeTemplate(row: OnboardingTemplate) {
|
async function removeTemplate(row: OnboardingTemplate) {
|
||||||
await ElMessageBox.confirm(`确认删除入职模板“${row.templateName}”吗?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除入职模板“${row.templateName}”吗?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteOnboardingTemplate([row.id!]);
|
await onboardingTaskApi.deleteOnboardingTemplate([row.id!]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
query.pageNum = 1;
|
query.pageNum = 1;
|
||||||
|
|||||||
@@ -168,6 +168,7 @@
|
|||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
||||||
import { onboardingTaskApi } from "@/api/modules/biz/onboarding";
|
import { onboardingTaskApi } from "@/api/modules/biz/onboarding";
|
||||||
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
@@ -293,7 +294,11 @@ async function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function remove(row: any) {
|
async function remove(row: any) {
|
||||||
await ElMessageBox.confirm(`确认删除自定义变量“${row.variableCode}”?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除自定义变量“${row.variableCode}”?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteVariableDefinitions([row.id]);
|
await onboardingTaskApi.deleteVariableDefinitions([row.id]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
loadData();
|
loadData();
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import {
|
|||||||
OnboardingMaterialType,
|
OnboardingMaterialType,
|
||||||
onboardingTaskApi
|
onboardingTaskApi
|
||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
import OnboardingTemplatePanel from "./components/OnboardingTemplatePanel.vue";
|
import OnboardingTemplatePanel from "./components/OnboardingTemplatePanel.vue";
|
||||||
import VariableDefinitionPanel from "./components/VariableDefinitionPanel.vue";
|
import VariableDefinitionPanel from "./components/VariableDefinitionPanel.vue";
|
||||||
|
|
||||||
@@ -152,7 +153,11 @@ async function saveMaterial() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeMaterial(row: OnboardingMaterialType) {
|
async function removeMaterial(row: OnboardingMaterialType) {
|
||||||
await ElMessageBox.confirm(`确认删除资料类型“${row.materialName}”吗?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除资料类型“${row.materialName}”吗?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteMaterialTypes([row.id!]);
|
await onboardingTaskApi.deleteMaterialTypes([row.id!]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
await loadMaterials();
|
await loadMaterials();
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ import {
|
|||||||
type OnboardingPosition
|
type OnboardingPosition
|
||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
import type { ColumnProps, ProTableInstance, EnumProps } from "@/components/ProTable/interface";
|
import type { ColumnProps, ProTableInstance, EnumProps } from "@/components/ProTable/interface";
|
||||||
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
|
|
||||||
const activeTab = ref("company");
|
const activeTab = ref("company");
|
||||||
|
|
||||||
@@ -672,20 +673,32 @@ async function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeCompany(row: OnboardingCompany) {
|
async function removeCompany(row: OnboardingCompany) {
|
||||||
await ElMessageBox.confirm(`确认删除公司“${row.companyName}”?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除公司“${row.companyName}”?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteCompany([row.id]);
|
await onboardingTaskApi.deleteCompany([row.id]);
|
||||||
companyTable.value?.refresh();
|
companyTable.value?.refresh();
|
||||||
await loadCompanies();
|
await loadCompanies();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeDepartment(row: OnboardingDepartment) {
|
async function removeDepartment(row: OnboardingDepartment) {
|
||||||
await ElMessageBox.confirm(`确认删除部门“${row.departmentName}”?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除部门“${row.departmentName}”?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deleteDepartment([row.id]);
|
await onboardingTaskApi.deleteDepartment([row.id]);
|
||||||
departmentTable.value?.refresh();
|
departmentTable.value?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removePosition(row: OnboardingPosition) {
|
async function removePosition(row: OnboardingPosition) {
|
||||||
await ElMessageBox.confirm(`确认删除岗位“${row.positionName}”?`, "删除确认", { type: "warning" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm(`确认删除岗位“${row.positionName}”?`, "删除确认", { type: "warning" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.deletePosition([row.id]);
|
await onboardingTaskApi.deletePosition([row.id]);
|
||||||
positionTable.value?.refresh();
|
positionTable.value?.refresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1022,6 +1022,7 @@ import {
|
|||||||
type OnboardingTaskContractVariableGroup
|
type OnboardingTaskContractVariableGroup
|
||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||||
|
import { getMessageBoxPromptResult, isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
|
|
||||||
const proTable = ref<ProTableInstance>();
|
const proTable = ref<ProTableInstance>();
|
||||||
|
|
||||||
@@ -1885,6 +1886,10 @@ async function copyOrShareTaskLink(row: OnboardingTask) {
|
|||||||
positionName: row.positionName || "岗位"
|
positionName: row.positionName || "岗位"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// get-link 可能已经为过期链接自动续期,立即同步列表中的展示时间。
|
||||||
|
row.linkExpireAt = data.expiresAt;
|
||||||
|
proTable.value?.refresh();
|
||||||
|
|
||||||
copyText(linkUrl, "专属入职链接已复制到剪贴板!");
|
copyText(linkUrl, "专属入职链接已复制到剪贴板!");
|
||||||
shareVisible.value = true;
|
shareVisible.value = true;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -1984,11 +1989,14 @@ function buildContractVariableSaveValues(group: OnboardingTaskContractVariableGr
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function approve(row: OnboardingTask) {
|
async function approve(row: OnboardingTask) {
|
||||||
await ElMessageBox.confirm(
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
"确认审核通过?系统将自动创建正式员工档案并锁定签署材料快照。",
|
ElMessageBox.confirm(
|
||||||
"审核通过确认",
|
"确认审核通过?系统将自动创建正式员工档案并锁定签署材料快照。",
|
||||||
{ type: "success", confirmButtonText: "审核通过", cancelButtonText: "取消" }
|
"审核通过确认",
|
||||||
|
{ type: "success", confirmButtonText: "审核通过", cancelButtonText: "取消" }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.approve({ taskId: row.id });
|
await onboardingTaskApi.approve({ taskId: row.id });
|
||||||
ElMessage.success("审核通过,已成功建档!");
|
ElMessage.success("审核通过,已成功建档!");
|
||||||
@@ -1997,11 +2005,15 @@ async function approve(row: OnboardingTask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function reject(row: OnboardingTask) {
|
async function reject(row: OnboardingTask) {
|
||||||
const { value } = await ElMessageBox.prompt(
|
const result = await getMessageBoxPromptResult(
|
||||||
"请输入退回补充说明(将展示给候选人查看):",
|
ElMessageBox.prompt(
|
||||||
"退回入职任务",
|
"请输入退回补充说明(将展示给候选人查看):",
|
||||||
{ inputPattern: /\S+/, inputErrorMessage: "退回原因不能为空", confirmButtonText: "确认退回", type: "warning" }
|
"退回入职任务",
|
||||||
|
{ inputPattern: /\S+/, inputErrorMessage: "退回原因不能为空", confirmButtonText: "确认退回", type: "warning" }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
if (!result) return;
|
||||||
|
const { value } = result;
|
||||||
|
|
||||||
await onboardingTaskApi.reject({ taskId: row.id, auditOpinion: value });
|
await onboardingTaskApi.reject({ taskId: row.id, auditOpinion: value });
|
||||||
ElMessage.warning("已退回修改,候选人可重新补录");
|
ElMessage.warning("已退回修改,候选人可重新补录");
|
||||||
@@ -2043,10 +2055,13 @@ onBeforeUnmount(clearMaterialPreviews);
|
|||||||
async function reviewMaterial(taskMaterialId: number, reviewStatus: string) {
|
async function reviewMaterial(taskMaterialId: number, reviewStatus: string) {
|
||||||
let reviewRemark = "";
|
let reviewRemark = "";
|
||||||
if (reviewStatus === "need_supplement") {
|
if (reviewStatus === "need_supplement") {
|
||||||
const result = await ElMessageBox.prompt("请输入该材料退回补充说明:", "材料不合格退回", {
|
const result = await getMessageBoxPromptResult(
|
||||||
inputPattern: /\S+/,
|
ElMessageBox.prompt("请输入该材料退回补充说明:", "材料不合格退回", {
|
||||||
inputErrorMessage: "请填写说明"
|
inputPattern: /\S+/,
|
||||||
});
|
inputErrorMessage: "请填写说明"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (!result) return;
|
||||||
reviewRemark = result.value;
|
reviewRemark = result.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2059,7 +2074,11 @@ async function reviewMaterial(taskMaterialId: number, reviewStatus: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function archive(row: OnboardingTask) {
|
async function archive(row: OnboardingTask) {
|
||||||
await ElMessageBox.confirm("确认归档该入职任务?", "归档任务", { type: "info" });
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
|
ElMessageBox.confirm("确认归档该入职任务?", "归档任务", { type: "info" })
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await onboardingTaskApi.archive({ taskId: row.id });
|
await onboardingTaskApi.archive({ taskId: row.id });
|
||||||
ElMessage.success("任务已归档");
|
ElMessage.success("任务已归档");
|
||||||
detailVisible.value = false;
|
detailVisible.value = false;
|
||||||
@@ -2067,11 +2086,14 @@ async function archive(row: OnboardingTask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function revoke(row: OnboardingTask) {
|
async function revoke(row: OnboardingTask) {
|
||||||
await ElMessageBox.confirm(
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
"撤销后候选人填写链接将立即失效,确定继续撤销吗?",
|
ElMessageBox.confirm(
|
||||||
"撤销任务",
|
"撤销后候选人填写链接将立即失效,确定继续撤销吗?",
|
||||||
{ type: "error", confirmButtonText: "确定撤销", cancelButtonText: "取消" }
|
"撤销任务",
|
||||||
|
{ type: "error", confirmButtonText: "确定撤销", cancelButtonText: "取消" }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
await onboardingTaskApi.revoke({ taskId: row.id });
|
await onboardingTaskApi.revoke({ taskId: row.id });
|
||||||
ElMessage.success("任务已撤销");
|
ElMessage.success("任务已撤销");
|
||||||
detailVisible.value = false;
|
detailVisible.value = false;
|
||||||
@@ -2079,11 +2101,14 @@ async function revoke(row: OnboardingTask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeTask(row: OnboardingTask) {
|
async function removeTask(row: OnboardingTask) {
|
||||||
await ElMessageBox.confirm(
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
`确认永久删除已撤销的入职任务【${row.taskNo}】吗?此操作将清理该任务全部数据,且不可恢复。`,
|
ElMessageBox.confirm(
|
||||||
"删除任务确认",
|
`确认永久删除已撤销的入职任务【${row.taskNo}】吗?此操作将清理该任务全部数据,且不可恢复。`,
|
||||||
{ type: "error", confirmButtonText: "确定删除", cancelButtonText: "取消" }
|
"删除任务确认",
|
||||||
|
{ type: "error", confirmButtonText: "确定删除", cancelButtonText: "取消" }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
await onboardingTaskApi.deleteTask(row.id);
|
await onboardingTaskApi.deleteTask(row.id);
|
||||||
ElMessage.success("入职任务已删除");
|
ElMessage.success("入职任务已删除");
|
||||||
detailVisible.value = false;
|
detailVisible.value = false;
|
||||||
|
|||||||
@@ -34,6 +34,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="isLinkExpired"
|
||||||
|
class="link-expired-alert"
|
||||||
|
title="入职链接已过期"
|
||||||
|
description="该链接已超过有效期,不能继续填写或提交。请联系工作人员重新获取入职链接。"
|
||||||
|
type="error"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
<!-- 审核退回后向候选人明确展示需要补充或修改的内容。 -->
|
<!-- 审核退回后向候选人明确展示需要补充或修改的内容。 -->
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="task.taskStatus === 'rejected'"
|
v-if="task.taskStatus === 'rejected'"
|
||||||
@@ -47,7 +58,7 @@
|
|||||||
|
|
||||||
<!-- 分步向导模式 -->
|
<!-- 分步向导模式 -->
|
||||||
<div class="wizard-card">
|
<div class="wizard-card">
|
||||||
<div v-if="!canEdit" class="readonly-banner">
|
<div v-if="!canEdit && !isLinkExpired" class="readonly-banner">
|
||||||
<el-alert
|
<el-alert
|
||||||
title="当前为查阅模式(入职资料已提交审核)"
|
title="当前为查阅模式(入职资料已提交审核)"
|
||||||
type="info"
|
type="info"
|
||||||
@@ -290,7 +301,12 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!educationHistory.length" class="empty-hint">暂未添加学习经历,可点击右上角添加</div>
|
<div v-if="!educationHistory.length" class="empty-hint">暂未添加学习经历,可点击右上角添加</div>
|
||||||
<div v-for="(item, idx) in educationHistory" :key="idx" class="dynamic-row-box">
|
<div
|
||||||
|
v-for="(item, idx) in educationHistory"
|
||||||
|
:id="`education-history-${idx}`"
|
||||||
|
:key="idx"
|
||||||
|
class="dynamic-row-box"
|
||||||
|
>
|
||||||
<div class="row-badge-header">
|
<div class="row-badge-header">
|
||||||
<span class="row-badge">学习经历 #{{ idx + 1 }}</span>
|
<span class="row-badge">学习经历 #{{ idx + 1 }}</span>
|
||||||
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="educationHistory.splice(idx, 1)">删除本条</el-button>
|
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="educationHistory.splice(idx, 1)">删除本条</el-button>
|
||||||
@@ -332,7 +348,12 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!workHistory.length" class="empty-hint">暂未添加工作经历(应届生可留空)</div>
|
<div v-if="!workHistory.length" class="empty-hint">暂未添加工作经历(应届生可留空)</div>
|
||||||
<div v-for="(item, idx) in workHistory" :key="idx" class="dynamic-row-box">
|
<div
|
||||||
|
v-for="(item, idx) in workHistory"
|
||||||
|
:id="`work-history-${idx}`"
|
||||||
|
:key="idx"
|
||||||
|
class="dynamic-row-box"
|
||||||
|
>
|
||||||
<div class="row-badge-header">
|
<div class="row-badge-header">
|
||||||
<span class="row-badge">工作经历 #{{ idx + 1 }}</span>
|
<span class="row-badge">工作经历 #{{ idx + 1 }}</span>
|
||||||
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="workHistory.splice(idx, 1)">删除本条</el-button>
|
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="workHistory.splice(idx, 1)">删除本条</el-button>
|
||||||
@@ -379,7 +400,12 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!familyMembers.length" class="empty-hint">暂未添加家庭成员信息</div>
|
<div v-if="!familyMembers.length" class="empty-hint">暂未添加家庭成员信息</div>
|
||||||
<div v-for="(item, idx) in familyMembers" :key="idx" class="dynamic-row-box">
|
<div
|
||||||
|
v-for="(item, idx) in familyMembers"
|
||||||
|
:id="`family-member-${idx}`"
|
||||||
|
:key="idx"
|
||||||
|
class="dynamic-row-box"
|
||||||
|
>
|
||||||
<div class="row-badge-header">
|
<div class="row-badge-header">
|
||||||
<span class="row-badge">家庭成员 #{{ idx + 1 }}</span>
|
<span class="row-badge">家庭成员 #{{ idx + 1 }}</span>
|
||||||
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="familyMembers.splice(idx, 1)">删除本条</el-button>
|
<el-button v-if="canEdit" link type="danger" icon="Delete" @click="familyMembers.splice(idx, 1)">删除本条</el-button>
|
||||||
@@ -440,7 +466,13 @@
|
|||||||
class="mb-3"
|
class="mb-3"
|
||||||
/>
|
/>
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col v-for="v in extraVariables" :key="v.variableCode" :xs="24" :sm="12">
|
<el-col
|
||||||
|
v-for="(v, index) in extraVariables"
|
||||||
|
:id="`extra-variable-${index}`"
|
||||||
|
:key="v.variableCode"
|
||||||
|
:xs="24"
|
||||||
|
:sm="12"
|
||||||
|
>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:label="v.variableName || v.variableCode"
|
:label="v.variableName || v.variableCode"
|
||||||
:required="v.required"
|
:required="v.required"
|
||||||
@@ -500,6 +532,7 @@
|
|||||||
<div v-else class="materials-grid">
|
<div v-else class="materials-grid">
|
||||||
<div
|
<div
|
||||||
v-for="mat in materialRequirements"
|
v-for="mat in materialRequirements"
|
||||||
|
:id="`material-requirement-${mat.materialTypeId}`"
|
||||||
:key="mat.materialTypeId"
|
:key="mat.materialTypeId"
|
||||||
class="material-item-card"
|
class="material-item-card"
|
||||||
>
|
>
|
||||||
@@ -622,6 +655,7 @@
|
|||||||
<div v-else class="contracts-list">
|
<div v-else class="contracts-list">
|
||||||
<div
|
<div
|
||||||
v-for="c in contracts"
|
v-for="c in contracts"
|
||||||
|
:id="`contract-confirm-${c.id}`"
|
||||||
:key="c.id"
|
:key="c.id"
|
||||||
class="contract-sign-card"
|
class="contract-sign-card"
|
||||||
>
|
>
|
||||||
@@ -708,7 +742,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
||||||
import {
|
import {
|
||||||
@@ -735,6 +769,8 @@ const originalViewportContent = viewportMeta?.content;
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
|
const currentTimestamp = ref(Date.now());
|
||||||
|
let linkExpiryTimer: number | undefined;
|
||||||
const currentStep = ref(0);
|
const currentStep = ref(0);
|
||||||
const uploadingMaterials = reactive<Record<string, boolean>>({});
|
const uploadingMaterials = reactive<Record<string, boolean>>({});
|
||||||
type ContractPrepareStatus = "idle" | "preparing" | "ready" | "error";
|
type ContractPrepareStatus = "idle" | "preparing" | "ready" | "error";
|
||||||
@@ -883,8 +919,20 @@ const regRules: FormRules = {
|
|||||||
emergencyPhone: [{ validator: validateOptionalContactPhone, trigger: "blur" }]
|
emergencyPhone: [{ validator: validateOptionalContactPhone, trigger: "blur" }]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 根据当前 Token 的实际有效期实时锁定已打开的填写页面。 */
|
||||||
|
const isLinkExpired = computed(() => {
|
||||||
|
if (!task.value?.linkExpireAt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresAt = Date.parse(task.value.linkExpireAt.replace(" ", "T"));
|
||||||
|
return !Number.isNaN(expiresAt) && expiresAt <= currentTimestamp.value;
|
||||||
|
});
|
||||||
|
|
||||||
const canEdit = computed(() => {
|
const canEdit = computed(() => {
|
||||||
return task.value?.taskStatus === "pending_fill" || task.value?.taskStatus === "rejected";
|
const editableStatus = task.value?.taskStatus === "pending_fill"
|
||||||
|
|| task.value?.taskStatus === "rejected";
|
||||||
|
return editableStatus && !isLinkExpired.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 当前正在校验变量并生成文件的合同。 */
|
/** 当前正在校验变量并生成文件的合同。 */
|
||||||
@@ -995,10 +1043,36 @@ function isIsoDate(value: string) {
|
|||||||
&& date.getDate() === Number(match[3]);
|
&& date.getDate() === Number(match[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将长表单平滑定位到首个错误区域,并尽量聚焦其中可编辑的控件。
|
||||||
|
*/
|
||||||
|
function locateValidationTarget(target: string | HTMLElement | null | undefined) {
|
||||||
|
nextTick(() => {
|
||||||
|
const element = typeof target === "string"
|
||||||
|
? document.getElementById(target)
|
||||||
|
: target;
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const focusable = element.matches("input:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex='-1'])")
|
||||||
|
? element as HTMLElement
|
||||||
|
: element.querySelector<HTMLElement>(
|
||||||
|
"input:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex='-1'])"
|
||||||
|
);
|
||||||
|
focusable?.focus({ preventScroll: true });
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function validateExtraVariables() {
|
function validateExtraVariables() {
|
||||||
let firstMessage = "";
|
let firstMessage = "";
|
||||||
|
let firstTargetId = "";
|
||||||
|
|
||||||
for (const variable of extraVariables.value) {
|
for (const [index, variable] of extraVariables.value.entries()) {
|
||||||
const variableCode = variable.variableCode;
|
const variableCode = variable.variableCode;
|
||||||
const variableName = variable.variableName || variableCode;
|
const variableName = variable.variableName || variableCode;
|
||||||
const value = String(customVariables[variableCode] ?? "").trim();
|
const value = String(customVariables[variableCode] ?? "").trim();
|
||||||
@@ -1019,6 +1093,7 @@ function validateExtraVariables() {
|
|||||||
|
|
||||||
if (!firstMessage && extraVariableErrors[variableCode]) {
|
if (!firstMessage && extraVariableErrors[variableCode]) {
|
||||||
firstMessage = extraVariableErrors[variableCode];
|
firstMessage = extraVariableErrors[variableCode];
|
||||||
|
firstTargetId = `extra-variable-${index}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,6 +1102,7 @@ function validateExtraVariables() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ElMessage.warning(firstMessage);
|
ElMessage.warning(firstMessage);
|
||||||
|
locateValidationTarget(firstTargetId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,6 +1118,7 @@ function validateRegistrationLists() {
|
|||||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||||
if (missing) {
|
if (missing) {
|
||||||
ElMessage.warning(`第 ${index + 1} 条教育经历请填写${missing[1]},或删除整条经历`);
|
ElMessage.warning(`第 ${index + 1} 条教育经历请填写${missing[1]},或删除整条经历`);
|
||||||
|
locateValidationTarget(`education-history-${index}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1057,6 +1134,7 @@ function validateRegistrationLists() {
|
|||||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||||
if (missing) {
|
if (missing) {
|
||||||
ElMessage.warning(`第 ${index + 1} 条工作经历请填写${missing[1]},或删除整条经历`);
|
ElMessage.warning(`第 ${index + 1} 条工作经历请填写${missing[1]},或删除整条经历`);
|
||||||
|
locateValidationTarget(`work-history-${index}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1072,16 +1150,19 @@ function validateRegistrationLists() {
|
|||||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||||
if (missing) {
|
if (missing) {
|
||||||
ElMessage.warning(`第 ${index + 1} 条家庭成员请填写${missing[1]},或删除整条记录`);
|
ElMessage.warning(`第 ${index + 1} 条家庭成员请填写${missing[1]},或删除整条记录`);
|
||||||
|
locateValidationTarget(`family-member-${index}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const age = Number(row.age);
|
const age = Number(row.age);
|
||||||
if (!Number.isInteger(age) || age < 0 || age > 120) {
|
if (!Number.isInteger(age) || age < 0 || age > 120) {
|
||||||
ElMessage.warning(`第 ${index + 1} 条家庭成员的年龄应为0至120之间的整数`);
|
ElMessage.warning(`第 ${index + 1} 条家庭成员的年龄应为0至120之间的整数`);
|
||||||
|
locateValidationTarget(`family-member-${index}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!isContactPhone(row.phone)) {
|
if (!isContactPhone(row.phone)) {
|
||||||
ElMessage.warning(`第 ${index + 1} 条家庭成员的联系电话格式不正确`);
|
ElMessage.warning(`第 ${index + 1} 条家庭成员的联系电话格式不正确`);
|
||||||
|
locateValidationTarget(`family-member-${index}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1244,9 +1325,36 @@ async function saveCurrentEmployeeInfo() {
|
|||||||
await onboardingPublicApi.saveEmployeeInfo(payload);
|
await onboardingPublicApi.saveEmployeeInfo(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element Plus 校验失败时会拒绝 Promise 并返回字段错误对象。
|
||||||
|
* 这是正常的表单提示,不应继续交给全局异常处理显示“未知错误”。
|
||||||
|
*/
|
||||||
|
async function validateRegistrationForm() {
|
||||||
|
if (!regFormRef.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await regFormRef.value.validate();
|
||||||
|
return true;
|
||||||
|
} catch (fields: any) {
|
||||||
|
const firstField = Object.keys(fields || {})[0];
|
||||||
|
const firstMessage = firstField ? fields[firstField]?.[0]?.message : "";
|
||||||
|
if (firstField) {
|
||||||
|
regFormRef.value.scrollToField(firstField);
|
||||||
|
}
|
||||||
|
ElMessage.warning(firstMessage || "请检查并完善标红的登记信息");
|
||||||
|
await nextTick();
|
||||||
|
locateValidationTarget(document.querySelector<HTMLElement>(".reg-form .el-form-item.is-error"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function nextStep() {
|
async function nextStep() {
|
||||||
if (currentStep.value === 0) {
|
if (currentStep.value === 0) {
|
||||||
await regFormRef.value?.validate();
|
if (!await validateRegistrationForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!validateRegistrationLists()) {
|
if (!validateRegistrationLists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1264,7 +1372,9 @@ async function nextStep() {
|
|||||||
} else if (currentStep.value === 2) {
|
} else if (currentStep.value === 2) {
|
||||||
const missing = materialRequirements.value.filter(m => m.required && !getUploadedMaterials(m.materialTypeId).length);
|
const missing = materialRequirements.value.filter(m => m.required && !getUploadedMaterials(m.materialTypeId).length);
|
||||||
if (missing.length) {
|
if (missing.length) {
|
||||||
return ElMessage.warning(`请先上传必传材料:${missing.map((m: any) => m.materialName).join("、")}`);
|
ElMessage.warning(`请先上传必传材料:${missing.map((m: any) => m.materialName).join("、")}`);
|
||||||
|
locateValidationTarget(`material-requirement-${missing[0].materialTypeId}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentStep.value++;
|
currentStep.value++;
|
||||||
@@ -1475,17 +1585,23 @@ function previewAndDownloadContract(c: any) {
|
|||||||
async function submitAll() {
|
async function submitAll() {
|
||||||
const unprepared = contracts.value.filter(c => !isContractPrepared(c.id));
|
const unprepared = contracts.value.filter(c => !isContractPrepared(c.id));
|
||||||
if (unprepared.length) {
|
if (unprepared.length) {
|
||||||
return ElMessage.warning(`请先等待签署材料准备完成:${unprepared.map(c => c.contractName).join("、")}`);
|
ElMessage.warning(`请先等待签署材料准备完成:${unprepared.map(c => c.contractName).join("、")}`);
|
||||||
|
locateValidationTarget(`contract-confirm-${unprepared[0].id}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unviewed = contracts.value.filter(c => !c.hasViewed);
|
const unviewed = contracts.value.filter(c => !c.hasViewed);
|
||||||
if (unviewed.length) {
|
if (unviewed.length) {
|
||||||
return ElMessage.warning(`请先下载并查看签署材料:${unviewed.map(c => c.contractName).join("、")}`);
|
ElMessage.warning(`请先下载并查看签署材料:${unviewed.map(c => c.contractName).join("、")}`);
|
||||||
|
locateValidationTarget(`contract-confirm-${unviewed[0].id}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unconfirmed = contracts.value.filter(c => !c.isAgreed);
|
const unconfirmed = contracts.value.filter(c => !c.isAgreed);
|
||||||
if (unconfirmed.length) {
|
if (unconfirmed.length) {
|
||||||
return ElMessage.warning(`请先勾选同意全部签署材料(未勾选:${unconfirmed.map(c => c.contractName).join("、")})`);
|
ElMessage.warning(`请先勾选同意全部签署材料(未勾选:${unconfirmed.map(c => c.contractName).join("、")})`);
|
||||||
|
locateValidationTarget(`contract-confirm-${unconfirmed[0].id}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmed = await isMessageBoxConfirmed(
|
const confirmed = await isMessageBoxConfirmed(
|
||||||
@@ -1525,10 +1641,18 @@ onMounted(() => {
|
|||||||
"viewport-fit=cover"
|
"viewport-fit=cover"
|
||||||
].join(", ");
|
].join(", ");
|
||||||
}
|
}
|
||||||
|
currentTimestamp.value = Date.now();
|
||||||
|
linkExpiryTimer = window.setInterval(() => {
|
||||||
|
currentTimestamp.value = Date.now();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
if (linkExpiryTimer !== undefined) {
|
||||||
|
window.clearInterval(linkExpiryTimer);
|
||||||
|
}
|
||||||
if (viewportMeta && originalViewportContent) {
|
if (viewportMeta && originalViewportContent) {
|
||||||
viewportMeta.content = originalViewportContent;
|
viewportMeta.content = originalViewportContent;
|
||||||
}
|
}
|
||||||
@@ -1603,6 +1727,7 @@ onBeforeUnmount(() => {
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-expired-alert,
|
||||||
.reject-reason-alert {
|
.reject-reason-alert {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user