feat: implement participating companies and jobs tabs in outdoor fair detail
- Add ParticipatingCompaniesTab component to manage participating companies. - Add ParticipatingJobsTab component to manage jobs associated with participating companies. - Remove StatisticsTab component as it is no longer needed. - Update OutdoorFairDetail to include new tabs for participating companies and jobs. - Modify API services to support fetching and managing participating companies and jobs. - Update types to reflect changes in job and company data structures.
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button, Modal, Tag, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import {
|
||||
getParticipatingCompanies,
|
||||
getParticipatingJobDetail,
|
||||
getParticipatingJobs,
|
||||
removeJobFromCompany,
|
||||
removeParticipatingCompany,
|
||||
} from '@/services/jobportal/outdoorFairDetail';
|
||||
import CompanyAddModal from './CompanyAddModal';
|
||||
import JobEditModal from './JobEditModal';
|
||||
|
||||
interface Props {
|
||||
fairId: number;
|
||||
}
|
||||
|
||||
const ParticipatingCompaniesTab: React.FC<Props> = ({ fairId }) => {
|
||||
const companyActionRef = useRef<ActionType>();
|
||||
const jobActionRef = useRef<ActionType>();
|
||||
const [companyModalOpen, setCompanyModalOpen] = useState(false);
|
||||
const [jobListModalOpen, setJobListModalOpen] = useState(false);
|
||||
const [jobEditModalOpen, setJobEditModalOpen] = useState(false);
|
||||
const [selectedCompany, setSelectedCompany] =
|
||||
useState<API.OutdoorFairDetail.ParticipatingCompany | null>(null);
|
||||
const [editingJob, setEditingJob] = useState<API.OutdoorFairDetail.PostedJob | null>(null);
|
||||
const [educationEnum, setEducationEnum] = useState<any>({});
|
||||
const [experienceEnum, setExperienceEnum] = useState<any>({});
|
||||
const [areaEnum, setAreaEnum] = useState<any>({});
|
||||
const [jobTypeEnum, setJobTypeEnum] = useState<any>({});
|
||||
const [scaleEnum, setScaleEnum] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
getDictValueEnum('education', true, true),
|
||||
getDictValueEnum('experience', true, true),
|
||||
getDictValueEnum('area', true, true),
|
||||
getDictValueEnum('job_type', true, true),
|
||||
getDictValueEnum('scale', true, true),
|
||||
]).then(([education, experience, area, jobType, scale]) => {
|
||||
setEducationEnum(education);
|
||||
setExperienceEnum(experience);
|
||||
setAreaEnum(area);
|
||||
setJobTypeEnum(jobType);
|
||||
setScaleEnum(scale);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const openJobEditor = async (job?: API.OutdoorFairDetail.PostedJob) => {
|
||||
if (job?.jobId) {
|
||||
const res = await getParticipatingJobDetail(fairId, job.jobId);
|
||||
if (res.code !== 200) {
|
||||
message.error(res.msg || '岗位详情加载失败');
|
||||
return;
|
||||
}
|
||||
setEditingJob(res.data);
|
||||
} else {
|
||||
setEditingJob(null);
|
||||
}
|
||||
setJobEditModalOpen(true);
|
||||
};
|
||||
|
||||
const jobColumns: ProColumns<API.OutdoorFairDetail.PostedJob>[] = [
|
||||
{ title: '岗位名称', dataIndex: 'jobTitle', ellipsis: true },
|
||||
{
|
||||
title: '薪资范围',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => `${record.minSalary}-${record.maxSalary} 元/月`,
|
||||
},
|
||||
{
|
||||
title: '学历要求',
|
||||
dataIndex: 'education',
|
||||
valueType: 'select',
|
||||
valueEnum: educationEnum,
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '工作经验',
|
||||
dataIndex: 'experience',
|
||||
valueType: 'select',
|
||||
valueEnum: experienceEnum,
|
||||
width: 110,
|
||||
},
|
||||
{ title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 },
|
||||
{
|
||||
title: '发布状态',
|
||||
dataIndex: 'isPublish',
|
||||
hideInSearch: true,
|
||||
width: 90,
|
||||
render: (_, record) => (
|
||||
<Tag color={record.isPublish === 1 ? 'green' : 'red'}>
|
||||
{record.isPublish === 1 ? '已发布' : '未发布'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 130,
|
||||
render: (_, record) => [
|
||||
<Button key="edit" type="link" size="small" onClick={() => openJobEditor(record)}>
|
||||
编辑
|
||||
</Button>,
|
||||
<Button
|
||||
key="delete"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
onClick={() =>
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定删除岗位「${record.jobTitle}」吗?`,
|
||||
onOk: async () => {
|
||||
const res = await removeJobFromCompany(fairId, record.jobId!);
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功');
|
||||
jobActionRef.current?.reload();
|
||||
companyActionRef.current?.reload();
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
删除
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProTable<API.OutdoorFairDetail.ParticipatingCompany>
|
||||
actionRef={companyActionRef}
|
||||
rowKey="id"
|
||||
headerTitle="参会企业"
|
||||
search={{ labelWidth: 'auto' }}
|
||||
options={false}
|
||||
pagination={{ pageSize: 10 }}
|
||||
toolBarRender={() => [
|
||||
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => setCompanyModalOpen(true)}>
|
||||
添加企业
|
||||
</Button>,
|
||||
]}
|
||||
request={async (params) => {
|
||||
const res = await getParticipatingCompanies(fairId, {
|
||||
current: params.current,
|
||||
pageSize: params.pageSize,
|
||||
companyName: params.companyName,
|
||||
});
|
||||
return { data: res.rows || [], total: res.total || 0, success: res.code === 200 };
|
||||
}}
|
||||
columns={[
|
||||
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
|
||||
{
|
||||
title: '行业',
|
||||
dataIndex: 'industry',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
const v = record.industry;
|
||||
if (!v) return '--';
|
||||
// 行业分类 id 均为 6 位编号,纯数字裸码(如 4、6)无对应分类项,兜底为“其他”
|
||||
if (/^\d+$/.test(String(v))) return <Tag>其他</Tag>;
|
||||
return <Tag>{v}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '规模',
|
||||
dataIndex: 'scale',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
const v = record.scale;
|
||||
if (v === undefined || v === null || v === '') return '其他';
|
||||
// 规模为字典码,渲染为标签;匹配不到时兜底“其他”,不显示裸数字
|
||||
const label = scaleEnum[v]?.label || scaleEnum[v]?.text;
|
||||
return label ? <Tag>{label}</Tag> : '其他';
|
||||
},
|
||||
},
|
||||
{ title: '联系人', dataIndex: 'contactPerson', hideInSearch: true },
|
||||
{ title: '联系电话', dataIndex: 'contactPhone', hideInSearch: true },
|
||||
{
|
||||
title: '展位号',
|
||||
dataIndex: 'boothNumber',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => record.boothNumber || '--',
|
||||
},
|
||||
{ title: '岗位数', dataIndex: 'jobCount', hideInSearch: true, width: 80 },
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 220,
|
||||
render: (_, record) => [
|
||||
<Button
|
||||
key="jobs"
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setSelectedCompany(record);
|
||||
setJobListModalOpen(true);
|
||||
}}
|
||||
>
|
||||
查看岗位
|
||||
</Button>,
|
||||
<Button
|
||||
key="addJob"
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setSelectedCompany(record);
|
||||
openJobEditor();
|
||||
}}
|
||||
>
|
||||
添加岗位
|
||||
</Button>,
|
||||
<Button
|
||||
key="remove"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
onClick={() =>
|
||||
Modal.confirm({
|
||||
title: '确认移除',
|
||||
content: `确定移除「${record.companyName}」吗?`,
|
||||
onOk: async () => {
|
||||
const res = await removeParticipatingCompany(record.id, fairId);
|
||||
if (res.code === 200) {
|
||||
message.success('移除成功');
|
||||
companyActionRef.current?.reload();
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
移除
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={selectedCompany ? `${selectedCompany.companyName} — 发布岗位` : '企业岗位'}
|
||||
open={jobListModalOpen}
|
||||
width={1100}
|
||||
footer={null}
|
||||
destroyOnClose
|
||||
onCancel={() => setJobListModalOpen(false)}
|
||||
>
|
||||
{selectedCompany && (
|
||||
<ProTable<API.OutdoorFairDetail.PostedJob>
|
||||
actionRef={jobActionRef}
|
||||
rowKey="jobId"
|
||||
search={{ labelWidth: 'auto' }}
|
||||
options={false}
|
||||
pagination={{ pageSize: 10 }}
|
||||
toolBarRender={() => [
|
||||
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => openJobEditor()}>
|
||||
添加岗位
|
||||
</Button>,
|
||||
]}
|
||||
request={async (params) => {
|
||||
const res = await getParticipatingJobs(fairId, {
|
||||
companyId: selectedCompany.companyId,
|
||||
current: params.current,
|
||||
pageSize: params.pageSize,
|
||||
jobTitle: params.jobTitle,
|
||||
education: params.education,
|
||||
experience: params.experience,
|
||||
});
|
||||
return { data: res.rows || [], total: res.total || 0, success: res.code === 200 };
|
||||
}}
|
||||
columns={jobColumns}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<CompanyAddModal
|
||||
open={companyModalOpen}
|
||||
fairId={fairId}
|
||||
onCancel={() => setCompanyModalOpen(false)}
|
||||
onSuccess={() => {
|
||||
setCompanyModalOpen(false);
|
||||
companyActionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
|
||||
<JobEditModal
|
||||
open={jobEditModalOpen}
|
||||
fairId={fairId}
|
||||
company={selectedCompany}
|
||||
job={editingJob}
|
||||
educationEnum={educationEnum}
|
||||
experienceEnum={experienceEnum}
|
||||
areaEnum={areaEnum}
|
||||
jobTypeEnum={jobTypeEnum}
|
||||
onCancel={() => {
|
||||
setJobEditModalOpen(false);
|
||||
setEditingJob(null);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setJobEditModalOpen(false);
|
||||
setEditingJob(null);
|
||||
jobActionRef.current?.reload();
|
||||
companyActionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParticipatingCompaniesTab;
|
||||
Reference in New Issue
Block a user