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', 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',

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 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';
@@ -196,6 +196,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',

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 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':

View File

@@ -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';
@@ -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 openQrCode = async (record: OutdoorFairItem) => {
const res = await getMyOutdoorFairQrCode(record.id); const res = await getMyOutdoorFairQrCode(record.id);
if (res.code === 200) { if (res.code === 200) {
@@ -451,7 +463,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 +487,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"

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() { export async function getCmsCurrentCompany() {
return request<{ return request<{

View File

@@ -58,6 +58,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 +76,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 +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 * 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 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,

View File

@@ -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;
} }
} }

View File

@@ -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[];
} }