feat: 为岗位面试状态添加可点击下拉栏功能
- 创建InterviewStatusDropdown组件,显示面试状态动画 - 集成Lottie动画播放器,加载对应状态的动画文件 - 点击面试状态按钮后弹出下拉栏,展示动画和状态说明 - 添加11种不同面试状态的动画映射和描述文字 - 下拉栏支持点击遮罩层关闭 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
src/assets/animations/interviewStatus/1-off_初筛未通过.json
Normal file
1
src/assets/animations/interviewStatus/1-off_初筛未通过.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/1-on_hr评估中.json
Normal file
1
src/assets/animations/interviewStatus/1-on_hr评估中.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/2-off_面试未通过.json
Normal file
1
src/assets/animations/interviewStatus/2-off_面试未通过.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/2-on_到场面试.json
Normal file
1
src/assets/animations/interviewStatus/2-on_到场面试.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/2-on_收到通知.json
Normal file
1
src/assets/animations/interviewStatus/2-on_收到通知.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/2-wati_等待面试.json
Normal file
1
src/assets/animations/interviewStatus/2-wati_等待面试.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/3-off_未参与面试.json
Normal file
1
src/assets/animations/interviewStatus/3-off_未参与面试.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/3-wati_等待HR通知.json
Normal file
1
src/assets/animations/interviewStatus/3-wati_等待HR通知.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/4-off_拒绝Offer.json
Normal file
1
src/assets/animations/interviewStatus/4-off_拒绝Offer.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/4-on_收到Offer.json
Normal file
1
src/assets/animations/interviewStatus/4-on_收到Offer.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/animations/interviewStatus/4-wati_等待回复.json
Normal file
1
src/assets/animations/interviewStatus/4-wati_等待回复.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,94 @@
|
|||||||
|
.interview-status-dropdown-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||||
|
width: 360px;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #e5e6eb;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown-header .close-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown-header .close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interview-status-dropdown-content {
|
||||||
|
padding: 20px;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-description {
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #4e5969;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import lottie from 'lottie-web';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
// 状态动画映射
|
||||||
|
const statusAnimationMap = {
|
||||||
|
'HR初筛未通过': () => import('@/assets/animations/interviewStatus/1-off_初筛未通过.json'),
|
||||||
|
'HR评估中': () => import('@/assets/animations/interviewStatus/1-on_hr评估中.json'),
|
||||||
|
'面试未通过': () => import('@/assets/animations/interviewStatus/2-off_面试未通过.json'),
|
||||||
|
'到场面试': () => import('@/assets/animations/interviewStatus/2-on_到场面试.json'),
|
||||||
|
'收到通知': () => import('@/assets/animations/interviewStatus/2-on_收到通知.json'),
|
||||||
|
'等待面试': () => import('@/assets/animations/interviewStatus/2-wati_等待面试.json'),
|
||||||
|
'未参与面试': () => import('@/assets/animations/interviewStatus/3-off_未参与面试.json'),
|
||||||
|
'等待HR通知': () => import('@/assets/animations/interviewStatus/3-wati_等待HR通知.json'),
|
||||||
|
'拒绝Offer': () => import('@/assets/animations/interviewStatus/4-off_拒绝Offer.json'),
|
||||||
|
'收到Offer': () => import('@/assets/animations/interviewStatus/4-on_收到Offer.json'),
|
||||||
|
'等待回复': () => import('@/assets/animations/interviewStatus/4-wati_等待回复.json'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ status, statusText, isOpen, onClose, position }) => {
|
||||||
|
const animationContainer = useRef(null);
|
||||||
|
const lottieInstance = useRef(null);
|
||||||
|
const [animationData, setAnimationData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && statusText) {
|
||||||
|
// 加载对应的动画数据
|
||||||
|
const loadAnimation = statusAnimationMap[statusText];
|
||||||
|
if (loadAnimation) {
|
||||||
|
loadAnimation().then(module => {
|
||||||
|
setAnimationData(module.default);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isOpen, statusText]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (animationData && animationContainer.current && isOpen) {
|
||||||
|
// 清除之前的动画实例
|
||||||
|
if (lottieInstance.current) {
|
||||||
|
lottieInstance.current.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的动画实例
|
||||||
|
lottieInstance.current = lottie.loadAnimation({
|
||||||
|
container: animationContainer.current,
|
||||||
|
renderer: 'svg',
|
||||||
|
loop: true,
|
||||||
|
autoplay: true,
|
||||||
|
animationData: animationData
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (lottieInstance.current) {
|
||||||
|
lottieInstance.current.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [animationData, isOpen]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="interview-status-dropdown-overlay" onClick={onClose} />
|
||||||
|
<div className="interview-status-dropdown" style={{ top: position?.top, left: position?.left }}>
|
||||||
|
<div className="interview-status-dropdown-header">
|
||||||
|
<h3>{statusText}</h3>
|
||||||
|
<button className="close-btn" onClick={onClose}>×</button>
|
||||||
|
</div>
|
||||||
|
<div className="interview-status-dropdown-content">
|
||||||
|
<div className="animation-container" ref={animationContainer} />
|
||||||
|
<div className="status-description">
|
||||||
|
{getStatusDescription(statusText)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态描述
|
||||||
|
const getStatusDescription = (statusText) => {
|
||||||
|
const descriptions = {
|
||||||
|
'HR初筛未通过': '您的简历未通过HR初步筛选,建议优化简历内容后重新投递其他岗位。',
|
||||||
|
'HR评估中': 'HR正在评估您的简历,请耐心等待结果通知。',
|
||||||
|
'面试未通过': '很遗憾,您未通过本次面试。建议总结经验,继续努力!',
|
||||||
|
'到场面试': '请准时到达面试地点,祝您面试顺利!',
|
||||||
|
'收到通知': '恭喜!您已收到面试通知,请查看具体时间和地点。',
|
||||||
|
'等待面试': '面试安排中,请保持手机畅通,等待具体通知。',
|
||||||
|
'未参与面试': '您未能参加预定的面试,如需重新安排请联系HR。',
|
||||||
|
'等待HR通知': '面试已完成,HR正在评估结果,请耐心等待。',
|
||||||
|
'拒绝Offer': '您已拒绝该职位的Offer,祝您找到更合适的机会。',
|
||||||
|
'收到Offer': '恭喜!您已收到正式Offer,请尽快确认是否接受。',
|
||||||
|
'等待回复': '企业正在等待您对Offer的回复,请尽快做出决定。',
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptions[statusText] || '状态更新中,请稍后查看。';
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { Spin, Empty } from "@arco-design/web-react";
|
|||||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||||
import toast from "@/components/Toast";
|
import toast from "@/components/Toast";
|
||||||
import JobList from "./components/JobList";
|
import JobList from "./components/JobList";
|
||||||
|
import InterviewStatusDropdown from "./components/InterviewStatusDropdown";
|
||||||
import {
|
import {
|
||||||
getCompanyJobsPageData,
|
getCompanyJobsPageData,
|
||||||
getJobsList,
|
getJobsList,
|
||||||
@@ -25,6 +26,9 @@ const CompanyJobsPage = () => {
|
|||||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
const [selectedInterview, setSelectedInterview] = useState(null);
|
||||||
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// 初始化页面数据 - 使用聚合接口
|
// 初始化页面数据 - 使用聚合接口
|
||||||
@@ -120,6 +124,24 @@ const CompanyJobsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理面试状态点击
|
||||||
|
const handleStatusClick = (e, item) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
setDropdownPosition({
|
||||||
|
top: rect.bottom + 5,
|
||||||
|
left: rect.left
|
||||||
|
});
|
||||||
|
setSelectedInterview(item);
|
||||||
|
setDropdownOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭下拉栏
|
||||||
|
const handleCloseDropdown = () => {
|
||||||
|
setDropdownOpen(false);
|
||||||
|
setSelectedInterview(null);
|
||||||
|
};
|
||||||
|
|
||||||
// 获取企业内推岗位 - 用于分页加载更多
|
// 获取企业内推岗位 - 用于分页加载更多
|
||||||
const fetchJobsList = async () => {
|
const fetchJobsList = async () => {
|
||||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||||
@@ -262,6 +284,8 @@ const CompanyJobsPage = () => {
|
|||||||
item.status !== "COMPLETED" &&
|
item.status !== "COMPLETED" &&
|
||||||
"company-jobs-page-interview-item-btn-active"
|
"company-jobs-page-interview-item-btn-active"
|
||||||
}`}
|
}`}
|
||||||
|
onClick={(e) => handleStatusClick(e, item)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
{item.statusText}
|
{item.statusText}
|
||||||
</div>
|
</div>
|
||||||
@@ -274,6 +298,14 @@ const CompanyJobsPage = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* 面试状态下拉栏 */}
|
||||||
|
<InterviewStatusDropdown
|
||||||
|
status={selectedInterview?.status}
|
||||||
|
statusText={selectedInterview?.statusText}
|
||||||
|
isOpen={dropdownOpen}
|
||||||
|
onClose={handleCloseDropdown}
|
||||||
|
position={dropdownPosition}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user