Refactor outdoor fair management:
- Remove .DS_Store file. - Update .gitignore to include .DS_Store. - Modify proxy configuration to use environment variable for backend URL. - Correct component paths in routes. - Implement outdoor fair detail fetching with improved API call. - Enhance EditModal for outdoor fair with dynamic dictionary options. - Add functionality to upload outdoor fair photos. - Update service layer to handle outdoor fair dictionary options.
This commit is contained in:
@@ -3,7 +3,7 @@ import { history, useSearchParams } from '@umijs/max';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Tabs, Button, Spin, message } from 'antd';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { getOutdoorFairList } from '@/services/jobportal/outdoorFair';
|
||||
import { getOutdoorFairInfo } from '@/services/jobportal/outdoorFair';
|
||||
import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair';
|
||||
import FairInfoTab from './components/FairInfoTab';
|
||||
import BoothTab from './components/BoothTab';
|
||||
@@ -22,12 +22,11 @@ const OutdoorFairDetail: React.FC = () => {
|
||||
const fetchFairInfo = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getOutdoorFairList({ current: 1, pageSize: 100 });
|
||||
const found = res.rows.find((item) => item.id === fairId);
|
||||
if (found) {
|
||||
setFairInfo(found);
|
||||
const res = await getOutdoorFairInfo(fairId);
|
||||
if (res.code === 200 && res.data) {
|
||||
setFairInfo(res.data);
|
||||
} else {
|
||||
message.error('未找到该招聘会信息');
|
||||
message.error(res.msg || '未找到该招聘会信息');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -72,7 +71,13 @@ const OutdoorFairDetail: React.FC = () => {
|
||||
children: (() => {
|
||||
switch (tab.key) {
|
||||
case 'fair-info':
|
||||
return <FairInfoTab fairId={fairId} fairInfo={fairInfo} onFairUpdate={fetchFairInfo} />;
|
||||
return (
|
||||
<FairInfoTab
|
||||
fairId={fairId}
|
||||
fairInfo={fairInfo}
|
||||
onFairUpdate={fetchFairInfo}
|
||||
/>
|
||||
);
|
||||
case 'booth':
|
||||
return <BoothTab fairId={fairId} />;
|
||||
case 'company-review':
|
||||
|
||||
@@ -1,55 +1,54 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
ModalForm,
|
||||
ProForm,
|
||||
ProFormText,
|
||||
ProFormDigit,
|
||||
ProFormDateTimePicker,
|
||||
ProFormSwitch,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Form } from 'antd';
|
||||
import type { OutdoorFairItem, OutdoorFairForm } from '@/services/jobportal/outdoorFair';
|
||||
import { Button, Divider, Form, Image, Input, message, Modal, Space, Upload } from 'antd';
|
||||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
|
||||
import type {
|
||||
OutdoorFairItem,
|
||||
OutdoorFairForm,
|
||||
OutdoorFairDictForm,
|
||||
} from '@/services/jobportal/outdoorFair';
|
||||
import { uploadOutdoorFairPhoto } from '@/services/jobportal/outdoorFair';
|
||||
|
||||
interface EditModalProps {
|
||||
open: boolean;
|
||||
values?: OutdoorFairItem;
|
||||
fairTypeOptions: any[];
|
||||
regionOptions: any[];
|
||||
onCreateDictOption: (values: OutdoorFairDictForm) => Promise<boolean>;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: OutdoorFairForm) => Promise<void>;
|
||||
}
|
||||
|
||||
/** 招聘会类型选项 */
|
||||
const FAIR_TYPE_OPTIONS = [
|
||||
{ label: '综合类', value: '综合类' },
|
||||
{ label: '校园招聘', value: '校园招聘' },
|
||||
{ label: '行业专场', value: '行业专场' },
|
||||
{ label: '高新技术专场', value: '高新技术专场' },
|
||||
{ label: '智能制造专场', value: '智能制造专场' },
|
||||
{ label: '文旅专场', value: '文旅专场' },
|
||||
{ label: '其他', value: '其他' },
|
||||
];
|
||||
type DictAddTarget = {
|
||||
dictType: OutdoorFairDictForm['dictType'];
|
||||
fieldName: keyof Pick<OutdoorFairForm, 'fairType' | 'region'>;
|
||||
title: string;
|
||||
placeholder: string;
|
||||
};
|
||||
|
||||
/** 举办区域选项 */
|
||||
const REGION_OPTIONS = [
|
||||
{ label: '石河子市', value: '石河子市' },
|
||||
{ label: '第一师(阿拉尔市)', value: '第一师(阿拉尔市)' },
|
||||
{ label: '第二师(铁门关市)', value: '第二师(铁门关市)' },
|
||||
{ label: '第三师(图木舒克市)', value: '第三师(图木舒克市)' },
|
||||
{ label: '第四师(可克达拉市)', value: '第四师(可克达拉市)' },
|
||||
{ label: '第五师(双河市)', value: '第五师(双河市)' },
|
||||
{ label: '第六师(五家渠市)', value: '第六师(五家渠市)' },
|
||||
{ label: '第七师(胡杨河市)', value: '第七师(胡杨河市)' },
|
||||
{ label: '第八师(石河子市)', value: '第八师(石河子市)' },
|
||||
{ label: '第九师(白杨市)', value: '第九师(白杨市)' },
|
||||
{ label: '第十师(北屯市)', value: '第十师(北屯市)' },
|
||||
{ label: '第十一师(建工师)', value: '第十一师(建工师)' },
|
||||
{ label: '第十二师', value: '第十二师' },
|
||||
{ label: '第十三师(新星市)', value: '第十三师(新星市)' },
|
||||
{ label: '第十四师(昆玉市)', value: '第十四师(昆玉市)' },
|
||||
];
|
||||
|
||||
const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit }) => {
|
||||
const EditModal: React.FC<EditModalProps> = ({
|
||||
open,
|
||||
values,
|
||||
fairTypeOptions,
|
||||
regionOptions,
|
||||
onCreateDictOption,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [dictModalOpen, setDictModalOpen] = useState(false);
|
||||
const [dictInputValue, setDictInputValue] = useState('');
|
||||
const [dictSubmitLoading, setDictSubmitLoading] = useState(false);
|
||||
const [dictAddTarget, setDictAddTarget] = useState<DictAddTarget>();
|
||||
const photoUrl = Form.useWatch('photoUrl', form);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -60,6 +59,85 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
|
||||
}
|
||||
}, [open, values, form]);
|
||||
|
||||
const openDictAddModal = (target: DictAddTarget) => {
|
||||
setDictAddTarget(target);
|
||||
setDictInputValue('');
|
||||
setDictModalOpen(true);
|
||||
};
|
||||
|
||||
const renderDropdownWithCreate = (
|
||||
menu: React.ReactNode,
|
||||
target: DictAddTarget,
|
||||
): React.ReactNode => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Space style={{ padding: '0 8px 8px' }}>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => openDictAddModal(target)}
|
||||
>
|
||||
{target.title}
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
|
||||
const handleDictModalOk = async () => {
|
||||
const dictLabel = dictInputValue.trim();
|
||||
if (!dictAddTarget || !dictLabel) {
|
||||
return;
|
||||
}
|
||||
setDictSubmitLoading(true);
|
||||
try {
|
||||
const success = await onCreateDictOption({
|
||||
dictType: dictAddTarget.dictType,
|
||||
dictLabel,
|
||||
});
|
||||
if (success) {
|
||||
form.setFieldValue(dictAddTarget.fieldName, dictLabel);
|
||||
setDictModalOpen(false);
|
||||
}
|
||||
} finally {
|
||||
setDictSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const beforePhotoUpload = (file: File) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件');
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
if (!isLt10M) {
|
||||
message.error('图片大小不能超过 10MB');
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlePhotoUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
try {
|
||||
const res = await uploadOutdoorFairPhoto(file as File);
|
||||
if (res.code === 200 && res.url) {
|
||||
form.setFieldValue('photoUrl', res.url);
|
||||
message.success('照片上传成功');
|
||||
onSuccess?.(res);
|
||||
} else {
|
||||
const error = new Error(res.msg || '照片上传失败');
|
||||
message.error(error.message);
|
||||
onError?.(error);
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '照片上传失败');
|
||||
onError?.(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
title={values ? '编辑户外招聘会' : '新增户外招聘会'}
|
||||
@@ -72,6 +150,11 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
|
||||
}}
|
||||
>
|
||||
<ProFormText name="id" hidden />
|
||||
<ProFormText
|
||||
name="photoUrl"
|
||||
hidden
|
||||
rules={[{ required: true, message: '请上传招聘会照片' }]}
|
||||
/>
|
||||
<ProFormText
|
||||
name="title"
|
||||
label="招聘会标题"
|
||||
@@ -90,7 +173,17 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
|
||||
name="fairType"
|
||||
label="招聘会类型"
|
||||
width="md"
|
||||
options={FAIR_TYPE_OPTIONS}
|
||||
options={fairTypeOptions}
|
||||
fieldProps={{
|
||||
showSearch: true,
|
||||
dropdownRender: (menu) =>
|
||||
renderDropdownWithCreate(menu, {
|
||||
dictType: 'outdoor_fair_type',
|
||||
fieldName: 'fairType',
|
||||
title: '新增招聘会类型',
|
||||
placeholder: '请输入招聘会类型',
|
||||
}),
|
||||
}}
|
||||
rules={[{ required: true, message: '请选择招聘会类型' }]}
|
||||
placeholder="请选择招聘会类型"
|
||||
/>
|
||||
@@ -100,7 +193,17 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
|
||||
name="region"
|
||||
label="举办区域"
|
||||
width="md"
|
||||
options={REGION_OPTIONS}
|
||||
options={regionOptions}
|
||||
fieldProps={{
|
||||
showSearch: true,
|
||||
dropdownRender: (menu) =>
|
||||
renderDropdownWithCreate(menu, {
|
||||
dictType: 'outdoor_fair_region',
|
||||
fieldName: 'region',
|
||||
title: '新增举办区域',
|
||||
placeholder: '请输入举办区域',
|
||||
}),
|
||||
}}
|
||||
rules={[{ required: true, message: '请选择举办区域' }]}
|
||||
placeholder="请选择举办区域"
|
||||
/>
|
||||
@@ -157,12 +260,47 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
|
||||
colProps={{ md: 24 }}
|
||||
initialValue={false}
|
||||
/>
|
||||
<ProFormText
|
||||
name="photoUrl"
|
||||
label="招聘会照片链接"
|
||||
placeholder="请输入照片URL地址"
|
||||
rules={[{ required: true, message: '请输入招聘会照片链接' }]}
|
||||
/>
|
||||
<ProForm.Item label="招聘会照片" required>
|
||||
<Space direction="vertical" size={12}>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
maxCount={1}
|
||||
showUploadList={false}
|
||||
beforeUpload={beforePhotoUpload}
|
||||
customRequest={handlePhotoUpload}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>上传照片</Button>
|
||||
</Upload>
|
||||
{photoUrl ? (
|
||||
<Image
|
||||
src={photoUrl}
|
||||
width={180}
|
||||
height={120}
|
||||
style={{ objectFit: 'cover', borderRadius: 6 }}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ color: '#999' }}>支持 jpg、jpeg、png、gif、bmp,大小不超过 10MB</span>
|
||||
)}
|
||||
</Space>
|
||||
</ProForm.Item>
|
||||
<Modal
|
||||
title={dictAddTarget?.title}
|
||||
open={dictModalOpen}
|
||||
confirmLoading={dictSubmitLoading}
|
||||
onOk={handleDictModalOk}
|
||||
onCancel={() => setDictModalOpen(false)}
|
||||
okButtonProps={{ disabled: !dictInputValue.trim() }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Input
|
||||
value={dictInputValue}
|
||||
maxLength={100}
|
||||
showCount
|
||||
placeholder={dictAddTarget?.placeholder}
|
||||
onChange={(event) => setDictInputValue(event.target.value)}
|
||||
onPressEnter={handleDictModalOk}
|
||||
/>
|
||||
</Modal>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useAccess, history } from '@umijs/max';
|
||||
import { Button, message, Modal } from 'antd';
|
||||
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
@@ -8,19 +8,57 @@ import {
|
||||
addOutdoorFair,
|
||||
updateOutdoorFair,
|
||||
deleteOutdoorFair,
|
||||
addOutdoorFairDictOption,
|
||||
} from '@/services/jobportal/outdoorFair';
|
||||
import type {
|
||||
OutdoorFairItem,
|
||||
OutdoorFairListParams,
|
||||
OutdoorFairForm,
|
||||
OutdoorFairDictForm,
|
||||
} from '@/services/jobportal/outdoorFair';
|
||||
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
||||
import EditModal from './components/EditModal';
|
||||
|
||||
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
||||
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
||||
|
||||
const OutdoorFairList: React.FC = () => {
|
||||
const access = useAccess();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentRow, setCurrentRow] = useState<OutdoorFairItem>();
|
||||
const [fairTypeValueEnum, setFairTypeValueEnum] = useState<Record<string, any>>({});
|
||||
const [fairTypeOptions, setFairTypeOptions] = useState<any[]>([]);
|
||||
const [regionValueEnum, setRegionValueEnum] = useState<Record<string, any>>({});
|
||||
const [regionOptions, setRegionOptions] = useState<any[]>([]);
|
||||
|
||||
const refreshDictOptions = useCallback(async () => {
|
||||
const [typeValueEnum, typeOptions, regionEnum, regionSelectOptions] = await Promise.all([
|
||||
getDictValueEnum(OUTDOOR_FAIR_TYPE_DICT),
|
||||
getDictSelectOption(OUTDOOR_FAIR_TYPE_DICT),
|
||||
getDictValueEnum(OUTDOOR_FAIR_REGION_DICT),
|
||||
getDictSelectOption(OUTDOOR_FAIR_REGION_DICT),
|
||||
]);
|
||||
setFairTypeValueEnum(typeValueEnum);
|
||||
setFairTypeOptions(typeOptions);
|
||||
setRegionValueEnum(regionEnum);
|
||||
setRegionOptions(regionSelectOptions);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshDictOptions();
|
||||
}, [refreshDictOptions]);
|
||||
|
||||
const handleCreateDictOption = async (values: OutdoorFairDictForm) => {
|
||||
const res = await addOutdoorFairDictOption(values);
|
||||
if (res.code === 200) {
|
||||
await refreshDictOptions();
|
||||
message.success('字典项新增成功');
|
||||
return true;
|
||||
}
|
||||
message.error(res.msg || '字典项新增失败');
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
Modal.confirm({
|
||||
@@ -53,13 +91,15 @@ const OutdoorFairList: React.FC = () => {
|
||||
title: '招聘会类型',
|
||||
dataIndex: 'fairType',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
valueType: 'select',
|
||||
valueEnum: fairTypeValueEnum,
|
||||
},
|
||||
{
|
||||
title: '举办区域',
|
||||
dataIndex: 'region',
|
||||
ellipsis: true,
|
||||
hideInSearch: true,
|
||||
valueType: 'select',
|
||||
valueEnum: regionValueEnum,
|
||||
},
|
||||
{
|
||||
title: '举办地址',
|
||||
@@ -175,6 +215,7 @@ const OutdoorFairList: React.FC = () => {
|
||||
title: params.title,
|
||||
hostUnit: params.hostUnit,
|
||||
fairType: params.fairType,
|
||||
region: params.region,
|
||||
} as OutdoorFairListParams);
|
||||
return { data: res.rows, total: res.total, success: true };
|
||||
}}
|
||||
@@ -196,14 +237,15 @@ const OutdoorFairList: React.FC = () => {
|
||||
<EditModal
|
||||
open={modalVisible}
|
||||
values={currentRow}
|
||||
fairTypeOptions={fairTypeOptions}
|
||||
regionOptions={regionOptions}
|
||||
onCreateDictOption={handleCreateDictOption}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
}}
|
||||
onSubmit={async (values: OutdoorFairForm) => {
|
||||
const res = values.id
|
||||
? await updateOutdoorFair(values)
|
||||
: await addOutdoorFair(values);
|
||||
const res = values.id ? await updateOutdoorFair(values) : await addOutdoorFair(values);
|
||||
if (res.code === 200) {
|
||||
message.success(values.id ? '修改成功' : '新增成功');
|
||||
setModalVisible(false);
|
||||
|
||||
Reference in New Issue
Block a user