招聘会三个模块开发
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-24 09:06:09 +08:00
parent e7616b0c8e
commit 3b1877f302
23 changed files with 939 additions and 98 deletions

View File

@@ -0,0 +1,103 @@
import React, { useState } from 'react';
import { Modal, Form, Select, Input, Button, message, Space } from 'antd';
import type { BoothItem } from './FairBoothModal';
interface Props {
booth: BoothItem | null;
open: boolean;
onClose: () => void;
onSuccess: (boothNumber: string, companyName: string) => void;
}
const AVAILABLE_COMPANIES = [
{ companyId: 1001, companyName: '新疆天业(集团)有限公司', industry: '化工', scale: '1000人以上' },
{ companyId: 1002, companyName: '石河子经济技术开发区管委会', industry: '政府/公共事业', scale: '500-1000人' },
{ companyId: 1003, companyName: '新疆西部牧业股份有限公司', industry: '农业/畜牧', scale: '500-1000人' },
{ companyId: 1004, companyName: '兵团水利水电工程集团', industry: '建筑/工程', scale: '1000人以上' },
{ companyId: 1005, companyName: '阿拉尔新爵纺织有限公司', industry: '纺织/服装', scale: '200-500人' },
{ companyId: 1006, companyName: '新疆通用航空有限责任公司', industry: '航空/交通', scale: '200-500人' },
{ companyId: 1007, companyName: '石河子市商业银行', industry: '金融', scale: '500-1000人' },
{ companyId: 1008, companyName: '兵团广播电视大学', industry: '教育/科研', scale: '200-500人' },
];
const BoothAssignModal: React.FC<Props> = ({ booth, open, onClose, onSuccess }) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
if (!booth) return null;
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setLoading(true);
// 模拟提交
setTimeout(() => {
message.success(`展位「${booth.boothNumber}」已分配给「${values.companyName}`);
onSuccess(booth.boothNumber, values.companyName);
form.resetFields();
onClose();
setLoading(false);
}, 500);
} catch (error) {
console.error('表单验证失败', error);
}
};
return (
<Modal
title={`分配展位 - ${booth.boothNumber}`}
open={open}
onCancel={onClose}
footer={[
<Button key="cancel" onClick={onClose}></Button>,
<Button key="submit" type="primary" loading={loading} onClick={handleSubmit}>
</Button>,
]}
width={500}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item
name="companyName"
label="选择企业"
rules={[{ required: true, message: '请选择要分配的企业' }]}
>
<Select
showSearch
placeholder="请搜索并选择企业"
optionFilterProp="label"
options={AVAILABLE_COMPANIES.map(c => ({
label: c.companyName,
value: c.companyName,
}))}
/>
</Form.Item>
<Form.Item label="企业信息" shouldUpdate>
{() => {
const companyName = form.getFieldValue('companyName');
const company = AVAILABLE_COMPANIES.find(c => c.companyName === companyName);
if (!company) return null;
return (
<div style={{ background: '#f5f5f5', padding: 16, borderRadius: 8 }}>
<Space direction="vertical" size="small">
<div><strong></strong>{company.companyName}</div>
<div><strong></strong>{company.industry}</div>
<div><strong></strong>{company.scale}</div>
</Space>
</div>
);
}}
</Form.Item>
<Form.Item name="remark" label="备注">
<Input.TextArea rows={3} placeholder="请输入分配备注(选填)" />
</Form.Item>
</Form>
</Modal>
);
};
export default BoothAssignModal;

View File

