11
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled

This commit is contained in:
francis-fh
2026-06-18 02:30:11 +08:00
parent 4f484f1d00
commit e7616b0c8e
35 changed files with 5815 additions and 19 deletions

View File

@@ -0,0 +1,140 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components';
import { PlusOutlined, DeleteOutlined, FormOutlined, LockOutlined, UnlockOutlined, PaperClipOutlined } from '@ant-design/icons';
import {
getBoothList,
addBooth,
updateBooth,
fixBooth,
reserveBooth,
releaseBooth,
} from '@/services/jobportal/indoorJobFair';
import type { BoothItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const BoothTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [editingBooth, setEditingBooth] = useState<BoothItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
available: { text: '可用', color: 'green' },
fixed: { text: '固定', color: 'blue' },
reserved: { text: '预留', color: 'orange' },
locked: { text: '锁定', color: 'red' },
};
const columns: ProColumns<BoothItem>[] = [
{ title: '展位号', dataIndex: 'boothNumber', width: 100 },
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true, render: (_, r) => r.companyName || '--' },
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (_, record) => {
const s = statusMap[record.status] || { text: record.status, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '操作人', dataIndex: 'operator', width: 100, render: (_, r) => r.operator || '--' },
{ title: '操作时间', dataIndex: 'operateTime', width: 170, render: (_, r) => r.operateTime || '--' },
{ title: '备注', dataIndex: 'remark', ellipsis: true, render: (_, r) => r.remark || '--' },
{
title: '操作',
valueType: 'option',
width: 250,
render: (_, record) => [
<Button key="edit" type="link" size="small" icon={<FormOutlined />}
onClick={() => { setEditingBooth(record); setModalOpen(true); }}
></Button>,
<Button key="fix" type="link" size="small" icon={<LockOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit') || record.status === 'fixed'}
onClick={async () => {
const res = await fixBooth(record.id, '当前用户');
if (res.code === 200) { message.success('固定成功'); actionRef.current?.reload(); }
else { message.error(res.msg); }
}}
></Button>,
<Button key="reserve" type="link" size="small" icon={<PaperClipOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit') || record.status === 'reserved'}
onClick={async () => {
const res = await reserveBooth(record.id, '当前用户');
if (res.code === 200) { message.success('预留成功'); actionRef.current?.reload(); }
else { message.error(res.msg); }
}}
></Button>,
<Button key="release" type="link" size="small" icon={<UnlockOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit') || record.status === 'available'}
onClick={async () => {
const res = await releaseBooth(record.id);
if (res.code === 200) { message.success('解除成功'); actionRef.current?.reload(); }
else { message.error(res.msg); }
}}
></Button>,
],
},
];
return (
<div>
<ProTable
headerTitle="摊位信息列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getBoothList({
current: params.current,
pageSize: params.pageSize,
boothNumber: params.boothNumber,
companyName: params.companyName,
});
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="add" type="primary" icon={<PlusOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:add')}
onClick={() => { setEditingBooth(null); setModalOpen(true); }}
></Button>,
]}
/>
<ModalForm
title={editingBooth ? '编辑摊位' : '新增摊位'}
open={modalOpen}
width={400}
modalProps={{ destroyOnClose: true, onCancel: () => setModalOpen(false) }}
onFinish={async (values) => {
const res = editingBooth
? await updateBooth({ id: editingBooth.id, ...values })
: await addBooth({ venueId: 1, ...values } as any);
if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; }
message.error(res.msg); return false;
}}
initialValues={editingBooth}
>
<ProFormText name="boothNumber" label="展位号" rules={[{ required: true, message: '请输入展位号' }]} placeholder="如A01" />
<ProFormText name="companyName" label="企业名称" placeholder="请输入企业名称" />
<ProFormSelect name="status" label="状态"
options={[
{ label: '可用', value: 'available' },
{ label: '固定', value: 'fixed' },
{ label: '预留', value: 'reserved' },
{ label: '锁定', value: 'locked' },
]}
/>
<ProFormText name="remark" label="备注" placeholder="请输入备注" />
</ModalForm>
</div>
);
};
export default BoothTab;

