1111
This commit is contained in:
@@ -12,13 +12,26 @@
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #companyId="scope">
|
||||
<el-tag effect="plain" type="primary">
|
||||
{{ scope.row.companyName || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #departmentId="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 #contractType="scope">
|
||||
<el-tag effect="plain" type="primary">
|
||||
{{ scope.row.contractType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #positionNames="scope">
|
||||
<template #positionId="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-for="name in scope.row.positionNames || []" :key="name" size="small">
|
||||
{{ name }}
|
||||
@@ -498,6 +511,31 @@ const templateEditing = ref(false);
|
||||
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 companySearchOptions = computed(() =>
|
||||
searchCompanies.value.map(c => ({
|
||||
label: c.companyName,
|
||||
value: c.id
|
||||
}))
|
||||
);
|
||||
|
||||
const departmentSearchOptions = computed(() =>
|
||||
searchDepartments.value.map(d => ({
|
||||
label: d.departmentName,
|
||||
value: d.id
|
||||
}))
|
||||
);
|
||||
|
||||
const positionSearchOptions = computed(() =>
|
||||
searchPositions.value.map(p => ({
|
||||
label: p.positionName,
|
||||
value: p.id
|
||||
}))
|
||||
);
|
||||
|
||||
const versions = ref<OnboardingContractVersion[]>([]);
|
||||
const versionVisible = ref(false);
|
||||
const variablesVisible = ref(false);
|
||||
@@ -560,11 +598,38 @@ 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: "companyId",
|
||||
label: "所属公司",
|
||||
minWidth: 160,
|
||||
enum: companySearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择所属公司" }
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "departmentId",
|
||||
label: "所属部门",
|
||||
minWidth: 140,
|
||||
enum: departmentSearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择所属部门" }
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "positionId",
|
||||
label: "适用岗位",
|
||||
minWidth: 200,
|
||||
enum: positionSearchOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: { filterable: true, clearable: true, placeholder: "请选择适用岗位" }
|
||||
}
|
||||
},
|
||||
{ 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",
|
||||
@@ -737,9 +802,47 @@ async function removeVersion(row: OnboardingContractVersion) {
|
||||
ElMessage.success("版本删除成功");
|
||||
}
|
||||
|
||||
function downloadDocx(fileId?: number) {
|
||||
/**
|
||||
* 使用后台登录令牌读取文件流,避免直接打开受保护接口时出现 401。
|
||||
* 移动端使用独立的入职 Token 公共接口,不经过这里。
|
||||
*/
|
||||
async function downloadDocx(fileId?: number) {
|
||||
if (!fileId) return ElMessage.warning("暂无 DOCX 源文件");
|
||||
window.open(`/api/sys/dev/file/download?id=${fileId}`, "_blank");
|
||||
try {
|
||||
const response: any = await onboardingTaskApi.downloadFile(fileId);
|
||||
const blob = response?.data;
|
||||
if (!(blob instanceof Blob)) {
|
||||
throw new Error("服务器未返回有效的文件流");
|
||||
}
|
||||
|
||||
const disposition = String(response?.headers?.["content-disposition"] || "");
|
||||
const encodedName =
|
||||
disposition.match(/filename\*=UTF-8\'\'([^;]+)/i)?.[1] ||
|
||||
disposition.match(/filename="?([^";]+)"?/i)?.[1];
|
||||
let fileName = "合同模板.docx";
|
||||
if (encodedName) {
|
||||
try {
|
||||
fileName = decodeURIComponent(encodedName.replace(/\+/g, "%20"));
|
||||
} catch {
|
||||
fileName = encodedName;
|
||||
}
|
||||
}
|
||||
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = objectUrl;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
|
||||
} catch (error: any) {
|
||||
ElMessage.error(
|
||||
error?.msg ||
|
||||
error?.message ||
|
||||
"DOCX 源文件下载失败"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type EpTagType = "success" | "warning" | "info" | "primary" | "danger";
|
||||
@@ -806,7 +909,35 @@ async function saveVariable() {
|
||||
ElMessage.success("变量已确认并保存");
|
||||
}
|
||||
|
||||
onMounted(loadCompanies);
|
||||
async function loadSearchOptions() {
|
||||
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),
|
||||
onboardingTaskApi.listPositions(comp.id)
|
||||
]);
|
||||
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));
|
||||
searchDepartments.value = Array.from(uniqueDeptMap.values());
|
||||
|
||||
const uniquePosMap = new Map<number, OnboardingPositionOption>();
|
||||
posList.forEach(p => uniquePosMap.set(p.id, p));
|
||||
searchPositions.value = Array.from(uniquePosMap.values());
|
||||
}
|
||||
|
||||
onMounted(loadSearchOptions);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,12 +1,60 @@
|
||||
<template>
|
||||
<div class="template-panel">
|
||||
<div class="toolbar">
|
||||
<el-input v-model="query.keyword" clearable placeholder="搜索模板编码或名称" class="keyword-input"
|
||||
@keyup.enter="loadTemplates" />
|
||||
<el-select v-model="query.companyId" clearable filterable placeholder="全部公司" class="company-filter">
|
||||
<el-input
|
||||
v-model="query.keyword"
|
||||
clearable
|
||||
placeholder="搜索模板编码或名称"
|
||||
class="keyword-input"
|
||||
@keyup.enter="loadTemplates"
|
||||
@clear="loadTemplates"
|
||||
/>
|
||||
<el-select
|
||||
v-model="query.companyId"
|
||||
clearable
|
||||
filterable
|
||||
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>
|
||||
<el-button type="primary" @click="loadTemplates">查询</el-button>
|
||||
<el-select
|
||||
v-model="query.departmentId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部部门"
|
||||
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>
|
||||
<el-select
|
||||
v-model="query.positionId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部岗位"
|
||||
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>
|
||||
<el-select
|
||||
v-model="query.status"
|
||||
clearable
|
||||
placeholder="模板状态"
|
||||
class="status-filter"
|
||||
@change="loadTemplates"
|
||||
@clear="loadTemplates"
|
||||
>
|
||||
<el-option label="全部状态" value="" />
|
||||
<el-option label="启用" value="enable" />
|
||||
<el-option label="停用" value="disable" />
|
||||
</el-select>
|
||||
<el-button type="primary" icon="Search" @click="loadTemplates">查询</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<div class="toolbar-spacer" />
|
||||
<el-button type="primary" icon="Plus" @click="openCreate">新增入职模板</el-button>
|
||||
@@ -235,6 +283,7 @@ import {
|
||||
OnboardingDepartmentOption,
|
||||
OnboardingMaterialType,
|
||||
OnboardingPosition,
|
||||
OnboardingPositionOption,
|
||||
OnboardingTemplate,
|
||||
onboardingTaskApi
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
@@ -246,6 +295,8 @@ const rows = ref<OnboardingTemplate[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const companies = ref<OnboardingCompany[]>([]);
|
||||
const queryDepartments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const queryPositions = ref<OnboardingPositionOption[]>([]);
|
||||
const departments = ref<OnboardingDepartmentOption[]>([]);
|
||||
const positions = ref<OnboardingPosition[]>([]);
|
||||
const contractTemplates = ref<OnboardingContractTemplate[]>([]);
|
||||
@@ -255,6 +306,9 @@ const versionOptions = reactive<Record<number, OnboardingContractVersion[]>>({})
|
||||
const query = reactive({
|
||||
keyword: "",
|
||||
companyId: undefined as number | undefined,
|
||||
departmentId: undefined as number | undefined,
|
||||
positionId: undefined as number | undefined,
|
||||
status: "enable" as string | undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
@@ -273,6 +327,30 @@ const form = reactive<OnboardingTemplate>({
|
||||
materials: []
|
||||
});
|
||||
|
||||
async function loadAllQueryOptions() {
|
||||
if (!companies.value || companies.value.length === 0) return;
|
||||
const deptList: OnboardingDepartmentOption[] = [];
|
||||
const posList: OnboardingPositionOption[] = [];
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
async function loadBaseData() {
|
||||
const [companyRes, materialRes] = await Promise.all([
|
||||
onboardingTaskApi.companyPage({ pageNum: 1, pageSize: 100, status: "enable" }).catch(() => ({ data: { list: [] } })),
|
||||
@@ -280,6 +358,7 @@ async function loadBaseData() {
|
||||
]);
|
||||
companies.value = companyRes.data?.list || [];
|
||||
materialTypes.value = materialRes.data || [];
|
||||
await loadAllQueryOptions();
|
||||
}
|
||||
|
||||
async function loadTemplates() {
|
||||
@@ -293,10 +372,61 @@ async function loadTemplates() {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
query.keyword = "";
|
||||
query.companyId = undefined;
|
||||
query.departmentId = undefined;
|
||||
query.positionId = undefined;
|
||||
query.status = "enable";
|
||||
query.pageNum = 1;
|
||||
loadAllQueryOptions();
|
||||
loadTemplates();
|
||||
}
|
||||
|
||||
@@ -521,7 +651,19 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.company-filter {
|
||||
width: 180px;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.department-filter {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.position-filter {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.toolbar-spacer {
|
||||
|
||||
Reference in New Issue
Block a user