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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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