View File

@@ -0,0 +1,77 @@
import React, { useRef, useState } from 'react';
import { Button, message } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { ExportOutlined } from '@ant-design/icons';
import { getCompanyQueryList } from '@/services/jobportal/indoorJobFair';
import type { CompanyQueryItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const CompanyQueryTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const handleExport = async (type: string) => {
if (type === 'selected' && selectedRowKeys.length === 0) {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
};
const columns: ProColumns<CompanyQueryItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '招聘会名称',
dataIndex: 'fairName',
hideInTable: true,
order: 1,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true },
{ title: '行业', dataIndex: 'industry', width: 120, hideInSearch: true },
{ title: '规模', dataIndex: 'scale', width: 120, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{ title: '报名时间', dataIndex: 'registrationTime', valueType: 'dateTime', width: 170, hideInSearch: true },
];
return (
<div>
<ProTable
headerTitle="参会单位列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
}}
request={async (params) => {
const res = await getCompanyQueryList({
current: params.current,
pageSize: params.pageSize,
fairName: params.fairName,
});
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="exportSelected" icon={<ExportOutlined />}
onClick={() => handleExport('selected')}
></Button>,
<Button key="exportPage" icon={<ExportOutlined />}
onClick={() => handleExport('page')}
></Button>,
<Button key="exportAll" icon={<ExportOutlined />}
onClick={() => handleExport('all')}
></Button>,
]}
/>
</div>
);
};
export default CompanyQueryTab;

View File

@@ -0,0 +1,153 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components';
import { EyeOutlined } from '@ant-design/icons';
import { getCompanyRecordList, updateCompanyRecordStatus, updateCompanyCreditStatus } from '@/services/jobportal/indoorJobFair';
import type { CompanyRecordItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const CompanyRecordTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [currentRecord, setCurrentRecord] = useState<CompanyRecordItem | null>(null);
const [modalType, setModalType] = useState<'status' | 'credit'>('status');
const statusMap: Record<string, { text: string; color: string }> = {
normal: { text: '正常', color: 'green' },
abnormal: { text: '异常', color: 'red' },
};
const creditStatusMap: Record<string, { text: string; color: string }> = {
good: { text: '良好', color: 'green' },
poor: { text: '失信', color: 'red' },
};
const handleUpdateStatus = async (values: any) => {
if (!currentRecord) return false;
const res = await updateCompanyRecordStatus(currentRecord.id, values.status);
if (res.code === 200) {
message.success('状态修改成功');
setModalOpen(false);
actionRef.current?.reload();
return true;
}
message.error(res.msg);
return false;
};
const handleUpdateCredit = async (values: any) => {
if (!currentRecord) return false;
const res = await updateCompanyCreditStatus(currentRecord.id, values.creditStatus);
if (res.code === 200) {
message.success('失信记录更新成功');
setModalOpen(false);
actionRef.current?.reload();
return true;
}
message.error(res.msg);
return false;
};
const columns: ProColumns<CompanyRecordItem>[] = [
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true },
{ title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true },
{ title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{
title: '状态',
dataIndex: 'status',
width: 80,
hideInSearch: true,
render: (_, record) => {
const s = statusMap[record.status] || { text: record.status, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{
title: '失信情况',
dataIndex: 'creditStatus',
width: 80,
hideInSearch: true,
render: (_, record) => {
const s = creditStatusMap[record.creditStatus] || { text: record.creditStatus, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '参会次数', dataIndex: 'fairCount', width: 80, hideInSearch: true },
{ title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime', width: 170, hideInSearch: true },
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, record) => [
<Button key="view" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
></Button>,
<Button key="status" type="link" size="small"
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setCurrentRecord(record); setModalType('status'); setModalOpen(true); }}
></Button>,
<Button key="credit" type="link" size="small" danger
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setCurrentRecord(record); setModalType('credit'); setModalOpen(true); }}
></Button>,
],
},
];
return (
<div>
<ProTable
headerTitle="单位参会查询列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getCompanyRecordList({
current: params.current,
pageSize: params.pageSize,
companyName: params.companyName,
contactPerson: params.contactPerson,
contactPhone: params.contactPhone,
});
return { data: res.rows, total: res.total, success: true };
}}
/>
<ModalForm
title={modalType === 'status' ? '修改状态' : '失信记录'}
open={modalOpen}
width={400}
modalProps={{ destroyOnClose: true, onCancel: () => setModalOpen(false) }}
onFinish={modalType === 'status' ? handleUpdateStatus : handleUpdateCredit}
initialValues={currentRecord}
>
{modalType === 'status' ? (
<ProFormSelect name="status" label="状态"
options={[
{ label: '正常', value: 'normal' },
{ label: '异常', value: 'abnormal' },
]}
/>
) : (
<ProFormSelect name="creditStatus" label="失信情况"
options={[
{ label: '良好', value: 'good' },
{ label: '失信', value: 'poor' },
]}
/>
)}
</ModalForm>
</div>
);
};
export default CompanyRecordTab;

