简历推荐功能开发
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:
@@ -2,7 +2,7 @@ import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { FormattedMessage, useAccess, useParams } from '@umijs/max';
|
||||
import { Button, FormInstance, message, Tag } from 'antd';
|
||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { FormOutlined, PlusOutlined,AlignLeftOutlined } from '@ant-design/icons';
|
||||
import { FormOutlined, PlusOutlined, AlignLeftOutlined, SendOutlined } from '@ant-design/icons';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import DictTag from '@/components/DictTag';
|
||||
import { exportCmsAppUserExport } from '@/services/mobileusers/list';
|
||||
@@ -15,6 +15,7 @@ import similarityJobs from '@/utils/similarity_Job';
|
||||
import Detail from './detail';
|
||||
import Hire from './hire';
|
||||
import ResumeView from './resumeView';
|
||||
import InterviewInvite from '../ResumeRecommend/InterviewInvite';
|
||||
|
||||
|
||||
const handleExport = async (values: API.MobileUser.ListParams) => {
|
||||
@@ -51,6 +52,7 @@ function ManagementList() {
|
||||
const [jobInfo, setJobInfo] = useState({});
|
||||
const [matchingDegree, setMatchingDegree] = useState<any>(null);
|
||||
const [resumeVisible, setResumeVisible] = useState<boolean>(false);
|
||||
const [inviteVisible, setInviteVisible] = useState<boolean>(false);
|
||||
const [hireAdd, setHireAdd] = useState<any>(null);
|
||||
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||
const params = useParams();
|
||||
@@ -191,7 +193,7 @@ function ManagementList() {
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
dataIndex: 'jobId',
|
||||
width: 200,
|
||||
width: 280,
|
||||
render: (jobId, record) => [
|
||||
<div key="first-row" style={{marginBottom: 8, display: 'flex', justifyContent: 'center'}}>
|
||||
<Button
|
||||
@@ -210,7 +212,20 @@ function ManagementList() {
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="edit"
|
||||
key="invite-interview"
|
||||
icon={<SendOutlined/>}
|
||||
hidden={!access.hasPerms('cms:interview:add')}
|
||||
onClick={() => {
|
||||
setCurrentRow(record);
|
||||
setInviteVisible(true);
|
||||
}}
|
||||
>
|
||||
邀请面试
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="hire"
|
||||
icon={<FormOutlined/>}
|
||||
hidden={!access.hasPerms('cms:employeeConfirm:add')}
|
||||
onClick={() => {
|
||||
@@ -224,9 +239,8 @@ function ManagementList() {
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="edit"
|
||||
key="match-detail"
|
||||
icon={<AlignLeftOutlined/>}
|
||||
// hidden={!access.hasPerms('area:business:List.update')}
|
||||
onClick={() => {
|
||||
setModalVisible(true);
|
||||
setCurrentRow(record);
|
||||
@@ -323,6 +337,21 @@ function ManagementList() {
|
||||
setCurrentRow(undefined);
|
||||
}}
|
||||
/>
|
||||
<InterviewInvite
|
||||
open={inviteVisible}
|
||||
userId={currentRow?.userId}
|
||||
userName={currentRow?.name}
|
||||
jobId={jobId}
|
||||
companyId={(jobInfo as any)?.companyId}
|
||||
jobName={(jobInfo as any)?.jobTitle}
|
||||
onClose={(needRefresh) => {
|
||||
setInviteVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
if (needRefresh) {
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Modal, Card, Descriptions, List, Button, Divider, Form, Select, Input, DatePicker, message} from 'antd';
|
||||
import React, { useEffect, useRef,useState } from 'react';
|
||||
import { ProDescriptions} from '@ant-design/pro-components';
|
||||
import { Modal, Card, Descriptions, List, Button, Form, Select, Input, DatePicker, message, Empty, Spin } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
addCmsUserWorkExperiencesList,
|
||||
addCmsInterview,
|
||||
getCmsInterviewList,
|
||||
getAppSkillList,
|
||||
} from '@/services/Management/list';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -18,11 +18,11 @@ export type ListFormProps = {
|
||||
matching?: any;
|
||||
};
|
||||
|
||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
const ResumeViewModal: React.FC<ListFormProps> = (props) => {
|
||||
|
||||
const mapRef = useRef(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [contactInfoList, setContactInfoList] = useState<API.Management.WorkExperence[]>([]);
|
||||
const [experienceList, setExperienceList] = useState<any[]>([]);
|
||||
const [skillList, setSkillList] = useState<any[]>([]);
|
||||
|
||||
// 面试邀约相关状态
|
||||
const [interviewForm] = Form.useForm();
|
||||
@@ -31,16 +31,41 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
const [alreadyInvited, setAlreadyInvited] = useState<boolean>(false);
|
||||
const [checkingInvitation, setCheckingInvitation] = useState<boolean>(false);
|
||||
|
||||
// 性别映射
|
||||
const sexMap: Record<string, string> = {
|
||||
'0': '男',
|
||||
'1': '女',
|
||||
};
|
||||
|
||||
// 学历映射
|
||||
const educationMap: Record<string, string> = {
|
||||
'1': '初中及以下',
|
||||
'2': '高中',
|
||||
'3': '中专',
|
||||
'4': '大专',
|
||||
'5': '本科',
|
||||
'6': '硕士',
|
||||
'7': '博士',
|
||||
};
|
||||
|
||||
// 政治面貌映射
|
||||
const politicalMap: Record<string, string> = {
|
||||
'1': '中共党员',
|
||||
'2': '中共预备党员',
|
||||
'3': '共青团员',
|
||||
'4': '群众',
|
||||
'5': '民主党派',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.open && props.values) {
|
||||
console.log(props.values);
|
||||
fetchResumeDetail();
|
||||
fetchSkills();
|
||||
checkExistingInvitation();
|
||||
// 重置面试邀约表单
|
||||
interviewForm.resetFields();
|
||||
setInterviewMethod('online');
|
||||
}
|
||||
}, [props.values,props.open]);
|
||||
}, [props.values, props.open]);
|
||||
|
||||
// 检查是否已存在有效邀约
|
||||
const checkExistingInvitation = async () => {
|
||||
@@ -52,7 +77,6 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
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'
|
||||
);
|
||||
@@ -67,13 +91,12 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
|
||||
const fetchResumeDetail = async () => {
|
||||
if (!props.values?.userId) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const parm={userId: props.values.userId, jobId: props.matching?.jobId};
|
||||
const parm = { userId: props.values.userId, jobId: props.matching?.jobId };
|
||||
const response = await addCmsUserWorkExperiencesList(parm);
|
||||
if (response.code === 200) {
|
||||
setContactInfoList(response.rows);
|
||||
setExperienceList(response.rows);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取经历详情失败:', error);
|
||||
@@ -82,6 +105,18 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSkills = async () => {
|
||||
if (!props.values?.userId) return;
|
||||
try {
|
||||
const response = await getAppSkillList({ userId: props.values.userId });
|
||||
if (response.code === 200) {
|
||||
setSkillList(response.rows || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取技能信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 发送面试邀约
|
||||
const handleInterviewSubmit = async () => {
|
||||
try {
|
||||
@@ -125,162 +160,200 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const values = props.values;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={props.open}
|
||||
onCancel={() => props.onClose()}
|
||||
title="简历详情"
|
||||
width={800}
|
||||
width={900}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => props.onClose()}>
|
||||
取消
|
||||
</Button>
|
||||
</Button>,
|
||||
]}
|
||||
destroyOnClose
|
||||
>
|
||||
<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 (
|
||||
{!values ? (
|
||||
<Empty description="暂无数据" />
|
||||
) : (
|
||||
<>
|
||||
{/* 基本信息 */}
|
||||
<Card title="基本信息" bordered style={{ marginBottom: 16 }}>
|
||||
<Descriptions column={3} size="small" bordered>
|
||||
<Descriptions.Item label="姓名">{values.name || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="性别">
|
||||
{sexMap[values.sex || ''] || values.sex || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="出生日期">{values.birthDate || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="手机号码">{values.phone || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="学历">
|
||||
{educationMap[values.education || ''] || values.education || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="政治面貌">
|
||||
{politicalMap[values.politicalAffiliation || ''] || values.politicalAffiliation || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="期望薪资">
|
||||
{values.salaryMin && values.salaryMax
|
||||
? `${values.salaryMin}-${values.salaryMax}`
|
||||
: '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="身份证号">{values.idCard || '-'}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
{/* 技能列表 */}
|
||||
{skillList.length > 0 && (
|
||||
<Card title="技能信息" bordered style={{ marginBottom: 16 }}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={skillList}
|
||||
renderItem={(skill: any) => (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
title={skill.name || '未命名技能'}
|
||||
description={`等级: ${skill.levels || '未填写'}`}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 工作经历 */}
|
||||
<Card title="工作经历" bordered style={{ marginBottom: 16 }}>
|
||||
<Spin spinning={loading}>
|
||||
{!experienceList || experienceList.length === 0 ? (
|
||||
<Empty description="暂无工作经历" />
|
||||
) : (
|
||||
<List
|
||||
itemLayout="vertical"
|
||||
dataSource={contactInfoList}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
dataSource={experienceList}
|
||||
renderItem={(item: any, index: number) => (
|
||||
<List.Item style={{ marginBottom: 12 }}>
|
||||
<Card
|
||||
title={`经历信息 #${index + 1}`}
|
||||
title={`经历 #${index + 1}`}
|
||||
bordered
|
||||
style={{ width: '100%', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}
|
||||
size="small"
|
||||
style={{ width: '100%', boxShadow: '0 1px 4px rgba(0,0,0,0.08)' }}
|
||||
>
|
||||
<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 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>
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
<Card title="面试邀约" bordered style={{ marginTop: 16 }}>
|
||||
<Form
|
||||
form={interviewForm}
|
||||
layout="vertical"
|
||||
initialValues={{ interviewMethod: 'online', meetingLink: 'https://mc.repohub.cn/pro.html' }}
|
||||
>
|
||||
<Form.Item
|
||||
name="interviewMethod"
|
||||
label="面试方式"
|
||||
rules={[{ required: true, message: '请选择面试方式' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择面试方式"
|
||||
onChange={(value) => setInterviewMethod(value)}
|
||||
{/* 面试邀约 */}
|
||||
<Card title="面试邀约" bordered>
|
||||
<Form
|
||||
form={interviewForm}
|
||||
layout="vertical"
|
||||
initialValues={{ interviewMethod: 'online', meetingLink: 'https://mc.repohub.cn/pro.html' }}
|
||||
>
|
||||
<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"
|
||||
label="在线会议链接"
|
||||
name="interviewMethod"
|
||||
label="面试方式"
|
||||
rules={[{ required: true, message: '请选择面试方式' }]}
|
||||
>
|
||||
<Input placeholder="https://mc.repohub.cn/pro.html" disabled />
|
||||
<Select
|
||||
placeholder="请选择面试方式"
|
||||
onChange={(value) => setInterviewMethod(value)}
|
||||
>
|
||||
<Option value="online">线上</Option>
|
||||
<Option value="offline">线下</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
name="meetingPassword"
|
||||
label="会议密码"
|
||||
rules={[{ required: true, message: '请输入会议密码' }]}
|
||||
|
||||
<Form.Item
|
||||
name="interviewTime"
|
||||
label="面试时间"
|
||||
rules={[{ required: true, message: '请选择面试时间' }]}
|
||||
>
|
||||
<Input placeholder="请输入会议密码" />
|
||||
</Form.Item> */}
|
||||
</>
|
||||
)}
|
||||
<DatePicker
|
||||
showTime
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请选择面试时间"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{interviewMethod === 'offline' && (
|
||||
<Form.Item
|
||||
name="interviewLocation"
|
||||
label="面试地点"
|
||||
rules={[{ required: true, message: '请输入面试地点' }]}
|
||||
>
|
||||
<Input placeholder="请输入面试地点" />
|
||||
</Form.Item>
|
||||
)}
|
||||
{interviewMethod === 'online' && (
|
||||
<Form.Item
|
||||
name="meetingLink"
|
||||
label="在线会议链接"
|
||||
>
|
||||
<Input placeholder="请输入在线会议链接" />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
name="remark"
|
||||
label="备注"
|
||||
>
|
||||
<TextArea rows={3} placeholder="选填,如有其他注意事项可在此说明" />
|
||||
</Form.Item>
|
||||
{interviewMethod === 'offline' && (
|
||||
<Form.Item
|
||||
name="interviewLocation"
|
||||
label="面试地点"
|
||||
rules={[{ required: true, message: '请输入面试地点' }]}
|
||||
>
|
||||
<Input placeholder="请输入面试地点" />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
name="interviewerName"
|
||||
label="面试官姓名"
|
||||
rules={[{ required: true, message: '请输入面试官姓名' }]}
|
||||
>
|
||||
<Input placeholder="请输入面试官姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="remark"
|
||||
label="备注"
|
||||
>
|
||||
<TextArea rows={3} placeholder="选填,如有其他注意事项可在此说明" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="contactPhone"
|
||||
label="面试官联系方式"
|
||||
rules={[{ required: true, message: '请输入面试官联系方式' }]}
|
||||
>
|
||||
<Input placeholder="请输入手机号或微信等联系方式" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="interviewerName"
|
||||
label="面试官姓名"
|
||||
rules={[{ required: true, message: '请输入面试官姓名' }]}
|
||||
>
|
||||
<Input placeholder="请输入面试官姓名" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={submitting || checkingInvitation}
|
||||
disabled={alreadyInvited}
|
||||
onClick={handleInterviewSubmit}
|
||||
>
|
||||
{alreadyInvited ? '已邀约' : '发送面试邀约'}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
<Form.Item
|
||||
name="contactPhone"
|
||||
label="面试官联系方式"
|
||||
rules={[{ required: true, message: '请输入面试官联系方式' }]}
|
||||
>
|
||||
<Input placeholder="请输入手机号或微信等联系方式" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={submitting || checkingInvitation}
|
||||
disabled={alreadyInvited}
|
||||
onClick={handleInterviewSubmit}
|
||||
>
|
||||
{alreadyInvited ? '已邀约' : '发送面试邀约'}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default listEdit;
|
||||
export default ResumeViewModal;
|
||||
|
||||
Reference in New Issue
Block a user