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:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ModalForm,
|
||||
ProForm,
|
||||
ProFormDigit,
|
||||
ProFormDateTimePicker,
|
||||
ProFormSwitch,
|
||||
ProFormSelect,
|
||||
@@ -22,6 +21,7 @@ interface EditModalProps {
|
||||
values?: OutdoorFairItem;
|
||||
fairTypeOptions: any[];
|
||||
regionOptions: any[];
|
||||
venueOptions: any[];
|
||||
onCreateDictOption: (values: OutdoorFairDictForm) => Promise<boolean>;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: OutdoorFairForm) => Promise<void>;
|
||||
@@ -39,6 +39,7 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
values,
|
||||
fairTypeOptions,
|
||||
regionOptions,
|
||||
venueOptions,
|
||||
onCreateDictOption,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
@@ -66,9 +67,9 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
};
|
||||
|
||||
const renderDropdownWithCreate = (
|
||||
menu: React.ReactNode,
|
||||
menu: React.ReactElement,
|
||||
target: DictAddTarget,
|
||||
): React.ReactNode => (
|
||||
): React.ReactElement => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
@@ -150,6 +151,8 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
}}
|
||||
>
|
||||
<ProFormText name="id" hidden />
|
||||
<ProFormText name="venueName" hidden />
|
||||
<ProFormText name="boothCount" hidden />
|
||||
<ProFormText
|
||||
name="photoUrl"
|
||||
hidden
|
||||
@@ -207,15 +210,26 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
rules={[{ required: true, message: '请选择举办区域' }]}
|
||||
placeholder="请选择举办区域"
|
||||
/>
|
||||
<ProFormDigit
|
||||
name="boothCount"
|
||||
label="展位数量"
|
||||
width="md"
|
||||
min={1}
|
||||
rules={[{ required: true, message: '请输入展位数量' }]}
|
||||
placeholder="请输入展位数量"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProFormSelect
|
||||
name="venueId"
|
||||
label="绑定场地"
|
||||
options={venueOptions}
|
||||
fieldProps={{
|
||||
showSearch: true,
|
||||
optionFilterProp: 'label',
|
||||
onChange: (value) => {
|
||||
const selectedVenue = venueOptions.find((option) => option.value === value);
|
||||
form.setFieldValue('venueName', selectedVenue?.label);
|
||||
form.setFieldValue('boothCount', selectedVenue?.floorCount ?? 0);
|
||||
if (selectedVenue?.venueAddress) {
|
||||
form.setFieldValue('address', selectedVenue.venueAddress);
|
||||
}
|
||||
},
|
||||
}}
|
||||
rules={[{ required: true, message: '请选择绑定场地' }]}
|
||||
placeholder="请选择场地"
|
||||
/>
|
||||
<ProFormText
|
||||
name="address"
|
||||
label="举办地址"
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
OutdoorFairDictForm,
|
||||
} from '@/services/jobportal/outdoorFair';
|
||||
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
||||
import { getVenueInfoList } from '@/services/jobportal/venueInfo';
|
||||
import EditModal from './components/EditModal';
|
||||
|
||||
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
||||
@@ -31,6 +32,7 @@ const OutdoorFairList: React.FC = () => {
|
||||
const [fairTypeOptions, setFairTypeOptions] = useState<any[]>([]);
|
||||
const [regionValueEnum, setRegionValueEnum] = useState<Record<string, any>>({});
|
||||
const [regionOptions, setRegionOptions] = useState<any[]>([]);
|
||||
const [venueOptions, setVenueOptions] = useState<any[]>([]);
|
||||
|
||||
const refreshDictOptions = useCallback(async () => {
|
||||
const [typeValueEnum, typeOptions, regionEnum, regionSelectOptions] = await Promise.all([
|
||||
@@ -49,6 +51,24 @@ const OutdoorFairList: React.FC = () => {
|
||||
refreshDictOptions();
|
||||
}, [refreshDictOptions]);
|
||||
|
||||
const refreshVenueOptions = useCallback(async () => {
|
||||
const res = await getVenueInfoList({ current: 1, pageSize: 999 });
|
||||
if (res.code === 200) {
|
||||
setVenueOptions(
|
||||
(res.rows || []).map((item) => ({
|
||||
label: item.venueName,
|
||||
value: item.id,
|
||||
venueAddress: item.venueAddress,
|
||||
floorCount: item.floorCount,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshVenueOptions();
|
||||
}, [refreshVenueOptions]);
|
||||
|
||||
const handleCreateDictOption = async (values: OutdoorFairDictForm) => {
|
||||
const res = await addOutdoorFairDictOption(values);
|
||||
if (res.code === 200) {
|
||||
@@ -101,6 +121,24 @@ const OutdoorFairList: React.FC = () => {
|
||||
valueType: 'select',
|
||||
valueEnum: regionValueEnum,
|
||||
},
|
||||
{
|
||||
title: '绑定场地',
|
||||
dataIndex: 'venueName',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
render: (_, record) =>
|
||||
record.venueId && record.venueName ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => history.push(`/jobfair/venue-info/detail?id=${record.venueId}`)}
|
||||
>
|
||||
{record.venueName}
|
||||
</Button>
|
||||
) : (
|
||||
'--'
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '举办地址',
|
||||
dataIndex: 'address',
|
||||
@@ -216,6 +254,7 @@ const OutdoorFairList: React.FC = () => {
|
||||
hostUnit: params.hostUnit,
|
||||
fairType: params.fairType,
|
||||
region: params.region,
|
||||
venueId: params.venueId,
|
||||
} as OutdoorFairListParams);
|
||||
return { data: res.rows, total: res.total, success: true };
|
||||
}}
|
||||
@@ -239,6 +278,7 @@ const OutdoorFairList: React.FC = () => {
|
||||
values={currentRow}
|
||||
fairTypeOptions={fairTypeOptions}
|
||||
regionOptions={regionOptions}
|
||||
venueOptions={venueOptions}
|
||||
onCreateDictOption={handleCreateDictOption}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
|
||||
83
src/pages/Jobfair/Venueinfo/Detail/index.tsx
Normal file
83
src/pages/Jobfair/Venueinfo/Detail/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { history, useSearchParams } from '@umijs/max';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Button, Card, Descriptions, Spin, Tag, message } from 'antd';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { getVenueInfo } from '@/services/jobportal/venueInfo';
|
||||
import type { VenueInfoItem } from '@/services/jobportal/venueInfo';
|
||||
|
||||
const VenueInfoDetail: React.FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const venueId = Number(searchParams.get('id'));
|
||||
const [venueInfo, setVenueInfo] = useState<VenueInfoItem>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVenueInfo = async () => {
|
||||
if (!venueId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getVenueInfo(venueId);
|
||||
if (res.code === 200 && res.data) {
|
||||
setVenueInfo(res.data);
|
||||
} else {
|
||||
message.error(res.msg || '未找到该场地信息');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchVenueInfo();
|
||||
}, [venueId]);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
header={{
|
||||
title: venueInfo ? `场地详情 — ${venueInfo.venueName}` : '场地详情',
|
||||
onBack: () => history.back(),
|
||||
extra: [
|
||||
<Button key="back" icon={<ArrowLeftOutlined />} onClick={() => history.back()}>
|
||||
返回
|
||||
</Button>,
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
{venueInfo && (
|
||||
<Card>
|
||||
<Descriptions column={3} bordered size="small">
|
||||
<Descriptions.Item label="场地类型">
|
||||
<Tag color="blue">{venueInfo.venueType}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地名称" span={2}>
|
||||
{venueInfo.venueName}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地面积">{venueInfo.venueArea ?? '--'}</Descriptions.Item>
|
||||
<Descriptions.Item label="展位数量">{venueInfo.floorCount ?? '--'}</Descriptions.Item>
|
||||
<Descriptions.Item label="联系电话">
|
||||
{venueInfo.contactPhone || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="场地地址" span={3}>
|
||||
{venueInfo.venueAddress}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="乘坐线路" span={3}>
|
||||
{venueInfo.routeLineNames || '--'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="经办日期">{venueInfo.handleDate || '--'}</Descriptions.Item>
|
||||
<Descriptions.Item label="经办机构">{venueInfo.handleOrg || '--'}</Descriptions.Item>
|
||||
<Descriptions.Item label="经办人">{venueInfo.handler || '--'}</Descriptions.Item>
|
||||
<Descriptions.Item label="备注" span={3}>
|
||||
{venueInfo.remark || '--'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
)}
|
||||
</Spin>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default VenueInfoDetail;
|
||||
231
src/pages/Jobfair/Venueinfo/components/EditModal.tsx
Normal file
231
src/pages/Jobfair/Venueinfo/components/EditModal.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ModalForm,
|
||||
ProForm,
|
||||
ProFormDatePicker,
|
||||
ProFormDigit,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProFormTextArea,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button, Divider, Form, Input, Modal, Space } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import type {
|
||||
VenueInfoDictForm,
|
||||
VenueInfoForm,
|
||||
VenueInfoItem,
|
||||
} from '@/services/jobportal/venueInfo';
|
||||
|
||||
interface EditModalProps {
|
||||
open: boolean;
|
||||
values?: VenueInfoItem;
|
||||
venueTypeOptions: any[];
|
||||
lineOptions: any[];
|
||||
defaultHandler?: string;
|
||||
onCreateDictOption: (values: VenueInfoDictForm) => Promise<boolean>;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: VenueInfoForm) => Promise<void>;
|
||||
}
|
||||
|
||||
const VENUE_TYPE_DICT = 'venue_info_type';
|
||||
|
||||
const splitRouteLineIds = (routeLineIds?: string) => {
|
||||
if (!routeLineIds) {
|
||||
return [];
|
||||
}
|
||||
return routeLineIds
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const EditModal: React.FC<EditModalProps> = ({
|
||||
open,
|
||||
values,
|
||||
venueTypeOptions,
|
||||
lineOptions,
|
||||
defaultHandler,
|
||||
onCreateDictOption,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [dictModalOpen, setDictModalOpen] = useState(false);
|
||||
const [dictInputValue, setDictInputValue] = useState('');
|
||||
const [dictSubmitLoading, setDictSubmitLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
form.resetFields();
|
||||
if (values) {
|
||||
form.setFieldsValue({
|
||||
...values,
|
||||
routeLineIds: splitRouteLineIds(values.routeLineIds),
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
handleDate: new Date(),
|
||||
handler: defaultHandler,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [open, values, defaultHandler, form]);
|
||||
|
||||
const renderVenueTypeDropdown = (menu: React.ReactElement): React.ReactElement => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Space style={{ padding: '0 8px 8px' }}>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
setDictInputValue('');
|
||||
setDictModalOpen(true);
|
||||
}}
|
||||
>
|
||||
新增场地类型
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
|
||||
const handleDictModalOk = async () => {
|
||||
const dictLabel = dictInputValue.trim();
|
||||
if (!dictLabel) {
|
||||
return;
|
||||
}
|
||||
setDictSubmitLoading(true);
|
||||
try {
|
||||
const success = await onCreateDictOption({
|
||||
dictType: VENUE_TYPE_DICT,
|
||||
dictLabel,
|
||||
});
|
||||
if (success) {
|
||||
form.setFieldValue('venueType', dictLabel);
|
||||
setDictModalOpen(false);
|
||||
}
|
||||
} finally {
|
||||
setDictSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
title={values ? '编辑场地' : '添加场地'}
|
||||
form={form}
|
||||
open={open}
|
||||
width={900}
|
||||
modalProps={{ destroyOnClose: true, onCancel }}
|
||||
onFinish={async (formValues) => {
|
||||
const selectedLineIds = (formValues.routeLineIds || []) as string[];
|
||||
const selectedLineNames = lineOptions
|
||||
.filter((option) => selectedLineIds.includes(String(option.value)))
|
||||
.map((option) => option.label)
|
||||
.join(',');
|
||||
await onSubmit({
|
||||
...formValues,
|
||||
id: values?.id,
|
||||
routeLineIds: selectedLineIds,
|
||||
routeLineNames: selectedLineNames,
|
||||
} as VenueInfoForm);
|
||||
}}
|
||||
>
|
||||
<ProFormText name="id" hidden />
|
||||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
name="venueType"
|
||||
label="场地类型"
|
||||
width="md"
|
||||
options={venueTypeOptions}
|
||||
fieldProps={{
|
||||
showSearch: true,
|
||||
dropdownRender: renderVenueTypeDropdown,
|
||||
}}
|
||||
rules={[{ required: true, message: '请选择场地类型' }]}
|
||||
placeholder="请选择场地类型"
|
||||
/>
|
||||
<ProFormText
|
||||
name="venueName"
|
||||
label="场地名称"
|
||||
width="md"
|
||||
rules={[{ required: true, message: '请输入场地名称' }]}
|
||||
placeholder="请输入场地名称"
|
||||
/>
|
||||
<ProFormDigit
|
||||
name="venueArea"
|
||||
label="场地面积"
|
||||
width="sm"
|
||||
min={0}
|
||||
fieldProps={{ precision: 2 }}
|
||||
placeholder="请输入场地面积"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormDigit
|
||||
name="floorCount"
|
||||
label="展位数量"
|
||||
width="md"
|
||||
min={0}
|
||||
fieldProps={{ precision: 0 }}
|
||||
placeholder="请输入展位数量"
|
||||
/>
|
||||
<ProFormText name="contactPhone" label="联系电话" width="md" placeholder="请输入联系电话" />
|
||||
</ProForm.Group>
|
||||
<ProFormText
|
||||
name="venueAddress"
|
||||
label="场地地址"
|
||||
rules={[{ required: true, message: '请输入场地地址' }]}
|
||||
placeholder="请输入场地地址"
|
||||
/>
|
||||
<ProFormSelect
|
||||
name="routeLineIds"
|
||||
label="乘坐线路"
|
||||
mode="multiple"
|
||||
options={lineOptions}
|
||||
fieldProps={{
|
||||
showSearch: true,
|
||||
optionFilterProp: 'label',
|
||||
}}
|
||||
placeholder="请选择乘坐线路"
|
||||
/>
|
||||
<ProFormTextArea
|
||||
name="remark"
|
||||
label="备注"
|
||||
fieldProps={{ maxLength: 100, showCount: true }}
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
<ProForm.Group>
|
||||
<ProFormDatePicker
|
||||
name="handleDate"
|
||||
label="经办日期"
|
||||
width="md"
|
||||
fieldProps={{ format: 'YYYY-MM-DD' }}
|
||||
/>
|
||||
<ProFormText name="handleOrg" label="经办机构" width="md" placeholder="请输入经办机构" />
|
||||
<ProFormText name="handler" label="经办人" width="md" placeholder="请输入经办人" />
|
||||
</ProForm.Group>
|
||||
<Modal
|
||||
title="新增场地类型"
|
||||
open={dictModalOpen}
|
||||
confirmLoading={dictSubmitLoading}
|
||||
onOk={handleDictModalOk}
|
||||
onCancel={() => setDictModalOpen(false)}
|
||||
okButtonProps={{ disabled: !dictInputValue.trim() }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Input
|
||||
value={dictInputValue}
|
||||
maxLength={100}
|
||||
showCount
|
||||
placeholder="请输入场地类型"
|
||||
onChange={(event) => setDictInputValue(event.target.value)}
|
||||
onPressEnter={handleDictModalOk}
|
||||
/>
|
||||
</Modal>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditModal;
|
||||
247
src/pages/Jobfair/Venueinfo/index.tsx
Normal file
247
src/pages/Jobfair/Venueinfo/index.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useAccess, useModel } from '@umijs/max';
|
||||
import { Button, message, Modal } from 'antd';
|
||||
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { DeleteOutlined, FormOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
addVenueInfo,
|
||||
addVenueInfoDictOption,
|
||||
deleteVenueInfo,
|
||||
getVenueInfoList,
|
||||
updateVenueInfo,
|
||||
} from '@/services/jobportal/venueInfo';
|
||||
import type {
|
||||
VenueInfoDictForm,
|
||||
VenueInfoForm,
|
||||
VenueInfoItem,
|
||||
VenueInfoListParams,
|
||||
} from '@/services/jobportal/venueInfo';
|
||||
import { getCmsLineList } from '@/services/area/subway';
|
||||
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
||||
import EditModal from './components/EditModal';
|
||||
|
||||
const VENUE_TYPE_DICT = 'venue_info_type';
|
||||
|
||||
const VenueInfoList: React.FC = () => {
|
||||
const access = useAccess();
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentRow, setCurrentRow] = useState<VenueInfoItem>();
|
||||
const [venueTypeValueEnum, setVenueTypeValueEnum] = useState<Record<string, any>>({});
|
||||
const [venueTypeOptions, setVenueTypeOptions] = useState<any[]>([]);
|
||||
const [lineOptions, setLineOptions] = useState<any[]>([]);
|
||||
|
||||
const defaultHandler =
|
||||
initialState?.currentUser?.nickName || initialState?.currentUser?.userName || undefined;
|
||||
|
||||
const refreshVenueTypeOptions = useCallback(async () => {
|
||||
const [valueEnum, options] = await Promise.all([
|
||||
getDictValueEnum(VENUE_TYPE_DICT),
|
||||
getDictSelectOption(VENUE_TYPE_DICT),
|
||||
]);
|
||||
setVenueTypeValueEnum(valueEnum);
|
||||
setVenueTypeOptions(options);
|
||||
}, []);
|
||||
|
||||
const refreshLineOptions = useCallback(async () => {
|
||||
const res = await getCmsLineList({ current: '1', pageSize: '999' } as any);
|
||||
if (res.code === 200) {
|
||||
setLineOptions(
|
||||
(res.rows || []).map((item) => ({
|
||||
label: item.lineName,
|
||||
value: String(item.lineId),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshVenueTypeOptions();
|
||||
refreshLineOptions();
|
||||
}, [refreshVenueTypeOptions, refreshLineOptions]);
|
||||
|
||||
const handleCreateDictOption = async (values: VenueInfoDictForm) => {
|
||||
const res = await addVenueInfoDictOption(values);
|
||||
if (res.code === 200) {
|
||||
await refreshVenueTypeOptions();
|
||||
message.success('场地类型新增成功');
|
||||
return true;
|
||||
}
|
||||
message.error(res.msg || '场地类型新增失败');
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该场地信息吗?',
|
||||
onOk: async () => {
|
||||
const res = await deleteVenueInfo(id);
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功');
|
||||
actionRef.current?.reload();
|
||||
} else {
|
||||
message.error(res.msg || '删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ProColumns<VenueInfoItem>[] = [
|
||||
{
|
||||
title: '场地类型',
|
||||
dataIndex: 'venueType',
|
||||
ellipsis: true,
|
||||
valueType: 'select',
|
||||
valueEnum: venueTypeValueEnum,
|
||||
},
|
||||
{
|
||||
title: '场地名称',
|
||||
dataIndex: 'venueName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '场地面积',
|
||||
dataIndex: 'venueArea',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '展位数量',
|
||||
dataIndex: 'floorCount',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '场地地址',
|
||||
dataIndex: 'venueAddress',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'contactPhone',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '乘坐线路',
|
||||
dataIndex: 'routeLineNames',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '经办日期',
|
||||
dataIndex: 'handleDate',
|
||||
valueType: 'date',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '经办机构',
|
||||
dataIndex: 'handleOrg',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '经办人',
|
||||
dataIndex: 'handler',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
render: (_, record) => [
|
||||
<Button
|
||||
key="edit"
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<FormOutlined />}
|
||||
hidden={!access.hasPerms('cms:venueInfo:edit')}
|
||||
onClick={() => {
|
||||
setCurrentRow(record);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>,
|
||||
<Button
|
||||
key="delete"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
hidden={!access.hasPerms('cms:venueInfo:remove')}
|
||||
onClick={() => handleDelete(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<VenueInfoItem>
|
||||
headerTitle="场地信息维护列表"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
scroll={{ x: 'max-content' }}
|
||||
request={async (params) => {
|
||||
const res = await getVenueInfoList({
|
||||
current: params.current,
|
||||
pageSize: params.pageSize,
|
||||
venueType: params.venueType,
|
||||
venueName: params.venueName,
|
||||
venueAddress: params.venueAddress,
|
||||
} as VenueInfoListParams);
|
||||
return { data: res.rows, total: res.total, success: true };
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="add"
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
hidden={!access.hasPerms('cms:venueInfo:add')}
|
||||
onClick={() => {
|
||||
setCurrentRow(undefined);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
新增
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
<EditModal
|
||||
open={modalVisible}
|
||||
values={currentRow}
|
||||
venueTypeOptions={venueTypeOptions}
|
||||
lineOptions={lineOptions}
|
||||
defaultHandler={defaultHandler}
|
||||
onCreateDictOption={handleCreateDictOption}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
}}
|
||||
onSubmit={async (values: VenueInfoForm) => {
|
||||
const res = values.id ? await updateVenueInfo(values) : await addVenueInfo(values);
|
||||
if (res.code === 200) {
|
||||
message.success(values.id ? '修改成功' : '新增成功');
|
||||
setModalVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
actionRef.current?.reload();
|
||||
} else {
|
||||
message.error(res.msg || '操作失败');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default VenueInfoList;
|
||||
Reference in New Issue
Block a user