View File

@@ -0,0 +1,153 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { ExportOutlined, EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { getCompanyReviewList, batchApproveCompany, batchRejectCompany } from '@/services/jobportal/indoorJobFair';
import type { CompanyReviewItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const CompanyReviewTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审核', color: 'default' },
approved: { text: '已通过', color: 'green' },
rejected: { text: '已拒绝', color: 'red' },
};
const handleBatchApprove = async () => {
if (selectedRowKeys.length === 0) {
message.warning('请先选择要审核的数据');
return;
}
const res = await batchApproveCompany(selectedRowKeys as number[]);
if (res.code === 200) {
message.success('批量审核成功');
setSelectedRowKeys([]);
actionRef.current?.reload();
} else {
message.error(res.msg);
}
};
const handleBatchReject = async () => {
if (selectedRowKeys.length === 0) {
message.warning('请先选择要审核的数据');
return;
}
const res = await batchRejectCompany(selectedRowKeys as number[]);
if (res.code === 200) {
message.success('批量审核失败');
setSelectedRowKeys([]);
actionRef.current?.reload();
} else {
message.error(res.msg);
}
};
const handleExport = async (type: string) => {
if (type === 'selected' && selectedRowKeys.length === 0) {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
};
const columns: ProColumns<CompanyReviewItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '招聘会名称',
dataIndex: 'fairName',
hideInTable: true,
order: 1,
},
{
title: '单位名称',
dataIndex: 'companyName',
order: 2,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{ title: '行业', dataIndex: 'industry', width: 100, hideInSearch: true },
{ title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{
title: '审核状态',
dataIndex: 'reviewStatus',
valueType: 'select',
width: 100,
hideInSearch: true,
render: (_, record) => {
const s = statusMap[record.reviewStatus] || { text: record.reviewStatus, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, record) => [
<Button key="viewCompany" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
></Button>,
<Button key="viewFair" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看招聘会详情(模拟)')}
></Button>,
],
},
];
return (
<div>
<ProTable
headerTitle="参会单位审核列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
}}
request={async (params) => {
const res = await getCompanyReviewList({
current: params.current,
pageSize: params.pageSize,
fairName: params.fairName,
companyName: params.companyName,
reviewStatus: params.reviewStatus,
});
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="batchApprove" type="primary" icon={<CheckOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={handleBatchApprove}
></Button>,
<Button key="batchReject" danger icon={<CloseOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={handleBatchReject}
></Button>,
<Button key="exportSelected" icon={<ExportOutlined />}
onClick={() => handleExport('selected')}
></Button>,
<Button key="exportPage" icon={<ExportOutlined />}
onClick={() => handleExport('page')}
></Button>,
<Button key="exportAll" icon={<ExportOutlined />}
onClick={() => handleExport('all')}
></Button>,
]}
/>
</div>
);
};
export default CompanyReviewTab;

View File

@@ -0,0 +1,147 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components';
import { PlusOutlined, DeleteOutlined, FormOutlined, ApartmentOutlined } from '@ant-design/icons';
import {
getJobFairList,
addJobFair,
updateJobFair,
deleteJobFair,
} from '@/services/jobportal/indoorJobFair';
import type { JobFairItem, JobFairForm } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
fairInfo: JobFairItem;
onFairUpdate: () => void;
}
const FairManageTab: React.FC<Props> = ({ fairInfo }) => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [editingFair, setEditingFair] = useState<JobFairItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '筹备中', color: 'default' },
ongoing: { text: '进行中', color: 'green' },
finished: { text: '已结束', color: 'red' },
};
const columns: ProColumns<JobFairItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true },
{ title: '场地名称', dataIndex: 'venueName', ellipsis: true, hideInSearch: true },
{ title: '开始日期', dataIndex: 'startDate', hideInSearch: true },
{ title: '结束日期', dataIndex: 'endDate', hideInSearch: true },
{ title: '使用展位数', dataIndex: 'boothUseCount', hideInSearch: true },
{
title: '状态',
dataIndex: 'status',
hideInSearch: true,
render: (_, record) => {
const s = statusMap[record.status] || { text: record.status, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime', hideInSearch: true },
{
title: '操作',
valueType: 'option',
width: 200,
render: (_, record) => [
<Button key="booth" type="link" size="small" icon={<ApartmentOutlined />}
onClick={() => message.info('展位管理功能开发中')}
></Button>,
<Button key="edit" type="link" size="small" icon={<FormOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setEditingFair(record); setModalOpen(true); }}
></Button>,
<Button key="delete" type="link" size="small" danger icon={<DeleteOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:remove')}
onClick={() => {
Modal.confirm({
title: '确认删除',
content: `确定删除招聘会「${record.fairName}」吗?`,
onOk: async () => {
const res = await deleteJobFair(record.id);
if (res.code === 200) { message.success('删除成功'); actionRef.current?.reload(); }
else { message.error(res.msg); }
},
});
}}
></Button>,
],
},
];
const venueOptions = [
{ label: '石河子大学体育馆', value: 1 },
{ label: '第八师人力资源市场', value: 2 },
{ label: '阿拉尔市人才交流中心', value: 3 },
{ label: '图木舒克市职业介绍所', value: 4 },
];
const venueNameMap: Record<number, string> = {
1: '石河子大学体育馆',
2: '第八师人力资源市场',
3: '阿拉尔市人才交流中心',
4: '图木舒克市职业介绍所',
};
return (
<div>
<ProTable
headerTitle="招聘会列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getJobFairList({
current: params.current,
pageSize: params.pageSize,
fairName: params.fairName,
startDate: params.startDate,
endDate: params.endDate,
} as any);
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="add" type="primary" icon={<PlusOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:add')}
onClick={() => { setEditingFair(null); setModalOpen(true); }}
></Button>,
]}
/>
<ModalForm
title={editingFair ? '编辑招聘会' : '新增招聘会'}
open={modalOpen}
width={500}
modalProps={{ destroyOnClose: true, onCancel: () => setModalOpen(false) }}
onFinish={async (values) => {
const submitValues = {
...values,
venueName: venueNameMap[values.venueId as number] || '',
};
const res = editingFair
? await updateJobFair({ id: editingFair.id, ...submitValues } as JobFairForm)
: await addJobFair(submitValues as JobFairForm);
if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; }
message.error(res.msg); return false;
}}
initialValues={editingFair}
>
<ProFormText name="fairName" label="招聘会名称" rules={[{ required: true, message: '请输入招聘会名称' }]} />
<ProFormSelect name="venueId" label="选择场地" rules={[{ required: true, message: '请选择场地' }]}
options={venueOptions} />
<ProFormText name="startDate" label="开始日期" rules={[{ required: true, message: '请输入开始日期' }]} />
<ProFormText name="endDate" label="结束日期" rules={[{ required: true, message: '请输入结束日期' }]} />
</ModalForm>
</div>
);
};
export default FairManageTab;

