Compare commits
3 Commits
767da998cb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3faced1751 | |||
| e86621aeed | |||
| 527fcea91f |
@@ -259,6 +259,17 @@ export default [
|
|||||||
name: 'company',
|
name: 'company',
|
||||||
path: '/company',
|
path: '/company',
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
name: '企业列表',
|
||||||
|
path: '/company/list',
|
||||||
|
component: './Company/List',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '未参加招聘会详情',
|
||||||
|
path: '/company/list/missed-outdoor-fairs',
|
||||||
|
component: './Company/List/MissedOutdoorFairs',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '企业信息',
|
name: '企业信息',
|
||||||
path: '/company/info/index',
|
path: '/company/info/index',
|
||||||
|
|||||||
34
src/app.tsx
34
src/app.tsx
@@ -7,10 +7,7 @@ import { history } from '@umijs/max';
|
|||||||
import defaultSettings from '../config/defaultSettings';
|
import defaultSettings from '../config/defaultSettings';
|
||||||
import { errorConfig } from './requestErrorConfig';
|
import { errorConfig } from './requestErrorConfig';
|
||||||
import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
|
import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
|
||||||
import {
|
import { exchangeThirdPartyCredential, isThirdPartyTransitionPage } from './utils/thirdPartyLogin';
|
||||||
exchangeThirdPartyCredential,
|
|
||||||
isThirdPartyTransitionPage,
|
|
||||||
} from './utils/thirdPartyLogin';
|
|
||||||
import { shouldSkipManagementBootstrap } from './utils/routeContext';
|
import { shouldSkipManagementBootstrap } from './utils/routeContext';
|
||||||
import { saveGetInfoCache } from './utils/jobPortalAuth';
|
import { saveGetInfoCache } from './utils/jobPortalAuth';
|
||||||
import {
|
import {
|
||||||
@@ -30,6 +27,7 @@ import { stringify } from 'querystring';
|
|||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getProductionApiBaseUrl } from './utils/publicUrl';
|
import { getProductionApiBaseUrl } from './utils/publicUrl';
|
||||||
|
import defaultAvatar from './assets/avatar.jpg';
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
@@ -53,8 +51,6 @@ const loginOut = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
||||||
* */
|
* */
|
||||||
@@ -70,8 +66,7 @@ export async function getInitialState(): Promise<{
|
|||||||
skipErrorHandler: true,
|
skipErrorHandler: true,
|
||||||
});
|
});
|
||||||
if (response.user.avatar === '') {
|
if (response.user.avatar === '') {
|
||||||
response.user.avatar =
|
response.user.avatar = defaultAvatar;
|
||||||
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
|
|
||||||
}
|
}
|
||||||
saveGetInfoCache(response as unknown as Record<string, unknown>);
|
saveGetInfoCache(response as unknown as Record<string, unknown>);
|
||||||
return {
|
return {
|
||||||
@@ -110,7 +105,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
// actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
|
// actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
|
||||||
actionsRender: () => [<SelectLang key="SelectLang" />],
|
actionsRender: () => [<SelectLang key="SelectLang" />],
|
||||||
avatarProps: {
|
avatarProps: {
|
||||||
src: initialState?.currentUser?.avatar,
|
src: initialState?.currentUser?.avatar || defaultAvatar,
|
||||||
|
fallback: defaultAvatar,
|
||||||
|
onError: () => true,
|
||||||
title: <AvatarName />,
|
title: <AvatarName />,
|
||||||
render: (_, avatarChildren) => {
|
render: (_, avatarChildren) => {
|
||||||
return <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>;
|
return <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>;
|
||||||
@@ -138,10 +135,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
onPageChange: () => {
|
onPageChange: () => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
// 如果没有登录,重定向到 login,但排除过渡页面和求职者页面(考虑基础路径 /shihezi/)
|
// 如果没有登录,重定向到 login,但排除过渡页面和求职者页面(考虑基础路径 /shihezi/)
|
||||||
if (
|
if (!initialState?.currentUser && !shouldSkipManagementBootstrap(location.pathname)) {
|
||||||
!initialState?.currentUser &&
|
|
||||||
!shouldSkipManagementBootstrap(location.pathname)
|
|
||||||
) {
|
|
||||||
history.push(PageEnum.LOGIN);
|
history.push(PageEnum.LOGIN);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -203,14 +197,16 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function onRouteChange({ clientRoutes, location }: { clientRoutes: any; location: any }) {
|
export async function onRouteChange({
|
||||||
|
clientRoutes,
|
||||||
|
location,
|
||||||
|
}: {
|
||||||
|
clientRoutes: any;
|
||||||
|
location: any;
|
||||||
|
}) {
|
||||||
const menus = getRemoteMenu();
|
const menus = getRemoteMenu();
|
||||||
const token = getAccessToken();
|
const token = getAccessToken();
|
||||||
if (
|
if (menus === null && token && !shouldSkipManagementBootstrap(location.pathname)) {
|
||||||
menus === null &&
|
|
||||||
token &&
|
|
||||||
!shouldSkipManagementBootstrap(location.pathname)
|
|
||||||
) {
|
|
||||||
console.log('检测到路由信息未加载,正在重新获取路由信息...');
|
console.log('检测到路由信息未加载,正在重新获取路由信息...');
|
||||||
|
|
||||||
// 重新获取路由信息,而不是刷新页面
|
// 重新获取路由信息,而不是刷新页面
|
||||||
|
|||||||
58
src/pages/Company/List/MissedOutdoorFairs/index.tsx
Normal file
58
src/pages/Company/List/MissedOutdoorFairs/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { history, useSearchParams } from '@umijs/max';
|
||||||
|
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { Button, message } from 'antd';
|
||||||
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
|
import { getCmsCompanyMissedOutdoorFairs } from '@/services/company/list';
|
||||||
|
|
||||||
|
const MissedOutdoorFairs: React.FC = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const companyId = Number(searchParams.get('companyId'));
|
||||||
|
|
||||||
|
const columns: ProColumns<API.CompanyList.MissedOutdoorFair>[] = [
|
||||||
|
{ title: '招聘会名称', dataIndex: 'title', ellipsis: true },
|
||||||
|
{ title: '举办单位', dataIndex: 'hostUnit', ellipsis: true },
|
||||||
|
{ title: '举办地址', dataIndex: 'address', ellipsis: true },
|
||||||
|
{ title: '举办开始时间', dataIndex: 'holdTime', valueType: 'dateTime', width: 180 },
|
||||||
|
{ title: '举办结束时间', dataIndex: 'endTime', valueType: 'dateTime', width: 180 },
|
||||||
|
{ title: '报名时间', dataIndex: 'registrationTime', valueType: 'dateTime', width: 180 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
header={{
|
||||||
|
title: '未参加招聘会详情',
|
||||||
|
extra: [
|
||||||
|
<Button key="back" icon={<ArrowLeftOutlined />} onClick={() => history.back()}>
|
||||||
|
返回企业列表
|
||||||
|
</Button>,
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProTable<API.CompanyList.MissedOutdoorFair>
|
||||||
|
rowKey="fairId"
|
||||||
|
columns={columns}
|
||||||
|
search={false}
|
||||||
|
options={false}
|
||||||
|
pagination={{ pageSize: 10 }}
|
||||||
|
request={async (params) => {
|
||||||
|
if (!companyId) {
|
||||||
|
message.error('缺少企业ID');
|
||||||
|
return { data: [], total: 0, success: false };
|
||||||
|
}
|
||||||
|
const res = await getCmsCompanyMissedOutdoorFairs(companyId, {
|
||||||
|
current: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: res.rows || [],
|
||||||
|
total: res.total || 0,
|
||||||
|
success: res.code === 200,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MissedOutdoorFairs;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { FormattedMessage, useAccess } from '@umijs/max';
|
import { FormattedMessage, history, useAccess } from '@umijs/max';
|
||||||
import { Button, FormInstance, message, Modal, Tag } from 'antd';
|
import { Button, FormInstance, message, Modal, Tag } from 'antd';
|
||||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { DeleteOutlined, FormOutlined, PlusOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, FormOutlined, PlusOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
|
||||||
@@ -225,6 +225,34 @@ function ManagementList() {
|
|||||||
return <Tag color="orange">待审核</Tag>;
|
return <Tag color="orange">待审核</Tag>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '招聘会参会情况',
|
||||||
|
dataIndex: 'missedOutdoorFairOnly',
|
||||||
|
valueType: 'select',
|
||||||
|
hideInTable: true,
|
||||||
|
valueEnum: {
|
||||||
|
true: { text: '有未参加招聘会记录' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '未参加招聘会详情',
|
||||||
|
dataIndex: 'missedOutdoorFairCount',
|
||||||
|
hideInSearch: true,
|
||||||
|
align: 'center',
|
||||||
|
render: (_, record) => {
|
||||||
|
const count = record.missedOutdoorFairCount || 0;
|
||||||
|
if (!count) return '0';
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => history.push(`/company/list/missed-outdoor-fairs?companyId=${record.companyId}`)}
|
||||||
|
>
|
||||||
|
{count} 场详情
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '驳回原因',
|
title: '驳回原因',
|
||||||
dataIndex: 'notPassReason',
|
dataIndex: 'notPassReason',
|
||||||
|
|||||||
@@ -255,6 +255,10 @@ const PortalJobFairPage: React.FC = () => {
|
|||||||
onChange={(event) => setSearchInput(event.target.value)}
|
onChange={(event) => setSearchInput(event.target.value)}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
placeholder="搜索招聘会名称"
|
placeholder="搜索招聘会名称"
|
||||||
|
id="portal-job-fair-search"
|
||||||
|
name="jobFairTitle"
|
||||||
|
aria-label="搜索招聘会名称"
|
||||||
|
autoComplete="off"
|
||||||
value={searchInput}
|
value={searchInput}
|
||||||
/>
|
/>
|
||||||
{selectedDate && (
|
{selectedDate && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { useAccess } from '@umijs/max';
|
import { useAccess } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
message,
|
message,
|
||||||
Modal,
|
Modal,
|
||||||
Spin,
|
Spin,
|
||||||
|
Dropdown,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
EnvironmentOutlined,
|
EnvironmentOutlined,
|
||||||
@@ -35,6 +36,9 @@ import {
|
|||||||
EditOutlined,
|
EditOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
EyeOutlined,
|
EyeOutlined,
|
||||||
|
InfoCircleOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
SearchOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import {
|
import {
|
||||||
@@ -48,18 +52,29 @@ import type {
|
|||||||
CrossCityAllianceForm,
|
CrossCityAllianceForm,
|
||||||
} from '@/services/jobfair/crossCityAlliance';
|
} from '@/services/jobfair/crossCityAlliance';
|
||||||
import { getCrossDomainJobs, getCrossDomainStatistics } from '@/services/jobfair/publicJobFair';
|
import { getCrossDomainJobs, getCrossDomainStatistics } from '@/services/jobfair/publicJobFair';
|
||||||
import type { CrossDomainJobItem } from '@/services/jobfair/publicJobFair';
|
import type {
|
||||||
|
CrossDomainJobItem,
|
||||||
|
CrossDomainStatisticsFairItem,
|
||||||
|
} from '@/services/jobfair/publicJobFair';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
const PAGE_SIZE_FOR_STATS = 10000;
|
const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'year'), dayjs()];
|
||||||
|
|
||||||
const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'month'), dayjs()];
|
type QuickDateRangeKey = '7days' | '30days' | '1year';
|
||||||
|
|
||||||
const getDateRangePresets = () => [
|
const getQuickDateRange = (key: QuickDateRangeKey): [Dayjs, Dayjs] => {
|
||||||
{ label: '近7天', value: [dayjs().subtract(6, 'day'), dayjs()] as [Dayjs, Dayjs] },
|
const end = dayjs();
|
||||||
{ label: '近1个月', value: getDefaultDateRange() },
|
const days = { '7days': 6, '30days': 29 } as const;
|
||||||
|
if (key === '1year') return [end.subtract(1, 'year'), end];
|
||||||
|
return [end.subtract(days[key], 'day'), end];
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickDateRangeItems = [
|
||||||
|
{ key: '7days', label: '近7天' },
|
||||||
|
{ key: '30days', label: '近30天' },
|
||||||
|
{ key: '1year', label: '近1年' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getValueEnumText = (valueEnum: any, value?: string | number) => {
|
const getValueEnumText = (valueEnum: any, value?: string | number) => {
|
||||||
@@ -107,7 +122,17 @@ const AllianceManageModal: React.FC<AllianceManageModalProps> = ({ open, onClose
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns<CrossCityAllianceItem>[] = [
|
const columns: ProColumns<CrossCityAllianceItem>[] = [
|
||||||
{ title: '城市名称', dataIndex: 'name', ellipsis: true },
|
{
|
||||||
|
title: '城市名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'cross-city-alliance-name-search',
|
||||||
|
name: 'name',
|
||||||
|
'aria-label': '城市名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
@@ -150,7 +175,7 @@ const AllianceManageModal: React.FC<AllianceManageModalProps> = ({ open, onClose
|
|||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={860}
|
width={860}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<ProTable<CrossCityAllianceItem>
|
<ProTable<CrossCityAllianceItem>
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
@@ -189,7 +214,7 @@ const AllianceManageModal: React.FC<AllianceManageModalProps> = ({ open, onClose
|
|||||||
<ModalForm<CrossCityAllianceForm>
|
<ModalForm<CrossCityAllianceForm>
|
||||||
title={currentRow ? '编辑联盟城市' : '新增联盟城市'}
|
title={currentRow ? '编辑联盟城市' : '新增联盟城市'}
|
||||||
open={formOpen}
|
open={formOpen}
|
||||||
modalProps={{ destroyOnClose: true, onCancel: () => setFormOpen(false) }}
|
modalProps={{ destroyOnHidden: true, onCancel: () => setFormOpen(false) }}
|
||||||
initialValues={
|
initialValues={
|
||||||
currentRow
|
currentRow
|
||||||
? {
|
? {
|
||||||
@@ -218,6 +243,12 @@ const AllianceManageModal: React.FC<AllianceManageModalProps> = ({ open, onClose
|
|||||||
name="name"
|
name="name"
|
||||||
label="城市名称"
|
label="城市名称"
|
||||||
placeholder="请输入城市名称,如:乌鲁木齐市"
|
placeholder="请输入城市名称,如:乌鲁木齐市"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'cross-city-alliance-name-input',
|
||||||
|
name: 'name',
|
||||||
|
'aria-label': '城市名称',
|
||||||
|
autoComplete: 'address-level2',
|
||||||
|
}}
|
||||||
rules={[{ required: true, message: '请输入城市名称' }]}
|
rules={[{ required: true, message: '请输入城市名称' }]}
|
||||||
/>
|
/>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
@@ -262,18 +293,33 @@ const CitySelectTab: React.FC<CitySelectTabProps> = ({
|
|||||||
<Spin spinning={citiesLoading} size="small">
|
<Spin spinning={citiesLoading} size="small">
|
||||||
<Space wrap style={{ marginTop: 12 }}>
|
<Space wrap style={{ marginTop: 12 }}>
|
||||||
{cities.map((city) => (
|
{cities.map((city) => (
|
||||||
<Tag.CheckableTag
|
<button
|
||||||
key={city.id}
|
key={city.id}
|
||||||
checked={selectedCities.includes(city.name)}
|
type="button"
|
||||||
onChange={() => onToggle(city.name)}
|
aria-pressed={selectedCities.includes(city.name)}
|
||||||
|
aria-label={`${city.name}${selectedCities.includes(city.name) ? ',已选择' : ',未选择'}`}
|
||||||
|
onClick={() => onToggle(city.name)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
onToggle(city.name);
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
appearance: 'none',
|
||||||
|
border: selectedCities.includes(city.name)
|
||||||
|
? '1px solid #1677ff'
|
||||||
|
: '1px solid #d9d9d9',
|
||||||
|
background: selectedCities.includes(city.name) ? '#e6f4ff' : '#fafafa',
|
||||||
|
color: selectedCities.includes(city.name) ? '#1677ff' : 'rgba(0, 0, 0, 0.88)',
|
||||||
|
cursor: 'pointer',
|
||||||
padding: '4px 12px',
|
padding: '4px 12px',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{city.name}
|
{city.name}
|
||||||
</Tag.CheckableTag>
|
</button>
|
||||||
))}
|
))}
|
||||||
{selectedCities.length > 0 && (
|
{selectedCities.length > 0 && (
|
||||||
<Button type="link" size="small" onClick={onClear}>
|
<Button type="link" size="small" onClick={onClear}>
|
||||||
@@ -394,88 +440,80 @@ const DataStatsTab: React.FC = () => {
|
|||||||
const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange());
|
const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange());
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
|
fairCount: 0,
|
||||||
totalCompanies: 0,
|
totalCompanies: 0,
|
||||||
totalJobs: 0,
|
totalJobs: 0,
|
||||||
totalVacancies: 0,
|
totalVacancies: 0,
|
||||||
totalViews: 0,
|
totalViews: 0,
|
||||||
industryDistribution: [] as { industry: string; count: number; percentage: string }[],
|
fairs: [] as CrossDomainStatisticsFairItem[],
|
||||||
monthlyJobs: [] as { month: string; count: number }[],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const loadStats = useCallback(async (range: [Dayjs, Dayjs]) => {
|
||||||
const loadStats = async () => {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const startDate = dateRange[0].format('YYYY-MM-DD');
|
const startDate = range[0].format('YYYY-MM-DD');
|
||||||
const endDate = dateRange[1].format('YYYY-MM-DD');
|
const endDate = range[1].format('YYYY-MM-DD');
|
||||||
const [statisticsRes, jobRes] = await Promise.all([
|
const statisticsRes = await getCrossDomainStatistics({ startDate, endDate });
|
||||||
getCrossDomainStatistics({ startDate, endDate }),
|
|
||||||
getCrossDomainJobs({
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
current: 1,
|
|
||||||
pageSize: PAGE_SIZE_FOR_STATS,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
if (statisticsRes.code !== 200 || !statisticsRes.data) {
|
if (statisticsRes.code !== 200 || !statisticsRes.data) {
|
||||||
throw new Error(statisticsRes.msg || '跨域招聘会统计加载失败');
|
throw new Error(statisticsRes.msg || '跨域招聘会统计加载失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 岗位可能被多场跨域招聘会引用,图表也必须按岗位 ID 去重。
|
|
||||||
const jobs = Array.from(
|
|
||||||
new Map((jobRes.rows || []).map((job) => [String(job.jobId), job])).values(),
|
|
||||||
);
|
|
||||||
const totalJobs = statisticsRes.data.jobCount;
|
|
||||||
const industryCount = jobs.reduce<Record<string, number>>((acc, job) => {
|
|
||||||
const industry = job.industry || '其他';
|
|
||||||
acc[industry] = (acc[industry] || 0) + 1;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
const monthlyCount = jobs.reduce<Record<string, number>>((acc, job) => {
|
|
||||||
if (!job.postingDate) return acc;
|
|
||||||
const month = String(job.postingDate).slice(0, 7);
|
|
||||||
acc[month] = (acc[month] || 0) + 1;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
|
fairCount: statisticsRes.data.fairCount,
|
||||||
totalCompanies: statisticsRes.data.companyCount,
|
totalCompanies: statisticsRes.data.companyCount,
|
||||||
totalJobs,
|
totalJobs: statisticsRes.data.jobCount,
|
||||||
totalVacancies: statisticsRes.data.demandCount,
|
totalVacancies: statisticsRes.data.demandCount,
|
||||||
totalViews: statisticsRes.data.viewCount,
|
totalViews: statisticsRes.data.viewCount,
|
||||||
industryDistribution: Object.entries(industryCount).map(([industry, count]) => ({
|
fairs: statisticsRes.data.fairs || [],
|
||||||
industry,
|
|
||||||
count,
|
|
||||||
percentage: totalJobs > 0 ? ((count / totalJobs) * 100).toFixed(1) : '0.0',
|
|
||||||
})),
|
|
||||||
monthlyJobs: Object.entries(monthlyCount)
|
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
|
||||||
.map(([month, count]) => ({ month, count })),
|
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '跨域招聘会统计加载失败,请稍后重试');
|
message.error(error?.message || '跨域招聘会统计加载失败,请稍后重试');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
loadStats();
|
|
||||||
}, [dateRange]);
|
|
||||||
|
|
||||||
const industryColumns = [
|
useEffect(() => {
|
||||||
{ title: '行业', dataIndex: 'industry', key: 'industry' },
|
loadStats(getDefaultDateRange());
|
||||||
{ title: '岗位数量', dataIndex: 'count', key: 'count', align: 'center' as const },
|
}, [loadStats]);
|
||||||
|
|
||||||
|
const fairColumns = [
|
||||||
|
{ title: '招聘会名称', dataIndex: 'fairTitle', key: 'fairTitle', ellipsis: true },
|
||||||
{
|
{
|
||||||
title: '占比',
|
title: '起始时间',
|
||||||
dataIndex: 'percentage',
|
dataIndex: 'startTime',
|
||||||
key: 'percentage',
|
key: 'startTime',
|
||||||
align: 'center' as const,
|
width: 180,
|
||||||
render: (val: string) => `${val}%`,
|
render: (value: string) => value || '--',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '参会企业',
|
||||||
|
dataIndex: 'companyCount',
|
||||||
|
key: 'companyCount',
|
||||||
|
width: 110,
|
||||||
|
align: 'right' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发布岗位数',
|
||||||
|
dataIndex: 'jobCount',
|
||||||
|
key: 'jobCount',
|
||||||
|
width: 110,
|
||||||
|
align: 'right' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '招聘需求人数',
|
||||||
|
dataIndex: 'demandCount',
|
||||||
|
key: 'demandCount',
|
||||||
|
width: 120,
|
||||||
|
align: 'right' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '岗位浏览量',
|
||||||
|
dataIndex: 'viewCount',
|
||||||
|
key: 'viewCount',
|
||||||
|
width: 110,
|
||||||
|
align: 'right' as const,
|
||||||
},
|
},
|
||||||
];
|
|
||||||
|
|
||||||
const monthlyColumns = [
|
|
||||||
{ title: '月份', dataIndex: 'month', key: 'month' },
|
|
||||||
{ title: '发布岗位数', dataIndex: 'count', key: 'count', align: 'center' as const },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -485,21 +523,44 @@ const DataStatsTab: React.FC = () => {
|
|||||||
<Space wrap>
|
<Space wrap>
|
||||||
<span>统计时间</span>
|
<span>统计时间</span>
|
||||||
<RangePicker
|
<RangePicker
|
||||||
|
id={{
|
||||||
|
start: 'cross-city-statistics-start-date',
|
||||||
|
end: 'cross-city-statistics-end-date',
|
||||||
|
}}
|
||||||
|
aria-label="统计起止日期"
|
||||||
value={dateRange}
|
value={dateRange}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
presets={getDateRangePresets()}
|
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value?.[0] && value?.[1]) {
|
if (value?.[0] && value?.[1]) {
|
||||||
setDateRange(value as [Dayjs, Dayjs]);
|
setDateRange(value as [Dayjs, Dayjs]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Dropdown
|
||||||
|
menu={{
|
||||||
|
items: quickDateRangeItems,
|
||||||
|
onClick: ({ key }) => setDateRange(getQuickDateRange(key as QuickDateRangeKey)),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
时间快速选择 <DownOutlined />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => loadStats(dateRange)}
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Card title={`统计结果(共 ${stats.fairCount} 场线上招聘会)`} style={{ marginBottom: 16 }}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Card>
|
<Card size="small" style={{ background: '#fafafa' }}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="参会企业"
|
title="参会企业"
|
||||||
value={stats.totalCompanies}
|
value={stats.totalCompanies}
|
||||||
@@ -509,7 +570,7 @@ const DataStatsTab: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Card>
|
<Card size="small" style={{ background: '#fafafa' }}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="发布岗位数"
|
title="发布岗位数"
|
||||||
value={stats.totalJobs}
|
value={stats.totalJobs}
|
||||||
@@ -519,7 +580,7 @@ const DataStatsTab: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Card>
|
<Card size="small" style={{ background: '#fafafa' }}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="招聘需求人数"
|
title="招聘需求人数"
|
||||||
value={stats.totalVacancies}
|
value={stats.totalVacancies}
|
||||||
@@ -529,7 +590,7 @@ const DataStatsTab: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Card>
|
<Card size="small" style={{ background: '#f0f5ff' }}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="岗位浏览量"
|
title="岗位浏览量"
|
||||||
value={stats.totalViews}
|
value={stats.totalViews}
|
||||||
@@ -539,35 +600,41 @@ const DataStatsTab: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Row gutter={[16, 16]}>
|
<Card
|
||||||
<Col xs={24} lg={12}>
|
title="涉及招聘会明细"
|
||||||
<Card title="行业分布统计" style={{ marginBottom: 16 }}>
|
style={{ marginBottom: 16 }}
|
||||||
<Table
|
styles={{ body: { paddingTop: 8 } }}
|
||||||
rowKey="industry"
|
>
|
||||||
columns={industryColumns}
|
<Table<CrossDomainStatisticsFairItem>
|
||||||
dataSource={stats.industryDistribution}
|
rowKey="fairId"
|
||||||
|
columns={fairColumns}
|
||||||
|
dataSource={stats.fairs}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
|
scroll={{ x: 860 }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
<InfoCircleOutlined style={{ color: '#1677ff', marginRight: 8 }} />
|
||||||
|
统计口径
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Row gutter={[24, 8]}>
|
||||||
|
<Col xs={24} lg={12}>
|
||||||
|
仅统计时间范围内招聘会类型为“线上”且标记为“跨域”的招聘会;参会企业按有效报名企业统计,即使企业未选择岗位也计入。
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}>
|
||||||
<Card title="月度岗位发布趋势" style={{ marginBottom: 16 }}>
|
岗位、招聘需求人数和浏览量均按岗位 ID 去重,招聘数为空、填写“若干”或非正数时按 1
|
||||||
<Table
|
人计算。
|
||||||
rowKey="month"
|
|
||||||
columns={monthlyColumns}
|
|
||||||
dataSource={stats.monthlyJobs}
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card size="small" title="统计口径">
|
|
||||||
仅统计时间范围内招聘会类型为“线上”且标记为“跨域”的招聘会;参会企业按有效报名企业统计,即使企业未选择岗位也计入。岗位、招聘需求人数和浏览量均按岗位
|
|
||||||
ID 去重,招聘数为空、填写“若干”或非正数时按 1 人计算。
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
|
Dropdown,
|
||||||
Empty,
|
Empty,
|
||||||
Row,
|
Row,
|
||||||
Segmented,
|
Segmented,
|
||||||
Select,
|
Select,
|
||||||
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
Statistic,
|
Statistic,
|
||||||
Table,
|
Table,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
import { DownOutlined, InfoCircleOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import { getPublicJobFairList } from '@/services/jobfair/publicJobFair';
|
import { getPublicJobFairList } from '@/services/jobfair/publicJobFair';
|
||||||
@@ -29,6 +31,7 @@ import {
|
|||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
type StatisticsScope = 'single' | 'range';
|
type StatisticsScope = 'single' | 'range';
|
||||||
|
type QuickDateRangeKey = '7days' | '30days' | '1year';
|
||||||
|
|
||||||
interface FairOption {
|
interface FairOption {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -38,11 +41,25 @@ interface FairOption {
|
|||||||
const formatDate = (value: any): string | undefined =>
|
const formatDate = (value: any): string | undefined =>
|
||||||
value && typeof value.format === 'function' ? value.format('YYYY-MM-DD') : undefined;
|
value && typeof value.format === 'function' ? value.format('YYYY-MM-DD') : undefined;
|
||||||
|
|
||||||
const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'month'), dayjs()];
|
const getDefaultDateRange = (): [Dayjs, Dayjs] => [dayjs().subtract(1, 'year'), dayjs()];
|
||||||
|
|
||||||
const getDateRangePresets = () => [
|
const getDateRangePresets = () => [
|
||||||
{ label: '近7天', value: [dayjs().subtract(6, 'day'), dayjs()] as [Dayjs, Dayjs] },
|
{ label: '近7天', value: [dayjs().subtract(6, 'day'), dayjs()] as [Dayjs, Dayjs] },
|
||||||
{ label: '近1个月', value: getDefaultDateRange() },
|
{ label: '近30天', value: [dayjs().subtract(29, 'day'), dayjs()] as [Dayjs, Dayjs] },
|
||||||
|
{ label: '近1年', value: getDefaultDateRange() },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getQuickDateRange = (key: QuickDateRangeKey): [Dayjs, Dayjs] => {
|
||||||
|
const end = dayjs();
|
||||||
|
if (key === '7days') return [end.subtract(6, 'day'), end];
|
||||||
|
if (key === '30days') return [end.subtract(29, 'day'), end];
|
||||||
|
return [end.subtract(1, 'year'), end];
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickDateRangeItems = [
|
||||||
|
{ key: '7days', label: '近7天' },
|
||||||
|
{ key: '30days', label: '近30天' },
|
||||||
|
{ key: '1year', label: '近1年' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FairStatistics: React.FC = () => {
|
const FairStatistics: React.FC = () => {
|
||||||
@@ -50,7 +67,8 @@ const FairStatistics: React.FC = () => {
|
|||||||
const [scope, setScope] = useState<StatisticsScope>('range');
|
const [scope, setScope] = useState<StatisticsScope>('range');
|
||||||
const [fairOptions, setFairOptions] = useState<FairOption[]>([]);
|
const [fairOptions, setFairOptions] = useState<FairOption[]>([]);
|
||||||
const [selectedFairId, setSelectedFairId] = useState<string>();
|
const [selectedFairId, setSelectedFairId] = useState<string>();
|
||||||
const [dateRange, setDateRange] = useState<any>(getDefaultDateRange());
|
const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange());
|
||||||
|
const [queryDateRange, setQueryDateRange] = useState<[Dayjs, Dayjs]>(getDefaultDateRange());
|
||||||
const [loadingFairs, setLoadingFairs] = useState(false);
|
const [loadingFairs, setLoadingFairs] = useState(false);
|
||||||
const [loadingSummary, setLoadingSummary] = useState(false);
|
const [loadingSummary, setLoadingSummary] = useState(false);
|
||||||
const [summary, setSummary] = useState<FairStatisticsSummary>();
|
const [summary, setSummary] = useState<FairStatisticsSummary>();
|
||||||
@@ -106,6 +124,7 @@ const FairStatistics: React.FC = () => {
|
|||||||
// 切换类型时同步清空条件,避免新类型携带旧类型招聘会 ID 发起查询。
|
// 切换类型时同步清空条件,避免新类型携带旧类型招聘会 ID 发起查询。
|
||||||
setSelectedFairId(undefined);
|
setSelectedFairId(undefined);
|
||||||
setDateRange(getDefaultDateRange());
|
setDateRange(getDefaultDateRange());
|
||||||
|
setQueryDateRange(getDefaultDateRange());
|
||||||
setSummary(undefined);
|
setSummary(undefined);
|
||||||
setFairOptions([]);
|
setFairOptions([]);
|
||||||
setFairType(type);
|
setFairType(type);
|
||||||
@@ -113,8 +132,8 @@ const FairStatistics: React.FC = () => {
|
|||||||
|
|
||||||
const queryReady = useMemo(() => {
|
const queryReady = useMemo(() => {
|
||||||
if (scope === 'single') return Boolean(selectedFairId);
|
if (scope === 'single') return Boolean(selectedFairId);
|
||||||
return Boolean(dateRange?.[0] && dateRange?.[1]);
|
return Boolean(queryDateRange?.[0] && queryDateRange?.[1]);
|
||||||
}, [dateRange, scope, selectedFairId]);
|
}, [queryDateRange, scope, selectedFairId]);
|
||||||
|
|
||||||
const loadSummary = useCallback(async () => {
|
const loadSummary = useCallback(async () => {
|
||||||
if (!queryReady) {
|
if (!queryReady) {
|
||||||
@@ -126,8 +145,8 @@ const FairStatistics: React.FC = () => {
|
|||||||
const res = await getFairStatisticsSummary({
|
const res = await getFairStatisticsSummary({
|
||||||
fairType,
|
fairType,
|
||||||
fairId: scope === 'single' ? selectedFairId : undefined,
|
fairId: scope === 'single' ? selectedFairId : undefined,
|
||||||
startDate: scope === 'range' ? formatDate(dateRange?.[0]) : undefined,
|
startDate: scope === 'range' ? formatDate(queryDateRange?.[0]) : undefined,
|
||||||
endDate: scope === 'range' ? formatDate(dateRange?.[1]) : undefined,
|
endDate: scope === 'range' ? formatDate(queryDateRange?.[1]) : undefined,
|
||||||
});
|
});
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
setSummary(res.data);
|
setSummary(res.data);
|
||||||
@@ -141,7 +160,7 @@ const FairStatistics: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoadingSummary(false);
|
setLoadingSummary(false);
|
||||||
}
|
}
|
||||||
}, [dateRange, fairType, queryReady, scope, selectedFairId]);
|
}, [fairType, queryDateRange, queryReady, scope, selectedFairId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSummary();
|
loadSummary();
|
||||||
@@ -174,7 +193,7 @@ const FairStatistics: React.FC = () => {
|
|||||||
<Card style={{ marginBottom: 16 }}>
|
<Card style={{ marginBottom: 16 }}>
|
||||||
<Row gutter={[16, 16]} align="middle">
|
<Row gutter={[16, 16]} align="middle">
|
||||||
<Col>
|
<Col>
|
||||||
<Button.Group>
|
<Space.Compact>
|
||||||
<Button
|
<Button
|
||||||
type={fairType === 'online' ? 'primary' : 'default'}
|
type={fairType === 'online' ? 'primary' : 'default'}
|
||||||
onClick={() => handleFairTypeChange('online')}
|
onClick={() => handleFairTypeChange('online')}
|
||||||
@@ -187,7 +206,7 @@ const FairStatistics: React.FC = () => {
|
|||||||
>
|
>
|
||||||
户外招聘会
|
户外招聘会
|
||||||
</Button>
|
</Button>
|
||||||
</Button.Group>
|
</Space.Compact>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<span style={{ marginRight: 8 }}>统计范围</span>
|
<span style={{ marginRight: 8 }}>统计范围</span>
|
||||||
@@ -221,14 +240,40 @@ const FairStatistics: React.FC = () => {
|
|||||||
style={{ width: '100%', minWidth: 320 }}
|
style={{ width: '100%', minWidth: 320 }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<Space wrap style={{ width: '100%' }}>
|
||||||
<RangePicker
|
<RangePicker
|
||||||
|
id={{ start: 'fair-statistics-start-date', end: 'fair-statistics-end-date' }}
|
||||||
|
aria-label="统计起止日期"
|
||||||
value={dateRange}
|
value={dateRange}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
presets={getDateRangePresets()}
|
presets={getDateRangePresets()}
|
||||||
onChange={(value) => setDateRange(value)}
|
onChange={(value) => {
|
||||||
style={{ width: '100%', minWidth: 320 }}
|
if (value?.[0] && value?.[1]) {
|
||||||
|
setDateRange(value as [Dayjs, Dayjs]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ minWidth: 320 }}
|
||||||
placeholder={['起始日期', '结束日期']}
|
placeholder={['起始日期', '结束日期']}
|
||||||
/>
|
/>
|
||||||
|
<Dropdown
|
||||||
|
menu={{
|
||||||
|
items: quickDateRangeItems,
|
||||||
|
onClick: ({ key }) => setDateRange(getQuickDateRange(key as QuickDateRangeKey)),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
时间快速选择 <DownOutlined />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
loading={loadingSummary}
|
||||||
|
onClick={() => setQueryDateRange([...dateRange])}
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -266,7 +311,11 @@ const FairStatistics: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{summary && (
|
{summary && (
|
||||||
<Card title="涉及招聘会明细" style={{ marginBottom: 16 }} bodyStyle={{ paddingTop: 8 }}>
|
<Card
|
||||||
|
title="涉及招聘会明细"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
styles={{ body: { paddingTop: 8 } }}
|
||||||
|
>
|
||||||
<Table<FairStatisticsFairItem>
|
<Table<FairStatisticsFairItem>
|
||||||
rowKey="fairId"
|
rowKey="fairId"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ const InstitutionManagement: React.FC = () => {
|
|||||||
dataIndex: 'institutionName',
|
dataIndex: 'institutionName',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 260,
|
width: 260,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'job-fair-institution-name-search',
|
||||||
|
name: 'institutionName',
|
||||||
|
'aria-label': '机构名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '机构简介',
|
title: '机构简介',
|
||||||
@@ -155,13 +161,27 @@ const InstitutionManagement: React.FC = () => {
|
|||||||
name="institutionName"
|
name="institutionName"
|
||||||
label="机构名称"
|
label="机构名称"
|
||||||
rules={[{ required: true, message: '请输入机构名称' }]}
|
rules={[{ required: true, message: '请输入机构名称' }]}
|
||||||
fieldProps={{ maxLength: 200, showCount: true }}
|
fieldProps={{
|
||||||
|
id: 'job-fair-institution-name-input',
|
||||||
|
name: 'institutionName',
|
||||||
|
'aria-label': '机构名称',
|
||||||
|
autoComplete: 'organization',
|
||||||
|
maxLength: 200,
|
||||||
|
showCount: true,
|
||||||
|
}}
|
||||||
placeholder="请输入机构名称"
|
placeholder="请输入机构名称"
|
||||||
/>
|
/>
|
||||||
<ProFormTextArea
|
<ProFormTextArea
|
||||||
name="institutionIntro"
|
name="institutionIntro"
|
||||||
label="机构简介"
|
label="机构简介"
|
||||||
fieldProps={{ maxLength: 2000, showCount: true, rows: 5 }}
|
fieldProps={{
|
||||||
|
id: 'job-fair-institution-intro-input',
|
||||||
|
name: 'institutionIntro',
|
||||||
|
'aria-label': '机构简介',
|
||||||
|
maxLength: 2000,
|
||||||
|
showCount: true,
|
||||||
|
rows: 5,
|
||||||
|
}}
|
||||||
placeholder="请输入机构简介"
|
placeholder="请输入机构简介"
|
||||||
/>
|
/>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
|
|||||||
@@ -1,33 +1,81 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { useAccess } from '@umijs/max';
|
import { useAccess } from '@umijs/max';
|
||||||
import { Button, Modal, message } from 'antd';
|
import { Button, Modal, Select, message } from 'antd';
|
||||||
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormDateTimePicker } from '@ant-design/pro-components';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
getOutdoorFairAttendeeList,
|
|
||||||
addOutdoorFairAttendee,
|
addOutdoorFairAttendee,
|
||||||
updateOutdoorFairAttendee,
|
|
||||||
deleteOutdoorFairAttendee,
|
deleteOutdoorFairAttendee,
|
||||||
|
getAvailableOutdoorFairAttendeeUsers,
|
||||||
|
getOutdoorFairAttendeeList,
|
||||||
} from '@/services/jobportal/outdoorFairAttendee';
|
} from '@/services/jobportal/outdoorFairAttendee';
|
||||||
import type {
|
import type {
|
||||||
OutdoorFairAttendeeItem,
|
OutdoorFairAttendeeItem,
|
||||||
OutdoorFairAttendeeForm,
|
OutdoorFairAttendeeUserOption,
|
||||||
} from '@/services/jobportal/outdoorFairAttendee';
|
} from '@/services/jobportal/outdoorFairAttendee';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fairId: number;
|
fairId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getUserLabel = (user: OutdoorFairAttendeeUserOption) => {
|
||||||
|
const name = user.name || '未填写姓名';
|
||||||
|
return user.phone ? `${name}(${user.phone})` : name;
|
||||||
|
};
|
||||||
|
|
||||||
const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [currentRow, setCurrentRow] = useState<OutdoorFairAttendeeItem>();
|
const [userLoading, setUserLoading] = useState(false);
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
|
const [userOptions, setUserOptions] = useState<OutdoorFairAttendeeUserOption[]>([]);
|
||||||
|
|
||||||
|
const loadUserOptions = async (keyword?: string) => {
|
||||||
|
setUserLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getAvailableOutdoorFairAttendeeUsers({ fairId, keyword });
|
||||||
|
if (res.code === 200) {
|
||||||
|
setUserOptions(res.data || []);
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || '移动端用户加载失败');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setUserOptions([]);
|
||||||
|
message.error('移动端用户加载失败');
|
||||||
|
} finally {
|
||||||
|
setUserLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openAddModal = () => {
|
||||||
|
setUserOptions([]);
|
||||||
|
setModalVisible(true);
|
||||||
|
loadUserOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUserSelect = async (userId: number) => {
|
||||||
|
setSubmitLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await addOutdoorFairAttendee({ fairId, userId });
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('签到成功');
|
||||||
|
setModalVisible(false);
|
||||||
|
actionRef.current?.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(res.msg || '签到失败');
|
||||||
|
} catch {
|
||||||
|
message.error('签到失败,请重试');
|
||||||
|
} finally {
|
||||||
|
setSubmitLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: '确定要删除该签到人员吗?',
|
content: '确定要删除该签到记录吗?',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
const res = await deleteOutdoorFairAttendee(id);
|
const res = await deleteOutdoorFairAttendee(id);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@@ -41,9 +89,19 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns<OutdoorFairAttendeeItem>[] = [
|
const columns: ProColumns<OutdoorFairAttendeeItem>[] = [
|
||||||
{ title: '人员', dataIndex: 'name', ellipsis: true },
|
{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-attendee-name-search',
|
||||||
|
name: 'name',
|
||||||
|
'aria-label': '参会人员姓名',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{ title: '手机号', dataIndex: 'phone', ellipsis: true, hideInSearch: true },
|
{ title: '手机号', dataIndex: 'phone', ellipsis: true, hideInSearch: true },
|
||||||
{ title: '住址', dataIndex: 'address', ellipsis: true, hideInSearch: true },
|
{ title: '地址', dataIndex: 'address', ellipsis: true, hideInSearch: true },
|
||||||
{
|
{
|
||||||
title: '签到时间',
|
title: '签到时间',
|
||||||
dataIndex: 'checkInTime',
|
dataIndex: 'checkInTime',
|
||||||
@@ -53,22 +111,9 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
width: 160,
|
width: 100,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (_, record) => [
|
render: (_, record) => [
|
||||||
<Button
|
|
||||||
key="edit"
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
icon={<EditOutlined />}
|
|
||||||
hidden={!access.hasPerms('cms:outdoorFair:edit')}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentRow(record);
|
|
||||||
setModalVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</Button>,
|
|
||||||
<Button
|
<Button
|
||||||
key="delete"
|
key="delete"
|
||||||
type="link"
|
type="link"
|
||||||
@@ -100,10 +145,7 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
hidden={!access.hasPerms('cms:outdoorFair:add')}
|
hidden={!access.hasPerms('cms:outdoorFair:add')}
|
||||||
onClick={() => {
|
onClick={openAddModal}
|
||||||
setCurrentRow(undefined);
|
|
||||||
setModalVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
录入签到人员
|
录入签到人员
|
||||||
</Button>,
|
</Button>,
|
||||||
@@ -120,53 +162,34 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModalForm<OutdoorFairAttendeeForm>
|
<Modal
|
||||||
title={currentRow ? '编辑签到人员' : '录入签到人员'}
|
title="录入签到人员"
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
modalProps={{ destroyOnClose: true, onCancel: () => setModalVisible(false) }}
|
destroyOnClose
|
||||||
initialValues={
|
footer={null}
|
||||||
currentRow
|
onCancel={() => setModalVisible(false)}
|
||||||
? {
|
|
||||||
id: currentRow.id,
|
|
||||||
fairId,
|
|
||||||
name: currentRow.name,
|
|
||||||
phone: currentRow.phone,
|
|
||||||
address: currentRow.address,
|
|
||||||
checkInTime: currentRow.checkInTime,
|
|
||||||
}
|
|
||||||
: { fairId }
|
|
||||||
}
|
|
||||||
onFinish={async (values) => {
|
|
||||||
const payload = { ...values, fairId };
|
|
||||||
const res = values.id
|
|
||||||
? await updateOutdoorFairAttendee(payload)
|
|
||||||
: await addOutdoorFairAttendee(payload);
|
|
||||||
if (res.code === 200) {
|
|
||||||
message.success(values.id ? '修改成功' : '新增成功');
|
|
||||||
setModalVisible(false);
|
|
||||||
setCurrentRow(undefined);
|
|
||||||
actionRef.current?.reload();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
message.error(res.msg || '操作失败');
|
|
||||||
return false;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ProFormText
|
<div style={{ marginBottom: 8 }}>请选择移动端用户,选择后将立即完成签到:</div>
|
||||||
name="name"
|
<Select<number>
|
||||||
label="人员"
|
showSearch
|
||||||
placeholder="请输入人员姓名"
|
allowClear
|
||||||
rules={[{ required: true, message: '请输入人员姓名' }]}
|
autoFocus
|
||||||
|
loading={userLoading || submitLoading}
|
||||||
|
disabled={submitLoading}
|
||||||
|
placeholder="请选择移动端用户"
|
||||||
|
id="outdoor-attendee-user-select"
|
||||||
|
aria-label="选择移动端用户"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
optionFilterProp="label"
|
||||||
|
options={userOptions.map((user) => ({
|
||||||
|
value: user.userId,
|
||||||
|
label: getUserLabel(user),
|
||||||
|
}))}
|
||||||
|
onSearch={loadUserOptions}
|
||||||
|
onSelect={handleUserSelect}
|
||||||
|
notFoundContent={userLoading ? '加载中...' : '暂无可签到用户'}
|
||||||
/>
|
/>
|
||||||
<ProFormText name="phone" label="手机号" placeholder="请输入手机号" />
|
</Modal>
|
||||||
<ProFormText name="address" label="住址" placeholder="请输入住址" />
|
|
||||||
<ProFormDateTimePicker
|
|
||||||
name="checkInTime"
|
|
||||||
label="签到时间"
|
|
||||||
placeholder="请选择签到时间"
|
|
||||||
fieldProps={{ style: { width: '100%' } }}
|
|
||||||
/>
|
|
||||||
</ModalForm>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Col, Row, Tag } from 'antd';
|
||||||
|
import { ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
import { getOutdoorFairCompanyAttendanceList } from '@/services/jobportal/outdoorFairDetail';
|
||||||
|
import type { OutdoorFairCompanyAttendanceItem } from '@/services/jobportal/outdoorFairDetail';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fairId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getColumns = (checkedIn: boolean): ProColumns<OutdoorFairCompanyAttendanceItem>[] => [
|
||||||
|
{
|
||||||
|
title: '企业名称',
|
||||||
|
dataIndex: 'companyName',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: `outdoor-company-attendance-${checkedIn ? 'checked-in' : 'not-checked-in'}-search`,
|
||||||
|
name: `companyName${checkedIn ? 'CheckedIn' : 'NotCheckedIn'}`,
|
||||||
|
'aria-label': `${checkedIn ? '已签到' : '未签到'}企业名称`,
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '行业',
|
||||||
|
dataIndex: 'industry',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record.industry || '--',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '联系人',
|
||||||
|
dataIndex: 'contactPerson',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record.contactPerson || '--',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '签到时间',
|
||||||
|
dataIndex: 'checkInTime',
|
||||||
|
valueType: 'dateTime',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record.checkInTime || '--',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CompanyAttendanceList: React.FC<{ fairId: number; checkedIn: boolean }> = ({
|
||||||
|
fairId,
|
||||||
|
checkedIn,
|
||||||
|
}) => (
|
||||||
|
<ProTable<OutdoorFairCompanyAttendanceItem>
|
||||||
|
rowKey="id"
|
||||||
|
columns={getColumns(checkedIn)}
|
||||||
|
search={{ labelWidth: 'auto' }}
|
||||||
|
options={false}
|
||||||
|
pagination={{ pageSize: 8 }}
|
||||||
|
request={async (params) => {
|
||||||
|
const res = await getOutdoorFairCompanyAttendanceList(fairId, {
|
||||||
|
current: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
companyName: params.companyName,
|
||||||
|
checkedIn,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: res.rows || [],
|
||||||
|
total: res.total || 0,
|
||||||
|
success: res.code === 200,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CompanyAttendanceTab: React.FC<Props> = ({ fairId }) => (
|
||||||
|
<Row gutter={16} align="top">
|
||||||
|
<Col span={12}>
|
||||||
|
<Card title="已签到企业" extra={<Tag color="green">已签到</Tag>} bodyStyle={{ padding: 0 }}>
|
||||||
|
<CompanyAttendanceList fairId={fairId} checkedIn />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card title="未签到企业" extra={<Tag color="orange">未签到</Tag>} bodyStyle={{ padding: 0 }}>
|
||||||
|
<CompanyAttendanceList fairId={fairId} checkedIn={false} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CompanyAttendanceTab;
|
||||||
@@ -15,6 +15,12 @@ const CompanyUnsubscribeTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
dataIndex: 'companyName',
|
dataIndex: 'companyName',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
order: 1,
|
order: 1,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-company-unsubscribe-name-search',
|
||||||
|
name: 'companyName',
|
||||||
|
'aria-label': '企业名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ title: '联系人', dataIndex: 'contactPerson', width: 120, hideInSearch: true },
|
{ title: '联系人', dataIndex: 'contactPerson', width: 120, hideInSearch: true },
|
||||||
{ title: '联系电话', dataIndex: 'contactPhone', width: 150, hideInSearch: true },
|
{ title: '联系电话', dataIndex: 'contactPhone', width: 150, hideInSearch: true },
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
title: '设备码',
|
title: '设备码',
|
||||||
dataIndex: 'deviceCode',
|
dataIndex: 'deviceCode',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-device-code-search',
|
||||||
|
name: 'deviceCode',
|
||||||
|
'aria-label': '设备码',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Typography.Text copyable code>
|
<Typography.Text copyable code>
|
||||||
{record.deviceCode}
|
{record.deviceCode}
|
||||||
@@ -191,7 +197,13 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
name="deviceCode"
|
name="deviceCode"
|
||||||
label="设备码"
|
label="设备码"
|
||||||
placeholder="请输入设备码"
|
placeholder="请输入设备码"
|
||||||
fieldProps={{ maxLength: 64 }}
|
fieldProps={{
|
||||||
|
id: 'outdoor-device-code-input',
|
||||||
|
name: 'deviceCode',
|
||||||
|
'aria-label': '设备码',
|
||||||
|
autoComplete: 'off',
|
||||||
|
maxLength: 64,
|
||||||
|
}}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入设备码' },
|
{ required: true, message: '请输入设备码' },
|
||||||
{ max: 64, message: '设备码长度不能超过64个字符' },
|
{ max: 64, message: '设备码长度不能超过64个字符' },
|
||||||
@@ -201,6 +213,7 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
name="companyId"
|
name="companyId"
|
||||||
label="绑定企业"
|
label="绑定企业"
|
||||||
placeholder="选填,不选则创建未绑定的设备码"
|
placeholder="选填,不选则创建未绑定的设备码"
|
||||||
|
fieldProps={{ id: 'outdoor-device-company-input', 'aria-label': '绑定企业' }}
|
||||||
showSearch
|
showSearch
|
||||||
allowClear
|
allowClear
|
||||||
request={async () => {
|
request={async () => {
|
||||||
@@ -247,6 +260,7 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
label="参会企业"
|
label="参会企业"
|
||||||
placeholder="请选择参会企业"
|
placeholder="请选择参会企业"
|
||||||
showSearch
|
showSearch
|
||||||
|
fieldProps={{ id: 'outdoor-device-company-bind-select', 'aria-label': '参会企业' }}
|
||||||
request={async () => {
|
request={async () => {
|
||||||
const [companies, devicesRes] = await Promise.all([
|
const [companies, devicesRes] = await Promise.all([
|
||||||
getOutdoorFairCompanyOptions(fairId),
|
getOutdoorFairCompanyOptions(fairId),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getVenueInfo, getVenueInfoList } from '@/services/jobportal/venueInfo';
|
|||||||
import type { VenueInfoItem } from '@/services/jobportal/venueInfo';
|
import type { VenueInfoItem } from '@/services/jobportal/venueInfo';
|
||||||
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
|
||||||
import EditModal from '@/pages/Jobfair/Outdoorfair/components/EditModal';
|
import EditModal from '@/pages/Jobfair/Outdoorfair/components/EditModal';
|
||||||
|
import { normalizePublicUrl } from '@/utils/publicUrl';
|
||||||
|
|
||||||
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
||||||
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
||||||
@@ -138,6 +139,7 @@ const FairInfoTab: React.FC<Props> = ({ fairInfo, onFairUpdate }) => {
|
|||||||
{fairInfo.address}
|
{fairInfo.address}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="展位数量">{fairInfo.boothCount}</Descriptions.Item>
|
<Descriptions.Item label="展位数量">{fairInfo.boothCount}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="展位图数量">{fairInfo.boothMapCount ?? 0}</Descriptions.Item>
|
||||||
<Descriptions.Item label="举办时间">{fairInfo.holdTime}</Descriptions.Item>
|
<Descriptions.Item label="举办时间">{fairInfo.holdTime}</Descriptions.Item>
|
||||||
<Descriptions.Item label="结束时间">{fairInfo.endTime}</Descriptions.Item>
|
<Descriptions.Item label="结束时间">{fairInfo.endTime}</Descriptions.Item>
|
||||||
<Descriptions.Item label="报名开始时间">{fairInfo.applyStartTime}</Descriptions.Item>
|
<Descriptions.Item label="报名开始时间">{fairInfo.applyStartTime}</Descriptions.Item>
|
||||||
@@ -148,7 +150,7 @@ const FairInfoTab: React.FC<Props> = ({ fairInfo, onFairUpdate }) => {
|
|||||||
<Descriptions.Item label="招聘会照片" span={3}>
|
<Descriptions.Item label="招聘会照片" span={3}>
|
||||||
{fairInfo.photoUrl ? (
|
{fairInfo.photoUrl ? (
|
||||||
<Image
|
<Image
|
||||||
src={fairInfo.photoUrl}
|
src={normalizePublicUrl(fairInfo.photoUrl)}
|
||||||
alt={fairInfo.title}
|
alt={fairInfo.title}
|
||||||
width={240}
|
width={240}
|
||||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||||
@@ -192,9 +194,7 @@ const FairInfoTab: React.FC<Props> = ({ fairInfo, onFairUpdate }) => {
|
|||||||
<Descriptions.Item label="场地面积">
|
<Descriptions.Item label="场地面积">
|
||||||
{venueDetail.venueArea != null ? `${venueDetail.venueArea} ㎡` : '--'}
|
{venueDetail.venueArea != null ? `${venueDetail.venueArea} ㎡` : '--'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="展位数量">
|
<Descriptions.Item label="展位数量">{venueDetail.floorCount ?? '--'}</Descriptions.Item>
|
||||||
{venueDetail.floorCount ?? '--'}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="场地地址" span={2}>
|
<Descriptions.Item label="场地地址" span={2}>
|
||||||
{venueDetail.venueAddress || '--'}
|
{venueDetail.venueAddress || '--'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
@@ -204,15 +204,9 @@ const FairInfoTab: React.FC<Props> = ({ fairInfo, onFairUpdate }) => {
|
|||||||
<Descriptions.Item label="乘坐线路" span={3}>
|
<Descriptions.Item label="乘坐线路" span={3}>
|
||||||
{venueDetail.routeLineNames || '--'}
|
{venueDetail.routeLineNames || '--'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="经办日期">
|
<Descriptions.Item label="经办日期">{venueDetail.handleDate || '--'}</Descriptions.Item>
|
||||||
{venueDetail.handleDate || '--'}
|
<Descriptions.Item label="经办机构">{venueDetail.handleOrg || '--'}</Descriptions.Item>
|
||||||
</Descriptions.Item>
|
<Descriptions.Item label="经办人">{venueDetail.handler || '--'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="经办机构">
|
|
||||||
{venueDetail.handleOrg || '--'}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="经办人">
|
|
||||||
{venueDetail.handler || '--'}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="备注" span={3}>
|
<Descriptions.Item label="备注" span={3}>
|
||||||
{venueDetail.remark || '--'}
|
{venueDetail.remark || '--'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ type ReviewTarget =
|
|||||||
record: API.OutdoorFairDetail.PostedJob;
|
record: API.OutdoorFairDetail.PostedJob;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const REVIEW_STATUS_ENUM = {
|
||||||
|
'0': { text: '待审核', status: 'Processing' },
|
||||||
|
'1': { text: '已通过', status: 'Success' },
|
||||||
|
'2': { text: '已驳回', status: 'Error' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatSearchDateTime = (value: any) =>
|
||||||
|
value && typeof value.format === 'function' ? value.format('YYYY-MM-DD HH:mm:ss') : value;
|
||||||
|
|
||||||
const ParticipatingCompaniesTab: React.FC<Props> = ({ fairId }) => {
|
const ParticipatingCompaniesTab: React.FC<Props> = ({ fairId }) => {
|
||||||
const companyActionRef = useRef<ActionType>();
|
const companyActionRef = useRef<ActionType>();
|
||||||
const jobActionRef = useRef<ActionType>();
|
const jobActionRef = useRef<ActionType>();
|
||||||
@@ -264,11 +273,54 @@ const ParticipatingCompaniesTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
current: params.current,
|
current: params.current,
|
||||||
pageSize: params.pageSize,
|
pageSize: params.pageSize,
|
||||||
companyName: params.companyName,
|
companyName: params.companyName,
|
||||||
|
reviewStatus: params.reviewStatus,
|
||||||
|
startTime: formatSearchDateTime(params.startTime),
|
||||||
|
endTime: formatSearchDateTime(params.endTime),
|
||||||
});
|
});
|
||||||
return { data: res.rows || [], total: res.total || 0, success: res.code === 200 };
|
return { data: res.rows || [], total: res.total || 0, success: res.code === 200 };
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
|
{
|
||||||
|
title: '企业名称',
|
||||||
|
dataIndex: 'companyName',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-company-name-search',
|
||||||
|
name: 'companyName',
|
||||||
|
'aria-label': '企业名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '报名开始时间',
|
||||||
|
dataIndex: 'startTime',
|
||||||
|
hideInTable: true,
|
||||||
|
valueType: 'dateTime',
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-company-start-time-search',
|
||||||
|
name: 'startTime',
|
||||||
|
'aria-label': '报名开始时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ startTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '报名结束时间',
|
||||||
|
dataIndex: 'endTime',
|
||||||
|
hideInTable: true,
|
||||||
|
valueType: 'dateTime',
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-company-end-time-search',
|
||||||
|
name: 'endTime',
|
||||||
|
'aria-label': '报名结束时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ endTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '行业',
|
title: '行业',
|
||||||
dataIndex: 'industry',
|
dataIndex: 'industry',
|
||||||
@@ -298,9 +350,22 @@ const ParticipatingCompaniesTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
{
|
{
|
||||||
title: '审核状态',
|
title: '审核状态',
|
||||||
dataIndex: 'reviewStatus',
|
dataIndex: 'reviewStatus',
|
||||||
hideInSearch: true,
|
valueType: 'select',
|
||||||
|
valueEnum: REVIEW_STATUS_ENUM,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-company-review-status-search',
|
||||||
|
name: 'reviewStatus',
|
||||||
|
'aria-label': '审核状态',
|
||||||
|
},
|
||||||
render: (_, record) => renderReviewStatus(record.reviewStatus),
|
render: (_, record) => renderReviewStatus(record.reviewStatus),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '报名时间',
|
||||||
|
dataIndex: 'registrationTime',
|
||||||
|
valueType: 'dateTime',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 170,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '展位号',
|
title: '展位号',
|
||||||
dataIndex: 'boothNumber',
|
dataIndex: 'boothNumber',
|
||||||
|
|||||||
@@ -105,7 +105,17 @@ const ParticipatingJobsTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns<API.OutdoorFairDetail.PostedJob>[] = [
|
const columns: ProColumns<API.OutdoorFairDetail.PostedJob>[] = [
|
||||||
{ title: '岗位名称', dataIndex: 'jobTitle', ellipsis: true },
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
dataIndex: 'jobTitle',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-job-title-search',
|
||||||
|
name: 'jobTitle',
|
||||||
|
'aria-label': '岗位名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '企业名称',
|
title: '企业名称',
|
||||||
dataIndex: 'companyId',
|
dataIndex: 'companyId',
|
||||||
@@ -126,6 +136,10 @@ const ParticipatingJobsTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-job-company-search',
|
||||||
|
name: 'companyId',
|
||||||
|
'aria-label': '企业名称',
|
||||||
|
autoComplete: 'off',
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
optionFilterProp: 'label',
|
optionFilterProp: 'label',
|
||||||
placeholder: '请选择或搜索企业',
|
placeholder: '请选择或搜索企业',
|
||||||
@@ -144,6 +158,11 @@ const ParticipatingJobsTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: educationEnum,
|
valueEnum: educationEnum,
|
||||||
width: 110,
|
width: 110,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-job-education-search',
|
||||||
|
name: 'education',
|
||||||
|
'aria-label': '学历要求',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '工作经验',
|
title: '工作经验',
|
||||||
@@ -151,6 +170,11 @@ const ParticipatingJobsTab: React.FC<Props> = ({ fairId }) => {
|
|||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: experienceEnum,
|
valueEnum: experienceEnum,
|
||||||
width: 110,
|
width: 110,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-participating-job-experience-search',
|
||||||
|
name: 'experience',
|
||||||
|
'aria-label': '工作经验',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 },
|
{ title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 },
|
||||||
{ title: '工作地点', dataIndex: 'jobLocation', hideInSearch: true, ellipsis: true },
|
{ title: '工作地点', dataIndex: 'jobLocation', hideInSearch: true, ellipsis: true },
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ParticipatingCompaniesTab from './components/ParticipatingCompaniesTab';
|
|||||||
import ParticipatingJobsTab from './components/ParticipatingJobsTab';
|
import ParticipatingJobsTab from './components/ParticipatingJobsTab';
|
||||||
import CompanyUnsubscribeTab from './components/CompanyUnsubscribeTab';
|
import CompanyUnsubscribeTab from './components/CompanyUnsubscribeTab';
|
||||||
import AttendeeTab from './components/AttendeeTab';
|
import AttendeeTab from './components/AttendeeTab';
|
||||||
|
import CompanyAttendanceTab from './components/CompanyAttendanceTab';
|
||||||
import DeviceTab from './components/DeviceTab';
|
import DeviceTab from './components/DeviceTab';
|
||||||
|
|
||||||
const OutdoorFairDetail: React.FC = () => {
|
const OutdoorFairDetail: React.FC = () => {
|
||||||
@@ -43,6 +44,7 @@ const OutdoorFairDetail: React.FC = () => {
|
|||||||
const tabItems = [
|
const tabItems = [
|
||||||
{ key: 'fair-info', label: '招聘会管理' },
|
{ key: 'fair-info', label: '招聘会管理' },
|
||||||
{ key: 'participating-companies', label: '参会企业' },
|
{ key: 'participating-companies', label: '参会企业' },
|
||||||
|
{ key: 'company-attendance', label: '企业签到' },
|
||||||
{ key: 'company-unsubscribe', label: '企业退订' },
|
{ key: 'company-unsubscribe', label: '企业退订' },
|
||||||
{ key: 'participating-jobs', label: '参会岗位' },
|
{ key: 'participating-jobs', label: '参会岗位' },
|
||||||
{ key: 'attendee', label: '参会人员管理' },
|
{ key: 'attendee', label: '参会人员管理' },
|
||||||
@@ -88,6 +90,8 @@ const OutdoorFairDetail: React.FC = () => {
|
|||||||
return <BoothTab fairId={fairId} fairInfo={fairInfo} />;
|
return <BoothTab fairId={fairId} fairInfo={fairInfo} />;
|
||||||
case 'participating-companies':
|
case 'participating-companies':
|
||||||
return <ParticipatingCompaniesTab fairId={fairId} />;
|
return <ParticipatingCompaniesTab fairId={fairId} />;
|
||||||
|
case 'company-attendance':
|
||||||
|
return <CompanyAttendanceTab fairId={fairId} />;
|
||||||
case 'company-unsubscribe':
|
case 'company-unsubscribe':
|
||||||
return <CompanyUnsubscribeTab fairId={fairId} />;
|
return <CompanyUnsubscribeTab fairId={fairId} />;
|
||||||
case 'participating-jobs':
|
case 'participating-jobs':
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Descriptions, Divider, Image, Modal, Tag } from 'antd';
|
import { Descriptions, Divider, Image, Modal, Tag } from 'antd';
|
||||||
import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair';
|
import type { OutdoorFairItem } from '@/services/jobportal/outdoorFair';
|
||||||
|
import { normalizePublicUrl } from '@/utils/publicUrl';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fair?: OutdoorFairItem | null;
|
fair?: OutdoorFairItem | null;
|
||||||
@@ -33,17 +34,25 @@ const CompanyFairDetailModal: React.FC<Props> = ({ fair, open, onCancel }) => (
|
|||||||
{fair.address || '--'}
|
{fair.address || '--'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="展位数量">{fair.boothCount ?? '--'}</Descriptions.Item>
|
<Descriptions.Item label="展位数量">{fair.boothCount ?? '--'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="展位图数量">{fair.boothMapCount ?? 0}</Descriptions.Item>
|
||||||
<Descriptions.Item label="已预定摊位数">{fair.reservedBoothCount ?? 0}</Descriptions.Item>
|
<Descriptions.Item label="已预定摊位数">{fair.reservedBoothCount ?? 0}</Descriptions.Item>
|
||||||
<Descriptions.Item label="举办时间">{fair.holdTime || '--'}</Descriptions.Item>
|
<Descriptions.Item label="举办时间">{fair.holdTime || '--'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="结束时间">{fair.endTime || '--'}</Descriptions.Item>
|
<Descriptions.Item label="结束时间">{fair.endTime || '--'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="报名开始时间">{fair.applyStartTime || '--'}</Descriptions.Item>
|
<Descriptions.Item label="报名开始时间">{fair.applyStartTime || '--'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="报名截止时间">{fair.applyEndTime || '--'}</Descriptions.Item>
|
<Descriptions.Item label="报名截止时间">{fair.applyEndTime || '--'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="线上申请">
|
<Descriptions.Item label="线上申请">
|
||||||
<Tag color={fair.onlineApply ? 'green' : 'default'}>{fair.onlineApply ? '是' : '否'}</Tag>
|
<Tag color={fair.onlineApply ? 'green' : 'default'}>
|
||||||
|
{fair.onlineApply ? '是' : '否'}
|
||||||
|
</Tag>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="招聘会照片">
|
<Descriptions.Item label="招聘会照片">
|
||||||
{fair.photoUrl ? (
|
{fair.photoUrl ? (
|
||||||
<Image src={fair.photoUrl} alt={fair.title} width={180} style={{ objectFit: 'cover' }} />
|
<Image
|
||||||
|
src={normalizePublicUrl(fair.photoUrl)}
|
||||||
|
alt={fair.title}
|
||||||
|
width={180}
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
'--'
|
'--'
|
||||||
)}
|
)}
|
||||||
@@ -59,7 +68,9 @@ const CompanyFairDetailModal: React.FC<Props> = ({ fair, open, onCancel }) => (
|
|||||||
企业参会专属信息
|
企业参会专属信息
|
||||||
</Divider>
|
</Divider>
|
||||||
<Descriptions bordered column={1} size="small">
|
<Descriptions bordered column={1} size="small">
|
||||||
<Descriptions.Item label="需携带资料">{content(fair.requiredMaterials)}</Descriptions.Item>
|
<Descriptions.Item label="需携带资料">
|
||||||
|
{content(fair.requiredMaterials)}
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="到场须知">{content(fair.attendanceNotes)}</Descriptions.Item>
|
<Descriptions.Item label="到场须知">{content(fair.attendanceNotes)}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {
|
|||||||
OutdoorFairDictForm,
|
OutdoorFairDictForm,
|
||||||
} from '@/services/jobportal/outdoorFair';
|
} from '@/services/jobportal/outdoorFair';
|
||||||
import { uploadOutdoorFairPhoto } from '@/services/jobportal/outdoorFair';
|
import { uploadOutdoorFairPhoto } from '@/services/jobportal/outdoorFair';
|
||||||
|
import { normalizePublicUrl } from '@/utils/publicUrl';
|
||||||
|
|
||||||
interface EditModalProps {
|
interface EditModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -356,7 +357,7 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
</Upload>
|
</Upload>
|
||||||
{photoUrl ? (
|
{photoUrl ? (
|
||||||
<Image
|
<Image
|
||||||
src={photoUrl}
|
src={normalizePublicUrl(photoUrl)}
|
||||||
width={180}
|
width={180}
|
||||||
height={120}
|
height={120}
|
||||||
style={{ objectFit: 'cover', borderRadius: 6 }}
|
style={{ objectFit: 'cover', borderRadius: 6 }}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
UnorderedListOutlined,
|
UnorderedListOutlined,
|
||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
|
CheckOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
getOutdoorFairList,
|
getOutdoorFairList,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
signupOutdoorFair,
|
signupOutdoorFair,
|
||||||
unsubscribeOutdoorFair,
|
unsubscribeOutdoorFair,
|
||||||
getMyOutdoorFairQrCode,
|
getMyOutdoorFairQrCode,
|
||||||
|
checkInOutdoorFair,
|
||||||
getOutdoorFairMiniProgramQrCode,
|
getOutdoorFairMiniProgramQrCode,
|
||||||
exportOutdoorFair,
|
exportOutdoorFair,
|
||||||
} from '@/services/jobportal/outdoorFair';
|
} from '@/services/jobportal/outdoorFair';
|
||||||
@@ -45,6 +47,9 @@ import type { RecruitmentFairExportParams } from '@/services/jobfair/recruitment
|
|||||||
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
const OUTDOOR_FAIR_TYPE_DICT = 'outdoor_fair_type';
|
||||||
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
const OUTDOOR_FAIR_REGION_DICT = 'outdoor_fair_region';
|
||||||
|
|
||||||
|
const formatSearchDateTime = (value: any) =>
|
||||||
|
value && typeof value.format === 'function' ? value.format('YYYY-MM-DD HH:mm:ss') : value;
|
||||||
|
|
||||||
const OutdoorFairList: React.FC = () => {
|
const OutdoorFairList: React.FC = () => {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
@@ -183,7 +188,13 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
jobCount: 0,
|
jobCount: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSignup = async (record: OutdoorFairItem) => {
|
const handleSignup = (record: OutdoorFairItem) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '报名户外招聘会',
|
||||||
|
content: `确定报名「${record.title}」吗?报名后需要等待管理员审核。`,
|
||||||
|
okText: '确认报名',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: async () => {
|
||||||
const res = await signupOutdoorFair(record.id);
|
const res = await signupOutdoorFair(record.id);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
message.success('报名成功,等待审核');
|
message.success('报名成功,等待审核');
|
||||||
@@ -191,6 +202,18 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.msg || '报名失败');
|
message.error(res.msg || '报名失败');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckIn = async (record: OutdoorFairItem) => {
|
||||||
|
const res = await checkInOutdoorFair(record.id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('签到成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || '签到失败');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openQrCode = async (record: OutdoorFairItem) => {
|
const openQrCode = async (record: OutdoorFairItem) => {
|
||||||
@@ -292,7 +315,17 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const jobColumns: ProColumns<API.OutdoorFairDetail.PostedJob>[] = [
|
const jobColumns: ProColumns<API.OutdoorFairDetail.PostedJob>[] = [
|
||||||
{ title: '岗位名称', dataIndex: 'jobTitle', ellipsis: true },
|
{
|
||||||
|
title: '岗位名称',
|
||||||
|
dataIndex: 'jobTitle',
|
||||||
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-company-job-title-search',
|
||||||
|
name: 'jobTitle',
|
||||||
|
'aria-label': '岗位名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '薪资范围',
|
title: '薪资范围',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
@@ -304,6 +337,11 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: educationEnum,
|
valueEnum: educationEnum,
|
||||||
width: 110,
|
width: 110,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-company-job-education-search',
|
||||||
|
name: 'education',
|
||||||
|
'aria-label': '学历要求',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '工作经验',
|
title: '工作经验',
|
||||||
@@ -311,6 +349,11 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: experienceEnum,
|
valueEnum: experienceEnum,
|
||||||
width: 110,
|
width: 110,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-company-job-experience-search',
|
||||||
|
name: 'experience',
|
||||||
|
'aria-label': '工作经验',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 },
|
{ title: '招聘人数', dataIndex: 'vacancies', hideInSearch: true, width: 90 },
|
||||||
{
|
{
|
||||||
@@ -328,11 +371,23 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
title: '招聘会标题',
|
title: '招聘会标题',
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-title-search',
|
||||||
|
name: 'title',
|
||||||
|
'aria-label': '招聘会标题',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '举办单位',
|
title: '举办单位',
|
||||||
dataIndex: 'hostUnit',
|
dataIndex: 'hostUnit',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-host-unit-search',
|
||||||
|
name: 'hostUnit',
|
||||||
|
'aria-label': '举办单位',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '招聘会类型',
|
title: '招聘会类型',
|
||||||
@@ -340,6 +395,11 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: fairTypeValueEnum,
|
valueEnum: fairTypeValueEnum,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-type-search',
|
||||||
|
name: 'fairType',
|
||||||
|
'aria-label': '招聘会类型',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '举办区域',
|
title: '举办区域',
|
||||||
@@ -347,6 +407,11 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: regionValueEnum,
|
valueEnum: regionValueEnum,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-region-search',
|
||||||
|
name: 'region',
|
||||||
|
'aria-label': '举办区域',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '场地名称',
|
title: '场地名称',
|
||||||
@@ -354,6 +419,9 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-venue-search',
|
||||||
|
name: 'venueId',
|
||||||
|
'aria-label': '场地名称',
|
||||||
options: venueOptions,
|
options: venueOptions,
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
@@ -375,6 +443,12 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
dataIndex: 'boothCount',
|
dataIndex: 'boothCount',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '展位图数量',
|
||||||
|
dataIndex: 'boothMapCount',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => record.boothMapCount ?? 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '已预定摊位数',
|
title: '已预定摊位数',
|
||||||
dataIndex: 'reservedBoothCount',
|
dataIndex: 'reservedBoothCount',
|
||||||
@@ -382,28 +456,60 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
render: (_, record) => record.reservedBoothCount ?? 0,
|
render: (_, record) => record.reservedBoothCount ?? 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '举办时间',
|
title: '开始时间',
|
||||||
dataIndex: 'holdTime',
|
dataIndex: 'holdTime',
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
hideInSearch: true,
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-start-time-search',
|
||||||
|
name: 'startTime',
|
||||||
|
'aria-label': '开始时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ startTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '结束时间',
|
title: '结束时间',
|
||||||
dataIndex: 'endTime',
|
dataIndex: 'endTime',
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
hideInSearch: true,
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-end-time-search',
|
||||||
|
name: 'endTime',
|
||||||
|
'aria-label': '结束时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ endTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '报名开始时间',
|
title: '报名开始时间',
|
||||||
dataIndex: 'applyStartTime',
|
dataIndex: 'applyStartTime',
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
hideInSearch: true,
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-apply-start-time-search',
|
||||||
|
name: 'applyStartTime',
|
||||||
|
'aria-label': '报名开始时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ applyStartTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '报名截止时间',
|
title: '报名截止时间',
|
||||||
dataIndex: 'applyEndTime',
|
dataIndex: 'applyEndTime',
|
||||||
valueType: 'dateTime',
|
valueType: 'dateTime',
|
||||||
hideInSearch: true,
|
fieldProps: {
|
||||||
|
id: 'outdoor-fair-apply-end-time-search',
|
||||||
|
name: 'applyEndTime',
|
||||||
|
'aria-label': '报名截止时间',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
transform: (value) => ({ applyEndTime: formatSearchDateTime(value) }),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '线上申请',
|
title: '线上申请',
|
||||||
@@ -440,7 +546,7 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
render: (_, record) =>
|
render: (_, record) =>
|
||||||
record.photoUrl ? (
|
record.photoUrl ? (
|
||||||
<img
|
<img
|
||||||
src={record.photoUrl}
|
src={normalizePublicUrl(record.photoUrl)}
|
||||||
alt={record.title}
|
alt={record.title}
|
||||||
style={{ width: 60, height: 45, objectFit: 'cover', borderRadius: 4 }}
|
style={{ width: 60, height: 45, objectFit: 'cover', borderRadius: 4 }}
|
||||||
/>
|
/>
|
||||||
@@ -451,7 +557,7 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
width: isEnterprise ? 520 : 260,
|
width: isEnterprise ? 620 : 260,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (_, record) => [
|
render: (_, record) => [
|
||||||
isEnterprise && !record.signedUp ? (
|
isEnterprise && !record.signedUp ? (
|
||||||
@@ -475,6 +581,29 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
添加岗位
|
添加岗位
|
||||||
</Button>
|
</Button>
|
||||||
) : null,
|
) : null,
|
||||||
|
isEnterprise &&
|
||||||
|
record.signedUp &&
|
||||||
|
record.reviewStatus === '1' &&
|
||||||
|
record.companyCheckInStatus === 'checked_in' ? (
|
||||||
|
<Button key="checkedIn" type="link" size="small" icon={<CheckOutlined />} disabled>
|
||||||
|
已签到
|
||||||
|
</Button>
|
||||||
|
) : null,
|
||||||
|
isEnterprise &&
|
||||||
|
record.signedUp &&
|
||||||
|
record.reviewStatus === '1' &&
|
||||||
|
record.companyCheckInStatus !== 'checked_in' &&
|
||||||
|
record.companyCheckInAllowed ? (
|
||||||
|
<Button
|
||||||
|
key="checkIn"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<CheckOutlined />}
|
||||||
|
onClick={() => handleCheckIn(record)}
|
||||||
|
>
|
||||||
|
签到
|
||||||
|
</Button>
|
||||||
|
) : null,
|
||||||
isEnterprise && record.signedUp && record.reviewStatus === '1' ? (
|
isEnterprise && record.signedUp && record.reviewStatus === '1' ? (
|
||||||
<Button
|
<Button
|
||||||
key="jobs"
|
key="jobs"
|
||||||
@@ -587,6 +716,10 @@ const OutdoorFairList: React.FC = () => {
|
|||||||
region: params.region,
|
region: params.region,
|
||||||
venueId: params.venueId,
|
venueId: params.venueId,
|
||||||
onlineApply: params.onlineApply,
|
onlineApply: params.onlineApply,
|
||||||
|
startTime: formatSearchDateTime(params.startTime),
|
||||||
|
endTime: formatSearchDateTime(params.endTime),
|
||||||
|
applyStartTime: formatSearchDateTime(params.applyStartTime),
|
||||||
|
applyEndTime: formatSearchDateTime(params.applyEndTime),
|
||||||
} as OutdoorFairListParams);
|
} as OutdoorFairListParams);
|
||||||
return { data: res.rows, total: res.total, success: true };
|
return { data: res.rows, total: res.total, success: true };
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ const JobFairDetail: React.FC = () => {
|
|||||||
const [reviewCompany, setReviewCompany] = useState<API.PublicJobFair.CompanyInfo>();
|
const [reviewCompany, setReviewCompany] = useState<API.PublicJobFair.CompanyInfo>();
|
||||||
const [reviewSubmitting, setReviewSubmitting] = useState(false);
|
const [reviewSubmitting, setReviewSubmitting] = useState(false);
|
||||||
const [jobFairRegionTypeEnum, setJobFairRegionTypeEnum] = useState<any>({});
|
const [jobFairRegionTypeEnum, setJobFairRegionTypeEnum] = useState<any>({});
|
||||||
|
const [educationEnum, setEducationEnum] = useState<any>({});
|
||||||
|
const [experienceEnum, setExperienceEnum] = useState<any>({});
|
||||||
const [reviewForm] = Form.useForm<ReviewFormValues>();
|
const [reviewForm] = Form.useForm<ReviewFormValues>();
|
||||||
const reviewStatus = Form.useWatch('reviewStatus', reviewForm);
|
const reviewStatus = Form.useWatch('reviewStatus', reviewForm);
|
||||||
|
|
||||||
@@ -77,8 +79,16 @@ const JobFairDetail: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDictValueEnum('job_fair_region_type').then(setJobFairRegionTypeEnum);
|
getDictValueEnum('job_fair_region_type').then(setJobFairRegionTypeEnum);
|
||||||
|
getDictValueEnum('education', true, true).then(setEducationEnum);
|
||||||
|
getDictValueEnum('experience', true, true).then(setExperienceEnum);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getJobDictLabel = (value: string | number | undefined, enums: any) => {
|
||||||
|
if (value === undefined || value === null || value === '') return '未说明';
|
||||||
|
const item = enums[String(value)] || enums[value];
|
||||||
|
return item?.label || item?.text || '待配置';
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemoveCompany = (company: API.PublicJobFair.CompanyInfo) => {
|
const handleRemoveCompany = (company: API.PublicJobFair.CompanyInfo) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认移除',
|
title: '确认移除',
|
||||||
@@ -320,8 +330,12 @@ const JobFairDetail: React.FC = () => {
|
|||||||
? `${job.minSalary || 0}-${job.maxSalary || 0} 元/月`
|
? `${job.minSalary || 0}-${job.maxSalary || 0} 元/月`
|
||||||
: '面议'}
|
: '面议'}
|
||||||
</Tag>
|
</Tag>
|
||||||
{job.education && <Tag>{job.education}</Tag>}
|
{job.education && (
|
||||||
{job.experience && <Tag>{job.experience}</Tag>}
|
<Tag>{getJobDictLabel(job.education, educationEnum)}</Tag>
|
||||||
|
)}
|
||||||
|
{job.experience && (
|
||||||
|
<Tag>{getJobDictLabel(job.experience, experienceEnum)}</Tag>
|
||||||
|
)}
|
||||||
{job.vacancies !== undefined && <Tag>招{job.vacancies}人</Tag>}
|
{job.vacancies !== undefined && <Tag>招{job.vacancies}人</Tag>}
|
||||||
</Space>
|
</Space>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ const PublicJobFairList: React.FC = () => {
|
|||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
valueType: 'dateRange',
|
valueType: 'dateRange',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
|
id: { start: 'public-job-fair-start-date', end: 'public-job-fair-end-date' },
|
||||||
|
name: ['publicJobFairStartDate', 'publicJobFairEndDate'],
|
||||||
|
'aria-label': '招聘会时间范围',
|
||||||
format: 'YYYY-MM-DD',
|
format: 'YYYY-MM-DD',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
@@ -116,6 +119,12 @@ const PublicJobFairList: React.FC = () => {
|
|||||||
title: '招聘会标题',
|
title: '招聘会标题',
|
||||||
dataIndex: 'jobFairTitle',
|
dataIndex: 'jobFairTitle',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'public-job-fair-title-search',
|
||||||
|
name: 'jobFairTitle',
|
||||||
|
'aria-label': '招聘会标题',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '地址',
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
width="md"
|
width="md"
|
||||||
options={venueTypeOptions}
|
options={venueTypeOptions}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
id: 'venue-info-type-input',
|
||||||
|
'aria-label': '场地类型',
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
dropdownRender: renderVenueTypeDropdown,
|
dropdownRender: renderVenueTypeDropdown,
|
||||||
}}
|
}}
|
||||||
@@ -207,13 +209,24 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
width="md"
|
width="md"
|
||||||
rules={[{ required: true, message: '请输入场地名称' }]}
|
rules={[{ required: true, message: '请输入场地名称' }]}
|
||||||
placeholder="请输入场地名称"
|
placeholder="请输入场地名称"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'venue-info-name-input',
|
||||||
|
name: 'venueName',
|
||||||
|
'aria-label': '场地名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormDigit
|
<ProFormDigit
|
||||||
name="venueArea"
|
name="venueArea"
|
||||||
label="场地面积"
|
label="场地面积"
|
||||||
width="sm"
|
width="sm"
|
||||||
min={0}
|
min={0}
|
||||||
fieldProps={{ precision: 2 }}
|
fieldProps={{
|
||||||
|
id: 'venue-info-area-input',
|
||||||
|
name: 'venueArea',
|
||||||
|
'aria-label': '场地面积(平方米)',
|
||||||
|
precision: 2,
|
||||||
|
}}
|
||||||
placeholder="请输入场地面积"
|
placeholder="请输入场地面积"
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
@@ -223,16 +236,38 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
label="展位数量"
|
label="展位数量"
|
||||||
width="md"
|
width="md"
|
||||||
min={0}
|
min={0}
|
||||||
fieldProps={{ precision: 0 }}
|
fieldProps={{
|
||||||
|
id: 'venue-info-booth-count-input',
|
||||||
|
name: 'floorCount',
|
||||||
|
'aria-label': '展位数量',
|
||||||
|
precision: 0,
|
||||||
|
}}
|
||||||
placeholder="请输入展位数量"
|
placeholder="请输入展位数量"
|
||||||
/>
|
/>
|
||||||
<ProFormText name="contactPhone" label="联系电话" width="md" placeholder="请输入联系电话" />
|
<ProFormText
|
||||||
|
name="contactPhone"
|
||||||
|
label="联系电话"
|
||||||
|
width="md"
|
||||||
|
placeholder="请输入联系电话"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'venue-info-contact-phone-input',
|
||||||
|
name: 'contactPhone',
|
||||||
|
'aria-label': '联系电话',
|
||||||
|
autoComplete: 'tel',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="venueAddress"
|
name="venueAddress"
|
||||||
label="场地地址"
|
label="场地地址"
|
||||||
rules={[{ required: true, message: '请输入场地地址' }]}
|
rules={[{ required: true, message: '请输入场地地址' }]}
|
||||||
placeholder="请输入场地地址"
|
placeholder="请输入场地地址"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'venue-info-address-input',
|
||||||
|
name: 'venueAddress',
|
||||||
|
'aria-label': '场地地址',
|
||||||
|
autoComplete: 'street-address',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
name="routeLineIds"
|
name="routeLineIds"
|
||||||
@@ -240,6 +275,8 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
mode="multiple"
|
mode="multiple"
|
||||||
options={lineOptions}
|
options={lineOptions}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
id: 'venue-info-route-line-select',
|
||||||
|
'aria-label': '乘坐线路',
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
optionFilterProp: 'label',
|
optionFilterProp: 'label',
|
||||||
}}
|
}}
|
||||||
@@ -254,6 +291,8 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
value: item.id,
|
value: item.id,
|
||||||
}))}
|
}))}
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
|
id: 'venue-info-institution-select',
|
||||||
|
'aria-label': '所属机构',
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
optionFilterProp: 'label',
|
optionFilterProp: 'label',
|
||||||
dropdownRender: renderInstitutionDropdown,
|
dropdownRender: renderInstitutionDropdown,
|
||||||
@@ -263,7 +302,13 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
<ProFormTextArea
|
<ProFormTextArea
|
||||||
name="remark"
|
name="remark"
|
||||||
label="备注"
|
label="备注"
|
||||||
fieldProps={{ maxLength: 100, showCount: true }}
|
fieldProps={{
|
||||||
|
id: 'venue-info-remark-input',
|
||||||
|
name: 'remark',
|
||||||
|
'aria-label': '备注',
|
||||||
|
maxLength: 100,
|
||||||
|
showCount: true,
|
||||||
|
}}
|
||||||
placeholder="请输入备注"
|
placeholder="请输入备注"
|
||||||
/>
|
/>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
@@ -271,10 +316,37 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
name="handleDate"
|
name="handleDate"
|
||||||
label="经办日期"
|
label="经办日期"
|
||||||
width="md"
|
width="md"
|
||||||
fieldProps={{ format: 'YYYY-MM-DD' }}
|
fieldProps={{
|
||||||
|
id: 'venue-info-handle-date-input',
|
||||||
|
name: 'handleDate',
|
||||||
|
'aria-label': '经办日期',
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="handleOrg"
|
||||||
|
label="经办机构"
|
||||||
|
width="md"
|
||||||
|
placeholder="请输入经办机构"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'venue-info-handle-org-input',
|
||||||
|
name: 'handleOrg',
|
||||||
|
'aria-label': '经办机构',
|
||||||
|
autoComplete: 'organization',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="handler"
|
||||||
|
label="经办人"
|
||||||
|
width="md"
|
||||||
|
placeholder="请输入经办人"
|
||||||
|
fieldProps={{
|
||||||
|
id: 'venue-info-handler-input',
|
||||||
|
name: 'handler',
|
||||||
|
'aria-label': '经办人',
|
||||||
|
autoComplete: 'name',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormText name="handleOrg" label="经办机构" width="md" placeholder="请输入经办机构" />
|
|
||||||
<ProFormText name="handler" label="经办人" width="md" placeholder="请输入经办人" />
|
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
<Modal
|
<Modal
|
||||||
title="新增场地类型"
|
title="新增场地类型"
|
||||||
@@ -290,6 +362,10 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
maxLength={100}
|
maxLength={100}
|
||||||
showCount
|
showCount
|
||||||
placeholder="请输入场地类型"
|
placeholder="请输入场地类型"
|
||||||
|
id="venue-info-type-option-input"
|
||||||
|
name="venueTypeOption"
|
||||||
|
aria-label="新增场地类型"
|
||||||
|
autoComplete="off"
|
||||||
onChange={(event) => setDictInputValue(event.target.value)}
|
onChange={(event) => setDictInputValue(event.target.value)}
|
||||||
onPressEnter={handleDictModalOk}
|
onPressEnter={handleDictModalOk}
|
||||||
/>
|
/>
|
||||||
@@ -308,10 +384,26 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
label="机构名称"
|
label="机构名称"
|
||||||
rules={[{ required: true, message: '请输入机构名称' }]}
|
rules={[{ required: true, message: '请输入机构名称' }]}
|
||||||
>
|
>
|
||||||
<Input maxLength={200} showCount placeholder="请输入机构名称" />
|
<Input
|
||||||
|
id="venue-info-institution-name-input"
|
||||||
|
name="institutionName"
|
||||||
|
aria-label="机构名称"
|
||||||
|
autoComplete="organization"
|
||||||
|
maxLength={200}
|
||||||
|
showCount
|
||||||
|
placeholder="请输入机构名称"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="institutionIntro" label="机构简介">
|
<Form.Item name="institutionIntro" label="机构简介">
|
||||||
<Input.TextArea maxLength={2000} showCount rows={4} placeholder="请输入机构简介" />
|
<Input.TextArea
|
||||||
|
id="venue-info-institution-intro-input"
|
||||||
|
name="institutionIntro"
|
||||||
|
aria-label="机构简介"
|
||||||
|
maxLength={2000}
|
||||||
|
showCount
|
||||||
|
rows={4}
|
||||||
|
placeholder="请输入机构简介"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -125,11 +125,22 @@ const VenueInfoList: React.FC = () => {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
valueEnum: venueTypeValueEnum,
|
valueEnum: venueTypeValueEnum,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'venue-info-type-search',
|
||||||
|
name: 'venueType',
|
||||||
|
'aria-label': '场地类型',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '场地名称',
|
title: '场地名称',
|
||||||
dataIndex: 'venueName',
|
dataIndex: 'venueName',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
fieldProps: {
|
||||||
|
id: 'venue-info-name-search',
|
||||||
|
name: 'venueName',
|
||||||
|
'aria-label': '场地名称',
|
||||||
|
autoComplete: 'off',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '场地面积',
|
title: '场地面积',
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ export async function getCmsCompanyDetail(companyId: number | string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取企业未参加的已结束户外招聘会明细。 */
|
||||||
|
export async function getCmsCompanyMissedOutdoorFairs(
|
||||||
|
companyId: number | string,
|
||||||
|
params?: { current?: number; pageSize?: number },
|
||||||
|
) {
|
||||||
|
return request<{
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
total: number;
|
||||||
|
rows: API.CompanyList.MissedOutdoorFair[];
|
||||||
|
}>(`/api/cms/company/${companyId}/missed-outdoor-fairs`, {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取当前企业用户关联的企业信息 */
|
/** 获取当前企业用户关联的企业信息 */
|
||||||
export async function getCmsCurrentCompany() {
|
export async function getCmsCurrentCompany() {
|
||||||
return request<{
|
return request<{
|
||||||
|
|||||||
@@ -86,6 +86,18 @@ export interface CrossDomainStatistics {
|
|||||||
jobCount: number;
|
jobCount: number;
|
||||||
demandCount: number;
|
demandCount: number;
|
||||||
viewCount: number;
|
viewCount: number;
|
||||||
|
fairs: CrossDomainStatisticsFairItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跨域线上招聘会统计中的单场明细 */
|
||||||
|
export interface CrossDomainStatisticsFairItem {
|
||||||
|
fairId: string;
|
||||||
|
fairTitle: string;
|
||||||
|
startTime?: string;
|
||||||
|
companyCount: number;
|
||||||
|
jobCount: number;
|
||||||
|
demandCount: number;
|
||||||
|
viewCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCrossDomainStatistics(params: CrossDomainStatisticsParams) {
|
export async function getCrossDomainStatistics(params: CrossDomainStatisticsParams) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface OutdoorFairItem {
|
|||||||
boothCount: number;
|
boothCount: number;
|
||||||
/** 已预定摊位数 */
|
/** 已预定摊位数 */
|
||||||
reservedBoothCount?: number;
|
reservedBoothCount?: number;
|
||||||
|
/** 招聘会实际展位图中的展位数量 */
|
||||||
|
boothMapCount?: number;
|
||||||
/** 举办时间 */
|
/** 举办时间 */
|
||||||
holdTime: string;
|
holdTime: string;
|
||||||
/** 招聘会结束时间 */
|
/** 招聘会结束时间 */
|
||||||
@@ -58,6 +60,12 @@ export interface OutdoorFairItem {
|
|||||||
reviewStatus?: string;
|
reviewStatus?: string;
|
||||||
/** 当前企业报名审核备注 */
|
/** 当前企业报名审核备注 */
|
||||||
reviewRemark?: string;
|
reviewRemark?: string;
|
||||||
|
/** 当前企业签到状态:not_checked_in/checked_in */
|
||||||
|
companyCheckInStatus?: 'not_checked_in' | 'checked_in';
|
||||||
|
/** 当前企业签到时间 */
|
||||||
|
companyCheckInTime?: string;
|
||||||
|
/** 当前企业当前是否允许签到 */
|
||||||
|
companyCheckInAllowed?: boolean;
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
}
|
}
|
||||||
@@ -70,6 +78,14 @@ export interface OutdoorFairListParams {
|
|||||||
region?: string;
|
region?: string;
|
||||||
venueId?: number;
|
venueId?: number;
|
||||||
onlineApply?: boolean;
|
onlineApply?: boolean;
|
||||||
|
/** 举办开始时间下限 */
|
||||||
|
startTime?: string;
|
||||||
|
/** 举办结束时间上限 */
|
||||||
|
endTime?: string;
|
||||||
|
/** 报名开始时间下限 */
|
||||||
|
applyStartTime?: string;
|
||||||
|
/** 报名截止时间上限 */
|
||||||
|
applyEndTime?: string;
|
||||||
current?: number;
|
current?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
}
|
}
|
||||||
@@ -343,6 +359,25 @@ export async function getMyOutdoorFairQrCode(fairId: number) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前企业在招聘会举办期间签到
|
||||||
|
* POST /api/cms/outdoor-fair/{fairId}/company/check-in
|
||||||
|
*/
|
||||||
|
export async function checkInOutdoorFair(fairId: number) {
|
||||||
|
return request<{
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
data?: {
|
||||||
|
fairId: number;
|
||||||
|
companyId: number;
|
||||||
|
checkInTime: string;
|
||||||
|
attendanceStatus: 'checked_in';
|
||||||
|
};
|
||||||
|
}>(`${CMS_OUTDOOR_FAIR_BASE}/${fairId}/company/check-in`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员获取招聘会微信小程序码
|
* 管理员获取招聘会微信小程序码
|
||||||
* GET /api/cms/outdoor-fair/{fairId}/mini-program-qrcode
|
* GET /api/cms/outdoor-fair/{fairId}/mini-program-qrcode
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { request } from '@umijs/max';
|
|||||||
export interface OutdoorFairAttendeeItem {
|
export interface OutdoorFairAttendeeItem {
|
||||||
id: number;
|
id: number;
|
||||||
fairId: number;
|
fairId: number;
|
||||||
|
/** 关联的移动端用户 ID */
|
||||||
|
userId?: number;
|
||||||
/** 人员姓名 */
|
/** 人员姓名 */
|
||||||
name: string;
|
name: string;
|
||||||
/** 手机号 */
|
/** 手机号 */
|
||||||
@@ -36,14 +38,18 @@ export interface OutdoorFairAttendeeListResult {
|
|||||||
rows: OutdoorFairAttendeeItem[];
|
rows: OutdoorFairAttendeeItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 签到人员新增/编辑表单 */
|
/** 后台录入签到参数 */
|
||||||
export interface OutdoorFairAttendeeForm {
|
export interface OutdoorFairAttendeeForm {
|
||||||
id?: number;
|
|
||||||
fairId: number;
|
fairId: number;
|
||||||
name: string;
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 后台录入签到时的移动端用户选项 */
|
||||||
|
export interface OutdoorFairAttendeeUserOption {
|
||||||
|
userId: number;
|
||||||
|
name?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
checkInTime?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 通用响应 */
|
/** 通用响应 */
|
||||||
@@ -53,14 +59,14 @@ export interface OutdoorFairAttendeeCommonResult {
|
|||||||
data?: OutdoorFairAttendeeItem;
|
data?: OutdoorFairAttendeeItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE = '/api/cms/outdoor-fair-attendee';
|
/** 可录入签到的移动端用户列表响应 */
|
||||||
|
export interface OutdoorFairAttendeeUserOptionResult {
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
data?: OutdoorFairAttendeeUserOption[];
|
||||||
|
}
|
||||||
|
|
||||||
const formatDateTime = (value: any) => {
|
const BASE = '/api/cms/outdoor-fair-attendee';
|
||||||
if (value && typeof value.format === 'function') {
|
|
||||||
return value.format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取签到人员列表
|
* 获取签到人员列表
|
||||||
@@ -74,24 +80,27 @@ export async function getOutdoorFairAttendeeList(params?: OutdoorFairAttendeeLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增签到人员
|
* 获取当前招聘会尚未签到的移动端用户
|
||||||
|
* GET /api/cms/outdoor-fair-attendee/available-users
|
||||||
|
*/
|
||||||
|
export async function getAvailableOutdoorFairAttendeeUsers(params: {
|
||||||
|
fairId: number;
|
||||||
|
keyword?: string;
|
||||||
|
}) {
|
||||||
|
return request<OutdoorFairAttendeeUserOptionResult>(`${BASE}/available-users`, {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择移动端用户并立即完成签到
|
||||||
* POST /api/cms/outdoor-fair-attendee
|
* POST /api/cms/outdoor-fair-attendee
|
||||||
*/
|
*/
|
||||||
export async function addOutdoorFairAttendee(data: OutdoorFairAttendeeForm) {
|
export async function addOutdoorFairAttendee(data: OutdoorFairAttendeeForm) {
|
||||||
return request<OutdoorFairAttendeeCommonResult>(BASE, {
|
return request<OutdoorFairAttendeeCommonResult>(BASE, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { ...data, checkInTime: formatDateTime(data.checkInTime) },
|
data,
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改签到人员
|
|
||||||
* PUT /api/cms/outdoor-fair-attendee
|
|
||||||
*/
|
|
||||||
export async function updateOutdoorFairAttendee(data: OutdoorFairAttendeeForm) {
|
|
||||||
return request<OutdoorFairAttendeeCommonResult>(BASE, {
|
|
||||||
method: 'PUT',
|
|
||||||
data: { ...data, checkInTime: formatDateTime(data.checkInTime) },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ type GateEntryLog = API.OutdoorFairDetail.GateEntryLog;
|
|||||||
type FairStatistics = API.OutdoorFairDetail.FairStatistics;
|
type FairStatistics = API.OutdoorFairDetail.FairStatistics;
|
||||||
type CompanyQrCodeInfo = API.OutdoorFairDetail.CompanyQrCodeInfo;
|
type CompanyQrCodeInfo = API.OutdoorFairDetail.CompanyQrCodeInfo;
|
||||||
|
|
||||||
|
export interface OutdoorFairCompanyAttendanceItem {
|
||||||
|
id: number;
|
||||||
|
fairId: number;
|
||||||
|
companyId: number;
|
||||||
|
companyName: string;
|
||||||
|
industry?: string;
|
||||||
|
scale?: string;
|
||||||
|
contactPerson?: string;
|
||||||
|
contactPhone?: string;
|
||||||
|
reviewTime?: string;
|
||||||
|
registrationTime?: string;
|
||||||
|
attendanceStatus: '0' | '1';
|
||||||
|
checkInTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 模块1: 招聘会管理 假数据 ====================
|
// ==================== 模块1: 招聘会管理 假数据 ====================
|
||||||
|
|
||||||
const MOCK_COMPANIES: ParticipatingCompany[] = [
|
const MOCK_COMPANIES: ParticipatingCompany[] = [
|
||||||
@@ -1016,6 +1031,11 @@ export async function getParticipatingCompanies(
|
|||||||
current?: number;
|
current?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
companyName?: string;
|
companyName?: string;
|
||||||
|
reviewStatus?: string;
|
||||||
|
/** 报名时间下限 */
|
||||||
|
startTime?: string;
|
||||||
|
/** 报名时间上限 */
|
||||||
|
endTime?: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return request<{ code: number; msg?: string; total: number; rows: ParticipatingCompany[] }>(
|
return request<{ code: number; msg?: string; total: number; rows: ParticipatingCompany[] }>(
|
||||||
@@ -1027,6 +1047,27 @@ export async function getParticipatingCompanies(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取招聘会企业签到列表。 */
|
||||||
|
export async function getOutdoorFairCompanyAttendanceList(
|
||||||
|
fairId: number,
|
||||||
|
params?: {
|
||||||
|
current?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
companyName?: string;
|
||||||
|
checkedIn?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return request<{
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
total: number;
|
||||||
|
rows: OutdoorFairCompanyAttendanceItem[];
|
||||||
|
}>(`/api/cms/outdoor-fair/${fairId}/company-attendance`, {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取招聘会企业退订记录。 */
|
/** 获取招聘会企业退订记录。 */
|
||||||
export async function getCompanyUnsubscribes(
|
export async function getCompanyUnsubscribes(
|
||||||
fairId: number,
|
fairId: number,
|
||||||
|
|||||||
13
src/types/company/list.d.ts
vendored
13
src/types/company/list.d.ts
vendored
@@ -1,4 +1,14 @@
|
|||||||
declare namespace API.CompanyList {
|
declare namespace API.CompanyList {
|
||||||
|
export interface MissedOutdoorFair {
|
||||||
|
fairId: number;
|
||||||
|
title: string;
|
||||||
|
hostUnit?: string;
|
||||||
|
address?: string;
|
||||||
|
holdTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
registrationTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompanyListResult {
|
export interface CompanyListResult {
|
||||||
total: number;
|
total: number;
|
||||||
rows: RootObjectRows[];
|
rows: RootObjectRows[];
|
||||||
@@ -30,6 +40,7 @@ declare namespace API.CompanyList {
|
|||||||
notPassReason?: string;
|
notPassReason?: string;
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
status?: 0 | 1 | 2; // 0 待审核, 1 通过, 2 驳回
|
status?: 0 | 1 | 2; // 0 待审核, 1 通过, 2 驳回
|
||||||
|
missedOutdoorFairCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
@@ -51,5 +62,7 @@ declare namespace API.CompanyList {
|
|||||||
status?: 0 | 1 | 2;
|
status?: 0 | 1 | 2;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
/** 仅查看有未参加已结束户外招聘会记录的企业。 */
|
||||||
|
missedOutdoorFairOnly?: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/types/jobfair/outdoorFairDetail.d.ts
vendored
6
src/types/jobfair/outdoorFairDetail.d.ts
vendored
@@ -30,6 +30,12 @@ declare namespace API.OutdoorFairDetail {
|
|||||||
reviewBy?: string;
|
reviewBy?: string;
|
||||||
reviewTime?: string;
|
reviewTime?: string;
|
||||||
reviewRemark?: string;
|
reviewRemark?: string;
|
||||||
|
/** 企业报名时间。 */
|
||||||
|
registrationTime?: string;
|
||||||
|
/** 企业签到状态:not_checked_in/checked_in。 */
|
||||||
|
companyCheckInStatus?: 'not_checked_in' | 'checked_in';
|
||||||
|
/** 企业签到时间。 */
|
||||||
|
companyCheckInTime?: string;
|
||||||
/** 仅兼容旧 mock 数据,真实企业接口不再返回岗位列表。 */
|
/** 仅兼容旧 mock 数据,真实企业接口不再返回岗位列表。 */
|
||||||
jobList?: PostedJob[];
|
jobList?: PostedJob[];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user