feat: 新增招聘会企业签到与未参会统计

This commit is contained in:
2026-07-24 12:35:30 +08:00
parent 25ba8dc0da
commit 527fcea91f
11 changed files with 332 additions and 2 deletions

View File

@@ -259,6 +259,17 @@ export default [
name: 'company',
path: '/company',
routes: [
{
name: '企业列表',
path: '/company/list',
component: './Company/List',
},
{
name: '未参加招聘会详情',
path: '/company/list/missed-outdoor-fairs',
component: './Company/List/MissedOutdoorFairs',
hideInMenu: true,
},
{
name: '企业信息',
path: '/company/info/index',

View 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;

View File

@@ -1,5 +1,5 @@
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 { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { DeleteOutlined, FormOutlined, PlusOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
@@ -196,6 +196,34 @@ function ManagementList() {
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: '驳回原因',
dataIndex: 'notPassReason',

View File

@@ -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 columns: ProColumns<OutdoorFairCompanyAttendanceItem>[] = [
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
{
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={columns}
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;

View File

@@ -11,6 +11,7 @@ import ParticipatingCompaniesTab from './components/ParticipatingCompaniesTab';
import ParticipatingJobsTab from './components/ParticipatingJobsTab';
import CompanyUnsubscribeTab from './components/CompanyUnsubscribeTab';
import AttendeeTab from './components/AttendeeTab';
import CompanyAttendanceTab from './components/CompanyAttendanceTab';
import DeviceTab from './components/DeviceTab';
const OutdoorFairDetail: React.FC = () => {
@@ -43,6 +44,7 @@ const OutdoorFairDetail: React.FC = () => {
const tabItems = [
{ key: 'fair-info', label: '招聘会管理' },
{ key: 'participating-companies', label: '参会企业' },
{ key: 'company-attendance', label: '企业签到' },
{ key: 'company-unsubscribe', label: '企业退订' },
{ key: 'participating-jobs', label: '参会岗位' },
{ key: 'attendee', label: '参会人员管理' },
@@ -88,6 +90,8 @@ const OutdoorFairDetail: React.FC = () => {
return <BoothTab fairId={fairId} fairInfo={fairInfo} />;
case 'participating-companies':
return <ParticipatingCompaniesTab fairId={fairId} />;
case 'company-attendance':
return <CompanyAttendanceTab fairId={fairId} />;
case 'company-unsubscribe':
return <CompanyUnsubscribeTab fairId={fairId} />;
case 'participating-jobs':

View File

@@ -10,6 +10,7 @@ import {
QrcodeOutlined,
UnorderedListOutlined,
ExportOutlined,
CheckOutlined,
} from '@ant-design/icons';
import {
getOutdoorFairList,
@@ -20,6 +21,7 @@ import {
signupOutdoorFair,
unsubscribeOutdoorFair,
getMyOutdoorFairQrCode,
checkInOutdoorFair,
getOutdoorFairMiniProgramQrCode,
exportOutdoorFair,
} from '@/services/jobportal/outdoorFair';
@@ -193,6 +195,16 @@ const OutdoorFairList: React.FC = () => {
}
};
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 res = await getMyOutdoorFairQrCode(record.id);
if (res.code === 200) {
@@ -451,7 +463,7 @@ const OutdoorFairList: React.FC = () => {
{
title: '操作',
valueType: 'option',
width: isEnterprise ? 520 : 260,
width: isEnterprise ? 620 : 260,
fixed: 'right',
render: (_, record) => [
isEnterprise && !record.signedUp ? (
@@ -475,6 +487,29 @@ const OutdoorFairList: React.FC = () => {
</Button>
) : 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' ? (
<Button
key="jobs"

View File

@@ -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() {
return request<{

View File

@@ -58,6 +58,12 @@ export interface OutdoorFairItem {
reviewStatus?: string;
/** 当前企业报名审核备注 */
reviewRemark?: string;
/** 当前企业签到状态not_checked_in/checked_in */
companyCheckInStatus?: 'not_checked_in' | 'checked_in';
/** 当前企业签到时间 */
companyCheckInTime?: string;
/** 当前企业当前是否允许签到 */
companyCheckInAllowed?: boolean;
createTime?: string;
updateTime?: string;
}
@@ -70,6 +76,14 @@ export interface OutdoorFairListParams {
region?: string;
venueId?: number;
onlineApply?: boolean;
/** 举办开始时间下限 */
startTime?: string;
/** 举办结束时间上限 */
endTime?: string;
/** 报名开始时间下限 */
applyStartTime?: string;
/** 报名截止时间上限 */
applyEndTime?: string;
current?: number;
pageSize?: number;
}
@@ -343,6 +357,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

View File

@@ -19,6 +19,21 @@ type GateEntryLog = API.OutdoorFairDetail.GateEntryLog;
type FairStatistics = API.OutdoorFairDetail.FairStatistics;
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: 招聘会管理 假数据 ====================
const MOCK_COMPANIES: ParticipatingCompany[] = [
@@ -1016,6 +1031,11 @@ export async function getParticipatingCompanies(
current?: number;
pageSize?: number;
companyName?: string;
reviewStatus?: string;
/** 报名时间下限 */
startTime?: string;
/** 报名时间上限 */
endTime?: string;
},
) {
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(
fairId: number,

View File

@@ -1,4 +1,14 @@
declare namespace API.CompanyList {
export interface MissedOutdoorFair {
fairId: number;
title: string;
hostUnit?: string;
address?: string;
holdTime?: string;
endTime?: string;
registrationTime?: string;
}
export interface CompanyListResult {
total: number;
rows: RootObjectRows[];
@@ -30,6 +40,7 @@ declare namespace API.CompanyList {
notPassReason?: string;
updateTime?: string;
status?: 0 | 1 | 2; // 0 待审核, 1 通过, 2 驳回
missedOutdoorFairCount?: number;
}
export interface Params {
@@ -51,5 +62,7 @@ declare namespace API.CompanyList {
status?: 0 | 1 | 2;
startDate?: string;
endDate?: string;
/** 仅查看有未参加已结束户外招聘会记录的企业。 */
missedOutdoorFairOnly?: boolean;
}
}

View File

@@ -30,6 +30,12 @@ declare namespace API.OutdoorFairDetail {
reviewBy?: string;
reviewTime?: string;
reviewRemark?: string;
/** 企业报名时间。 */
registrationTime?: string;
/** 企业签到状态not_checked_in/checked_in。 */
companyCheckInStatus?: 'not_checked_in' | 'checked_in';
/** 企业签到时间。 */
companyCheckInTime?: string;
/** 仅兼容旧 mock 数据,真实企业接口不再返回岗位列表。 */
jobList?: PostedJob[];
}