新增面试邀约功能。
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:31:10 +08:00
parent 3b1877f302
commit ebfb4fa580
7 changed files with 396 additions and 3 deletions

View File

@@ -0,0 +1,179 @@
import React, { useState, useEffect } from 'react';
import {
Card,
Row,
Col,
Tag,
Space,
Typography,
Button,
Empty,
message,
Spin
} from 'antd';
import {
EnvironmentOutlined,
ArrowLeftOutlined,
ClockCircleOutlined,
VideoCameraOutlined
} from '@ant-design/icons';
import { history } from '@umijs/max';
import JobPortalHeader from '@/components/JobPortalHeader';
import { getCmsInterviewList } from '@/services/Management/list';
const { Title, Text } = Typography;
// 面试状态映射
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待确认', color: 'blue' },
accepted: { text: '已接受', color: 'green' },
rejected: { text: '已拒绝', color: 'red' },
completed: { text: '已完成', color: 'purple' },
};
// 从缓存获取用户信息
const getUserIdFromCache = (): number | null => {
try {
const cached = localStorage.getItem('userInfo');
if (cached) {
const userInfo = JSON.parse(cached);
return userInfo?.userId || null;
}
} catch (error) {
console.error('读取缓存用户信息失败:', error);
}
return null;
};
const InterviewsPage: React.FC = () => {
const [interviews, setInterviews] = useState<API.Management.InterviewInvitation[]>([]);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
fetchInterviews();
}, []);
const fetchInterviews = async () => {
const userId = getUserIdFromCache();
if (!userId) {
message.warning('请先登录');
history.push('/job-portal/personal-center');
return;
}
try {
setLoading(true);
const response = await getCmsInterviewList({ userId });
if (response.code === 200) {
setInterviews(response.rows || []);
} else {
message.error(response.msg || '获取面试邀约列表失败');
}
} catch (error) {
console.error('获取面试邀约列表失败:', error);
message.error('获取面试邀约列表失败');
} finally {
setLoading(false);
}
};
const handleBack = () => {
history.push('/job-portal/personal-center');
};
return (
<div className="interviews-page">
<JobPortalHeader showSearch={false} showHotJobs={false} />
<Spin spinning={loading}>
<div className="page-content">
<div className="back-button">
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={handleBack}
>
</Button>
</div>
<div className="page-header">
<Title level={2}></Title>
<Text type="secondary"> {interviews.length} </Text>
</div>
{interviews.length === 0 && !loading ? (
<Empty
description="暂无面试邀约"
style={{ marginTop: '100px' }}
/>
) : (
<div className="job-list">
{interviews.map((item, index) => {
const st = statusMap[item.status || 'pending'] || statusMap.pending;
return (
<Card
key={item.id || index}
className="job-card"
hoverable
>
<Row gutter={16}>
<Col span={18}>
<div className="job-info">
<Title level={4} style={{ marginBottom: 8 }}>
<ClockCircleOutlined /> {item.interviewTime || '未指定'}
</Title>
<Space style={{ marginBottom: 8 }}>
<Tag color={item.interviewMethod === 'online' ? 'blue' : 'orange'}>
{item.interviewMethod === 'online' ? '线上' : '线下'}
</Tag>
<Tag color={st.color}>{st.text}</Tag>
</Space>
<div style={{ marginTop: 8 }}>
{item.interviewMethod === 'online' ? (
<Space direction="vertical" size={4}>
<Text>
<VideoCameraOutlined />
</Text>
{item.meetingLink && (
<Text type="secondary">
{item.meetingLink}
</Text>
)}
{item.meetingPassword && (
<Text type="secondary">
{item.meetingPassword}
</Text>
)}
</Space>
) : (
<Text>
<EnvironmentOutlined /> {item.interviewLocation || '未指定地点'}
</Text>
)}
</div>
{item.contactPhone && (
<div style={{ marginTop: 8 }}>
<Text type="secondary">{item.contactPhone}</Text>
</div>
)}
{item.remark && (
<div style={{ marginTop: 8 }}>
<Text type="secondary">{item.remark}</Text>
</div>
)}
</div>
</Col>
</Row>
</Card>
);
})}
</div>
)}
</div>
</Spin>
</div>
);
};
export default InterviewsPage;

View File

@@ -17,7 +17,8 @@ import {
SafetyCertificateOutlined,
BellOutlined,
FileTextOutlined,
CameraOutlined
CameraOutlined,
CalendarOutlined
} from '@ant-design/icons';
import { history } from '@umijs/max';
import JobPortalHeader from '@/components/JobPortalHeader';
@@ -179,6 +180,15 @@ const PersonalCenter: React.FC = () => {
color: '#722ed1',
link: '/job-portal/personal-center/showcase'
},
{
id: 6,
title: '我的面试邀约',
icon: <CalendarOutlined />,
status: '',
hasArrow: true,
color: '#1677ff',
link: '/job-portal/personal-center/interviews'
},
{
id: 2,
title: '在线面试',