查询方式优化
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingDepartmentOption,
|
||||
type OnboardingPositionOption
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
|
||||
type ScopeSelection = { companyId?: number; departmentId?: number; positionId?: number };
|
||||
|
||||
/** 公司、部门、岗位只按当前选择加载;过期响应不能覆盖新选择。 */
|
||||
export function useOnboardingScopeOptions(
|
||||
getSelection: () => ScopeSelection | undefined,
|
||||
syncOptions?: () => void
|
||||
) {
|
||||
const departments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const positions = ref<OnboardingPositionOption[]>([]);
|
||||
let companyRequest = 0;
|
||||
let departmentRequest = 0;
|
||||
|
||||
function reset() {
|
||||
companyRequest++;
|
||||
departmentRequest++;
|
||||
departments.value = [];
|
||||
positions.value = [];
|
||||
const selection = getSelection();
|
||||
if (selection) {
|
||||
selection.departmentId = undefined;
|
||||
selection.positionId = undefined;
|
||||
}
|
||||
syncOptions?.();
|
||||
}
|
||||
|
||||
async function onCompanyChange(companyId?: number) {
|
||||
reset();
|
||||
if (!companyId) return;
|
||||
const request = companyRequest;
|
||||
const { data } = await onboardingTaskApi.listDepartments(companyId).catch(() => ({ data: [] }));
|
||||
if (request !== companyRequest || getSelection()?.companyId !== companyId) return;
|
||||
departments.value = data || [];
|
||||
syncOptions?.();
|
||||
}
|
||||
|
||||
async function onDepartmentChange(departmentId?: number) {
|
||||
const request = ++departmentRequest;
|
||||
positions.value = [];
|
||||
const selection = getSelection();
|
||||
if (selection) selection.positionId = undefined;
|
||||
syncOptions?.();
|
||||
const companyId = selection?.companyId;
|
||||
if (!companyId || !departmentId) return;
|
||||
const { data } = await onboardingTaskApi.listPositionsByDepartment(departmentId).catch(() => ({ data: [] }));
|
||||
const current = getSelection();
|
||||
if (request !== departmentRequest || current?.companyId !== companyId || current?.departmentId !== departmentId) return;
|
||||
positions.value = data || [];
|
||||
syncOptions?.();
|
||||
}
|
||||
|
||||
return { departments, positions, onCompanyChange, onDepartmentChange, reset };
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
title="签署材料模板列表"
|
||||
:columns="templateColumns"
|
||||
:request-api="onboardingTaskApi.templatePage"
|
||||
@reset="resetSearchScope"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="'onboardingContractAdd'" type="primary" icon="Plus" @click="openTemplateCreate">
|
||||
@@ -584,6 +585,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingContract">
|
||||
import { useOnboardingScopeOptions } from "@/hooks/useOnboardingScopeOptions";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import dayjs from "dayjs";
|
||||
@@ -615,10 +617,19 @@ const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const departments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const positions = ref<OnboardingPositionOption[]>([]);
|
||||
const searchCompanies = ref<OnboardingCompanyOption[]>([]);
|
||||
const searchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const searchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const allSearchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const allSearchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const {
|
||||
departments: searchDepartments,
|
||||
positions: searchPositions,
|
||||
onCompanyChange: onSearchCompanyChange,
|
||||
onDepartmentChange: onSearchDepartmentChange,
|
||||
reset: resetSearchScope
|
||||
} = useOnboardingScopeOptions(
|
||||
() => templateTable.value?.searchParam,
|
||||
() => {
|
||||
templateTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
templateTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
}
|
||||
);
|
||||
|
||||
const companySearchOptions = computed(() =>
|
||||
searchCompanies.value.map(c => ({
|
||||
@@ -641,58 +652,6 @@ const positionSearchOptions = computed(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
async function onSearchCompanyChange(companyId?: number) {
|
||||
if (companyId) {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(companyId).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(companyId).catch(() => ({ data: [] }))
|
||||
]);
|
||||
searchDepartments.value = depRes.data || [];
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchDepartments.value = allSearchDepartments.value;
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
|
||||
templateTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
templateTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (templateTable.value?.searchParam) {
|
||||
const currentDept = templateTable.value.searchParam.departmentId;
|
||||
if (currentDept && !searchDepartments.value.some(d => d.id === currentDept)) {
|
||||
templateTable.value.searchParam.departmentId = undefined;
|
||||
}
|
||||
const currentPos = templateTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
templateTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onSearchDepartmentChange(departmentId?: number) {
|
||||
if (departmentId) {
|
||||
const posRes = await onboardingTaskApi.listPositionsByDepartment(departmentId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
const selectedCompanyId = templateTable.value?.searchParam?.companyId;
|
||||
if (selectedCompanyId) {
|
||||
const posRes = await onboardingTaskApi.listPositions(selectedCompanyId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
}
|
||||
|
||||
templateTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (templateTable.value?.searchParam) {
|
||||
const currentPos = templateTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
templateTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const versions = ref<OnboardingContractVersion[]>([]);
|
||||
type VersionSealField = "companySealEnabled" | "acrossPageSealEnabled";
|
||||
const sealUpdating = reactive<Record<number, VersionSealField | undefined>>({});
|
||||
@@ -797,7 +756,12 @@ const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
placeholder: "请选择所属部门",
|
||||
get placeholder() {
|
||||
return templateTable.value?.searchParam?.companyId ? "请选择所属部门" : "请先选择所属公司";
|
||||
},
|
||||
get disabled() {
|
||||
return !templateTable.value?.searchParam?.companyId;
|
||||
},
|
||||
onChange: (val?: number) => onSearchDepartmentChange(val)
|
||||
}
|
||||
}
|
||||
@@ -809,7 +773,16 @@ const templateColumns: ColumnProps<OnboardingContractTemplate>[] = [
|
||||
enum: positionSearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择适用岗位" }
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
get placeholder() {
|
||||
return templateTable.value?.searchParam?.departmentId ? "请选择适用岗位" : "请先选择所属部门";
|
||||
},
|
||||
get disabled() {
|
||||
return !templateTable.value?.searchParam?.departmentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ prop: "templateCode", label: "模板编码", minWidth: 160, search: { el: "input" } },
|
||||
@@ -1138,43 +1111,9 @@ async function saveVariable() {
|
||||
}
|
||||
|
||||
async function loadSearchOptions() {
|
||||
try {
|
||||
await loadCompanies();
|
||||
searchCompanies.value = companies.value;
|
||||
|
||||
const deptList: OnboardingDepartmentOption[] = [];
|
||||
const posList: OnboardingPositionOption[] = [];
|
||||
|
||||
if (companies.value && companies.value.length > 0) {
|
||||
const promises = companies.value.map(async comp => {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(comp.id).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(comp.id).catch(() => ({ data: [] }))
|
||||
]);
|
||||
if (depRes.data) deptList.push(...depRes.data);
|
||||
if (posRes.data) posList.push(...posRes.data);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
const uniqueDeptMap = new Map<number, OnboardingDepartmentOption>();
|
||||
deptList.forEach(d => uniqueDeptMap.set(d.id, d));
|
||||
const finalDepts = Array.from(uniqueDeptMap.values());
|
||||
allSearchDepartments.value = finalDepts;
|
||||
searchDepartments.value = finalDepts;
|
||||
|
||||
const uniquePosMap = new Map<number, OnboardingPositionOption>();
|
||||
posList.forEach(p => uniquePosMap.set(p.id, p));
|
||||
const finalPositions = Array.from(uniquePosMap.values());
|
||||
allSearchPositions.value = finalPositions;
|
||||
searchPositions.value = finalPositions;
|
||||
|
||||
templateTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
templateTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
templateTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
} catch (e) {
|
||||
console.error("加载签署材料搜索选项失败", e);
|
||||
}
|
||||
await loadCompanies();
|
||||
searchCompanies.value = companies.value;
|
||||
templateTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
}
|
||||
|
||||
onMounted(loadSearchOptions);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="员工档案" :columns="columns" :request-api="getTableData">
|
||||
<ProTable ref="proTable" title="员工档案" :columns="columns" :request-api="getTableData" @reset="resetSearchScope">
|
||||
<template #tableHeader>
|
||||
<div class="employee-table-toolbar">
|
||||
<el-button v-auth="'onboardingEmployeeExport'" type="primary" plain icon="Download" :loading="exporting" @click="exportEmployees">
|
||||
@@ -576,6 +576,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingEmployee">
|
||||
import { useOnboardingScopeOptions } from "@/hooks/useOnboardingScopeOptions";
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import {
|
||||
Briefcase,
|
||||
@@ -596,9 +597,7 @@ import type { UploadFile, UploadRequestOptions, UploadUserFile } from "element-p
|
||||
import {
|
||||
onboardingTaskApi,
|
||||
type OnboardingEmployee,
|
||||
type OnboardingCompanyOption,
|
||||
type OnboardingDepartmentOption,
|
||||
type OnboardingPositionOption
|
||||
type OnboardingCompanyOption
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { getMessageBoxPromptResult, isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||
@@ -806,10 +805,19 @@ const imageMimeTypes: Record<string, string> = {
|
||||
};
|
||||
|
||||
const searchCompanies = ref<OnboardingCompanyOption[]>([]);
|
||||
const searchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const searchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const allSearchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const allSearchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const {
|
||||
departments: searchDepartments,
|
||||
positions: searchPositions,
|
||||
onCompanyChange: onSearchCompanyChange,
|
||||
onDepartmentChange: onSearchDepartmentChange,
|
||||
reset: resetSearchScope
|
||||
} = useOnboardingScopeOptions(
|
||||
() => proTable.value?.searchParam,
|
||||
() => {
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
}
|
||||
);
|
||||
|
||||
const companySearchOptions = computed(() =>
|
||||
searchCompanies.value.map(c => ({
|
||||
@@ -832,58 +840,6 @@ const positionSearchOptions = computed(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
async function onSearchCompanyChange(companyId?: number) {
|
||||
if (companyId) {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(companyId).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(companyId).catch(() => ({ data: [] }))
|
||||
]);
|
||||
searchDepartments.value = depRes.data || [];
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchDepartments.value = allSearchDepartments.value;
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (proTable.value?.searchParam) {
|
||||
const currentDept = proTable.value.searchParam.departmentId;
|
||||
if (currentDept && !searchDepartments.value.some(d => d.id === currentDept)) {
|
||||
proTable.value.searchParam.departmentId = undefined;
|
||||
}
|
||||
const currentPos = proTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
proTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onSearchDepartmentChange(departmentId?: number) {
|
||||
if (departmentId) {
|
||||
const posRes = await onboardingTaskApi.listPositionsByDepartment(departmentId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
const selectedCompanyId = proTable.value?.searchParam?.companyId;
|
||||
if (selectedCompanyId) {
|
||||
const posRes = await onboardingTaskApi.listPositions(selectedCompanyId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
}
|
||||
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (proTable.value?.searchParam) {
|
||||
const currentPos = proTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
proTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonList(value?: string) {
|
||||
if (!value) return [];
|
||||
|
||||
@@ -1083,7 +1039,12 @@ const columns: ColumnProps<OnboardingEmployee>[] = [
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
placeholder: "请选择所属部门",
|
||||
get placeholder() {
|
||||
return proTable.value?.searchParam?.companyId ? "请选择所属部门" : "请先选择所属公司";
|
||||
},
|
||||
get disabled() {
|
||||
return !proTable.value?.searchParam?.companyId;
|
||||
},
|
||||
onChange: (val?: number) => onSearchDepartmentChange(val)
|
||||
}
|
||||
}
|
||||
@@ -1095,7 +1056,16 @@ const columns: ColumnProps<OnboardingEmployee>[] = [
|
||||
enum: positionSearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择入职岗位" }
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
get placeholder() {
|
||||
return proTable.value?.searchParam?.departmentId ? "请选择入职岗位" : "请先选择所属部门";
|
||||
},
|
||||
get disabled() {
|
||||
return !proTable.value?.searchParam?.departmentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1713,43 +1683,9 @@ async function download(fileId: number, fallbackFileName?: string) {
|
||||
}
|
||||
|
||||
async function loadSearchOptions() {
|
||||
try {
|
||||
const { data } = await onboardingTaskApi.listCompanies().catch(() => ({ data: [] }));
|
||||
searchCompanies.value = data || [];
|
||||
|
||||
const deptList: OnboardingDepartmentOption[] = [];
|
||||
const posList: OnboardingPositionOption[] = [];
|
||||
|
||||
if (data && data.length > 0) {
|
||||
const promises = data.map(async (comp: any) => {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(comp.id).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(comp.id).catch(() => ({ data: [] }))
|
||||
]);
|
||||
if (depRes.data) deptList.push(...depRes.data);
|
||||
if (posRes.data) posList.push(...posRes.data);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
const uniqueDeptMap = new Map<number, OnboardingDepartmentOption>();
|
||||
deptList.forEach(d => uniqueDeptMap.set(d.id, d));
|
||||
const finalDepts = Array.from(uniqueDeptMap.values());
|
||||
allSearchDepartments.value = finalDepts;
|
||||
searchDepartments.value = finalDepts;
|
||||
|
||||
const uniquePosMap = new Map<number, OnboardingPositionOption>();
|
||||
posList.forEach(p => uniquePosMap.set(p.id, p));
|
||||
const finalPositions = Array.from(uniquePosMap.values());
|
||||
allSearchPositions.value = finalPositions;
|
||||
searchPositions.value = finalPositions;
|
||||
|
||||
proTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
} catch (e) {
|
||||
console.error("加载员工档案搜索选项失败", e);
|
||||
}
|
||||
const { data } = await onboardingTaskApi.listCompanies();
|
||||
searchCompanies.value = data || [];
|
||||
proTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
}
|
||||
|
||||
onMounted(loadSearchOptions);
|
||||
@@ -2333,4 +2269,4 @@ onBeforeUnmount(clearMaterialPreviews);
|
||||
color: #fff;
|
||||
background: var(--metric-color);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
placeholder="全部公司"
|
||||
class="company-filter"
|
||||
@change="onQueryCompanyChange"
|
||||
@clear="onQueryCompanyChange"
|
||||
>
|
||||
<el-option v-for="item in companies" :key="item.id" :label="item.companyName" :value="item.id" />
|
||||
</el-select>
|
||||
@@ -24,10 +23,10 @@
|
||||
v-model="query.departmentId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部部门"
|
||||
:placeholder="query.companyId ? '全部部门' : '请先选择所属公司'"
|
||||
:disabled="!query.companyId"
|
||||
class="department-filter"
|
||||
@change="onQueryDepartmentChange"
|
||||
@clear="onQueryDepartmentChange"
|
||||
>
|
||||
<el-option v-for="item in queryDepartments" :key="item.id" :label="item.departmentName" :value="item.id" />
|
||||
</el-select>
|
||||
@@ -35,10 +34,10 @@
|
||||
v-model="query.positionId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部岗位"
|
||||
:placeholder="query.departmentId ? '全部岗位' : '请先选择所属部门'"
|
||||
:disabled="!query.departmentId"
|
||||
class="position-filter"
|
||||
@change="loadTemplates"
|
||||
@clear="loadTemplates"
|
||||
>
|
||||
<el-option v-for="item in queryPositions" :key="item.id" :label="item.positionName" :value="item.id" />
|
||||
</el-select>
|
||||
@@ -48,7 +47,6 @@
|
||||
placeholder="模板状态"
|
||||
class="status-filter"
|
||||
@change="loadTemplates"
|
||||
@clear="loadTemplates"
|
||||
>
|
||||
<el-option label="全部状态" value="" />
|
||||
<el-option label="启用" value="enable" />
|
||||
@@ -304,16 +302,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useOnboardingScopeOptions } from "@/hooks/useOnboardingScopeOptions";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
OnboardingCompany,
|
||||
OnboardingCompanyOption,
|
||||
OnboardingContractTemplate,
|
||||
OnboardingContractVersion,
|
||||
OnboardingDepartmentOption,
|
||||
OnboardingMaterialType,
|
||||
OnboardingPosition,
|
||||
OnboardingPositionOption,
|
||||
OnboardingTemplate,
|
||||
onboardingTaskApi
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
@@ -328,9 +326,8 @@ const visible = ref(false);
|
||||
const rows = ref<OnboardingTemplate[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const companies = ref<OnboardingCompany[]>([]);
|
||||
const queryDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const queryPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const props = defineProps<{ loadMaterials: () => Promise<OnboardingMaterialType[]> }>();
|
||||
const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const departments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const positions = ref<OnboardingPosition[]>([]);
|
||||
const contractTemplates = ref<OnboardingContractTemplate[]>([]);
|
||||
@@ -364,96 +361,54 @@ const form = reactive<OnboardingTemplate>({
|
||||
materials: []
|
||||
});
|
||||
|
||||
async function loadAllQueryOptions() {
|
||||
if (!companies.value || companies.value.length === 0) return;
|
||||
const deptList: OnboardingDepartmentOption[] = [];
|
||||
const posList: OnboardingPositionOption[] = [];
|
||||
const {
|
||||
departments: queryDepartments,
|
||||
positions: queryPositions,
|
||||
onCompanyChange: changeQueryCompany,
|
||||
onDepartmentChange: changeQueryDepartment,
|
||||
reset: resetQueryScope
|
||||
} = useOnboardingScopeOptions(() => query);
|
||||
|
||||
const promises = companies.value.map(async comp => {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(comp.id).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(comp.id).catch(() => ({ data: [] }))
|
||||
]);
|
||||
if (depRes.data) deptList.push(...depRes.data);
|
||||
if (posRes.data) posList.push(...posRes.data);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
const uniqueDeptMap = new Map<number, OnboardingDepartmentOption>();
|
||||
deptList.forEach(d => uniqueDeptMap.set(d.id, d));
|
||||
queryDepartments.value = Array.from(uniqueDeptMap.values());
|
||||
|
||||
const uniquePosMap = new Map<number, OnboardingPositionOption>();
|
||||
posList.forEach(p => uniquePosMap.set(p.id, p));
|
||||
queryPositions.value = Array.from(uniquePosMap.values());
|
||||
let companiesLoaded = false;
|
||||
let companyRequest: Promise<void> | undefined;
|
||||
async function loadCompanies() {
|
||||
if (companiesLoaded) return;
|
||||
if (!companyRequest) {
|
||||
companyRequest = onboardingTaskApi.listCompanies().then(({ data }) => {
|
||||
companies.value = data || [];
|
||||
companiesLoaded = true;
|
||||
}).finally(() => { companyRequest = undefined; });
|
||||
}
|
||||
await companyRequest;
|
||||
}
|
||||
|
||||
async function loadBaseData() {
|
||||
const [companyRes, materialRes] = await Promise.all([
|
||||
onboardingTaskApi.companyPage({ pageNum: 1, pageSize: 100, status: "enable" }).catch(() => ({ data: { list: [] } })),
|
||||
onboardingTaskApi.listMaterialTypes().catch(() => ({ data: [] }))
|
||||
]);
|
||||
companies.value = companyRes.data?.list || [];
|
||||
materialTypes.value = materialRes.data || [];
|
||||
await loadAllQueryOptions();
|
||||
const [, materials] = await Promise.all([loadCompanies(), props.loadMaterials()]);
|
||||
materialTypes.value = materials;
|
||||
}
|
||||
|
||||
let templateRequest = 0;
|
||||
async function loadTemplates() {
|
||||
const request = ++templateRequest;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await onboardingTaskApi.onboardingTemplatePage(query);
|
||||
const res = await onboardingTaskApi.onboardingTemplatePage({ ...query });
|
||||
if (request !== templateRequest) return;
|
||||
rows.value = res.data?.list || [];
|
||||
total.value = res.data?.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
if (request === templateRequest) loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onQueryCompanyChange(companyId?: number) {
|
||||
query.departmentId = undefined;
|
||||
query.positionId = undefined;
|
||||
if (!companyId) {
|
||||
await loadAllQueryOptions();
|
||||
} else {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(companyId).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(companyId).catch(() => ({ data: [] }))
|
||||
]);
|
||||
queryDepartments.value = depRes.data || [];
|
||||
queryPositions.value = (posRes.data || []).map(p => ({
|
||||
id: p.id,
|
||||
positionName: p.positionName,
|
||||
companyId: companyId,
|
||||
departmentId: p.departmentId
|
||||
} as any));
|
||||
}
|
||||
loadTemplates();
|
||||
query.pageNum = 1;
|
||||
await Promise.all([changeQueryCompany(companyId), loadTemplates()]);
|
||||
}
|
||||
|
||||
async function onQueryDepartmentChange(departmentId?: number) {
|
||||
query.positionId = undefined;
|
||||
if (!departmentId) {
|
||||
if (query.companyId) {
|
||||
const posRes = await onboardingTaskApi.listPositions(query.companyId).catch(() => ({ data: [] }));
|
||||
queryPositions.value = (posRes.data || []).map(p => ({
|
||||
id: p.id,
|
||||
positionName: p.positionName,
|
||||
companyId: query.companyId!,
|
||||
departmentId: p.departmentId
|
||||
} as any));
|
||||
} else {
|
||||
await loadAllQueryOptions();
|
||||
}
|
||||
} else {
|
||||
const posRes = await onboardingTaskApi.listPositionsByDepartment(departmentId).catch(() => ({ data: [] }));
|
||||
queryPositions.value = (posRes.data || []).map(p => ({
|
||||
id: p.id,
|
||||
positionName: p.positionName,
|
||||
companyId: p.companyId,
|
||||
departmentId: departmentId
|
||||
} as any));
|
||||
}
|
||||
loadTemplates();
|
||||
query.pageNum = 1;
|
||||
await Promise.all([changeQueryDepartment(departmentId), loadTemplates()]);
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
@@ -463,7 +418,7 @@ function resetQuery() {
|
||||
query.positionId = undefined;
|
||||
query.status = "enable";
|
||||
query.pageNum = 1;
|
||||
loadAllQueryOptions();
|
||||
resetQueryScope();
|
||||
loadTemplates();
|
||||
}
|
||||
|
||||
@@ -744,7 +699,7 @@ async function removeTemplate(row: OnboardingTemplate) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadBaseData(), loadTemplates()]);
|
||||
await Promise.all([loadCompanies(), loadTemplates()]);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="main-box config-container">
|
||||
<el-tabs v-model="activeTab" class="config-tabs">
|
||||
<el-tab-pane label="合同变量字典" name="variables">
|
||||
<el-tab-pane lazy label="合同变量字典" name="variables">
|
||||
<div class="tab-card-wrapper">
|
||||
<VariableDefinitionPanel />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="上传资料类型" name="materials">
|
||||
<el-tab-pane lazy label="上传资料类型" name="materials">
|
||||
<div class="tab-card-wrapper material-tab-wrapper">
|
||||
<div class="toolbar">
|
||||
<el-button v-auth="'onboardingEntryConfigAdd'" type="primary" icon="Plus" @click="openMaterialCreate">
|
||||
@@ -64,9 +64,9 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="入职模板" name="templates">
|
||||
<el-tab-pane lazy label="入职模板" name="templates">
|
||||
<div class="tab-card-wrapper">
|
||||
<OnboardingTemplatePanel />
|
||||
<OnboardingTemplatePanel :load-materials="loadMaterials" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@@ -113,7 +113,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingEntryConfig">
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
OnboardingMaterialType,
|
||||
@@ -140,9 +140,15 @@ const materialForm = reactive<Partial<OnboardingMaterialType>>({
|
||||
status: "enable"
|
||||
});
|
||||
|
||||
let materialRequest: Promise<OnboardingMaterialType[]> | undefined;
|
||||
async function loadMaterials() {
|
||||
const res = await onboardingTaskApi.listMaterialTypes();
|
||||
materialTypes.value = res.data || [];
|
||||
if (!materialRequest) {
|
||||
materialRequest = onboardingTaskApi.listMaterialTypes().then(({ data }) => {
|
||||
materialTypes.value = data || [];
|
||||
return materialTypes.value;
|
||||
}).finally(() => { materialRequest = undefined; });
|
||||
}
|
||||
return materialRequest;
|
||||
}
|
||||
|
||||
function openMaterialCreate() {
|
||||
@@ -184,8 +190,8 @@ async function removeMaterial(row: OnboardingMaterialType) {
|
||||
await loadMaterials();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadMaterials();
|
||||
watch(activeTab, tab => {
|
||||
if (tab === "materials") void loadMaterials();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="main-box setup-container">
|
||||
<el-tabs v-model="activeTab" class="setup-tabs">
|
||||
<!-- 1. 入职公司管理 -->
|
||||
<el-tab-pane label="入职公司" name="company">
|
||||
<el-tab-pane lazy label="入职公司" name="company">
|
||||
<div class="table-wrapper">
|
||||
<ProTable
|
||||
ref="companyTable"
|
||||
@@ -62,7 +62,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 2. 入职部门管理 -->
|
||||
<el-tab-pane label="入职部门" name="department">
|
||||
<el-tab-pane lazy label="入职部门" name="department">
|
||||
<div class="table-wrapper">
|
||||
<ProTable
|
||||
ref="departmentTable"
|
||||
@@ -127,7 +127,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 3. 入职岗位管理 -->
|
||||
<el-tab-pane label="入职岗位" name="position">
|
||||
<el-tab-pane lazy label="入职岗位" name="position">
|
||||
<div class="table-wrapper">
|
||||
<ProTable
|
||||
ref="positionTable"
|
||||
@@ -452,7 +452,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingSetup">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
||||
import { ref, reactive, computed, watch, nextTick } from "vue";
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
|
||||
import { Plus, Edit, Delete } from "@element-plus/icons-vue";
|
||||
import {
|
||||
@@ -779,9 +779,14 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
|
||||
}
|
||||
];
|
||||
|
||||
let companyRequest: Promise<void> | undefined;
|
||||
async function loadCompanies() {
|
||||
const { data } = await onboardingTaskApi.listCompanies();
|
||||
companies.value = data || [];
|
||||
if (!companyRequest) {
|
||||
companyRequest = onboardingTaskApi.listCompanies().then(({ data }) => {
|
||||
companies.value = data || [];
|
||||
}).finally(() => { companyRequest = undefined; });
|
||||
}
|
||||
await companyRequest;
|
||||
}
|
||||
|
||||
async function loadDepartmentsForCompany(companyId?: number) {
|
||||
@@ -938,7 +943,9 @@ async function removePosition(row: OnboardingPosition) {
|
||||
positionTable.value?.refresh();
|
||||
}
|
||||
|
||||
onMounted(loadCompanies);
|
||||
watch(activeTab, tab => {
|
||||
if (tab !== "company" && !companies.value.length) void loadCompanies();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
title="入职任务列表"
|
||||
:columns="columns"
|
||||
:request-api="getTableData"
|
||||
@reset="resetSearchScope"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<div class="task-table-toolbar">
|
||||
@@ -1521,6 +1522,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="onboardingTask">
|
||||
import { useOnboardingScopeOptions } from "@/hooks/useOnboardingScopeOptions";
|
||||
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
@@ -1566,10 +1568,19 @@ const companies = ref<OnboardingCompanyOption[]>([]);
|
||||
const departments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const positions = ref<OnboardingPositionOption[]>([]);
|
||||
const searchCompanies = ref<OnboardingCompanyOption[]>([]);
|
||||
const searchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const searchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const allSearchDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const allSearchPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const {
|
||||
departments: searchDepartments,
|
||||
positions: searchPositions,
|
||||
onCompanyChange: onSearchCompanyChange,
|
||||
onDepartmentChange: onSearchDepartmentChange,
|
||||
reset: resetSearchScope
|
||||
} = useOnboardingScopeOptions(
|
||||
() => proTable.value?.searchParam,
|
||||
() => {
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
}
|
||||
);
|
||||
|
||||
const companySearchOptions = computed(() =>
|
||||
searchCompanies.value.map(c => ({
|
||||
@@ -1592,58 +1603,6 @@ const positionSearchOptions = computed(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
async function onSearchCompanyChange(companyId?: number) {
|
||||
if (companyId) {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(companyId).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(companyId).catch(() => ({ data: [] }))
|
||||
]);
|
||||
searchDepartments.value = depRes.data || [];
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchDepartments.value = allSearchDepartments.value;
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (proTable.value?.searchParam) {
|
||||
const currentDept = proTable.value.searchParam.departmentId;
|
||||
if (currentDept && !searchDepartments.value.some(d => d.id === currentDept)) {
|
||||
proTable.value.searchParam.departmentId = undefined;
|
||||
}
|
||||
const currentPos = proTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
proTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onSearchDepartmentChange(departmentId?: number) {
|
||||
if (departmentId) {
|
||||
const posRes = await onboardingTaskApi.listPositionsByDepartment(departmentId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
const selectedCompanyId = proTable.value?.searchParam?.companyId;
|
||||
if (selectedCompanyId) {
|
||||
const posRes = await onboardingTaskApi.listPositions(selectedCompanyId).catch(() => ({ data: [] }));
|
||||
searchPositions.value = posRes.data || [];
|
||||
} else {
|
||||
searchPositions.value = allSearchPositions.value;
|
||||
}
|
||||
}
|
||||
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
|
||||
if (proTable.value?.searchParam) {
|
||||
const currentPos = proTable.value.searchParam.positionId;
|
||||
if (currentPos && !searchPositions.value.some(p => p.id === currentPos)) {
|
||||
proTable.value.searchParam.positionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onboardingTemplates = ref<OnboardingTemplate[]>([]);
|
||||
const contractTemplates = ref<OnboardingContractTemplate[]>([]);
|
||||
|
||||
@@ -1952,7 +1911,12 @@ const columns: ColumnProps<OnboardingTask>[] = [
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
placeholder: "请选择所属部门",
|
||||
get placeholder() {
|
||||
return proTable.value?.searchParam?.companyId ? "请选择所属部门" : "请先选择所属公司";
|
||||
},
|
||||
get disabled() {
|
||||
return !proTable.value?.searchParam?.companyId;
|
||||
},
|
||||
onChange: (val?: number) => onSearchDepartmentChange(val)
|
||||
}
|
||||
}
|
||||
@@ -1964,7 +1928,16 @@ const columns: ColumnProps<OnboardingTask>[] = [
|
||||
enum: positionSearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择入职岗位" }
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
get placeholder() {
|
||||
return proTable.value?.searchParam?.departmentId ? "请选择入职岗位" : "请先选择所属部门";
|
||||
},
|
||||
get disabled() {
|
||||
return !proTable.value?.searchParam?.departmentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2267,48 +2240,10 @@ async function getTableData(params: any) {
|
||||
}
|
||||
|
||||
async function loadCompanies() {
|
||||
try {
|
||||
const [companyResult, contractResult] = await Promise.all([
|
||||
onboardingTaskApi.listCompanies().catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.templatePage({ pageNum: 1, pageSize: 100, status: "enable" }).catch(() => ({ data: { list: [] } }))
|
||||
]);
|
||||
companies.value = companyResult.data || [];
|
||||
searchCompanies.value = companyResult.data || [];
|
||||
contractTemplates.value = contractResult.data?.list || [];
|
||||
|
||||
const deptList: OnboardingDepartmentOption[] = [];
|
||||
const posList: OnboardingPositionOption[] = [];
|
||||
|
||||
if (companies.value && companies.value.length > 0) {
|
||||
const promises = companies.value.map(async comp => {
|
||||
const [depRes, posRes] = await Promise.all([
|
||||
onboardingTaskApi.listDepartments(comp.id).catch(() => ({ data: [] })),
|
||||
onboardingTaskApi.listPositions(comp.id).catch(() => ({ data: [] }))
|
||||
]);
|
||||
if (depRes.data) deptList.push(...depRes.data);
|
||||
if (posRes.data) posList.push(...posRes.data);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
const uniqueDeptMap = new Map<number, OnboardingDepartmentOption>();
|
||||
deptList.forEach(d => uniqueDeptMap.set(d.id, d));
|
||||
const finalDepts = Array.from(uniqueDeptMap.values());
|
||||
allSearchDepartments.value = finalDepts;
|
||||
searchDepartments.value = finalDepts;
|
||||
|
||||
const uniquePosMap = new Map<number, OnboardingPositionOption>();
|
||||
posList.forEach(p => uniquePosMap.set(p.id, p));
|
||||
const finalPositions = Array.from(uniquePosMap.values());
|
||||
allSearchPositions.value = finalPositions;
|
||||
searchPositions.value = finalPositions;
|
||||
|
||||
proTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
proTable.value?.enumMap.set("departmentId", departmentSearchOptions.value);
|
||||
proTable.value?.enumMap.set("positionId", positionSearchOptions.value);
|
||||
} catch (e) {
|
||||
console.error("加载公司与搜索选项失败", e);
|
||||
}
|
||||
const { data } = await onboardingTaskApi.listCompanies();
|
||||
companies.value = data || [];
|
||||
searchCompanies.value = data || [];
|
||||
proTable.value?.enumMap.set("companyId", companySearchOptions.value);
|
||||
}
|
||||
|
||||
async function onCompanyChange(companyId: number) {
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
// Offline checks: execute the real composable and page setup code with stubbed APIs.
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import vm from "node:vm";
|
||||
import test from "node:test";
|
||||
import { compileScript, parse } from "@vue/compiler-sfc";
|
||||
import ts from "typescript";
|
||||
import * as vue from "vue";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const root = new URL("../", import.meta.url);
|
||||
const read = path => readFileSync(new URL(path, root), "utf8");
|
||||
const deferred = () => {
|
||||
let resolve, reject;
|
||||
const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
|
||||
return { promise, resolve, reject };
|
||||
};
|
||||
|
||||
function harness(overrides = {}) {
|
||||
const calls = [];
|
||||
const api = new Proxy(overrides, {
|
||||
get(target, method) {
|
||||
return (...args) => {
|
||||
calls.push({ method, args });
|
||||
if (method in target) return target[method](...args);
|
||||
if (method === "listCompanies") return Promise.resolve({ data: [{ id: 1, companyName: "A" }, { id: 2, companyName: "B" }] });
|
||||
if (method === "listDepartments") return Promise.resolve({ data: [{ id: args[0] * 10, departmentName: "部门" }] });
|
||||
if (method === "listPositionsByDepartment") return Promise.resolve({ data: [{ id: args[0] * 10, positionName: "岗位" }] });
|
||||
if (method === "listMaterialTypes") return Promise.resolve({ data: [{ id: 1, status: "enable" }] });
|
||||
if (method === "onboardingTemplatePage") return Promise.resolve({ data: { list: [], total: 0 } });
|
||||
throw new Error("Unexpected API: " + method);
|
||||
};
|
||||
}
|
||||
});
|
||||
function evaluate(source, imports = {}) {
|
||||
const output = ts.transpileModule(source.replaceAll("import.meta.env", "({})"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true }
|
||||
}).outputText;
|
||||
const module = { exports: {} };
|
||||
vm.runInNewContext(output, {
|
||||
module, exports: module.exports, console,
|
||||
require(name) {
|
||||
if (name in imports) return imports[name];
|
||||
if (name === "vue") return vue;
|
||||
if (name === "@/api/modules/biz/onboarding") return { onboardingTaskApi: api };
|
||||
if (name === "@/hooks/useOnboardingScopeOptions") return scopeModule;
|
||||
if (name === "@/utils/onboardingPermission") return {
|
||||
useOnboardingPermission: () => ({ canManage: () => true, disabledReason: () => "", hasAction: () => true })
|
||||
};
|
||||
if (name === "@/utils/messageBox" || name === "@/utils") return {};
|
||||
if (name === "element-plus") return { ElMessage: { success() {}, warning() {}, error() {} }, ElMessageBox: {} };
|
||||
if (name === "@element-plus/icons-vue" || name.endsWith(".vue")) return {};
|
||||
return require(name);
|
||||
}
|
||||
});
|
||||
return module.exports;
|
||||
}
|
||||
const scopeModule = evaluate(read("src/hooks/useOnboardingScopeOptions.ts"));
|
||||
function page(path, props = {}) {
|
||||
const mounted = [];
|
||||
const descriptor = parse(read(path), { filename: path }).descriptor;
|
||||
const code = compileScript(descriptor, { id: path }).content;
|
||||
const component = evaluate(code, { vue: { ...vue, onMounted: callback => mounted.push(callback), onBeforeUnmount() {} } }).default;
|
||||
const effects = vue.effectScope();
|
||||
const state = effects.run(() => component.setup(props, { expose() {} }));
|
||||
return { state, mount: () => Promise.all(mounted.map(callback => callback())), stop: () => effects.stop() };
|
||||
}
|
||||
return { api, calls, scopeModule, page };
|
||||
}
|
||||
|
||||
test("dependent options load only after selecting their parent, and reset makes no request", async () => {
|
||||
const h = harness();
|
||||
const selection = vue.reactive({});
|
||||
const options = h.scopeModule.useOnboardingScopeOptions(() => selection);
|
||||
assert.equal(h.calls.length, 0);
|
||||
await options.onDepartmentChange(10);
|
||||
assert.equal(h.calls.length, 0);
|
||||
selection.companyId = 1;
|
||||
await options.onCompanyChange(1);
|
||||
assert.equal(h.calls.length, 1);
|
||||
assert.equal(h.calls[0].method, "listDepartments");
|
||||
assert.equal(options.positions.value.length, 0);
|
||||
selection.departmentId = 10;
|
||||
await options.onDepartmentChange(10);
|
||||
assert.equal(h.calls[1].method, "listPositionsByDepartment");
|
||||
selection.positionId = 100;
|
||||
selection.companyId = undefined;
|
||||
options.reset();
|
||||
assert.equal(selection.departmentId, undefined);
|
||||
assert.equal(selection.positionId, undefined);
|
||||
assert.equal(options.departments.value.length, 0);
|
||||
assert.equal(options.positions.value.length, 0);
|
||||
assert.equal(h.calls.length, 2);
|
||||
});
|
||||
|
||||
test("slow department responses cannot restore a previous company or survive reset", async () => {
|
||||
const a = deferred(), b = deferred(), c = deferred();
|
||||
const h = harness({ listDepartments: id => ({ 1: a, 2: b, 3: c })[id].promise });
|
||||
const selection = { companyId: 1 };
|
||||
const options = h.scopeModule.useOnboardingScopeOptions(() => selection);
|
||||
const first = options.onCompanyChange(1);
|
||||
selection.companyId = 2;
|
||||
const second = options.onCompanyChange(2);
|
||||
b.resolve({ data: [{ id: 20 }] });
|
||||
await second;
|
||||
a.resolve({ data: [{ id: 10 }] });
|
||||
await first;
|
||||
assert.equal(options.departments.value[0].id, 20);
|
||||
selection.companyId = 3;
|
||||
const third = options.onCompanyChange(3);
|
||||
options.reset();
|
||||
c.resolve({ data: [{ id: 30 }] });
|
||||
await third;
|
||||
assert.equal(options.departments.value.length, 0);
|
||||
});
|
||||
|
||||
test("slow position responses cannot overwrite a new department; failure leaves options empty", async () => {
|
||||
const a = deferred(), b = deferred();
|
||||
const h = harness({ listPositionsByDepartment: id => (id === 10 ? a : b).promise });
|
||||
const selection = { companyId: 1, departmentId: 10 };
|
||||
const options = h.scopeModule.useOnboardingScopeOptions(() => selection);
|
||||
const first = options.onDepartmentChange(10);
|
||||
selection.departmentId = 20;
|
||||
const second = options.onDepartmentChange(20);
|
||||
b.resolve({ data: [{ id: 200 }] });
|
||||
await second;
|
||||
a.resolve({ data: [{ id: 100 }] });
|
||||
await first;
|
||||
assert.equal(options.positions.value[0].id, 200);
|
||||
const failed = harness({ listDepartments: () => Promise.reject(new Error("offline")) });
|
||||
const empty = failed.scopeModule.useOnboardingScopeOptions(() => ({ companyId: 1 }));
|
||||
await empty.onCompanyChange(1);
|
||||
assert.equal(empty.departments.value.length, 0);
|
||||
});
|
||||
|
||||
for (const [name, tableName, columnName] of [
|
||||
["contract", "templateTable", "templateColumns"],
|
||||
["task", "proTable", "columns"],
|
||||
["employee", "proTable", "columns"]
|
||||
]) {
|
||||
test(name + ": initialization, filter cascade and reset use only necessary requests", async () => {
|
||||
const h = harness();
|
||||
const page = h.page("src/views/biz/onboarding/" + name + "/index.vue");
|
||||
const state = page.state;
|
||||
state[tableName].value = { searchParam: {}, enumMap: new Map() };
|
||||
await page.mount();
|
||||
assert.equal(h.calls.length, 1);
|
||||
assert.equal(h.calls[0].method, "listCompanies");
|
||||
const department = state[columnName].find(item => item.prop === "departmentId");
|
||||
const position = state[columnName].find(item => item.prop === "positionId");
|
||||
assert.equal(department.search.props.disabled, true);
|
||||
assert.equal(position.search.props.disabled, true);
|
||||
state[tableName].value.searchParam.companyId = 1;
|
||||
await state.onSearchCompanyChange(1);
|
||||
assert.equal(department.search.props.disabled, false);
|
||||
assert.equal(position.search.props.disabled, true);
|
||||
assert.equal(state[tableName].value.enumMap.get("departmentId")[0].value, 10);
|
||||
state[tableName].value.searchParam.departmentId = 10;
|
||||
await state.onSearchDepartmentChange(10);
|
||||
assert.equal(position.search.props.disabled, false);
|
||||
assert.equal(state[tableName].value.enumMap.get("positionId")[0].value, 100);
|
||||
state[tableName].value.searchParam = {};
|
||||
state.resetSearchScope();
|
||||
assert.equal(h.calls.length, 3);
|
||||
assert.equal(state[tableName].value.enumMap.get("departmentId").length, 0);
|
||||
assert.equal(state[tableName].value.enumMap.get("positionId").length, 0);
|
||||
page.stop();
|
||||
});
|
||||
}
|
||||
|
||||
test("entry config loads no hidden data; material loads share one in-flight request", async () => {
|
||||
const pending = deferred();
|
||||
const h = harness({ listMaterialTypes: () => pending.promise });
|
||||
const page = h.page("src/views/biz/onboarding/entry-config/index.vue");
|
||||
await page.mount();
|
||||
assert.equal(h.calls.length, 0);
|
||||
page.state.activeTab.value = "materials";
|
||||
await vue.nextTick();
|
||||
const shared = page.state.loadMaterials();
|
||||
assert.equal(h.calls.length, 1);
|
||||
pending.resolve({ data: [{ id: 5 }] });
|
||||
await shared;
|
||||
assert.equal(page.state.materialTypes.value[0].id, 5);
|
||||
page.stop();
|
||||
});
|
||||
|
||||
test("task form fetches only the selected template's materials and preserves names and HR fields", async () => {
|
||||
const h = harness({
|
||||
onboardingTemplateDetail: () => Promise.resolve({ data: {
|
||||
contracts: [{ contractTemplateId: 11, contractTemplateVersionId: 12, required: true }]
|
||||
} }),
|
||||
templateDetail: () => Promise.resolve({ data: {
|
||||
id: 11, companyId: 1, departmentId: 10, status: "enable", templateName: "劳动合同"
|
||||
} }),
|
||||
variablePage: () => Promise.resolve({ data: { list: [{
|
||||
variableCode: "hr.Salary", variableName: "薪资", variableType: "contract",
|
||||
hrEditable: true, defaultValue: "5000", dataType: "number", required: true
|
||||
}] } })
|
||||
});
|
||||
const page = h.page("src/views/biz/onboarding/task/index.vue");
|
||||
await page.mount();
|
||||
page.state.form.companyId = 1;
|
||||
page.state.form.departmentId = 10;
|
||||
await page.state.onOnboardingTemplateChange(7);
|
||||
assert.equal(h.calls.length, 4);
|
||||
assert.equal(h.calls[1].method, "onboardingTemplateDetail");
|
||||
assert.equal(h.calls[2].method, "templateDetail");
|
||||
assert.equal(h.calls[2].args[0].id, 11);
|
||||
assert.equal(h.calls[3].method, "variablePage");
|
||||
assert.equal(page.state.contractPreviews.value[0].contractName, "劳动合同");
|
||||
assert.equal(page.state.form.contractVariables[0].variableValue, "5000");
|
||||
assert.equal(page.state.form.contractVariables[0].contractName, "劳动合同");
|
||||
assert.equal(page.state.contractConfigurationError.value, "");
|
||||
page.stop();
|
||||
});
|
||||
|
||||
test("entry template list loads just companies and its page; form data is deferred", async () => {
|
||||
const h = harness();
|
||||
let materials = 0;
|
||||
const page = h.page("src/views/biz/onboarding/entry-config/components/OnboardingTemplatePanel.vue", {
|
||||
loadMaterials: async () => { materials++; return [{ id: 7, status: "enable" }]; }
|
||||
});
|
||||
await page.mount();
|
||||
assert.equal(h.calls.length, 2);
|
||||
assert.equal(materials, 0);
|
||||
page.state.query.companyId = 1;
|
||||
await page.state.onQueryCompanyChange(1);
|
||||
assert.equal(h.calls.length, 4);
|
||||
assert.equal(h.calls[2].method, "listDepartments");
|
||||
assert.equal(h.calls[3].method, "onboardingTemplatePage");
|
||||
page.state.resetQuery();
|
||||
await vue.nextTick();
|
||||
assert.equal(h.calls.length, 5);
|
||||
assert.equal(page.state.queryDepartments.value.length, 0);
|
||||
await page.state.openCreate();
|
||||
assert.equal(materials, 1);
|
||||
assert.equal(page.state.materialTypes.value[0].id, 7);
|
||||
assert.equal(h.calls.length, 5);
|
||||
page.stop();
|
||||
});
|
||||
|
||||
test("entry template list ignores older search responses", async () => {
|
||||
const a = deferred(), b = deferred();
|
||||
let count = 0;
|
||||
const h = harness({ onboardingTemplatePage: () => (++count === 1 ? a : b).promise });
|
||||
const page = h.page("src/views/biz/onboarding/entry-config/components/OnboardingTemplatePanel.vue", { loadMaterials: async () => [] });
|
||||
const first = page.state.loadTemplates();
|
||||
page.state.query.keyword = "new";
|
||||
const second = page.state.loadTemplates();
|
||||
b.resolve({ data: { list: [{ id: 2 }], total: 1 } });
|
||||
await second;
|
||||
a.resolve({ data: { list: [{ id: 1 }], total: 1 } });
|
||||
await first;
|
||||
assert.equal(page.state.rows.value[0].id, 2);
|
||||
assert.equal(page.state.loading.value, false);
|
||||
page.stop();
|
||||
});
|
||||
|
||||
test("company setup defers options until needed and shares loading across fast tab changes", async () => {
|
||||
const pending = deferred();
|
||||
const h = harness({ listCompanies: () => pending.promise });
|
||||
const page = h.page("src/views/biz/onboarding/setup/index.vue");
|
||||
await page.mount();
|
||||
assert.equal(h.calls.length, 0);
|
||||
page.state.activeTab.value = "department";
|
||||
await vue.nextTick();
|
||||
page.state.activeTab.value = "position";
|
||||
await vue.nextTick();
|
||||
assert.equal(h.calls.length, 1);
|
||||
pending.resolve({ data: [{ id: 1, companyName: "A" }] });
|
||||
await page.state.loadCompanies();
|
||||
page.state.activeTab.value = "department";
|
||||
await vue.nextTick();
|
||||
assert.equal(h.calls.length, 1);
|
||||
page.stop();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user