feat: 🎸 更新了很多信息

This commit is contained in:
2025-08-17 00:49:04 +08:00
parent ee6e2da964
commit ebd51b5ea8
64 changed files with 1714 additions and 8730 deletions

View File

@@ -0,0 +1,173 @@
.course-list-wrapper {
width: 304px;
height: 798px;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 20px 16px;
overflow-y: hidden;
.course-list-title {
width: 100%;
height: 30px;
position: relative;
box-sizing: border-box;
font-size: 20px;
font-weight: 600;
line-height: 20px;
color: #1d2129;
padding-bottom: 10px;
border-bottom: 1px solid #e5e6eb;
margin-bottom: 10px;
&::before {
content: "";
position: absolute;
left: 20px;
bottom: 5px;
width: 31px;
height: 3px;
background-image: url("@/assets/images/Common/title_icon.png");
background-size: 100% 100%;
}
}
.course-list-content {
width: 100%;
height: 700px;
overflow-y: auto;
.course-list {
width: 100%;
/* 自定义折叠面板元素 */
.course-list-item {
width: 272px;
margin-bottom: 10px;
box-sizing: border-box;
padding: 3px 0;
color: #4e5969;
font-size: 14px;
font-weight: 400;
line-height: 21px;
border: none;
.arco-collapse-item-header {
border-radius: 8px;
background-color: #f2f3f5;
}
.arco-timeline-item {
margin-bottom: 10px;
padding-left: 0;
}
.arco-collapse-item-content-expanded {
background-color: #fff;
}
.arco-collapse-item-content-box {
margin-top: 10px;
padding: 0 0 0 5px;
box-sizing: border-box;
}
/* 自定义时间轴元素 */
.time-line-dot-icon {
width: 20px;
height: 20px;
background-image: url("@/assets/images/Common/time_line_dot_icon.png");
background-size: 100% 100%;
}
.time-line-clock-icon {
width: 12px;
height: 12px;
background-image: url("@/assets/images/Common/time_line_clock_icon.png");
background-size: 100% 100%;
}
.time-line-item {
width: 248px;
height: 74px;
background-color: #f2f3f5;
border-radius: 8px;
position: relative;
box-sizing: border-box;
padding: 10px;
> p {
width: 100%;
height: 22px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #1d2129;
}
> .time-line-item-info {
margin-top: 10px;
width: 100%;
height: 20px;
font-size: 12px;
font-weight: 400;
line-height: 20px;
color: #4e5969;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.finish {
&::before {
content: "已结束";
position: absolute;
right: 10px;
top: 10px;
width: 48px;
height: 20px;
line-height: 18px;
text-align: center;
border-radius: 12px;
background-color: #c9cdd4;
color: #86909c;
font-size: 12px;
font-weight: 600;
box-sizing: border-box;
border: 1px solid #c9cdd4;
}
}
.active {
border: 1px solid #0275f2;
&::before {
content: "";
position: absolute;
right: 10px;
top: 10px;
width: 60px;
height: 20px;
background-image: url("@/assets/images/CoursesVideoPlayer/living_icon.png");
background-size: 100% 100%;
}
}
.coming {
&::before {
content: "即将开始";
position: absolute;
right: 10px;
top: 10px;
width: 60px;
height: 20px;
line-height: 18px;
text-align: center;
border-radius: 12px;
background-color: #f2f3f5;
color: #ff7d00;
font-size: 12px;
font-weight: 600;
box-sizing: border-box;
border: 1px solid #ff7d00;
}
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
import { Collapse, Timeline } from "@arco-design/web-react";
import "./index.css";
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
const CourseList = ({ className = "" }) => {
return (
<div className={`${className} course-list-wrapper`}>
<p className="course-list-title">课程列表</p>
<div className="course-list-content">
<Collapse
lazyload
className="course-list"
bordered={false}
expandIconPosition="right"
>
<CollapseItem
header="课程单元1"
name="1"
className="course-list-item"
>
<Timeline>
<TimelineItem
dot={<div className="time-line-dot-icon" />}
lineType="dashed"
>
<div className="time-line-item finish">
<p>终生学习系统</p>
<div className="time-line-item-info">
<span>张老师</span>
<span>2023-01-01</span>
</div>
</div>
</TimelineItem>
<TimelineItem
dot={<div className="time-line-clock-icon" />}
lineType="dashed"
>
<div className="time-line-item active">
<p>终生学习系统</p>
<div className="time-line-item-info">
<span>张老师</span>
<span>2023-01-01</span>
</div>
</div>
</TimelineItem>
</Timeline>
</CollapseItem>
</Collapse>
</div>
</div>
);
};
export default CourseList;

View File

@@ -3,13 +3,10 @@
height: 798px;
position: relative;
.lock {
position: absolute;
top: 110px;
left: 50%;
transform: translateX(-50%);
width: 433px;
height: 384px;
.video-lock-wrapper {
width: 100%;
height: 100%;
position: relative;
}
.courses-video-player {
@@ -189,6 +186,7 @@
.teacher-introduce {
width: 100%;
height: 84px;
overflow: hidden;
}
.courses-video-player-teacher-introduce {
width: 100%;

View File

@@ -1,17 +1,18 @@
import { useState } from "react";
import { Avatar } from "@arco-design/web-react";
import LOCKICON from "@/assets/images/Common/lock_bg.png";
import Locked from "@/components/Locked";
import "./index.css";
export default ({ className = "" }) => {
const [isLock, setIsLock] = useState(false);
export default ({ className = "", isLock = false }) => {
return (
<div className={`${className} courses-video-player-wrapper`}>
{/* 直播板块 */}
<div className="courses-video-player">
{isLock ? (
<img className="lock" src={LOCKICON} alt="lock" />
<Locked
className="video-lock-wrapper"
text="该板块将于「垂直能力提升」阶段启动后开放届时,请留意教务系统通知,您可在该板块进行线上
1V1 求职策略定制"
/>
) : (
<>
<div className="courses-video-player-header">
@@ -27,7 +28,6 @@ export default ({ className = "" }) => {
</>
)}
</div>
{/* 直播信息板块 */}
<div className="courses-video-player-info">
{/* 直播观众信息 */}
<div className="courses-video-player-audience-info">

View File

@@ -19,7 +19,10 @@ const navigation = {
{ name: "📺 课程直播间", path: "/live" },
{ name: "🌳 就业管家知识树", path: "/career-tree" },
{ name: "📝 课后作业", path: "/homework" },
{ name: "🎯 1V1定制求职策略", path: "/job-strategy" },
{
name: "🎯 1V1定制求职策略",
path: ["/job-strategy", "/job-strategy-detail"],
},
{ name: "🎭 线下面试模拟", path: "/interview-simulation" },
],
},

View File

@@ -1,7 +1,12 @@
import { useNavigate } from "react-router-dom";
import "./index.css";
const LiveSummary = ({ className = "", showBtn = false, isLiving = true }) => {
const handleClickBtn = () => {};
const navigate = useNavigate();
const handleClickBtn = () => {
navigate("/job-strategy-detail");
};
return (
<div className={`${className} live-summary-wrapper`}>

View File

@@ -0,0 +1,32 @@
.lock-wrapper {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: rgba(255, 255, 255, 0.9);
.lock {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
height: 320px;
background-image: url("@/assets/images/Common/lock_bg.png");
background-size: 100% 100%;
> span {
position: absolute;
bottom: -50px;
left: 50%;
transform: translateX(-50%);
width: 433px;
height: 44px;
text-align: center;
color: #1d2129;
font-size: 14px;
font-weight: 600;
}
}
}

View File

@@ -0,0 +1,9 @@
import "./index.css";
export default ({ text = "", className = "" }) => {
return (
<div className={`lock-wrapper ${className}`}>
<div className="lock">{text ? <span>{text}</span> : null}</div>
</div>
);
};

View File

@@ -1,383 +0,0 @@
/* 简历编辑弹窗样式 */
.resume-edit-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
padding: 20px;
}
.resume-edit-modal-content {
background: #ffffff;
border-radius: 12px;
max-width: 900px;
width: 100%;
max-height: 90vh;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
animation: modalFadeIn 0.3s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* 弹窗头部 */
.resume-edit-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
border-bottom: 1px solid #f0f0f0;
background: #fafbff;
}
.resume-edit-modal-title {
font-size: 20px;
font-weight: 600;
color: #111111;
margin: 0;
}
.resume-edit-modal-actions {
display: flex;
align-items: center;
gap: 12px;
}
.btn-edit {
background: #1e40af;
color: #ffffff;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease;
}
.btn-edit:hover {
background: #1e3a8a;
}
.resume-edit-modal-close {
background: none;
border: none;
font-size: 28px;
font-weight: 300;
color: #666666;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: background-color 150ms ease;
}
.resume-edit-modal-close:hover {
background: #f0f0f0;
color: #333333;
}
/* 弹窗主体 */
.resume-edit-modal-body {
padding: 32px;
max-height: calc(90vh - 160px);
overflow-y: auto;
}
/* 简历区域 */
.resume-edit-section {
margin-bottom: 32px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 24px;
}
.resume-edit-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.resume-edit-section-title {
font-size: 18px;
font-weight: 600;
color: #111111;
margin: 0 0 20px 0;
display: flex;
align-items: center;
gap: 8px;
}
.resume-edit-section-title::before {
content: '';
width: 4px;
height: 20px;
background: #1e40af;
border-radius: 2px;
}
/* 表单样式 */
.form-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.form-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-item-full {
grid-column: 1 / -1;
}
.form-label {
font-size: 15px;
font-weight: 600;
color: #555555;
min-width: 80px;
}
.form-input {
padding: 12px 16px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 15px;
font-weight: 400;
color: #333333;
background: #ffffff;
transition: border-color 150ms ease;
}
.form-input:focus {
outline: none;
border-color: #1e40af;
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
}
.form-value {
font-size: 15px;
font-weight: 400;
color: #333333;
padding: 12px 0;
line-height: 1.5;
}
/* 工作经历特殊样式 */
.experience-item {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
}
.responsibilities-section {
margin-top: 16px;
}
.responsibilities-list {
margin: 8px 0 0 0;
padding-left: 20px;
list-style-type: disc;
}
.responsibility-item {
font-size: 15px;
font-weight: 400;
color: #333333;
line-height: 1.6;
margin-bottom: 8px;
}
/* 技能特长样式 */
.skills-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.skills-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.skill-item {
background: #e0edff;
color: #1e40af;
font-size: 13px;
font-weight: 500;
padding: 6px 12px;
border-radius: 6px;
border: 1px solid #b8daff;
display: flex;
align-items: center;
gap: 6px;
transition: all 150ms ease;
}
.skill-text {
white-space: nowrap;
}
.skill-remove {
background: none;
border: none;
color: #1e40af;
font-size: 16px;
font-weight: 600;
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 150ms ease;
}
.skill-remove:hover {
background: #1e40af;
color: #ffffff;
}
.skill-add-form {
display: flex;
gap: 8px;
align-items: center;
}
.skill-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
font-weight: 400;
color: #333333;
transition: border-color 150ms ease;
}
.skill-input:focus {
outline: none;
border-color: #1e40af;
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
}
.skill-add-btn {
background: #10b981;
color: #ffffff;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease;
white-space: nowrap;
}
.skill-add-btn:hover {
background: #059669;
}
/* 弹窗底部 */
.resume-edit-modal-footer {
padding: 20px 32px;
border-top: 1px solid #f0f0f0;
background: #fafbff;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.btn-secondary {
background: #ffffff;
color: #666666;
border: 1px solid #e0e0e0;
padding: 10px 20px;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 150ms ease;
}
.btn-secondary:hover {
background: #f8f9fa;
color: #333333;
border-color: #d0d0d0;
}
.btn-primary {
background: #1e40af;
color: #ffffff;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease;
}
.btn-primary:hover {
background: #1e3a8a;
}
.btn-primary:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* 响应式设计 */
@media (max-width: 768px) {
.resume-edit-modal-overlay {
padding: 10px;
}
.resume-edit-modal-content {
max-height: 95vh;
}
.resume-edit-modal-header {
padding: 16px 20px;
}
.resume-edit-modal-body {
padding: 20px;
}
.form-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.skill-add-form {
flex-direction: column;
align-items: stretch;
}
}

View File

@@ -1,455 +0,0 @@
import React, { useState, useEffect } from "react";
import "./index.css";
const ResumeEditModal = ({
resume,
isOpen,
onClose,
onSave,
isEditMode = false,
}) => {
const [editData, setEditData] = useState({
personalInfo: {
name: "",
phone: "",
email: "",
location: "",
},
education: {
university: "",
major: "",
degree: "",
graduationYear: "",
},
experience: {
company: "",
position: "",
duration: "",
responsibilities: [],
},
skills: [],
});
const [isEditing, setIsEditing] = useState(isEditMode);
const [currentSkill, setCurrentSkill] = useState("");
useEffect(() => {
if (resume && isOpen) {
setEditData({
personalInfo: {
name: resume.personalInfo?.name || "",
phone: resume.personalInfo?.phone || "",
email: resume.personalInfo?.email || "",
location: resume.personalInfo?.location || "",
},
education: {
university: resume.education?.university || "",
major: resume.education?.major || "",
degree: resume.education?.degree || "",
graduationYear: resume.education?.graduationYear || "",
},
experience: {
company: resume.company || "",
position: resume.name || "",
duration: resume.experience || "",
responsibilities: [
"负责核心业务开发与维护",
"参与系统架构设计",
"协助团队制定技术规范",
],
},
skills: resume.skills || [],
});
}
}, [resume, isOpen]);
if (!isOpen || !resume) return null;
const handleInputChange = (section, field, value) => {
setEditData((prev) => ({
...prev,
[section]: {
...prev[section],
[field]: value,
},
}));
};
const handleAddSkill = () => {
if (currentSkill.trim() && !editData.skills.includes(currentSkill.trim())) {
setEditData((prev) => ({
...prev,
skills: [...prev.skills, currentSkill.trim()],
}));
setCurrentSkill("");
}
};
const handleRemoveSkill = (skillToRemove) => {
setEditData((prev) => ({
...prev,
skills: prev.skills.filter((skill) => skill !== skillToRemove),
}));
};
const handleSave = () => {
onSave({
...resume,
personalInfo: editData.personalInfo,
education: editData.education,
experience: editData.experience,
skills: editData.skills,
});
setIsEditing(false);
};
const handleOverlayClick = (e) => {
if (e.target === e.currentTarget) {
onClose();
}
};
return (
<div className="resume-edit-modal-overlay" onClick={handleOverlayClick}>
<div className="resume-edit-modal-content">
<div className="resume-edit-modal-header">
<h3 className="resume-edit-modal-title">
{isEditing ? "编辑简历" : "简历详情"}
</h3>
<div className="resume-edit-modal-actions">
{!isEditing && (
<button className="btn-edit" onClick={() => setIsEditing(true)}>
编辑
</button>
)}
<button className="resume-edit-modal-close" onClick={onClose}>
×
</button>
</div>
</div>
<div className="resume-edit-modal-body">
{/* 个人信息 */}
<div className="resume-edit-section">
<h4 className="resume-edit-section-title">个人信息</h4>
<div className="resume-edit-content">
<div className="form-grid">
<div className="form-item">
<label className="form-label">姓名</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.personalInfo.name}
onChange={(e) =>
handleInputChange(
"personalInfo",
"name",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.personalInfo.name}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">电话</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.personalInfo.phone}
onChange={(e) =>
handleInputChange(
"personalInfo",
"phone",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.personalInfo.phone}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">邮箱</label>
{isEditing ? (
<input
type="email"
className="form-input"
value={editData.personalInfo.email}
onChange={(e) =>
handleInputChange(
"personalInfo",
"email",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.personalInfo.email}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">地址</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.personalInfo.location}
onChange={(e) =>
handleInputChange(
"personalInfo",
"location",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.personalInfo.location}
</span>
)}
</div>
</div>
</div>
</div>
{/* 教育背景 */}
<div className="resume-edit-section">
<h4 className="resume-edit-section-title">教育背景</h4>
<div className="resume-edit-content">
<div className="form-grid">
<div className="form-item">
<label className="form-label">院校</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.education.university}
onChange={(e) =>
handleInputChange(
"education",
"university",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.education.university}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">专业</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.education.major}
onChange={(e) =>
handleInputChange("education", "major", e.target.value)
}
/>
) : (
<span className="form-value">
{editData.education.major}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">学历</label>
{isEditing ? (
<select
className="form-input"
value={editData.education.degree}
onChange={(e) =>
handleInputChange("education", "degree", e.target.value)
}
>
<option value="">请选择学历</option>
<option value="专科">专科</option>
<option value="本科">本科</option>
<option value="硕士">硕士</option>
<option value="博士">博士</option>
</select>
) : (
<span className="form-value">
{editData.education.degree}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">毕业年份</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.education.graduationYear}
onChange={(e) =>
handleInputChange(
"education",
"graduationYear",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.education.graduationYear}
</span>
)}
</div>
</div>
</div>
</div>
{/* 工作经历 */}
<div className="resume-edit-section">
<h4 className="resume-edit-section-title">工作经历</h4>
<div className="resume-edit-content">
<div className="experience-item">
<div className="form-grid">
<div className="form-item">
<label className="form-label">公司</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.experience.company}
onChange={(e) =>
handleInputChange(
"experience",
"company",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.experience.company}
</span>
)}
</div>
<div className="form-item">
<label className="form-label">职位</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.experience.position}
onChange={(e) =>
handleInputChange(
"experience",
"position",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.experience.position}
</span>
)}
</div>
<div className="form-item form-item-full">
<label className="form-label">工作时间</label>
{isEditing ? (
<input
type="text"
className="form-input"
value={editData.experience.duration}
onChange={(e) =>
handleInputChange(
"experience",
"duration",
e.target.value
)
}
/>
) : (
<span className="form-value">
{editData.experience.duration}
</span>
)}
</div>
</div>
<div className="responsibilities-section">
<label className="form-label">工作职责</label>
<ul className="responsibilities-list">
{editData.experience.responsibilities.map((resp, index) => (
<li key={index} className="responsibility-item">
{resp}
</li>
))}
</ul>
</div>
</div>
</div>
</div>
{/* 技能特长 */}
<div className="resume-edit-section">
<h4 className="resume-edit-section-title">技能特长</h4>
<div className="resume-edit-content">
<div className="skills-container">
<div className="skills-list">
{editData.skills.map((skill, index) => (
<div key={index} className="skill-item">
<span className="skill-text">{skill}</span>
{isEditing && (
<button
className="skill-remove"
onClick={() => handleRemoveSkill(skill)}
>
×
</button>
)}
</div>
))}
</div>
{isEditing && (
<div className="skill-add-form">
<input
type="text"
className="skill-input"
value={currentSkill}
onChange={(e) => setCurrentSkill(e.target.value)}
placeholder="添加技能"
onKeyPress={(e) => e.key === "Enter" && handleAddSkill()}
/>
<button className="skill-add-btn" onClick={handleAddSkill}>
添加
</button>
</div>
)}
</div>
</div>
</div>
</div>
<div className="resume-edit-modal-footer">
<div className="modal-actions">
<button className="btn-secondary" onClick={onClose}>
取消
</button>
{isEditing && (
<button className="btn-primary" onClick={handleSave}>
保存
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default ResumeEditModal;

View File

@@ -1,632 +0,0 @@
import React, { useState } from "react";
import Portal from "@/components/common/Portal";
const CourseEvaluationModal = ({ isVisible, onClose, onSubmit }) => {
// 评价状态
const [ratings, setRatings] = useState({
discipline: 0, // 课堂纪律
teaching: 0, // 教学水平
effectiveness: 0, // 课堂实效
overall: 0, // 综合评价
});
const [comment, setComment] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
// 管理body的modal-open类
React.useEffect(() => {
if (isVisible) {
document.body.classList.add("modal-open");
} else {
document.body.classList.remove("modal-open");
}
// 清理函数
return () => {
document.body.classList.remove("modal-open");
};
}, [isVisible]);
// 评价维度配置
const ratingDimensions = [
{ key: "discipline", label: "课堂纪律" },
{ key: "teaching", label: "教学水平" },
{ key: "effectiveness", label: "课堂实效" },
{ key: "overall", label: "课程收获" },
];
// 处理星级评分
const handleStarRating = (dimension, rating) => {
setRatings((prev) => ({
...prev,
[dimension]: rating,
}));
};
// 渲染星级评分
const renderStarRating = (dimension, currentRating) => {
return (
<div className="star-rating">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
className={`star ${star <= currentRating ? "filled" : ""}`}
onClick={() => handleStarRating(dimension, star)}
onMouseEnter={(e) => {
// 悬停效果
const stars =
e.currentTarget.parentElement.querySelectorAll(".star");
stars.forEach((s, index) => {
s.classList.toggle("hover", index < star);
});
}}
onMouseLeave={(e) => {
// 清除悬停效果
const stars =
e.currentTarget.parentElement.querySelectorAll(".star");
stars.forEach((s) => s.classList.remove("hover"));
}}
>
</button>
))}
</div>
);
};
// 渲染评分条(模拟导师评分显示)
const renderRatingBars = () => {
const ratings = [
{ stars: 5, percentage: 88.1 },
{ stars: 4, percentage: 5.8 },
{ stars: 3, percentage: 3.6 },
{ stars: 2, percentage: 1.2 },
{ stars: 1, percentage: 0.2 },
];
return (
<div className="rating-bars">
{ratings.map((item) => (
<div key={item.stars} className="rating-bar-row">
<div className="stars-label">
{Array.from({ length: 5 }, (_, i) => (
<span
key={i}
className={`bar-star ${i < item.stars ? "filled" : ""}`}
>
</span>
))}
</div>
<div className="rating-bar">
<div
className="rating-fill"
style={{ width: `${item.percentage}%` }}
></div>
</div>
<div className="percentage">{item.percentage}%</div>
</div>
))}
</div>
);
};
// 处理提交
const handleSubmit = async () => {
setIsSubmitting(true);
// 模拟提交延迟
setTimeout(() => {
setIsSubmitting(false);
if (onSubmit) {
onSubmit({ ratings, comment });
}
onClose();
}, 1000);
};
// 处理关闭
const handleClose = () => {
onClose();
};
if (!isVisible) {
return null;
}
return (
<Portal className="course-evaluation-portal">
<div className="course-evaluation-overlay">
<div className="course-evaluation-modal">
{/* 弹窗头部 */}
<div className="modal-header">
<div className="header-content">
<div className="book-icon">📖</div>
<div className="header-text">
<h2 className="modal-title">请对本节课进行评价</h2>
<p className="modal-subtitle">请您客观公正的评价</p>
</div>
</div>
</div>
{/* 弹窗内容 */}
<div className="modal-content">
{/* 老师信息和评分展示 */}
<div className="teacher-evaluation-section">
<div className="teacher-info">
<div className="teacher-avatar">
<img
src="/api/placeholder/80/80"
alt="老师头像"
onError={(e) => {
e.target.style.display = "none";
e.target.nextSibling.style.display = "flex";
}}
/>
<div
className="avatar-placeholder"
style={{ display: "none" }}
>
</div>
</div>
<div className="teacher-details">
<h3 className="teacher-name">顾华</h3>
<p className="course-name">机械与智能制造班</p>
<div className="teacher-rating">
<span className="rating-score">9.5</span>
<span className="rating-text">863位学员评价</span>
</div>
</div>
</div>
{/* 评分条形图 */}
<div className="rating-visualization">{renderRatingBars()}</div>
</div>
{/* 评价维度 */}
<div className="evaluation-dimensions">
<div className="dimensions-grid">
{ratingDimensions.map((dimension) => (
<div key={dimension.key} className="dimension-item">
<div className="dimension-label">{dimension.label}</div>
{renderStarRating(dimension.key, ratings[dimension.key])}
<div className="rating-text">
({ratings[dimension.key]}/5)
</div>
</div>
))}
</div>
</div>
{/* 详细评价 */}
<div className="detailed-evaluation">
<div className="evaluation-label">课程优化建议选填</div>
<textarea
className="evaluation-textarea"
placeholder="请分享您对本次课程的改进建议(选填)..."
value={comment}
onChange={(e) => setComment(e.target.value)}
maxLength={500}
/>
<div className="char-count">{comment.length}/500</div>
</div>
</div>
{/* 弹窗底部按钮 */}
<div className="modal-footer">
<button
className="cancel-button"
onClick={handleClose}
disabled={isSubmitting}
>
取消
</button>
<button
className="submit-button"
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? "提交中..." : "提交评价"}
</button>
</div>
</div>
</div>
{/* 样式定义 */}
<style>{`
.course-evaluation-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 20px;
backdrop-filter: blur(4px);
}
.course-evaluation-modal {
background: white;
border-radius: 16px;
width: 100%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
animation: modalEnter 0.3s ease;
}
@keyframes modalEnter {
from {
opacity: 0;
transform: scale(0.9) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-header {
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
padding: 24px;
border-radius: 16px 16px 0 0;
}
.header-content {
display: flex;
align-items: center;
gap: 16px;
}
.book-icon {
width: 48px;
height: 48px;
background: #2196f3;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
}
.header-text {
flex: 1;
}
.modal-title {
margin: 0 0 4px 0;
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
}
.modal-subtitle {
margin: 0;
font-size: 14px;
color: #666;
}
.modal-content {
padding: 24px;
}
.teacher-evaluation-section {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
border: 1px solid #e9ecef;
}
.teacher-info {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.teacher-avatar {
position: relative;
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
background: #ddd;
}
.teacher-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
font-weight: 600;
}
.teacher-details {
flex: 1;
}
.teacher-name {
margin: 0 0 4px 0;
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
}
.course-name {
margin: 0 0 8px 0;
font-size: 16px;
color: #666;
}
.teacher-rating {
display: flex;
align-items: baseline;
gap: 8px;
}
.rating-score {
font-size: 32px;
font-weight: 700;
color: #2196f3;
}
.rating-text {
font-size: 14px;
color: #666;
}
.rating-visualization {
margin-top: 16px;
}
.rating-bars {
display: flex;
flex-direction: column;
gap: 8px;
}
.rating-bar-row {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
}
.stars-label {
width: 60px;
display: flex;
gap: 2px;
}
.bar-star {
color: #ddd;
font-size: 10px;
}
.bar-star.filled {
color: #ffc107;
}
.rating-bar {
flex: 1;
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.rating-fill {
height: 100%;
background: linear-gradient(90deg, #ffc107 0%, #ff9800 100%);
transition: width 0.3s ease;
}
.percentage {
width: 40px;
text-align: right;
color: #666;
font-size: 12px;
}
.evaluation-dimensions {
margin-bottom: 24px;
}
.dimensions-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.dimension-item {
background: #f8f9fa;
padding: 16px;
border-radius: 12px;
border: 1px solid #e9ecef;
text-align: center;
}
.dimension-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 12px;
}
.star-rating {
display: flex;
justify-content: center;
gap: 4px;
margin-bottom: 8px;
}
.star {
background: none;
border: none;
font-size: 24px;
color: #ddd;
cursor: pointer;
transition: all 0.2s ease;
padding: 4px;
}
.star.filled,
.star.hover {
color: #ffc107;
transform: scale(1.1);
}
.star:hover {
transform: scale(1.2);
}
.rating-text {
font-size: 12px;
color: #666;
}
.detailed-evaluation {
margin-bottom: 24px;
}
.evaluation-label {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 12px;
}
.evaluation-textarea {
width: 100%;
min-height: 120px;
padding: 16px;
border: 1px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
line-height: 1.5;
resize: vertical;
font-family: inherit;
box-sizing: border-box;
}
.evaluation-textarea:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}
.char-count {
text-align: right;
font-size: 12px;
color: #666;
margin-top: 8px;
}
.modal-footer {
padding: 24px;
border-top: 1px solid #e9ecef;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.cancel-button,
.submit-button {
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.cancel-button {
background: #f8f9fa;
color: #666;
border: 1px solid #e9ecef;
}
.cancel-button:hover:not(:disabled) {
background: #e9ecef;
}
.submit-button {
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white;
min-width: 120px;
}
.submit-button:hover:not(:disabled) {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);
}
.submit-button:disabled,
.cancel-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 响应式设计 */
@media (max-width: 768px) {
.course-evaluation-overlay {
padding: 12px;
}
.modal-header {
padding: 20px;
}
.modal-content {
padding: 20px;
}
.dimensions-grid {
grid-template-columns: 1fr;
}
.teacher-info {
flex-direction: column;
text-align: center;
}
.teacher-avatar {
width: 60px;
height: 60px;
}
.teacher-name {
font-size: 20px;
}
.rating-score {
font-size: 28px;
}
}
`}</style>
</Portal>
);
};
export default CourseEvaluationModal;

View File

@@ -1,690 +0,0 @@
import React, { useState, useRef, useEffect } from "react";
import CourseEvaluationModal from "./CourseEvaluationModal.jsx";
const VideoPlayer = ({
title,
courseStatus,
startTime,
endTime,
onReplayRequest,
onFullscreenChange,
onTimeUpdate,
}) => {
const [isPlaying, setIsPlaying] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
const [showControls, setShowControls] = useState(true);
const [volume, setVolume] = useState(1);
const [isMuted, setIsMuted] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [showEvaluationModal, setShowEvaluationModal] = useState(false);
const [hasVideoEnded, setHasVideoEnded] = useState(false);
const videoRef = useRef(null);
const containerRef = useRef(null);
const controlsTimeoutRef = useRef(null);
// 格式化课程时间显示
const formatCourseTime = (timeString) => {
try {
const date = new Date(timeString);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const courseDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate()
);
const diffDays = Math.floor((courseDate - today) / (1000 * 60 * 60 * 24));
if (diffDays === 0) {
return `今天 ${date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
})}`;
} else if (diffDays === 1) {
return `明天 ${date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
})}`;
} else if (diffDays === -1) {
return `昨天 ${date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
})}`;
} else {
return date.toLocaleString("zh-CN", {
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}
} catch (error) {
return timeString;
}
};
// 处理回放请求
const handleReplayRequest = () => {
if (onReplayRequest) {
onReplayRequest();
}
};
// 处理播放/暂停
const handlePlayPause = () => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
}
};
// 处理静音
const handleMute = () => {
if (videoRef.current) {
const newMutedState = !isMuted;
videoRef.current.muted = newMutedState;
setIsMuted(newMutedState);
}
};
// 处理音量调节
const handleVolumeChange = (e) => {
const newVolume = parseFloat(e.target.value);
if (videoRef.current) {
videoRef.current.volume = newVolume;
setVolume(newVolume);
setIsMuted(newVolume === 0);
}
};
// 处理全屏
const handleFullscreen = () => {
if (!isFullscreen) {
if (containerRef.current.requestFullscreen) {
containerRef.current.requestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
};
// 处理鼠标移动显示控制栏
const handleMouseMove = () => {
setShowControls(true);
// 清除之前的定时器
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
// 3秒后隐藏控制栏仅在播放时
if (isPlaying) {
controlsTimeoutRef.current = setTimeout(() => {
setShowControls(false);
}, 3000);
}
};
// 处理课程评价弹窗关闭
const handleEvaluationClose = () => {
setShowEvaluationModal(false);
};
// 处理课程评价提交
const handleEvaluationSubmit = (evaluationData) => {
setShowEvaluationModal(false);
// 这里可以调用API提交评价数据
};
// 视频事件处理
const handleVideoEvents = {
onPlay: () => {
setIsPlaying(true);
setIsLoading(false);
setHasError(false);
setHasVideoEnded(false);
},
onPause: () => {
setIsPlaying(false);
},
onLoadStart: () => {
setIsLoading(true);
setHasError(false);
},
onCanPlay: () => {
setIsLoading(false);
},
onError: (e) => {
setIsLoading(false);
setHasError(true);
},
onVolumeChange: () => {
if (videoRef.current) {
setVolume(videoRef.current.volume);
setIsMuted(videoRef.current.muted);
}
},
onTimeUpdate: () => {
if (videoRef.current && onTimeUpdate) {
const currentTime = videoRef.current.currentTime;
const duration = videoRef.current.duration;
onTimeUpdate({
currentTime,
duration,
progress: duration > 0 ? currentTime / duration : 0,
});
}
},
onEnded: () => {
setIsPlaying(false);
setHasVideoEnded(true);
// 延迟一点显示弹窗,让用户看到视频结束
setTimeout(() => {
setShowEvaluationModal(true);
}, 500);
},
};
// 监听全屏状态变化
useEffect(() => {
const handleFullscreenChange = () => {
const fullscreen = !!document.fullscreenElement;
setIsFullscreen(fullscreen);
if (onFullscreenChange) {
onFullscreenChange(fullscreen);
}
};
document.addEventListener("fullscreenchange", handleFullscreenChange);
return () => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
};
}, [onFullscreenChange]);
// 清理定时器
useEffect(() => {
return () => {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
};
}, []);
// 统一使用固定的视频源
const videoSrc = "/live.mp4";
// 根据课程状态决定是否显示视频播放器
const shouldShowVideo = courseStatus === "live" || courseStatus === "replay";
// 添加容器尺寸监控日志
useEffect(() => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
// Container size monitoring
}
}, [shouldShowVideo, courseStatus]);
// 添加视频元素尺寸监控日志
useEffect(() => {
if (videoRef.current && shouldShowVideo) {
const video = videoRef.current;
const handleLoadedMetadata = () => {};
video.addEventListener("loadedmetadata", handleLoadedMetadata);
return () => {
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
};
}
}, [shouldShowVideo]);
return (
<div
ref={containerRef}
className="video-player-container unified-container"
onMouseMove={shouldShowVideo ? handleMouseMove : undefined}
onMouseLeave={() =>
shouldShowVideo && isPlaying && setShowControls(false)
}
>
{/* 根据状态渲染不同内容 */}
{shouldShowVideo ? (
<>
{/* 视频元素 */}
<video
ref={videoRef}
className="video-player"
src={videoSrc}
poster="/api/placeholder/800/450"
autoPlay={courseStatus === "live"}
muted={false}
{...handleVideoEvents}
/>
{/* 加载状态 */}
{isLoading && (
<div className="video-overlay loading-overlay">
<div className="loading-spinner"></div>
<div>加载中...</div>
</div>
)}
{/* 错误状态 */}
{hasError && (
<div className="video-overlay error-overlay">
<div className="error-icon"></div>
<div>视频加载失败</div>
<button
className="retry-button"
onClick={() => {
setHasError(false);
setIsLoading(true);
if (videoRef.current) {
videoRef.current.load();
}
}}
>
重试
</button>
</div>
)}
{/* 播放按钮覆盖层 */}
{!isPlaying && !isLoading && !hasError && (
<div className="video-overlay play-overlay">
<button className="play-button-large" onClick={handlePlayPause}>
</button>
</div>
)}
{/* 控制栏 */}
<div className={`video-controls ${showControls ? "visible" : ""}`}>
{/* 播放/暂停按钮 */}
<button
className="control-button"
onClick={handlePlayPause}
title={isPlaying ? "暂停" : "播放"}
>
{isPlaying ? "⏸" : "▶"}
</button>
{/* 音量控制 */}
<div className="volume-control">
<button
className="control-button"
onClick={handleMute}
title={isMuted ? "取消静音" : "静音"}
>
{isMuted || volume === 0 ? "🔇" : volume < 0.5 ? "🔉" : "🔊"}
</button>
<input
type="range"
min="0"
max="1"
step="0.1"
value={volume}
onChange={handleVolumeChange}
className="volume-slider"
title="音量"
/>
</div>
{/* 直播状态指示器 */}
{courseStatus === "live" && (
<div className="live-indicator">
<span className="live-dot"></span>
直播中
</div>
)}
{/* 回放状态指示器 */}
{courseStatus === "replay" && (
<div className="replay-indicator">
<span className="replay-icon">📺</span>
课程回放中
</div>
)}
{/* 右侧控制 */}
<div className="controls-right">
{/* 测试评价弹窗按钮(仅开发测试用) */}
<button
className="control-button test-evaluation-button"
onClick={() => {
setShowEvaluationModal(true);
}}
title="测试课程评价(开发测试功能)"
style={{
background: "rgba(255, 193, 7, 0.8)",
borderRadius: "4px",
}}
>
📝
</button>
{/* 全屏按钮 */}
<button
className="control-button"
onClick={handleFullscreen}
title={isFullscreen ? "退出全屏" : "全屏"}
>
{isFullscreen ? "⛶" : "⛶"}
</button>
</div>
</div>
</>
) : (
/* 非播放状态的提示界面 */
<div className="course-status-overlay">
{courseStatus === "upcoming" && (
<div className="upcoming-content">
<div className="status-icon"></div>
<h3 className="status-title">此课程即将开始</h3>
<p className="status-message">
开始时间{startTime ? formatCourseTime(startTime) : "待定"}
</p>
<div className="status-description">
请耐心等待课程开始届时将自动开启直播
</div>
</div>
)}
{courseStatus === "completed" && (
<div className="completed-content">
<div className="status-icon"></div>
<h3 className="status-title">此课程已结束</h3>
<p className="status-message">
结束时间{endTime ? formatCourseTime(endTime) : ""}
</p>
<button className="replay-button" onClick={handleReplayRequest}>
查看课程回放
</button>
</div>
)}
</div>
)}
{/* 课程评价弹窗 */}
<CourseEvaluationModal
isVisible={showEvaluationModal}
onClose={handleEvaluationClose}
onSubmit={handleEvaluationSubmit}
courseInfo={{
title: title,
teacher: "顾华",
course: "机械与智能制造班",
}}
/>
{/* 样式定义 */}
<style>{`
.video-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
color: white;
font-size: 16px;
z-index: 5;
}
.loading-overlay {
background: rgba(0, 0, 0, 0.7);
padding: 24px;
border-radius: 8px;
}
.error-overlay {
background: rgba(0, 0, 0, 0.8);
padding: 24px;
border-radius: 8px;
text-align: center;
}
.error-icon {
font-size: 48px;
margin-bottom: 8px;
}
.retry-button {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 8px;
}
.play-overlay {
background: rgba(0, 0, 0, 0.3);
width: 100%;
height: 100%;
top: 0;
left: 0;
transform: none;
justify-content: center;
}
.play-button-large {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
border: none;
font-size: 32px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.play-button-large:hover {
background: white;
transform: scale(1.1);
}
.video-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
padding: 20px 16px 16px;
display: flex;
align-items: center;
gap: 12px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 10;
}
.video-controls.visible {
opacity: 1;
}
.control-button {
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
padding: 8px;
border-radius: 4px;
transition: background-color 0.15s ease;
}
.control-button:hover {
background: rgba(255, 255, 255, 0.2);
}
.volume-control {
display: flex;
align-items: center;
gap: 8px;
}
.volume-slider {
width: 80px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.volume-slider::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: white;
cursor: pointer;
}
.live-indicator {
display: flex;
align-items: center;
gap: 6px;
color: #ef4444;
font-size: 14px;
font-weight: 500;
}
.live-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ef4444;
animation: pulse 2s infinite;
}
.replay-indicator {
display: flex;
align-items: center;
gap: 6px;
color: #6b7280;
font-size: 14px;
font-weight: 500;
}
.replay-icon {
font-size: 18px;
}
.controls-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
}
.video-player-container {
width: 100%;
height: 100%;
}
.unified-container {
aspect-ratio: 16/9;
min-height: 400px;
width: 100%;
position: relative;
background: #000;
}
.video-player {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.course-status-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.6));
display: flex;
align-items: center;
justify-content: center;
color: white;
z-index: 5;
}
.upcoming-content, .completed-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
text-align: center;
padding: 40px 20px;
max-width: 600px;
margin: 0 auto;
}
.status-icon {
font-size: 64px;
margin-bottom: 8px;
opacity: 0.9;
}
.status-title {
font-size: 28px;
font-weight: 600;
color: white;
margin: 0;
line-height: 1.2;
}
.status-message {
font-size: 18px;
color: #e5e7eb;
margin: 0;
line-height: 1.4;
}
.status-description {
font-size: 16px;
color: #d1d5db;
margin: 0;
line-height: 1.4;
}
.replay-button {
background: #3b82f6;
color: white;
border: none;
padding: 12px 32px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 200ms ease;
margin-top: 8px;
}
.replay-button:hover {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
`}</style>
</div>
);
};
export default VideoPlayer;