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:
@@ -1,54 +1,43 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { history, useAccess } from '@umijs/max';
|
||||
import { Button, Card, Descriptions, List, Tag, message, Modal, Space } from 'antd';
|
||||
import { ActionType, ProTable } from '@ant-design/pro-components';
|
||||
import { PlusOutlined, DeleteOutlined, FormOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Descriptions, Image, Tag, message } from 'antd';
|
||||
import { FormOutlined } from '@ant-design/icons';
|
||||
import type { OutdoorFairDictForm, OutdoorFairItem } from '@/services/jobportal/outdoorFair';
|
||||
import { addOutdoorFairDictOption, updateOutdoorFair } from '@/services/jobportal/outdoorFair';
|
||||
import { getVenueInfoList } from '@/services/jobportal/venueInfo';
|
||||
import { getDictSelectOption } from '@/services/system/dict';
|
||||
import {
|
||||
getParticipatingCompanies,
|
||||
addParticipatingCompany,
|
||||
removeParticipatingCompany,
|
||||
addJobForCompany,
|
||||
updateJobForCompany,
|
||||
removeJobFromCompany,
|
||||
} from '@/services/jobportal/outdoorFairDetail';
|
||||
import { getVenueInfo, getVenueInfoList } from '@/services/jobportal/venueInfo';
|
||||
import type { VenueInfoItem } from '@/services/jobportal/venueInfo';
|
||||
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
||||
import EditModal from '@/pages/Jobfair/Outdoorfair/components/EditModal';
|
||||
import CompanyAddModal from './CompanyAddModal';
|
||||
import JobEditModal from './JobEditModal';
|
||||
|
||||
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
||||
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
||||
const VENUE_INFO_TYPE_DICT = 'venue_info_type';
|
||||
|
||||
interface Props {
|
||||
fairId: number;
|
||||
fairInfo: OutdoorFairItem;
|
||||
refreshKey?: number;
|
||||
onFairUpdate: () => void;
|
||||
}
|
||||
|
||||
const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpdate }) => {
|
||||
const FairInfoTab: React.FC<Props> = ({ fairInfo, onFairUpdate }) => {
|
||||
const access = useAccess();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [companyModalOpen, setCompanyModalOpen] = useState(false);
|
||||
const [jobModalOpen, setJobModalOpen] = useState(false);
|
||||
const [selectedCompany, setSelectedCompany] = useState<any>(null);
|
||||
const [editingJob, setEditingJob] = useState<any>(null);
|
||||
const [fairTypeOptions, setFairTypeOptions] = useState<any[]>([]);
|
||||
const [regionOptions, setRegionOptions] = useState<any[]>([]);
|
||||
const [venueOptions, setVenueOptions] = useState<any[]>([]);
|
||||
const [venueDetail, setVenueDetail] = useState<VenueInfoItem>();
|
||||
const [venueTypeValueEnum, setVenueTypeValueEnum] = useState<Record<string, any>>({});
|
||||
|
||||
const refreshEditModalOptions = useCallback(async () => {
|
||||
const [typeOptions, regionSelectOptions, venueRes] = await Promise.all([
|
||||
const [typeOptions, regionSelectOptions, venueRes, venueTypeValueEnum] = await Promise.all([
|
||||
getDictSelectOption(OUTDOOR_FAIR_TYPE_DICT),
|
||||
getDictSelectOption(OUTDOOR_FAIR_REGION_DICT),
|
||||
getVenueInfoList({ current: 1, pageSize: 999 }),
|
||||
getDictValueEnum(VENUE_INFO_TYPE_DICT),
|
||||
]);
|
||||
setFairTypeOptions(typeOptions);
|
||||
setRegionOptions(regionSelectOptions);
|
||||
setVenueTypeValueEnum(venueTypeValueEnum);
|
||||
if (venueRes.code === 200) {
|
||||
setVenueOptions(
|
||||
(venueRes.rows || []).map((item) => ({
|
||||
@@ -65,9 +54,26 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpda
|
||||
refreshEditModalOptions();
|
||||
}, [refreshEditModalOptions]);
|
||||
|
||||
// 拉取绑定场地的详细信息
|
||||
useEffect(() => {
|
||||
actionRef.current?.reload();
|
||||
}, [refreshKey]);
|
||||
const fetchVenueDetail = async () => {
|
||||
if (!fairInfo.venueId) {
|
||||
setVenueDetail(undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getVenueInfo(fairInfo.venueId);
|
||||
if (res.code === 200 && res.data) {
|
||||
setVenueDetail(res.data);
|
||||
} else {
|
||||
setVenueDetail(undefined);
|
||||
}
|
||||
} catch {
|
||||
setVenueDetail(undefined);
|
||||
}
|
||||
};
|
||||
fetchVenueDetail();
|
||||
}, [fairInfo.venueId]);
|
||||
|
||||
const handleCreateDictOption = async (values: OutdoorFairDictForm) => {
|
||||
const res = await addOutdoorFairDictOption(values);
|
||||
@@ -91,8 +97,7 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpda
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 基本信息卡片 */}
|
||||
<>
|
||||
<Card
|
||||
title="基本信息"
|
||||
extra={
|
||||
@@ -106,7 +111,6 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpda
|
||||
编辑信息
|
||||
</Button>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Descriptions column={3} bordered size="small">
|
||||
<Descriptions.Item label="招聘会标题" span={3}>
|
||||
@@ -141,171 +145,66 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpda
|
||||
<Descriptions.Item label="线上申请">
|
||||
{fairInfo.onlineApply ? '是' : '否'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="招聘会照片" span={3}>
|
||||
{fairInfo.photoUrl ? (
|
||||
<Image
|
||||
src={fairInfo.photoUrl}
|
||||
alt={fairInfo.title}
|
||||
width={240}
|
||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
{/* 参会企业及岗位 */}
|
||||
<Card
|
||||
title="参会企业及岗位"
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCompanyModalOpen(true)}>
|
||||
添加企业
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<ProTable
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
search={false}
|
||||
options={false}
|
||||
pagination={{ pageSize: 10 }}
|
||||
request={async (params) => {
|
||||
const res = await getParticipatingCompanies(fairId, {
|
||||
current: params.current,
|
||||
pageSize: params.pageSize,
|
||||
companyName: params.companyName,
|
||||
});
|
||||
return { data: res.rows, total: res.total, success: true };
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<div style={{ padding: '0 24px' }}>
|
||||
{record.jobList.length === 0 ? (
|
||||
<p style={{ color: '#999' }}>暂无岗位</p>
|
||||
) : (
|
||||
<List
|
||||
dataSource={record.jobList}
|
||||
renderItem={(job: any) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button
|
||||
key="edit"
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setEditingJob(job);
|
||||
setSelectedCompany(record);
|
||||
setJobModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>,
|
||||
<Button
|
||||
key="delete"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定删除岗位「${job.jobTitle}」吗?`,
|
||||
onOk: async () => {
|
||||
const res = await removeJobFromCompany(job.id);
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功');
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={job.jobTitle}
|
||||
description={
|
||||
<Space>
|
||||
<Tag color="blue">
|
||||
薪资 {job.minSalary}-{job.maxSalary}K
|
||||
</Tag>
|
||||
<Tag>{job.education}</Tag>
|
||||
<Tag>{job.experience}</Tag>
|
||||
<Tag>招聘 {job.vacancies} 人</Tag>
|
||||
<Tag color={job.status === 'active' ? 'green' : 'red'}>
|
||||
{job.status === 'active' ? '在招' : '已关闭'}
|
||||
</Tag>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
columns={[
|
||||
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
|
||||
{
|
||||
title: '行业',
|
||||
dataIndex: 'industry',
|
||||
width: 100,
|
||||
hideInSearch: true,
|
||||
render: (_, r: any) => <Tag>{r.industry}</Tag>,
|
||||
},
|
||||
{ title: '规模', dataIndex: 'scale', width: 110, hideInSearch: true },
|
||||
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
|
||||
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
|
||||
{
|
||||
title: '展位号',
|
||||
dataIndex: 'boothNumber',
|
||||
width: 80,
|
||||
hideInSearch: true,
|
||||
render: (_, r: any) => r.boothNumber || '--',
|
||||
},
|
||||
{
|
||||
title: '岗位数',
|
||||
width: 80,
|
||||
hideInSearch: true,
|
||||
render: (_, r: any) => r.jobList?.length || 0,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 200,
|
||||
render: (_, record: any) => [
|
||||
<Button
|
||||
key="addJob"
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setEditingJob(null);
|
||||
setSelectedCompany(record);
|
||||
setJobModalOpen(true);
|
||||
}}
|
||||
>
|
||||
添加岗位
|
||||
</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('移除成功');
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
移除
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card title="场地信息" style={{ marginTop: 16 }}>
|
||||
{venueDetail ? (
|
||||
<Descriptions column={3} bordered size="small">
|
||||
<Descriptions.Item label="场地名称" span={3}>
|
||||
{venueDetail.venueName || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地类型">
|
||||
{venueTypeValueEnum[venueDetail.venueType]?.text ||
|
||||
venueTypeValueEnum[venueDetail.venueType]?.label ||
|
||||
venueDetail.venueType ||
|
||||
'--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地面积">
|
||||
{venueDetail.venueArea != null ? `${venueDetail.venueArea} ㎡` : '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="展位数量">
|
||||
{venueDetail.floorCount ?? '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地地址" span={2}>
|
||||
{venueDetail.venueAddress || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="联系电话">
|
||||
{venueDetail.contactPhone || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="乘坐线路" span={3}>
|
||||
{venueDetail.routeLineNames || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="经办日期">
|
||||
{venueDetail.handleDate || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="经办机构">
|
||||
{venueDetail.handleOrg || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="经办人">
|
||||
{venueDetail.handler || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="备注" span={3}>
|
||||
{venueDetail.remark || '--'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
) : (
|
||||
<div style={{ color: '#999' }}>该招聘会未绑定场地信息</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 编辑招聘会基本信息弹窗 */}
|
||||
<EditModal
|
||||
open={editModalOpen}
|
||||
values={fairInfo}
|
||||
@@ -325,36 +224,7 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, refreshKey, onFairUpda
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 添加企业弹窗 */}
|
||||
<CompanyAddModal
|
||||
open={companyModalOpen}
|
||||
fairId={fairId}
|
||||
onCancel={() => setCompanyModalOpen(false)}
|
||||
onSuccess={() => {
|
||||
setCompanyModalOpen(false);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 添加/编辑岗位弹窗 */}
|
||||
<JobEditModal
|
||||
open={jobModalOpen}
|
||||
fairId={fairId}
|
||||
company={selectedCompany}
|
||||
job={editingJob}
|
||||
onCancel={() => {
|
||||
setJobModalOpen(false);
|
||||
setEditingJob(null);
|
||||
setSelectedCompany(null);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setJobModalOpen(false);
|
||||
setEditingJob(null);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user