View File

@@ -0,0 +1,90 @@
import React, { useRef, useState } from 'react';
import { Button, message } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { ExportOutlined } from '@ant-design/icons';
import { getJobQueryList } from '@/services/jobportal/indoorJobFair';
import type { JobQueryItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const JobQueryTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const handleExport = async (type: string) => {
if (type === 'selected' && selectedRowKeys.length === 0) {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
};
const columns: ProColumns<JobQueryItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '招聘会名称',
dataIndex: 'fairName',
hideInTable: true,
order: 1,
},
{
title: '单位名称',
dataIndex: 'companyName',
hideInTable: true,
order: 2,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{ title: '职位名称', dataIndex: 'jobTitle', ellipsis: true },
{
title: '薪资范围',
dataIndex: 'salary',
hideInSearch: true,
render: (_, record) => `${record.minSalary}-${record.maxSalary}`,
},
{ title: '学历要求', dataIndex: 'education', width: 100, hideInSearch: true },
{ title: '经验要求', dataIndex: 'experience', width: 100, hideInSearch: true },
{ title: '招聘人数', dataIndex: 'vacancies', width: 80, hideInSearch: true },
{ title: '状态', dataIndex: 'status', width: 80, hideInSearch: true, render: (_, r) => r.status === 'open' ? '招聘中' : '已结束' },
];
return (
<div>
<ProTable
headerTitle="参会岗位列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
}}
request={async (params) => {
const res = await getJobQueryList({
current: params.current,
pageSize: params.pageSize,
fairName: params.fairName,
companyName: params.companyName,
});
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="exportSelected" icon={<ExportOutlined />}
onClick={() => handleExport('selected')}
></Button>,
<Button key="exportPage" icon={<ExportOutlined />}
onClick={() => handleExport('page')}
></Button>,
<Button key="exportAll" icon={<ExportOutlined />}
onClick={() => handleExport('all')}
></Button>,
]}
/>
</div>
);
};
export default JobQueryTab;

