feat: implement job management for companies in public job fairs
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled

This commit is contained in:
2026-07-15 15:46:15 +08:00
parent f0c841e9c5
commit 2e88885a27
11 changed files with 804 additions and 131 deletions

View File

@@ -243,6 +243,7 @@ export default [
name: '招聘会详情',
path: '/jobfair/public-job-fair/detail',
component: './Jobfair/PublicJobFair/Detail',
access: 'canViewPublicJobFairDetail',
},
{
name: '报名人员',

View File

@@ -4,7 +4,7 @@ set -Eeuo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/shihezi}"
DEPLOY_HOST="${DEPLOY_HOST:-39.98.44.136}"
DEPLOY_HOST="${DEPLOY_HOST:-47.111.103.66}"
DEPLOY_USER="${DEPLOY_USER:-root}"
DEPLOY_SSH_PORT="${DEPLOY_SSH_PORT:-22}"
DEPLOY_PATH="${DEPLOY_PATH:-/opt/service/project/shz-admin/dist}"
@@ -40,7 +40,7 @@ usage() {
默认配置(均可通过环境变量覆盖):
DEPLOY_USER SSH 用户名,默认 root
DEPLOY_PATH 服务器上的前端静态文件目录,默认 /opt/service/project/shz-admin/dist
DEPLOY_HOST SSH 主机,默认 39.98.44.136
DEPLOY_HOST SSH 主机,默认 47.111.103.66
DEPLOY_SSH_PORT SSH 端口,默认 22
BUILD_DIR 本地构建目录,默认 ./shihezi由 config/config.ts 的 outputPath 决定)
YARN_CMD Yarn 命令,默认 yarn

View File

@@ -0,0 +1,44 @@
# shz-admin 前端与现有 SHZ 后端代理配置。
# 安装位置:/etc/nginx/default.d/shz-admin.conf
client_max_body_size 100m;
location = /shihezi {
return 301 /shihezi/;
}
location = /shihezi/index.html {
alias /opt/service/project/shz-admin/dist/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}
location /shihezi/ {
alias /opt/service/project/shz-admin/dist/;
try_files $uri $uri/ /shihezi/index.html;
add_header Cache-Control "no-cache" always;
}
location /api/shihezi/ {
# 对外保留 /api/shihezi/ 前缀,转发到本机后端时剥离该前缀。
proxy_pass http://127.0.0.1:9091/;
proxy_http_version 1.1;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
}
location /data/file/ {
proxy_pass http://127.0.0.1:9091/data/file/;
proxy_http_version 1.1;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}

View File

@@ -5,6 +5,14 @@ import { clearTokenCache } from './utils/tokenCache';
* */
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
const { currentUser } = initialState ?? {};
const roles = Array.isArray(currentUser?.roles) ? (currentUser.roles as unknown[]) : [];
const isEnterprise =
currentUser?.userType === 'view' ||
roles.some((role) => {
if (typeof role === 'string') return role === 'view';
if (!role || typeof role !== 'object') return false;
return (role as { roleKey?: string }).roleKey === 'view';
});
const hasPerms = (perm: string) => {
return matchPermission(initialState?.currentUser?.permissions, perm);
};
@@ -13,6 +21,8 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
};
return {
canAdmin: currentUser && currentUser.access === 'admin',
isEnterprise,
canViewPublicJobFairDetail: !isEnterprise && hasPerms('cms:publicJobFair:query'),
hasPerms,
roleFiler,
};

View File

