This commit is contained in:
fuenmao
2026-08-28 17:32:47 +08:00
parent 14515ccdd6
commit e2697b4bdb
9 changed files with 249 additions and 174 deletions
+2 -2
View File
@@ -25,10 +25,10 @@ VITE_PWA = true
VITE_HTTP_PROXY = true VITE_HTTP_PROXY = true
# 线上环境接口地址 # 线上环境接口地址
VITE_API_URL = http://medical.23544.com:7106/ VITE_API_URL = https://onboardx-api.cnzhyx.cn/
# 开发环境跨域代理,支持配置多个 # 开发环境跨域代理,支持配置多个
VITE_PROXY = [["/api","http://medical.23544.com:7106/"]] VITE_PROXY = [["/api","https://onboardx-api.cnzhyx.cn/"]]
#是否启用mqtt #是否启用mqtt
VITE_MQTT = false VITE_MQTT = false
+3 -1
View File
@@ -145,13 +145,15 @@ export interface OnboardingEmployee {
employeeNo?: string; employeeNo?: string;
employeeName: string; employeeName: string;
companyId: number; companyId: number;
companyName?: string;
departmentId?: number; departmentId?: number;
departmentName?: string; departmentName?: string;
positionId: number; positionId: number;
positionName?: string;
phone: string; phone: string;
idCardNumber: string; idCardNumber: string;
entryDate?: string; entryDate?: string;
createTime: string; createTime?: string;
} }
export interface OnboardingFormField { export interface OnboardingFormField {
+21 -21
View File
@@ -2,13 +2,13 @@
<div class="table-box"> <div class="table-box">
<ProTable <ProTable
ref="templateTable" ref="templateTable"
title="合同模板列表" title="签署材料模板列表"
:columns="templateColumns" :columns="templateColumns"
:request-api="onboardingTaskApi.templatePage" :request-api="onboardingTaskApi.templatePage"
> >
<template #tableHeader> <template #tableHeader>
<el-button type="primary" icon="Plus" @click="openTemplateCreate"> <el-button type="primary" icon="Plus" @click="openTemplateCreate">
新增合同模板 新增签署材料模板
</el-button> </el-button>
</template> </template>
@@ -60,10 +60,10 @@
</template> </template>
</ProTable> </ProTable>
<!-- 新增 / 编辑合同模板基础信息弹窗 --> <!-- 新增 / 编辑签署材料模板基础信息弹窗 -->
<el-dialog <el-dialog
v-model="templateVisible" v-model="templateVisible"
:title="templateEditing ? '编辑合同模板' : '新增合同模板'" :title="templateEditing ? '编辑签署材料模板' : '新增签署材料模板'"
width="720px" width="720px"
destroy-on-close destroy-on-close
append-to-body append-to-body
@@ -112,7 +112,7 @@
collapse-tags collapse-tags
collapse-tags-tooltip collapse-tags-tooltip
class="w-full" class="w-full"
placeholder="请选择该合同可适用的岗位" placeholder="请选择该签署材料可适用的岗位"
:disabled="!templateForm.departmentId" :disabled="!templateForm.departmentId"
> >
<el-option <el-option
@@ -132,7 +132,7 @@
<el-input v-model="templateForm.templateName" placeholder="如:标准全职劳动合同" /> <el-input v-model="templateForm.templateName" placeholder="如:标准全职劳动合同" />
</el-form-item> </el-form-item>
<el-form-item label="合同类型" required> <el-form-item label="材料类型" required>
<el-input v-model="templateForm.contractType" placeholder="如:劳动合同、保密协议、竞业协议" /> <el-input v-model="templateForm.contractType" placeholder="如:劳动合同、保密协议、竞业协议" />
</el-form-item> </el-form-item>
@@ -144,7 +144,7 @@
</el-form-item> </el-form-item>
<el-form-item label="模板说明"> <el-form-item label="模板说明">
<el-input v-model="templateForm.description" type="textarea" :rows="3" placeholder="合同模板适用说明或签署注意事项" /> <el-input v-model="templateForm.description" type="textarea" :rows="3" placeholder="签署材料模板适用说明或签署注意事项" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -154,10 +154,10 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 一站式合同版本与配置大抽屉 --> <!-- 一站式签署材料版本与配置大抽屉 -->
<el-drawer <el-drawer
v-model="drawerVisible" v-model="drawerVisible"
:title="`合同模板管理 · ${selectedTemplate?.templateName || ''}`" :title="`签署材料模板管理 · ${selectedTemplate?.templateName || ''}`"
size="1150px" size="1150px"
destroy-on-close destroy-on-close
> >
@@ -188,7 +188,7 @@
上传新版本 DOCX 上传新版本 DOCX
</el-button> </el-button>
<span class="pane-tip-text"> <span class="pane-tip-text">
支持多版本迭代。启用某个版本后,该版本的 DOCX 将用于生成新入职员工的合同。 支持多版本迭代。启用某个版本后,该版本的 DOCX 将用于生成新入职员工的签署材料。
</span> </span>
</div> </div>
@@ -274,7 +274,7 @@
</template> </template>
</el-drawer> </el-drawer>
<!-- 上传合同 DOCX 版本弹窗 --> <!-- 上传签署材料 DOCX 版本弹窗 -->
<el-dialog <el-dialog
v-model="versionVisible" v-model="versionVisible"
title="上传新版本 DOCX" title="上传新版本 DOCX"
@@ -334,7 +334,7 @@
<!-- 变量配置弹窗 --> <!-- 变量配置弹窗 -->
<el-dialog <el-dialog
v-model="variablesVisible" v-model="variablesVisible"
:title="`合同变量配置 · 版本 ${selectedVersion?.versionNo || ''}`" :title="`签署材料变量配置 · 版本 ${selectedVersion?.versionNo || ''}`"
width="1220px" width="1220px"
top="4vh" top="4vh"
destroy-on-close destroy-on-close
@@ -353,7 +353,7 @@
<el-tabs v-model="variableTab" type="card" class="variable-filter-tabs"> <el-tabs v-model="variableTab" type="card" class="variable-filter-tabs">
<el-tab-pane :label="`全部变量 (${variableCounts.all})`" name="all" /> <el-tab-pane :label="`全部变量 (${variableCounts.all})`" name="all" />
<el-tab-pane :label="`员工填写 (${variableCounts.employee})`" name="employee" /> <el-tab-pane :label="`员工填写 (${variableCounts.employee})`" name="employee" />
<el-tab-pane :label="`人事合同参数 (${variableCounts.hr})`" name="hr" /> <el-tab-pane :label="`人事预设参数 (${variableCounts.hr})`" name="hr" />
<el-tab-pane :label="`公司信息 (${variableCounts.company})`" name="company" /> <el-tab-pane :label="`公司信息 (${variableCounts.company})`" name="company" />
<el-tab-pane :label="`系统预置 (${variableCounts.system})`" name="system" /> <el-tab-pane :label="`系统预置 (${variableCounts.system})`" name="system" />
</el-tabs> </el-tabs>
@@ -439,7 +439,7 @@
<el-select v-model="variableForm.variableType" class="w-full"> <el-select v-model="variableForm.variableType" class="w-full">
<el-option label="员工填写(从新员工基本信息/动态字段读取)" value="employee" /> <el-option label="员工填写(从新员工基本信息/动态字段读取)" value="employee" />
<el-option label="公司信息(从公司档案自动读取)" value="company" /> <el-option label="公司信息(从公司档案自动读取)" value="company" />
<el-option label="合同/人事填写(人事发起时录入)" value="contract" /> <el-option label="人事预设填写(人事发起时录入)" value="contract" />
<el-option label="系统生成(签署日期等)" value="system" /> <el-option label="系统生成(签署日期等)" value="system" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -630,7 +630,7 @@ const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
}, },
{ prop: "templateCode", label: "模板编码", minWidth: 160, search: { el: "input" } }, { prop: "templateCode", label: "模板编码", minWidth: 160, search: { el: "input" } },
{ prop: "templateName", label: "模板名称", minWidth: 200, search: { el: "input" } }, { prop: "templateName", label: "模板名称", minWidth: 200, search: { el: "input" } },
{ prop: "contractType", label: "合同类型", width: 140, search: { el: "input" } }, { prop: "contractType", label: "材料类型", width: 140, search: { el: "input" } },
{ {
prop: "status", prop: "status",
label: "状态", label: "状态",
@@ -738,7 +738,7 @@ async function saveTemplate() {
|| !templateForm.templateName || !templateForm.templateName
|| !templateForm.contractType || !templateForm.contractType
) { ) {
return ElMessage.warning("请完整填写公司、部门、适用岗位及合同模板基本信息"); return ElMessage.warning("请完整填写公司、部门、适用岗位及签署材料模板基本信息");
} }
await onboardingTaskApi.saveTemplate(templateForm, templateEditing.value); await onboardingTaskApi.saveTemplate(templateForm, templateEditing.value);
templateVisible.value = false; templateVisible.value = false;
@@ -747,7 +747,7 @@ async function saveTemplate() {
} }
async function removeTemplate(row: OnboardingContractTemplate) { async function removeTemplate(row: OnboardingContractTemplate) {
await ElMessageBox.confirm(`确认删除合同模板“${row.templateName}”?`, "删除确认", { type: "warning" }); await ElMessageBox.confirm(`确认删除签署材料模板“${row.templateName}”?`, "删除确认", { type: "warning" });
await onboardingTaskApi.deleteTemplate([row.id]); await onboardingTaskApi.deleteTemplate([row.id]);
templateTable.value?.refresh(); templateTable.value?.refresh();
ElMessage.success("删除成功"); ElMessage.success("删除成功");
@@ -760,7 +760,7 @@ function openVersionCreate() {
function beforeDocxUpload(file: File) { function beforeDocxUpload(file: File) {
if (!file.name.toLowerCase().endsWith(".docx")) { if (!file.name.toLowerCase().endsWith(".docx")) {
ElMessage.error("合同模板仅支持 DOCX 文件"); ElMessage.error("签署材料模板仅支持 DOCX 文件");
return false; return false;
} }
return true; return true;
@@ -796,7 +796,7 @@ async function changeVersionStatus(row: OnboardingContractVersion, status: strin
} }
async function removeVersion(row: OnboardingContractVersion) { async function removeVersion(row: OnboardingContractVersion) {
await ElMessageBox.confirm(`确认删除合同版本“${row.versionNo}”?此操作不可撤销。`, "删除确认", { type: "warning" }); await ElMessageBox.confirm(`确认删除签署材料版本“${row.versionNo}”?此操作不可撤销。`, "删除确认", { type: "warning" });
await onboardingTaskApi.deleteVersion(row.id); await onboardingTaskApi.deleteVersion(row.id);
await loadVersions(); await loadVersions();
ElMessage.success("版本删除成功"); ElMessage.success("版本删除成功");
@@ -819,7 +819,7 @@ async function downloadDocx(fileId?: number) {
const encodedName = const encodedName =
disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] || disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] ||
disposition.match(/filename="?([^";]+)"?/i)?.[1]; disposition.match(/filename="?([^";]+)"?/i)?.[1];
let fileName = "合同模板.docx"; let fileName = "签署材料模板.docx";
if (encodedName) { if (encodedName) {
try { try {
fileName = decodeURIComponent(encodedName.replace(/\+/g, "%20")); fileName = decodeURIComponent(encodedName.replace(/\+/g, "%20"));
@@ -870,7 +870,7 @@ function variableTypeText(type: string) {
const map: Record<string, string> = { const map: Record<string, string> = {
employee: "员工填写", employee: "员工填写",
company: "公司信息", company: "公司信息",
contract: "人事合同参数", contract: "人事预设参数",
system: "系统自动", system: "系统自动",
unresolved: "待确认" unresolved: "待确认"
}; };
+160 -87
View File
@@ -1,43 +1,32 @@
<template> <template>
<div class="table-box"> <div class="table-box">
<ProTable <ProTable ref="proTable" title="正式员工档案" :columns="columns" :request-api="onboardingTaskApi.employeePage">
ref="proTable"
title="正式员工档案"
:columns="columns"
:request-api="onboardingTaskApi.employeePage"
>
<template #operation="scope"> <template #operation="scope">
<el-button link type="primary" icon="UserFilled" @click="showDetail(scope.row)"> <el-button link type="primary" icon="UserFilled" @click="showDetail(scope.row)"> 查看完整档案 </el-button>
查看完整档案
</el-button>
</template> </template>
</ProTable> </ProTable>
<!-- 员工完整档案抽屉 --> <!-- 员工完整档案抽屉 -->
<el-drawer <el-drawer v-model="detailVisible" :title="`员工档案详情 · ${detail?.employee?.employeeName || ''}`" size="820px" destroy-on-close>
v-model="detailVisible"
:title="`员工档案详情 · ${detail?.employee?.employeeName || ''}`"
size="820px"
destroy-on-close
>
<div v-if="detail?.employee" class="employee-drawer-content"> <div v-if="detail?.employee" class="employee-drawer-content">
<!-- 头部个人简要信息 --> <!-- 头部个人简要信息 -->
<div class="employee-header-card"> <div class="employee-header-card">
<el-avatar :size="64" class="employee-avatar"> <el-avatar :size="64" class="employee-avatar">
{{ detail.employee.employeeName ? detail.employee.employeeName.substring(0, 1) : '员' }} {{ detail.employee.employeeName ? detail.employee.employeeName.substring(0, 1) : "员" }}
</el-avatar> </el-avatar>
<div class="employee-header-info"> <div class="employee-header-info">
<div class="employee-name-row"> <div class="employee-name-row">
<span class="employee-name">{{ detail.employee.employeeName }}</span> <span class="employee-name">{{ detail.employee.employeeName }}</span>
<el-tag size="small" :type="detail.employee.gender === '女' ? 'danger' : 'primary'" effect="plain"> <el-tag size="small" :type="detail.employee.gender === '女' ? 'danger' : 'primary'" effect="plain">
{{ detail.employee.gender || '未知' }} {{ detail.employee.gender || "未知" }}
</el-tag> </el-tag>
<el-tag size="small" type="success">正式员工</el-tag> <el-tag size="small" type="success">正式员工</el-tag>
</div> </div>
<div class="employee-meta-row"> <div class="employee-meta-row">
<span>员工编号:{{ detail.employee.employeeNo || '-' }}</span> <span>所属公司:{{ detail.employee.companyName || "-" }}</span>
<span>联系电话:{{ detail.employee.phone }}</span> <span>所属部门:{{ detail.employee.departmentName || "-" }}</span>
<span>入职日期:{{ detail.employee.entryDate || '-' }}</span> <span>所属岗位:{{ detail.employee.positionName || "-" }}</span>
<span>入职日期:{{ formatDate(detail.employee.entryDate) }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -46,38 +35,64 @@
<!-- 1. 基本人事档案 --> <!-- 1. 基本人事档案 -->
<el-tab-pane label="基本人事档案" name="basic"> <el-tab-pane label="基本人事档案" name="basic">
<el-card shadow="never" class="section-card"> <el-card shadow="never" class="section-card">
<template #header> <template #header><div class="card-header-title">个人基本信息</div></template>
<div class="card-header-title">身份与联系信息</div>
</template>
<el-descriptions :column="2" border> <el-descriptions :column="2" border>
<el-descriptions-item label="姓名">{{ detail.employee.employeeName }}</el-descriptions-item> <el-descriptions-item label="姓名">{{ employeeInfo.employeeName || detail.employee.employeeName }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.employee.gender || '-' }}</el-descriptions-item> <el-descriptions-item label="性别">{{ employeeInfo.gender || detail.employee.gender || "-" }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detail.employee.phone }}</el-descriptions-item> <el-descriptions-item label="民族">{{ employeeInfo.nation || detail.employee.nation || "-" }}</el-descriptions-item>
<el-descriptions-item label="身份证号码">{{ detail.employee.idCardNumber }}</el-descriptions-item> <el-descriptions-item label="出生日期">{{ employeeInfo.birthday || formatDate(detail.employee.birthDate) }}</el-descriptions-item>
<el-descriptions-item label="出生日期">{{ detail.employee.birthDate || '-' }}</el-descriptions-item> <el-descriptions-item label="身高">{{ employeeInfo.height ? `${employeeInfo.height} cm` : "-" }}</el-descriptions-item>
<el-descriptions-item label="民族">{{ detail.employee.nation || '-' }}</el-descriptions-item> <el-descriptions-item label="体重">{{ employeeInfo.weight ? `${employeeInfo.weight} kg` : "-" }}</el-descriptions-item>
<el-descriptions-item label="户口所在地" :span="2"> <el-descriptions-item label="健康状况">{{ employeeInfo.healthCondition || "-" }}</el-descriptions-item>
{{ detail.employee.registeredResidence || '-' }} <el-descriptions-item label="婚育情况">{{ employeeInfo.maritalStatus || "-" }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="联系电话">{{ employeeInfo.phone || detail.employee.phone || "-" }}</el-descriptions-item>
<el-descriptions-item label="现居通讯地址" :span="2"> <el-descriptions-item label="电子邮箱">{{ employeeInfo.email || "-" }}</el-descriptions-item>
{{ detail.employee.contactAddress || '-' }} <el-descriptions-item label="身份证号码" :span="2">{{
</el-descriptions-item> employeeInfo.idCardNumber || detail.employee.idCardNumber || "-"
}}</el-descriptions-item>
<el-descriptions-item label="户口所在地" :span="2">{{
employeeInfo.registeredResidence || detail.employee.registeredResidence || "-"
}}</el-descriptions-item>
<el-descriptions-item label="现居通讯地址" :span="2">{{
employeeInfo.currentAddress || detail.employee.contactAddress || "-"
}}</el-descriptions-item>
<el-descriptions-item label="房东联系方式" :span="2">{{ employeeInfo.landlordContact || "-" }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-card> </el-card>
<el-card shadow="never" class="section-card mt-4"> <el-card shadow="never" class="section-card mt-4">
<template #header> <template #header><div class="card-header-title">学历、资质与特长</div></template>
<div class="card-header-title">学历与就职信息</div>
</template>
<el-descriptions :column="2" border> <el-descriptions :column="2" border>
<el-descriptions-item label="毕业院校">{{ detail.employee.college || '-' }}</el-descriptions-item> <el-descriptions-item label="最高学历">{{ employeeInfo.education || detail.employee.education || "-" }}</el-descriptions-item>
<el-descriptions-item label="最高学历">{{ detail.employee.education || '-' }}</el-descriptions-item> <el-descriptions-item label="所学专业">{{ employeeInfo.major || detail.employee.major || "-" }}</el-descriptions-item>
<el-descriptions-item label="所学专业">{{ detail.employee.major || '-' }}</el-descriptions-item> <el-descriptions-item label="毕业院校" :span="2">{{
<el-descriptions-item label="部门名称">{{ detail.employee.departmentName || '-' }}</el-descriptions-item> employeeInfo.graduatedSchool || detail.employee.college || "-"
<el-descriptions-item label="合同社保单位">{{ detail.employee.socialInsuranceCompany || '-' }}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="合同性质">{{ detail.employee.contractNature || '全职劳动合同' }}</el-descriptions-item> <el-descriptions-item label="职业资格">{{ employeeInfo.vocationalQualification || "-" }}</el-descriptions-item>
<el-descriptions-item label="合同开始日期">{{ detail.employee.contractStartDate || '-' }}</el-descriptions-item> <el-descriptions-item label="资格等级">{{ employeeInfo.qualificationLevel || "-" }}</el-descriptions-item>
<el-descriptions-item label="合同终止日期">{{ detail.employee.contractEndDate || '-' }}</el-descriptions-item> <el-descriptions-item label="技术特长" :span="2">{{ employeeInfo.technicalSpecialty || "-" }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header><div class="card-header-title">任职与签署材料信息</div></template>
<el-descriptions :column="2" border>
<el-descriptions-item label="所属公司">{{ detail.employee.companyName || "-" }}</el-descriptions-item>
<el-descriptions-item label="所属部门">{{
detail.employee.departmentName || employeeInfo.onboardingDepartment || "-"
}}</el-descriptions-item>
<el-descriptions-item label="所属岗位">{{
detail.employee.positionName || employeeInfo.onboardingPosition || "-"
}}</el-descriptions-item>
<el-descriptions-item label="员工编号">{{ detail.employee.employeeNo || employeeInfo.employeeNo || "-" }}</el-descriptions-item>
<el-descriptions-item label="入职日期">{{ formatDate(detail.employee.entryDate) }}</el-descriptions-item>
<el-descriptions-item label="社保缴纳单位">{{ detail.employee.socialInsuranceCompany || "-" }}</el-descriptions-item>
<el-descriptions-item label="聘用/材料性质">{{ detail.employee.contractNature || "全职劳动合同" }}</el-descriptions-item>
<el-descriptions-item label="签署材料开始日期">{{ formatDate(detail.employee.contractStartDate) }}</el-descriptions-item>
<el-descriptions-item label="签署材料终止日期">{{ formatDate(detail.employee.contractEndDate) }}</el-descriptions-item>
<el-descriptions-item label="其他说明" :span="2">{{
employeeInfo.otherRemarks || detail.employee.remark || "-"
}}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-card> </el-card>
@@ -86,16 +101,60 @@
<div class="card-header-title">工资卡账户信息</div> <div class="card-header-title">工资卡账户信息</div>
</template> </template>
<el-descriptions :column="2" border> <el-descriptions :column="2" border>
<el-descriptions-item label="银行卡号">{{ detail.employee.bankCardNumber || '-' }}</el-descriptions-item> <el-descriptions-item label="银行卡号">{{ detail.employee.bankCardNumber || "-" }}</el-descriptions-item>
<el-descriptions-item label="开户人姓名">{{ detail.employee.bankAccountName || detail.employee.employeeName }}</el-descriptions-item> <el-descriptions-item label="开户人姓名">{{ detail.employee.bankAccountName || detail.employee.employeeName }}</el-descriptions-item>
<el-descriptions-item label="工资卡开户行" :span="2">{{ detail.employee.bankName || '-' }}</el-descriptions-item> <el-descriptions-item label="工资卡开户行" :span="2">{{ detail.employee.bankName || "-" }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-card> </el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header><div class="card-header-title">紧急联系人</div></template>
<el-descriptions :column="2" border>
<el-descriptions-item label="联系人姓名">{{ employeeInfo.emergencyContact || "-" }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ employeeInfo.emergencyPhone || "-" }}</el-descriptions-item>
<el-descriptions-item label="联系地址" :span="2">{{ employeeInfo.emergencyAddress || "-" }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header><div class="card-header-title">教育学习经历</div></template>
<el-empty v-if="!educationHistory.length" description="暂无教育学习经历" :image-size="70" />
<el-table v-else :data="educationHistory" border size="small">
<el-table-column prop="timeRange" label="时间" min-width="140" />
<el-table-column prop="location" label="学校/地点" min-width="150" />
<el-table-column prop="content" label="学习内容" min-width="180" />
<el-table-column prop="result" label="学历/结果" min-width="120" />
</el-table>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header><div class="card-header-title">主要工作经历</div></template>
<el-empty v-if="!workHistory.length" description="暂无工作经历" :image-size="70" />
<el-table v-else :data="workHistory" border size="small">
<el-table-column prop="timeRange" label="时间" min-width="140" />
<el-table-column prop="companyName" label="工作单位" min-width="160" />
<el-table-column prop="position" label="岗位" min-width="120" />
<el-table-column prop="salary" label="薪资" min-width="100" />
<el-table-column prop="leaveReason" label="离职原因" min-width="160" />
</el-table>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header><div class="card-header-title">家庭成员</div></template>
<el-empty v-if="!familyMembers.length" description="暂无家庭成员信息" :image-size="70" />
<el-table v-else :data="familyMembers" border size="small">
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="relationship" label="关系" min-width="100" />
<el-table-column prop="age" label="年龄" min-width="80" />
<el-table-column prop="company" label="工作单位" min-width="160" />
<el-table-column prop="phone" label="联系电话" min-width="140" />
</el-table>
</el-card>
</el-tab-pane> </el-tab-pane>
<!-- 2. 归档合同与协议 --> <!-- 2. 归档合同与协议 -->
<el-tab-pane label="归档合同协议" name="contracts"> <el-tab-pane label="归档签署材料" name="contracts">
<el-empty v-if="!detail.contracts?.length" description="暂无已归档的劳动合同" /> <el-empty v-if="!detail.contracts?.length" description="暂无已归档的签署材料" />
<div v-else class="contracts-list"> <div v-else class="contracts-list">
<div v-for="contract in detail.contracts" :key="contract.id" class="contract-box"> <div v-for="contract in detail.contracts" :key="contract.id" class="contract-box">
<div class="contract-info-left"> <div class="contract-info-left">
@@ -112,9 +171,14 @@
plain plain
size="small" size="small"
icon="Download" icon="Download"
@click="download(contract.generatedDocxFileId, `${detail?.employee?.employeeName || '员工'}_${contract.contractName || '入职合同'}.docx`)" @click="
download(
contract.generatedDocxFileId,
`${detail?.employee?.employeeName || '员工'}_${contract.contractName || '入职合同'}.docx`
)
"
> >
下载合同 DOCX 下载签署材料 DOCX
</el-button> </el-button>
<el-tag v-else type="info">尚未生成电子文件</el-tag> <el-tag v-else type="info">尚未生成电子文件</el-tag>
</div> </div>
@@ -127,21 +191,13 @@
<el-empty v-if="!detail.audits?.length" description="暂无流转记录" /> <el-empty v-if="!detail.audits?.length" description="暂无流转记录" />
<div v-else class="timeline-box"> <div v-else class="timeline-box">
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item v-for="item in detail.audits" :key="item.id" type="primary" :timestamp="formatTime(item.auditedAt)" placement="top">
v-for="item in detail.audits"
:key="item.id"
type="primary"
:timestamp="formatTime(item.auditedAt)"
placement="top"
>
<div class="audit-card"> <div class="audit-card">
<div class="audit-action-name font-bold"> <div class="audit-action-name font-bold">
{{ auditActionText(item.auditAction) }} {{ auditActionText(item.auditAction) }}
<span v-if="item.createUser" class="audit-operator">(操作人:{{ item.createUser }})</span> <span v-if="item.createUser" class="audit-operator">(操作人:{{ item.createUser }})</span>
</div> </div>
<div v-if="item.auditOpinion" class="audit-opinion"> <div v-if="item.auditOpinion" class="audit-opinion">审核意见:{{ item.auditOpinion }}</div>
审核意见:{{ item.auditOpinion }}
</div>
</div> </div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
@@ -157,25 +213,33 @@
</template> </template>
<script setup lang="ts" name="onboardingEmployee"> <script setup lang="ts" name="onboardingEmployee">
import { ref } from "vue"; import { computed, ref } from "vue";
import { UserFilled, Download } from "@element-plus/icons-vue"; import { UserFilled, Download } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { import { onboardingTaskApi, type OnboardingEmployee } from "@/api/modules/biz/onboarding";
onboardingTaskApi,
type OnboardingEmployee
} from "@/api/modules/biz/onboarding";
import type { ColumnProps } from "@/components/ProTable/interface"; import type { ColumnProps } from "@/components/ProTable/interface";
const detailVisible = ref(false); const detailVisible = ref(false);
const activeTab = ref("basic"); const activeTab = ref("basic");
const detail = ref<any>(); const detail = ref<any>();
const employeeInfo = computed<Record<string, any>>(() => detail.value?.employeeInfo || {});
function parseJsonList(value?: string) {
if (!value) return [];
try {
const result = JSON.parse(value);
return Array.isArray(result) ? result : [];
} catch {
return [];
}
}
const educationHistory = computed(() => parseJsonList(employeeInfo.value.educationHistoryJson));
const workHistory = computed(() => parseJsonList(employeeInfo.value.workHistoryJson));
const familyMembers = computed(() => parseJsonList(employeeInfo.value.familyMembersJson));
const columns: ColumnProps<OnboardingEmployee>[] = [ const columns: ColumnProps<OnboardingEmployee>[] = [
{
prop: "employeeNo",
label: "员工编号",
minWidth: 160
},
{ {
prop: "employeeName", prop: "employeeName",
label: "姓名", label: "姓名",
@@ -189,10 +253,9 @@ const columns: ColumnProps<OnboardingEmployee>[] = [
align: "center" align: "center"
}, },
{ {
prop: "phone", prop: "companyName",
label: "手机号码", label: "所属公司",
minWidth: 160, minWidth: 180
search: { el: "input" }
}, },
{ {
prop: "departmentName", prop: "departmentName",
@@ -200,15 +263,21 @@ const columns: ColumnProps<OnboardingEmployee>[] = [
minWidth: 140 minWidth: 140
}, },
{ {
prop: "entryDate", prop: "positionName",
label: "入职日期", label: "所属岗位",
minWidth: 140 minWidth: 140
}, },
{ {
prop: "createTime", prop: "phone",
label: "建档时间", label: "手机号码",
minWidth: 180, minWidth: 160,
render: scope => formatTime(scope.row.createTime) search: { el: "input" }
},
{
prop: "entryDate",
label: "入职日期",
minWidth: 140,
render: scope => formatDate(scope.row.entryDate)
}, },
{ {
prop: "operation", prop: "operation",
@@ -234,6 +303,11 @@ const formatTime = (timeStr?: string) => {
return timeStr.replace("T", " ").substring(0, 19); return timeStr.replace("T", " ").substring(0, 19);
}; };
const formatDate = (timeStr?: string) => {
if (!timeStr) return "-";
return timeStr.replace("T", " ").substring(0, 10);
};
async function showDetail(row: OnboardingEmployee) { async function showDetail(row: OnboardingEmployee) {
const { data } = await onboardingTaskApi.employeeDetail({ const { data } = await onboardingTaskApi.employeeDetail({
id: row.id id: row.id
@@ -246,9 +320,7 @@ async function showDetail(row: OnboardingEmployee) {
function resolveDownloadFileName(response: any, fallbackFileName?: string) { function resolveDownloadFileName(response: any, fallbackFileName?: string) {
const disposition = String(response?.headers?.["content-disposition"] || ""); const disposition = String(response?.headers?.["content-disposition"] || "");
const encodedName = const encodedName = disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] || disposition.match(/filename="?([^";]+)"?/i)?.[1];
disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] ||
disposition.match(/filename="?([^";]+)"?/i)?.[1];
if (encodedName) { if (encodedName) {
try { try {
@@ -258,7 +330,7 @@ function resolveDownloadFileName(response: any, fallbackFileName?: string) {
} }
} }
return fallbackFileName || "入职合同.docx"; return fallbackFileName || "入职签署材料.docx";
} }
async function download(fileId: number, fallbackFileName?: string) { async function download(fileId: number, fallbackFileName?: string) {
@@ -278,7 +350,7 @@ async function download(fileId: number, fallbackFileName?: string) {
link.remove(); link.remove();
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0); window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
} catch (error: any) { } catch (error: any) {
ElMessage.error(error?.msg || error?.message || "合同文件下载失败"); ElMessage.error(error?.msg || error?.message || "签署材料文件下载失败");
} }
} }
</script> </script>
@@ -341,6 +413,7 @@ async function download(fileId: number, fallbackFileName?: string) {
} }
.section-card { .section-card {
flex-wrap: wrap;
border-radius: 8px; border-radius: 8px;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
} }
@@ -60,7 +60,7 @@
<el-button type="primary" icon="Plus" @click="openCreate">新增入职模板</el-button> <el-button type="primary" icon="Plus" @click="openCreate">新增入职模板</el-button>
</div> </div>
<el-alert title="入职模板统一管理公司、岗位、合同文件与员工上传材料;员工入职档案自动采用标准《入职登记表》。" type="info" :closable="false" show-icon <el-alert title="入职模板统一管理公司、岗位、签署材料文件与员工上传材料;员工入职档案自动采用标准《入职登记表》。" type="info" :closable="false" show-icon
class="tip" /> class="tip" />
<div class="table-container"> <div class="table-container">
@@ -78,7 +78,7 @@
</el-table-column> </el-table-column>
<el-table-column label="配置内容" min-width="200"> <el-table-column label="配置内容" min-width="200">
<template #default="scope"> <template #default="scope">
合同 {{ scope.row.contractCount || 0 }} 份 / 材料 {{ scope.row.materialCount || 0 }} 项 签署材料 {{ scope.row.contractCount || 0 }} 份 / 材料 {{ scope.row.materialCount || 0 }} 项
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="状态" width="90" align="center"> <el-table-column prop="status" label="状态" width="90" align="center">
@@ -186,7 +186,7 @@
<el-table :data="form.contracts" border empty-text="暂无关联合同,请点击右上角添加" class="mt-2"> <el-table :data="form.contracts" border empty-text="暂无关联合同,请点击右上角添加" class="mt-2">
<el-table-column label="合同模板" min-width="220"> <el-table-column label="合同模板" min-width="220">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.contractTemplateId" filterable class="full-width" placeholder="选择合同模板" <el-select v-model="scope.row.contractTemplateId" filterable class="full-width" placeholder="选择签署材料模板"
@change="onContractTemplateChange(scope.row)"> @change="onContractTemplateChange(scope.row)">
<el-option v-for="item in contractTemplates" :key="item.id" :label="item.templateName" <el-option v-for="item in contractTemplates" :key="item.id" :label="item.templateName"
:value="item.id" /> :value="item.id" />
@@ -494,7 +494,7 @@ function addContract() {
return; return;
} }
if (!contractTemplates.value.length) { if (!contractTemplates.value.length) {
ElMessage.warning("当前公司、部门和岗位没有可用的合同模板"); ElMessage.warning("当前公司、部门和岗位没有可用的签署材料模板");
return; return;
} }
@@ -602,7 +602,7 @@ async function save() {
return ElMessage.warning("请完整填写模板编码、名称、公司、部门及适用岗位"); return ElMessage.warning("请完整填写模板编码、名称、公司、部门及适用岗位");
} }
if (!form.contracts.length || form.contracts.some(item => !item.contractTemplateId || !item.contractTemplateVersionId)) { if (!form.contracts.length || form.contracts.some(item => !item.contractTemplateId || !item.contractTemplateVersionId)) {
return ElMessage.warning("请完整选择至少一份合同模板及版本"); return ElMessage.warning("请完整选择至少一份签署材料模板及版本");
} }
saving.value = true; saving.value = true;
@@ -18,7 +18,7 @@
> >
<el-option label="全部变量分类" value="" /> <el-option label="全部变量分类" value="" />
<el-option label="员工登记表变量 (employee)" value="employee" /> <el-option label="员工登记表变量 (employee)" value="employee" />
<el-option label="人事合同参数 (hr)" value="hr" /> <el-option label="人事预设参数 (hr)" value="hr" />
<el-option label="公司信息变量 (company)" value="company" /> <el-option label="公司信息变量 (company)" value="company" />
<el-option label="系统预置变量 (system)" value="system" /> <el-option label="系统预置变量 (system)" value="system" />
</el-select> </el-select>
@@ -29,7 +29,7 @@
</div> </div>
<el-alert <el-alert
title="变量字典库定义了合同 DOCX 中所有合法的占位符(如 {{employee.EmployeeName}})。上传 Word 时系统会自动校验占位符是否在此字典中,防止拼写错误。" title="变量字典库定义了签署材料 DOCX 中所有合法的占位符(如 {{employee.EmployeeName}})。上传 Word 时系统会自动校验占位符是否在此字典中,防止拼写错误。"
type="info" type="info"
show-icon show-icon
:closable="false" :closable="false"
@@ -112,7 +112,7 @@
<el-form-item label="变量分类" prop="variableCategory" required> <el-form-item label="变量分类" prop="variableCategory" required>
<el-select v-model="form.variableCategory" :disabled="form.isBuiltIn" style="width: 100%"> <el-select v-model="form.variableCategory" :disabled="form.isBuiltIn" style="width: 100%">
<el-option label="员工变量 (employee.)" value="employee" /> <el-option label="员工变量 (employee.)" value="employee" />
<el-option label="人事合同参数 (hr.)" value="hr" /> <el-option label="人事预设参数 (hr.)" value="hr" />
<el-option label="公司信息 (company.)" value="company" /> <el-option label="公司信息 (company.)" value="company" />
<el-option label="系统变量 (system.)" value="system" /> <el-option label="系统变量 (system.)" value="system" />
</el-select> </el-select>
+6 -6
View File
@@ -149,8 +149,8 @@
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="合同甲方全称" required> <el-form-item label="签署材料甲方全称" required>
<el-input v-model="companyForm.legalName" placeholder="用于合同填写的法定主体全称" /> <el-input v-model="companyForm.legalName" placeholder="用于签署材料填写的法定主体全称" />
</el-form-item> </el-form-item>
<el-row :gutter="20"> <el-row :gutter="20">
@@ -290,7 +290,7 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="默认合同年限"> <el-form-item label="默认签署材料年限">
<el-input-number v-model="positionForm.defaultContractYears" :min="1" :max="20" class="w-full" /> <el-input-number v-model="positionForm.defaultContractYears" :min="1" :max="20" class="w-full" />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -419,7 +419,7 @@ const companyColumns: ColumnProps<OnboardingCompany>[] = [
}, },
{ {
prop: "legalName", prop: "legalName",
label: "合同甲方全称", label: "签署材料甲方全称",
minWidth: 220 minWidth: 220
}, },
{ {
@@ -534,7 +534,7 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
}, },
{ {
prop: "defaultContractYears", prop: "defaultContractYears",
label: "默认合同年限", label: "默认签署材料年限",
width: 130, width: 130,
align: "center", align: "center",
render: scope => `${scope.row.defaultContractYears || 3} 年` render: scope => `${scope.row.defaultContractYears || 3} 年`
@@ -648,7 +648,7 @@ async function openPositionEdit(row: OnboardingPosition) {
async function save() { async function save() {
if (dialogKind.value === "company") { if (dialogKind.value === "company") {
if (!companyForm.companyCode || !companyForm.companyName || !companyForm.legalName) { if (!companyForm.companyCode || !companyForm.companyName || !companyForm.legalName) {
return ElMessage.warning("请填写公司编码、公司名称和合同甲方全称"); return ElMessage.warning("请填写公司编码、公司名称和签署材料甲方全称");
} }
await onboardingTaskApi.saveCompany(companyForm, editing.value); await onboardingTaskApi.saveCompany(companyForm, editing.value);
companyTable.value?.refresh(); companyTable.value?.refresh();
+23 -23
View File
@@ -64,7 +64,7 @@
icon="Setting" icon="Setting"
@click="openContractVariables(scope.row)" @click="openContractVariables(scope.row)"
> >
合同参数 签署材料参数
</el-button> </el-button>
<el-button <el-button
@@ -214,12 +214,12 @@
<!-- 2. 合同人事参数卡片 --> <!-- 2. 合同人事参数卡片 -->
<div v-if="form.contractVariables.length" class="form-card-section"> <div v-if="form.contractVariables.length" class="form-card-section">
<div class="section-title"> <div class="section-title">
<span class="title-text">合同人事参数预设</span> <span class="title-text">签署材料人事参数预设</span>
<span class="title-tip">以下参数将自动替换进 DOCX 合同中,请在向员工发送链接前核对填写</span> <span class="title-tip">以下参数将自动替换进 DOCX 签署材料中,请在向员工发送链接前核对填写</span>
</div> </div>
<el-alert <el-alert
title="提示:此处填写的内容将作为合同正式条款,如薪资、合同起止日期、试用期等,请谨慎填写。" title="提示:此处填写的内容将作为签署材料正式条款,如薪资、协议起止日期、试用期等,请谨慎填写。"
type="warning" type="warning"
:closable="false" :closable="false"
show-icon show-icon
@@ -292,7 +292,7 @@
<!-- 任务创建成功与分享弹窗 --> <!-- 任务创建成功与分享弹窗 -->
<el-dialog v-model="shareVisible" title="入职任务已创建" width="560px" destroy-on-close> <el-dialog v-model="shareVisible" title="入职任务已创建" width="560px" destroy-on-close>
<el-result icon="success" title="任务创建成功!" sub-title="请先确认合同人事参数,再将专属链接发送给候选人"> <el-result icon="success" title="任务创建成功!" sub-title="请先确认签署材料人事参数,再将专属链接发送给候选人">
<template #extra> <template #extra>
<div class="share-box"> <div class="share-box">
<div class="share-item"> <div class="share-item">
@@ -510,8 +510,8 @@
<el-card v-if="detailCustomVars.length" shadow="never" class="mb-3 border-card"> <el-card v-if="detailCustomVars.length" shadow="never" class="mb-3 border-card">
<template #header> <template #header>
<div class="card-header-flex"> <div class="card-header-flex">
<span class="font-bold card-title-text">合同专属补充信息</span> <span class="font-bold card-title-text">签署材料专属补充信息</span>
<el-tag size="small" type="warning">合同专有项</el-tag> <el-tag size="small" type="warning">签署材料专有项</el-tag>
</div> </div>
</template> </template>
<el-descriptions :column="2" border size="default"> <el-descriptions :column="2" border size="default">
@@ -611,8 +611,8 @@
</el-tab-pane> </el-tab-pane>
<!-- 3. 合同管理与归档 --> <!-- 3. 合同管理与归档 -->
<el-tab-pane label="入职合同列表" name="contracts"> <el-tab-pane label="入职签署材料列表" name="contracts">
<el-empty v-if="!detail.contracts || !detail.contracts.length" description="暂无关联合同" /> <el-empty v-if="!detail.contracts || !detail.contracts.length" description="暂无关联签署材料" />
<div v-else class="contracts-panel"> <div v-else class="contracts-panel">
<div v-for="item in detail.contracts" :key="item.id" class="contract-card-item"> <div v-for="item in detail.contracts" :key="item.id" class="contract-card-item">
<div class="contract-left"> <div class="contract-left">
@@ -634,7 +634,7 @@
icon="DocumentChecked" icon="DocumentChecked"
@click="generate(item.id)" @click="generate(item.id)"
> >
生成DOCX合同 生成DOCX签署材料
</el-button> </el-button>
<el-button <el-button
v-else v-else
@@ -728,24 +728,24 @@
@close="closeImageViewer" @close="closeImageViewer"
/> />
<!-- 合同参数录入弹窗 --> <!-- 签署材料参数录入弹窗 -->
<el-dialog <el-dialog
v-model="contractVariablesVisible" v-model="contractVariablesVisible"
:title="`${contractVariableTask?.taskNo || ''} · 合同参数录入`" :title="`${contractVariableTask?.taskNo || ''} · 签署材料参数录入`"
width="1180px" width="1180px"
top="4vh" top="4vh"
destroy-on-close destroy-on-close
> >
<div class="contract-vars-dialog-body"> <div class="contract-vars-dialog-body">
<el-alert <el-alert
title="说明:此处展示本次入职任务关联的所有合同变量。带有【人事变量】标签的项为本次需要 HR 填写的核心条款(如薪资、试用期、合同起止日等);员工变量与公司变量将在流程中自动带入。" title="说明:此处展示本次入职任务关联的所有签署材料变量。带有【人事变量】标签的项为本次需要 HR 填写的核心条款(如薪资、试用期、协议起止日等);员工变量与公司变量将在流程中自动带入。"
type="info" type="info"
show-icon show-icon
:closable="false" :closable="false"
style="margin-bottom: 16px;" style="margin-bottom: 16px;"
/> />
<el-empty v-if="!contractVariableGroups.length" description="当前任务合同无需配置人事变量" /> <el-empty v-if="!contractVariableGroups.length" description="当前任务签署材料无需配置人事变量" />
<div v-else> <div v-else>
<!-- 顶部合同分组切换 Tab --> <!-- 顶部合同分组切换 Tab -->
@@ -862,7 +862,7 @@
<div class="dialog-footer-flex"> <div class="dialog-footer-flex">
<el-button @click="contractVariablesVisible = false">关闭</el-button> <el-button @click="contractVariablesVisible = false">关闭</el-button>
<el-button type="primary" icon="Check" :loading="savingAllVars" @click="saveAllContractVariables"> <el-button type="primary" icon="Check" :loading="savingAllVars" @click="saveAllContractVariables">
一键保存全部合同参数 一键保存全部签署材料参数
</el-button> </el-button>
</div> </div>
</template> </template>
@@ -1112,7 +1112,7 @@ const variableTypeText = (type: string) => {
const map: Record<string, string> = { const map: Record<string, string> = {
employee: "员工变量", employee: "员工变量",
company: "公司变量", company: "公司变量",
contract: "合同人事参数", contract: "人事预设参数",
system: "系统变量" system: "系统变量"
}; };
return map[type] || type; return map[type] || type;
@@ -1359,7 +1359,7 @@ async function createTask() {
item => item.required && isBlankContractVariableValue(item.variableValue) item => item.required && isBlankContractVariableValue(item.variableValue)
); );
if (missingVariable) { if (missingVariable) {
return ElMessage.warning(`请填写合同人事参数“${missingVariable.variableName}”`); return ElMessage.warning(`请填写签署材料人事参数“${missingVariable.variableName}”`);
} }
creating.value = true; creating.value = true;
@@ -1403,7 +1403,7 @@ function copyText(text?: string, successMsg = "复制成功") {
function copyInviteMessage() { function copyInviteMessage() {
if (!createdTaskInfo.value) return; if (!createdTaskInfo.value) return;
const msg = `【入职邀请】您好!欢迎加入${createdTaskInfo.value.companyName},担任${createdTaskInfo.value.positionName}一职。请点击以下链接完成您的入职登记与合同确认:${createdTaskInfo.value.linkUrl} (链接有效期至:${createdTaskInfo.value.expiresAt})`; const msg = `【入职邀请】您好!欢迎加入${createdTaskInfo.value.companyName},担任${createdTaskInfo.value.positionName}一职。请点击以下链接完成您的入职登记与签署材料确认:${createdTaskInfo.value.linkUrl} (链接有效期至:${createdTaskInfo.value.expiresAt})`;
copyText(msg, "邀请文案已复制至剪贴板"); copyText(msg, "邀请文案已复制至剪贴板");
} }
@@ -1457,7 +1457,7 @@ async function saveContractVariables(group: OnboardingTaskContractVariableGroup)
); );
if (requiredBlank) { if (requiredBlank) {
return ElMessage.warning("请填写所有必填合同人事参数"); return ElMessage.warning("请填写所有必填签署材料人事参数");
} }
await onboardingTaskApi.saveTaskContractVariables({ await onboardingTaskApi.saveTaskContractVariables({
@@ -1489,7 +1489,7 @@ async function saveAllContractVariables() {
values: buildContractVariableSaveValues(group) values: buildContractVariableSaveValues(group)
}); });
} }
ElMessage.success("全部合同参数已保存成功!"); ElMessage.success("全部签署材料参数已保存成功!");
contractVariablesVisible.value = false; contractVariablesVisible.value = false;
} finally { } finally {
savingAllVars.value = false; savingAllVars.value = false;
@@ -1520,7 +1520,7 @@ function buildContractVariableSaveValues(group: OnboardingTaskContractVariableGr
async function approve(row: OnboardingTask) { async function approve(row: OnboardingTask) {
await ElMessageBox.confirm( await ElMessageBox.confirm(
"确认审核通过?系统将自动创建正式员工档案并锁定合同快照。", "确认审核通过?系统将自动创建正式员工档案并锁定签署材料快照。",
"审核通过确认", "审核通过确认",
{ type: "success", confirmButtonText: "审核通过", cancelButtonText: "取消" } { type: "success", confirmButtonText: "审核通过", cancelButtonText: "取消" }
); );
@@ -1546,7 +1546,7 @@ async function reject(row: OnboardingTask) {
async function generate(taskContractId: number) { async function generate(taskContractId: number) {
await onboardingTaskApi.generateContract(taskContractId); await onboardingTaskApi.generateContract(taskContractId);
ElMessage.success("DOCX合同文件已生成"); ElMessage.success("DOCX签署材料文件已生成");
if (detail.value?.task?.id) { if (detail.value?.task?.id) {
await showDetail(detail.value.task, true); await showDetail(detail.value.task, true);
} }
@@ -2163,7 +2163,7 @@ onMounted(loadCompanies);
color: #2563eb; color: #2563eb;
} }
/* 合同参数录入弹窗优化 */ /* 签署材料参数录入弹窗优化 */
.contract-vars-dialog-body { .contract-vars-dialog-body {
max-height: 68vh; max-height: 68vh;
overflow-y: auto; overflow-y: auto;
+26 -26
View File
@@ -8,7 +8,7 @@
<span>OnboardX 智能入职服务</span> <span>OnboardX 智能入职服务</span>
</div> </div>
<h1 class="brand-title">欢迎加入新团队</h1> <h1 class="brand-title">欢迎加入新团队</h1>
<p class="brand-subtitle">请按照 4 步指引完成入职登记表、证件上传与合同查阅确认</p> <p class="brand-subtitle">请按照 4 步指引完成入职登记表、证件上传与签署材料查阅确认</p>
</div> </div>
</header> </header>
@@ -60,16 +60,16 @@
<div class="steps-wrapper desktop-only"> <div class="steps-wrapper desktop-only">
<el-steps :active="currentStep" finish-status="success" align-center> <el-steps :active="currentStep" finish-status="success" align-center>
<el-step title="入职登记表" icon="User" /> <el-step title="入职登记表" icon="User" />
<el-step title="合同补充项" icon="Document" /> <el-step title="材料补充项" icon="Document" />
<el-step title="证件材料" icon="FolderChecked" /> <el-step title="证件材料" icon="FolderChecked" />
<el-step title="合同确认" icon="Tickets" /> <el-step title="材料确认" icon="Tickets" />
</el-steps> </el-steps>
</div> </div>
<div class="mobile-steps-wrapper mobile-only"> <div class="mobile-steps-wrapper mobile-only">
<div class="mobile-steps-track"> <div class="mobile-steps-track">
<div <div
v-for="(stepName, index) in ['1. 登记表', '2. 补充项', '3. 证件材料', '4. 合同确认']" v-for="(stepName, index) in ['1. 登记表', '2. 补充项', '3. 证件材料', '4. 材料确认']"
:key="index" :key="index"
class="mobile-step-item" class="mobile-step-item"
:class="{ :class="{
@@ -83,7 +83,7 @@
</div> </div>
</div> </div>
<div class="mobile-current-indicator"> <div class="mobile-current-indicator">
当前进行:<span class="highlight-step">第 {{ currentStep + 1 }}/4 步 · {{ ['入职登记表填写', '合同专有补充项', '证件材料上传', '合同查阅与确认'][currentStep] }}</span> 当前进行:<span class="highlight-step">第 {{ currentStep + 1 }}/4 步 · {{ ['入职登记表填写', '签署材料补充项', '证件材料上传', '签署材料查阅与确认'][currentStep] }}</span>
</div> </div>
</div> </div>
@@ -419,21 +419,21 @@
<!-- 步骤 2: 合同专属增量补充项 (自适应差集) --> <!-- 步骤 2: 合同专属增量补充项 (自适应差集) -->
<div v-show="currentStep === 1" class="step-content"> <div v-show="currentStep === 1" class="step-content">
<div class="section-header"> <div class="section-header">
<h3 class="section-title">合同专属补充信息</h3> <h3 class="section-title">签署材料专属补充信息</h3>
<p class="section-desc">以下是本次签署合同中特殊约定的增量字段,登记表未涵盖的项需在此补充确认</p> <p class="section-desc">以下是本次签署材料中特殊约定的增量字段,登记表未涵盖的项需在此补充确认</p>
</div> </div>
<div v-if="!extraVariables.length" class="all-covered-card"> <div v-if="!extraVariables.length" class="all-covered-card">
<el-result <el-result
icon="success" icon="success"
title="个人信息已完整覆盖" title="个人信息已完整覆盖"
sub-title="您在《入职登记表》中填写的姓名、身份证、电话、银行卡等信息已完整满足本次合同签署所需,无需额外补充!" sub-title="您在《入职登记表》中填写的姓名、身份证、电话、银行卡等信息已完整满足本次签署材料所需,无需额外补充!"
/> />
</div> </div>
<el-form v-else label-position="top" class="extra-form"> <el-form v-else label-position="top" class="extra-form">
<el-alert <el-alert
title="请补充填写以下合同专有信息:" title="请补充填写以下签署材料专有信息:"
type="info" type="info"
show-icon show-icon
:closable="false" :closable="false"
@@ -568,21 +568,21 @@
<!-- 步骤 4: 合同查阅与签署 --> <!-- 步骤 4: 合同查阅与签署 -->
<div v-show="currentStep === 3" class="step-content"> <div v-show="currentStep === 3" class="step-content">
<div class="section-header"> <div class="section-header">
<h3 class="section-title">入职合同查阅与确认</h3> <h3 class="section-title">入职签署材料查阅与确认</h3>
<p class="section-desc">系统已为您代入真实登记信息生成专属合同,请认真查阅条款并勾选确认</p> <p class="section-desc">系统已为您代入真实登记信息生成专属签署材料,请认真查阅条款并勾选确认</p>
</div> </div>
<el-alert <el-alert
v-if="preparingContract" v-if="preparingContract"
class="contract-preparing-alert" class="contract-preparing-alert"
title="正在生成您的专属合同" title="正在生成您的专属签署材料"
:description="contractPreparingDescription" :description="contractPreparingDescription"
type="info" type="info"
show-icon show-icon
:closable="false" :closable="false"
/> />
<el-empty v-if="!contracts.length" description="暂无待签署入职合同" /> <el-empty v-if="!contracts.length" description="暂无待确认签署材料" />
<div v-else class="contracts-list"> <div v-else class="contracts-list">
<div <div
@@ -599,7 +599,7 @@
</el-tag> </el-tag>
</div> </div>
<div class="contract-desc"> <div class="contract-desc">
请点击下方按钮下载/查阅带您专属信息的合同全文,仔细核对您的基本信息与薪资条款。 请点击下方按钮下载/查阅带您专属信息的签署材料全文,仔细核对您的基本信息与条款内容。
</div> </div>
</div> </div>
@@ -638,7 +638,7 @@
v-if="canEdit && isContractPrepared(c.id) && !c.hasViewed" v-if="canEdit && isContractPrepared(c.id) && !c.hasViewed"
class="contract-view-hint" class="contract-view-hint"
> >
请先点击“下载并查看合同”,查看后才能勾选确认。 请先点击“下载并查看签署材料”,查看后才能勾选确认。
</div> </div>
</div> </div>
</div> </div>
@@ -772,7 +772,7 @@ const contractPreparingDescription = computed(() => {
} }
const index = contracts.value.findIndex(item => item.id === contract.id); const index = contracts.value.findIndex(item => item.id === contract.id);
return `正在校验合同信息并生成第 ${index + 1}/${contracts.value.length} 份《${contract.contractName}》,请稍候,不要关闭页面。`; return `正在校验材料信息并生成第 ${index + 1}/${contracts.value.length} 份《${contract.contractName}》,请稍候,不要关闭页面。`;
}); });
// 计算差集增量变量 (合同需要的 employee. 变量中,登记表没有的字段) // 计算差集增量变量 (合同需要的 employee. 变量中,登记表没有的字段)
@@ -1091,15 +1091,15 @@ function isContractPrepareFailed(taskContractId: number) {
function getContractActionText(taskContractId: number) { function getContractActionText(taskContractId: number) {
const status = getContractPrepareStatus(taskContractId); const status = getContractPrepareStatus(taskContractId);
if (status === "preparing") { if (status === "preparing") {
return "正在准备合同"; return "正在准备签署材料";
} }
if (status === "error") { if (status === "error") {
return "重新准备合同"; return "重新准备签署材料";
} }
if (status === "ready") { if (status === "ready") {
return "下载并查看合同"; return "下载并查看签署材料";
} }
return "等待准备合同"; return "等待准备签署材料";
} }
/** /**
@@ -1126,7 +1126,7 @@ async function prepareContract(contract: any) {
contractPrepareStatus[key] = "ready"; contractPrepareStatus[key] = "ready";
return true; return true;
} catch (err: any) { } catch (err: any) {
const message = err?.msg || err?.message || "合同准备失败,请稍后重试"; const message = err?.msg || err?.message || "签署材料准备失败,请稍后重试";
contractPrepareStatus[key] = "error"; contractPrepareStatus[key] = "error";
contractPrepareErrors[key] = message; contractPrepareErrors[key] = message;
// 业务异常已由统一请求拦截器提示;仅为网络等非标准异常补充提示,避免重复弹窗。 // 业务异常已由统一请求拦截器提示;仅为网络等非标准异常补充提示,避免重复弹窗。
@@ -1150,7 +1150,7 @@ async function handleContractAction(contract: any) {
if (isContractPrepareFailed(contract.id)) { if (isContractPrepareFailed(contract.id)) {
const prepared = await prepareContract(contract); const prepared = await prepareContract(contract);
if (prepared) { if (prepared) {
ElMessage.success("合同已重新准备完成,请再次点击下载查看"); ElMessage.success("签署材料已重新准备完成,请再次点击下载查看");
} }
return; return;
} }
@@ -1179,21 +1179,21 @@ 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("、")}`); return ElMessage.warning(`请先等待签署材料准备完成:${unprepared.map(c => c.contractName).join("、")}`);
} }
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("、")}`); return ElMessage.warning(`请先下载并查看签署材料:${unviewed.map(c => c.contractName).join("、")}`);
} }
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("、")})`); return ElMessage.warning(`请先勾选同意全部签署材料(未勾选:${unconfirmed.map(c => c.contractName).join("、")})`);
} }
const confirmed = await isMessageBoxConfirmed( const confirmed = await isMessageBoxConfirmed(
ElMessageBox.confirm("确认提交全部入职资料与合同确认?提交后将进入人事复核建档流程。", "最终提交确认", { ElMessageBox.confirm("确认提交全部入职资料与签署材料确认?提交后将进入人事复核建档流程。", "最终提交确认", {
type: "success", type: "success",
confirmButtonText: "确认提交", confirmButtonText: "确认提交",
cancelButtonText: "再检查一下" cancelButtonText: "再检查一下"