修改相关bug

This commit is contained in:
fuenmao
2026-09-17 11:37:32 +08:00
parent a0a181af57
commit 04c621961c
6 changed files with 255 additions and 82 deletions
+1
View File
@@ -756,6 +756,7 @@ export const onboardingTaskApi = {
versionPage(params: {
templateId: number;
versionStatus?: string;
pageNum: number;
pageSize: number;
}) {
+39 -9
View File
@@ -230,8 +230,12 @@
</template>
</el-table-column>
<el-table-column prop="effectiveDate" label="生效日期" width="130" align="center" />
<el-table-column prop="expiryDate" label="失效日期" width="130" align="center" />
<el-table-column label="生效时间" width="175" align="center">
<template #default="scope">{{ formatVersionTime(scope.row.effectiveDate) }}</template>
</el-table-column>
<el-table-column label="失效时间" width="175" align="center">
<template #default="scope">{{ formatVersionTime(scope.row.expiryDate) }}</template>
</el-table-column>
<el-table-column prop="remark" label="版本说明" min-width="150" />
<el-table-column label="操作" width="340" fixed="right" align="center">
@@ -344,12 +348,26 @@
</div>
</el-form-item>
<el-form-item label="生效日期">
<el-date-picker v-model="versionForm.effectiveDate" value-format="YYYY-MM-DD" class="w-full" />
<el-form-item label="生效时间">
<el-date-picker
v-model="versionForm.effectiveDate"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择生效时间"
class="w-full"
/>
</el-form-item>
<el-form-item label="失效日期">
<el-date-picker v-model="versionForm.expiryDate" value-format="YYYY-MM-DD" class="w-full" />
<el-form-item label="失效时间">
<el-date-picker
v-model="versionForm.expiryDate"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择失效时间"
class="w-full"
/>
</el-form-item>
<el-form-item label="版本备注">
@@ -476,7 +494,7 @@
</el-form-item>
<el-form-item label="变量类型" required>
<el-select v-model="variableForm.variableType" class="w-full">
<el-select v-model="variableForm.variableType" disabled class="w-full">
<el-option label="员工填写(从新员工基本信息/动态字段读取)" value="employee" />
<el-option label="公司信息(从公司档案自动读取)" value="company" />
<el-option label="人事预设填写(人事发起时录入)" value="contract" />
@@ -485,7 +503,7 @@
</el-form-item>
<el-form-item label="数据类型" required>
<el-select v-model="variableForm.dataType" class="w-full">
<el-select v-model="variableForm.dataType" disabled class="w-full">
<el-option label="文本" value="string" />
<el-option label="日期" value="date" />
<el-option label="数字" value="number" />
@@ -494,7 +512,7 @@
</el-form-item>
<el-form-item v-if="['employee', 'company'].includes(variableForm.variableType || '')" label="取值字段">
<el-input v-model="variableForm.employeeField" placeholder="如:EmployeeName、IdCardNumber、Phone" />
<el-input v-model="variableForm.employeeField" disabled placeholder="如:EmployeeName、IdCardNumber、Phone" />
</el-form-item>
<el-form-item label="默认值">
@@ -529,6 +547,7 @@
<script setup lang="ts" name="onboardingContract">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import { Check } from "@element-plus/icons-vue";
import {
onboardingTaskApi,
@@ -892,10 +911,21 @@ async function uploadDocx(options: any) {
}
}
function formatVersionTime(value?: string) {
return value ? dayjs(value).format("YYYY-MM-DD HH:mm:ss") : "--";
}
async function saveVersion() {
if (!selectedTemplate.value || !versionForm.versionNo || !versionForm.sourceDocxFileId) {
return ElMessage.warning("请填写版本号并上传 DOCX 文件");
}
if (
versionForm.effectiveDate
&& versionForm.expiryDate
&& !dayjs(versionForm.expiryDate).isAfter(dayjs(versionForm.effectiveDate))
) {
return ElMessage.warning("失效时间必须晚于生效时间");
}
await onboardingTaskApi.addVersion({ ...versionForm, templateId: selectedTemplate.value.id });
versionVisible.value = false;
await loadVersions();
@@ -295,6 +295,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {
OnboardingCompany,
OnboardingContractTemplate,
@@ -508,10 +509,23 @@ async function onContractTemplateChange(row: any) {
await loadContractVersions(row.contractTemplateId);
}
function isVersionCurrentlyAvailable(version: OnboardingContractVersion) {
const now = dayjs();
const effectiveAt = version.effectiveDate ? dayjs(version.effectiveDate) : undefined;
const expiresAt = version.expiryDate ? dayjs(version.expiryDate) : undefined;
return version.versionStatus === "enabled"
&& (!effectiveAt || (effectiveAt.isValid() && !effectiveAt.isAfter(now)))
&& (!expiresAt || (expiresAt.isValid() && expiresAt.isAfter(now)));
}
async function loadContractVersions(templateId: number) {
if (versionOptions[templateId]) return;
const res = await onboardingTaskApi.versionPage({ templateId, pageNum: 1, pageSize: 100 });
versionOptions[templateId] = (res.data?.list || []).filter((it: any) => it.versionStatus === "enabled");
const res = await onboardingTaskApi.versionPage({
templateId,
versionStatus: "enabled",
pageNum: 1,
pageSize: 100
});
versionOptions[templateId] = (res.data?.list || []).filter(isVersionCurrentlyAvailable);
}
function addContract() {
@@ -619,12 +633,25 @@ async function openEdit(row: OnboardingTemplate) {
const positionRes = await onboardingTaskApi.listPositionsByDepartment(form.departmentId);
positions.value = positionRes.data || [];
}
const configuredContractCount = form.contracts.length;
await loadAvailableContractTemplates();
const removedTemplateCount = configuredContractCount - form.contracts.length;
let clearedVersionCount = 0;
for (const contract of form.contracts) {
if (contract.contractTemplateId) {
await loadContractVersions(contract.contractTemplateId);
const availableVersions = versionOptions[contract.contractTemplateId] || [];
if (!availableVersions.some(item => item.id === contract.contractTemplateVersionId)) {
contract.contractTemplateVersionId = undefined as any;
clearedVersionCount++;
}
}
}
if (removedTemplateCount > 0 || clearedVersionCount > 0) {
ElMessage.warning(
"该入职模板原来关联了已停用、未生效或已失效的签署材料,已从当前选择中移除或清空,请重新选择后保存"
);
}
visible.value = true;
}
@@ -645,6 +672,14 @@ async function save() {
if (!form.contracts.length || form.contracts.some(item => !item.contractTemplateId || !item.contractTemplateVersionId)) {
return ElMessage.warning("请完整选择至少一份签署材料模板及版本");
}
const containsUnavailableVersion = form.contracts.some(contract => {
const versions = versionOptions[contract.contractTemplateId] || [];
const selected = versions.find(item => item.id === contract.contractTemplateVersionId);
return !selected || !isVersionCurrentlyAvailable(selected);
});
if (containsUnavailableVersion) {
return ElMessage.warning("所选签署材料版本已停用、尚未生效或已经失效,请重新选择");
}
saving.value = true;
try {
+95 -33
View File
@@ -78,9 +78,17 @@
</template>
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
<el-tooltip
v-if="scope.row.companyName || getCompanyName(scope.row.companyId)"
:content="scope.row.companyName || getCompanyName(scope.row.companyId)"
placement="top"
:show-after="300"
>
<el-tag effect="plain" type="primary" class="truncate-tag">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
<template #status="scope">
@@ -135,15 +143,30 @@
</template>
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
<el-tooltip
v-if="scope.row.companyName || getCompanyName(scope.row.companyId)"
:content="scope.row.companyName || getCompanyName(scope.row.companyId)"
placement="top"
:show-after="300"
>
<el-tag effect="plain" type="primary" class="truncate-tag">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
<template #departmentName="scope">
<el-tag v-if="scope.row.departmentName" effect="light" type="warning">
{{ scope.row.departmentName }}
</el-tag>
<el-tooltip
v-if="scope.row.departmentName"
:content="scope.row.departmentName"
placement="top"
:show-after="300"
>
<el-tag effect="light" type="warning" class="truncate-tag">
{{ scope.row.departmentName }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
@@ -204,18 +227,18 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="公司编码" prop="companyCode">
<el-input v-model.trim="companyForm.companyCode" placeholder="如:TECH_CORP" clearable />
<el-input v-model.trim="companyForm.companyCode" placeholder="如:TECH_CORP" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司简称/名称" prop="companyName">
<el-input v-model.trim="companyForm.companyName" placeholder="如:总公司 / 科技分部" clearable />
<el-input v-model.trim="companyForm.companyName" placeholder="如:总公司 / 科技分部" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="签署材料甲方全称" prop="legalName">
<el-input v-model.trim="companyForm.legalName" placeholder="必须与契约锁中的企业名称完全一致" clearable />
<el-input v-model.trim="companyForm.legalName" placeholder="必须与契约锁中的企业名称完全一致" maxlength="100" show-word-limit clearable />
</el-form-item>
<el-row :gutter="20">
@@ -225,14 +248,15 @@
v-model.trim="companyForm.unifiedSocialCreditCode"
placeholder="18位统一社会信用代码"
maxlength="18"
show-word-limit
clearable
@input="val => companyForm.unifiedSocialCreditCode = (val || '').toUpperCase()"
@input="val => companyForm.unifiedSocialCreditCode = (val || '').replace(/[^0-9A-Za-z]/g, '').toUpperCase()"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法定代表人" prop="legalRepresentative">
<el-input v-model.trim="companyForm.legalRepresentative" placeholder="法定代表人姓名" clearable />
<el-input v-model.trim="companyForm.legalRepresentative" placeholder="法定代表人姓名" maxlength="20" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -240,7 +264,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model.trim="companyForm.contactPhone" placeholder="企业联系电话(手机或固话)" clearable />
<el-input v-model.trim="companyForm.contactPhone" placeholder="企业联系电话(手机或固话)" maxlength="20" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -254,11 +278,11 @@
</el-row>
<el-form-item label="注册地址" prop="registeredAddress">
<el-input v-model.trim="companyForm.registeredAddress" placeholder="企业法定注册地址" clearable />
<el-input v-model.trim="companyForm.registeredAddress" placeholder="企业法定注册地址" maxlength="150" show-word-limit clearable />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="companyForm.remark" type="textarea" :rows="2" placeholder="备注信息(选填)" />
<el-input v-model="companyForm.remark" type="textarea" :rows="3" placeholder="备注信息(选填)" maxlength="300" show-word-limit />
</el-form-item>
<el-divider content-position="left">契约锁电子签配置(由技术人员添加)</el-divider>
@@ -272,12 +296,26 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="默认业务分类 Id" prop="qiyuesuoCategoryId">
<el-input v-model.trim="companyForm.qiyuesuoCategoryId" placeholder="契约锁当前公司的分类 Id" clearable />
<el-input
v-model.trim="companyForm.qiyuesuoCategoryId"
placeholder="契约锁当前公司的分类 Id"
maxlength="19"
show-word-limit
clearable
@input="val => companyForm.qiyuesuoCategoryId = (val || '').replace(/\D/g, '')"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业印章 Id" prop="qiyuesuoSealId">
<el-input v-model.trim="companyForm.qiyuesuoSealId" placeholder="契约锁当前公司的印章 Id" clearable />
<el-input
v-model.trim="companyForm.qiyuesuoSealId"
placeholder="契约锁当前公司的印章 Id"
maxlength="19"
show-word-limit
clearable
@input="val => companyForm.qiyuesuoSealId = (val || '').replace(/\D/g, '')"
/>
</el-form-item>
</el-col>
</el-row>
@@ -299,12 +337,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="部门编码" required>
<el-input v-model="departmentForm.departmentCode" placeholder="如:DEPT_TECH" />
<el-input v-model.trim="departmentForm.departmentCode" placeholder="如:DEPT_TECH" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" required>
<el-input v-model="departmentForm.departmentName" placeholder="如:技术研发部" />
<el-input v-model.trim="departmentForm.departmentName" placeholder="如:技术研发部" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -326,7 +364,7 @@
</el-row>
<el-form-item label="备注">
<el-input v-model="departmentForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
<el-input v-model="departmentForm.remark" type="textarea" :rows="2" placeholder="备注信息" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
@@ -370,12 +408,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="岗位编码" required>
<el-input v-model="positionForm.positionCode" placeholder="如:DEV_FRONTEND" />
<el-input v-model.trim="positionForm.positionCode" placeholder="如:DEV_FRONTEND" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位名称" required>
<el-input v-model="positionForm.positionName" placeholder="如:高级前端开发工程师" />
<el-input v-model.trim="positionForm.positionName" placeholder="如:高级前端开发工程师" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -401,7 +439,7 @@
</el-form-item>
<el-form-item label="备注">
<el-input v-model="positionForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
<el-input v-model="positionForm.remark" type="textarea" :rows="2" placeholder="备注信息" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
@@ -505,7 +543,7 @@ const companyRules = reactive<FormRules>({
],
companyName: [
{ required: true, message: "请输入公司简称/名称", trigger: "blur" },
{ min: 2, max: 100, message: "公司名称长度需在 2 到 100 个字符之间", trigger: "blur" }
{ min: 2, max: 50, message: "公司名称长度需在 2 到 50 个字符之间", trigger: "blur" }
],
legalName: [
{ required: true, message: "请输入签署材料甲方全称", trigger: "blur" },
@@ -540,21 +578,26 @@ const companyRules = reactive<FormRules>({
],
registeredAddress: [
{ required: true, message: "请输入企业法定注册地址", trigger: "blur" },
{ min: 4, max: 200, message: "注册地址请至少输入4个字符", trigger: "blur" }
{ min: 4, max: 150, message: "注册地址需在 4 到 150 个字符之间", trigger: "blur" }
],
remark: [
{ max: 300, message: "备注不能超过 300 个字符", trigger: "blur" }
],
qiyuesuoCategoryId: [
{
pattern: /^\d*$/,
message: "业务分类 Id 必须为纯数字",
trigger: "blur"
}
},
{ max: 19, message: "业务分类 Id 不能超过 19 位数字", trigger: "blur" }
],
qiyuesuoSealId: [
{
pattern: /^\d*$/,
message: "企业印章 Id 必须为纯数字",
trigger: "blur"
}
},
{ max: 19, message: "企业印章 Id 不能超过 19 位数字", trigger: "blur" }
]
});
const departmentForm = reactive<Partial<OnboardingDepartment>>(emptyDepartment());
@@ -646,7 +689,8 @@ const departmentColumns: ColumnProps<OnboardingDepartment>[] = [
prop: "departmentName",
label: "部门名称",
minWidth: 180,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "sortCode",
@@ -693,13 +737,15 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
prop: "positionCode",
label: "岗位编码",
width: 150,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "positionName",
label: "岗位名称",
minWidth: 180,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "defaultContractYears",
@@ -831,7 +877,10 @@ async function save() {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 必须同时填写(或均不填)");
}
if (categoryId && (!/^\d+$/.test(categoryId) || !/^\d+$/.test(sealId))) {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 只能填写数字");
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 只能填写纯数字");
}
if (categoryId && (categoryId.length > 19 || sealId.length > 19)) {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 不能超过 19 位数字");
}
companyForm.qiyuesuoCategoryId = categoryId;
companyForm.qiyuesuoSealId = sealId;
@@ -971,4 +1020,17 @@ onMounted(loadCompanies);
padding-top: 3px;
line-height: 1.25;
}
.truncate-tag {
max-width: 140px;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.truncate-tag :deep(.el-tag__content) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
+32 -3
View File
@@ -769,7 +769,7 @@
<el-descriptions-item
v-for="item in detailCustomVars"
:key="item.key"
:label="item.key"
:label="item.label"
>
{{ item.value || '-' }}
</el-descriptions-item>
@@ -1685,10 +1685,30 @@ const detailFamilyList = computed(() => {
}
});
const registrationEmployeeFieldCodes = new Set([
"employeename", "name", "username", "gender", "nation", "birthday", "registeredresidence",
"height", "weight", "healthcondition", "maritalstatus", "education", "major", "graduatedschool",
"college", "vocationalqualification", "qualificationlevel", "technicalspecialty", "phone",
"mobile", "email", "idcardnumber", "idcard", "currentaddress", "contactaddress", "landlordcontact",
"bankcardnumber", "bankcard", "bankname", "bankaccountname", "emergencycontact", "emergencyphone",
"emergencyaddress", "onboardingdepartment", "onboardingdate", "onboardingposition", "otherremarks"
]);
const detailContractVariableNames = ref<Record<string, string>>({});
function isRegistrationEmployeeVariable(variableCode: string) {
const fieldCode = (String(variableCode || "").split(".")[1] || variableCode || "").toLowerCase();
return registrationEmployeeFieldCodes.has(fieldCode);
}
const detailCustomVars = computed(() => {
try {
const obj = JSON.parse(detail.value?.employeeInfo?.customVariablesJson || "{}");
return Object.entries(obj).map(([k, v]) => ({ key: k, value: String(v) }));
return Object.entries(obj)
.filter(([key]) => !isRegistrationEmployeeVariable(key))
.map(([key, value]) => ({
key,
label: detailContractVariableNames.value[key] || key,
value: value == null ? "" : String(value)
}));
} catch {
return [];
}
@@ -2604,7 +2624,16 @@ async function copyOrShareTaskLink(row: OnboardingTask) {
async function showDetail(row: OnboardingTask, preserveTab: boolean = false) {
const currentTab = activeDetailTab.value;
const { data }: any = await onboardingTaskApi.detail({ id: row.id });
const [{ data }, variableResponse]: any[] = await Promise.all([
onboardingTaskApi.detail({ id: row.id }),
onboardingTaskApi.taskContractVariables(row.id).catch(() => ({ data: [] }))
]);
detailContractVariableNames.value = Object.fromEntries(
(variableResponse.data || [])
.flatMap((group: OnboardingTaskContractVariableGroup) => group.variables || [])
.filter((variable: any) => variable.variableType === "employee" && !isRegistrationEmployeeVariable(variable.variableCode))
.map((variable: any) => [variable.variableCode, variable.variableName || variable.variableCode])
);
detail.value = data;
if (!preserveTab) {
activeDetailTab.value = "profile";
+50 -34
View File
@@ -76,33 +76,32 @@
<!-- 步骤导航条:PC端展示完整步骤,移动端展示紧凑自适应导航 -->
<div class="steps-wrapper desktop-only">
<el-steps :active="currentStep" finish-status="success" align-center>
<el-step title="入职登记表" icon="User" />
<el-step title="材料补充项" icon="Document" />
<el-step title="证件材料" icon="FolderChecked" />
<el-step title="材料确认" icon="Tickets" />
<el-step v-for="step in visibleSteps" :key="step.key" :title="step.title" :icon="step.icon" />
</el-steps>
</div>
<div class="mobile-steps-wrapper mobile-only">
<div class="mobile-steps-track">
<div v-for="(stepName, index) in ['1. 登记表', '2. 补充项', '3. 证件材料', '4. 材料确认']" :key="index"
<div v-for="(step, index) in visibleSteps" :key="step.key"
class="mobile-step-item" :class="{
'is-active': currentStep === index,
'is-finished': currentStep > index,
'is-pending': currentStep < index
}">
<div class="step-indicator-bar" />
<span class="step-label">{{ stepName }}</span>
<span class="step-label">{{ (index + 1) + '. ' + step.shortTitle }}</span>
</div>
</div>
<div class="mobile-current-indicator">
当前进行:<span class="highlight-step">第 {{ currentStep + 1 }}/4 步 · {{ ['入职登记表填写', '签署材料补充项', '证件材料上传',
'签署材料查阅与确认'][currentStep] }}</span>
当前进行:<span class="highlight-step">第 {{ currentStep + 1 }}/{{ visibleSteps.length }} 步 · {{ currentStepDefinition.description }}</span>
</div>
</div>
<!-- 步骤 1: 入职登记表 -->
<div v-show="currentStep === 0" class="step-content">
<div v-show="currentStepKey === 'registration'" class="step-content">
<div class="registration-header">
<h2 class="reg-title">入 职 登 记 表</h2>
<p class="reg-subtitle">请如实填写个人基础档案、教育经历及家庭相关信息,标注 * 为必填项,信息将用于建立人事档案,请务必保证资料真实准确。</p>
@@ -458,23 +457,13 @@
</div>
<!-- 步骤 2: 合同专属增量补充项 (自适应差集) -->
<div v-show="currentStep === 1" class="step-content">
<div v-if="extraVariables.length" v-show="currentStepKey === 'extra'" class="step-content">
<div class="section-header">
<h3 class="section-title">签署材料专属补充信息</h3>
<p class="section-desc">以下是本次签署材料中特殊约定的增量字段,登记表未涵盖的项需在此补充确认</p>
</div>
<div v-if="!extraVariables.length" class="all-covered-card">
<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">
<el-form label-position="top" class="extra-form">
<el-alert title="请补充填写以下签署材料专有信息:" type="info" show-icon :closable="false" class="mb-3" />
<el-row :gutter="16">
<el-col v-for="(v, index) in extraVariables" :id="`extra-variable-${index}`" :key="v.variableCode"
@@ -503,7 +492,7 @@
</div>
<!-- 步骤 3: 证件材料上传 -->
<div v-show="currentStep === 2" class="step-content">
<div v-show="currentStepKey === 'materials'" class="step-content">
<div class="section-header">
<h3 class="section-title">证件与材料上传</h3>
<p class="section-desc">请上传清晰真实有效的证件扫描件或照片(支持 PNG、JPG、PDF)</p>
@@ -588,7 +577,7 @@
</div>
<!-- 步骤 4: 合同查阅与签署 -->
<div v-show="currentStep === 3" class="step-content">
<div v-show="currentStepKey === 'confirmation'" class="step-content">
<div v-if="canEdit" id="identity-confirmation-card" class="identity-confirmation-card">
<div class="identity-confirmation-heading">
<div>
@@ -678,13 +667,13 @@
<!-- 底部向导操作栏 -->
<div class="wizard-footer">
<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)"
<el-button v-if="currentStep < visibleSteps.length - 1" type="primary" size="large"
:loading="savingStep || (currentStepKey === 'materials' && isAnyMaterialUploading)"
:disabled="savingStep || isAnyMaterialUploading"
@click="nextStep">
{{ (currentStep === 2 && isAnyMaterialUploading) ? "材料上传中..." : (savingStep ? "保存中..." : "下一步") }}
{{ (currentStepKey === 'materials' && isAnyMaterialUploading) ? "材料上传中..." : (savingStep ? "保存中..." : "下一步") }}
</el-button>
<el-button v-if="currentStep === 3 && canEdit" type="success" size="large" icon="Check"
<el-button v-if="currentStepKey === 'confirmation' && canEdit" type="success" size="large" icon="Check"
:loading="submitting" :disabled="savingStep || isAnyMaterialUploading" @click="submitAll">
确认无误并提交入职资料
</el-button>
@@ -1159,6 +1148,23 @@ const extraVariables = computed(() => {
return result;
});
type OnboardingStepKey = "registration" | "extra" | "materials" | "confirmation";
const visibleSteps = computed(() => {
const steps = [
{ key: "registration" as OnboardingStepKey, title: "入职登记表", shortTitle: "登记表", description: "入职登记表填写", icon: "User" }
];
if (extraVariables.value.length) {
steps.push({ key: "extra", title: "材料补充项", shortTitle: "补充项", description: "签署材料补充项", icon: "Document" });
}
steps.push(
{ key: "materials", title: "证件材料", shortTitle: "证件材料", description: "证件材料上传", icon: "FolderChecked" },
{ key: "confirmation", title: "材料确认", shortTitle: "材料确认", description: "签署材料查阅与确认", icon: "Tickets" }
);
return steps;
});
const currentStepDefinition = computed(() => visibleSteps.value[currentStep.value] || visibleSteps.value[0]);
const currentStepKey = computed<OnboardingStepKey>(() => currentStepDefinition.value.key);
function addEducation() {
educationHistory.value.push({ timeRange: "", location: "", content: "", result: "" });
}
@@ -1459,7 +1465,8 @@ async function load(silent = false) {
uploadedMaterials.value = data.materials || [];
contractVariables.value = data.contractVariables || [];
formFieldRequirements.value = data.formFields || [];
contractVariables.value.forEach((variable: any) => {
Object.keys(customVariables).forEach(key => delete customVariables[key]);
extraVariables.value.forEach((variable: any) => {
if (!(variable.variableCode in customVariables) && variable.defaultValue != null) {
customVariables[variable.variableCode] = String(variable.defaultValue);
}
@@ -1478,7 +1485,14 @@ async function load(silent = false) {
try { familyMembers.value = JSON.parse(data.employeeInfo.familyMembersJson); } catch { }
}
if (data.employeeInfo.customVariablesJson) {
try { Object.assign(customVariables, JSON.parse(data.employeeInfo.customVariablesJson)); } catch { }
try {
const savedVariables = JSON.parse(data.employeeInfo.customVariablesJson);
extraVariables.value.forEach((variable: any) => {
if (Object.prototype.hasOwnProperty.call(savedVariables, variable.variableCode)) {
customVariables[variable.variableCode] = savedVariables[variable.variableCode];
}
});
} catch { }
}
}
await refreshMaterialPreviews();
@@ -1505,7 +1519,9 @@ async function saveCurrentEmployeeInfo() {
educationHistoryJson: JSON.stringify(educationHistory.value),
workHistoryJson: JSON.stringify(workHistory.value),
familyMembersJson: JSON.stringify(familyMembers.value),
customVariablesJson: JSON.stringify(customVariables)
customVariablesJson: JSON.stringify(Object.fromEntries(
extraVariables.value.map((variable: any) => [variable.variableCode, customVariables[variable.variableCode] ?? ""])
))
};
await onboardingPublicApi.saveEmployeeInfo(payload);
}
@@ -1546,7 +1562,7 @@ async function nextStep() {
}
savingStep.value = true;
try {
if (currentStep.value === 0) {
if (currentStepKey.value === "registration") {
if (!await validateRegistrationForm()) {
return;
}
@@ -1558,14 +1574,14 @@ async function nextStep() {
ElMessage.closeAll();
ElMessage.success("入职登记表已临时保存");
}
} else if (currentStep.value === 1) {
} else if (currentStepKey.value === "extra") {
if (!validateExtraVariables()) {
return;
}
if (canEdit.value) {
await saveCurrentEmployeeInfo();
}
} else if (currentStep.value === 2) {
} else if (currentStepKey.value === "materials") {
if (isAnyMaterialUploading.value) {
ElMessage.closeAll();
ElMessage.warning("材料正在上传中,请稍候再点击下一步");
@@ -1581,7 +1597,7 @@ async function nextStep() {
}
currentStep.value++;
window.scrollTo({ top: 0, behavior: "smooth" });
if (currentStep.value === 3) {
if (currentStepKey.value === "confirmation") {
await prepareContracts();
}
} catch (error: any) {