diff --git a/config/proxy.ts b/config/proxy.ts index b0897be..730ada7 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -43,6 +43,8 @@ export default { // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, + // 测试环境目前使用 IP 访问 HTTPS,证书域名不匹配;仅影响本地开发代理。 + secure: false, }, }, diff --git a/config/routes.ts b/config/routes.ts index 565eb4b..35ccb17 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -48,6 +48,14 @@ export default [ path: '/job-portal/list', // 职位列表 component: './JobPortal/List', }, + { + path: '/job-portal/job-fair', // 招聘会(线上、户外) + component: './JobPortal/JobFair', + }, + { + path: '/job-portal/job-fair/detail', // 招聘会详情 + component: './JobPortal/JobFair/Detail', + }, { path: '/job-portal/detail', // 职位详情 component: './JobPortal/Detail', diff --git a/src/components/JobPortalHeader/index.tsx b/src/components/JobPortalHeader/index.tsx index deea569..983a39f 100644 --- a/src/components/JobPortalHeader/index.tsx +++ b/src/components/JobPortalHeader/index.tsx @@ -14,6 +14,7 @@ import { FileTextOutlined, HomeOutlined, BellOutlined, + CalendarOutlined, ReadOutlined, LoginOutlined, FundProjectionScreenOutlined, @@ -102,6 +103,7 @@ const JobPortalHeader: React.FC = ({ // 判断激活导航(简化为 startsWith 匹配) const isHome = /^\/job-portal(\/(list|detail))?$/.test(location.pathname); + const isJobFair = location.pathname.startsWith('/job-portal/job-fair'); const isResume = location.pathname.startsWith('/job-portal/resume'); const isMine = location.pathname.startsWith('/job-portal/personal-center') || location.pathname.startsWith('/job-portal/profile'); const isMessage = location.pathname.startsWith('/job-portal/message'); @@ -318,6 +320,14 @@ const JobPortalHeader: React.FC = ({ > 找工作 + + + {detail ? ( + +
+
+ + {channel === 'outdoor' ? '户外招聘会' : '线上招聘会'} + + {detail.jobFairTitle} +
+
+ + + 举办时间 + + } + > + {formatDateTime(detail.jobFairStartTime)} 至{' '} + {formatDateTime(detail.jobFairEndTime)} + + + 举办地点 + + } + > + {detail.jobFairAddress || detail.jobFairVenueName || '暂未公布'} + + + 主办单位 + + } + > + {detail.jobFairHostUnit || detail.jobFairOrganizeUnit || '暂未公布'} + + {detail.jobFairRegion && ( + + 举办区域 + + } + > + {detail.jobFairRegion} + + )} + {detail.boothNum && ( + {detail.boothNum} + )} + {detail.jobFairPhone && ( + + 联系电话 + + } + > + {detail.jobFairPhone} + + )} + +
+ 招聘会简介 + {detail.jobFairIntroduction || '暂未提供招聘会简介。'} +
+
+ ) : ( + !loading && ( + + + + ) + )} +
+ + + ); +}; + +export default PortalJobFairDetailPage; diff --git a/src/pages/JobPortal/JobFair/index.less b/src/pages/JobPortal/JobFair/index.less new file mode 100644 index 0000000..95e27dc --- /dev/null +++ b/src/pages/JobPortal/JobFair/index.less @@ -0,0 +1,333 @@ +@import '../theme.less'; + +.portal-job-fair-page { + min-height: 100vh; + + .job-fair-content { + .jp-page-container(); + padding-top: 28px; + padding-bottom: 56px; + } + + .job-fair-hero { + position: relative; + overflow: hidden; + display: flex; + align-items: center; + justify-content: space-between; + min-height: 128px; + padding: 24px 38px; + border-radius: @jp-radius-lg; + color: #fff; + background: linear-gradient(120deg, #0d6efd, #21a3ee); + box-shadow: 0 10px 28px rgba(24, 144, 255, 0.22); + + .ant-typography { + color: #fff; + margin-bottom: 6px; + } + + .job-fair-hero-icon { + opacity: 0.18; + font-size: 90px; + } + } + + .job-fair-panel, + .fair-calendar-card, + .fair-summary-card, + .fair-card { + .jp-card-base(); + } + + .job-fair-panel { + margin-top: 22px; + padding: 0 24px 20px; + + .job-fair-tabs { + .ant-tabs-nav { + margin-bottom: 16px; + } + + .ant-tabs-tab { + padding: 16px 8px; + font-size: 16px; + } + } + } + + .job-fair-toolbar { + display: flex; + align-items: center; + gap: 12px; + + .ant-input-search { + max-width: 440px; + } + + .ant-tag { + margin: 0; + padding: 5px 9px; + border-radius: 5px; + } + } + + .job-fair-supporting { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: 20px; + + .fair-calendar-card { + height: 100%; + min-height: 0; + + .ant-card-head, + .ant-card-body { + border: 0; + } + + .ant-picker-calendar { + background: transparent; + } + + .fair-calendar-date { + position: relative; + height: 24px; + margin: 0 3px; + border-radius: 4px; + color: @jp-text-primary; + line-height: 24px; + text-align: center; + } + + .ant-picker-cell-selected .fair-calendar-date { + color: #fff; + background: @jp-primary; + } + + .fair-calendar-marker { + position: absolute; + right: 4px; + bottom: 1px; + width: 5px; + height: 5px; + border-radius: 50%; + background: #fa8c16; + } + + .clear-date-button { + padding: 0; + } + } + } + + .fair-summary-grid { + display: grid; + grid-column: span 2; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; + height: 100%; + + .fair-summary-card { + height: 100%; + min-height: 0; + + .ant-card-head { + border-bottom-color: @jp-border; + } + } + } + + .fair-summary-list { + display: flex; + flex-direction: column; + gap: 8px; + } + + .fair-summary-item { + display: grid; + grid-template-columns: 64px minmax(0, 1fr); + gap: 10px; + width: 100%; + padding: 10px; + border: 0; + border-radius: 6px; + color: @jp-text-primary; + text-align: left; + background: #f8fbff; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: @jp-primary-light; + } + + .fair-summary-date { + color: @jp-primary; + font-size: 12px; + line-height: 20px; + white-space: nowrap; + } + + .fair-summary-title { + overflow: hidden; + font-size: 14px; + line-height: 20px; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .fair-summary-empty { + display: block; + padding-top: 24px; + text-align: center; + } + + .job-fair-list-section { + margin-top: 28px; + } + + .job-fair-list-heading { + display: flex; + align-items: baseline; + justify-content: space-between; + margin-bottom: 16px; + + .ant-typography { + .jp-section-title(); + margin: 0; + } + } + + .fair-card { + height: 100%; + transition: + box-shadow 0.2s, + transform 0.2s; + + &:hover { + box-shadow: @jp-shadow-hover; + transform: translateY(-2px); + } + + .ant-card-body { + display: flex; + flex-direction: column; + height: 100%; + box-sizing: border-box; + padding: 20px; + } + } + + .fair-card-tags { + display: flex; + gap: 8px; + margin-bottom: 12px; + + .ant-tag { + margin: 0; + } + } + + .fair-card-title { + min-height: 48px; + margin-bottom: 14px !important; + color: @jp-text-primary; + line-height: 24px !important; + } + + .fair-card-meta { + display: flex; + flex: 1; + flex-direction: column; + gap: 9px; + min-height: 82px; + color: @jp-text-secondary; + font-size: 13px; + + > div { + display: flex; + align-items: flex-start; + line-height: 20px; + + .anticon { + flex: 0 0 auto; + margin: 3px 8px 0 0; + color: @jp-primary; + } + } + } + + .fair-card-introduction { + min-height: 42px; + margin: 16px 0 0 !important; + padding-top: 14px; + border-top: 1px solid @jp-border; + color: @jp-text-muted; + font-size: 13px; + line-height: 21px; + } + + .job-fair-pagination { + display: flex; + justify-content: flex-end; + margin-top: 28px; + } +} + +@media (max-width: 900px) { + .portal-job-fair-page { + .job-fair-supporting, + .fair-summary-grid { + grid-template-columns: 1fr; + } + + .fair-summary-grid { + grid-column: auto; + } + + .fair-summary-card { + min-height: auto !important; + } + } +} + +@media (max-width: 576px) { + .portal-job-fair-page { + .job-fair-content { + padding: 16px 12px 36px; + } + + .job-fair-hero { + min-height: 112px; + padding: 20px; + + .job-fair-hero-icon { + font-size: 62px; + } + } + + .job-fair-panel { + padding: 0 16px 16px; + } + + .job-fair-toolbar { + align-items: stretch; + flex-direction: column; + + .ant-input-search { + max-width: none; + } + } + + .fair-summary-grid { + gap: 12px; + } + + .job-fair-pagination { + justify-content: center; + } + } +} diff --git a/src/pages/JobPortal/JobFair/index.tsx b/src/pages/JobPortal/JobFair/index.tsx new file mode 100644 index 0000000..a781ca2 --- /dev/null +++ b/src/pages/JobPortal/JobFair/index.tsx @@ -0,0 +1,380 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { + Button, + Calendar, + Card, + Col, + ConfigProvider, + Empty, + Input, + Pagination, + Row, + Spin, + Tabs, + Tag, + Typography, +} from 'antd'; +import { + CalendarOutlined, + ClockCircleOutlined, + EnvironmentOutlined, + SearchOutlined, + TeamOutlined, +} from '@ant-design/icons'; +import { history } from '@umijs/max'; +import dayjs, { Dayjs } from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import zhCN from 'antd/locale/zh_CN'; +import JobPortalHeader from '@/components/JobPortalHeader'; +import { + getPortalCurrentMonthJobFairs, + getPortalCurrentQuarterJobFairs, + getPortalJobFairDates, + getPortalJobFairPage, + type JobFairChannel, + type PortalJobFairItem, +} from '@/services/jobportal/jobFair'; +import './index.less'; + +const { Title, Text, Paragraph } = Typography; + +const PAGE_SIZE = 9; + +const fairTabs: Array<{ key: JobFairChannel; label: string }> = [ + { key: 'online', label: '线上招聘会' }, + { key: 'outdoor', label: '户外招聘会' }, +]; + +function formatDateTime(value?: string) { + if (!value) { + return '时间待定'; + } + const date = dayjs(value.replace(' ', 'T')); + return date.isValid() ? date.format('YYYY年MM月DD日 HH:mm') : value; +} + +function formatSummaryDate(value?: string) { + if (!value) { + return '时间待定'; + } + const date = dayjs(value.replace(' ', 'T')); + return date.isValid() ? date.format('MM月DD日') : value.slice(0, 10); +} + +function getFairStatus(fair: PortalJobFairItem) { + const start = fair.jobFairStartTime ? dayjs(fair.jobFairStartTime.replace(' ', 'T')) : null; + const end = fair.jobFairEndTime ? dayjs(fair.jobFairEndTime.replace(' ', 'T')) : null; + const now = dayjs(); + + if (start?.isValid() && now.isBefore(start)) { + return { label: '待开始', color: 'gold' }; + } + if (end?.isValid() && now.isAfter(end)) { + return { label: '已结束', color: 'default' }; + } + if (start?.isValid() && end?.isValid()) { + return { label: '进行中', color: 'green' }; + } + return fair.jobFairStatus ? { label: fair.jobFairStatus, color: 'blue' } : null; +} + +const PortalJobFairPage: React.FC = () => { + const [channel, setChannel] = useState('online'); + const [searchInput, setSearchInput] = useState(''); + const [keyword, setKeyword] = useState(''); + const [selectedDate, setSelectedDate] = useState(); + const [calendarValue, setCalendarValue] = useState(() => dayjs().locale('zh-cn')); + const [fairDates, setFairDates] = useState>(new Set()); + const [fairs, setFairs] = useState([]); + const [monthFairs, setMonthFairs] = useState([]); + const [quarterFairs, setQuarterFairs] = useState([]); + const [pageNum, setPageNum] = useState(1); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [summaryLoading, setSummaryLoading] = useState(false); + + const loadFairs = useCallback(async () => { + setLoading(true); + try { + const response = await getPortalJobFairPage(channel, { + pageNum, + pageSize: PAGE_SIZE, + jobFairTitle: keyword || undefined, + zphjbsj: selectedDate, + }); + if (response?.code !== 200) { + throw new Error(response?.msg || '招聘会列表加载失败'); + } + setFairs(response.data?.list || []); + setTotal(response.data?.total || 0); + } catch (error) { + setFairs([]); + setTotal(0); + } finally { + setLoading(false); + } + }, [channel, keyword, pageNum, selectedDate]); + + useEffect(() => { + void loadFairs(); + }, [loadFairs]); + + useEffect(() => { + let cancelled = false; + + const loadSupportingData = async () => { + setSummaryLoading(true); + try { + const [datesResponse, monthResponse, quarterResponse] = await Promise.all([ + getPortalJobFairDates(channel), + getPortalCurrentMonthJobFairs(channel), + getPortalCurrentQuarterJobFairs(channel), + ]); + if (cancelled) { + return; + } + setFairDates(new Set(Array.isArray(datesResponse?.data) ? datesResponse.data : [])); + setMonthFairs(Array.isArray(monthResponse?.data) ? monthResponse.data : []); + setQuarterFairs(Array.isArray(quarterResponse?.data) ? quarterResponse.data : []); + } catch (error) { + if (!cancelled) { + setFairDates(new Set()); + setMonthFairs([]); + setQuarterFairs([]); + } + } finally { + if (!cancelled) { + setSummaryLoading(false); + } + } + }; + + void loadSupportingData(); + return () => { + cancelled = true; + }; + }, [channel]); + + const handleChannelChange = (nextChannel: string) => { + setChannel(nextChannel as JobFairChannel); + setPageNum(1); + setSelectedDate(undefined); + }; + + const handleSearch = (value?: string) => { + setKeyword((value ?? searchInput).trim()); + setPageNum(1); + }; + + const handleSelectDate = (value: Dayjs) => { + const localizedValue = value.locale('zh-cn'); + const nextDate = localizedValue.format('YYYY-MM-DD'); + setCalendarValue(localizedValue); + setSelectedDate(nextDate); + setPageNum(1); + }; + + const handleSummaryFairClick = (fair: PortalJobFairItem) => { + if (!fair.jobFairId) { + return; + } + history.push( + `/job-portal/job-fair/detail?channel=${channel}&jobFairId=${encodeURIComponent(fair.jobFairId)}`, + { fair }, + ); + }; + + const renderSummary = (title: string, fairsToRender: PortalJobFairItem[]) => ( + + + {fairsToRender.length ? ( +
+ {fairsToRender.slice(0, 3).map((fair) => ( + + ))} +
+ ) : ( + + 暂无招聘会安排 + + )} +
+
+ ); + + return ( +
+ + +
+
+
+ 招聘会 + 汇集线上与户外招聘会信息,选择日期即可查看当天的活动安排。 +
+
+ +
+ +
+ + 搜索 + + } + onChange={(event) => setSearchInput(event.target.value)} + onSearch={handleSearch} + placeholder="搜索招聘会名称" + value={searchInput} + /> + {selectedDate && ( + { + setSelectedDate(undefined); + setPageNum(1); + }} + > + 举办日期:{selectedDate} + + )} +
+
+ +
+ + + { + if (info.type !== 'date') { + return info.originNode; + } + const date = current.format('YYYY-MM-DD'); + return ( +
+ {current.date()} + {fairDates.has(date) && ( + + )} +
+ ); + }} + fullscreen={false} + onPanelChange={(value) => setCalendarValue(value.locale('zh-cn'))} + onSelect={handleSelectDate} + value={calendarValue} + /> +
+ +
+
+ {renderSummary(`本月${channel === 'outdoor' ? '户外' : ''}招聘会`, monthFairs)} + {renderSummary(`本季度${channel === 'outdoor' ? '户外' : ''}招聘会`, quarterFairs)} +
+
+ +
+
+ {channel === 'outdoor' ? '户外招聘会' : '线上招聘会'} + 共 {total} 场 +
+ + + {fairs.length ? ( + <> + + {fairs.map((fair) => { + const status = getFairStatus(fair); + return ( + + +
+ + {channel === 'outdoor' ? '户外招聘会' : '线上招聘会'} + + {status && {status.label}} +
+ + {fair.jobFairTitle} + +
+
+ + {formatDateTime(fair.jobFairStartTime)} +
+
+ + {fair.jobFairAddress || '地点待定'} +
+
+ + {fair.jobFairHostUnit || fair.jobFairOrganizeUnit || '主办单位待定'} +
+
+ + {fair.jobFairIntroduction || '暂未提供招聘会简介。'} + +
+ + ); + })} +
+ {total > PAGE_SIZE && ( +
+ setPageNum(nextPage)} + pageSize={PAGE_SIZE} + showSizeChanger={false} + showTotal={(count) => `共 ${count} 场招聘会`} + total={total} + /> +
+ )} + + ) : ( + !loading && + )} +
+
+
+
+ ); +}; + +export default PortalJobFairPage; diff --git a/src/pages/Jobfair/PublicJobFair/Detail/index.tsx b/src/pages/Jobfair/PublicJobFair/Detail/index.tsx index 3a10486..d717ca1 100644 --- a/src/pages/Jobfair/PublicJobFair/Detail/index.tsx +++ b/src/pages/Jobfair/PublicJobFair/Detail/index.tsx @@ -23,6 +23,8 @@ import { removeJobFromJobFair, reviewPublicJobFairCompany, } from '@/services/jobfair/publicJobFair'; +import { getDictValueEnum } from '@/services/system/dict'; +import DictTag from '@/components/DictTag'; import CompanySelectModal from '../components/CompanySelectModal'; import JobSelectModal from '../components/JobSelectModal'; @@ -51,6 +53,7 @@ const JobFairDetail: React.FC = () => { const [reviewModalOpen, setReviewModalOpen] = useState(false); const [reviewCompany, setReviewCompany] = useState(); const [reviewSubmitting, setReviewSubmitting] = useState(false); + const [jobFairRegionTypeEnum, setJobFairRegionTypeEnum] = useState({}); const [reviewForm] = Form.useForm(); const reviewStatus = Form.useWatch('reviewStatus', reviewForm); @@ -72,6 +75,10 @@ const JobFairDetail: React.FC = () => { } }, [jobFairId, isEnterprise]); + useEffect(() => { + getDictValueEnum('job_fair_region_type').then(setJobFairRegionTypeEnum); + }, []); + const handleRemoveCompany = (company: API.PublicJobFair.CompanyInfo) => { Modal.confirm({ title: '确认移除', @@ -174,6 +181,13 @@ const JobFairDetail: React.FC = () => { {jobFairTypeMap[detail?.jobFairType || '']} + + {detail?.jobFairRegionType ? ( + + ) : ( + '-' + )} + {detail?.jobFairAddress} {detail?.jobFairPhone} diff --git a/src/pages/Jobfair/PublicJobFair/components/EditModal.tsx b/src/pages/Jobfair/PublicJobFair/components/EditModal.tsx index dd39ea7..cd0858b 100644 --- a/src/pages/Jobfair/PublicJobFair/components/EditModal.tsx +++ b/src/pages/Jobfair/PublicJobFair/components/EditModal.tsx @@ -18,6 +18,7 @@ interface EditModalProps { open: boolean; values?: API.PublicJobFair.JobFairItem; jobFairTypeEnum: any; + jobFairRegionTypeEnum: any; onCancel: () => void; onSubmit: (values: API.PublicJobFair.JobFairForm) => Promise; } @@ -69,6 +70,7 @@ const EditModal: React.FC = ({ open, values, jobFairTypeEnum, + jobFairRegionTypeEnum, onCancel, onSubmit, }) => { @@ -175,6 +177,12 @@ const EditModal: React.FC = ({ valueEnum={jobFairTypeEnum} rules={[{ required: true, message: '请选择招聘会类型' }]} /> + {isCrossDomain === 'y' && ( { const [companyJobModalOpen, setCompanyJobModalOpen] = useState(false); const [currentJobFair, setCurrentJobFair] = useState(); const [jobFairTypeEnum, setJobFairTypeEnum] = useState({}); + const [jobFairRegionTypeEnum, setJobFairRegionTypeEnum] = useState({}); const [yesNoEnum, setYesNoEnum] = useState({}); const [selectedRowKeys, setSelectedRowKeys] = useState([]); useEffect(() => { getDictValueEnum('job_fair_type', true).then(setJobFairTypeEnum); + getDictValueEnum('job_fair_region_type').then(setJobFairRegionTypeEnum); getDictValueEnum('sys_yes_no').then(setYesNoEnum); }, []); @@ -129,6 +131,26 @@ const PublicJobFairList: React.FC = () => { valueEnum: jobFairTypeEnum, render: (_, record) => , }, + { + title: '区域类型', + dataIndex: 'jobFairRegionType', + valueType: 'select', + valueEnum: jobFairRegionTypeEnum, + render: (_, record) => ( + + ), + }, + { + title: '举办状态', + dataIndex: 'jobFairStatus', + hideInSearch: true, + render: (_, record) => { + const status = record.jobFairStatus; + if (!status) return '-'; + const color = status === '未举办' ? 'blue' : status === '正在举办' ? 'green' : 'default'; + return {status}; + }, + }, { title: '报名审核状态', dataIndex: 'enterpriseReviewStatus', @@ -284,6 +306,7 @@ const PublicJobFairList: React.FC = () => { pageSize: params.pageSize, jobFairTitle: params.jobFairTitle, jobFairType: params.jobFairType, + jobFairRegionType: params.jobFairRegionType, startDate: params.startDate, endDate: params.endDate, }); @@ -322,6 +345,7 @@ const PublicJobFairList: React.FC = () => { open={modalVisible} values={currentRow} jobFairTypeEnum={jobFairTypeEnum} + jobFairRegionTypeEnum={jobFairRegionTypeEnum} onCancel={() => { setModalVisible(false); setCurrentRow(undefined); diff --git a/src/pages/Management/List/edit.tsx b/src/pages/Management/List/edit.tsx index 1c9de25..65ae041 100644 --- a/src/pages/Management/List/edit.tsx +++ b/src/pages/Management/List/edit.tsx @@ -10,6 +10,7 @@ import { } from '@ant-design/pro-components'; import { Form, Button, Input, Tag, TreeSelect, Switch } from 'antd'; import { PlusOutlined, MinusCircleOutlined, EnvironmentOutlined } from '@ant-design/icons'; +import { useModel } from '@umijs/max'; import React, { useEffect, useRef, useState } from 'react'; import { DictValueEnumObj } from '@/components/DictTag'; import { getCmsCompanyList } from '@/services/company/list'; @@ -72,6 +73,8 @@ const waitTime = (time: number = 100) => { }; const listEdit: React.FC = (props) => { + const { initialState } = useModel('@@initialState'); + const isEnterprise = initialState?.currentUser?.userType === 'view'; const [form] = Form.useForm(); const companyNameMap = useRef>({}); const [jobDetail, setJobDetail] = useState(null); @@ -265,7 +268,7 @@ const listEdit: React.FC = (props) => { @@ -434,26 +437,28 @@ const listEdit: React.FC = (props) => { initialValue={0} fieldProps={{ defaultChecked: false }} /> - { - const resData = await getCmsCompanyList({ name: keyWords }); - return resData.rows.map((item) => { - companyNameMap.current[item.companyId] = item.name; - return { label: item.name, value: item.companyId }; - }); - }} - fieldProps={{ - onChange: (companyId: number) => { - form.setFieldValue('companyName', companyNameMap.current[companyId] ?? ''); - }, - }} - placeholder="请输入公司名称选择公司" - rules={[{ required: true, message: '请输入公司名称选择公司!' }]} - label="招聘公司" - /> + {!isEnterprise && ( + { + const resData = await getCmsCompanyList({ name: keyWords }); + return resData.rows.map((item) => { + companyNameMap.current[item.companyId] = item.name; + return { label: item.name, value: item.companyId }; + }); + }} + fieldProps={{ + onChange: (companyId: number) => { + form.setFieldValue('companyName', companyNameMap.current[companyId] ?? ''); + }, + }} + placeholder="请输入公司名称选择公司" + rules={[{ required: true, message: '请输入公司名称选择公司!' }]} + label="招聘公司" + /> + )} = (props) => { { + id?: number; + title?: string; + hostUnit?: string; + fairType?: string; + region?: string; + venueName?: string; + address?: string; + boothCount?: number; + holdTime?: string; + endTime?: string; + photoUrl?: string; +} + +interface PortalOutdoorFairDetailResult { + code: number; + msg?: string; + /** + * 旧接口返回 { fair: {...} },当前公共接口直接返回招聘会对象。 + * 两种格式均保留兼容,避免接口升级后户外详情页显示为空。 + */ + data?: PortalOutdoorFairDetailData | { fair?: PortalOutdoorFairDetailData }; +} + +function isOutdoorFairDetailWrapper( + data: NonNullable, +): data is { fair?: PortalOutdoorFairDetailData } { + return 'fair' in data; +} + +export interface PortalJobFairDetailResult { + code: number; + msg?: string; + data?: PortalJobFairItem; +} + +const FAIR_API = { + online: '/app/jobfair/public/jobfair', + outdoor: '/app/outdoor-fair', +} as const; + +/** 获取线上或户外招聘会分页列表。 */ +export async function getPortalJobFairPage( + channel: JobFairChannel, + params?: PortalJobFairPageParams, +) { + return request(`${FAIR_API[channel]}/page`, { + method: 'GET', + params, + }); +} + +/** 获取有招聘会的日期,用于与小程序一致的日期筛选提示。 */ +export async function getPortalJobFairDates(channel: JobFairChannel) { + return request(`${FAIR_API[channel]}/dates`, { + method: 'GET', + }); +} + +/** 获取当前月招聘会摘要。 */ +export async function getPortalCurrentMonthJobFairs(channel: JobFairChannel) { + return request(`${FAIR_API[channel]}/currentMonth`, { + method: 'GET', + }); +} + +/** 获取当前季度招聘会摘要。 */ +export async function getPortalCurrentQuarterJobFairs(channel: JobFairChannel) { + return request(`${FAIR_API[channel]}/currentQuarter`, { + method: 'GET', + }); +} + +/** 获取招聘会详情,并把户外招聘会详情转换为与列表一致的公共字段。 */ +export async function getPortalJobFairDetail(channel: JobFairChannel, jobFairId: string) { + if (channel === 'online') { + return request(`${FAIR_API.online}/detail`, { + method: 'GET', + params: { jobFairId }, + }); + } + + const response = await request( + `${FAIR_API.outdoor}/${encodeURIComponent(jobFairId)}`, + { method: 'GET' }, + ); + const payload = response.data; + const fair = payload && (isOutdoorFairDetailWrapper(payload) ? payload.fair : payload); + if (!fair) { + return { code: response.code, msg: response.msg } as PortalJobFairDetailResult; + } + return { + code: response.code, + msg: response.msg, + data: { + jobFairId: fair.jobFairId || String(fair.id ?? jobFairId), + jobFairTitle: fair.jobFairTitle || fair.title || '户外招聘会', + jobFairAddress: fair.jobFairAddress || fair.address || fair.venueName, + jobFairType: fair.jobFairType || fair.fairType || 'outdoor', + jobFairStartTime: fair.jobFairStartTime || fair.holdTime, + jobFairEndTime: fair.jobFairEndTime || fair.endTime, + jobFairHostUnit: fair.jobFairHostUnit || fair.hostUnit, + jobFairOrganizeUnit: fair.jobFairOrganizeUnit, + jobFairIntroduction: fair.jobFairIntroduction, + jobFairPhone: fair.jobFairPhone, + jobFairImage: fair.jobFairImage || fair.photoUrl, + boothNum: + fair.boothNum || (fair.boothCount === undefined ? undefined : String(fair.boothCount)), + jobFairRegion: fair.jobFairRegion || fair.region, + jobFairVenueName: fair.jobFairVenueName || fair.venueName, + }, + } as PortalJobFairDetailResult; +} diff --git a/src/types/jobfair/publicJobFair.d.ts b/src/types/jobfair/publicJobFair.d.ts index 76980c0..dd02d17 100644 --- a/src/types/jobfair/publicJobFair.d.ts +++ b/src/types/jobfair/publicJobFair.d.ts @@ -8,6 +8,7 @@ declare namespace API.PublicJobFair { pageSize?: number; jobFairTitle?: string; jobFairType?: string; + jobFairRegionType?: string; startDate?: string; endDate?: string; } @@ -18,8 +19,10 @@ declare namespace API.PublicJobFair { jobFairTitle: string; jobFairAddress: string; jobFairType: string; + jobFairRegionType?: string; jobFairStartTime: string; jobFairEndTime: string; + jobFairStatus?: '未举办' | '正在举办' | '已举办'; jobFairSignUpStartTime?: string; jobFairSignUpEndTime?: string; jobFairHostUnit?: string; @@ -101,6 +104,7 @@ declare namespace API.PublicJobFair { jobFairTitle: string; jobFairAddress: string; jobFairType: string; + jobFairRegionType: string; jobFairStartTime: string; jobFairEndTime: string; jobFairSignUpStartTime?: string;