feat: 为岗位面试状态添加可点击下拉栏功能

- 创建InterviewStatusDropdown组件,显示面试状态动画
- 集成Lottie动画播放器,加载对应状态的动画文件
- 点击面试状态按钮后弹出下拉栏,展示动画和状态说明
- 添加11种不同面试状态的动画映射和描述文字
- 下拉栏支持点击遮罩层关闭

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-08 05:24:00 +08:00
parent 14c33d0ffd
commit 321da26eb2
14 changed files with 236 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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] || '状态更新中,请稍后查看。';
};

View File

@@ -6,6 +6,7 @@ import { Spin, Empty } from "@arco-design/web-react";
import InfiniteScroll from "@/components/InfiniteScroll";
import toast from "@/components/Toast";
import JobList from "./components/JobList";
import InterviewStatusDropdown from "./components/InterviewStatusDropdown";
import {
getCompanyJobsPageData,
getJobsList,
@@ -25,6 +26,9 @@ const CompanyJobsPage = () => {
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
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();
// 初始化页面数据 - 使用聚合接口
@@ -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 () => {
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
@@ -262,6 +284,8 @@ const CompanyJobsPage = () => {
item.status !== "COMPLETED" &&
"company-jobs-page-interview-item-btn-active"
}`}
onClick={(e) => handleStatusClick(e, item)}
style={{ cursor: 'pointer' }}
>
{item.statusText}
</div>
@@ -274,6 +298,14 @@ const CompanyJobsPage = () => {
</>
)}
</div>
{/* 面试状态下拉栏 */}
<InterviewStatusDropdown
status={selectedInterview?.status}
statusText={selectedInterview?.statusText}
isOpen={dropdownOpen}
onClose={handleCloseDropdown}
position={dropdownPosition}
/>
</div>
);
};