入职样式优化
This commit is contained in:
@@ -1,28 +1,36 @@
|
||||
<template>
|
||||
<main class="fill-page" v-loading="loading">
|
||||
<!-- 顶部品牌横幅 -->
|
||||
<header class="brand-header">
|
||||
<div class="brand-content">
|
||||
<div class="brand-badge">
|
||||
<el-icon>
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
<span>智能入职服务</span>
|
||||
<main class="fill-page" :class="{ 'is-error-state': Boolean(error) }" v-loading="loading">
|
||||
<!-- 异常状态展示(居中悬浮卡片,对齐 public-status 风格) -->
|
||||
<div v-if="error" class="public-error-container">
|
||||
<section class="error-status-card">
|
||||
<div class="error-status-mark">!</div>
|
||||
<h1 class="error-status-title">{{ errorTitle }}</h1>
|
||||
<p class="error-status-desc">{{ errorDescription }}</p>
|
||||
<div class="error-status-actions">
|
||||
<el-button type="primary" class="error-retry-btn" @click="load()">重新加载</el-button>
|
||||
</div>
|
||||
<h1 class="brand-title">很高兴与你相遇,欢迎加入!</h1>
|
||||
<p class="brand-subtitle">请完成以下入职流程:填写登记信息、上传证件、阅读并签署相关入职材料,开启你的职场新旅程。</p>
|
||||
</div>
|
||||
</header>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 容器卡片 -->
|
||||
<div class="main-container">
|
||||
<el-result v-if="error" icon="error" title="访问受限" :sub-title="error">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="load()">重新加载</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
<!-- 正常入职内容 -->
|
||||
<template v-else>
|
||||
<!-- 顶部品牌横幅 -->
|
||||
<header class="brand-header">
|
||||
<div class="brand-content">
|
||||
<div class="brand-badge">
|
||||
<el-icon>
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
<span>智能入职服务</span>
|
||||
</div>
|
||||
<h1 class="brand-title">很高兴与你相遇,欢迎加入!</h1>
|
||||
<p class="brand-subtitle">请完成以下入职流程:填写登记信息、上传证件、阅读并签署相关入职材料,开启你的职场新旅程。</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<template v-else-if="task">
|
||||
<!-- 容器卡片 -->
|
||||
<div class="main-container">
|
||||
<template v-if="task">
|
||||
<!-- 任务状态提示条 -->
|
||||
<div class="status-banner" :class="task.taskStatus">
|
||||
<div class="status-banner-main">
|
||||
@@ -457,8 +465,13 @@
|
||||
</div>
|
||||
|
||||
<div v-if="!extraVariables.length" class="all-covered-card">
|
||||
<el-result icon="success" title="个人信息已完整覆盖"
|
||||
sub-title="您在《入职登记表》中填写的姓名、身份证、电话、银行卡等信息已完整满足本次签署材料所需,无需额外补充!" />
|
||||
<div class="covered-status-mark">
|
||||
<el-icon :size="28"><Check /></el-icon>
|
||||
</div>
|
||||
<h4 class="covered-status-title">个人信息已完整覆盖</h4>
|
||||
<p class="covered-status-desc">
|
||||
您在《入职登记表》中填写的姓名、身份证、电话、银行卡等信息已完整满足本次签署材料所需,无需额外补充!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<el-form v-else label-position="top" class="extra-form">
|
||||
@@ -664,25 +677,60 @@
|
||||
|
||||
<!-- 底部向导操作栏 -->
|
||||
<div class="wizard-footer">
|
||||
<el-button v-if="currentStep > 0" size="large" @click="currentStep--">上一步</el-button>
|
||||
<el-button v-if="currentStep < 3" type="primary" size="large" @click="nextStep">
|
||||
下一步
|
||||
<el-button v-if="currentStep > 0" size="large" :disabled="savingStep || isAnyMaterialUploading" @click="currentStep--">上一步</el-button>
|
||||
<el-button v-if="currentStep < 3" type="primary" size="large"
|
||||
:loading="savingStep || (currentStep === 2 && isAnyMaterialUploading)"
|
||||
:disabled="savingStep || isAnyMaterialUploading"
|
||||
@click="nextStep">
|
||||
{{ (currentStep === 2 && isAnyMaterialUploading) ? "材料上传中..." : (savingStep ? "保存中..." : "下一步") }}
|
||||
</el-button>
|
||||
<el-button v-if="currentStep === 3 && canEdit" type="success" size="large" icon="Check"
|
||||
:loading="submitting" @click="submitAll">
|
||||
:loading="submitting" :disabled="savingStep || isAnyMaterialUploading" @click="submitAll">
|
||||
确认无误并提交入职资料
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
||||
import { ElMessage as RawElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
|
||||
|
||||
/**
|
||||
* 移动端/公开端单例独占式 Toast 封装:
|
||||
* 1. 弹出新提示前自动 closeAll(),彻底杜绝手机端多条提示同时排队堆叠满屏的问题
|
||||
* 2. 启用 grouping: true 自动合并短时间内同一文案的提示
|
||||
*/
|
||||
const ElMessage = {
|
||||
success(options: any) {
|
||||
RawElMessage.closeAll();
|
||||
const config = typeof options === "string" ? { message: options } : options;
|
||||
return RawElMessage.success({ grouping: true, ...config });
|
||||
},
|
||||
warning(options: any) {
|
||||
RawElMessage.closeAll();
|
||||
const config = typeof options === "string" ? { message: options } : options;
|
||||
return RawElMessage.warning({ grouping: true, ...config });
|
||||
},
|
||||
error(options: any) {
|
||||
RawElMessage.closeAll();
|
||||
const config = typeof options === "string" ? { message: options } : options;
|
||||
return RawElMessage.error({ grouping: true, ...config });
|
||||
},
|
||||
info(options: any) {
|
||||
RawElMessage.closeAll();
|
||||
const config = typeof options === "string" ? { message: options } : options;
|
||||
return RawElMessage.info({ grouping: true, ...config });
|
||||
},
|
||||
closeAll() {
|
||||
RawElMessage.closeAll();
|
||||
}
|
||||
};
|
||||
import {
|
||||
OnboardingPublicTask,
|
||||
onboardingPublicApi
|
||||
@@ -707,11 +755,34 @@ const originalViewportContent = viewportMeta?.content;
|
||||
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const savingStep = ref(false);
|
||||
const error = ref("");
|
||||
const errorTitle = computed(() => {
|
||||
const msg = error.value || "";
|
||||
if (msg.includes("链接不存在") || msg.includes("Token") || msg.includes("无效")) {
|
||||
return "入职链接无效";
|
||||
}
|
||||
if (msg.includes("过期")) {
|
||||
return "入职链接已过期";
|
||||
}
|
||||
return "访问受限";
|
||||
});
|
||||
|
||||
const errorDescription = computed(() => {
|
||||
const msg = error.value || "";
|
||||
if (msg.includes("链接不存在") || msg.includes("Token") || msg.includes("无效")) {
|
||||
return "入职链接不存在或已失效,请联系招聘人员重新获取入职链接。";
|
||||
}
|
||||
if (msg.includes("过期")) {
|
||||
return "该链接已超过有效期,不能继续填写。请联系招聘人员重新获取入职链接。";
|
||||
}
|
||||
return `${msg},请检查网络或联系招聘人员协助处理。`;
|
||||
});
|
||||
const currentTimestamp = ref(Date.now());
|
||||
let linkExpiryTimer: number | undefined;
|
||||
const currentStep = ref(0);
|
||||
const uploadingMaterials = reactive<Record<string, boolean>>({});
|
||||
const isAnyMaterialUploading = computed(() => Object.values(uploadingMaterials).some(Boolean));
|
||||
type ContractPrepareStatus = "idle" | "preparing" | "ready" | "error";
|
||||
const contractPrepareStatus = reactive<Record<string, ContractPrepareStatus>>({});
|
||||
const contractPrepareErrors = reactive<Record<string, string>>({});
|
||||
@@ -1465,36 +1536,64 @@ async function validateRegistrationForm() {
|
||||
}
|
||||
|
||||
async function nextStep() {
|
||||
if (currentStep.value === 0) {
|
||||
if (!await validateRegistrationForm()) {
|
||||
return;
|
||||
}
|
||||
if (!validateRegistrationLists()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
ElMessage.success("入职登记表已临时保存");
|
||||
}
|
||||
} else if (currentStep.value === 1) {
|
||||
if (!validateExtraVariables()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
}
|
||||
} else if (currentStep.value === 2) {
|
||||
const missing = materialRequirements.value.filter(m => m.required && !getUploadedMaterials(m.materialTypeId).length);
|
||||
if (missing.length) {
|
||||
ElMessage.warning(`请先上传必传材料:${missing.map((m: any) => m.materialName).join("、")}`);
|
||||
locateValidationTarget(`material-requirement-${missing[0].materialTypeId}`);
|
||||
return;
|
||||
}
|
||||
if (savingStep.value) {
|
||||
return;
|
||||
}
|
||||
currentStep.value++;
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
if (currentStep.value === 3) {
|
||||
await prepareContracts();
|
||||
if (isAnyMaterialUploading.value) {
|
||||
ElMessage.closeAll();
|
||||
ElMessage.warning("材料正在上传中,请稍候再点击下一步");
|
||||
return;
|
||||
}
|
||||
savingStep.value = true;
|
||||
try {
|
||||
if (currentStep.value === 0) {
|
||||
if (!await validateRegistrationForm()) {
|
||||
return;
|
||||
}
|
||||
if (!validateRegistrationLists()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
ElMessage.closeAll();
|
||||
ElMessage.success("入职登记表已临时保存");
|
||||
}
|
||||
} else if (currentStep.value === 1) {
|
||||
if (!validateExtraVariables()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
}
|
||||
} else if (currentStep.value === 2) {
|
||||
if (isAnyMaterialUploading.value) {
|
||||
ElMessage.closeAll();
|
||||
ElMessage.warning("材料正在上传中,请稍候再点击下一步");
|
||||
return;
|
||||
}
|
||||
const missing = materialRequirements.value.filter(m => m.required && !getUploadedMaterials(m.materialTypeId).length);
|
||||
if (missing.length) {
|
||||
ElMessage.closeAll();
|
||||
ElMessage.warning(`请先上传必传材料:${missing.map((m: any) => m.materialName).join("、")}`);
|
||||
locateValidationTarget(`material-requirement-${missing[0].materialTypeId}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
currentStep.value++;
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
if (currentStep.value === 3) {
|
||||
await prepareContracts();
|
||||
}
|
||||
} catch (error: any) {
|
||||
const message = error?.msg || error?.message || "临时保存失败,请稍后重试";
|
||||
ElMessage.closeAll();
|
||||
await ElMessageBox.alert(message, "无法进入下一步", {
|
||||
type: "warning",
|
||||
confirmButtonText: "我知道了",
|
||||
closeOnClickModal: false
|
||||
}).catch(() => undefined);
|
||||
} finally {
|
||||
savingStep.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1933,6 +2032,87 @@ onBeforeUnmount(() => {
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
/* 异常状态居中卡片样式(对齐 public-status 风格) */
|
||||
.fill-page.is-error-state {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
background: #f0fdf7;
|
||||
}
|
||||
|
||||
.public-error-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-status-card {
|
||||
width: min(100%, 420px);
|
||||
padding: 42px 28px;
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1fae5;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 36px rgb(5 150 105 / 10%);
|
||||
}
|
||||
|
||||
.error-status-mark {
|
||||
display: flex;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
background: #059669;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.error-status-title {
|
||||
margin: 0;
|
||||
color: #064e3b;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.error-status-desc {
|
||||
margin: 14px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.error-status-actions {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.error-retry-btn {
|
||||
height: 40px;
|
||||
padding: 0 26px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
background-color: #059669 !important;
|
||||
border-color: #059669 !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.2);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.error-retry-btn:hover {
|
||||
background-color: #047857 !important;
|
||||
border-color: #047857 !important;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
|
||||
/* 顶部大背景 (Hero Header Background) - 纯色柔和淡绿,去渐变 */
|
||||
.brand-header {
|
||||
background: #ecfdf5;
|
||||
@@ -2625,6 +2805,72 @@ onBeforeUnmount(() => {
|
||||
background-color: #ecfdf5;
|
||||
}
|
||||
|
||||
.wizard-footer .el-button--success {
|
||||
background-color: #059669 !important;
|
||||
border-color: #059669 !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
padding: 10px 24px;
|
||||
height: 40px;
|
||||
box-shadow: 0 2px 8px rgba(5, 150, 105, 0.25);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.wizard-footer .el-button--success:hover {
|
||||
background-color: #047857 !important;
|
||||
border-color: #047857 !important;
|
||||
color: #ffffff !important;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.35);
|
||||
}
|
||||
|
||||
.wizard-footer .el-button--success:active {
|
||||
background-color: #065f46 !important;
|
||||
border-color: #065f46 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* 个人信息已完整覆盖卡片与图标 */
|
||||
.all-covered-card {
|
||||
padding: 38px 24px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1fae5;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 4px 20px rgba(5, 150, 105, 0.06);
|
||||
text-align: center;
|
||||
margin: 16px 0 24px;
|
||||
}
|
||||
|
||||
.covered-status-mark {
|
||||
display: flex;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
background: #059669;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 14px rgba(5, 150, 105, 0.25);
|
||||
}
|
||||
|
||||
.covered-status-title {
|
||||
margin: 0 0 10px;
|
||||
color: #064e3b;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.covered-status-desc {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.readonly-banner {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user