前端第一版
This commit is contained in:
Vendored
+3
@@ -60,6 +60,7 @@ declare module 'vue' {
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
@@ -74,6 +75,8 @@ declare module 'vue' {
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { moduleRequest } from "@/api/request";
|
||||
|
||||
const http = moduleRequest("/open/onboarding/");
|
||||
|
||||
export const onboardingPublicApi = {
|
||||
detail(token: string) {
|
||||
return http.post("detail", { token });
|
||||
},
|
||||
|
||||
saveEmployeeInfo(params: Record<string, unknown>) {
|
||||
return http.post("save-employee-info", params);
|
||||
},
|
||||
|
||||
saveDynamicValues(params: {
|
||||
token: string;
|
||||
values: Array<{
|
||||
fieldId: number;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
}) {
|
||||
return http.post("save-dynamic-values", params);
|
||||
},
|
||||
|
||||
confirmContract(params: {
|
||||
token: string;
|
||||
taskContractId: number;
|
||||
}) {
|
||||
return http.post("confirm-contract", params);
|
||||
},
|
||||
|
||||
submit(token: string) {
|
||||
return http.post("submit", { token });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,521 @@
|
||||
import { moduleRequest } from "@/api/request";
|
||||
import type { ReqId, ResPage } from "@/api/interface";
|
||||
|
||||
const taskHttp = moduleRequest("/biz/onboarding/task/");
|
||||
const auditHttp = moduleRequest("/biz/onboarding/audit/");
|
||||
|
||||
export interface OnboardingTask {
|
||||
id: number;
|
||||
taskNo: string;
|
||||
companyId: number;
|
||||
positionId: number;
|
||||
taskStatus: string;
|
||||
linkExpireAt?: string;
|
||||
submitCount: number;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
export interface OnboardingCompanyOption {
|
||||
id: number;
|
||||
companyName: string;
|
||||
}
|
||||
|
||||
export interface OnboardingPositionOption {
|
||||
id: number;
|
||||
positionName: string;
|
||||
}
|
||||
|
||||
export interface OnboardingCompany extends OnboardingCompanyOption {
|
||||
companyCode: string;
|
||||
legalName: string;
|
||||
unifiedSocialCreditCode?: string;
|
||||
legalRepresentative?: string;
|
||||
registeredAddress?: string;
|
||||
contactPhone?: string;
|
||||
remark?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OnboardingPosition extends OnboardingPositionOption {
|
||||
companyId: number;
|
||||
positionCode: string;
|
||||
defaultContractYears?: number;
|
||||
defaultProbationMonths?: number;
|
||||
remark?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OnboardingContractTemplate {
|
||||
id: number;
|
||||
templateCode: string;
|
||||
templateName: string;
|
||||
contractType: string;
|
||||
description?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OnboardingContractVersion {
|
||||
id: number;
|
||||
templateId: number;
|
||||
versionNo: string;
|
||||
sourceDocxFileId: number;
|
||||
effectiveDate?: string;
|
||||
expiryDate?: string;
|
||||
versionStatus: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface OnboardingTemplateVariable {
|
||||
id: number;
|
||||
templateVersionId: number;
|
||||
variableCode: string;
|
||||
variableName: string;
|
||||
variableType: string;
|
||||
dataType: string;
|
||||
employeeField?: string;
|
||||
defaultValue?: string;
|
||||
required: boolean;
|
||||
hrEditable: boolean;
|
||||
sortCode?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface OnboardingContractScope {
|
||||
id: number;
|
||||
companyId: number;
|
||||
positionId?: number;
|
||||
templateId: number;
|
||||
templateVersionId: number;
|
||||
required: boolean;
|
||||
sortCode?: number;
|
||||
}
|
||||
|
||||
export interface OnboardingTaskContractVariableItem {
|
||||
id: number;
|
||||
variableCode: string;
|
||||
variableName: string;
|
||||
variableType: string;
|
||||
dataType: string;
|
||||
variableValue?: string;
|
||||
required: boolean;
|
||||
hrEditable: boolean;
|
||||
editable: boolean;
|
||||
}
|
||||
|
||||
export interface OnboardingTaskContractVariableGroup {
|
||||
taskContractId: number;
|
||||
contractName: string;
|
||||
contractType: string;
|
||||
variables: OnboardingTaskContractVariableItem[];
|
||||
}
|
||||
|
||||
export interface OnboardingEmployee {
|
||||
id: number;
|
||||
employeeNo?: string;
|
||||
employeeName: string;
|
||||
companyId: number;
|
||||
positionId: number;
|
||||
phone: string;
|
||||
idCardNumber: string;
|
||||
entryDate?: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
export interface OnboardingFormField {
|
||||
id: number;
|
||||
fieldCode: string;
|
||||
fieldName: string;
|
||||
dataType: string;
|
||||
optionsJson?: string;
|
||||
validationRule?: string;
|
||||
placeholder?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OnboardingMaterialType {
|
||||
id: number;
|
||||
materialCode: string;
|
||||
materialName: string;
|
||||
allowedExtensions?: string;
|
||||
maxFileCount: number;
|
||||
maxFileSizeMb?: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OnboardingFormRequirement {
|
||||
id: number;
|
||||
companyId: number;
|
||||
positionId?: number;
|
||||
fieldId: number;
|
||||
required: boolean;
|
||||
sortCode?: number;
|
||||
}
|
||||
|
||||
export interface OnboardingMaterialRequirement {
|
||||
id: number;
|
||||
companyId: number;
|
||||
positionId?: number;
|
||||
materialTypeId: number;
|
||||
required: boolean;
|
||||
sortCode?: number;
|
||||
}
|
||||
|
||||
export const onboardingTaskApi = {
|
||||
page(params: Record<string, unknown>) {
|
||||
return taskHttp.get<ResPage<OnboardingTask>>("page", params);
|
||||
},
|
||||
|
||||
detail(params: ReqId) {
|
||||
return taskHttp.get("detail", params);
|
||||
},
|
||||
|
||||
create(params: Record<string, unknown>) {
|
||||
return taskHttp.post("create", params);
|
||||
},
|
||||
|
||||
approve(params: {
|
||||
taskId: number;
|
||||
auditOpinion?: string;
|
||||
}) {
|
||||
return auditHttp.post("approve", params);
|
||||
},
|
||||
|
||||
reject(params: {
|
||||
taskId: number;
|
||||
auditOpinion: string;
|
||||
}) {
|
||||
return auditHttp.post("reject", params);
|
||||
},
|
||||
|
||||
generateContract(taskContractId: number) {
|
||||
return moduleRequest("/biz/onboarding/contract-document/").post(
|
||||
"generate",
|
||||
{ taskContractId }
|
||||
);
|
||||
},
|
||||
|
||||
archive(params: {
|
||||
taskId: number;
|
||||
auditOpinion?: string;
|
||||
}) {
|
||||
return taskHttp.post("archive", params);
|
||||
},
|
||||
|
||||
revoke(params: {
|
||||
taskId: number;
|
||||
auditOpinion?: string;
|
||||
}) {
|
||||
return taskHttp.post("revoke", params);
|
||||
},
|
||||
|
||||
reviewMaterial(params: {
|
||||
taskMaterialId: number;
|
||||
reviewStatus: string;
|
||||
reviewRemark?: string;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/material/").post(
|
||||
"review",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
listCompanies() {
|
||||
return moduleRequest("/biz/onboarding/company/").get<
|
||||
OnboardingCompanyOption[]
|
||||
>("list-enabled");
|
||||
},
|
||||
|
||||
listPositions(companyId: number) {
|
||||
return moduleRequest("/biz/onboarding/position/").get<
|
||||
OnboardingPositionOption[]
|
||||
>("list-enabled", { companyId });
|
||||
},
|
||||
|
||||
companyPage(params: Record<string, unknown>) {
|
||||
return moduleRequest("/biz/onboarding/company/").get<
|
||||
ResPage<OnboardingCompany>
|
||||
>("page", params);
|
||||
},
|
||||
|
||||
companyDetail(params: ReqId) {
|
||||
return moduleRequest("/biz/onboarding/company/").get<OnboardingCompany>(
|
||||
"detail",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
saveCompany(
|
||||
params: Partial<OnboardingCompany>,
|
||||
edit = false
|
||||
) {
|
||||
return moduleRequest("/biz/onboarding/company/").post(
|
||||
edit ? "edit" : "add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteCompany(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/company/").post(
|
||||
"delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
positionPage(params: Record<string, unknown>) {
|
||||
return moduleRequest("/biz/onboarding/position/").get<
|
||||
ResPage<OnboardingPosition>
|
||||
>("page", params);
|
||||
},
|
||||
|
||||
positionDetail(params: ReqId) {
|
||||
return moduleRequest("/biz/onboarding/position/").get<OnboardingPosition>(
|
||||
"detail",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
savePosition(
|
||||
params: Partial<OnboardingPosition>,
|
||||
edit = false
|
||||
) {
|
||||
return moduleRequest("/biz/onboarding/position/").post(
|
||||
edit ? "edit" : "add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deletePosition(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/position/").post(
|
||||
"delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
templatePage(params: Record<string, unknown>) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").get<
|
||||
ResPage<OnboardingContractTemplate>
|
||||
>("page", params);
|
||||
},
|
||||
|
||||
templateDetail(params: ReqId) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").get<
|
||||
OnboardingContractTemplate
|
||||
>("detail", params);
|
||||
},
|
||||
|
||||
saveTemplate(
|
||||
params: Partial<OnboardingContractTemplate>,
|
||||
edit = false
|
||||
) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
edit ? "edit" : "add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteTemplate(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
versionPage(params: {
|
||||
templateId: number;
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").get<
|
||||
ResPage<OnboardingContractVersion>
|
||||
>("version/page", params);
|
||||
},
|
||||
|
||||
addVersion(params: Partial<OnboardingContractVersion>) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"version/add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
setVersionStatus(params: {
|
||||
id: number;
|
||||
versionStatus: string;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"version/set-status",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
variablePage(params: {
|
||||
templateVersionId: number;
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").get<
|
||||
ResPage<OnboardingTemplateVariable>
|
||||
>("variable/page", params);
|
||||
},
|
||||
|
||||
saveVariable(
|
||||
params: Partial<OnboardingTemplateVariable>,
|
||||
edit = false
|
||||
) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
edit ? "variable/edit" : "variable/add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteVariable(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"variable/delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
scopePage(params: {
|
||||
templateId: number;
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").get<
|
||||
ResPage<OnboardingContractScope>
|
||||
>("scope/page", params);
|
||||
},
|
||||
|
||||
addScope(params: Partial<OnboardingContractScope>) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"scope/add",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteScope(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/contract-template/").post(
|
||||
"scope/delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
uploadDocx(params: FormData) {
|
||||
return moduleRequest("/sys/dev/file/").post<number>(
|
||||
"uploadLocal",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
taskContractVariables(taskId: number) {
|
||||
return moduleRequest("/biz/onboarding/task-contract-variable/").get<
|
||||
OnboardingTaskContractVariableGroup[]
|
||||
>("list", { taskId });
|
||||
},
|
||||
|
||||
saveTaskContractVariables(params: {
|
||||
taskContractId: number;
|
||||
values: Array<{
|
||||
id: number;
|
||||
variableValue?: string;
|
||||
}>;
|
||||
}) {
|
||||
return moduleRequest("/biz/onboarding/task-contract-variable/").post(
|
||||
"save",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
employeePage(params: Record<string, unknown>) {
|
||||
return moduleRequest("/biz/onboarding/employee/").get<
|
||||
ResPage<OnboardingEmployee>
|
||||
>("page", params);
|
||||
},
|
||||
|
||||
employeeDetail(params: ReqId) {
|
||||
return moduleRequest("/biz/onboarding/employee/").get(
|
||||
"detail",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
listFormFields() {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").get<
|
||||
OnboardingFormField[]
|
||||
>("form-field/list");
|
||||
},
|
||||
|
||||
saveFormField(params: Partial<OnboardingFormField>) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"form-field/save",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteFormFields(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"form-field/delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
listMaterialTypes() {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").get<
|
||||
OnboardingMaterialType[]
|
||||
>("material-type/list");
|
||||
},
|
||||
|
||||
saveMaterialType(params: Partial<OnboardingMaterialType>) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"material-type/save",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteMaterialTypes(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"material-type/delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
listFormRequirements(companyId: number) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").get<
|
||||
OnboardingFormRequirement[]
|
||||
>("form-requirement/list", { companyId });
|
||||
},
|
||||
|
||||
saveFormRequirement(params: Partial<OnboardingFormRequirement>) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"form-requirement/save",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteFormRequirements(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"form-requirement/delete",
|
||||
{ ids }
|
||||
);
|
||||
},
|
||||
|
||||
listMaterialRequirements(companyId: number) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").get<
|
||||
OnboardingMaterialRequirement[]
|
||||
>("material-requirement/list", { companyId });
|
||||
},
|
||||
|
||||
saveMaterialRequirement(
|
||||
params: Partial<OnboardingMaterialRequirement>
|
||||
) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"material-requirement/save",
|
||||
params
|
||||
);
|
||||
},
|
||||
|
||||
deleteMaterialRequirements(ids: number[]) {
|
||||
return moduleRequest("/biz/onboarding/entry-config/").post(
|
||||
"material-requirement/delete",
|
||||
{ ids }
|
||||
);
|
||||
}
|
||||
};
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"]
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
|
||||
@@ -31,6 +31,12 @@ export const staticRouter: RouteRecordRaw[] = [
|
||||
title: "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/onboarding/fill",
|
||||
name: "onboardingFill",
|
||||
component: () => import("@/views/onboarding/fill/index.vue"),
|
||||
meta: { title: "入职资料填写" }
|
||||
},
|
||||
{
|
||||
path: "/layout",
|
||||
name: "layout",
|
||||
|
||||
@@ -0,0 +1,975 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable
|
||||
ref="templateTable"
|
||||
title="合同模板列表"
|
||||
:columns="templateColumns"
|
||||
:request-api="onboardingTaskApi.templatePage"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button type="primary" icon="Plus" @click="openTemplateCreate">
|
||||
新增合同模板
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #contractType="scope">
|
||||
<el-tag effect="plain" type="primary">
|
||||
{{ scope.row.contractType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #status="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
|
||||
{{ scope.row.status === "enable" ? "启用" : "停用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #operation="scope">
|
||||
<el-button link type="primary" icon="Files" @click="openManagementDrawer(scope.row)">
|
||||
版本与配置
|
||||
</el-button>
|
||||
|
||||
<el-button link type="primary" icon="Edit" @click="openTemplateEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removeTemplate(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<!-- 新增 / 编辑合同模板基础信息弹窗 -->
|
||||
<el-dialog
|
||||
v-model="templateVisible"
|
||||
:title="templateEditing ? '编辑合同模板' : '新增合同模板'"
|
||||
width="580px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="templateForm" label-width="110px">
|
||||
<el-form-item label="模板编码" required>
|
||||
<el-input v-model="templateForm.templateCode" placeholder="如:LABOR_CONTRACT" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模板名称" required>
|
||||
<el-input v-model="templateForm.templateName" placeholder="如:标准全职劳动合同" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="合同类型" required>
|
||||
<el-input v-model="templateForm.contractType" placeholder="如:劳动合同、保密协议、竞业协议" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" required>
|
||||
<el-select v-model="templateForm.status" class="w-full">
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模板说明">
|
||||
<el-input v-model="templateForm.description" type="textarea" :rows="3" placeholder="合同模板适用说明或签署注意事项" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="templateVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveTemplate">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 一站式合同版本与配置大抽屉 -->
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:title="`合同模板管理 · ${selectedTemplate?.templateName || ''}`"
|
||||
size="960px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="selectedTemplate" class="drawer-content-box">
|
||||
<!-- 头部简要卡片 -->
|
||||
<div class="template-header-card">
|
||||
<div class="header-left">
|
||||
<div class="template-title-row">
|
||||
<span class="template-title">{{ selectedTemplate.templateName }}</span>
|
||||
<el-tag type="primary">{{ selectedTemplate.contractType }}</el-tag>
|
||||
<el-tag :type="selectedTemplate.status === 'enable' ? 'success' : 'info'">
|
||||
{{ selectedTemplate.status === 'enable' ? '启用中' : '已停用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="template-meta-row">
|
||||
<span>模板编码:<code>{{ selectedTemplate.templateCode }}</code></span>
|
||||
<span v-if="selectedTemplate.description">说明:{{ selectedTemplate.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="drawerActiveTab" class="management-tabs">
|
||||
<!-- 1. 版本与 DOCX 模板管理 -->
|
||||
<el-tab-pane label="版本与DOCX模板" name="versions">
|
||||
<div class="tab-pane-content">
|
||||
<div class="pane-action-bar">
|
||||
<el-button type="primary" icon="Upload" @click="openVersionCreate">
|
||||
上传新版本 DOCX
|
||||
</el-button>
|
||||
<span class="pane-tip-text">
|
||||
支持多版本迭代。启用某个版本后,该版本的 DOCX 将用于生成新入职员工的合同。
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="versions" border stripe class="mt-3">
|
||||
<el-table-column prop="versionNo" label="版本号" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<span class="font-bold">{{ scope.row.versionNo }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="versionStatus" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="versionType(scope.row.versionStatus)">
|
||||
{{ versionText(scope.row.versionStatus) }}
|
||||
</el-tag>
|
||||
</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 prop="remark" label="版本说明" min-width="150" />
|
||||
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Setting"
|
||||
@click="openVariables(scope.row)"
|
||||
>
|
||||
变量
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Download"
|
||||
@click="downloadDocx(scope.row.sourceDocxFileId)"
|
||||
>
|
||||
下载源文件
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="scope.row.versionStatus !== 'enabled'"
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="changeVersionStatus(scope.row, 'enabled')"
|
||||
>
|
||||
启用
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-else
|
||||
link
|
||||
type="warning"
|
||||
icon="Close"
|
||||
@click="changeVersionStatus(scope.row, 'disabled')"
|
||||
>
|
||||
停用
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 2. 适用范围配置 -->
|
||||
<el-tab-pane label="适用范围 (公司与岗位)" name="scopes">
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="scope-add-card">
|
||||
<template #header>
|
||||
<span class="font-bold">为公司/岗位分配此合同模板</span>
|
||||
</template>
|
||||
<el-form :model="scopeForm" inline class="scope-inline-form">
|
||||
<el-form-item label="所属公司" required>
|
||||
<el-select
|
||||
v-model="scopeForm.companyId"
|
||||
placeholder="请选择公司"
|
||||
filterable
|
||||
style="width: 200px"
|
||||
@change="onScopeCompanyChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="company in companies"
|
||||
:key="company.id"
|
||||
:label="company.companyName"
|
||||
:value="company.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="适用岗位">
|
||||
<el-select
|
||||
v-model="scopeForm.positionId"
|
||||
placeholder="全公司所有岗位"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="position in scopePositions"
|
||||
:key="position.id"
|
||||
:label="position.positionName"
|
||||
:value="position.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="绑定版本" required>
|
||||
<el-select
|
||||
v-model="scopeForm.templateVersionId"
|
||||
placeholder="请选择合同版本"
|
||||
style="width: 180px"
|
||||
>
|
||||
<el-option
|
||||
v-for="ver in enabledVersions"
|
||||
:key="ver.id"
|
||||
:label="`版本 ${ver.versionNo}`"
|
||||
:value="ver.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Plus" @click="saveScope">
|
||||
添加适用范围
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-table :data="scopes" border stripe class="mt-4">
|
||||
<el-table-column label="所属公司" min-width="180">
|
||||
<template #default="scope">
|
||||
<el-tag effect="plain" type="primary">
|
||||
{{ companyName(scope.row.companyId) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="适用岗位" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ positionName(scope.row.positionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="生效版本" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" type="success">
|
||||
版本 {{ versionName(scope.row.templateVersionId) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" icon="Delete" @click="removeScope(scope.row)">
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 上传合同 DOCX 版本弹窗 -->
|
||||
<el-dialog
|
||||
v-model="versionVisible"
|
||||
title="上传新版本 DOCX"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<el-alert
|
||||
title="仅支持 .docx 文件。请在 Word 文档中使用 {{变量编码}}(如 {{EmployeeName}}、{{Salary}})作为占位符。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
|
||||
<el-form :model="versionForm" label-width="120px">
|
||||
<el-form-item label="版本编号" required>
|
||||
<el-input v-model="versionForm.versionNo" placeholder="如:v2026.01" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="DOCX源文件" required>
|
||||
<el-upload
|
||||
:http-request="uploadDocx"
|
||||
:before-upload="beforeDocxUpload"
|
||||
:show-file-list="false"
|
||||
accept=".docx"
|
||||
>
|
||||
<el-button type="primary" plain icon="Upload">
|
||||
{{ versionForm.sourceDocxFileId ? '重新选择DOCX文件' : '选择并上传DOCX' }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="versionForm.sourceDocxFileId" class="file-uploaded-tag">
|
||||
<el-icon color="#10b981"><Check /></el-icon>
|
||||
<span>文件上传成功(ID: {{ versionForm.sourceDocxFileId }})</span>
|
||||
</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>
|
||||
|
||||
<el-form-item label="失效日期">
|
||||
<el-date-picker v-model="versionForm.expiryDate" value-format="YYYY-MM-DD" class="w-full" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="版本备注">
|
||||
<el-input v-model="versionForm.remark" type="textarea" :rows="2" placeholder="本次版本修订说明" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="versionVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveVersion">保存版本</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 变量配置弹窗 -->
|
||||
<el-dialog
|
||||
v-model="variablesVisible"
|
||||
:title="`合同变量配置 · 版本 ${selectedVersion?.versionNo || ''}`"
|
||||
width="1050px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<div class="toolbar-flex">
|
||||
<el-button type="primary" icon="Plus" @click="openVariableCreate">
|
||||
新增变量定义
|
||||
</el-button>
|
||||
<el-alert
|
||||
title="在 DOCX 模板中使用 {{变量编码}} 作为占位符。点击变量编码旁复制按钮可直接复制占位符。"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="flex: 1; margin: 0 0 0 16px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-table :data="variables" border stripe class="mt-3">
|
||||
<el-table-column prop="variableCode" label="变量编码 (DOCX占位符)" min-width="220">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<code style="font-weight: 600; color: #2563eb;">{{ formatPlaceholder(scope.row.variableCode) }}</code>
|
||||
<el-button
|
||||
size="small"
|
||||
link
|
||||
type="primary"
|
||||
icon="CopyDocument"
|
||||
@click="copyPlaceholder(scope.row.variableCode)"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="variableName" label="变量显示名称" min-width="160" />
|
||||
|
||||
<el-table-column prop="variableType" label="来源类型" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="variableTypeTag(scope.row.variableType)">
|
||||
{{ variableTypeText(scope.row.variableType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="dataType" label="数据类型" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" type="info">{{ scope.row.dataType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="employeeField" label="对应员工字段" min-width="140" />
|
||||
|
||||
<el-table-column label="必填" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.required ? 'danger' : 'info'">
|
||||
{{ scope.row.required ? "是" : "否" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="openVariableEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removeVariable(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 新增 / 编辑单个变量 -->
|
||||
<el-dialog
|
||||
v-model="variableVisible"
|
||||
:title="variableEditing ? '编辑变量' : '新增变量'"
|
||||
width="640px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="variableForm" label-width="130px">
|
||||
<el-form-item label="变量编码" required>
|
||||
<el-input v-model="variableForm.variableCode" placeholder="如:EMPLOYEE_NAME" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="变量名称" required>
|
||||
<el-input v-model="variableForm.variableName" placeholder="如:员工姓名" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="变量类型" required>
|
||||
<el-select v-model="variableForm.variableType" class="w-full">
|
||||
<el-option label="员工填写(从新员工基本信息/动态字段读取)" value="employee" />
|
||||
<el-option label="公司信息(从公司档案自动读取)" value="company" />
|
||||
<el-option label="合同/人事填写(人事发起时录入)" value="contract" />
|
||||
<el-option label="系统生成(签署日期等)" value="system" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据类型" required>
|
||||
<el-select v-model="variableForm.dataType" class="w-full">
|
||||
<el-option label="文本" value="string" />
|
||||
<el-option label="日期" value="date" />
|
||||
<el-option label="数字" value="number" />
|
||||
<el-option label="金额" value="decimal" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="variableForm.variableType === 'employee'" label="员工字段">
|
||||
<el-input v-model="variableForm.employeeField" placeholder="如:EmployeeName、IdCardNumber、Phone" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="默认值">
|
||||
<el-input v-model="variableForm.defaultValue" placeholder="未填写时的备选默认值" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="variableForm.sortCode" :min="0" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="必填">
|
||||
<el-switch v-model="variableForm.required" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="人事可修改">
|
||||
<el-switch v-model="variableForm.hrEditable" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="variableForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="variableVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveVariable">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingContract">
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Edit, Delete, Files, Upload, Setting, Download, Check, CopyDocument } from "@element-plus/icons-vue";
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingCompanyOption,
|
||||
type OnboardingContractScope,
|
||||
type OnboardingContractTemplate,
|
||||
type OnboardingContractVersion,
|
||||
type OnboardingPositionOption,
|
||||
type OnboardingTemplateVariable
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
|
||||
const templateTable = ref<ProTableInstance>();
|
||||
|
||||
const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const scopePositions = ref<OnboardingPositionOption[]>([]);
|
||||
|
||||
const selectedTemplate = ref<OnboardingContractTemplate>();
|
||||
const selectedVersion = ref<OnboardingContractVersion>();
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const drawerActiveTab = ref("versions");
|
||||
|
||||
const templateVisible = ref(false);
|
||||
const templateEditing = ref(false);
|
||||
|
||||
const versions = ref<OnboardingContractVersion[]>([]);
|
||||
const versionVisible = ref(false);
|
||||
|
||||
const variablesVisible = ref(false);
|
||||
const variableEditing = ref(false);
|
||||
const variables = ref<OnboardingTemplateVariable[]>([]);
|
||||
const variableVisible = ref(false);
|
||||
|
||||
const scopes = ref<OnboardingContractScope[]>([]);
|
||||
|
||||
const emptyTemplate = (): Partial<OnboardingContractTemplate> => ({
|
||||
templateCode: "",
|
||||
templateName: "",
|
||||
contractType: "",
|
||||
status: "enable",
|
||||
description: ""
|
||||
});
|
||||
|
||||
const emptyVersion = (): Partial<OnboardingContractVersion> => ({
|
||||
versionNo: "",
|
||||
sourceDocxFileId: undefined,
|
||||
effectiveDate: undefined,
|
||||
expiryDate: undefined,
|
||||
remark: ""
|
||||
});
|
||||
|
||||
const emptyVariable = (): Partial<OnboardingTemplateVariable> => ({
|
||||
variableCode: "",
|
||||
variableName: "",
|
||||
variableType: "employee",
|
||||
dataType: "string",
|
||||
employeeField: "",
|
||||
defaultValue: "",
|
||||
sortCode: 10,
|
||||
required: true,
|
||||
hrEditable: true,
|
||||
remark: ""
|
||||
});
|
||||
|
||||
const emptyScope = (): Partial<OnboardingContractScope> => ({
|
||||
companyId: undefined,
|
||||
positionId: undefined,
|
||||
templateVersionId: undefined
|
||||
});
|
||||
|
||||
const templateForm = reactive<Partial<OnboardingContractTemplate>>(emptyTemplate());
|
||||
const versionForm = reactive<Partial<OnboardingContractVersion>>(emptyVersion());
|
||||
const variableForm = reactive<Partial<OnboardingTemplateVariable>>(emptyVariable());
|
||||
const scopeForm = reactive<Partial<OnboardingContractScope>>(emptyScope());
|
||||
|
||||
const enabledVersions = computed(() =>
|
||||
versions.value.filter(item => item.versionStatus === "enabled")
|
||||
);
|
||||
|
||||
const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
|
||||
{
|
||||
prop: "templateCode",
|
||||
label: "模板编码",
|
||||
minWidth: 160,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "templateName",
|
||||
label: "模板名称",
|
||||
minWidth: 200,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "contractType",
|
||||
label: "合同类型",
|
||||
width: 140,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
width: 100,
|
||||
align: "center",
|
||||
enum: [
|
||||
{ label: "启用", value: "enable" },
|
||||
{ label: "停用", value: "disable" }
|
||||
],
|
||||
search: { el: "select" }
|
||||
},
|
||||
{
|
||||
prop: "description",
|
||||
label: "模板说明",
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 260
|
||||
}
|
||||
];
|
||||
|
||||
function formatTime(timeStr?: string) {
|
||||
if (!timeStr) return "-";
|
||||
return timeStr.replace("T", " ").substring(0, 19);
|
||||
}
|
||||
|
||||
function resetTemplate() {
|
||||
Object.assign(templateForm, emptyTemplate());
|
||||
}
|
||||
|
||||
function resetVersion() {
|
||||
Object.assign(versionForm, emptyVersion());
|
||||
}
|
||||
|
||||
function resetVariable() {
|
||||
Object.assign(variableForm, emptyVariable());
|
||||
}
|
||||
|
||||
function resetScope() {
|
||||
Object.assign(scopeForm, emptyScope());
|
||||
}
|
||||
|
||||
async function loadCompanies() {
|
||||
const { data } = await onboardingTaskApi.listCompanies();
|
||||
companies.value = data;
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
if (!selectedTemplate.value) return;
|
||||
const { data } = await onboardingTaskApi.versionPage({
|
||||
templateId: selectedTemplate.value.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
versions.value = data.list;
|
||||
}
|
||||
|
||||
async function loadVariables() {
|
||||
if (!selectedVersion.value) return;
|
||||
const { data } = await onboardingTaskApi.variablePage({
|
||||
templateVersionId: selectedVersion.value.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
variables.value = data.list;
|
||||
}
|
||||
|
||||
async function loadScopes() {
|
||||
if (!selectedTemplate.value) return;
|
||||
const { data } = await onboardingTaskApi.scopePage({
|
||||
templateId: selectedTemplate.value.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
scopes.value = data.list;
|
||||
}
|
||||
|
||||
async function openManagementDrawer(row: OnboardingContractTemplate) {
|
||||
selectedTemplate.value = row;
|
||||
drawerActiveTab.value = "versions";
|
||||
await Promise.all([loadVersions(), loadScopes()]);
|
||||
drawerVisible.value = true;
|
||||
}
|
||||
|
||||
function openTemplateCreate() {
|
||||
templateEditing.value = false;
|
||||
resetTemplate();
|
||||
templateVisible.value = true;
|
||||
}
|
||||
|
||||
async function openTemplateEdit(row: OnboardingContractTemplate) {
|
||||
const { data } = await onboardingTaskApi.templateDetail({ id: row.id });
|
||||
templateEditing.value = true;
|
||||
Object.assign(templateForm, data);
|
||||
templateVisible.value = true;
|
||||
}
|
||||
|
||||
async function saveTemplate() {
|
||||
if (!templateForm.templateCode || !templateForm.templateName || !templateForm.contractType) {
|
||||
return ElMessage.warning("请填写模板编码、名称和合同类型");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.saveTemplate(templateForm, templateEditing.value);
|
||||
templateVisible.value = false;
|
||||
templateTable.value?.refresh();
|
||||
ElMessage.success("保存成功");
|
||||
}
|
||||
|
||||
async function removeTemplate(row: OnboardingContractTemplate) {
|
||||
await ElMessageBox.confirm(`确认删除合同模板“${row.templateName}”?`, "删除确认", { type: "warning" });
|
||||
await onboardingTaskApi.deleteTemplate([row.id]);
|
||||
templateTable.value?.refresh();
|
||||
ElMessage.success("删除成功");
|
||||
}
|
||||
|
||||
function openVersionCreate() {
|
||||
resetVersion();
|
||||
versionVisible.value = true;
|
||||
}
|
||||
|
||||
function beforeDocxUpload(file: File) {
|
||||
if (!file.name.toLowerCase().endsWith(".docx")) {
|
||||
ElMessage.error("合同模板仅支持 DOCX 文件");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function uploadDocx(options: any) {
|
||||
const form = new FormData();
|
||||
form.append("file", options.file);
|
||||
try {
|
||||
const { data } = await onboardingTaskApi.uploadDocx(form);
|
||||
versionForm.sourceDocxFileId = data;
|
||||
options.onSuccess?.(data);
|
||||
ElMessage.success("DOCX 文件上传成功");
|
||||
} catch (error) {
|
||||
options.onError?.(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveVersion() {
|
||||
if (!selectedTemplate.value || !versionForm.versionNo || !versionForm.sourceDocxFileId) {
|
||||
return ElMessage.warning("请填写版本号并上传DOCX文件");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.addVersion({
|
||||
...versionForm,
|
||||
templateId: selectedTemplate.value.id
|
||||
});
|
||||
|
||||
versionVisible.value = false;
|
||||
await loadVersions();
|
||||
ElMessage.success("版本已保存");
|
||||
}
|
||||
|
||||
async function changeVersionStatus(row: OnboardingContractVersion, status: string) {
|
||||
await onboardingTaskApi.setVersionStatus({ id: row.id, versionStatus: status });
|
||||
await loadVersions();
|
||||
ElMessage.success(`版本状态已更新为:${versionText(status)}`);
|
||||
}
|
||||
|
||||
function downloadDocx(fileId?: number) {
|
||||
if (!fileId) return ElMessage.warning("暂无DOCX源文件");
|
||||
window.open(`/api/sys/dev/file/download?id=${fileId}`, "_blank");
|
||||
}
|
||||
|
||||
type EpTagType = "success" | "warning" | "info" | "primary" | "danger";
|
||||
|
||||
function versionText(status: string) {
|
||||
return status === "enabled" ? "已启用" : status === "disabled" ? "已停用" : "草稿";
|
||||
}
|
||||
|
||||
function versionType(status: string): EpTagType {
|
||||
return status === "enabled" ? "success" : status === "disabled" ? "info" : "warning";
|
||||
}
|
||||
|
||||
function variableTypeTag(type: string): EpTagType {
|
||||
const map: Record<string, EpTagType> = {
|
||||
employee: "primary",
|
||||
company: "success",
|
||||
contract: "warning",
|
||||
system: "info"
|
||||
};
|
||||
return map[type] || "info";
|
||||
}
|
||||
|
||||
function variableTypeText(type: string) {
|
||||
const map: Record<string, string> = {
|
||||
employee: "员工填写",
|
||||
company: "公司信息",
|
||||
contract: "人事合同参数",
|
||||
system: "系统自动"
|
||||
};
|
||||
return map[type] || type;
|
||||
}
|
||||
|
||||
function formatPlaceholder(code: string) {
|
||||
return `{{${code}}}`;
|
||||
}
|
||||
|
||||
function copyPlaceholder(code: string) {
|
||||
navigator.clipboard.writeText(`{{${code}}}`);
|
||||
ElMessage.success(`占位符 {{${code}}} 已复制到剪贴板`);
|
||||
}
|
||||
|
||||
async function openVariables(row: OnboardingContractVersion) {
|
||||
selectedVersion.value = row;
|
||||
await loadVariables();
|
||||
variablesVisible.value = true;
|
||||
}
|
||||
|
||||
function openVariableCreate() {
|
||||
variableEditing.value = false;
|
||||
resetVariable();
|
||||
variableVisible.value = true;
|
||||
}
|
||||
|
||||
function openVariableEdit(row: OnboardingTemplateVariable) {
|
||||
variableEditing.value = true;
|
||||
Object.assign(variableForm, row);
|
||||
variableVisible.value = true;
|
||||
}
|
||||
|
||||
async function saveVariable() {
|
||||
if (
|
||||
!selectedVersion.value ||
|
||||
!variableForm.variableCode ||
|
||||
!variableForm.variableName ||
|
||||
!variableForm.variableType ||
|
||||
!variableForm.dataType
|
||||
) {
|
||||
return ElMessage.warning("请填写变量编码、名称、类型和数据类型");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.saveVariable(
|
||||
{
|
||||
...variableForm,
|
||||
templateVersionId: selectedVersion.value.id
|
||||
},
|
||||
variableEditing.value
|
||||
);
|
||||
|
||||
variableVisible.value = false;
|
||||
await loadVariables();
|
||||
ElMessage.success("变量已保存");
|
||||
}
|
||||
|
||||
async function removeVariable(row: OnboardingTemplateVariable) {
|
||||
await ElMessageBox.confirm(`确认删除变量“${row.variableName}”?`, "删除确认", { type: "warning" });
|
||||
await onboardingTaskApi.deleteVariable([row.id]);
|
||||
await loadVariables();
|
||||
ElMessage.success("删除成功");
|
||||
}
|
||||
|
||||
async function onScopeCompanyChange(companyId: number) {
|
||||
scopeForm.positionId = undefined;
|
||||
const { data } = await onboardingTaskApi.listPositions(companyId);
|
||||
scopePositions.value = data;
|
||||
}
|
||||
|
||||
async function saveScope() {
|
||||
if (!selectedTemplate.value || !scopeForm.companyId || !scopeForm.templateVersionId) {
|
||||
return ElMessage.warning("请选择公司和已启用的合同版本");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.addScope({
|
||||
...scopeForm,
|
||||
templateId: selectedTemplate.value.id
|
||||
});
|
||||
|
||||
await loadScopes();
|
||||
resetScope();
|
||||
ElMessage.success("适用范围已添加");
|
||||
}
|
||||
|
||||
async function removeScope(row: OnboardingContractScope) {
|
||||
await ElMessageBox.confirm("确认删除该适用范围?", "删除确认", { type: "warning" });
|
||||
await onboardingTaskApi.deleteScope([row.id]);
|
||||
await loadScopes();
|
||||
ElMessage.success("删除成功");
|
||||
}
|
||||
|
||||
const companyName = (id: number) => companies.value.find(c => c.id === id)?.companyName || id;
|
||||
|
||||
const positionName = (id?: number) => {
|
||||
if (!id) return "全公司 (所有岗位)";
|
||||
return scopePositions.value.find(p => p.id === id)?.positionName || id;
|
||||
};
|
||||
|
||||
const versionName = (id: number) => versions.value.find(v => v.id === id)?.versionNo || id;
|
||||
|
||||
onMounted(loadCompanies);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.table-search) {
|
||||
margin-bottom: 12px !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.table-main) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.drawer-content-box {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.template-header-card {
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.template-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.template-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.template-meta-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pane-action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pane-tip-text {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-uploaded-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: #10b981;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.toolbar-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
title="正式员工档案"
|
||||
:columns="columns"
|
||||
:request-api="onboardingTaskApi.employeePage"
|
||||
>
|
||||
<template #operation="scope">
|
||||
<el-button link type="primary" icon="UserFilled" @click="showDetail(scope.row)">
|
||||
查看完整档案
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<!-- 员工完整档案抽屉 -->
|
||||
<el-drawer
|
||||
v-model="detailVisible"
|
||||
:title="`员工档案详情 · ${detail?.employee?.employeeName || ''}`"
|
||||
size="820px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="detail?.employee" class="employee-drawer-content">
|
||||
<!-- 头部个人简要信息 -->
|
||||
<div class="employee-header-card">
|
||||
<el-avatar :size="64" class="employee-avatar">
|
||||
{{ detail.employee.employeeName ? detail.employee.employeeName.substring(0, 1) : '员' }}
|
||||
</el-avatar>
|
||||
<div class="employee-header-info">
|
||||
<div class="employee-name-row">
|
||||
<span class="employee-name">{{ detail.employee.employeeName }}</span>
|
||||
<el-tag size="small" :type="detail.employee.gender === '女' ? 'danger' : 'primary'" effect="plain">
|
||||
{{ detail.employee.gender || '未知' }}
|
||||
</el-tag>
|
||||
<el-tag size="small" type="success">正式员工</el-tag>
|
||||
</div>
|
||||
<div class="employee-meta-row">
|
||||
<span>员工编号:{{ detail.employee.employeeNo || '-' }}</span>
|
||||
<span>联系电话:{{ detail.employee.phone }}</span>
|
||||
<span>入职日期:{{ detail.employee.entryDate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="employee-tabs">
|
||||
<!-- 1. 基本人事档案 -->
|
||||
<el-tab-pane label="基本人事档案" name="basic">
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<div class="card-header-title">身份与联系信息</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="姓名">{{ detail.employee.employeeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detail.employee.gender || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detail.employee.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号码">{{ detail.employee.idCardNumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">{{ detail.employee.birthDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ detail.employee.nation || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="户口所在地" :span="2">
|
||||
{{ detail.employee.registeredResidence || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="现居通讯地址" :span="2">
|
||||
{{ detail.employee.contactAddress || '-' }}
|
||||
</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.college || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最高学历">{{ detail.employee.education || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所学专业">{{ detail.employee.major || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门名称">{{ detail.employee.departmentName || '-' }}</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="合同开始日期">{{ detail.employee.contractStartDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同终止日期">{{ detail.employee.contractEndDate || '-' }}</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.bankCardNumber || '-' }}</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>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 2. 归档合同与协议 -->
|
||||
<el-tab-pane label="归档合同协议" name="contracts">
|
||||
<el-empty v-if="!detail.contracts?.length" description="暂无已归档的劳动合同" />
|
||||
<div v-else class="contracts-list">
|
||||
<div v-for="contract in detail.contracts" :key="contract.id" class="contract-box">
|
||||
<div class="contract-info-left">
|
||||
<div class="contract-title font-bold">{{ contract.contractName }}</div>
|
||||
<div class="contract-meta-text text-secondary">
|
||||
<span>状态:{{ contract.contractStatus }}</span>
|
||||
<span v-if="contract.offlineSignedAt">· 线下签署于:{{ formatTime(contract.offlineSignedAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contract-action-right">
|
||||
<el-button
|
||||
v-if="contract.generatedDocxFileId"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
icon="Download"
|
||||
@click="download(contract.generatedDocxFileId)"
|
||||
>
|
||||
下载合同 DOCX
|
||||
</el-button>
|
||||
<el-tag v-else type="info">尚未生成电子文件</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 3. 入职流转与审核日志 -->
|
||||
<el-tab-pane label="入职流转时间轴" name="audits">
|
||||
<el-empty v-if="!detail.audits?.length" description="暂无流转记录" />
|
||||
<div v-else class="timeline-box">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="item in detail.audits"
|
||||
:key="item.id"
|
||||
type="primary"
|
||||
:timestamp="formatTime(item.auditedAt)"
|
||||
placement="top"
|
||||
>
|
||||
<div class="audit-card">
|
||||
<div class="audit-action-name font-bold">
|
||||
{{ auditActionText(item.auditAction) }}
|
||||
<span v-if="item.createUser" class="audit-operator">(操作人:{{ item.createUser }})</span>
|
||||
</div>
|
||||
<div v-if="item.auditOpinion" class="audit-opinion">
|
||||
审核意见:{{ item.auditOpinion }}
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingEmployee">
|
||||
import { ref } from "vue";
|
||||
import { UserFilled, Download } from "@element-plus/icons-vue";
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingEmployee
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
import type { ColumnProps } from "@/components/ProTable/interface";
|
||||
|
||||
const detailVisible = ref(false);
|
||||
const activeTab = ref("basic");
|
||||
const detail = ref<any>();
|
||||
|
||||
const columns: ColumnProps<OnboardingEmployee>[] = [
|
||||
{
|
||||
prop: "employeeNo",
|
||||
label: "员工编号",
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
prop: "employeeName",
|
||||
label: "姓名",
|
||||
minWidth: 140,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "gender",
|
||||
label: "性别",
|
||||
minWidth: 80,
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
prop: "phone",
|
||||
label: "手机号码",
|
||||
minWidth: 160,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "entryDate",
|
||||
label: "入职日期",
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "建档时间",
|
||||
minWidth: 180,
|
||||
render: scope => formatTime(scope.row.createTime)
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 160
|
||||
}
|
||||
];
|
||||
|
||||
const auditActionText = (action: string) => {
|
||||
const map: Record<string, string> = {
|
||||
submit: "候选人提交入职资料",
|
||||
approve: "人事复核通过并转正建档",
|
||||
reject: "人事退回补充资料",
|
||||
archive: "任务归档",
|
||||
revoke: "任务撤销"
|
||||
};
|
||||
return map[action] || action;
|
||||
};
|
||||
|
||||
const formatTime = (timeStr?: string) => {
|
||||
if (!timeStr) return "-";
|
||||
return timeStr.replace("T", " ").substring(0, 19);
|
||||
};
|
||||
|
||||
async function showDetail(row: OnboardingEmployee) {
|
||||
const { data } = await onboardingTaskApi.employeeDetail({
|
||||
id: row.id
|
||||
});
|
||||
|
||||
detail.value = data;
|
||||
activeTab.value = "basic";
|
||||
detailVisible.value = true;
|
||||
}
|
||||
|
||||
function download(fileId: number) {
|
||||
window.open(`/api/sys/dev/file/download?id=${fileId}`, "_blank");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.table-search) {
|
||||
margin-bottom: 12px !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.table-main) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.employee-drawer-content {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.employee-header-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.employee-avatar {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.employee-header-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.employee-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.employee-name {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.employee-meta-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.card-header-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 合同列表 */
|
||||
.contracts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.contract-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.contract-meta-text {
|
||||
font-size: 13px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 时间轴 */
|
||||
.timeline-box {
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.audit-card {
|
||||
background: #f8fafc;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.audit-operator {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.audit-opinion {
|
||||
font-size: 13px;
|
||||
color: #dc2626;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,652 @@
|
||||
<template>
|
||||
<div class="main-box config-container">
|
||||
<el-tabs v-model="activeTab" class="config-tabs">
|
||||
<el-tab-pane label="动态填写字段" name="fields">
|
||||
<div class="tab-card-wrapper">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" icon="Plus" @click="openFieldCreate">
|
||||
新增字段
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="fields" border stripe>
|
||||
<el-table-column prop="fieldCode" label="字段编码" min-width="140" />
|
||||
<el-table-column prop="fieldName" label="字段名称" min-width="150" />
|
||||
<el-table-column prop="dataType" label="数据类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" type="info">{{ scope.row.dataType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="placeholder" label="提示语" min-width="160" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'enable' ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="openFieldEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removeField(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="上传资料类型" name="materials">
|
||||
<div class="tab-card-wrapper">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" icon="Plus" @click="openMaterialCreate">
|
||||
新增资料类型
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="materialTypes" border stripe>
|
||||
<el-table-column prop="materialCode" label="资料编码" min-width="140" />
|
||||
<el-table-column prop="materialName" label="资料名称" min-width="150" />
|
||||
<el-table-column prop="allowedExtensions" label="允许扩展名" min-width="140">
|
||||
<template #default="scope">
|
||||
<code>{{ scope.row.allowedExtensions || '不限' }}</code>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maxFileCount" label="最大数量" width="100" align="center" />
|
||||
<el-table-column prop="maxFileSizeMb" label="最大限制(MB)" width="120" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'enable' ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="openMaterialEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removeMaterial(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="公司岗位要求" name="requirements">
|
||||
<div class="tab-card-wrapper">
|
||||
<el-alert title="请先选择公司,再为全公司或某个岗位分别配置员工必填信息和上传资料。" type="info" :closable="false" class="hint" />
|
||||
|
||||
<el-select v-model="selectedCompanyId" placeholder="请选择公司" filterable @change="loadRequirements">
|
||||
<el-option v-for="company in companies" :key="company.id" :label="company.companyName" :value="company.id" />
|
||||
</el-select>
|
||||
|
||||
<template v-if="selectedCompanyId">
|
||||
<el-divider />
|
||||
|
||||
<el-tabs v-model="requirementTab">
|
||||
<el-tab-pane label="填写字段要求" name="form">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openRequirementCreate('form')">
|
||||
新增填写要求
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="formRequirements" border>
|
||||
<el-table-column label="字段">
|
||||
<template #default="scope">
|
||||
{{ fieldName(scope.row.fieldId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="岗位">
|
||||
<template #default="scope">
|
||||
{{ positionName(scope.row.positionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="必填">
|
||||
<template #default="scope">
|
||||
{{ scope.row.required ? "是" : "否" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sortCode" label="排序" />
|
||||
|
||||
<el-table-column label="操作" width="140">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="openRequirementEdit('form', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" @click="removeRequirement('form', scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="上传资料要求" name="material">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openRequirementCreate('material')">
|
||||
新增资料要求
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="materialRequirements" border>
|
||||
<el-table-column label="资料类型">
|
||||
<template #default="scope">
|
||||
{{ materialName(scope.row.materialTypeId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="岗位">
|
||||
<template #default="scope">
|
||||
{{ positionName(scope.row.positionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="必传">
|
||||
<template #default="scope">
|
||||
{{ scope.row.required ? "是" : "否" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sortCode" label="排序" />
|
||||
|
||||
<el-table-column label="操作" width="140">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="openRequirementEdit('material', scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" @click="removeRequirement('material', scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog v-model="fieldVisible" :title="fieldForm.id ? '编辑字段' : '新增字段'" width="560px" destroy-on-close>
|
||||
<el-form :model="fieldForm" label-width="110px">
|
||||
<el-form-item label="字段编码" required>
|
||||
<el-input v-model="fieldForm.fieldCode" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="字段名称" required>
|
||||
<el-input v-model="fieldForm.fieldName" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据类型" required>
|
||||
<el-select v-model="fieldForm.dataType">
|
||||
<el-option label="文本" value="string" />
|
||||
<el-option label="日期" value="date" />
|
||||
<el-option label="整数" value="int" />
|
||||
<el-option label="数字" value="number" />
|
||||
<el-option label="金额" value="decimal" />
|
||||
<el-option label="下拉选择" value="select" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="提示语">
|
||||
<el-input v-model="fieldForm.placeholder" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="选项JSON">
|
||||
<el-input v-model="fieldForm.optionsJson" type="textarea" placeholder="下拉/多选字段可填写选项JSON" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="校验规则">
|
||||
<el-input v-model="fieldForm.validationRule" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="fieldForm.status">
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="fieldVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" @click="saveField">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="materialVisible" :title="materialForm.id ? '编辑资料类型' : '新增资料类型'" width="560px" destroy-on-close>
|
||||
<el-form :model="materialForm" label-width="120px">
|
||||
<el-form-item label="资料编码" required>
|
||||
<el-input v-model="materialForm.materialCode" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="资料名称" required>
|
||||
<el-input v-model="materialForm.materialName" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="允许扩展名">
|
||||
<el-input v-model="materialForm.allowedExtensions" placeholder="如:jpg,png,pdf" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大文件数量">
|
||||
<el-input-number v-model="materialForm.maxFileCount" :min="1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大大小(MB)">
|
||||
<el-input-number v-model="materialForm.maxFileSizeMb" :min="1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="materialForm.status">
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="materialVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" @click="saveMaterial">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="requirementVisible" :title="requirementKind === 'form' ? '填写字段要求' : '上传资料要求'" width="560px"
|
||||
destroy-on-close>
|
||||
<el-form :model="requirementForm" label-width="110px">
|
||||
<el-form-item label="所属公司">
|
||||
<el-select v-model="requirementForm.companyId" disabled>
|
||||
<el-option v-for="company in companies" :key="company.id" :label="company.companyName"
|
||||
:value="company.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="适用岗位">
|
||||
<el-select v-model="requirementForm.positionId" clearable placeholder="不选表示全公司">
|
||||
<el-option v-for="position in positions" :key="position.id" :label="position.positionName"
|
||||
:value="position.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="requirementKind === 'form'" label="填写字段" required>
|
||||
<el-select v-model="requirementForm.fieldId">
|
||||
<el-option v-for="field in enabledFields" :key="field.id" :label="field.fieldName" :value="field.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-else label="资料类型" required>
|
||||
<el-select v-model="requirementForm.materialTypeId">
|
||||
<el-option v-for="material in enabledMaterials" :key="material.id" :label="material.materialName"
|
||||
:value="material.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否必填/必传">
|
||||
<el-switch v-model="requirementForm.required" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="requirementForm.sortCode" :min="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="requirementVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" @click="saveRequirement">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingEntryConfig">
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingCompanyOption,
|
||||
type OnboardingFormField,
|
||||
type OnboardingFormRequirement,
|
||||
type OnboardingMaterialRequirement,
|
||||
type OnboardingMaterialType,
|
||||
type OnboardingPositionOption
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
|
||||
const activeTab = ref("fields");
|
||||
const requirementTab = ref("form");
|
||||
|
||||
const fields = ref<OnboardingFormField[]>([]);
|
||||
const materialTypes = ref<OnboardingMaterialType[]>([]);
|
||||
const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const positions = ref<OnboardingPositionOption[]>([]);
|
||||
|
||||
const formRequirements = ref<OnboardingFormRequirement[]>([]);
|
||||
const materialRequirements = ref<OnboardingMaterialRequirement[]>([]);
|
||||
|
||||
const selectedCompanyId = ref<number>();
|
||||
|
||||
const fieldVisible = ref(false);
|
||||
const materialVisible = ref(false);
|
||||
const requirementVisible = ref(false);
|
||||
|
||||
const requirementKind = ref<"form" | "material">("form");
|
||||
|
||||
const fieldForm = reactive<Partial<OnboardingFormField>>({});
|
||||
const materialForm = reactive<Partial<OnboardingMaterialType>>({});
|
||||
const requirementForm = reactive<any>({});
|
||||
|
||||
const enabledFields = computed(() =>
|
||||
fields.value.filter(item => item.status === "enable")
|
||||
);
|
||||
|
||||
const enabledMaterials = computed(() =>
|
||||
materialTypes.value.filter(item => item.status === "enable")
|
||||
);
|
||||
|
||||
async function loadBase() {
|
||||
const [
|
||||
fieldResult,
|
||||
materialResult,
|
||||
companyResult
|
||||
] = await Promise.all([
|
||||
onboardingTaskApi.listFormFields(),
|
||||
onboardingTaskApi.listMaterialTypes(),
|
||||
onboardingTaskApi.listCompanies()
|
||||
]);
|
||||
|
||||
fields.value = fieldResult.data;
|
||||
materialTypes.value = materialResult.data;
|
||||
companies.value = companyResult.data;
|
||||
}
|
||||
|
||||
async function loadRequirements() {
|
||||
if (!selectedCompanyId.value) return;
|
||||
|
||||
const [
|
||||
formResult,
|
||||
materialResult,
|
||||
positionResult
|
||||
] = await Promise.all([
|
||||
onboardingTaskApi.listFormRequirements(selectedCompanyId.value),
|
||||
onboardingTaskApi.listMaterialRequirements(selectedCompanyId.value),
|
||||
onboardingTaskApi.listPositions(selectedCompanyId.value)
|
||||
]);
|
||||
|
||||
formRequirements.value = formResult.data;
|
||||
materialRequirements.value = materialResult.data;
|
||||
positions.value = positionResult.data;
|
||||
}
|
||||
|
||||
function resetField() {
|
||||
Object.assign(fieldForm, {
|
||||
id: undefined,
|
||||
fieldCode: "",
|
||||
fieldName: "",
|
||||
dataType: "string",
|
||||
placeholder: "",
|
||||
optionsJson: "",
|
||||
validationRule: "",
|
||||
status: "enable"
|
||||
});
|
||||
}
|
||||
|
||||
function resetMaterial() {
|
||||
Object.assign(materialForm, {
|
||||
id: undefined,
|
||||
materialCode: "",
|
||||
materialName: "",
|
||||
allowedExtensions: "",
|
||||
maxFileCount: 1,
|
||||
maxFileSizeMb: undefined,
|
||||
status: "enable"
|
||||
});
|
||||
}
|
||||
|
||||
function openFieldCreate() {
|
||||
resetField();
|
||||
fieldVisible.value = true;
|
||||
}
|
||||
|
||||
function openFieldEdit(row: OnboardingFormField) {
|
||||
Object.assign(fieldForm, row);
|
||||
fieldVisible.value = true;
|
||||
}
|
||||
|
||||
async function saveField() {
|
||||
if (
|
||||
!fieldForm.fieldCode ||
|
||||
!fieldForm.fieldName ||
|
||||
!fieldForm.dataType
|
||||
) {
|
||||
return ElMessage.warning(
|
||||
"请填写字段编码、名称和数据类型"
|
||||
);
|
||||
}
|
||||
|
||||
await onboardingTaskApi.saveFormField(fieldForm);
|
||||
|
||||
fieldVisible.value = false;
|
||||
|
||||
await loadBase();
|
||||
}
|
||||
|
||||
function openMaterialCreate() {
|
||||
resetMaterial();
|
||||
materialVisible.value = true;
|
||||
}
|
||||
|
||||
function openMaterialEdit(row: OnboardingMaterialType) {
|
||||
Object.assign(materialForm, row);
|
||||
materialVisible.value = true;
|
||||
}
|
||||
|
||||
async function saveMaterial() {
|
||||
if (
|
||||
!materialForm.materialCode ||
|
||||
!materialForm.materialName
|
||||
) {
|
||||
return ElMessage.warning(
|
||||
"请填写资料编码和名称"
|
||||
);
|
||||
}
|
||||
|
||||
await onboardingTaskApi.saveMaterialType(materialForm);
|
||||
|
||||
materialVisible.value = false;
|
||||
|
||||
await loadBase();
|
||||
}
|
||||
|
||||
async function removeField(row: OnboardingFormField) {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除字段“${row.fieldName}”?`,
|
||||
"删除确认"
|
||||
);
|
||||
|
||||
await onboardingTaskApi.deleteFormFields([row.id]);
|
||||
|
||||
await loadBase();
|
||||
}
|
||||
|
||||
async function removeMaterial(row: OnboardingMaterialType) {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除资料类型“${row.materialName}”?`,
|
||||
"删除确认"
|
||||
);
|
||||
|
||||
await onboardingTaskApi.deleteMaterialTypes([row.id]);
|
||||
|
||||
await loadBase();
|
||||
}
|
||||
|
||||
function resetRequirement() {
|
||||
Object.assign(requirementForm, {
|
||||
id: undefined,
|
||||
companyId: selectedCompanyId.value,
|
||||
positionId: undefined,
|
||||
fieldId: undefined,
|
||||
materialTypeId: undefined,
|
||||
required: true,
|
||||
sortCode: 0
|
||||
});
|
||||
}
|
||||
|
||||
function openRequirementCreate(
|
||||
kind: "form" | "material"
|
||||
) {
|
||||
requirementKind.value = kind;
|
||||
|
||||
resetRequirement();
|
||||
|
||||
requirementVisible.value = true;
|
||||
}
|
||||
|
||||
function openRequirementEdit(
|
||||
kind: "form" | "material",
|
||||
row: any
|
||||
) {
|
||||
requirementKind.value = kind;
|
||||
|
||||
Object.assign(requirementForm, row);
|
||||
|
||||
requirementVisible.value = true;
|
||||
}
|
||||
|
||||
async function saveRequirement() {
|
||||
if (
|
||||
requirementKind.value === "form" &&
|
||||
!requirementForm.fieldId
|
||||
) {
|
||||
return ElMessage.warning("请选择填写字段");
|
||||
}
|
||||
|
||||
if (
|
||||
requirementKind.value === "material" &&
|
||||
!requirementForm.materialTypeId
|
||||
) {
|
||||
return ElMessage.warning("请选择资料类型");
|
||||
}
|
||||
|
||||
if (requirementKind.value === "form") {
|
||||
await onboardingTaskApi.saveFormRequirement(
|
||||
requirementForm
|
||||
);
|
||||
} else {
|
||||
await onboardingTaskApi.saveMaterialRequirement(
|
||||
requirementForm
|
||||
);
|
||||
}
|
||||
|
||||
requirementVisible.value = false;
|
||||
|
||||
await loadRequirements();
|
||||
}
|
||||
|
||||
async function removeRequirement(
|
||||
kind: "form" | "material",
|
||||
row: any
|
||||
) {
|
||||
await ElMessageBox.confirm(
|
||||
"确认删除该要求?",
|
||||
"删除确认"
|
||||
);
|
||||
|
||||
if (kind === "form") {
|
||||
await onboardingTaskApi.deleteFormRequirements([
|
||||
row.id
|
||||
]);
|
||||
} else {
|
||||
await onboardingTaskApi.deleteMaterialRequirements([
|
||||
row.id
|
||||
]);
|
||||
}
|
||||
|
||||
await loadRequirements();
|
||||
}
|
||||
|
||||
const fieldName = (id: number) =>
|
||||
fields.value.find(item => item.id === id)?.fieldName || id;
|
||||
|
||||
const materialName = (id: number) =>
|
||||
materialTypes.value.find(item => item.id === id)?.materialName || id;
|
||||
|
||||
const positionName = (id?: number) =>
|
||||
!id
|
||||
? "全公司"
|
||||
: positions.value.find(item => item.id === id)?.positionName || id;
|
||||
|
||||
onMounted(loadBase);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.config-tabs {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.config-tabs :deep(.el-tabs__header) {
|
||||
background: #fff;
|
||||
padding: 10px 16px 0;
|
||||
margin: 0 0 10px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.config-tabs :deep(.el-tabs__content) {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tab-card-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,525 @@
|
||||
<template>
|
||||
<div class="main-box setup-container">
|
||||
<el-tabs v-model="activeTab" class="setup-tabs">
|
||||
<!-- 1. 入职公司管理 -->
|
||||
<el-tab-pane label="入职公司" name="company">
|
||||
<div class="table-wrapper">
|
||||
<ProTable
|
||||
ref="companyTable"
|
||||
title="入职公司列表"
|
||||
:columns="companyColumns"
|
||||
:request-api="onboardingTaskApi.companyPage"
|
||||
:search-col="5"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button type="primary" icon="Plus" @click="openCompanyCreate">
|
||||
新增入职公司
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #status="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
|
||||
{{ scope.row.status === "enable" ? "启用" : "停用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #operation="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="openCompanyEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removeCompany(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 2. 入职岗位管理 -->
|
||||
<el-tab-pane label="入职岗位" name="position">
|
||||
<div class="table-wrapper">
|
||||
<ProTable
|
||||
ref="positionTable"
|
||||
title="入职岗位列表"
|
||||
:columns="positionColumns"
|
||||
:request-api="onboardingTaskApi.positionPage"
|
||||
:search-col="5"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button type="primary" icon="Plus" @click="openPositionCreate">
|
||||
新增入职岗位
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #companyId="scope">
|
||||
<el-tag effect="plain" type="primary">
|
||||
{{ getCompanyName(scope.row.companyId) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #status="scope">
|
||||
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
|
||||
{{ scope.row.status === "enable" ? "启用" : "停用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #operation="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="openPositionEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
|
||||
<el-button link type="danger" icon="Delete" @click="removePosition(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 新增 / 编辑弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="680px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<el-form v-if="dialogKind === 'company'" :model="companyForm" label-width="140px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公司编码" required>
|
||||
<el-input v-model="companyForm.companyCode" placeholder="如:TECH_CORP" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公司简称/名称" required>
|
||||
<el-input v-model="companyForm.companyName" placeholder="如:总公司 / 科技分部" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="合同甲方全称" required>
|
||||
<el-input v-model="companyForm.legalName" placeholder="用于合同填写的法定主体全称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="统一社会信用代码">
|
||||
<el-input v-model="companyForm.unifiedSocialCreditCode" placeholder="18位统一信用代码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法定代表人">
|
||||
<el-input v-model="companyForm.legalRepresentative" placeholder="法定代表人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="companyForm.contactPhone" placeholder="企业联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" required>
|
||||
<el-select v-model="companyForm.status" class="w-full">
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="注册地址">
|
||||
<el-input v-model="companyForm.registeredAddress" placeholder="企业法定注册地址" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="companyForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form v-else :model="positionForm" label-width="140px">
|
||||
<el-form-item label="所属公司" required>
|
||||
<el-select v-model="positionForm.companyId" placeholder="请选择所属公司" filterable class="w-full">
|
||||
<el-option
|
||||
v-for="company in companies"
|
||||
:key="company.id"
|
||||
:label="company.companyName"
|
||||
:value="company.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位编码" required>
|
||||
<el-input v-model="positionForm.positionCode" placeholder="如:DEV_FRONTEND" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位名称" required>
|
||||
<el-input v-model="positionForm.positionName" placeholder="如:高级前端开发工程师" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认合同年限">
|
||||
<el-input-number v-model="positionForm.defaultContractYears" :min="1" :max="20" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认试用期(月)">
|
||||
<el-input-number v-model="positionForm.defaultProbationMonths" :min="0" :max="12" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="状态" required>
|
||||
<el-select v-model="positionForm.status" class="w-full">
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="positionForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingSetup">
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Edit, Delete } from "@element-plus/icons-vue";
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingCompany,
|
||||
type OnboardingCompanyOption,
|
||||
type OnboardingPosition
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
import type { ColumnProps, ProTableInstance, EnumProps } from "@/components/ProTable/interface";
|
||||
|
||||
const activeTab = ref("company");
|
||||
|
||||
const companyTable = ref<ProTableInstance>();
|
||||
const positionTable = ref<ProTableInstance>();
|
||||
|
||||
const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const companyOptions = computed<EnumProps[]>(() =>
|
||||
companies.value.map(c => ({
|
||||
label: c.companyName,
|
||||
value: c.id
|
||||
}))
|
||||
);
|
||||
|
||||
const getCompanyName = (companyId?: number) => {
|
||||
if (!companyId) return "-";
|
||||
const found = companies.value.find(c => c.id === companyId);
|
||||
return found ? found.companyName : String(companyId);
|
||||
};
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const dialogKind = ref<"company" | "position">("company");
|
||||
const editing = ref(false);
|
||||
|
||||
const emptyCompany = (): Partial<OnboardingCompany> => ({
|
||||
companyCode: "",
|
||||
companyName: "",
|
||||
legalName: "",
|
||||
unifiedSocialCreditCode: "",
|
||||
legalRepresentative: "",
|
||||
registeredAddress: "",
|
||||
contactPhone: "",
|
||||
status: "enable",
|
||||
remark: ""
|
||||
});
|
||||
|
||||
const emptyPosition = (): Partial<OnboardingPosition> => ({
|
||||
companyId: undefined,
|
||||
positionCode: "",
|
||||
positionName: "",
|
||||
defaultContractYears: 3,
|
||||
defaultProbationMonths: 3,
|
||||
status: "enable",
|
||||
remark: ""
|
||||
});
|
||||
|
||||
const companyForm = reactive<Partial<OnboardingCompany>>(emptyCompany());
|
||||
const positionForm = reactive<Partial<OnboardingPosition>>(emptyPosition());
|
||||
|
||||
const dialogTitle = computed(
|
||||
() => `${editing.value ? "编辑" : "新增"}${dialogKind.value === "company" ? "入职公司" : "入职岗位"}`
|
||||
);
|
||||
|
||||
const companyColumns: ColumnProps<OnboardingCompany>[] = [
|
||||
{
|
||||
prop: "companyCode",
|
||||
label: "公司编码",
|
||||
width: 140,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "companyName",
|
||||
label: "公司名称",
|
||||
minWidth: 180,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "legalName",
|
||||
label: "合同甲方全称",
|
||||
minWidth: 220
|
||||
},
|
||||
{
|
||||
prop: "unifiedSocialCreditCode",
|
||||
label: "统一社会信用代码",
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: "legalRepresentative",
|
||||
label: "法定代表人",
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: "contactPhone",
|
||||
label: "联系电话",
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
width: 100,
|
||||
align: "center",
|
||||
enum: [
|
||||
{ label: "启用", value: "enable" },
|
||||
{ label: "停用", value: "disable" }
|
||||
],
|
||||
search: { el: "select" }
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 160
|
||||
}
|
||||
];
|
||||
|
||||
const positionColumns: ColumnProps<OnboardingPosition>[] = [
|
||||
{
|
||||
prop: "companyId",
|
||||
label: "所属公司",
|
||||
minWidth: 200,
|
||||
enum: companyOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, placeholder: "请选择所属公司" }
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "positionCode",
|
||||
label: "岗位编码",
|
||||
width: 150,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "positionName",
|
||||
label: "岗位名称",
|
||||
minWidth: 180,
|
||||
search: { el: "input" }
|
||||
},
|
||||
{
|
||||
prop: "defaultContractYears",
|
||||
label: "默认合同年限",
|
||||
width: 130,
|
||||
align: "center",
|
||||
render: scope => `${scope.row.defaultContractYears || 3} 年`
|
||||
},
|
||||
{
|
||||
prop: "defaultProbationMonths",
|
||||
label: "默认试用期",
|
||||
width: 130,
|
||||
align: "center",
|
||||
render: scope => `${scope.row.defaultProbationMonths ?? 0} 个月`
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
width: 100,
|
||||
align: "center",
|
||||
enum: [
|
||||
{ label: "启用", value: "enable" },
|
||||
{ label: "停用", value: "disable" }
|
||||
],
|
||||
search: { el: "select" }
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 160
|
||||
}
|
||||
];
|
||||
|
||||
async function loadCompanies() {
|
||||
const { data } = await onboardingTaskApi.listCompanies();
|
||||
companies.value = data;
|
||||
}
|
||||
|
||||
function resetCompany() {
|
||||
Object.assign(companyForm, emptyCompany());
|
||||
}
|
||||
|
||||
function resetPosition() {
|
||||
Object.assign(positionForm, emptyPosition());
|
||||
}
|
||||
|
||||
function openCompanyCreate() {
|
||||
dialogKind.value = "company";
|
||||
editing.value = false;
|
||||
resetCompany();
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
function openPositionCreate() {
|
||||
dialogKind.value = "position";
|
||||
editing.value = false;
|
||||
resetPosition();
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function openCompanyEdit(row: OnboardingCompany) {
|
||||
const { data } = await onboardingTaskApi.companyDetail({ id: row.id });
|
||||
dialogKind.value = "company";
|
||||
editing.value = true;
|
||||
Object.assign(companyForm, data);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function openPositionEdit(row: OnboardingPosition) {
|
||||
const { data } = await onboardingTaskApi.positionDetail({ id: row.id });
|
||||
dialogKind.value = "position";
|
||||
editing.value = true;
|
||||
Object.assign(positionForm, data);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (dialogKind.value === "company") {
|
||||
if (!companyForm.companyCode || !companyForm.companyName || !companyForm.legalName) {
|
||||
return ElMessage.warning("请填写公司编码、公司名称和合同甲方全称");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.saveCompany(companyForm, editing.value);
|
||||
companyTable.value?.refresh();
|
||||
await loadCompanies();
|
||||
} else {
|
||||
if (!positionForm.companyId || !positionForm.positionCode || !positionForm.positionName) {
|
||||
return ElMessage.warning("请填写所属公司、岗位编码和岗位名称");
|
||||
}
|
||||
|
||||
await onboardingTaskApi.savePosition(positionForm, editing.value);
|
||||
positionTable.value?.refresh();
|
||||
}
|
||||
|
||||
dialogVisible.value = false;
|
||||
ElMessage.success("保存成功");
|
||||
}
|
||||
|
||||
async function removeCompany(row: OnboardingCompany) {
|
||||
await ElMessageBox.confirm(`确认删除公司“${row.companyName}”?`, "删除确认", { type: "warning" });
|
||||
await onboardingTaskApi.deleteCompany([row.id]);
|
||||
companyTable.value?.refresh();
|
||||
await loadCompanies();
|
||||
}
|
||||
|
||||
async function removePosition(row: OnboardingPosition) {
|
||||
await ElMessageBox.confirm(`确认删除岗位“${row.positionName}”?`, "删除确认", { type: "warning" });
|
||||
await onboardingTaskApi.deletePosition([row.id]);
|
||||
positionTable.value?.refresh();
|
||||
}
|
||||
|
||||
onMounted(loadCompanies);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.setup-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.setup-tabs {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.setup-tabs :deep(.el-tabs__header) {
|
||||
background: #fff;
|
||||
padding: 10px 16px 0;
|
||||
margin: 0 0 10px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.setup-tabs :deep(.el-tabs__content) {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.setup-tabs :deep(.el-tab-pane) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.table-search) {
|
||||
margin-bottom: 12px !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.table-search .search-isOpen) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.table-search .el-form-item) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.table-search .el-select),
|
||||
.table-wrapper :deep(.table-search .el-input) {
|
||||
max-width: 200px !important;
|
||||
}
|
||||
|
||||
.table-wrapper :deep(.table-main) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user