feat: add venue information management and detail view

- Added routes for venue information maintenance and detail views.
- Implemented venue information detail page with data fetching and display.
- Created edit modal for adding and editing venue information with form validation.
- Integrated venue type and line options for selection in the edit modal.
- Enhanced outdoor fair management to include venue binding and details.
- Updated services to handle venue information CRUD operations.
This commit is contained in:
2026-06-25 12:47:53 +08:00
parent 93f39886d9
commit 9e0afffef2
9 changed files with 958 additions and 46 deletions

View File

@@ -1,10 +1,12 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import React, { useCallback, useEffect, useRef, 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 type { OutdoorFairItem } from '@/services/jobportal/outdoorFair';
import { updateOutdoorFair } from '@/services/jobportal/outdoorFair';
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,
@@ -17,6 +19,9 @@ 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';
interface Props {
fairId: number;
fairInfo: OutdoorFairItem;
@@ -31,11 +36,53 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
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 refreshEditModalOptions = useCallback(async () => {
const [typeOptions, regionSelectOptions, venueRes] = await Promise.all([
getDictSelectOption(OUTDOOR_FAIR_TYPE_DICT),
getDictSelectOption(OUTDOOR_FAIR_REGION_DICT),
getVenueInfoList({ current: 1, pageSize: 999 }),
]);
setFairTypeOptions(typeOptions);
setRegionOptions(regionSelectOptions);
if (venueRes.code === 200) {
setVenueOptions(
(venueRes.rows || []).map((item) => ({
label: item.venueName,
value: item.id,
venueAddress: item.venueAddress,
floorCount: item.floorCount,
})),
);
}
}, []);
useEffect(() => {
refreshEditModalOptions();
}, [refreshEditModalOptions]);
const handleCreateDictOption = async (values: OutdoorFairDictForm) => {
const res = await addOutdoorFairDictOption(values);
if (res.code === 200) {
await refreshEditModalOptions();
message.success('字典项新增成功');
return true;
}
message.error(res.msg || '字典项新增失败');
return false;
};
const fairTypeColors: Record<string, string> = {
'综合类': 'blue', '校园招聘': 'green', '行业专场': 'orange',
'高新技术专场': 'purple', '智能制造专场': 'cyan', '文旅专场': 'magenta',
'其他': 'default',
: 'blue',
: 'green',
: 'orange',
: 'purple',
: 'cyan',
: 'magenta',
: 'default',
};
return (
@@ -57,19 +104,38 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
style={{ marginBottom: 16 }}
>
<Descriptions column={3} bordered size="small">
<Descriptions.Item label="招聘会标题" span={3}>{fairInfo.title}</Descriptions.Item>
<Descriptions.Item label="招聘会标题" span={3}>
{fairInfo.title}
</Descriptions.Item>
<Descriptions.Item label="举办单位">{fairInfo.hostUnit}</Descriptions.Item>
<Descriptions.Item label="招聘会类型">
<Tag color={fairTypeColors[fairInfo.fairType] || 'default'}>{fairInfo.fairType}</Tag>
</Descriptions.Item>
<Descriptions.Item label="举办区域">{fairInfo.region}</Descriptions.Item>
<Descriptions.Item label="举办地址" span={2}>{fairInfo.address}</Descriptions.Item>
<Descriptions.Item label="绑定场地">
{fairInfo.venueId && fairInfo.venueName ? (
<Button
type="link"
size="small"
onClick={() => history.push(`/jobfair/venue-info/detail?id=${fairInfo.venueId}`)}
>
{fairInfo.venueName}
</Button>
) : (
'--'
)}
</Descriptions.Item>
<Descriptions.Item label="举办地址" span={2}>
{fairInfo.address}
</Descriptions.Item>
<Descriptions.Item label="展位数量">{fairInfo.boothCount}</Descriptions.Item>
<Descriptions.Item label="举办时间">{fairInfo.holdTime}</Descriptions.Item>
<Descriptions.Item label="截止时间">{fairInfo.endTime}</Descriptions.Item>
<Descriptions.Item label="开放申请">{fairInfo.applyStartTime}</Descriptions.Item>
<Descriptions.Item label="截止申请">{fairInfo.applyEndTime}</Descriptions.Item>
<Descriptions.Item label="线上申请">{fairInfo.onlineApply ? '是' : '否'}</Descriptions.Item>
<Descriptions.Item label="线上申请">
{fairInfo.onlineApply ? '是' : '否'}
</Descriptions.Item>
</Descriptions>
</Card>
@@ -77,11 +143,7 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
<Card
title="参会企业及岗位"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCompanyModalOpen(true)}
>
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCompanyModalOpen(true)}>
</Button>
}
@@ -112,20 +174,32 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
<List.Item
actions={[
<Button
key="edit" type="link" size="small"
onClick={() => { setEditingJob(job); setSelectedCompany(record); setJobModalOpen(true); }}
key="edit"
type="link"
size="small"
onClick={() => {
setEditingJob(job);
setSelectedCompany(record);
setJobModalOpen(true);
}}
>
</Button>,
<Button
key="delete" type="link" size="small" danger
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(); }
if (res.code === 200) {
message.success('删除成功');
actionRef.current?.reload();
}
},
});
}}
@@ -138,7 +212,9 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
title={job.jobTitle}
description={
<Space>
<Tag color="blue"> {job.minSalary}-{job.maxSalary}K</Tag>
<Tag color="blue">
{job.minSalary}-{job.maxSalary}K
</Tag>
<Tag>{job.education}</Tag>
<Tag>{job.experience}</Tag>
<Tag> {job.vacancies} </Tag>
@@ -157,30 +233,61 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
}}
columns={[
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
{ title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true,
render: (_, r: any) => <Tag>{r.industry}</Tag> },
{
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,
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
key="addJob"
type="link"
size="small"
onClick={() => {
setEditingJob(null);
setSelectedCompany(record);
setJobModalOpen(true);
}}
>
</Button>,
<Button key="remove" type="link" size="small" danger
<Button
key="remove"
type="link"
size="small"
danger
onClick={() => {
Modal.confirm({
title: '确认移除', content: `确定移除「${record.companyName}」吗?`,
title: '确认移除',
content: `确定移除「${record.companyName}」吗?`,
onOk: async () => {
const res = await removeParticipatingCompany(record.id);
if (res.code === 200) { message.success('移除成功'); actionRef.current?.reload(); }
if (res.code === 200) {
message.success('移除成功');
actionRef.current?.reload();
}
},
});
}}
@@ -197,6 +304,10 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
<EditModal
open={editModalOpen}
values={fairInfo}
fairTypeOptions={fairTypeOptions}
regionOptions={regionOptions}
venueOptions={venueOptions}
onCreateDictOption={handleCreateDictOption}
onCancel={() => setEditModalOpen(false)}
onSubmit={async (values) => {
const res = await updateOutdoorFair(values as any);
@@ -215,7 +326,10 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
open={companyModalOpen}
fairId={fairId}
onCancel={() => setCompanyModalOpen(false)}
onSuccess={() => { setCompanyModalOpen(false); actionRef.current?.reload(); }}
onSuccess={() => {
setCompanyModalOpen(false);
actionRef.current?.reload();
}}
/>
{/* 添加/编辑岗位弹窗 */}
@@ -224,8 +338,16 @@ const FairInfoTab: React.FC<Props> = ({ fairId, fairInfo, onFairUpdate }) => {
fairId={fairId}
company={selectedCompany}
job={editingJob}
onCancel={() => { setJobModalOpen(false); setEditingJob(null); setSelectedCompany(null); }}
onSuccess={() => { setJobModalOpen(false); setEditingJob(null); actionRef.current?.reload(); }}
onCancel={() => {
setJobModalOpen(false);
setEditingJob(null);
setSelectedCompany(null);
}}
onSuccess={() => {
setJobModalOpen(false);
setEditingJob(null);
actionRef.current?.reload();
}}
/>
</div>
);