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

285 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;