@@ -18,7 +18,7 @@ const CompanyQueryTab: React.FC<Props> = () => {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据`);
};
const columns: ProColumns<CompanyQueryItem>[] = [

View File

@@ -87,7 +87,7 @@ const CompanyRecordTab: React.FC<Props> = () => {
width: 150,
render: (_, record) => [
<Button key="view" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
onClick={() => message.info('查看单位基本信息')}
></Button>,
<Button key="status" type="link" size="small"
hidden={!access.hasPerms('cms:indoorJobFair:edit')}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Modal, Descriptions, Tag, Space } from 'antd';
import type { CompanyReviewItem } from '@/services/jobportal/indoorJobFair';
interface Props {
record: CompanyReviewItem | null;
open: boolean;
onClose: () => void;
}
const CompanyInfoModal: React.FC<Props> = ({ record, open, onClose }) => {
if (!record) return null;
return (
<Modal
title="单位基本信息"
open={open}
onCancel={onClose}
footer={null}
width={600}
destroyOnClose
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="单位名称" span={2}>{record.companyName}</Descriptions.Item>
<Descriptions.Item label="行业">{record.industry}</Descriptions.Item>
<Descriptions.Item label="规模">{record.scale}</Descriptions.Item>
<Descriptions.Item label="联系人">{record.contactPerson}</Descriptions.Item>
<Descriptions.Item label="联系电话">{record.contactPhone}</Descriptions.Item>
<Descriptions.Item label="预约开始日期">{record.reservationStart}</Descriptions.Item>
<Descriptions.Item label="预约结束日期">{record.reservationEnd}</Descriptions.Item>
<Descriptions.Item label="审核状态">
<Tag color={record.reviewStatus === 'approved' ? 'green' : record.reviewStatus === 'rejected' ? 'red' : 'default'}>
{record.reviewStatus === 'approved' ? '已通过' : record.reviewStatus === 'rejected' ? '已拒绝' : '待审核'}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="审核时间">{record.reviewTime || '--'}</Descriptions.Item>
<Descriptions.Item label="审核备注" span={2}>{record.reviewRemark || '--'}</Descriptions.Item>
</Descriptions>
</Modal>
);
};
export default CompanyInfoModal;

View File

@@ -1,10 +1,13 @@
import React, { useRef, useState } from 'react';
import { useAccess } from '@umijs/max';
import { Button, message, Modal, Tag } from 'antd';
import { Button, message, 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';
import CompanyReviewModal from './CompanyReviewModal';
import FairInfoModal from './FairInfoModal';
import ReviewModal from './ReviewModal';
interface Props {
fairId: number;
@@ -15,6 +18,13 @@ const CompanyReviewTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
// 弹窗状态
const [companyModalOpen, setCompanyModalOpen] = useState(false);
const [fairModalOpen, setFairModalOpen] = useState(false);
const [reviewModalOpen, setReviewModalOpen] = useState(false);
const [currentRecord, setCurrentRecord] = useState<CompanyReviewItem | null>(null);
const [reviewType, setReviewType] = useState<'approve' | 'reject'>('approve');
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审核', color: 'default' },
approved: { text: '已通过', color: 'green' },
@@ -56,11 +66,26 @@ const CompanyReviewTab: React.FC<Props> = () => {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据`);
};
const handleViewCompany = (record: CompanyReviewItem) => {
setCurrentRecord(record);
setCompanyModalOpen(true);
};
const handleViewFair = (record: CompanyReviewItem) => {
setCurrentRecord(record);
setFairModalOpen(true);
};
const handleSingleReview = (record: CompanyReviewItem, type: 'approve' | 'reject') => {
setCurrentRecord(record);
setReviewType(type);
setReviewModalOpen(true);
};
const columns: ProColumns<CompanyReviewItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '招聘会名称',
dataIndex: 'fairName',
@@ -80,7 +105,6 @@ const CompanyReviewTab: React.FC<Props> = () => {
{
title: '审核状态',
dataIndex: 'reviewStatus',
valueType: 'select',
width: 100,
hideInSearch: true,
render: (_, record) => {
@@ -91,14 +115,24 @@ const CompanyReviewTab: React.FC<Props> = () => {
{
title: '操作',
valueType: 'option',
width: 150,
width: 220,
render: (_, record) => [
<Button key="viewCompany" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
onClick={() => handleViewCompany(record)}
></Button>,
<Button key="viewFair" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看招聘会详情(模拟)')}
onClick={() => handleViewFair(record)}
></Button>,
record.reviewStatus === 'pending' && (
<Button key="approve" type="link" size="small" icon={<CheckOutlined />}
onClick={() => handleSingleReview(record, 'approve')}
></Button>
),
record.reviewStatus === 'pending' && (
<Button key="reject" type="link" size="small" danger icon={<CloseOutlined />}
onClick={() => handleSingleReview(record, 'reject')}
></Button>
),
],
},
];
@@ -146,6 +180,26 @@ const CompanyReviewTab: React.FC<Props> = () => {
></Button>,
]}
/>
<CompanyReviewModal
record={currentRecord}
open={companyModalOpen}
onClose={() => setCompanyModalOpen(false)}
/>
<FairInfoModal
record={currentRecord}
open={fairModalOpen}
onClose={() => setFairModalOpen(false)}
/>
<ReviewModal
record={currentRecord}
type={reviewType}
open={reviewModalOpen}
onClose={() => setReviewModalOpen(false)}
onSuccess={() => actionRef.current?.reload()}
/>
</div>
);
};

View File