@@ -1,25 +1,58 @@
import React, { useEffect, useState } from 'react';
import { history, useSearchParams } from '@umijs/max';
import { Button, Card, Collapse, Descriptions, message, Modal, Space, Tag, List } from 'antd';
import { history, useAccess, useSearchParams } from '@umijs/max';
import {
Button,
Card,
Collapse,
Descriptions,
Form,
Input,
List,
message,
Modal,
Radio,
Result,
Space,
Tag,
} from 'antd';
import { PageContainer } from '@ant-design/pro-components';
import { ArrowLeftOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { ArrowLeftOutlined, AuditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import {
getPublicJobFairDetail,
removeCompanyFromJobFair,
removeJobFromJobFair,
reviewPublicJobFairCompany,
} from '@/services/jobfair/publicJobFair';
import CompanySelectModal from '../components/CompanySelectModal';
import JobSelectModal from '../components/JobSelectModal';
type ReviewFormValues = {
reviewStatus: '1' | '2';
reviewRemark?: string;
};
const reviewStatusMap: Record<string, { text: string; color: string }> = {
'0': { text: '待审核', color: 'gold' },
'1': { text: '已通过', color: 'green' },
'2': { text: '已驳回', color: 'red' },
};
const JobFairDetail: React.FC = () => {
const access = useAccess();
const isEnterprise = Boolean(access.isEnterprise);
const [searchParams] = useSearchParams();
const jobFairId = searchParams.get('id') || '';
const [detail, setDetail] = useState<API.PublicJobFair.JobFairItem & { companyList?: API.PublicJobFair.CompanyInfo[] }>();
const [detail, setDetail] = useState<API.PublicJobFair.DetailResult['data']>();
const [loading, setLoading] = useState(false);
const [companyModalOpen, setCompanyModalOpen] = useState(false);
const [jobModalOpen, setJobModalOpen] = useState(false);
const [currentCompany, setCurrentCompany] = useState<API.PublicJobFair.CompanyInfo>();
const [reviewModalOpen, setReviewModalOpen] = useState(false);
const [reviewCompany, setReviewCompany] = useState<API.PublicJobFair.CompanyInfo>();
const [reviewSubmitting, setReviewSubmitting] = useState(false);
const [reviewForm] = Form.useForm<ReviewFormValues>();
const reviewStatus = Form.useWatch('reviewStatus', reviewForm);
const fetchDetail = async () => {
if (!jobFairId) return;
@@ -28,19 +61,23 @@ const JobFairDetail: React.FC = () => {
setLoading(false);
if (res.code === 200) {
setDetail(res.data);
} else {
message.error(res.msg || '招聘会详情获取失败');
}
};
useEffect(() => {
if (!isEnterprise) {
fetchDetail();
}, [jobFairId]);
}
}, [jobFairId, isEnterprise]);
const handleRemoveCompany = (id: string) => {
const handleRemoveCompany = (company: API.PublicJobFair.CompanyInfo) => {
Modal.confirm({
title: '确认移除',
content: '确定移除该企业吗?',
content: `确定移除${company.companyName}」吗?审核未通过的企业不能移除。`,
onOk: async () => {
const res = await removeCompanyFromJobFair(id);
const res = await removeCompanyFromJobFair(company.id);
if (res.code === 200) {
message.success('移除成功');
fetchDetail();
@@ -51,24 +88,64 @@ const JobFairDetail: React.FC = () => {
});
};
const handleRemoveJob = (id: string) => {
const handleRemoveJob = (job: API.PublicJobFair.JobInfo) => {
Modal.confirm({
title: '确认移除',
content: '确定移除该岗位吗?',
title: '确认移除岗位',
content: `确定移除${job.jobTitle}」吗?`,
onOk: async () => {
const res = await removeJobFromJobFair(id);
const res = job.fairRelationId
? await removeJobFromJobFair(job.fairRelationId)
: { code: 500, msg: '未找到招聘会岗位关联' };
if (res.code === 200) {
message.success('移除成功');
message.success('岗位已移除');
fetchDetail();
} else {
message.error(res.msg || '移除失败');
message.error(res.msg || '岗位移除失败');
}
},
});
};
const openReviewModal = (company: API.PublicJobFair.CompanyInfo) => {
setReviewCompany(company);
reviewForm.setFieldsValue({ reviewStatus: '1', reviewRemark: '' });
setReviewModalOpen(true);
};
const submitReview = async (values: ReviewFormValues) => {
if (!reviewCompany) return;
setReviewSubmitting(true);
const res = await reviewPublicJobFairCompany(jobFairId, reviewCompany.id, values);
setReviewSubmitting(false);
if (res.code === 200) {
message.success('审核完成');
setReviewModalOpen(false);
setReviewCompany(undefined);
fetchDetail();
} else {
message.error(res.msg || '审核失败');
}
};
const jobFairTypeMap: Record<string, string> = { '1': '线上', '2': '线下' };
if (isEnterprise) {
return (
<PageContainer>
<Result
status="403"
title="企业用户不能查看招聘会详情"
subTitle="请在招聘会列表完成报名,并在审核通过后使用“编辑报名岗位”。"
extra={
<Button type="primary" onClick={() => history.replace('/jobfair/public-job-fair')}>
</Button>
}
/>
</PageContainer>
);
}
return (
<PageContainer
loading={loading}
@@ -81,11 +158,15 @@ const JobFairDetail: React.FC = () => {
],
}}
>
<Card title="基本信息" style={{ marginBottom: 16 }} extra={
<Card
title="基本信息"
style={{ marginBottom: 16 }}
extra={
<Button onClick={() => history.push(`/jobfair/public-job-fair/signups?id=${jobFairId}`)}>
</Button>
}>
}
>
<Descriptions column={2}>
<Descriptions.Item label="标题">{detail?.jobFairTitle}</Descriptions.Item>
<Descriptions.Item label="类型">
@@ -107,32 +188,67 @@ const JobFairDetail: React.FC = () => {
<Descriptions.Item label="跨域联盟城市" span={2}>
{detail?.crossDomainCities || '-'}
</Descriptions.Item>
<Descriptions.Item label="简介" span={2}>{detail?.jobFairIntroduction}</Descriptions.Item>
<Descriptions.Item label="简介" span={2}>
{detail?.jobFairIntroduction}
</Descriptions.Item>
</Descriptions>
</Card>
<Card
title="参会企业及岗位"
extra={
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCompanyModalOpen(true)}>
access.hasPerms('cms:publicJobFair:company:add') ? (
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCompanyModalOpen(true)}
>
</Button>
) : null
}
>
{detail?.companyList?.length ? (
<Collapse>
{detail.companyList.map((company) => (
{detail.companyList.map((company) => {
const reviewStatusValue = company.reviewStatus || '0';
const reviewStatus = reviewStatusMap[reviewStatusValue] || reviewStatusMap['0'];
const isApproved = reviewStatusValue === '1';
const canReview = access.hasPerms('cms:publicJobFair:review');
const canAdminAddJob = Boolean(
isApproved && access.hasPerms('cms:publicJobFair:job:add'),
);
const canRemoveCompany = Boolean(
isApproved && access.hasPerms('cms:publicJobFair:company:remove'),
);
const canRemoveJob =
isApproved &&
access.hasPerms('cms:publicJobFair:job:remove') &&
(company.jobInfoList || []).length > 0;
return (
<Collapse.Panel
key={company.id}
header={
<Space>
<Space wrap>
<span style={{ fontWeight: 500 }}>{company.companyName}</span>
<Tag>{company.industry}</Tag>
<Tag>{company.scale}</Tag>
<Tag color={reviewStatus.color}>{reviewStatus.text}</Tag>
</Space>
}
extra={
<Space onClick={(e) => e.stopPropagation()}>
{canReview && (
<Button
size="small"
icon={<AuditOutlined />}
onClick={() => openReviewModal(company)}
>
</Button>
)}
{canAdminAddJob && (
<Button
size="small"
icon={<PlusOutlined />}
@@ -143,46 +259,64 @@ const JobFairDetail: React.FC = () => {
>
</Button>
)}
{canRemoveCompany && (
<Button
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => handleRemoveCompany(company.id)}
onClick={() => handleRemoveCompany(company)}
>
</Button>
)}
</Space>
}
>
{company.reviewStatus === '2' && company.reviewRemark && (
<div style={{ marginBottom: 12, color: '#cf1322' }}>
{company.reviewRemark}
</div>
)}
<List
size="small"
dataSource={company.jobInfoList || []}
renderItem={(job) => (
<List.Item
actions={[
...(canRemoveJob
? [
<Button
key="remove"
type="link"
danger
size="small"
onClick={() => handleRemoveJob(job.id!)}
onClick={() => handleRemoveJob(job)}
>
</Button>,
]
: []),
]}
>
<Space>
<Space wrap>
<span>{job.jobTitle}</span>
<Tag color="orange">{job.minSalary}K-{job.maxSalary}K</Tag>
<Tag>{job.education}</Tag>
<Tag>{job.vacancies}</Tag>
<Tag color="orange">
{job.minSalary || job.maxSalary
? `${job.minSalary || 0}-${job.maxSalary || 0} 元/月`
: '面议'}
</Tag>
{job.education && <Tag>{job.education}</Tag>}
{job.experience && <Tag>{job.experience}</Tag>}
{job.vacancies !== undefined && <Tag>{job.vacancies}</Tag>}
</Space>
</List.Item>
)}
locale={{ emptyText: '暂无岗位' }}
/>
</Collapse.Panel>
))}
);
})}
</Collapse>
) : (
<div style={{ textAlign: 'center', padding: 24, color: '#999' }}></div>
@@ -213,6 +347,34 @@ const JobFairDetail: React.FC = () => {
fetchDetail();
}}
/>
<Modal
title={`审核参会企业${reviewCompany ? ` - ${reviewCompany.companyName}` : ''}`}
open={reviewModalOpen}
confirmLoading={reviewSubmitting}
onCancel={() => {
setReviewModalOpen(false);
setReviewCompany(undefined);
}}
onOk={() => reviewForm.submit()}
destroyOnClose
>
<Form form={reviewForm} layout="vertical" onFinish={submitReview}>
<Form.Item name="reviewStatus" label="审核结果" rules={[{ required: true }]}>
<Radio.Group>
<Radio value="1"></Radio>
<Radio value="2"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="reviewRemark"
label="审核意见"
rules={reviewStatus === '2' ? [{ required: true, message: '驳回时请填写原因' }] : []}
>
<Input.TextArea rows={4} placeholder="驳回时请输入原因" />
</Form.Item>
</Form>
</Modal>
</PageContainer>
);
};

View File

@@ -0,0 +1,170 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Button, List, message, Modal, Space, Tag } from 'antd';
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
addCurrentCompanyJob,
getCurrentCompanyJobs,
removeCurrentCompanyJob,
updateCurrentCompanyJob,
type PublicJobFairJobForm,
} from '@/services/jobfair/publicJobFair';
import JobFormModal from './JobFormModal';
interface CompanyJobManageModalProps {
open: boolean;
jobFairId: string;
jobFairTitle?: string;
onCancel: () => void;
}
/** 企业报名通过后,直接在列表弹窗维护自己的报名岗位。 */
const CompanyJobManageModal: React.FC<CompanyJobManageModalProps> = ({
open,
jobFairId,
jobFairTitle,
onCancel,
}) => {
const [jobs, setJobs] = useState<API.PublicJobFair.JobInfo[]>([]);
const [loading, setLoading] = useState(false);
const [jobFormOpen, setJobFormOpen] = useState(false);
const [editingJob, setEditingJob] = useState<API.PublicJobFair.JobInfo>();
const loadJobs = useCallback(async () => {
if (!jobFairId) return;
setLoading(true);
try {
const res = await getCurrentCompanyJobs(jobFairId);
if (res.code === 200) {
setJobs(res.data || []);
} else {
message.error(res.msg || '报名岗位获取失败');
}
} catch {
message.error('报名岗位获取失败,请稍后重试');
} finally {
setLoading(false);
}
}, [jobFairId]);
useEffect(() => {
if (!open) return;
setEditingJob(undefined);
void loadJobs();
}, [open, loadJobs]);
const closeJobForm = () => {
setJobFormOpen(false);
setEditingJob(undefined);
};
const submitJob = async (values: PublicJobFairJobForm) => {
if (editingJob) {
return updateCurrentCompanyJob(jobFairId, editingJob.jobId, values);
}
return addCurrentCompanyJob(jobFairId, values);
};
const removeJob = (job: API.PublicJobFair.JobInfo) => {
Modal.confirm({
title: '确认删除报名岗位',
content: `确定删除「${job.jobTitle}」吗?删除后需要重新新增才能报名该岗位。`,
onOk: async () => {
const res = await removeCurrentCompanyJob(jobFairId, job.jobId);
if (res.code === 200) {
message.success('报名岗位已删除');
loadJobs();
} else {
message.error(res.msg || '报名岗位删除失败');
}
},
});
};
return (
<>
<Modal
title={`编辑报名岗位${jobFairTitle ? ` - ${jobFairTitle}` : ''}`}
open={open}
width={800}
footer={null}
onCancel={() => {
closeJobForm();
onCancel();
}}
destroyOnClose
>
<div style={{ marginBottom: 16, textAlign: 'right' }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditingJob(undefined);
setJobFormOpen(true);
}}
>
</Button>
</div>
<List
loading={loading}
bordered
dataSource={jobs}
locale={{ emptyText: '暂无报名岗位,请先新增岗位' }}
renderItem={(job) => (
<List.Item
actions={[
<Button
key="edit"
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => {
setEditingJob(job);
setJobFormOpen(true);
}}
>
</Button>,
<Button
key="remove"
type="link"
danger
size="small"
icon={<DeleteOutlined />}
onClick={() => removeJob(job)}
>
</Button>,
]}
>
<Space wrap>
<span>{job.jobTitle}</span>
<Tag color="orange">
{job.minSalary || job.maxSalary
? `${job.minSalary || 0}-${job.maxSalary || 0} 元/月`
: '面议'}
</Tag>
{job.education && <Tag>{job.education}</Tag>}
{job.experience && <Tag>{job.experience}</Tag>}
{job.vacancies !== undefined && <Tag>{job.vacancies}</Tag>}
</Space>
</List.Item>
)}
/>
</Modal>
<JobFormModal
open={jobFormOpen}
job={editingJob}
onCancel={closeJobForm}
onSubmit={submitJob}
onSuccess={() => {
closeJobForm();
loadJobs();
}}
/>
</>
);
};
export default CompanyJobManageModal;

