feat: 新增招聘会企业签到与未参会统计
This commit is contained in:
@@ -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',
|
||||
|
||||
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 { 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',
|
||||
|
||||
@@ -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;
|
||||
@@ -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':
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
13
src/types/company/list.d.ts
vendored
13
src/types/company/list.d.ts
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
reviewTime?: string;
|
||||
reviewRemark?: string;
|
||||
/** 企业报名时间。 */
|
||||
registrationTime?: string;
|
||||
/** 企业签到状态:not_checked_in/checked_in。 */
|
||||
companyCheckInStatus?: 'not_checked_in' | 'checked_in';
|
||||
/** 企业签到时间。 */
|
||||
companyCheckInTime?: string;
|
||||
/** 仅兼容旧 mock 数据,真实企业接口不再返回岗位列表。 */
|
||||
jobList?: PostedJob[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user