Files
shz-admin/src/pages/Jobfair/FairStatistics/index.tsx

285 lines
9.6 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
Button,
Card,
Col,
DatePicker,
Empty,
Row,
Segmented,
Select,
Spin,
Statistic,
Table,
message,
} from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import type { ColumnsType } from 'antd/es/table';
import { getPublicJobFairList } from '@/services/jobfair/publicJobFair';
import { getOutdoorFairList } from '@/services/jobportal/outdoorFair';
import {
getFairStatisticsSummary,
type FairStatisticsFairItem,
type FairStatisticsSummary,
type RecruitmentFairType,
} from '@/services/jobfair/fairStatistics';
const { RangePicker } = DatePicker;
type StatisticsScope = 'single' | 'range';
interface FairOption {
value: string;
label: string;
}
const formatDate = (value: any): string | undefined =>
value && typeof value.format === 'function' ? value.format('YYYY-MM-DD') : undefined;
const FairStatistics: React.FC = () => {
const [fairType, setFairType] = useState<RecruitmentFairType>('online');
const [scope, setScope] = useState<StatisticsScope>('single');
const [fairOptions, setFairOptions] = useState<FairOption[]>([]);
const [selectedFairId, setSelectedFairId] = useState<string>();
const [dateRange, setDateRange] = useState<any>(null);
const [loadingFairs, setLoadingFairs] = useState(false);
const [loadingSummary, setLoadingSummary] = useState(false);
const [summary, setSummary] = useState<FairStatisticsSummary>();
const interactionLabel = fairType === 'online' ? '投递岗位数' : '签到数';
const interactionUnit = fairType === 'online' ? '个' : '人次';
const loadFairOptions = useCallback(async (type: RecruitmentFairType) => {
setLoadingFairs(true);
try {
if (type === 'online') {
const res = await getPublicJobFairList({ pageNum: 1, pageSize: 1000, jobFairType: '1' });
if (res.code !== 200) {
message.error(res.msg || '线上招聘会列表加载失败');
setFairOptions([]);
return;
}
setFairOptions(
(res.rows || []).map((item) => ({
value: String(item.jobFairId),
label: `${item.jobFairTitle}${item.jobFairStartTime ? `${item.jobFairStartTime}` : ''}`,
})),
);
} else {
const res = await getOutdoorFairList({ current: 1, pageSize: 1000 });
if (res.code !== 200) {
message.error(res.msg || '户外招聘会列表加载失败');
setFairOptions([]);
return;
}
setFairOptions(
(res.rows || []).map((item) => ({
value: String(item.id),
label: `${item.title}${item.holdTime ? `${item.holdTime}` : ''}`,
})),
);
}
} catch {
message.error('招聘会列表加载失败,请稍后重试');
setFairOptions([]);
} finally {
setLoadingFairs(false);
}
}, []);
useEffect(() => {
setSelectedFairId(undefined);
setDateRange(null);
setSummary(undefined);
loadFairOptions(fairType);
}, [fairType, loadFairOptions]);
const queryReady = useMemo(() => {
if (scope === 'single') return Boolean(selectedFairId);
return Boolean(dateRange?.[0] && dateRange?.[1]);
}, [dateRange, scope, selectedFairId]);
const loadSummary = useCallback(async () => {
if (!queryReady) {
setSummary(undefined);
return;
}
setLoadingSummary(true);
try {
const res = await getFairStatisticsSummary({
fairType,
fairId: scope === 'single' ? selectedFairId : undefined,
startDate: scope === 'range' ? formatDate(dateRange?.[0]) : undefined,
endDate: scope === 'range' ? formatDate(dateRange?.[1]) : undefined,
});
if (res.code === 200 && res.data) {
setSummary(res.data);
} else {
setSummary(undefined);
message.error(res.msg || '统计数据加载失败');
}
} catch {
setSummary(undefined);
message.error('统计数据加载失败,请稍后重试');
} finally {
setLoadingSummary(false);
}
}, [dateRange, fairType, queryReady, scope, selectedFairId]);
useEffect(() => {
loadSummary();
}, [loadSummary]);
const columns: ColumnsType<FairStatisticsFairItem> = useMemo(
() => [
{ title: '招聘会名称', dataIndex: 'fairTitle', ellipsis: true },
{ title: '起始时间', dataIndex: 'startTime', width: 180, render: (value) => value || '--' },
{ title: '参会单位数', dataIndex: 'companyCount', width: 110, align: 'right' },
{ title: '职位数', dataIndex: 'jobCount', width: 90, align: 'right' },
{ title: '需求人数', dataIndex: 'demandCount', width: 100, align: 'right' },
{ title: interactionLabel, dataIndex: 'interactionCount', width: 120, align: 'right' },
],
[interactionLabel],
);
const statisticsCards = [
{ title: '参会单位数', value: summary?.companyCount ?? 0, suffix: '家' },
{ title: '职位数', value: summary?.jobCount ?? 0, suffix: '个' },
{ title: '需求人数', value: summary?.demandCount ?? 0, suffix: '人' },
{ title: interactionLabel, value: summary?.interactionCount ?? 0, suffix: interactionUnit },
];
return (
<PageContainer
header={{ title: '招聘会数据统计' }}
content="分别统计线上招聘会与户外招聘会;可查看单场招聘会,或按招聘会起始时间汇总一个时间段。"
>
<Card style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]} align="middle">
<Col>
<Button.Group>
<Button
type={fairType === 'online' ? 'primary' : 'default'}
onClick={() => setFairType('online')}
>
线
</Button>
<Button
type={fairType === 'outdoor' ? 'primary' : 'default'}
onClick={() => setFairType('outdoor')}
>
</Button>
</Button.Group>
</Col>
<Col>
<span style={{ marginRight: 8 }}></span>
<Segmented
value={scope}
options={[
{ label: '统计单场', value: 'single' },
{ label: '按起始时间统计', value: 'range' },
]}
onChange={(value) => {
setScope(value as StatisticsScope);
setSummary(undefined);
}}
/>
</Col>
<Col flex="auto">
{scope === 'single' ? (
<Select
allowClear
showSearch
placeholder={`请选择一场${fairType === 'online' ? '线上' : '户外'}招聘会`}
optionFilterProp="label"
loading={loadingFairs}
value={selectedFairId}
options={fairOptions}
onChange={(value) => setSelectedFairId(value)}
style={{ width: '100%', minWidth: 320 }}
/>
) : (
<RangePicker
value={dateRange}
onChange={(value) => setDateRange(value)}
style={{ width: '100%', minWidth: 320 }}
placeholder={['起始日期', '结束日期']}
/>
)}
</Col>
</Row>
</Card>
<Spin spinning={loadingSummary}>
<Card
title={
summary
? `统计结果(共 ${summary.fairCount}${fairType === 'online' ? '线上' : '户外'}招聘会)`
: '统计结果'
}
style={{ marginBottom: 16 }}
>
{queryReady ? (
<Row gutter={[16, 16]}>
{statisticsCards.map((item) => (
<Col key={item.title} xs={24} sm={12} lg={6}>
<Card
size="small"
style={{ background: item.title === interactionLabel ? '#f0f5ff' : '#fafafa' }}
>
<Statistic title={item.title} value={item.value} suffix={item.suffix} />
</Card>
</Col>
))}
</Row>
) : (
<Empty
description={
scope === 'single' ? '请选择一场招聘会' : '请选择完整的起始日期和结束日期'
}
/>
)}
</Card>
{summary && (
<Card title="涉及招聘会明细" style={{ marginBottom: 16 }} bodyStyle={{ paddingTop: 8 }}>
<Table<FairStatisticsFairItem>
rowKey="fairId"
columns={columns}
dataSource={summary.fairs || []}
pagination={false}
size="small"
scroll={{ x: 860 }}
/>
</Card>
)}
</Spin>
<Card
size="small"
title={
<span>
<InfoCircleOutlined style={{ color: '#1677ff', marginRight: 8 }} />
</span>
}
>
<Row gutter={[24, 8]}>
<Col xs={24} lg={12}>
1
</Col>
<Col xs={24} lg={12}>
{fairType === 'online'
? '线上招聘会统计投递岗位数,即本场有效招聘会岗位中至少有一条有效投递记录的岗位数。'
: '户外招聘会不提供报名投递简历,统计现场签到记录数,不显示投递岗位数。'}
</Col>
</Row>
</Card>
</PageContainer>
);
};
export default FairStatistics;