feat: 改为卡片下方直接展开动画效果

- 移除弹窗式展示,改为在面试状态卡片下方直接展开
- 创建新的InterviewStatusAnimation组件,只展示动画
- 点击面试状态按钮后在卡片下方展开动画区域
- 移除标题和描述文字,界面更简洁
- 支持点击同一按钮收起动画

🤖 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:34:24 +08:00
parent 481999f963
commit 01492feb5a
4 changed files with 157 additions and 69 deletions

View File

@@ -0,0 +1,33 @@
.interview-status-animation-wrapper {
width: 100%;
padding: 10px 0;
background: linear-gradient(135deg, #f7faff 0%, #ffffff 100%);
border-top: 1px solid #e5e6eb;
animation: slideDown 0.3s ease-out;
overflow: hidden;
}
@keyframes slideDown {
from {
max-height: 0;
opacity: 0;
}
to {
max-height: 200px;
opacity: 1;
}
}
.animation-container {
width: 100%;
height: 180px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.animation-container svg {
max-width: 100%;
max-height: 100%;
}

View File

@@ -0,0 +1,68 @@
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 ({ statusText, isOpen }) => {
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-animation-wrapper">
<div className="animation-container" ref={animationContainer} />
</div>
);
};

View File

@@ -136,12 +136,18 @@
align-items: center;
flex-direction: column;
.interview-item-wrapper {
width: 100%;
margin-bottom: 10px;
transition: all 0.3s ease;
}
.company-jobs-page-interview-item {
flex-shrink: 0;
width: 100%;
border-radius: 8px;
border: 1px solid #e5e6eb;
margin-bottom: 20px;
margin-bottom: 0;
box-sizing: border-box;
padding: 20px;
list-style: none;

View File

@@ -6,7 +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 InterviewStatusAnimation from "./components/InterviewStatusAnimation";
import {
getCompanyJobsPageData,
getJobsList,
@@ -26,9 +26,7 @@ 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 [expandedItemId, setExpandedItemId] = useState(null);
const navigate = useNavigate();
// 初始化页面数据 - 使用聚合接口
@@ -127,25 +125,12 @@ const CompanyJobsPage = () => {
// 处理面试状态点击
const handleStatusClick = (e, item) => {
e.stopPropagation();
const rect = e.currentTarget.getBoundingClientRect();
// 计算下拉栏位置,让它在按钮下方居中
const dropdownWidth = 360;
const buttonCenter = rect.left + rect.width / 2;
const left = Math.max(10, buttonCenter - dropdownWidth / 2);
setDropdownPosition({
top: rect.bottom + 5,
left: left
});
setSelectedInterview(item);
setDropdownOpen(true);
};
// 关闭下拉栏
const handleCloseDropdown = () => {
setDropdownOpen(false);
setSelectedInterview(null);
// 如果点击的是已展开的项,则收起;否则展开新项
if (expandedItemId === item.id) {
setExpandedItemId(null);
} else {
setExpandedItemId(item.id);
}
};
// 获取企业内推岗位 - 用于分页加载更多
@@ -259,10 +244,8 @@ const CompanyJobsPage = () => {
className="company-jobs-page-interview-list"
>
{interviews.map((item) => (
<li
className="company-jobs-page-interview-item"
key={item.id}
>
<div key={item.id} className="interview-item-wrapper">
<li className="company-jobs-page-interview-item">
<div className="company-jobs-page-interview-item-info">
<p className="company-jobs-page-interview-item-info-position">
{item.position}
@@ -297,6 +280,11 @@ const CompanyJobsPage = () => {
</div>
</div>
</li>
<InterviewStatusAnimation
statusText={item.statusText}
isOpen={expandedItemId === item.id}
/>
</div>
))}
</InfiniteScroll>
</div>
@@ -304,14 +292,7 @@ const CompanyJobsPage = () => {
</>
)}
</div>
{/* 面试状态下拉栏 */}
<InterviewStatusDropdown
status={selectedInterview?.status}
statusText={selectedInterview?.statusText}
isOpen={dropdownOpen}
onClose={handleCloseDropdown}
position={dropdownPosition}
/>
</div>
);
};