@@ -0,0 +1,186 @@
import React, { useState } from 'react';
import { Modal, Tag, Space, Card, Table, Button, message, Select } from 'antd';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import type { JobFairItem } from '@/services/jobportal/indoorJobFair';
import BoothAssignModal from './BoothAssignModal';
interface Props {
fair: JobFairItem | null;
open: boolean;
onClose: () => void;
}
interface BoothItem {
id: number;
boothNumber: string;
companyName: string;
status: string;
checkInStatus: string;
}
const FairBoothModal: React.FC<Props> = ({ fair, open, onClose }) => {
const [filterStatus, setFilterStatus] = useState<string>('all');
const [assignModalOpen, setAssignModalOpen] = useState(false);
const [assigningBooth, setAssigningBooth] = useState<BoothItem | null>(null);
const [boothData, setBoothData] = useState<BoothItem[]>([
{ id: 1, boothNumber: 'A01', companyName: '新疆天业(集团)有限公司', status: '已分配', checkInStatus: '已签到' },
{ id: 2, boothNumber: 'A02', companyName: '石河子经济技术开发区管委会', status: '已分配', checkInStatus: '已签到' },
{ id: 3, boothNumber: 'A03', companyName: '--', status: '空闲', checkInStatus: '--' },
{ id: 4, boothNumber: 'A04', companyName: '新疆西部牧业股份有限公司', status: '已分配', checkInStatus: '未签到' },
{ id: 5, boothNumber: 'A05', companyName: '--', status: '空闲', checkInStatus: '--' },
{ id: 6, boothNumber: 'A06', companyName: '兵团水利水电工程集团', status: '已分配', checkInStatus: '已签到' },
{ id: 7, boothNumber: 'A07', companyName: '--', status: '空闲', checkInStatus: '--' },
{ id: 8, boothNumber: 'A08', companyName: '新疆通用航空有限责任公司', status: '已分配', checkInStatus: '未签到' },
{ id: 9, boothNumber: 'B01', companyName: '--', status: '空闲', checkInStatus: '--' },
{ id: 10, boothNumber: 'B02', companyName: '阿拉尔新爵纺织有限公司', status: '已分配', checkInStatus: '已签到' },
{ id: 11, boothNumber: 'B03', companyName: '--', status: '预留', checkInStatus: '--' },
{ id: 12, boothNumber: 'B04', companyName: '--', status: '空闲', checkInStatus: '--' },
]);
if (!fair) return null;
const filteredData = filterStatus === 'all'
? boothData
: boothData.filter(b => b.status === filterStatus);
const handleCheckIn = (record: BoothItem) => {
AntModal.confirm({
title: '确认签到',
content: `确认展位「${record.boothNumber}」(${record.companyName})已完成现场签到吗?`,
onOk: () => {
setBoothData(prev => prev.map(item =>
item.id === record.id ? { ...item, checkInStatus: '已签到' } : item
));
message.success('签到成功');
},
});
};
const handleCancel = (record: BoothItem) => {
AntModal.confirm({
title: '确认退订',
content: `确认展位「${record.boothNumber}」(${record.companyName})退订吗?`,
onOk: () => {
setBoothData(prev => prev.map(item =>
item.id === record.id ? { ...item, status: '空闲', checkInStatus: '--', companyName: '--' } : item
));
message.success('退订成功');
},
});
};
const handleAssign = (record: BoothItem) => {
setAssigningBooth(record);
setAssignModalOpen(true);
};
const handleAssignSuccess = (boothNumber: string, companyName: string) => {
if (!assigningBooth) return;
setBoothData(prev => prev.map(item =>
item.id === assigningBooth.id ? { ...item, status: '已分配', companyName, checkInStatus: '未签到' } : item
));
};
const columns = [
{ title: '展位号', dataIndex: 'boothNumber', width: 100 },
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (status: string) => {
let color = 'default';
if (status === '已分配') color = 'blue';
else if (status === '空闲') color = 'green';
else if (status === '预留') color = 'orange';
return <Tag color={color}>{status}</Tag>;
}
},
{
title: '签到状态',
dataIndex: 'checkInStatus',
width: 100,
render: (status: string) => {
if (status === '--') return <span style={{ color: '#999' }}>--</span>;
return <Tag color={status === '已签到' ? 'green' : 'orange'}>{status}</Tag>;
}
},
{
title: '操作',
width: 150,
render: (_: any, record: BoothItem) => (
record.status !== '空闲' ? (
<Space>
{record.checkInStatus === '未签到' ? (
<Button size="small" type="link" icon={<CheckOutlined />} onClick={() => handleCheckIn(record)}></Button>
) : (
<Button size="small" type="link" disabled icon={<CheckOutlined />}></Button>
)}
<Button size="small" type="link" danger icon={<CloseOutlined />} onClick={() => handleCancel(record)}>退</Button>
</Space>
) : (
<Button size="small" type="link" onClick={() => handleAssign(record)}></Button>
)
)
},
];
return (
<Modal
title={`展位管理 - ${fair.fairName}`}
open={open}
onCancel={onClose}
footer={null}
width={900}
destroyOnClose
>
<div style={{ marginBottom: 16 }}>
<Space>
<span>{boothData.length}</span>
<span>{boothData.filter(b => b.status === '已分配').length}</span>
<span>{boothData.filter(b => b.status === '空闲').length}</span>
<span>{boothData.filter(b => b.status === '预留').length}</span>
</Space>
<div style={{ marginTop: 8 }}>
<Select
value={filterStatus}
onChange={setFilterStatus}
style={{ width: 120 }}
options={[
{ label: '全部', value: 'all' },
{ label: '已分配', value: '已分配' },
{ label: '空闲', value: '空闲' },
{ label: '预留', value: '预留' },
]}
/>
</div>
</div>
<Table
columns={columns}
dataSource={filteredData}
rowKey="id"
size="small"
pagination={{ pageSize: 10, size: 'small' }}
/>
<Card size="small" title="招聘会信息" style={{ marginTop: 16 }}>
<Space split={<span style={{ color: '#ddd' }}>|</span>}>
<span><strong></strong>{fair.venueName}</span>
<span><strong></strong>{fair.startDate}</span>
<span><strong></strong>{fair.endDate}</span>
<span><strong></strong><Tag color={fair.status === 'ongoing' ? 'green' : fair.status === 'pending' ? 'orange' : 'red'}>{fair.status === 'ongoing' ? '进行中' : fair.status === 'pending' ? '筹备中' : '已结束'}</Tag></span>
</Space>
</Card>
<BoothAssignModal
booth={assigningBooth}
open={assignModalOpen}
onClose={() => setAssignModalOpen(false)}
onSuccess={handleAssignSuccess}
/>
</Modal>
);
};
export default FairBoothModal;

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { Modal, Descriptions, Tag, Space } from 'antd';
import type { CompanyReviewItem } from '@/services/jobportal/indoorJobFair';
interface Props {
record: CompanyReviewItem | null;
open: boolean;
onClose: () => void;
}
const FairInfoModal: React.FC<Props> = ({ record, open, onClose }) => {
if (!record) return null;
const fairInfo = {
fairName: record.fairName,
venueName: '石河子大学体育馆',
address: '石河子市北三路石河子大学内',
startDate: '2026-03-20',
endDate: '2026-03-22',
status: 'ongoing',
boothCount: 60,
boothUseCount: 45,
organizer: '石河子大学就业指导中心',
contactPerson: '王老师',
contactPhone: '0993-2058999',
};
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '筹备中', color: 'default' },
ongoing: { text: '进行中', color: 'green' },
finished: { text: '已结束', color: 'red' },
};
return (
<Modal
title="招聘会详情"
open={open}
onCancel={onClose}
footer={null}
width={600}
destroyOnClose
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="招聘会名称" span={2}>{fairInfo.fairName}</Descriptions.Item>
<Descriptions.Item label="场地名称">{fairInfo.venueName}</Descriptions.Item>
<Descriptions.Item label="场地地址">{fairInfo.address}</Descriptions.Item>
<Descriptions.Item label="开始日期">{fairInfo.startDate}</Descriptions.Item>
<Descriptions.Item label="结束日期">{fairInfo.endDate}</Descriptions.Item>
<Descriptions.Item label="招聘会状态">
<Tag color={statusMap[fairInfo.status]?.color}>{statusMap[fairInfo.status]?.text}</Tag>
</Descriptions.Item>
<Descriptions.Item label="展位总数">{fairInfo.boothCount}</Descriptions.Item>
<Descriptions.Item label="已使用展位">{fairInfo.boothUseCount}</Descriptions.Item>
<Descriptions.Item label="主办单位" span={2}>{fairInfo.organizer}</Descriptions.Item>
<Descriptions.Item label="联系人">{fairInfo.contactPerson}</Descriptions.Item>
<Descriptions.Item label="联系电话">{fairInfo.contactPhone}</Descriptions.Item>
</Descriptions>
</Modal>
);
};
export default FairInfoModal;