View File

@@ -0,0 +1,79 @@
import React, { useEffect } from 'react';
import { ModalForm, ProFormDigit, ProFormText, ProFormTextArea } from '@ant-design/pro-components';
import { Form, message } from 'antd';
import type { PublicJobFairJobForm } from '@/services/jobfair/publicJobFair';
interface JobFormModalProps {
open: boolean;
job?: API.PublicJobFair.JobInfo;
onCancel: () => void;
onSuccess: () => void;
onSubmit: (values: PublicJobFairJobForm) => Promise<API.PublicJobFair.CommonResult>;
}
/** 企业报名岗位新增/编辑表单。企业 ID 由后端从登录用户解析,不接受前端传入。 */
const JobFormModal: React.FC<JobFormModalProps> = ({
open,
job,
onCancel,
onSuccess,
onSubmit,
}) => {
const [form] = Form.useForm<PublicJobFairJobForm>();
useEffect(() => {
if (!open) return;
form.resetFields();
if (job) {
form.setFieldsValue({
jobId: job.jobId,
jobTitle: job.jobTitle,
minSalary: job.minSalary,
maxSalary: job.maxSalary,
education: job.education,
experience: job.experience,
vacancies: job.vacancies,
jobAddress: job.jobAddress,
description: job.description,
jobCategory: job.jobCategory,
type: job.type,
});
}
}, [open, job, form]);
return (
<ModalForm<PublicJobFairJobForm>
title={job ? '编辑报名岗位' : '新增报名岗位'}
form={form}
open={open}
width={680}
modalProps={{ destroyOnClose: true, onCancel }}
onFinish={async (values) => {
const res = await onSubmit({ ...values, jobId: job?.jobId });
if (res.code === 200) {
message.success(job ? '岗位修改成功' : '岗位新增成功');
onSuccess();
return true;
}
message.error(res.msg || '岗位操作失败');
return false;
}}
>
<ProFormText
name="jobTitle"
label="岗位名称"
placeholder="请输入岗位名称"
rules={[{ required: true, message: '请输入岗位名称' }]}
/>
<ProFormDigit name="minSalary" label="最低薪资(元/月)" min={0} />
<ProFormDigit name="maxSalary" label="最高薪资(元/月)" min={0} />
<ProFormText name="education" label="学历要求" placeholder="如:本科" />
<ProFormText name="experience" label="工作经验" placeholder="如1-3年" />
<ProFormDigit name="vacancies" label="招聘人数" min={0} />
<ProFormText name="jobAddress" label="工作地点" />
<ProFormTextArea name="description" label="岗位描述" />
</ModalForm>
);
};
export default JobFormModal;

