11
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
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:
190
src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx
Normal file
190
src/pages/Jobfair/Outdoorfair/Detail/components/AttendeeTab.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useAccess } from '@umijs/max';
|
||||
import { Button, message, Modal, Tag, Drawer, Space } from 'antd';
|
||||
import { ActionType, ProColumns, ProTable, ModalForm, ProFormText, ProFormSelect, ProFormDateTimePicker, ProFormTextArea } from '@ant-design/pro-components';
|
||||
import { ScanOutlined, EditOutlined, ScheduleOutlined, ExportOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
getAttendeeList,
|
||||
scanAttendeeEntry,
|
||||
updateAttendeeResume,
|
||||
trackInterview,
|
||||
} from '@/services/jobportal/outdoorFairDetail';
|
||||
|
||||
interface Props {
|
||||
fairId: number;
|
||||
}
|
||||
|
||||
const checkInMethodMap: Record<string, string> = {
|
||||
qr_scan: '扫码入场', id_card: '刷身份证', manual: '手动登记',
|
||||
};
|
||||
|
||||
const interviewStatusMap: Record<string, { text: string; color: string }> = {
|
||||
none: { text: '未面试', color: 'default' },
|
||||
scheduled: { text: '已预约', color: 'blue' },
|
||||
in_progress: { text: '面试中', color: 'processing' },
|
||||
completed: { text: '已完成', color: 'green' },
|
||||
passed: { text: '已通过', color: 'green' },
|
||||
failed: { text: '未通过', color: 'red' },
|
||||
};
|
||||
|
||||
const AttendeeTab: React.FC<Props> = ({ fairId }) => {
|
||||
const access = useAccess();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [scanModalOpen, setScanModalOpen] = useState(false);
|
||||
const [resumeDrawerOpen, setResumeDrawerOpen] = useState(false);
|
||||
const [currentAttendee, setCurrentAttendee] = useState<any>(null);
|
||||
const [interviewModalOpen, setInterviewModalOpen] = useState(false);
|
||||
|
||||
const columns: ProColumns<any>[] = [
|
||||
{ title: '姓名', dataIndex: 'name', width: 100 },
|
||||
{ title: '性别', dataIndex: 'gender', width: 60, hideInSearch: true },
|
||||
{ title: '手机号', dataIndex: 'phone', width: 130 },
|
||||
{ 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: '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,
|
||||
render: (_, r) => {
|
||||
const s = interviewStatusMap[r.interviewStatus] || { text: r.interviewStatus, color: 'default' };
|
||||
return <Tag color={s.color}>{s.text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作', valueType: 'option', width: 220,
|
||||
render: (_, record) => [
|
||||
<Button key="scan" type="link" size="small" icon={<ScanOutlined />}
|
||||
onClick={() => {
|
||||
if (record.checkInTime) { message.info('该人员已入场'); return; }
|
||||
Modal.confirm({
|
||||
title: '确认入场', content: `确认${record.name}扫码入场?`,
|
||||
onOk: async () => {
|
||||
const res = await scanAttendeeEntry({
|
||||
name: record.name, phone: record.phone, idCard: record.idCard, checkInMethod: 'qr_scan',
|
||||
});
|
||||
if (res.code === 200) { message.success('扫码入场成功'); actionRef.current?.reload(); }
|
||||
},
|
||||
});
|
||||
}}
|
||||
>{record.checkInTime ? '已入场' : '扫码入场'}</Button>,
|
||||
<Button key="resume" type="link" size="small" icon={<EditOutlined />}
|
||||
onClick={() => { setCurrentAttendee(record); setResumeDrawerOpen(true); }}
|
||||
>简历</Button>,
|
||||
<Button key="interview" type="link" size="small" icon={<ScheduleOutlined />}
|
||||
onClick={() => { setCurrentAttendee(record); setInterviewModalOpen(true); }}
|
||||
>面试</Button>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProTable
|
||||
headerTitle="参会人员列表"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
search={{ labelWidth: 80 }}
|
||||
request={async (params) => {
|
||||
const res = await getAttendeeList(fairId, {
|
||||
current: params.current, pageSize: params.pageSize,
|
||||
name: params.name, phone: params.phone, interviewStatus: params.interviewStatus,
|
||||
});
|
||||
return { data: res.rows, total: res.total, success: true };
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<Button key="simulate" type="primary" icon={<ScanOutlined />}
|
||||
onClick={() => setScanModalOpen(true)}
|
||||
>模拟扫码入场</Button>,
|
||||
<Button key="export" icon={<ExportOutlined />}>导出</Button>,
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 模拟扫码入场弹窗 */}
|
||||
<ModalForm
|
||||
title="模拟扫码入场"
|
||||
open={scanModalOpen}
|
||||
width={450}
|
||||
modalProps={{ destroyOnClose: true, onCancel: () => setScanModalOpen(false) }}
|
||||
onFinish={async (values) => {
|
||||
const res = await scanAttendeeEntry(values);
|
||||
if (res.code === 200) { message.success(res.msg); setScanModalOpen(false); actionRef.current?.reload(); return true; }
|
||||
message.error(res.msg); return false;
|
||||
}}
|
||||
>
|
||||
<ProFormText name="name" label="姓名" rules={[{ required: true, message: '请输入姓名' }]} placeholder="请输入姓名" />
|
||||
<ProFormText name="phone" label="手机号" rules={[{ required: true, message: '请输入手机号' }]} placeholder="请输入手机号" />
|
||||
<ProFormText name="idCard" label="身份证号" rules={[{ required: true }]} placeholder="请输入身份证号" />
|
||||
<ProFormSelect name="checkInMethod" label="入场方式" initialValue="qr_scan"
|
||||
options={[
|
||||
{ label: '扫码入场', value: 'qr_scan' },
|
||||
{ label: '刷身份证', value: 'id_card' },
|
||||
{ label: '手动登记', value: 'manual' },
|
||||
]}
|
||||
rules={[{ required: true }]}
|
||||
/>
|
||||
</ModalForm>
|
||||
|
||||
{/* 简历编辑抽屉 */}
|
||||
<Drawer
|
||||
title={`编辑简历 — ${currentAttendee?.name || ''}`}
|
||||
width={500}
|
||||
open={resumeDrawerOpen}
|
||||
onClose={() => setResumeDrawerOpen(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<ModalForm
|
||||
title={null}
|
||||
open
|
||||
submitter={{ searchConfig: { submitText: '保存简历' } }}
|
||||
modalProps={{ footer: null }}
|
||||
initialValues={currentAttendee}
|
||||
onFinish={async (values) => {
|
||||
if (!currentAttendee) return;
|
||||
const res = await updateAttendeeResume(currentAttendee.id, values);
|
||||
if (res.code === 200) { message.success('简历更新成功'); setResumeDrawerOpen(false); actionRef.current?.reload(); return true; }
|
||||
message.error(res.msg); return false;
|
||||
}}
|
||||
>
|
||||
<ProFormSelect name="education" label="学历" width="md"
|
||||
options={['高中', '大专', '本科', '硕士', '博士'].map(v => ({ label: v, value: v }))}
|
||||
/>
|
||||
<ProFormText name="graduationSchool" label="毕业院校" width="md" placeholder="请输入毕业院校" />
|
||||
<ProFormText name="major" label="专业" width="md" placeholder="请输入专业" />
|
||||
<ProFormTextArea name="resumeContent" label="简历内容" placeholder="请输入简历内容" />
|
||||
</ModalForm>
|
||||
</Drawer>
|
||||
|
||||
{/* 面试跟踪弹窗 */}
|
||||
<ModalForm
|
||||
title={`面试跟踪 — ${currentAttendee?.name || ''}`}
|
||||
open={interviewModalOpen}
|
||||
width={500}
|
||||
modalProps={{ destroyOnClose: true, onCancel: () => setInterviewModalOpen(false) }}
|
||||
initialValues={currentAttendee}
|
||||
onFinish={async (values) => {
|
||||
if (!currentAttendee) return;
|
||||
const res = await trackInterview({ attendeeId: currentAttendee.id, ...values });
|
||||
if (res.code === 200) { message.success('面试信息更新成功'); setInterviewModalOpen(false); actionRef.current?.reload(); return true; }
|
||||
message.error(res.msg); return false;
|
||||
}}
|
||||
>
|
||||
<ProFormSelect name="interviewStatus" label="面试状态" width="md"
|
||||
options={[
|
||||
{ label: '未面试', value: 'none' }, { label: '已预约', value: 'scheduled' },
|
||||
{ label: '面试中', value: 'in_progress' }, { label: '已完成', value: 'completed' },
|
||||
{ label: '已通过', value: 'passed' }, { label: '未通过', value: 'failed' },
|
||||
]}
|
||||
rules={[{ required: true }]}
|
||||
/>
|
||||
<ProFormText name="interviewCompany" label="面试企业" width="md" placeholder="请输入面试企业" />
|
||||
<ProFormDateTimePicker name="interviewTime" label="面试时间" width="md" fieldProps={{ format: 'YYYY-MM-DD HH:mm:ss' }} />
|
||||
<ProFormTextArea name="interviewResult" label="面试结果" placeholder="请输入面试结果或备注" />
|
||||
</ModalForm>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttendeeTab;
|
||||
Reference in New Issue
Block a user