View File

@@ -10,6 +10,7 @@ import {
deleteJobFair,
} from '@/services/jobportal/indoorJobFair';
import type { JobFairItem, JobFairForm } from '@/services/jobportal/indoorJobFair';
import FairBoothModal from './FairBoothModal';
interface Props {
fairId: number;
@@ -22,6 +23,8 @@ const FairManageTab: React.FC<Props> = ({ fairInfo }) => {
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [editingFair, setEditingFair] = useState<JobFairItem | null>(null);
const [boothModalOpen, setBoothModalOpen] = useState(false);
const [boothFair, setBoothFair] = useState<JobFairItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '筹备中', color: 'default' },
@@ -51,7 +54,7 @@ const FairManageTab: React.FC<Props> = ({ fairInfo }) => {
width: 200,
render: (_, record) => [
<Button key="booth" type="link" size="small" icon={<ApartmentOutlined />}
onClick={() => message.info('展位管理功能开发中')}
onClick={() => { setBoothFair(record); setBoothModalOpen(true); }}
></Button>,
<Button key="edit" type="link" size="small" icon={<FormOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
@@ -140,6 +143,12 @@ const FairManageTab: React.FC<Props> = ({ fairInfo }) => {
<ProFormText name="startDate" label="开始日期" rules={[{ required: true, message: '请输入开始日期' }]} />
<ProFormText name="endDate" label="结束日期" rules={[{ required: true, message: '请输入结束日期' }]} />
</ModalForm>
<FairBoothModal
fair={boothFair}
open={boothModalOpen}
onClose={() => setBoothModalOpen(false)}
/>
</div>
);
};

View File

@@ -18,7 +18,7 @@ const JobQueryTab: React.FC<Props> = () => {
message.warning('请先选择要导出的数据');
return;
}
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据(模拟)`);
message.success(`已导出${type === 'selected' ? '选中' : type === 'page' ? '当前页' : '全部'}数据`);
};
const columns: ProColumns<JobQueryItem>[] = [

View File

@@ -1,7 +1,7 @@
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 { Button, message, Tag, Modal, Descriptions } from 'antd';
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect } 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';
@@ -14,7 +14,9 @@ const JobReviewTab: React.FC<Props> = () => {
const access = useAccess();
const actionRef = useRef<ActionType>();
const [reviewModalOpen, setReviewModalOpen] = useState(false);
const [companyModalOpen, setCompanyModalOpen] = useState(false);
const [currentJob, setCurrentJob] = useState<JobReviewItem | null>(null);
const [reviewAction, setReviewAction] = useState<'approve' | 'reject'>('approve');
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审核', color: 'default' },
@@ -22,45 +24,67 @@ const JobReviewTab: React.FC<Props> = () => {
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 handleViewCompany = (record: JobReviewItem) => {
setCurrentJob(record);
setCompanyModalOpen(true);
};
const handleReject = async (values: any) => {
if (!currentJob) return false;
const res = await rejectJob(currentJob.id, values.reviewRemark);
const handleOpenReview = (record: JobReviewItem, action: 'approve' | 'reject') => {
setCurrentJob(record);
setReviewAction(action);
setReviewModalOpen(true);
};
const handleApprove = async (remark?: string) => {
if (!currentJob) return;
const res = await approveJob(currentJob.id, remark);
if (res.code === 200) {
message.success('审核通过');
message.success('审核通过');
setReviewModalOpen(false);
actionRef.current?.reload();
return true;
} else {
message.error(res.msg);
}
};
const handleReject = async (remark?: string) => {
if (!currentJob) return;
const res = await rejectJob(currentJob.id, remark);
if (res.code === 200) {
message.success('审核已拒绝');
setReviewModalOpen(false);
actionRef.current?.reload();
} else {
message.error(res.msg);
}
message.error(res.msg);
return false;
};
const columns: ProColumns<JobReviewItem>[] = [
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{
title: '单位名称',
dataIndex: 'companyName',
hideInTable: true,
order: 1,
},
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{
title: '职位名称',
dataIndex: 'jobTitle',
hideInTable: true,
order: 2,
},
{
title: '审核状态',
dataIndex: 'reviewStatus',
hideInTable: true,
valueType: 'select',
options: [
{ label: '待审核', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已拒绝', value: 'rejected' },
],
},
{ title: '招聘会名称', dataIndex: 'fairName', ellipsis: true, hideInSearch: true },
{ title: '单位名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{ title: '职位名称', dataIndex: 'jobTitle', ellipsis: true, hideInSearch: true },
{ title: '学历要求', dataIndex: 'education', width: 100, hideInSearch: true },
{ title: '经验要求', dataIndex: 'experience', width: 100, hideInSearch: true },
@@ -68,7 +92,6 @@ const JobReviewTab: React.FC<Props> = () => {
{
title: '审核状态',
dataIndex: 'reviewStatus',
valueType: 'select',
width: 100,
hideInSearch: true,
render: (_, record) => {
@@ -79,16 +102,20 @@ const JobReviewTab: React.FC<Props> = () => {
{
title: '操作',
valueType: 'option',
width: 200,
width: 180,
render: (_, record) => [
<Button key="view" type="link" size="small" icon={<EyeOutlined />}
onClick={() => message.info('查看单位基本信息(模拟)')}
onClick={() => handleViewCompany(record)}
></Button>,
record.reviewStatus === 'pending' && (
<Button key="review" type="link" size="small"
hidden={!access.hasPerms('cms:indoorJobFair:edit')}
onClick={() => { setCurrentJob(record); setReviewModalOpen(true); }}
></Button>
<Button key="approve" type="link" size="small" icon={<CheckOutlined />}
onClick={() => handleOpenReview(record, 'approve')}
></Button>
),
record.reviewStatus === 'pending' && (
<Button key="reject" type="link" size="small" danger icon={<CloseOutlined />}
onClick={() => handleOpenReview(record, 'reject')}
></Button>
),
],
},
@@ -115,24 +142,72 @@ const JobReviewTab: React.FC<Props> = () => {
}}
/>
<ModalForm
title="职位审核"
open={reviewModalOpen}
{/* 单位信息弹窗 */}
<Modal
title="单位基本信息"
open={companyModalOpen}
onCancel={() => setCompanyModalOpen(false)}
footer={null}
width={500}
modalProps={{ destroyOnClose: true, onCancel: () => setReviewModalOpen(false) }}
onFinish={async (values) => {
if (values.action === 'approve') {
return handleApprove(values);
} else {
return handleReject(values);
}
}}
initialValues={currentJob}
destroyOnClose
>
<ProFormText name="jobTitle" label="职位名称" disabled />
<ProFormText name="companyName" label="单位名称" disabled />
<ProFormText name="reviewRemark" label="审核意见" placeholder="请输入审核意见" />
</ModalForm>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="单位名称" span={2}>{currentJob?.companyName}</Descriptions.Item>
<Descriptions.Item label="招聘会">{currentJob?.fairName}</Descriptions.Item>
<Descriptions.Item label="职位名称">{currentJob?.jobTitle}</Descriptions.Item>
<Descriptions.Item label="学历要求">{currentJob?.education}</Descriptions.Item>
<Descriptions.Item label="经验要求">{currentJob?.experience}</Descriptions.Item>
<Descriptions.Item label="招聘人数">{currentJob?.vacancies}</Descriptions.Item>
</Descriptions>
</Modal>
{/* 审核弹窗 */}
<Modal
title={reviewAction === 'approve' ? '审核通过' : '审核拒绝'}
open={reviewModalOpen}
onCancel={() => setReviewModalOpen(false)}
footer={[
<Button key="cancel" onClick={() => setReviewModalOpen(false)}></Button>,
<Button
key="submit"
type="primary"
danger={reviewAction === 'reject'}
onClick={() => {
if (reviewAction === 'approve') {
handleApprove();
} else {
handleReject();
}
}}
>
{reviewAction === 'approve' ? '通过' : '拒绝'}
</Button>,
]}
width={500}
destroyOnClose
>
<Descriptions column={2} bordered size="small" style={{ marginBottom: 16 }}>
<Descriptions.Item label="单位名称" span={2}>{currentJob?.companyName}</Descriptions.Item>
<Descriptions.Item label="职位名称">{currentJob?.jobTitle}</Descriptions.Item>
<Descriptions.Item label="招聘人数">{currentJob?.vacancies}</Descriptions.Item>
<Descriptions.Item label="学历要求">{currentJob?.education}</Descriptions.Item>
<Descriptions.Item label="经验要求">{currentJob?.experience}</Descriptions.Item>
</Descriptions>
<ProFormSelect
name="action"
label="审核操作"
valueEnum={{ approve: '审核通过', reject: '审核拒绝' }}
initialValue={reviewAction}
disabled
/>
<ProFormText
name="remark"
label={reviewAction === 'reject' ? '拒绝原因' : '审核备注'}
placeholder={reviewAction === 'reject' ? '请输入拒绝原因' : '请输入审核备注(选填)'}
/>
</Modal>
</div>
);
};

View File

@@ -11,13 +11,7 @@ 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: 'operator', order: 1 },
{ 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 || '--' },

View File

@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { Modal, Form, Input, Button, message } from 'antd';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import type { CompanyReviewItem } from '@/services/jobportal/indoorJobFair';
import { batchApproveCompany, batchRejectCompany } from '@/services/jobportal/indoorJobFair';
interface Props {
record: CompanyReviewItem | null;
type: 'approve' | 'reject';
open: boolean;
onClose: () => void;
onSuccess: () => void;
}
const ReviewModal: React.FC<Props> = ({ record, type, open, onClose, onSuccess }) => {
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
if (!record) return null;
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setLoading(true);
const res = type === 'approve'
? await batchApproveCompany([record.id])
: await batchRejectCompany([record.id]);
if (res.code === 200) {
message.success(type === 'approve' ? '审核已通过' : '审核已拒绝');
onSuccess();
onClose();
form.resetFields();
} else {
message.error(res.msg);
}
} catch (error) {
console.error('表单验证失败', error);
} finally {
setLoading(false);
}
};
return (
<Modal
title={type === 'approve' ? '审核通过' : '审核拒绝'}
open={open}
onCancel={onClose}
footer={[
<Button key="cancel" onClick={onClose}></Button>,
<Button
key="submit"
type="primary"
loading={loading}
icon={type === 'approve' ? <CheckOutlined /> : <CloseOutlined />}
danger={type === 'reject'}
onClick={handleSubmit}
>
{type === 'approve' ? '通过' : '拒绝'}
</Button>,
]}
width={500}
destroyOnClose
>
<div style={{ marginBottom: 16 }}>
<strong></strong>{record.companyName}
</div>
<Form form={form} layout="vertical">
<Form.Item
name="remark"
label="审核意见"
rules={type === 'reject' ? [{ required: true, message: '请输入拒绝原因' }] : []}
>
<Input.TextArea
rows={4}
placeholder={type === 'approve' ? '请输入审核通过备注(选填)' : '请输入拒绝原因(必填)'}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default ReviewModal;

View File

@@ -0,0 +1,162 @@
import React, { useState } from 'react';
import { Modal, Tag, Space, Card, Row, Col, Button, message } from 'antd';
import { CloseOutlined, CheckOutlined } from '@ant-design/icons';
import type { VenueItem } from '@/services/jobportal/indoorJobFair';
interface Props {
venue: VenueItem | null;
open: boolean;
onClose: () => void;
}
const VENUE_LAYOUT_DATA = [
// A区
{ id: 1, area: 'A', row: 1, col: 1, boothNumber: 'A01', status: 'reserved', companyName: '新疆天业(集团)有限公司' },
{ id: 2, area: 'A', row: 1, col: 2, boothNumber: 'A02', status: 'fixed', companyName: '石河子经济技术开发区管委会' },
{ id: 3, area: 'A', row: 1, col: 3, boothNumber: 'A03', status: 'available', companyName: '' },
{ id: 4, area: 'A', row: 1, col: 4, boothNumber: 'A04', status: 'reserved', companyName: '新疆西部牧业股份有限公司' },
{ id: 5, area: 'A', row: 2, col: 1, boothNumber: 'A05', status: 'available', companyName: '' },
{ id: 6, area: 'A', row: 2, col: 2, boothNumber: 'A06', status: 'locked', companyName: '' },
{ id: 7, area: 'A', row: 2, col: 3, boothNumber: 'A07', status: 'available', companyName: '' },
{ id: 8, area: 'A', row: 2, col: 4, boothNumber: 'A08', status: 'reserved', companyName: '兵团水利水电工程集团' },
// B区
{ id: 9, area: 'B', row: 1, col: 1, boothNumber: 'B01', status: 'available', companyName: '' },
{ id: 10, area: 'B', row: 1, col: 2, boothNumber: 'B02', status: 'fixed', companyName: '阿拉尔新爵纺织有限公司' },
{ id: 11, area: 'B', row: 1, col: 3, boothNumber: 'B03', status: 'locked', companyName: '' },
{ id: 12, area: 'B', row: 1, col: 4, boothNumber: 'B04', status: 'available', companyName: '' },
{ id: 13, area: 'B', row: 2, col: 1, boothNumber: 'B05', status: 'reserved', companyName: '新疆通用航空有限责任公司' },
{ id: 14, area: 'B', row: 2, col: 2, boothNumber: 'B06', status: 'available', companyName: '' },
{ id: 15, area: 'B', row: 2, col: 3, boothNumber: 'B07', status: 'reserved', companyName: '石河子市商业银行' },
{ id: 16, area: 'B', row: 2, col: 4, boothNumber: 'B08', status: 'available', companyName: '' },
];
const statusMap: Record<string, { text: string; color: string }> = {
available: { text: '可用', color: '#52c41a' },
fixed: { text: '固定', color: '#1890ff' },
reserved: { text: '预留', color: '#fa8c16' },
locked: { text: '锁定', color: '#ff4d4f' },
};
const VenueLayoutModal: React.FC<Props> = ({ venue, open, onClose }) => {
const [selectedBooth, setSelectedBooth] = useState<typeof VENUE_LAYOUT_DATA[0] | null>(null);
if (!venue) return null;
const areas = ['A', 'B'];
const handleBoothClick = (booth: typeof VENUE_LAYOUT_DATA[0]) => {
setSelectedBooth(booth);
};
const getBoothStyle = (status: string) => {
switch (status) {
case 'available':
return { backgroundColor: '#f6ffed', borderColor: '#52c41a' };
case 'fixed':
return { backgroundColor: '#e6f7ff', borderColor: '#1890ff' };
case 'reserved':
return { backgroundColor: '#fff7e6', borderColor: '#fa8c16' };
case 'locked':
return { backgroundColor: '#fff1f0', borderColor: '#ff4d4f' };
default:
return {};
}
};
return (
<Modal
title={`场地布局 - ${venue.venueName}`}
open={open}
onCancel={onClose}
footer={null}
width={900}
destroyOnClose
>
<div style={{ marginBottom: 16 }}>
<Space>
<Tag color="#52c41a"></Tag>
<Tag color="#1890ff"></Tag>
<Tag color="#fa8c16"></Tag>
<Tag color="#ff4d4f"></Tag>
</Space>
<span style={{ marginLeft: 24, color: '#888' }}>
{venue.boothCount} | 使 {VENUE_LAYOUT_DATA.filter(b => b.status !== 'available').length} | {VENUE_LAYOUT_DATA.filter(b => b.status === 'available').length}
</span>
</div>
<Row gutter={16}>
<Col span={16}>
<Card size="small" title="平面布局图" style={{ marginBottom: 16 }}>
{areas.map((area) => (
<div key={area} style={{ marginBottom: 16 }}>
<div style={{ fontWeight: 'bold', marginBottom: 8 }}>{area}</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
{VENUE_LAYOUT_DATA.filter(b => b.area === area).map((booth) => (
<div
key={booth.id}
onClick={() => handleBoothClick(booth)}
style={{
...getBoothStyle(booth.status),
border: '1px solid',
borderRadius: 4,
padding: '8px 4px',
textAlign: 'center',
cursor: 'pointer',
minHeight: 60,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div style={{ fontSize: 12, fontWeight: 'bold' }}>{booth.boothNumber}</div>
{booth.companyName ? (
<div style={{ fontSize: 10, color: '#666', marginTop: 2, maxWidth: 80, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{booth.companyName}
</div>
) : (
<div style={{ fontSize: 10, color: '#999' }}>{statusMap[booth.status].text}</div>
)}
</div>
))}
</div>
</div>
))}
</Card>
</Col>
<Col span={8}>
<Card size="small" title="展位详情">
{selectedBooth ? (
<div>
<p><strong></strong>{selectedBooth.boothNumber}</p>
<p><strong></strong><Tag color={statusMap[selectedBooth.status].color}>{statusMap[selectedBooth.status].text}</Tag></p>
<p><strong></strong>{selectedBooth.area} {selectedBooth.row} {selectedBooth.col}</p>
<p><strong></strong>{selectedBooth.companyName || '--'}</p>
<Space style={{ marginTop: 16 }}>
<Button size="small" icon={<CheckOutlined />}></Button>
<Button size="small" icon={<CloseOutlined />}></Button>
</Space>
</div>
) : (
<div style={{ color: '#999', textAlign: 'center', padding: 20 }}>
</div>
)}
</Card>
<Card size="small" title="场地信息" style={{ marginTop: 16 }}>
<p><strong></strong>{venue.venueName}</p>
<p><strong></strong>{venue.employmentAgency}</p>
<p><strong></strong>{venue.venueType}</p>
<p><strong></strong>{venue.venueAddress}</p>
<p><strong></strong>{venue.contactPerson}</p>
<p><strong></strong>{venue.contactPhone}</p>
</Card>
</Col>
</Row>
</Modal>
);
};
export default VenueLayoutModal;

View File

@@ -10,6 +10,7 @@ import {
deleteVenue,
} from '@/services/jobportal/indoorJobFair';
import type { VenueItem, VenueForm } from '@/services/jobportal/indoorJobFair';
import VenueLayoutModal from './VenueLayoutModal';
interface Props {
fairId: number;
@@ -20,6 +21,8 @@ const VenueTab: React.FC<Props> = () => {
const actionRef = useRef<ActionType>();
const [modalOpen, setModalOpen] = useState(false);
const [editingVenue, setEditingVenue] = useState<VenueItem | null>(null);
const [layoutModalOpen, setLayoutModalOpen] = useState(false);
const [layoutVenue, setLayoutVenue] = useState<VenueItem | null>(null);
const statusMap: Record<string, { text: string; color: string }> = {
active: { text: '启用', color: 'green' },
@@ -54,7 +57,7 @@ const VenueTab: React.FC<Props> = () => {
onClick={() => { setEditingVenue(record); setModalOpen(true); }}
></Button>,
<Button key="layout" type="link" size="small" icon={<LayoutOutlined />}
onClick={() => message.info('场地布局功能开发中')}
onClick={() => { setLayoutVenue(record); setLayoutModalOpen(true); }}
></Button>,
<Button key="delete" type="link" size="small" danger icon={<DeleteOutlined />}
hidden={!access.hasPerms('cms:indoorJobFair:remove')}
@@ -124,6 +127,12 @@ const VenueTab: React.FC<Props> = () => {
<ProFormText name="contactPhone" label="联系电话" rules={[{ required: true, message: '请输入联系电话' }]} />
<ProFormText name="boothCount" label="展位数" rules={[{ required: true, message: '请输入展位数' }]} />
</ModalForm>
<VenueLayoutModal
venue={layoutVenue}
open={layoutModalOpen}
onClose={() => setLayoutModalOpen(false)}
/>
</div>
);
};

View File

@@ -36,17 +36,45 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
const [interviewModalOpen, setInterviewModalOpen] = useState(false);
const columns: ProColumns<any>[] = [
{ title: '姓名', dataIndex: 'name', width: 100 },
{
title: '姓名',
dataIndex: 'name',
width: 100,
hideInTable: true,
fieldProps: { placeholder: '请输入姓名' },
},
{
title: '手机号',
dataIndex: 'phone',
width: 130,
hideInTable: true,
fieldProps: { placeholder: '请输入手机号' },
},
{
title: '面试状态',
dataIndex: 'interviewStatus',
hideInTable: true,
valueType: 'select',
options: [
{ label: '未面试', value: 'none' },
{ label: '已预约', value: 'scheduled' },
{ label: '面试中', value: 'in_progress' },
{ label: '已完成', value: 'completed' },
{ label: '已通过', value: 'passed' },
{ label: '未通过', value: 'failed' },
],
},
{ title: '姓名', dataIndex: 'name', width: 100, hideInSearch: true },
{ title: '性别', dataIndex: 'gender', width: 60, hideInSearch: true },
{ title: '手机号', dataIndex: 'phone', width: 130 },
{ title: '手机号', dataIndex: 'phone', width: 130, hideInSearch: true },
{ title: '身份证号', dataIndex: 'idCard', width: 180, hideInSearch: true },
{ title: '学历', dataIndex: 'education', width: 80, hideInSearch: true },
{ title: '毕业院校', dataIndex: 'graduationSchool', width: 150, hideInSearch: true, ellipsis: true },
{ title: '入场时间', dataIndex: 'checkInTime', valueType: 'dateTime', width: 170, render: (_, r) => r.checkInTime || '未入场' },
{ title: '入场时间', dataIndex: 'checkInTime', valueType: 'dateTime', width: 170, hideInSearch: true, render: (_, r) => r.checkInTime || '未入场' },
{ title: '入场方式', dataIndex: 'checkInMethod', width: 100, hideInSearch: true,
render: (_, r) => checkInMethodMap[r.checkInMethod] || r.checkInMethod },
{ title: '意向企业', dataIndex: 'targetCompanies', width: 180, ellipsis: true, hideInSearch: true },
{ title: '面试状态', dataIndex: 'interviewStatus', width: 100,
{ title: '面试状态', dataIndex: 'interviewStatus', width: 100, hideInSearch: true,
render: (_, r) => {
const s = interviewStatusMap[r.interviewStatus] || { text: r.interviewStatus, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
@@ -97,14 +125,14 @@ const AttendeeTab: React.FC<Props> = ({ fairId }) => {
toolBarRender={() => [
<Button key="simulate" type="primary" icon={<ScanOutlined />}
onClick={() => setScanModalOpen(true)}
></Button>,
></Button>,
<Button key="export" icon={<ExportOutlined />}></Button>,
]}
/>
{/* 模拟扫码入场弹窗 */}
{/* 扫码入场弹窗 */}
<ModalForm
title="模拟扫码入场"
title="扫码入场"
open={scanModalOpen}
width={450}
modalProps={{ destroyOnClose: true, onCancel: () => setScanModalOpen(false) }}

View File

@@ -32,8 +32,27 @@ const BoothTab: React.FC<Props> = ({ fairId }) => {
const [assigningBooth, setAssigningBooth] = useState<any>(null);
const columns: ProColumns<any>[] = [
{ title: '展位号', dataIndex: 'boothNumber', width: 100 },
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true, render: (_, r) => r.companyName || '--' },
{
title: '展位号',
dataIndex: 'boothNumber',
width: 100,
hideInTable: true,
fieldProps: { placeholder: '请输入展位号' },
},
{
title: '审核状态',
dataIndex: 'reviewStatus',
width: 100,
hideInTable: true,
valueType: 'select',
options: [
{ label: '待审核', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已拒绝', value: 'rejected' },
],
},
{ title: '展位号', dataIndex: 'boothNumber', width: 100, hideInSearch: true },
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true, render: (_, r) => r.companyName || '--' },
{ title: '岗位数', dataIndex: 'jobCount', width: 80, hideInSearch: true },
{ title: '审核状态', dataIndex: 'reviewStatus', width: 100,
render: (_, r) => {
@@ -65,7 +84,7 @@ const BoothTab: React.FC<Props> = ({ fairId }) => {
disabled: !record.companyName,
onClick: () => {
if (!record.companyName) { message.warning('该展位尚未分配企业'); return; }
message.success(`已向「${record.companyName}」发送展位信息通知(模拟)`);
message.success(`已向「${record.companyName}」发送展位信息通知`);
},
},
{ type: 'divider' as const },

View File

@@ -10,7 +10,7 @@ interface Props {
onSuccess: () => void;
}
/** 模拟可添加的企业列表 */
/** 可添加的企业列表 */
const MOCK_AVAILABLE_COMPANIES = [
{ companyId: 1013, companyName: '石河子市天富集团', industry: '能源', scale: '1000人以上', contactPerson: '黄经理', contactPhone: '13309930001' },
{ companyId: 1014, companyName: '新疆兵团勘察设计院', industry: '建筑/工程', scale: '200-500人', contactPerson: '曹院长', contactPhone: '13309930002' },

View File

@@ -48,8 +48,8 @@ const CompanyReviewTab: React.FC<Props> = ({ fairId }) => {
const handleQrCheckIn = async (id: number) => {
Modal.confirm({
title: '模拟扫码签到',
content: '确认该单位已完成现场扫码签到吗?(模拟操作)',
title: '扫码签到',
content: '确认该单位已完成现场扫码签到吗?',
onOk: async () => {
const res = await qrCheckIn(id);
if (res.code === 200) { message.success(res.msg); actionRef.current?.reload(); }
@@ -58,22 +58,40 @@ const CompanyReviewTab: React.FC<Props> = ({ fairId }) => {
};
const handleNotify = (companyName: string, method: string) => {
message.success(`已通过${method}向「${companyName}」发送审核结果通知(模拟)`);
message.success(`已通过${method}向「${companyName}」发送审核结果通知`);
};
const columns: ProColumns<any>[] = [
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true },
{
title: '企业名称',
dataIndex: 'companyName',
ellipsis: true,
hideInTable: true,
fieldProps: { placeholder: '请输入企业名称' },
},
{
title: '审核状态',
dataIndex: 'reviewStatus',
hideInTable: true,
valueType: 'select',
options: [
{ label: '待审核', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已拒绝', value: 'rejected' },
],
},
{ title: '企业名称', dataIndex: 'companyName', ellipsis: true, hideInSearch: true },
{ title: '行业', dataIndex: 'industry', width: 80, hideInSearch: true, render: (_, r) => <Tag>{r.industry}</Tag> },
{ title: '规模', dataIndex: 'scale', width: 100, hideInSearch: true },
{ title: '联系人', dataIndex: 'contactPerson', width: 100, hideInSearch: true },
{ title: '联系电话', dataIndex: 'contactPhone', width: 130, hideInSearch: true },
{ title: '审核状态', dataIndex: 'reviewStatus', width: 100,
{ title: '审核状态', dataIndex: 'reviewStatus', width: 100, hideInSearch: true,
render: (_, r) => {
const s = reviewStatusMap[r.reviewStatus] || { text: r.reviewStatus, color: 'default' };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '签到状态', dataIndex: 'qrCheckInStatus', width: 100,
{ title: '签到状态', dataIndex: 'qrCheckInStatus', width: 100, hideInSearch: true,
render: (_, r) => <Tag color={r.qrCheckInStatus === 'checked_in' ? 'green' : 'default'}>
{r.qrCheckInStatus === 'checked_in' ? '已签到' : '未签到'}
</Tag>,

View File

@@ -86,7 +86,7 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
key: 'qrcode',
icon: <QrcodeOutlined />,
label: '重新生成二维码',
onClick: () => message.success(`已为「${record.deviceName}」重新生成二维码(模拟)`),
onClick: () => message.success(`已为「${record.deviceName}」重新生成二维码`),
}] : []),
{
key: 'edit',
@@ -173,8 +173,8 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
</Col>
</Row>
{/* 模拟闸机 */}
<Card title="模拟闸机" style={{ marginBottom: 16 }}>
{/* 闸机管理 */}
<Card title="闸机管理" style={{ marginBottom: 16 }}>
<Row gutter={16} style={{ marginBottom: 16 }}>
{gateStatuses.map((gate: any) => (
<Col span={12} key={gate.id}>
@@ -190,7 +190,7 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
<Button type="primary" size="small" icon={<ScanOutlined />}
disabled={!gate.isOpen}
onClick={() => { setScanGate(gate); setScanModalOpen(true); }}
></Button>
></Button>
</Space>
</Card>
</Col>
@@ -265,9 +265,9 @@ const DeviceTab: React.FC<Props> = ({ fairId }) => {
/>
</ModalForm>
{/* 模拟闸机扫码 */}
{/* 闸机扫码 */}
<ModalForm
title={`模拟闸机扫码 — ${scanGate?.gateName || ''}`}
title={`闸机扫码 — ${scanGate?.gateName || ''}`}
open={scanModalOpen}
width={400}
modalProps={{ destroyOnClose: true, onCancel: () => setScanModalOpen(false) }}