View File

@@ -1,26 +1,37 @@
import React, { useRef, useState, useEffect } from 'react';
import { useAccess, history } from '@umijs/max';
import { Button, FormInstance, message, Modal } from 'antd';
import { Button, FormInstance, message, Modal, Tag } from 'antd';
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
import { PlusOutlined, DeleteOutlined, FormOutlined, ExportOutlined, EyeOutlined } from '@ant-design/icons';
import {
PlusOutlined,
DeleteOutlined,
FormOutlined,
ExportOutlined,
EyeOutlined,
} from '@ant-design/icons';
import { getDictValueEnum } from '@/services/system/dict';
import DictTag from '@/components/DictTag';
import {
getPublicJobFairList,
getPublicJobFairDetail,
signupPublicJobFair,
addPublicJobFair,
updatePublicJobFair,
deletePublicJobFair,
exportPublicJobFair,
} from '@/services/jobfair/publicJobFair';
import EditModal from './components/EditModal';
import CompanyJobManageModal from './components/CompanyJobManageModal';
const PublicJobFairList: React.FC = () => {
const access = useAccess();
const isEnterprise = Boolean(access.isEnterprise);
const formRef = useRef<FormInstance>();
const actionRef = useRef<ActionType>();
const [modalVisible, setModalVisible] = useState(false);
const [currentRow, setCurrentRow] = useState<API.PublicJobFair.JobFairItem>();
const [companyJobModalOpen, setCompanyJobModalOpen] = useState(false);
const [currentJobFair, setCurrentJobFair] = useState<API.PublicJobFair.JobFairItem>();
const [jobFairTypeEnum, setJobFairTypeEnum] = useState<any>({});
const [yesNoEnum, setYesNoEnum] = useState<any>({});
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@@ -47,6 +58,22 @@ const PublicJobFairList: React.FC = () => {
});
};
const handleSignup = (record: API.PublicJobFair.JobFairItem) => {
Modal.confirm({
title: '报名线上招聘会',
content: `确定报名「${record.jobFairTitle}」吗?报名后需要等待管理员审核。`,
onOk: async () => {
const res = await signupPublicJobFair(record.jobFairId);
if (res.code === 200) {
message.success('报名成功,请等待管理员审核');
actionRef.current?.reload();
} else {
message.error(res.msg || '报名失败');
}
},
});
};
const buildListSearchParams = (values: Record<string, any>): API.PublicJobFair.ListParams => {
const { dateRange, current, pageSize, ...rest } = values || {};
const params = { ...rest } as API.PublicJobFair.ListParams;
@@ -102,6 +129,32 @@ const PublicJobFairList: React.FC = () => {
valueEnum: jobFairTypeEnum,
render: (_, record) => <DictTag enums={jobFairTypeEnum} value={record.jobFairType} />,
},
{
title: '报名审核状态',
dataIndex: 'enterpriseReviewStatus',
hideInSearch: true,
hideInTable: !isEnterprise,
render: (_, record) => {
if (record.isSignUp === undefined) return '-';
if (record.isSignUp !== '1') return <Tag></Tag>;
const statusMap: Record<string, { text: string; color: string }> = {
'0': { text: '待审核', color: 'gold' },
'1': { text: '已通过', color: 'green' },
'2': { text: '已驳回', color: 'red' },
};
const status = statusMap[record.enterpriseReviewStatus || '0'] || statusMap['0'];
return (
<span>
<Tag color={status.color}>{status.text}</Tag>
{record.enterpriseReviewComments && (
<span title={record.enterpriseReviewComments} style={{ color: '#999' }}>
</span>
)}
</span>
);
},
},
{
title: '是否跨域',
dataIndex: 'isCrossDomain',
@@ -130,9 +183,21 @@ const PublicJobFairList: React.FC = () => {
{
title: '操作',
valueType: 'option',
width: 200,
width: 360,
fixed: 'right',
render: (_, record) => [
render: (_, record) => {
const canViewDetail = !isEnterprise && access.hasPerms('cms:publicJobFair:query');
const canSignUp =
isEnterprise &&
access.hasPerms('cms:publicJobFair:signup') &&
(record.canSignUp ?? (record.isSignUp !== '1' || record.enterpriseReviewStatus === '2'));
const canEditCompanyJobs =
isEnterprise &&
access.hasPerms('cms:publicJobFair:job:edit') &&
(record.canEditJobs ?? record.enterpriseReviewStatus === '1');
return [
canViewDetail && (
<Button
key="detail"
type="link"
@@ -141,7 +206,33 @@ const PublicJobFairList: React.FC = () => {
onClick={() => history.push(`/jobfair/public-job-fair/detail?id=${record.jobFairId}`)}
>
</Button>,
</Button>
),
canSignUp && (
<Button
key="signup"
type="link"
size="small"
icon={<PlusOutlined />}
onClick={() => handleSignup(record)}
>
</Button>
),
canEditCompanyJobs && (
<Button
key="companyJobs"
type="link"
size="small"
icon={<FormOutlined />}
onClick={() => {
setCurrentJobFair(record);
setCompanyJobModalOpen(true);
}}
>
</Button>
),
<Button
key="edit"
type="link"
@@ -169,7 +260,8 @@ const PublicJobFairList: React.FC = () => {
>
</Button>,
],
];
},
},
];
@@ -248,6 +340,15 @@ const PublicJobFairList: React.FC = () => {
}
}}
/>
<CompanyJobManageModal
open={companyJobModalOpen}
jobFairId={currentJobFair?.jobFairId || ''}
jobFairTitle={currentJobFair?.jobFairTitle}
onCancel={() => {
setCompanyJobModalOpen(false);
setCurrentJobFair(undefined);
}}
/>
</PageContainer>
);
};

