代码优化

This commit is contained in:
fuenmao
2026-08-27 11:09:33 +08:00
parent c091fc92b6
commit 6d4ad7236f
7 changed files with 629 additions and 87 deletions
-34
View File
@@ -1,34 +0,0 @@
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 });
}
};
+90 -7
View File
@@ -9,10 +9,12 @@ export interface OnboardingTask {
id: number;
taskNo: string;
companyId: number;
positionId: number;
onboardingTemplateId?: number;
companyName?: string;
departmentId?: number;
departmentName?: string;
positionId: number;
positionName?: string;
onboardingTemplateId?: number;
onboardingTemplateName?: string;
taskStatus: string;
rejectReason?: string;
@@ -34,6 +36,11 @@ export interface OnboardingCompanyOption {
companyName: string;
}
export interface OnboardingDepartmentOption {
id: number;
departmentName: string;
}
export interface OnboardingPositionOption {
id: number;
positionName: string;
@@ -50,8 +57,21 @@ export interface OnboardingCompany extends OnboardingCompanyOption {
status: string;
}
export interface OnboardingDepartment extends OnboardingDepartmentOption {
companyId: number;
companyName?: string;
departmentCode: string;
sortCode: number;
remark?: string;
status: string;
createTime?: string;
}
export interface OnboardingPosition extends OnboardingPositionOption {
companyId: number;
companyName?: string;
departmentId?: number;
departmentName?: string;
positionCode: string;
defaultContractYears?: number;
defaultProbationMonths?: number;
@@ -61,6 +81,12 @@ export interface OnboardingPosition extends OnboardingPositionOption {
export interface OnboardingContractTemplate {
id: number;
companyId?: number;
companyName?: string;
departmentId?: number;
departmentName?: string;
positionIds: number[];
positionNames?: string[];
templateCode: string;
templateName: string;
contractType: string;
@@ -118,6 +144,8 @@ export interface OnboardingEmployee {
employeeNo?: string;
employeeName: string;
companyId: number;
departmentId?: number;
departmentName?: string;
positionId: number;
phone: string;
idCardNumber: string;
@@ -172,6 +200,8 @@ export interface OnboardingTemplate {
templateName: string;
companyId: number;
companyName?: string;
departmentId: number;
departmentName?: string;
positionIds: number[];
positionNames?: string[];
contracts: OnboardingTemplateContractConfig[];
@@ -265,6 +295,12 @@ export const onboardingTaskApi = {
>("list-enabled");
},
listDepartments(companyId: number) {
return moduleRequest("/biz/onboarding/department/").get<
OnboardingDepartmentOption[]
>("list-enabled", { companyId });
},
listPositions(companyId: number) {
return moduleRequest("/biz/onboarding/position/").get<
OnboardingPositionOption[]
@@ -301,12 +337,48 @@ export const onboardingTaskApi = {
);
},
departmentPage(params: Record<string, unknown>) {
return moduleRequest("/biz/onboarding/department/").get<
ResPage<OnboardingDepartment>
>("page", params);
},
departmentDetail(params: ReqId) {
return moduleRequest("/biz/onboarding/department/").get<OnboardingDepartment>(
"detail",
params
);
},
saveDepartment(
params: Partial<OnboardingDepartment>,
edit = false
) {
return moduleRequest("/biz/onboarding/department/").post(
edit ? "edit" : "add",
params
);
},
deleteDepartment(ids: number[]) {
return moduleRequest("/biz/onboarding/department/").post(
"delete",
{ ids }
);
},
positionPage(params: Record<string, unknown>) {
return moduleRequest("/biz/onboarding/position/").get<
ResPage<OnboardingPosition>
>("page", params);
},
listPositionsByDepartment(departmentId: number) {
return moduleRequest("/biz/onboarding/position/").get<
OnboardingPosition[]
>("list-by-department", { departmentId });
},
positionDetail(params: ReqId) {
return moduleRequest("/biz/onboarding/position/").get<OnboardingPosition>(
"detail",
@@ -358,13 +430,27 @@ export const onboardingTaskApi = {
);
},
listEnabledOnboardingTemplates(companyId: number, positionId: number) {
listEnabledOnboardingTemplates(
companyId: number,
departmentId: number,
positionId: number
) {
return moduleRequest("/biz/onboarding/template/").get<OnboardingTemplate[]>(
"list-enabled",
{ companyId, positionId }
{ companyId, departmentId, positionId }
);
},
listAvailableContractTemplates(params: {
companyId: number;
departmentId: number;
positionIds: number[];
}) {
return moduleRequest("/biz/onboarding/contract-template/").post<
OnboardingContractTemplate[]
>("list-available", params);
},
templatePage(params: Record<string, unknown>) {
return moduleRequest("/biz/onboarding/contract-template/").get<
ResPage<OnboardingContractTemplate>
@@ -555,9 +641,6 @@ export const onboardingPublicApi = {
saveEmployeeInfo(params: Record<string, unknown>) {
return publicHttp.post("save-employee-info", params);
},
saveMaterial(params: { token: string; materialTypeId: number; fileId: number }) {
return publicHttp.post("save-material", params);
},
confirmContract(params: { token: string; taskContractId: number }) {
return publicHttp.post("confirm-contract", params);
},
+124 -5
View File
@@ -18,6 +18,14 @@
</el-tag>
</template>
<template #positionNames="scope">
<el-space wrap>
<el-tag v-for="name in scope.row.positionNames || []" :key="name" size="small">
{{ name }}
</el-tag>
</el-space>
</template>
<template #status="scope">
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
{{ scope.row.status === "enable" ? "启用" : "停用" }}
@@ -43,11 +51,66 @@
<el-dialog
v-model="templateVisible"
:title="templateEditing ? '编辑合同模板' : '新增合同模板'"
width="580px"
width="720px"
destroy-on-close
append-to-body
>
<el-form :model="templateForm" label-width="110px">
<el-form-item label="所属公司" required>
<el-select
v-model="templateForm.companyId"
filterable
class="w-full"
placeholder="请选择所属公司"
@change="onTemplateCompanyChange"
>
<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="所属部门" required>
<el-select
v-model="templateForm.departmentId"
filterable
class="w-full"
placeholder="请先选择所属公司"
:disabled="!templateForm.companyId"
@change="onTemplateDepartmentChange"
>
<el-option
v-for="department in departments"
:key="department.id"
:label="department.departmentName"
:value="department.id"
/>
</el-select>
</el-form-item>
<el-form-item label="适用岗位" required>
<el-select
v-model="templateForm.positionIds"
multiple
filterable
collapse-tags
collapse-tags-tooltip
class="w-full"
placeholder="请选择该合同可适用的岗位"
:disabled="!templateForm.departmentId"
>
<el-option
v-for="position in positions"
:key="position.id"
:label="position.positionName"
:value="position.id"
/>
</el-select>
</el-form-item>
<el-form-item label="模板编码" required>
<el-input v-model="templateForm.templateCode" placeholder="如:LABOR_CONTRACT" />
</el-form-item>
@@ -411,13 +474,16 @@
</template>
<script setup lang="ts" name="onboardingContract">
import { computed, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Check } from "@element-plus/icons-vue";
import {
onboardingTaskApi,
type OnboardingCompanyOption,
type OnboardingContractTemplate,
type OnboardingContractVersion,
type OnboardingDepartmentOption,
type OnboardingPositionOption,
type OnboardingTemplateVariable
} from "@/api/modules/biz/onboarding";
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
@@ -429,6 +495,9 @@ const drawerVisible = ref(false);
const drawerActiveTab = ref("versions");
const templateVisible = ref(false);
const templateEditing = ref(false);
const companies = ref<OnboardingCompanyOption[]>([]);
const departments = ref<OnboardingDepartmentOption[]>([]);
const positions = ref<OnboardingPositionOption[]>([]);
const versions = ref<OnboardingContractVersion[]>([]);
const versionVisible = ref(false);
const variablesVisible = ref(false);
@@ -455,6 +524,9 @@ const variableCounts = computed(() => {
});
const emptyTemplate = (): Partial<OnboardingContractTemplate> => ({
companyId: undefined,
departmentId: undefined,
positionIds: [],
templateCode: "",
templateName: "",
contractType: "",
@@ -488,8 +560,11 @@ const versionForm = reactive<Partial<OnboardingContractVersion>>(emptyVersion())
const variableForm = reactive<Partial<OnboardingTemplateVariable>>(emptyVariable());
const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
{ prop: "companyName", label: "所属公司", minWidth: 160 },
{ prop: "departmentName", label: "所属部门", minWidth: 140 },
{ prop: "templateCode", label: "模板编码", minWidth: 160, search: { el: "input" } },
{ prop: "templateName", label: "模板名称", minWidth: 200, search: { el: "input" } },
{ prop: "positionNames", label: "适用岗位", minWidth: 200 },
{ prop: "contractType", label: "合同类型", width: 140, search: { el: "input" } },
{
prop: "status",
@@ -508,6 +583,33 @@ const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
function resetTemplate() {
Object.assign(templateForm, emptyTemplate());
departments.value = [];
positions.value = [];
}
async function loadCompanies() {
const { data } = await onboardingTaskApi.listCompanies();
companies.value = data || [];
}
async function onTemplateCompanyChange(companyId?: number) {
templateForm.departmentId = undefined;
templateForm.positionIds = [];
departments.value = [];
positions.value = [];
if (!companyId) return;
const { data } = await onboardingTaskApi.listDepartments(companyId);
departments.value = data || [];
}
async function onTemplateDepartmentChange(departmentId?: number) {
templateForm.positionIds = [];
positions.value = [];
if (!departmentId) return;
const { data } = await onboardingTaskApi.listPositionsByDepartment(departmentId);
positions.value = data || [];
}
function resetVersion() {
@@ -551,12 +653,27 @@ async function openTemplateEdit(row: OnboardingContractTemplate) {
const { data } = await onboardingTaskApi.templateDetail({ id: row.id });
templateEditing.value = true;
Object.assign(templateForm, data);
if (data.companyId) {
const departmentResult = await onboardingTaskApi.listDepartments(data.companyId);
departments.value = departmentResult.data || [];
}
if (data.departmentId) {
const positionResult = await onboardingTaskApi.listPositionsByDepartment(data.departmentId);
positions.value = positionResult.data || [];
}
templateVisible.value = true;
}
async function saveTemplate() {
if (!templateForm.templateCode || !templateForm.templateName || !templateForm.contractType) {
return ElMessage.warning("请填写模板编码、名称和合同类型");
if (
!templateForm.companyId
|| !templateForm.departmentId
|| !templateForm.positionIds?.length
|| !templateForm.templateCode
|| !templateForm.templateName
|| !templateForm.contractType
) {
return ElMessage.warning("请完整填写公司、部门、适用岗位及合同模板基本信息");
}
await onboardingTaskApi.saveTemplate(templateForm, templateEditing.value);
templateVisible.value = false;
@@ -688,6 +805,8 @@ async function saveVariable() {
await loadVariables();
ElMessage.success("变量已确认并保存");
}
onMounted(loadCompanies);
</script>
<style scoped>
@@ -807,4 +926,4 @@ async function saveVariable() {
padding: 2px 6px;
border-radius: 4px;
}
</style>
</style>
+6 -1
View File
@@ -193,6 +193,11 @@ const columns: ColumnProps<OnboardingEmployee>[] = [
minWidth: 160,
search: { el: "input" }
},
{
prop: "departmentName",
label: "所属部门",
minWidth: 140
},
{
prop: "entryDate",
label: "入职日期",
@@ -368,4 +373,4 @@ function download(fileId: number) {
color: #dc2626;
margin-top: 4px;
}
</style>
</style>
@@ -20,6 +20,7 @@
<el-table-column prop="templateCode" label="模板编码" min-width="150" />
<el-table-column prop="templateName" label="模板名称" min-width="180" />
<el-table-column prop="companyName" label="所属公司" min-width="160" />
<el-table-column prop="departmentName" label="所属部门" min-width="150" />
<el-table-column label="适用岗位" min-width="220">
<template #default="scope">
<el-space wrap>
@@ -86,7 +87,7 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="10">
<el-col :xs="24" :sm="8">
<el-form-item label="所属公司" required>
<el-select v-model="form.companyId" filterable class="full-width" placeholder="请选择公司"
@change="onCompanyChange">
@@ -94,10 +95,20 @@
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="14">
<el-col :xs="24" :sm="8">
<el-form-item label="所属部门" required>
<el-select v-model="form.departmentId" filterable class="full-width" placeholder="请先选择公司"
:disabled="!form.companyId" @change="onDepartmentChange">
<el-option v-for="item in departments" :key="item.id" :label="item.departmentName"
:value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
<el-form-item label="适用岗位" required>
<el-select v-model="form.positionIds" multiple filterable collapse-tags collapse-tags-tooltip
class="full-width" :disabled="!form.companyId" placeholder="请选择适用的岗位(可多选)">
class="full-width" :disabled="!form.departmentId" placeholder="请选择适用的岗位(可多选)"
@change="onPositionSelectionChange">
<el-option v-for="item in positions" :key="item.id" :label="item.positionName" :value="item.id" />
</el-select>
</el-form-item>
@@ -118,7 +129,8 @@
<span class="block-title">关联合同文件</span>
<span class="block-subtitle">选择该岗位入职时需要员工确认与签署的合同模板及版本</span>
</div>
<el-button type="primary" plain icon="Plus" size="small" @click="addContract">
<el-button type="primary" plain icon="Plus" size="small" :disabled="!form.positionIds.length"
@click="addContract">
添加合同文件
</el-button>
</div>
@@ -220,6 +232,7 @@ import {
OnboardingCompany,
OnboardingContractTemplate,
OnboardingContractVersion,
OnboardingDepartmentOption,
OnboardingMaterialType,
OnboardingPosition,
OnboardingTemplate,
@@ -233,6 +246,7 @@ const rows = ref<OnboardingTemplate[]>([]);
const total = ref(0);
const companies = ref<OnboardingCompany[]>([]);
const departments = ref<OnboardingDepartmentOption[]>([]);
const positions = ref<OnboardingPosition[]>([]);
const contractTemplates = ref<OnboardingContractTemplate[]>([]);
const materialTypes = ref<OnboardingMaterialType[]>([]);
@@ -250,6 +264,7 @@ const form = reactive<OnboardingTemplate>({
templateCode: "",
templateName: "",
companyId: 0,
departmentId: 0,
description: "",
status: "enable",
positionIds: [],
@@ -259,13 +274,11 @@ const form = reactive<OnboardingTemplate>({
});
async function loadBaseData() {
const [companyRes, templateRes, materialRes] = await Promise.all([
const [companyRes, materialRes] = await Promise.all([
onboardingTaskApi.companyPage({ pageNum: 1, pageSize: 100, status: "enable" }).catch(() => ({ data: { list: [] } })),
onboardingTaskApi.templatePage({ pageNum: 1, pageSize: 100, status: "enable" }).catch(() => ({ data: { list: [] } })),
onboardingTaskApi.listMaterialTypes().catch(() => ({ data: [] }))
]);
companies.value = companyRes.data?.list || [];
contractTemplates.value = templateRes.data?.list || [];
materialTypes.value = materialRes.data || [];
}
@@ -288,13 +301,49 @@ function resetQuery() {
}
async function onCompanyChange(companyId: number) {
form.departmentId = undefined as any;
form.positionIds = [];
form.contracts = [];
departments.value = [];
contractTemplates.value = [];
positions.value = [];
if (!companyId) {
positions.value = [];
return;
}
const res = await onboardingTaskApi.positionPage({ companyId, pageNum: 1, pageSize: 100, status: "enable" });
positions.value = res.data?.list || [];
const res = await onboardingTaskApi.listDepartments(companyId);
departments.value = res.data || [];
}
async function onDepartmentChange(departmentId: number) {
form.positionIds = [];
form.contracts = [];
positions.value = [];
contractTemplates.value = [];
if (!departmentId) return;
const res = await onboardingTaskApi.listPositionsByDepartment(departmentId);
positions.value = res.data || [];
}
async function onPositionSelectionChange() {
await loadAvailableContractTemplates();
}
async function loadAvailableContractTemplates() {
if (!form.companyId || !form.departmentId || !form.positionIds.length) {
contractTemplates.value = [];
form.contracts = [];
return;
}
const res = await onboardingTaskApi.listAvailableContractTemplates({
companyId: form.companyId,
departmentId: form.departmentId,
positionIds: form.positionIds
});
contractTemplates.value = res.data || [];
const availableIds = new Set(contractTemplates.value.map(item => item.id));
form.contracts = form.contracts.filter(item => availableIds.has(item.contractTemplateId));
}
async function onContractTemplateChange(row: any) {
@@ -310,6 +359,15 @@ async function loadContractVersions(templateId: number) {
}
function addContract() {
if (!form.positionIds.length) {
ElMessage.warning("请先选择适用岗位");
return;
}
if (!contractTemplates.value.length) {
ElMessage.warning("当前公司、部门和岗位没有可用的合同模板");
return;
}
form.contracts.push({
contractTemplateId: undefined as any,
contractTemplateVersionId: undefined as any,
@@ -344,6 +402,7 @@ async function openCreate() {
templateCode: "",
templateName: "",
companyId: undefined as any,
departmentId: undefined as any,
description: "",
status: "enable",
positionIds: [],
@@ -351,7 +410,9 @@ async function openCreate() {
formFields: [],
materials: []
});
departments.value = [];
positions.value = [];
contractTemplates.value = [];
visible.value = true;
}
@@ -364,6 +425,7 @@ async function openEdit(row: OnboardingTemplate) {
templateCode: detail.templateCode,
templateName: detail.templateName,
companyId: detail.companyId,
departmentId: detail.departmentId,
description: detail.description,
status: detail.status,
positionIds: detail.positionIds || [],
@@ -383,9 +445,14 @@ async function openEdit(row: OnboardingTemplate) {
});
if (form.companyId) {
const posRes = await onboardingTaskApi.positionPage({ companyId: form.companyId, pageNum: 1, pageSize: 100, status: "enable" });
positions.value = posRes.data?.list || [];
const departmentRes = await onboardingTaskApi.listDepartments(form.companyId);
departments.value = departmentRes.data || [];
}
if (form.departmentId) {
const positionRes = await onboardingTaskApi.listPositionsByDepartment(form.departmentId);
positions.value = positionRes.data || [];
}
await loadAvailableContractTemplates();
for (const contract of form.contracts) {
if (contract.contractTemplateId) {
await loadContractVersions(contract.contractTemplateId);
@@ -395,8 +462,14 @@ async function openEdit(row: OnboardingTemplate) {
}
async function save() {
if (!form.templateCode || !form.templateName || !form.companyId || !form.positionIds.length) {
return ElMessage.warning("请完整填写模板编码、名称、公司及适用岗位");
if (
!form.templateCode
|| !form.templateName
|| !form.companyId
|| !form.departmentId
|| !form.positionIds.length
) {
return ElMessage.warning("请完整填写模板编码、名称、公司、部门及适用岗位");
}
if (!form.contracts.length || form.contracts.some(item => !item.contractTemplateId || !item.contractTemplateVersionId)) {
return ElMessage.warning("请完整选择至少一份合同模板及版本");
+256 -16
View File
@@ -1,4 +1,4 @@
<template>
<template>
<div class="main-box setup-container">
<el-tabs v-model="activeTab" class="setup-tabs">
<!-- 1. 入职公司管理 -->
@@ -36,7 +36,48 @@
</div>
</el-tab-pane>
<!-- 2. 入职岗位管理 -->
<!-- 2. 入职部门管理 -->
<el-tab-pane label="入职部门" name="department">
<div class="table-wrapper">
<ProTable
ref="departmentTable"
title="入职部门列表"
:columns="departmentColumns"
:request-api="onboardingTaskApi.departmentPage"
:search-col="5"
>
<template #tableHeader>
<el-button type="primary" icon="Plus" @click="openDepartmentCreate">
新增入职部门
</el-button>
</template>
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ scope.row.companyName || 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="openDepartmentEdit(scope.row)">
编辑
</el-button>
<el-button link type="danger" icon="Delete" @click="removeDepartment(scope.row)">
删除
</el-button>
</template>
</ProTable>
</div>
</el-tab-pane>
<!-- 3. 入职岗位管理 -->
<el-tab-pane label="入职岗位" name="position">
<div class="table-wrapper">
<ProTable
@@ -54,10 +95,17 @@
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ getCompanyName(scope.row.companyId) }}
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
</template>
<template #departmentName="scope">
<el-tag v-if="scope.row.departmentName" effect="light" type="warning">
{{ scope.row.departmentName }}
</el-tag>
<span v-else class="text-gray-400">-</span>
</template>
<template #status="scope">
<el-tag size="small" :type="scope.row.status === 'enable' ? 'success' : 'info'">
{{ scope.row.status === "enable" ? "启用" : "停用" }}
@@ -86,6 +134,7 @@
destroy-on-close
append-to-body
>
<!-- 1. 公司表单 -->
<el-form v-if="dialogKind === 'company'" :model="companyForm" label-width="140px">
<el-row :gutter="20">
<el-col :span="12">
@@ -142,9 +191,10 @@
</el-form-item>
</el-form>
<el-form v-else :model="positionForm" label-width="140px">
<!-- 2. 部门表单 -->
<el-form v-else-if="dialogKind === 'department'" :model="departmentForm" label-width="140px">
<el-form-item label="所属公司" required>
<el-select v-model="positionForm.companyId" placeholder="请选择所属公司" filterable class="w-full">
<el-select v-model="departmentForm.companyId" placeholder="请选择所属公司" filterable class="w-full">
<el-option
v-for="company in companies"
:key="company.id"
@@ -154,6 +204,77 @@
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="部门编码" required>
<el-input v-model="departmentForm.departmentCode" placeholder="如:DEPT_TECH" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" required>
<el-input v-model="departmentForm.departmentName" 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="departmentForm.sortCode" :min="1" :max="999" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" required>
<el-select v-model="departmentForm.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="departmentForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
</el-form-item>
</el-form>
<!-- 3. 岗位表单 -->
<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"
@change="onPositionCompanyChange"
>
<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="所属部门" required>
<el-select
v-model="positionForm.departmentId"
placeholder="请先选择所属公司"
filterable
clearable
class="w-full"
:disabled="!positionForm.companyId"
>
<el-option
v-for="dept in formDepartments"
:key="dept.id"
:label="dept.departmentName"
:value="dept.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="岗位编码" required>
@@ -208,6 +329,8 @@ import {
onboardingTaskApi,
type OnboardingCompany,
type OnboardingCompanyOption,
type OnboardingDepartment,
type OnboardingDepartmentOption,
type OnboardingPosition
} from "@/api/modules/biz/onboarding";
import type { ColumnProps, ProTableInstance, EnumProps } from "@/components/ProTable/interface";
@@ -215,9 +338,12 @@ import type { ColumnProps, ProTableInstance, EnumProps } from "@/components/ProT
const activeTab = ref("company");
const companyTable = ref<ProTableInstance>();
const departmentTable = ref<ProTableInstance>();
const positionTable = ref<ProTableInstance>();
const companies = ref<OnboardingCompanyOption[]>([]);
const formDepartments = ref<OnboardingDepartmentOption[]>([]);
const companyOptions = computed<EnumProps[]>(() =>
companies.value.map(c => ({
label: c.companyName,
@@ -232,7 +358,7 @@ const getCompanyName = (companyId?: number) => {
};
const dialogVisible = ref(false);
const dialogKind = ref<"company" | "position">("company");
const dialogKind = ref<"company" | "department" | "position">("company");
const editing = ref(false);
const emptyCompany = (): Partial<OnboardingCompany> => ({
@@ -247,8 +373,18 @@ const emptyCompany = (): Partial<OnboardingCompany> => ({
remark: ""
});
const emptyDepartment = (): Partial<OnboardingDepartment> => ({
companyId: undefined,
departmentCode: "",
departmentName: "",
sortCode: 99,
status: "enable",
remark: ""
});
const emptyPosition = (): Partial<OnboardingPosition> => ({
companyId: undefined,
departmentId: undefined,
positionCode: "",
positionName: "",
defaultContractYears: 3,
@@ -258,11 +394,15 @@ const emptyPosition = (): Partial<OnboardingPosition> => ({
});
const companyForm = reactive<Partial<OnboardingCompany>>(emptyCompany());
const departmentForm = reactive<Partial<OnboardingDepartment>>(emptyDepartment());
const positionForm = reactive<Partial<OnboardingPosition>>(emptyPosition());
const dialogTitle = computed(
() => `${editing.value ? "编辑" : "新增"}${dialogKind.value === "company" ? "入职公司" : "入职岗位"}`
);
const dialogTitle = computed(() => {
const prefix = editing.value ? "编辑" : "新增";
if (dialogKind.value === "company") return `${prefix}入职公司`;
if (dialogKind.value === "department") return `${prefix}入职部门`;
return `${prefix}入职岗位`;
});
const companyColumns: ColumnProps<OnboardingCompany>[] = [
{
@@ -316,7 +456,7 @@ const companyColumns: ColumnProps<OnboardingCompany>[] = [
}
];
const positionColumns: ColumnProps<OnboardingPosition>[] = [
const departmentColumns: ColumnProps<OnboardingDepartment>[] = [
{
prop: "companyId",
label: "所属公司",
@@ -327,6 +467,59 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
props: { filterable: true, placeholder: "请选择所属公司" }
}
},
{
prop: "departmentCode",
label: "部门编码",
width: 160,
search: { el: "input" }
},
{
prop: "departmentName",
label: "部门名称",
minWidth: 180,
search: { el: "input" }
},
{
prop: "sortCode",
label: "显示排序",
width: 110,
align: "center"
},
{
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: 180,
enum: companyOptions,
search: {
el: "select",
props: { filterable: true, placeholder: "请选择所属公司" }
}
},
{
prop: "departmentName",
label: "所属部门",
minWidth: 180
},
{
prop: "positionCode",
label: "岗位编码",
@@ -374,13 +567,31 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
async function loadCompanies() {
const { data } = await onboardingTaskApi.listCompanies();
companies.value = data;
companies.value = data || [];
}
async function loadDepartmentsForCompany(companyId?: number) {
if (!companyId) {
formDepartments.value = [];
return;
}
const { data } = await onboardingTaskApi.listDepartments(companyId);
formDepartments.value = data || [];
}
async function onPositionCompanyChange(companyId?: number) {
positionForm.departmentId = undefined;
await loadDepartmentsForCompany(companyId);
}
function resetCompany() {
Object.assign(companyForm, emptyCompany());
}
function resetDepartment() {
Object.assign(departmentForm, emptyDepartment());
}
function resetPosition() {
Object.assign(positionForm, emptyPosition());
}
@@ -392,10 +603,18 @@ function openCompanyCreate() {
dialogVisible.value = true;
}
function openDepartmentCreate() {
dialogKind.value = "department";
editing.value = false;
resetDepartment();
dialogVisible.value = true;
}
function openPositionCreate() {
dialogKind.value = "position";
editing.value = false;
resetPosition();
formDepartments.value = [];
dialogVisible.value = true;
}
@@ -407,11 +626,22 @@ async function openCompanyEdit(row: OnboardingCompany) {
dialogVisible.value = true;
}
async function openDepartmentEdit(row: OnboardingDepartment) {
const { data } = await onboardingTaskApi.departmentDetail({ id: row.id });
dialogKind.value = "department";
editing.value = true;
Object.assign(departmentForm, 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);
if (data?.companyId) {
await loadDepartmentsForCompany(data.companyId);
}
dialogVisible.value = true;
}
@@ -420,15 +650,19 @@ async function save() {
if (!companyForm.companyCode || !companyForm.companyName || !companyForm.legalName) {
return ElMessage.warning("请填写公司编码、公司名称和合同甲方全称");
}
await onboardingTaskApi.saveCompany(companyForm, editing.value);
companyTable.value?.refresh();
await loadCompanies();
} else if (dialogKind.value === "department") {
if (!departmentForm.companyId || !departmentForm.departmentCode || !departmentForm.departmentName) {
return ElMessage.warning("请填写所属公司、部门编码和部门名称");
}
await onboardingTaskApi.saveDepartment(departmentForm, editing.value);
departmentTable.value?.refresh();
} else {
if (!positionForm.companyId || !positionForm.positionCode || !positionForm.positionName) {
return ElMessage.warning("请填写所属公司、岗位编码和岗位名称");
if (!positionForm.companyId || !positionForm.departmentId || !positionForm.positionCode || !positionForm.positionName) {
return ElMessage.warning("请填写所属公司、所属部门、岗位编码和岗位名称");
}
await onboardingTaskApi.savePosition(positionForm, editing.value);
positionTable.value?.refresh();
}
@@ -444,6 +678,12 @@ async function removeCompany(row: OnboardingCompany) {
await loadCompanies();
}
async function removeDepartment(row: OnboardingDepartment) {
await ElMessageBox.confirm(`确认删除部门“${row.departmentName}”?`, "删除确认", { type: "warning" });
await onboardingTaskApi.deleteDepartment([row.id]);
departmentTable.value?.refresh();
}
async function removePosition(row: OnboardingPosition) {
await ElMessageBox.confirm(`确认删除岗位“${row.positionName}”?`, "删除确认", { type: "warning" });
await onboardingTaskApi.deletePosition([row.id]);
@@ -522,4 +762,4 @@ onMounted(loadCompanies);
.w-full {
width: 100%;
}
</style>
</style>
+66 -10
View File
@@ -149,8 +149,8 @@
<span class="title-tip">请选择新员工入职的公司主体、岗位及适用的入职模板</span>
</div>
<el-row :gutter="20">
<el-col :xs="24" :sm="8">
<el-row :gutter="16">
<el-col :xs="24" :sm="6">
<el-form-item label="所属公司" required>
<el-select
v-model="form.companyId"
@@ -164,14 +164,29 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
<el-col :xs="24" :sm="6">
<el-form-item label="所属部门" required>
<el-select
v-model="form.departmentId"
filterable
placeholder="请选择所属部门"
class="full-width"
:disabled="!form.companyId"
@change="onDepartmentChange"
>
<el-option v-for="item in departments" :key="item.id" :label="item.departmentName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6">
<el-form-item label="入职岗位" required>
<el-select
v-model="form.positionId"
filterable
placeholder="请选择岗位"
class="full-width"
:disabled="!form.companyId"
:disabled="!form.departmentId"
@change="onPositionChange"
>
<el-option v-for="item in positions" :key="item.id" :label="item.positionName" :value="item.id" />
@@ -179,7 +194,7 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
<el-col :xs="24" :sm="6">
<el-form-item label="入职模板" required>
<el-select
v-model="form.onboardingTemplateId"
@@ -335,6 +350,7 @@
</div>
<div class="banner-meta-row">
<span>所属公司:<strong>{{ detail.task.companyName || '-' }}</strong></span>
<span>所属部门:<strong>{{ detail.task.departmentName || '-' }}</strong></span>
<span>入职岗位:<strong>{{ detail.task.positionName || '-' }}</strong></span>
<span>入职模板:<strong>{{ detail.task.onboardingTemplateName || '-' }}</strong></span>
</div>
@@ -878,6 +894,7 @@ import {
import {
onboardingTaskApi,
type OnboardingCompanyOption,
type OnboardingDepartmentOption,
type OnboardingPositionOption,
type OnboardingTemplate,
type OnboardingContractTemplate,
@@ -889,6 +906,7 @@ import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interf
const proTable = ref<ProTableInstance>();
const companies = ref<OnboardingCompanyOption[]>([]);
const departments = ref<OnboardingDepartmentOption[]>([]);
const positions = ref<OnboardingPositionOption[]>([]);
const onboardingTemplates = ref<OnboardingTemplate[]>([]);
const contractTemplates = ref<OnboardingContractTemplate[]>([]);
@@ -977,6 +995,7 @@ const statusCounts = reactive({
const form = reactive({
companyId: undefined as number | undefined,
departmentId: undefined as number | undefined,
positionId: undefined as number | undefined,
onboardingTemplateId: undefined as number | undefined,
contractVariables: [] as Array<{
@@ -998,6 +1017,21 @@ const columns: ColumnProps<OnboardingTask>[] = [
minWidth: 200,
search: { el: "input" }
},
{
prop: "companyName",
label: "所属公司",
minWidth: 160
},
{
prop: "departmentName",
label: "所属部门",
minWidth: 140
},
{
prop: "positionName",
label: "入职岗位",
minWidth: 140
},
{
prop: "taskStatus",
label: "当前状态",
@@ -1230,11 +1264,26 @@ async function loadCompanies() {
}
async function onCompanyChange(companyId: number) {
form.departmentId = undefined;
form.positionId = undefined;
form.onboardingTemplateId = undefined;
form.contractVariables = [];
positions.value = [];
onboardingTemplates.value = [];
const { data } = await onboardingTaskApi.listPositions(companyId);
departments.value = [];
if (!companyId) return;
const { data } = await onboardingTaskApi.listDepartments(companyId);
departments.value = data || [];
}
async function onDepartmentChange(departmentId: number) {
form.positionId = undefined;
form.onboardingTemplateId = undefined;
form.contractVariables = [];
positions.value = [];
onboardingTemplates.value = [];
if (!departmentId) return;
const { data } = await onboardingTaskApi.listPositionsByDepartment(departmentId);
positions.value = data || [];
}
@@ -1242,8 +1291,12 @@ async function onPositionChange(positionId: number) {
form.onboardingTemplateId = undefined;
form.contractVariables = [];
onboardingTemplates.value = [];
if (!form.companyId || !positionId) return;
const { data } = await onboardingTaskApi.listEnabledOnboardingTemplates(form.companyId, positionId);
if (!form.companyId || !form.departmentId || !positionId) return;
const { data } = await onboardingTaskApi.listEnabledOnboardingTemplates(
form.companyId,
form.departmentId,
positionId
);
onboardingTemplates.value = data || [];
if (onboardingTemplates.value.length === 1) {
form.onboardingTemplateId = onboardingTemplates.value[0].id;
@@ -1282,19 +1335,21 @@ async function onOnboardingTemplateChange(templateId: number) {
function openCreate() {
Object.assign(form, {
companyId: undefined,
departmentId: undefined,
positionId: undefined,
onboardingTemplateId: undefined,
contractVariables: [],
remark: ""
});
departments.value = [];
positions.value = [];
onboardingTemplates.value = [];
createVisible.value = true;
}
async function createTask() {
if (!form.companyId || !form.positionId || !form.onboardingTemplateId) {
return ElMessage.warning("请选择所属公司、入职岗位和入职模板");
if (!form.companyId || !form.departmentId || !form.positionId || !form.onboardingTemplateId) {
return ElMessage.warning("请选择所属公司、所属部门、入职岗位和入职模板");
}
const missingVariable = form.contractVariables.find(
item => item.required && isBlankContractVariableValue(item.variableValue)
@@ -1318,6 +1373,7 @@ async function createTask() {
const linkUrl = `${location.origin}/#/onboarding/fill?token=${result.linkToken}`;
const selectedCompany = companies.value.find(c => c.id === form.companyId)?.companyName || "";
const selectedDepartment = departments.value.find(d => d.id === form.departmentId)?.departmentName || "";
const selectedPosition = positions.value.find(p => p.id === form.positionId)?.positionName || "";
createdTaskInfo.value = {