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>
);

View File

@@ -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="举办地址"

View File

@@ -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);

View 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;

View 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;

View 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;