View File

@@ -0,0 +1,140 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Tag } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText } from '@ant-design/pro-components';
import { EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { getJobReviewList, approveJob, rejectJob } from '@/services/jobportal/indoorJobFair';
import type { JobReviewItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const JobReviewTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [reviewModalOpen, setReviewModalOpen] = useState(false);
const [currentJob, setCurrentJob] = useState<JobReviewItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审核', color: 'default' },
approved: { text: '已通过', color: 'green' },
rejected: { text: '已拒绝', color: 'red' },
};
const handleApprove = async (values: any) => {
if (!currentJob) return false;
const res = await approveJob(currentJob.id, values.reviewRemark);
if (res.code === 200) {
message.success('审核通过');
setReviewModalOpen(false);
actionRef.current?.reload();
return true;
}
message.error(res.msg);
return false;
};
const handleReject = async (values: any) => {
if (!currentJob) return false;
const res = await rejectJob(currentJob.id, values.reviewRemark);
if (res.code === 200) {
message.success('审核不通过');
setReviewModalOpen(false);
actionRef.current?.reload();
return true;
}
message.error(res.msg);
return false;
};
const columns: ProColumns<JobReviewItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '单位名称',
dataIndex: 'companyName',
order: 1,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{
title: '职位名称',
dataIndex: 'jobTitle',
order: 2,
},
{ title: '职位名称', dataIndex: 'jobTitle', ellipsis: true, hideInSearch: true },
{ title: '学历要求', dataIndex: 'education', width: 100, hideInSearch: true },
{ title: '经验要求', dataIndex: 'experience', width: 100, hideInSearch: true },
{ title: '招聘人数', dataIndex: 'vacancies', width: 80, hideInSearch: true },
{
title: '审核状态',
dataIndex: 'reviewStatus',
valueType: 'select',
width: 100,
hideInSearch: true,
render: (_, record) => {
const s = statusMap[record.reviewStatus] || { text: record.reviewStatus, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{
title: '操作',
valueType: 'option',
width: 200,
render: (_, record) => [
<Button key="view" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
></Button>,
record.reviewStatus === 'pending' && (
<Button key="review" type="link" size="small"
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setCurrentJob(record); setReviewModalOpen(true); }}
></Button>
),
],
},
];
return (
<div>
<ProTable
headerTitle="招聘会职位审核列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getJobReviewList({
current: params.current,
pageSize: params.pageSize,
companyName: params.companyName,
jobTitle: params.jobTitle,
reviewStatus: params.reviewStatus,
});
return { data: res.rows, total: res.total, success: true };
}}
/>
<ModalForm
title="职位审核"
open={reviewModalOpen}
width={500}
modalProps={{ destroyOnClose: true, onCancel: () => setReviewModalOpen(false) }}
onFinish={async (values) => {
if (values.action === 'approve') {
return handleApprove(values);
} else {
return handleReject(values);
}
}}
initialValues={currentJob}
>
<ProFormText name="jobTitle" label="职位名称" disabled />
<ProFormText name="companyName" label="单位名称" disabled />
<ProFormText name="reviewRemark" label="审核意见" placeholder="请输入审核意见" />
</ModalForm>
</div>
);
};
export default JobReviewTab;

