wuzhen update招生就业

This commit is contained in:
“wuzhen” committed 2026-04-02 10:49:59 +08:00
1 parent 7e122d76dd
commit e5c6fd7a24
55 files changed
+3563 -2397

No files matched your search

+102 -11
View File
@@ -1,12 +1,12 @@
/**
* 企业信息管理 API
* @author Cursor AI
* @date 2025-01-25
* @date 2026-03-30
*/
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from './types';
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery, EnterpriseJobForm, EnterpriseBrochureVO } from './types';
// TableDataInfo 接口定义
export interface TableDataInfo<T> {
@@ -19,7 +19,6 @@ export interface TableDataInfo<T> {
/**
* 查询企业信息列表
* @param query 查询参数
* @returns Promise<TableDataInfo<EnterpriseVO>>
*/
export function listEnterprise(query?: EnterpriseQuery): AxiosPromise<TableDataInfo<EnterpriseVO>> {
return request({
@@ -32,9 +31,8 @@ export function listEnterprise(query?: EnterpriseQuery): AxiosPromise<TableDataI
/**
* 查询企业信息详细
* @param id 企业主键
* @returns Promise<EnterpriseVO>
*/
export function getEnterprise(id: string | number): AxiosPromise<EnterpriseVO> {
export function getEnterprise(id: string | number): AxiosPromise<any> {
return request({
url: `/zsjy/enterprise/${id}`,
method: 'get'
@@ -42,9 +40,8 @@ export function getEnterprise(id: string | number): AxiosPromise<EnterpriseVO> {
}
/**
* 新增企业信息
* 新增企业信息(含岗位)
* @param data 企业信息
* @returns Promise<any>
*/
export function addEnterprise(data: EnterpriseForm): AxiosPromise<any> {
return request({
@@ -55,9 +52,8 @@ export function addEnterprise(data: EnterpriseForm): AxiosPromise<any> {
}
/**
* 修改企业信息
* 修改企业信息(含岗位)
* @param data 企业信息
* @returns Promise<any>
*/
export function updateEnterprise(data: EnterpriseForm): AxiosPromise<any> {
return request({
@@ -70,7 +66,6 @@ export function updateEnterprise(data: EnterpriseForm): AxiosPromise<any> {
/**
* 删除企业信息
* @param id 企业主键
* @returns Promise<any>
*/
export function delEnterprise(id: string | number | Array<string | number>): AxiosPromise<any> {
return request({
@@ -82,7 +77,6 @@ export function delEnterprise(id: string | number | Array<string | number>): Axi
/**
* 导出企业信息
* @param query 查询参数
* @returns Promise<any>
*/
export function exportEnterprise(query: EnterpriseQuery): AxiosPromise<any> {
return request({
@@ -92,3 +86,100 @@ export function exportEnterprise(query: EnterpriseQuery): AxiosPromise<any> {
responseType: 'blob'
});
}
/**
* 获取企业下拉选项
*/
export function listEnterpriseOptions(): AxiosPromise<any> {
return request({
url: '/zsjy/enterprise/options',
method: 'get'
});
}
/**
* 下载导入模板
*/
export function getImportTemplate(): AxiosPromise<any> {
return request({
url: '/zsjy/enterprise/importTemplate',
method: 'get',
responseType: 'blob'
});
}
/**
* 导入企业信息
* @param file Excel文件
* @param updateSupport 是否支持更新(同名覆盖)
*/
export function importEnterprise(file: File, updateSupport: boolean = false): AxiosPromise<any> {
const formData = new FormData();
formData.append('file', file);
formData.append('updateSupport', String(updateSupport));
return request({
url: '/zsjy/enterprise/importData',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
/**
* 生成招聘简章PDF(单个)
* @param enterpriseId 企业ID
*/
export function generateBrochure(enterpriseId: number | string): AxiosPromise<EnterpriseBrochureVO> {
return request({
url: `/zsjy/enterprise/brochure/${enterpriseId}`,
method: 'post'
});
}
/**
* 批量生成招聘简章PDF
* @param enterpriseIds 企业ID列表(逗号分隔)
*/
export function generateBrochureBatch(enterpriseIds: string): AxiosPromise<any> {
return request({
url: `/zsjy/enterprise/brochure/batch/${enterpriseIds}`,
method: 'post'
});
}
/**
* 获取最新简章信息
* @param enterpriseId 企业ID
*/
export function getBrochure(enterpriseId: number | string): AxiosPromise<any> {
return request({
url: `/zsjy/enterprise/brochure/${enterpriseId}`,
method: 'get'
});
}
/**
* 下载招聘简章PDF(通过企业ID)
* @param enterpriseId 企业ID
*/
export function downloadBrochure(enterpriseId: number | string): AxiosPromise<any> {
return request({
url: `/zsjy/enterprise/brochure/download/${enterpriseId}`,
method: 'get',
responseType: 'blob'
});
}
/**
* 下载招聘简章PDF(通过简章记录ID)
* @param id 简章记录ID
*/
export function downloadBrochureById(id: number | string): AxiosPromise<any> {
return request({
url: `/zsjy/enterprise/brochure/downloadById/${id}`,
method: 'get',
responseType: 'blob'
});
}
+46 -1737
View File
File diff suppressed because it is too large. Load diff
+10 -5
View File
@@ -248,17 +248,22 @@ service.interceptors.response.use(
return Promise.reject(error);
}
);
// 通用下载方法
export function download(url: string, params: any, fileName: string) {
// 通用下载方法(支持GET和POST)
export function download(url: string, params: any, fileName: string, method: string = 'post') {
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
const isGet = method.toLowerCase() === 'get';
// prettier-ignore
return service.post(url, params, {
transformRequest: [
return service({
url: url,
method: method,
params: isGet ? params : undefined,
data: isGet ? undefined : params,
transformRequest: isGet ? undefined : [
(params: any) => {
return tansParams(params);
}
],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
headers: isGet ? undefined : { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
timeout: 1200000 // 导出接口单独设置20分钟超时
}).then(async (resp: any) => {
+657 -97
View File
@@ -63,6 +63,12 @@
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport" v-hasPermi="['zsjy:enterprise:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Upload" @click="showImportDialog = true" v-hasPermi="['zsjy:enterprise:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Document" :disabled="multiple" type="success" @click="handleGenerateBrochureBatch" v-hasPermi="['zsjy:enterprise:brochure']">批量生成简章</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
@@ -83,15 +89,18 @@
</el-table-column>
<el-table-column label="联系人" align="center" prop="contactName" width="100" show-overflow-tooltip />
<el-table-column label="联系电话" align="center" prop="contactPhone" width="120" show-overflow-tooltip />
<el-table-column label="企业邮箱" align="center" prop="email" min-width="150" show-overflow-tooltip />
<el-table-column label="企业地址" align="center" prop="address" min-width="200" show-overflow-tooltip />
<el-table-column label="岗位数量" align="center" prop="jobCount" width="100">
<template #default="scope">
<el-tag type="primary" effect="plain">{{ scope.row.jobCount || 0 }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="160" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="320" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -115,6 +124,17 @@
v-hasPermi="['zsjy:enterprise:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="生成简章" placement="top">
<el-button
link
type="success"
icon="Document"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleGenerateBrochure(scope.row)"
v-hasPermi="['zsjy:enterprise:brochure']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
@@ -137,52 +157,74 @@
</el-card>
<!-- 添加或修改企业信息对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1100px" append-to-body class="enterprise-dialog">
<el-form ref="enterpriseFormRef" :model="form" :rules="rules" label-width="120px">
<el-divider content-position="left">企业基本信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="企业名称" prop="enterpriseName">
<el-input v-model="form.enterpriseName" placeholder="请输入企业名称" />
<el-input v-model="form.enterpriseName" placeholder="请输入企业名称" maxlength="200" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="industry">
<el-select v-model="form.industry" placeholder="请选择所属行业" style="width: 100%">
<el-select v-model="form.industry" placeholder="请选择所属行业" clearable style="width: 100%">
<el-option v-for="dict in zsjy_enterprise_industry" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人姓名" prop="contactName">
<el-input v-model="form.contactName" placeholder="请输入联系人姓名" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="办公电话" prop="officePhone">
<el-input v-model="form.officePhone" placeholder="请输入办公电话" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电子邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入电子邮箱" maxlength="100" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="企业地址" prop="address">
<el-input v-model="form.address" placeholder="请输入企业地址" maxlength="255" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="企业简介" prop="enterpriseIntro">
<el-input v-model="form.enterpriseIntro" type="textarea" placeholder="请输入企业简介(用于招聘简章展示)" :rows="4" maxlength="1500" show-word-limit />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="企业规模" prop="enterpriseScale">
<el-select v-model="form.enterpriseScale" placeholder="请选择企业规模" style="width: 100%">
<el-select v-model="form.enterpriseScale" placeholder="请选择企业规模" clearable style="width: 100%">
<el-option v-for="dict in zsjy_enterprise_scale" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系人姓名" prop="contactName">
<el-input v-model="form.contactName" placeholder="请输入联系人姓名" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入企业邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
@@ -192,30 +234,42 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="企业地址" prop="address">
<el-input v-model="form.address" placeholder="请输入企业地址" />
</el-form-item>
</el-col>
</el-row>
<!-- 岗位信息区域 -->
<el-divider content-position="left">
招聘岗位信息
<el-button type="primary" link icon="Plus" style="margin-left: 8px" @click="handleAddJob">添加岗位</el-button>
</el-divider>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="主营业务" prop="mainBusiness">
<el-input v-model="form.mainBusiness" type="textarea" placeholder="请输入主营业务" :autosize="{ minRows: 3, maxRows: 5 }" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注信息" :autosize="{ minRows: 2, maxRows: 4 }" />
</el-form-item>
</el-col>
</el-row>
<div class="job-table-container">
<el-table :data="form.jobList" border style="width: 100%" size="small">
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="岗位名称" min-width="150">
<template #default="scope">
<el-input v-model="scope.row.positionName" placeholder="请输入岗位名称" maxlength="100" />
</template>
</el-table-column>
<el-table-column label="岗位要求" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.requirements" type="textarea" placeholder="请输入岗位要求" :rows="2" />
</template>
</el-table-column>
<el-table-column label="待遇" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.benefits" type="textarea" placeholder="请输入待遇信息" :rows="2" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button link type="danger" icon="Delete" @click="handleRemoveJob(scope.$index)" :disabled="form.jobList.length <= 1">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!form.jobList || form.jobList.length === 0" class="empty-job-tip">
暂无岗位信息,点击上方「添加岗位」按钮新增
</div>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
@@ -236,27 +290,135 @@
</el-descriptions-item>
<el-descriptions-item label="联系人姓名">{{ viewForm.contactName }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewForm.contactPhone }}</el-descriptions-item>
<el-descriptions-item label="企业邮箱">{{ viewForm.email }}</el-descriptions-item>
<el-descriptions-item label="办公电话">{{ viewForm.officePhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="电子邮箱">{{ viewForm.email }}</el-descriptions-item>
<el-descriptions-item label="企业地址" span="2">{{ viewForm.address }}</el-descriptions-item>
<el-descriptions-item label="主营业务" span="2">{{ viewForm.mainBusiness }}</el-descriptions-item>
<el-descriptions-item label="企业简介" span="2">{{ viewForm.enterpriseIntro || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :options="sys_normal_disable" :value="viewForm.status" />
</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ viewForm.createTime }}</el-descriptions-item>
<el-descriptions-item label="备注" span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 岗位信息展示 -->
<el-divider content-position="left">招聘岗位信息</el-divider>
<el-table v-if="viewForm.jobList && viewForm.jobList.length > 0" :data="viewForm.jobList" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="positionName" label="岗位名称" min-width="150" />
<el-table-column prop="requirements" label="岗位要求" min-width="200" show-overflow-tooltip />
<el-table-column prop="benefits" label="待遇" min-width="200" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
</el-table>
<el-empty v-else description="暂无岗位信息" :image-size="60" />
<template #footer>
<div class="dialog-footer">
<el-button type="success" icon="Document" @click="handleViewGenerateBrochure(viewForm)" v-hasPermi="['zsjy:enterprise:brochure']">生成简章</el-button>
<el-button @click="viewDialog.visible = false">关 闭</el-button>
</div>
</template>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog title="导入企业信息" v-model="showImportDialog" width="500px" append-to-body>
<el-upload
ref="uploadRef"
class="upload-demo"
drag
:limit="1"
accept=".xlsx, .xls"
:auto-upload="false"
:headers="uploadHeaders"
:action="importUrl"
:disabled="importLoading"
:on-progress="handleImportProgress"
:on-success="handleImportSuccess"
:on-error="handleImportError"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将Excel文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">
<el-checkbox v-model="importUpdateSupport" />是否更新已存在的企业数据
<br />
仅支持 .xlsx 和 .xls 格式,<el-link type="primary" :underline="false" @click="handleDownloadTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" :loading="importLoading" @click="submitImport">确 定</el-button>
<el-button @click="showImportDialog = false">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 简章生成/预览对话框 -->
<el-dialog
title="招聘简章"
v-model="brochureDialog.visible"
width="900px"
:close-on-click-modal="false"
append-to-body
destroy-on-close
>
<div class="brochure-actions">
<el-button type="success" icon="Download" @click="handleDownloadBrochure" :loading="brochureLoading">下载PDF</el-button>
<el-button icon="Refresh" @click="handleReGenerateBrochure" :loading="brochureLoading">重新生成</el-button>
</div>
<div v-if="brochureDialog.brochureInfo" class="brochure-info">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="企业名称">{{ brochureDialog.brochureInfo.enterpriseName }}</el-descriptions-item>
<el-descriptions-item label="生成人">{{ brochureDialog.brochureInfo.generateByName }}</el-descriptions-item>
<el-descriptions-item label="生成时间" :span="2">{{ brochureDialog.brochureInfo.generateTime }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- PDF预览区 -->
<div class="pdf-preview-wrapper">
<div v-if="brochureLoading && !brochureDialog.previewUrl" class="pdf-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
<iframe
v-else-if="brochureDialog.previewUrl"
:src="brochureDialog.previewUrl"
class="pdf-iframe"
title="招聘简章预览"
/>
<div v-else class="pdf-empty">暂无预览,请先生成简章</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="brochureDialog.visible = false">关 闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Enterprise" lang="ts">
import { listEnterprise, getEnterprise, delEnterprise, addEnterprise, updateEnterprise } from '@/api/zsjy/enterprise';
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from '@/api/zsjy/types';
import {
listEnterprise,
getEnterprise,
delEnterprise,
addEnterprise,
updateEnterprise,
exportEnterprise,
importEnterprise,
getImportTemplate,
generateBrochure,
getBrochure,
downloadBrochure
} from '@/api/zsjy/enterprise';
import { Loading } from '@element-plus/icons-vue';
import { EnterpriseVO, EnterpriseJobForm } from '@/api/zsjy/types';
import request from '@/utils/request';
import { globalHeaders } from '@/utils/request';
import { computed } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -268,6 +430,28 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const importLoading = ref(false);
const brochureLoading = ref(false);
// 导入相关
const showImportDialog = ref(false);
const importUpdateSupport = ref(false);
const uploadRef = ref();
const importUrl = computed(() => import.meta.env.VITE_APP_BASE_API + '/zsjy/enterprise/importData?updateSupport=' + importUpdateSupport.value);
const uploadHeaders = globalHeaders();
// 简章相关
const brochureDialog = reactive<{
visible: boolean;
enterpriseId: any;
brochureInfo: any;
previewUrl: string | null;
}>({
visible: false,
enterpriseId: null,
brochureInfo: null,
previewUrl: null
});
const queryFormRef = ref<ElFormInstance>();
const enterpriseFormRef = ref<ElFormInstance>();
@@ -277,21 +461,25 @@ const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'))
const { zsjy_enterprise_industry } = toRefs<any>(proxy?.useDict('zsjy_enterprise_industry'));
const { zsjy_enterprise_scale } = toRefs<any>(proxy?.useDict('zsjy_enterprise_scale'));
const initFormData: EnterpriseForm = {
const initFormData = {
id: undefined,
enterpriseName: '',
industry: '',
mainBusiness: '',
enterpriseScale: '',
enterpriseIntro: '',
contactName: '',
contactPhone: '',
officePhone: '',
email: '',
website: '',
address: '',
status: '0',
remark: ''
remark: '',
jobList: [] as EnterpriseJobForm[]
};
const data = reactive<PageData<EnterpriseForm, EnterpriseQuery>>({
const data = reactive<PageData<typeof initFormData, any>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
@@ -303,20 +491,16 @@ const data = reactive<PageData<EnterpriseForm, EnterpriseQuery>>({
},
rules: {
enterpriseName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
industry: [{ required: true, message: '所属行业不能为空', trigger: 'change' }],
mainBusiness: [{ required: true, message: '主营业务不能为空', trigger: 'blur' }],
enterpriseScale: [{ required: true, message: '企业规模不能为空', trigger: 'change' }],
contactName: [{ required: true, message: '联系人姓名不能为空', trigger: 'blur' }],
contactPhone: [
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '企业邮箱不能为空', trigger: 'blur' },
{ required: true, message: '电子邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
address: [{ required: true, message: '企业地址不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
address: [{ required: true, message: '企业地址不能为空', trigger: 'blur' }]
}
});
@@ -338,10 +522,13 @@ const viewForm = ref<EnterpriseVO>({} as EnterpriseVO);
/** 查询企业信息列表 */
const getList = async () => {
loading.value = true;
const res = await listEnterprise(queryParams.value);
enterpriseList.value = res.rows;
total.value = res.total;
loading.value = false;
try {
const res = await listEnterprise(queryParams.value);
enterpriseList.value = res.rows;
total.value = res.total;
} finally {
loading.value = false;
}
};
/** 取消按钮 */
@@ -352,7 +539,7 @@ const cancel = () => {
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
form.value = { ...initFormData, jobList: [] };
enterpriseFormRef.value?.resetFields();
};
@@ -380,16 +567,28 @@ const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加企业信息';
// 初始化至少一个空岗位行
form.value.jobList = [{ positionName: '', requirements: '', benefits: '', status: '0' }];
};
/** 修改按钮操作 */
const handleUpdate = async (row?: EnterpriseVO) => {
reset();
const id = row?.id || ids.value[0];
const res = await getEnterprise(id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改企业信息';
loading.value = true;
try {
reset();
const id = row?.id || ids.value[0];
const res = await getEnterprise(id);
// 显式设置 jobList,避免 Object.assign 后 jobList 被其他引用污染
Object.assign(form.value, res.data, { jobList: res.data.jobList || [] });
// 确保岗位列表不为空(至少有一行空岗位方便用户继续添加)
if (!form.value.jobList || form.value.jobList.length === 0) {
form.value.jobList = [{ positionName: '', requirements: '', benefits: '', status: '0' }];
}
dialog.visible = true;
dialog.title = '修改企业信息';
} finally {
loading.value = false;
}
};
/** 查看详情 */
@@ -399,27 +598,51 @@ const handleView = async (row: EnterpriseVO) => {
viewDialog.visible = true;
};
/** 添加岗位 */
const handleAddJob = () => {
if (!form.value.jobList) {
form.value.jobList = [];
}
form.value.jobList.push({ positionName: '', requirements: '', benefits: '', status: '0' });
};
/** 删除岗位 */
const handleRemoveJob = (index: number) => {
if (form.value.jobList && form.value.jobList.length > 1) {
form.value.jobList.splice(index, 1);
}
};
/** 提交按钮 */
const submitForm = () => {
enterpriseFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateEnterprise(form.value).finally(() => (buttonLoading.value = false));
} else {
await addEnterprise(form.value).finally(() => (buttonLoading.value = false));
try {
// 过滤掉空岗位(岗位名称为空的不提交)
const validJobList = (form.value.jobList || []).filter((job) => job.positionName && job.positionName.trim() !== '');
const submitData = { ...form.value, jobList: validJobList };
if (form.value.id) {
await updateEnterprise(submitData);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addEnterprise(submitData);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: EnterpriseVO) => {
const idList = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除企业信息编号为"' + idList + '"的数据项?');
const idList = row?.id ? [row.id] : ids.value;
await proxy?.$modal.confirm('是否确认删除选中的企业信息?删除后将同时删除关联的岗位信息。');
await delEnterprise(idList);
proxy?.$modal.msgSuccess('删除成功');
await getList();
@@ -427,13 +650,271 @@ const handleDelete = async (row?: EnterpriseVO) => {
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'zsjy/enterprise/export',
{
...queryParams.value
},
`enterprise_${new Date().getTime()}.xlsx`
);
proxy?.download('zsjy/enterprise/export', { ...queryParams.value }, `enterprise_${new Date().getTime()}.xlsx`);
};
/** 下载导入模板 */
const handleDownloadTemplate = () => {
proxy?.download('zsjy/enterprise/importTemplate', {}, 'enterprise_import_template.xlsx', 'GET');
};
/** 提交导入 */
const submitImport = () => {
if (!uploadRef.value) {
proxy?.$modal.msgError('上传组件未初始化');
return;
}
importLoading.value = true;
uploadRef.value?.submit();
};
/** 导入文件上传中 */
const handleImportProgress = () => {
importLoading.value = true;
};
/** 导入文件上传成功 */
const handleImportSuccess = (response: any) => {
importLoading.value = false;
showImportDialog.value = false;
proxy?.$modal.msgSuccess(response.msg || '导入成功');
getList();
};
/** 导入文件上传失败 */
const handleImportError = (err: any) => {
importLoading.value = false;
console.error('导入失败:', err);
proxy?.$modal.msgError(err?.message || '导入失败');
};
/** 生成简章(单个) */
const handleGenerateBrochure = async (row: EnterpriseVO) => {
brochureLoading.value = true;
// 生成前清理旧预览
if (brochureDialog.previewUrl) {
window.URL.revokeObjectURL(brochureDialog.previewUrl);
brochureDialog.previewUrl = null;
}
try {
const res = await generateBrochure(row.id);
brochureDialog.enterpriseId = row.id;
brochureDialog.brochureInfo = res.data;
brochureDialog.visible = true;
// 生成成功后自动加载预览
await loadBrochurePreview(row.id);
proxy?.$modal.msgSuccess('简章生成成功');
} catch (err: any) {
proxy?.$modal.msgError(err?.msg || '生成失败');
} finally {
brochureLoading.value = false;
}
};
/** 批量生成简章 */
const handleGenerateBrochureBatch = async () => {
if (ids.value.length === 0) {
proxy?.$modal.msgWarning('请先选择要生成简章的企业');
return;
}
await proxy?.$modal.confirm(`确认要批量生成 ${ids.value.length} 家企业的招聘简章吗?\n生成后将下载一个包含 ${ids.value.length} 页的多页PDF文件。`);
brochureLoading.value = true;
proxy?.$modal.loading('正在生成多页PDF,请稍候...');
try {
const response: any = await request({
url: '/zsjy/enterprise/brochure/batch/download',
method: 'post',
data: ids.value,
responseType: 'blob',
timeout: 300000
});
proxy?.$modal.closeLoading();
// 处理响应
if (!response) {
throw new Error('下载响应为空');
}
// 检查是否是 Blob 对象
const blob = response instanceof Blob ? response : null;
if (!blob) {
throw new Error('下载响应格式错误');
}
// 检查文件大小
if (blob.size === 0) {
throw new Error('下载文件为空');
}
// 检查是否是 JSON 错误响应
if (blob.type === 'application/json' || blob.type === 'text/plain') {
const text = await blob.text();
try {
const json = JSON.parse(text);
throw new Error(json.msg || '下载失败');
} catch (e) {
if (e.message !== '下载失败') {
throw new Error('下载失败,请重试');
}
throw e;
}
}
// 下载文件
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `企业招聘简章_${new Date().getTime()}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
proxy?.$modal.msgSuccess('批量生成成功,共下载 ' + ids.value.length + ' 页');
} catch (err: any) {
console.error('批量生成失败:', err);
proxy?.$modal.msgError(err?.message || err?.msg || '批量生成失败');
} finally {
brochureLoading.value = false;
}
};
/** 查看详情时生成简章 */
const handleViewGenerateBrochure = async (row: EnterpriseVO) => {
await handleGenerateBrochure(row);
viewDialog.visible = false;
};
/** 下载PDF */
const handleDownloadBrochure = async () => {
if (!brochureDialog.enterpriseId) return;
// 显示下载加载状态
const loadingInstance = ElLoading.service({ text: '正在下载,请稍候...', background: 'rgba(0, 0, 0, 0.7)' });
try {
// 使用API中已有的downloadBrochure函数,确保认证正确
const blob: any = await downloadBrochure(brochureDialog.enterpriseId);
// 检查响应
if (!blob) {
throw new Error('下载响应为空');
}
// 检查是否是 Blob 对象
if (!(blob instanceof Blob)) {
throw new Error('下载响应格式错误');
}
// 检查文件大小
if (blob.size === 0) {
throw new Error('下载文件为空');
}
// 检查是否是 JSON 错误响应
if (blob.type === 'application/json' || (blob.type === 'text/plain')) {
const text = await blob.text();
try {
const json = JSON.parse(text);
throw new Error(json.msg || '下载失败');
} catch {
throw new Error('下载失败,请重试');
}
}
// 下载文件
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `招聘简章_${brochureDialog.brochureInfo?.enterpriseName || '未知企业'}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
proxy?.$modal.msgSuccess('下载成功');
} catch (err: any) {
console.error('下载PDF失败:', err);
proxy?.$modal.msgError(err?.message || '下载失败');
} finally {
loadingInstance.close();
}
};
/** 加载简章预览 */
const loadBrochurePreview = async (enterpriseId: any) => {
if (!enterpriseId) return;
brochureLoading.value = true;
try {
console.log('开始加载预览,企业ID:', enterpriseId);
const response = await downloadBrochure(enterpriseId);
console.log('预览响应类型:', typeof response, 'response:', response);
// 检查响应
if (!response) {
console.error('预览响应为空');
return;
}
// 检查是否是 Blob 对象
if (!(response instanceof Blob)) {
console.error('预览响应不是Blob对象:', typeof response);
return;
}
console.log('Blob大小:', response.size, '类型:', response.type);
// 检查文件大小
if (response.size === 0) {
console.error('预览文件为空');
return;
}
// 检查是否是 JSON 错误响应
if (response.type === 'application/json' || response.type === 'text/plain') {
const text = await response.text();
console.error('服务端返回了JSON错误:', text);
try {
const json = JSON.parse(text);
console.error('预览加载失败,服务端返回错误:', json.msg || json.message);
} catch {
console.error('预览加载失败,未知错误');
}
return;
}
// 清理旧的预览URL
if (brochureDialog.previewUrl) {
window.URL.revokeObjectURL(brochureDialog.previewUrl);
}
// 创建新的预览URL
brochureDialog.previewUrl = window.URL.createObjectURL(response);
console.log('预览加载成功');
} catch (err: any) {
console.error('预览加载失败:', err);
// 显示错误消息
proxy?.$modal.msgError('预览加载失败: ' + (err?.message || err?.msg || '未知错误'));
} finally {
brochureLoading.value = false;
}
};
/** 重新生成 */
const handleReGenerateBrochure = async () => {
if (!brochureDialog.enterpriseId) return;
brochureLoading.value = true;
try {
const res = await generateBrochure(brochureDialog.enterpriseId);
brochureDialog.brochureInfo = res.data;
await loadBrochurePreview(brochureDialog.enterpriseId);
proxy?.$modal.msgSuccess('重新生成成功');
} catch (err: any) {
proxy?.$modal.msgError(err?.msg || '重新生成失败');
} finally {
brochureLoading.value = false;
}
};
onMounted(() => {
@@ -442,7 +923,6 @@ onMounted(() => {
</script>
<style scoped lang="scss">
// 继承后勤管理的样式
.page-container {
.search-btn-container {
text-align: right;
@@ -473,26 +953,54 @@ onMounted(() => {
text-align: right;
}
// 表格适应性样式
.pdf-preview-wrapper {
margin-top: 15px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: #f5f7fa;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
.pdf-loading,
.pdf-empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: #909399;
padding: 40px;
.el-icon {
font-size: 32px;
}
}
.pdf-iframe {
width: 100%;
height: 600px;
border: none;
border-radius: 4px;
}
}
:deep(.el-table) {
.el-table__cell {
padding: 8px 0;
}
}
// 响应式表格
:deep(.el-table__body-wrapper) {
overflow-x: auto;
}
// 表格列宽自适应
.el-table {
width: 100%;
table-layout: auto;
}
}
// 描述列表样式优化
:deep(.el-descriptions) {
.el-descriptions__label {
font-weight: 600;
@@ -504,6 +1012,7 @@ onMounted(() => {
color: #303133;
}
}
.search-btn-container {
display: flex;
justify-content: flex-end;
@@ -513,4 +1022,55 @@ onMounted(() => {
margin-bottom: 0 !important;
}
}
.enterprise-dialog {
:deep(.el-dialog__body) {
max-height: 70vh;
overflow-y: auto;
}
}
.job-table-container {
margin-top: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
.empty-job-tip {
text-align: center;
color: #909399;
padding: 20px;
font-size: 14px;
}
}
.brochure-actions {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.brochure-info {
margin-top: 10px;
}
.upload-demo {
text-align: center;
.el-icon-upload {
font-size: 67px;
color: #409eff;
margin: 20px 0 10px;
}
.el-upload__text {
color: #606266;
font-size: 14px;
em {
color: #409eff;
font-style: normal;
}
}
}
</style>
+91 -34
View File
@@ -175,7 +175,30 @@
<el-radio v-for="dict in zsjy_poster_type" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="关联岗位" prop="positionId" v-if="form.posterType === '2'">
<!-- 新增岗位海报时,支持岗位多选 -->
<el-form-item
v-if="form.posterType === '2' && !form.id"
label="关联岗位"
prop="positionIdList"
>
<el-select
v-model="form.positionIdList"
placeholder="请选择关联岗位(可多选)"
style="width: 100%"
:disabled="!form.enterpriseId"
multiple
collapse-tags
collapse-tags-tooltip
>
<el-option v-for="item in formPositionOptions" :key="item.id" :label="item.positionName" :value="item.id" />
</el-select>
</el-form-item>
<!-- 编辑岗位海报时,保持单选,避免修改已生成数据的关联关系过于复杂 -->
<el-form-item
v-else-if="form.posterType === '2' && !!form.id"
label="关联岗位"
prop="positionId"
>
<el-select v-model="form.positionId" placeholder="请选择关联岗位" style="width: 100%" :disabled="!form.enterpriseId">
<el-option v-for="item in formPositionOptions" :key="item.id" :label="item.positionName" :value="item.id" />
</el-select>
@@ -199,28 +222,17 @@
<!-- 预览对话框 -->
<el-dialog title="海报预览" v-model="previewOpen" width="900px" append-to-body>
<div v-if="previewOpen && previewData.posterPath" class="poster-preview-container">
<!-- 海报图片预览 -->
<div class="poster-image-section">
<image-preview :src="previewData.posterPath" :width="400" :height="600" :preview="true" style="margin: 0 auto" />
</div>
<!-- 海报详情信息 -->
<div class="poster-info-section">
<el-descriptions title="海报详情" :column="2" border>
<el-descriptions-item label="海报类型">
<dict-tag :options="zsjy_poster_type" :value="previewData.posterType" />
</el-descriptions-item>
<el-descriptions-item label="所属企业">
{{ enterpriseDetail.enterpriseName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="关联岗位" v-if="previewData.posterType === '2'">
{{ positionDetail.positionName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ previewData.createTime || '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 使用 PosterPreview 组件生成带企业/岗位信息的海报,并支持导出 -->
<PosterPreview
:posterData="{
posterPath: previewData.posterPath,
enterpriseName: enterpriseDetail.enterpriseName || previewData.enterpriseName || '',
positionName: positionDetail.positionName || previewData.positionName || '',
posterType: previewData.posterType
}"
:enterpriseDetail="enterpriseDetail"
:positionDetail="positionDetail"
/>
</div>
<!-- 无海报图片提示 -->
@@ -256,6 +268,7 @@ import { listEnterpriseOptions } from '@/api/zsjy/jobPosition';
import { getEnterprise } from '@/api/zsjy/enterprise';
import { getJobPosition } from '@/api/zsjy/jobPosition';
import { RecruitmentPosterVO, RecruitmentPosterForm, RecruitmentPosterQuery } from '@/api/zsjy/types';
import PosterPreview from '@/components/PosterPreview/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -295,10 +308,11 @@ const queryParams = ref<RecruitmentPosterQuery>({
});
// 表单参数
const form = ref<RecruitmentPosterForm>({
const form = ref<RecruitmentPosterForm & { positionIdList?: Array<number | string> }>({
id: undefined,
enterpriseId: '',
positionId: undefined,
positionIdList: [],
posterPath: '',
posterType: '1'
});
@@ -307,8 +321,17 @@ const form = ref<RecruitmentPosterForm>({
const rules = {
enterpriseId: [{ required: true, message: '请选择所属企业', trigger: 'change' }],
posterType: [{ required: true, message: '请选择海报类型', trigger: 'change' }],
posterPath: [{ required: true, message: '请上传海报图片', trigger: 'change' }]
};
posterPath: [{ required: true, message: '请上传海报图片', trigger: 'change' }],
// 仅在新增岗位海报且岗位多选时生效
positionIdList: [
{
type: 'array',
required: true,
message: '请选择至少一个关联岗位',
trigger: 'change'
}
]
} as const;
// 预览数据
const previewOpen = ref(false);
@@ -378,6 +401,7 @@ const handleEnterpriseChange = (val: number | string) => {
/** 表单企业选择改变 */
const handleFormEnterpriseChange = (val: number | string) => {
form.value.positionId = undefined;
form.value.positionIdList = [];
if (val) {
getFormPositionOptions(val);
} else {
@@ -389,6 +413,7 @@ const handleFormEnterpriseChange = (val: number | string) => {
const handlePosterTypeChange = (val: string) => {
if (val === '1') {
form.value.positionId = undefined;
form.value.positionIdList = [];
}
};
@@ -460,17 +485,48 @@ const handlePreview = async (row: RecruitmentPosterVO) => {
/** 提交按钮 */
const submitForm = () => {
posterFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (form.value.id) {
await updateRecruitmentPoster(form.value);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addRecruitmentPoster(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
if (!valid) {
return;
}
// 编辑模式:保持原有单条更新逻辑
if (form.value.id) {
await updateRecruitmentPoster(form.value);
proxy?.$modal.msgSuccess('修改成功');
open.value = false;
getList();
return;
}
// 新增岗位海报且选择了多个岗位时,批量生成多条记录
if (
form.value.posterType === '2' &&
Array.isArray(form.value.positionIdList) &&
form.value.positionIdList.length > 0
) {
const { positionIdList, ...baseForm } = form.value as RecruitmentPosterForm & {
positionIdList: Array<number | string>;
};
const requests = positionIdList.map((pid) =>
addRecruitmentPoster({
...baseForm,
positionId: pid
})
);
await Promise.all(requests);
proxy?.$modal.msgSuccess(`新增成功(共生成 ${positionIdList.length} 条岗位海报)`);
open.value = false;
getList();
return;
}
// 其他情况(企业海报或单个岗位海报),按原有逻辑新增一条记录
await addRecruitmentPoster(form.value);
proxy?.$modal.msgSuccess('新增成功');
open.value = false;
getList();
});
};
@@ -502,6 +558,7 @@ const resetForm = () => {
id: undefined,
enterpriseId: '',
positionId: undefined,
positionIdList: [],
posterPath: '',
posterType: '1'
};