Files
shz-admin/src/pages/Management/List/SeeMatching/resumeView.tsx

287 lines
9.5 KiB
TypeScript
Raw Normal View History

2026-06-24 09:31:10 +08:00
import {Modal, Card, Descriptions, List, Button, Divider, Form, Select, Input, DatePicker, message} from 'antd';
2025-11-10 16:28:01 +08:00
import React, { useEffect, useRef,useState } from 'react';
import { ProDescriptions} from '@ant-design/pro-components';
import {
2026-06-24 09:31:10 +08:00
addCmsUserWorkExperiencesList,
addCmsInterview,
2026-06-24 18:18:42 +08:00
getCmsInterviewList,
2025-11-10 16:28:01 +08:00
} from '@/services/Management/list';
2026-06-24 09:31:10 +08:00
import dayjs from 'dayjs';
const { TextArea } = Input;
const { Option } = Select;
2025-11-10 16:28:01 +08:00
export type ListFormProps = {
onClose: (flag?: boolean, formVals?: unknown) => void;
open: boolean;
values?: Partial<API.MobileUser.ListRow>;
matching?: any;
};
const listEdit: React.FC<ListFormProps> = (props) => {
const mapRef = useRef(null);
const [loading, setLoading] = useState(false);
const [contactInfoList, setContactInfoList] = useState<API.Management.WorkExperence[]>([]);
2026-06-24 09:31:10 +08:00
// 面试邀约相关状态
const [interviewForm] = Form.useForm();
const [interviewMethod, setInterviewMethod] = useState<string>('online');
const [submitting, setSubmitting] = useState<boolean>(false);
2026-06-24 18:18:42 +08:00
const [alreadyInvited, setAlreadyInvited] = useState<boolean>(false);
const [checkingInvitation, setCheckingInvitation] = useState<boolean>(false);
2026-06-24 09:31:10 +08:00
2025-11-10 16:28:01 +08:00
useEffect(() => {
if (props.open && props.values) {
console.log(props.values);
fetchResumeDetail();
2026-06-24 18:18:42 +08:00
checkExistingInvitation();
2026-06-24 09:31:10 +08:00
// 重置面试邀约表单
interviewForm.resetFields();
setInterviewMethod('online');
2025-11-10 16:28:01 +08:00
}
}, [props.values,props.open]);
2026-06-24 18:18:42 +08:00
// 检查是否已存在有效邀约
const checkExistingInvitation = async () => {
if (!props.matching?.jobId || !props.values?.userId) return;
setCheckingInvitation(true);
try {
const res = await getCmsInterviewList({
jobId: Number(props.matching.jobId),
userId: props.values.userId,
});
if (res?.code === 200 && res?.rows?.length > 0) {
// 存在非 rejected 的邀约即为已邀约
const hasActive = res.rows.some(
(item: API.Management.InterviewInvitation) => item.status !== 'rejected'
);
setAlreadyInvited(hasActive);
}
} catch (error) {
console.error('检查邀约状态失败:', error);
} finally {
setCheckingInvitation(false);
}
};
2025-11-10 16:28:01 +08:00
const fetchResumeDetail = async () => {
if (!props.values?.userId) return;
setLoading(true);
try {
2026-06-25 17:57:46 +08:00
const parm={userId: props.values.userId, jobId: props.matching?.jobId};
2025-11-10 16:28:01 +08:00
const response = await addCmsUserWorkExperiencesList(parm);
if (response.code === 200) {
setContactInfoList(response.rows);
}
} catch (error) {
console.error('获取经历详情失败:', error);
} finally {
setLoading(false);
}
};
2026-06-24 09:31:10 +08:00
// 发送面试邀约
const handleInterviewSubmit = async () => {
try {
const values = await interviewForm.validateFields();
setSubmitting(true);
const payload: API.Management.InterviewInvitation = {
companyId: props.matching?.companyId,
jobId: Number(props.matching?.jobId),
userId: props.values?.userId,
applyId: props.values?.applyId ? Number(props.values.applyId) : undefined,
2026-06-24 18:18:42 +08:00
jobName: props.matching?.jobName,
2026-06-24 09:31:10 +08:00
interviewTime: values.interviewTime
? dayjs(values.interviewTime).format('YYYY-MM-DD HH:mm:ss')
: undefined,
interviewMethod: values.interviewMethod,
meetingLink: values.meetingLink,
meetingPassword: values.meetingPassword,
interviewLocation: values.interviewLocation,
contactPhone: values.contactPhone,
2026-06-25 13:32:03 +08:00
interviewerName: values.interviewerName,
2026-06-24 09:31:10 +08:00
status: 'pending',
remark: values.remark,
};
const response = await addCmsInterview(payload);
if (response.code === 200) {
message.success('面试邀约已发送');
2026-06-24 18:18:42 +08:00
setAlreadyInvited(true);
2026-06-24 09:31:10 +08:00
interviewForm.resetFields();
setInterviewMethod('online');
} else {
message.error(response.msg || '发送失败');
}
2026-06-24 18:18:42 +08:00
} catch (error: any) {
const errMsg = error?.response?.data?.msg || error?.message || '发送面试邀约失败';
message.error(errMsg);
2026-06-24 09:31:10 +08:00
console.error('发送面试邀约失败:', error);
} finally {
setSubmitting(false);
}
};
2025-11-10 16:28:01 +08:00
return (
<Modal
open={props.open}
onCancel={() => props.onClose()}
title="简历详情"
width={800}
footer={[
<Button key="cancel" onClick={() => props.onClose()}>
</Button>
]}
>
<ProDescriptions
column={2}
loading={loading}
>
{
<ProDescriptions.Item
dataIndex="contactInfoList"
label=""
span={2}
render={() => {
if (loading) return '加载中...';
if (!Array.isArray(contactInfoList) || contactInfoList.length === 0) {
return <span style={{ color: '#999' }}></span>;
}
return (
<List
itemLayout="vertical"
dataSource={contactInfoList}
renderItem={(item, index) => (
<List.Item
style={{ marginBottom: 16 }}
>
<Card
title={`经历信息 #${index + 1}`}
bordered
style={{ width: '100%', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}
>
<Descriptions column={2} size="small">
<Descriptions.Item label="公司名称">{item.companyName || '未填写'}</Descriptions.Item>
<Descriptions.Item label="职务">{item.position || '未填写'}</Descriptions.Item>
<Descriptions.Item label="开始时间">{item.startDate || '未填写'}</Descriptions.Item>
<Descriptions.Item label="截止时间">{item.endDate || '未填写'}</Descriptions.Item>
<Descriptions.Item label="描述" span={2}>
{item.description || '未填写'}
</Descriptions.Item>
</Descriptions>
</Card>
</List.Item>
)}
/>
);
}}
/>
}
</ProDescriptions>
2026-06-24 09:31:10 +08:00
<Divider />
<Card title="面试邀约" bordered style={{ marginTop: 16 }}>
<Form
form={interviewForm}
layout="vertical"
2026-06-24 18:18:42 +08:00
initialValues={{ interviewMethod: 'online', meetingLink: 'https://mc.repohub.cn/pro.html' }}
2026-06-24 09:31:10 +08:00
>
<Form.Item
name="interviewMethod"
label="面试方式"
rules={[{ required: true, message: '请选择面试方式' }]}
>
<Select
placeholder="请选择面试方式"
onChange={(value) => setInterviewMethod(value)}
>
<Option value="online">线</Option>
<Option value="offline">线</Option>
</Select>
</Form.Item>
<Form.Item
name="interviewTime"
label="面试时间"
rules={[{ required: true, message: '请选择面试时间' }]}
>
<DatePicker
showTime
style={{ width: '100%' }}
placeholder="请选择面试时间"
/>
</Form.Item>
{interviewMethod === 'online' && (
<>
<Form.Item
name="meetingLink"
2026-06-24 18:18:42 +08:00
label="在线会议链接"
2026-06-24 09:31:10 +08:00
>
2026-06-24 18:18:42 +08:00
<Input placeholder="https://mc.repohub.cn/pro.html" disabled />
2026-06-24 09:31:10 +08:00
</Form.Item>
2026-06-24 18:18:42 +08:00
{/* <Form.Item
2026-06-24 09:31:10 +08:00
name="meetingPassword"
label="会议密码"
rules={[{ required: true, message: '请输入会议密码' }]}
>
<Input placeholder="请输入会议密码" />
2026-06-24 18:18:42 +08:00
</Form.Item> */}
2026-06-24 09:31:10 +08:00
</>
)}
{interviewMethod === 'offline' && (
<Form.Item
name="interviewLocation"
label="面试地点"
rules={[{ required: true, message: '请输入面试地点' }]}
>
<Input placeholder="请输入面试地点" />
</Form.Item>
)}
<Form.Item
name="remark"
label="备注"
>
<TextArea rows={3} placeholder="选填,如有其他注意事项可在此说明" />
</Form.Item>
2026-06-25 13:32:03 +08:00
<Form.Item
name="interviewerName"
label="面试官姓名"
rules={[{ required: true, message: '请输入面试官姓名' }]}
>
<Input placeholder="请输入面试官姓名" />
</Form.Item>
2026-06-24 09:31:10 +08:00
<Form.Item
name="contactPhone"
label="面试官联系方式"
rules={[{ required: true, message: '请输入面试官联系方式' }]}
>
<Input placeholder="请输入手机号或微信等联系方式" />
</Form.Item>
<Form.Item>
2026-06-24 18:18:42 +08:00
<Button
type="primary"
loading={submitting || checkingInvitation}
disabled={alreadyInvited}
onClick={handleInterviewSubmit}
>
{alreadyInvited ? '已邀约' : '发送面试邀约'}
2026-06-24 09:31:10 +08:00
</Button>
</Form.Item>
</Form>
</Card>
2025-11-10 16:28:01 +08:00
</Modal>
);
};
export default listEdit;