View File

@@ -0,0 +1,51 @@
import React, { useRef } from 'react';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { getReservationRecordList } from '@/services/jobportal/indoorJobFair';
import type { ReservationRecordItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const ReservationRecordTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const columns: ProColumns<ReservationRecordItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '操作人',
dataIndex: 'operator',
order: 1,
},
{ title: '操作人', dataIndex: 'operator', width: 100, hideInSearch: true },
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{ title: '展位号', dataIndex: 'boothNumber', width: 100, hideInSearch: true },
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true, render: (_, r) => r.companyName || '--' },
{ title: '操作类型', dataIndex: 'operateType', width: 100, hideInSearch: true },
{ title: '操作时间', dataIndex: 'operateTime', valueType: 'dateTime', width: 170, hideInSearch: true },
{ title: '备注', dataIndex: 'remark', ellipsis: true, hideInSearch: true, render: (_, r) => r.remark || '--' },
];
return (
<div>
<ProTable
headerTitle="预留操作记录列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getReservationRecordList({
current: params.current,
pageSize: params.pageSize,
operator: params.operator,
});
return { data: res.rows, total: res.total, success: true };
}}
/>
</div>
);
};
export default ReservationRecordTab;

View File

@@ -0,0 +1,60 @@
import React, { useRef } from 'react';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { getUnsubscribeList } from '@/services/jobportal/indoorJobFair';
import type { UnsubscribeItem } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const UnsubscribeTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const columns: ProColumns<UnsubscribeItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '招聘会名称',
dataIndex: 'fairName',
hideInTable: true,
order: 1,
},
{
title: '单位名称',
dataIndex: 'companyName',
order: 2,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{ title: '展位号', dataIndex: 'boothNumber', width: 100, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{ title: '退订时间', dataIndex: 'unsubscribeTime', valueType: 'dateTime', width: 170, hideInSearch: true },
{ title: '退订原因', dataIndex: 'reason', ellipsis: true, hideInSearch: true },
];
return (
<div>
<ProTable
headerTitle="退订信息列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getUnsubscribeList({
current: params.current,
pageSize: params.pageSize,
fairName: params.fairName,
companyName: params.companyName,
boothNumber: params.boothNumber,
contactPerson: params.contactPerson,
contactPhone: params.contactPhone,
});
return { data: res.rows, total: res.total, success: true };
}}
/>
</div>
);
};
export default UnsubscribeTab;

View File

