招聘会三个模块开发
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
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:
@@ -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;
|
||||
@@ -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>[] = [
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>[] = [
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 || '--' },
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
Reference in New Issue
Block a user