View File

@@ -112,10 +112,11 @@ export default function ThirdPartyRedirect() {
console.log('userType value:', userInfo?.userType, 'type:', typeof userInfo?.userType);
if (menus && menus.length > 0 && userInfo?.userType !== 'common') {
setRemoteMenu(menus);
window.location.replace('http://39.98.44.136:6024/shihezi/management/management/list/index?type=1')
window.location.replace(
`${window.location.origin}/shihezi/management/management/list/index?type=1`,
);
}
}
} else {
message.error('暂时无法加载您的信息');
setIsError(true);

View File

@@ -3,6 +3,28 @@ import { downLoadXlsx } from '@/utils/downloadfile';
const BASE_URL = '/api/cms/publicJobFair';
/** 企业报名审核请求 */
export interface PublicJobFairCompanyReviewParams {
reviewStatus: '0' | '1' | '2';
reviewRemark?: string;
}
/** 企业报名岗位表单 */
export interface PublicJobFairJobForm {
jobId?: number;
jobTitle: string;
minSalary?: number;
maxSalary?: number;
education?: string;
experience?: string;
vacancies?: number;
jobLocation?: string;
jobAddress?: string;
description?: string;
jobCategory?: string;
type?: string;
}
/** 跨域招聘会岗位列表项 */
export interface CrossDomainJobItem {
jobId: number;
@@ -61,6 +83,28 @@ export async function getPublicJobFairDetail(jobFairId: string) {
});
}
/** 企业报名招聘会 */
export async function signupPublicJobFair(jobFairId: string) {
return request<API.PublicJobFair.CommonResult>(`${BASE_URL}/${jobFairId}/company/signup`, {
method: 'POST',
});
}
/** 管理员审核企业报名 */
export async function reviewPublicJobFairCompany(
jobFairId: string,
relationId: string,
data: PublicJobFairCompanyReviewParams,
) {
return request<API.PublicJobFair.CommonResult>(
`${BASE_URL}/${jobFairId}/companies/${relationId}/review`,
{
method: 'PUT',
data,
},
);
}
/** 新增招聘会 */
export async function addPublicJobFair(data: API.PublicJobFair.JobFairForm) {
return request<API.PublicJobFair.CommonResult>(BASE_URL, {
@@ -92,7 +136,6 @@ export async function addCompanyToJobFair(data: API.PublicJobFair.AddCompanyPara
});
}
/** 批量添加企业到招聘会 */
export async function batchAddCompanyToJobFair(data: API.PublicJobFair.AddCompanyParams[]) {
return request<API.PublicJobFair.CommonResult>(`${BASE_URL}/company/batch`, {
@@ -131,8 +174,45 @@ export async function removeJobFromJobFair(id: string) {
});
}
/** 企业新增报名岗位 */
export async function addCurrentCompanyJob(jobFairId: string, data: PublicJobFairJobForm) {
return request<API.PublicJobFair.CommonResult>(`${BASE_URL}/${jobFairId}/company/jobs`, {
method: 'POST',
data,
});
}
/** 企业修改报名岗位 */
export async function updateCurrentCompanyJob(
jobFairId: string,
jobId: number,
data: PublicJobFairJobForm,
) {
return request<API.PublicJobFair.CommonResult>(`${BASE_URL}/${jobFairId}/company/jobs/${jobId}`, {
method: 'PUT',
data,
});
}
/** 企业删除报名岗位 */
export async function removeCurrentCompanyJob(jobFairId: string, jobId: number) {
return request<API.PublicJobFair.CommonResult>(`${BASE_URL}/${jobFairId}/company/jobs/${jobId}`, {
method: 'DELETE',
});
}
/** 查询当前企业在招聘会中已报名的岗位 */
export async function getCurrentCompanyJobs(jobFairId: string) {
return request<API.PublicJobFair.CompanyJobsResult>(`${BASE_URL}/${jobFairId}/company/jobs`, {
method: 'GET',
});
}
/** 查询招聘会报名列表 */
export async function getJobFairSignups(jobFairId: string, params?: { userName?: string; status?: string }) {
export async function getJobFairSignups(
jobFairId: string,
params?: { userName?: string; status?: string },
) {
return request<API.PublicJobFair.SignupListResult>(`${BASE_URL}/signups/${jobFairId}`, {
method: 'GET',
params,

View File

@@ -34,6 +34,12 @@ declare namespace API.PublicJobFair {
isCrossDomain?: string;
crossDomainCities?: string;
createTime?: string;
isSignUp?: string;
canSignUp?: boolean;
canEditJobs?: boolean;
enterpriseId?: string;
enterpriseReviewStatus?: '0' | '1' | '2';
enterpriseReviewComments?: string;
}
/** 招聘会列表响应 */
@@ -47,6 +53,7 @@ declare namespace API.PublicJobFair {
/** 岗位信息 */
export interface JobInfo {
id?: string;
fairRelationId?: string;
jobId: number;
jobTitle: string;
minSalary?: number;
@@ -54,6 +61,10 @@ declare namespace API.PublicJobFair {
education?: string;
experience?: string;
vacancies?: number;
jobAddress?: string;
description?: string;
jobCategory?: string;
type?: string;
}
/** 企业信息 */
@@ -64,10 +75,17 @@ declare namespace API.PublicJobFair {
scale?: string;
industry?: string;
companyType?: string;
reviewStatus?: '0' | '1' | '2';
reviewBy?: string;
reviewTime?: string;
reviewRemark?: string;
canReview?: boolean;
canAddJob?: boolean;
canRemoveCompany?: boolean;
canEditJobs?: boolean;
jobInfoList?: JobInfo[];
}
/** 招聘会详情响应 */
export interface DetailResult {
code: number;
@@ -137,4 +155,11 @@ declare namespace API.PublicJobFair {
msg?: string;
data?: any;
}
/** 当前企业报名岗位响应 */
export interface CompanyJobsResult {
code: number;
msg?: string;
data?: JobInfo[];
}
}