@@ -0,0 +1,131 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } from '@ant-design/pro-components';
import { PlusOutlined, DeleteOutlined, FormOutlined, LayoutOutlined } from '@ant-design/icons';
import {
getVenueList,
addVenue,
updateVenue,
deleteVenue,
} from '@/services/jobportal/indoorJobFair';
import type { VenueItem, VenueForm } from '@/services/jobportal/indoorJobFair';
interface Props {
fairId: number;
}
const VenueTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [editingVenue, setEditingVenue] = useState<VenueItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
active: { text: '启用', color: 'green' },
inactive: { text: '停用', color: 'red' },
};
const columns: ProColumns<VenueItem>[] = [
{ title: '场地名称', dataIndex: 'venueName', ellipsis: true },
{ title: '就业机构', dataIndex: 'employmentAgency', ellipsis: true },
{ title: '场地类型', dataIndex: 'venueType', width: 100 },
{ title: '场地地址', dataIndex: 'venueAddress', ellipsis: true, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{ title: '展位数', dataIndex: 'boothCount', width: 80, hideInSearch: true },
{
title: '状态',
dataIndex: 'status',
width: 80,
hideInSearch: true,
render: (_, record) => {
const s = statusMap[record.status] || { text: record.status, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, record) => [
<Button key="edit" type="link" size="small" icon={<FormOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setEditingVenue(record); setModalOpen(true); }}
></Button>,
<Button key="layout" type="link" size="small" icon={<LayoutOutlined />}
onClick={() => message.info('场地布局功能开发中')}
></Button>,
<Button key="delete" type="link" size="small" danger icon={<DeleteOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:remove')}
onClick={() => {
Modal.confirm({
title: '确认删除',
content: `确定删除场地「${record.venueName}」吗?`,
onOk: async () => {
const res = await deleteVenue(record.id);
if (res.code === 200) { message.success('删除成功'); actionRef.current?.reload(); }
else { message.error(res.msg); }
},
});
}}
></Button>,
],
},
];
return (
<div>
<ProTable
headerTitle="场地信息列表"
actionRef={actionRef}
rowKey="id"
columns={columns}
scroll={{ x: 'max-content' }}
search={{ labelWidth: 100 }}
request={async (params) => {
const res = await getVenueList({
current: params.current,
pageSize: params.pageSize,
employmentAgency: params.employmentAgency,
venueName: params.venueName,
venueType: params.venueType,
});
return { data: res.rows, total: res.total, success: true };
}}
toolBarRender={() => [
<Button key="add" type="primary" icon={<PlusOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:add')}
onClick={() => { setEditingVenue(null); setModalOpen(true); }}
></Button>,
]}
/>
<ModalForm
title={editingVenue ? '编辑场地' : '新增场地'}
open={modalOpen}
width={500}
modalProps={{ destroyOnClose: true, onCancel: () => setModalOpen(false) }}
onFinish={async (values) => {
const res = editingVenue
? await updateVenue({ id: editingVenue.id, ...values })
: await addVenue(values as VenueForm);
if (res.code === 200) { message.success(res.msg); setModalOpen(false); actionRef.current?.reload(); return true; }
message.error(res.msg); return false;
}}
initialValues={editingVenue}
>
<ProFormText name="venueName" label="场地名称" rules={[{ required: true, message: '请输入场地名称' }]} />
<ProFormText name="employmentAgency" label="就业机构" rules={[{ required: true, message: '请输入就业机构' }]} />
<ProFormSelect name="venueType" label="场地类型" rules={[{ required: true, message: '请选择场地类型' }]}
options={[{ label: '室内', value: '室内' }, { label: '室外', value: '室外' }]} />
<ProFormText name="venueAddress" label="场地地址" rules={[{ required: true, message: '请输入场地地址' }]} />
<ProFormText name="contactPerson" label="联系人" rules={[{ required: true, message: '请输入联系人' }]} />
<ProFormText name="contactPhone" label="联系电话" rules={[{ required: true, message: '请输入联系电话' }]} />
<ProFormText name="boothCount" label="展位数" rules={[{ required: true, message: '请输入展位数' }]} />
</ModalForm>
</div>
);
};
export default VenueTab;