feat: 添加AI课程集成和修复初始化错误

- 将终生学习系统课添加到公共课直播间
- 修复allCalendarEvents初始化顺序问题
- 更正AI课程导师为李奇
- 添加AI课程与日历页面同步功能
This commit is contained in:
KQL
2025-09-07 23:09:48 +08:00
parent 6337cc4812
commit 27f2339c9e
81 changed files with 41799 additions and 1654 deletions

View File

@@ -19,9 +19,10 @@ const Rank = ({ className, data, loading }) => {
return (
<div className={`module-class-rank ${className}`}>
<p className="module-class-rank-title">
<p className="module-class-rank-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }}>
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '24px', height: '24px', marginRight: '8px', flexShrink: 0 }} />
<IconFont className="title-icon" src="recuUY5nNf7DWT" />
<span>班级排名</span>
<span style={{ fontWeight: 'bold' }}>班级排名</span>
</p>
{loading ? (
<Spin size={40} className="module-class-rank-spin" />

View File

@@ -40,6 +40,36 @@
.course-list {
width: 100%;
/* 分割线样式 */
.course-divider {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
position: relative;
.divider-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, #e5e6eb 20%, #e5e6eb 80%, transparent);
border-style: dashed;
border-width: 1px 0 0 0;
border-color: #e5e6eb;
}
.divider-text {
padding: 0 16px;
font-size: 14px;
font-weight: 500;
color: #86909c;
background-color: #fff;
position: relative;
z-index: 1;
}
}
/* 自定义折叠面板元素 */
.course-list-item {
width: 272px;
@@ -51,6 +81,14 @@
font-weight: 400;
line-height: 21px;
border: none;
&.has-preview-unit {
.arco-collapse-item-header {
background: linear-gradient(135deg, #f0f7ff 0%, #e8f3ff 100%);
border: 1px solid #b3d4ff;
box-shadow: 0 2px 8px rgba(64, 128, 255, 0.1);
}
}
.arco-collapse-item-header {
border-radius: 8px;
@@ -275,6 +313,40 @@
border: 1px solid #ff7d00;
}
}
.preview-badge {
position: absolute;
left: 50%;
top: 60%;
transform: translate(-50%, -50%);
padding: 4px 12px;
background: #4080ff;
color: #fff;
font-size: 12px;
font-weight: 600;
border-radius: 12px;
z-index: 10;
box-shadow: 0 3px 8px rgba(64, 128, 255, 0.3);
}
@keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 3px 8px rgba(255, 107, 107, 0.3);
}
50% {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.5);
}
100% {
transform: scale(1);
box-shadow: 0 3px 8px rgba(255, 107, 107, 0.3);
}
}
.has-preview {
/* 可试看标签已调整到中心位置,不需要额外样式 */
}
}
}
}

View File

@@ -7,7 +7,8 @@ const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
const CourseList = ({ className = "", onCourseClick }) => {
const [courseLiveList, setCourseLiveList] = useState([]);
const [compoundCourseList, setCompoundCourseList] = useState([]);
const [verticalCourseList, setVerticalCourseList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
@@ -20,14 +21,19 @@ const CourseList = ({ className = "", onCourseClick }) => {
try {
const res = await getCourseLiveList();
if (res.success) {
const courseList = res.data || [];
setCourseLiveList(courseList);
const compoundList = res.data.compound || [];
const verticalList = res.data.vertical || [];
setCompoundCourseList(compoundList);
setVerticalCourseList(verticalList);
// 合并两个列表用于查找默认选中的课程
const allCourses = [...compoundList, ...verticalList];
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of courseList) {
for (const unit of allCourses) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
@@ -45,7 +51,7 @@ const CourseList = ({ className = "", onCourseClick }) => {
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of courseList) {
for (const unit of allCourses) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
@@ -123,9 +129,10 @@ const CourseList = ({ className = "", onCourseClick }) => {
className="course-list"
bordered={false}
expandIconPosition="right"
defaultActiveKey={["1"]}
defaultActiveKey={[]}
>
{courseLiveList.map((unit, index) => (
{/* 复合能力课部分 */}
{compoundCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
@@ -167,6 +174,97 @@ const CourseList = ({ className = "", onCourseClick }) => {
</Timeline>
</CollapseItem>
))}
{/* 分割线 */}
{compoundCourseList.length > 0 && verticalCourseList.length > 0 && (
<div className="course-divider">
<span className="divider-line"></span>
<span className="divider-text">垂直能力课</span>
<span className="divider-line"></span>
</div>
)}
{/* 垂直能力课部分 */}
{verticalCourseList.map((unit, index) => {
// 检查单元是否包含可试看课程
const hasPreviewCourse = unit.courses.some(course => course.canPreview);
return (
<CollapseItem
key={unit.unitId}
header={
<div style={{
display: 'flex',
alignItems: 'center',
width: '100%',
position: 'relative'
}}>
{unit.unitName}
{hasPreviewCourse && (
<span style={{
marginLeft: '8px',
padding: '2px 8px',
background: '#4080ff',
color: '#fff',
fontSize: '11px',
fontWeight: '600',
borderRadius: '10px'
}}>
可试看
</span>
)}
</div>
}
name={`vertical-${index + 1}`}
className={`course-list-item vertical-course ${hasPreviewCourse ? 'has-preview-unit' : ''}`}
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''} ${course.canPreview ? 'has-preview' : ''}`}
onClick={() => {
// 先设置选中状态和触发课程点击事件
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName, courseType: 'vertical' });
// 如果是可试看课程,延迟打开新窗口
if (course.canPreview && course.previewUrl) {
setTimeout(() => {
window.open(course.previewUrl, '_blank');
}, 100);
}
}}
style={{ cursor: 'pointer' }}
>
{course.canPreview && (
<span className="preview-badge">可试看</span>
)}
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
);
})}
</Collapse>
</div>
</div>

View File

@@ -0,0 +1,236 @@
import { useState, useEffect } from "react";
import { Collapse, Timeline, Spin } from "@arco-design/web-react";
import { getCourseLiveList } from "@/services/courseLive";
import "./index.css";
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
const CourseList = ({ className = "", onCourseClick }) => {
const [compoundCourseList, setCompoundCourseList] = useState([]);
const [verticalCourseList, setVerticalCourseList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
useEffect(() => {
fetchCourseList();
}, []);
const fetchCourseList = async () => {
setLoading(true);
try {
const res = await getCourseLiveList();
if (res.success) {
const compoundList = res.data.compound || [];
const verticalList = res.data.vertical || [];
setCompoundCourseList(compoundList);
setVerticalCourseList(verticalList);
// 合并两个列表用于查找默认选中的课程
const allCourses = [...compoundList, ...verticalList];
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of allCourses) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
// 触发课程选择事件
if (onCourseClick) {
onCourseClick({
...todayCourse,
unitName: unit.unitName
});
}
foundTodayCourse = true;
break;
}
}
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of allCourses) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
if (onCourseClick) {
onCourseClick({
...activeCourse,
unitName: unit.unitName
});
}
break;
}
}
}
}
} catch (error) {
console.error("获取课程列表失败:", error);
} finally {
setLoading(false);
}
};
// 判断课程状态
const getCourseStatus = (course) => {
if (course.completed) return "finish";
if (course.current) return "active";
// 判断未来课程的具体状态
if (course.upcoming) {
const courseDate = new Date(course.date);
const today = new Date();
// 重置时间部分只比较日期
courseDate.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
const timeDiff = courseDate - today;
const daysDiff = Math.floor(timeDiff / (24 * 60 * 60 * 1000));
// 未来7天内的课程显示为"即将开始"
if (daysDiff > 0 && daysDiff <= 7) {
return "coming";
}
// 7天后的课程显示为"未开始"
return "not-started";
}
// 默认状态
return "pending";
};
// 获取图标类型
const getDotIcon = (course) => {
if (course.completed) return <div className="time-line-dot-icon" />;
if (course.current) return <div className="time-line-clock-icon" />;
return <div className="time-line-lock-icon" />;
};
if (loading) {
return (
<div className={`${className} course-list-wrapper`}>
<p className="course-list-title">课程列表</p>
<div className="course-list-content">
<Spin />
</div>
</div>
);
}
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"
defaultActiveKey={[]}
>
{/* 复合能力课部分 */}
{compoundCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
name={String(index + 1)}
className="course-list-item"
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''}`}
onClick={() => {
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName });
}}
style={{ cursor: 'pointer' }}
>
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
))}
{/* 分割线 */}
{compoundCourseList.length > 0 && verticalCourseList.length > 0 && (
<div className="course-divider">
<span className="divider-line"></span>
<span className="divider-text">垂直能力课</span>
<span className="divider-line"></span>
</div>
)}
{/* 垂直能力课部分 */}
{verticalCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
name={`vertical-${index + 1}`}
className="course-list-item vertical-course"
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''}`}
onClick={() => {
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName, courseType: 'vertical' });
}}
style={{ cursor: 'pointer' }}
>
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
))}
</Collapse>
</div>
</div>
);
};
export default CourseList;

View File

@@ -90,17 +90,30 @@
overflow: hidden;
img {
width: 150%;
height: 150%;
width: 200%;
height: 200%;
object-fit: cover;
object-position: center 30%;
position: absolute;
left: 50%;
top: 2%;
top: 0%;
transform: translateX(-50%);
}
}
/* 孙应战导师头像特殊调整 - 居中显示 */
.teacher-avatar.teacher-sunyingzhan {
img {
width: 175% !important;
height: 175% !important;
object-fit: cover !important;
object-position: center center !important;
top: -50% !important;
left: 5% !important;
transform: none !important;
}
}
/* 刘杰导师头像特殊调整 */
.teacher-avatar.teacher-liujie {
img {
@@ -111,6 +124,17 @@
}
}
/* 求职策略页面的头像特殊调整 */
.teacher-avatar.teacher-strategy {
img {
width: 150% !important;
height: 150% !important;
object-fit: cover !important;
object-position: center 40% !important;
top: 0% !important;
}
}
.avatar-wrapper {
position: relative;
width: 64px;
@@ -236,18 +260,19 @@
}
.courses-video-player-teacher-tags {
width: 100%;
height: 50px;
height: auto;
min-height: 50px;
.teacher-tags {
width: 100%;
height: 24px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
overflow-x: auto;
margin-top: 5px;
gap: 8px;
overflow: hidden;
> li {
box-sizing: border-box;
@@ -256,10 +281,9 @@
color: #0077ff;
font-size: 14px;
font-weight: 400;
margin-right: 10px;
border-radius: 4px;
flex-shrink: 0;
flex-wrap: nowrap;
white-space: nowrap;
}
}
}

View File

@@ -1,4 +1,4 @@
import { Avatar } from "@arco-design/web-react";
import { Avatar, Tooltip } from "@arco-design/web-react";
import Locked from "@/components/Locked";
import "./index.css";
@@ -7,19 +7,37 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
console.log(item);
};
// 默认导师信息 - 魏立慧老师(用于求职策略定制页面)
const defaultTeacher = {
name: "魏立慧",
introduction: "企业资深一线HR专注于为求职者提供一对一的个性化指导。通过真实招聘视角深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径帮助学生快速提升求职竞争力。求职策略以实用落地为核心注重互动交流与角色定位让学员在轻松氛围中获得直击痛点的求职策略。",
specialties: ["深谙用人逻辑", "擅长挖掘优势", "沟通真诚自然", "点评直击要害"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png",
type: "企业资深HR"
};
// 1V1求职规划课程信息
const oneOnOneCourse = {
courseName: "1V1 求职规划",
date: "2024-08-25",
unitName: "1V1 规划阶段",
teacherName: "魏立慧",
current: false
};
// 获取当前课程的导师信息
const currentTeacher = selectedCourse && teacherData
? teacherData[selectedCourse.teacherName]
: teacherData?.["魏立慧"] || {
name: "魏立慧",
introduction: "企业资深一线HR专注于为求职者提供一对一的个性化指导。通过真实招聘视角深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径帮助学生快速提升求职竞争力。求职策略以实用落地为核心注重互动交流与角色定位让学员在轻松氛围中获得直击痛点的求职策略。",
specialties: ["深谙用人逻辑", "擅长挖掘优势", "沟通真诚自然", "点评直击要害"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png",
type: "企业资深HR"
};
// 如果是锁定状态(求职策略页面),始终显示魏立慧老师
const currentTeacher = isLock
? defaultTeacher
: (selectedCourse && teacherData && teacherData[selectedCourse.teacherName]
? teacherData[selectedCourse.teacherName]
: null);
// 如果是锁定状态使用1V1求职规划课程信息
const displayCourse = isLock ? oneOnOneCourse : selectedCourse;
// 需要调整头像位置的导师
const needsAdjustment = ["赵志强", "魏立慧", "郭建辉"].includes(currentTeacher.name);
const needsAdjustment = currentTeacher && ["赵志强", "魏立慧", "郭建辉", "孙应战"].includes(currentTeacher.name);
// 根据导师设置不同的背景色 - 这些颜色提取自实际的PNG图片背景
const getAvatarBackground = (name) => {
@@ -27,16 +45,16 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
"刘杰": "#E3E2E0", // 浅灰色
"郭建辉": "#E0D9D3", // 米灰色
"赵志强": "#E3E2E0", // 浅灰色
"孙应战": "#E3E2E0", // 浅灰
"孙应战": "#FFFFFF", //
"魏立慧": "#DCD8D4" // 灰褐色
};
return backgrounds[name] || "#E3E2E0";
};
// 获取当前课程信息
const courseName = selectedCourse?.courseName || "钢铁是怎样炼成的";
const courseDate = selectedCourse?.date || "09.01";
const unitName = selectedCourse?.unitName || "教育体系认知";
const courseName = selectedCourse?.courseName || "";
const courseDate = selectedCourse?.date || "";
const unitName = selectedCourse?.unitName || "";
// 格式化日期时间
const formatDateTime = (date) => {
@@ -79,99 +97,171 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
<span onClick={() => handleClickBtn(2)}>下一集 &gt;</span>
</div>
<div className="courses-video-player-video">
{/* 所有课程都显示模糊的海报图和锁定状态 */}
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<img
src={unitPosters?.[unitName] || unitPosters?.["岗位体系认知"]}
alt={unitName}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
filter: 'blur(20px)'
}}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
{selectedCourse ? (
/* 选中课程时显示模糊的海报图和锁定状态 */
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<img
src={unitPosters?.[unitName] || unitPosters?.["岗位体系认知"]}
alt={unitName}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
filter: 'blur(10px)'
}}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '16px'
}}>
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
alt="lock"
style={{ width: '280px', height: '280px' }}
/>
<span style={{
color: '#fff',
fontSize: '16px',
fontWeight: '500',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: '8px 16px',
borderRadius: '4px'
}}>
DEMO演示非学员无查看权限
</span>
</div>
</div>
) : (
/* 未选中课程时显示黑屏 */
<div style={{
width: '100%',
height: '100%',
backgroundColor: '#000',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '16px'
justifyContent: 'center'
}}>
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
alt="lock"
style={{ width: '280px', height: '280px' }}
/>
<span style={{
color: '#fff',
fontSize: '16px',
fontWeight: '500',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: '8px 16px',
borderRadius: '4px'
color: '#666',
fontSize: '16px'
}}>
DEMO演示非学员无查看权限
请选择课程开始观看
</span>
</div>
</div>
)}
</div>
</>
)}
</div>
<div className="courses-video-player-info">
{/* 直播观众信息 */}
<div className="courses-video-player-audience-info">
<div className="avatar-wrapper">
<Avatar
className={`teacher-avatar ${needsAdjustment ? 'avatar-adjust' : ''} ${currentTeacher.name === '刘杰' ? 'teacher-liujie' : ''}`}
style={{ backgroundColor: getAvatarBackground(currentTeacher.name) }}
>
<img
alt="avatar"
src={currentTeacher.avatar}
/>
</Avatar>
{selectedCourse?.current && <div className="living-icon" />}
</div>
<span className="teacher-name">{currentTeacher.name}老师</span>
<span className="teacher-tag">{unitName}</span>
<div className="living-data">
<div className="living-data-item">
<span>开始</span>
<span>{formatDateTime(courseDate)} - 14:00</span>
{(isLock || (selectedCourse && currentTeacher)) ? (
<>
{/* 直播观众信息 */}
<div className="courses-video-player-audience-info">
<div className="avatar-wrapper">
<Avatar
className={`teacher-avatar ${needsAdjustment ? 'avatar-adjust' : ''} ${currentTeacher?.name === '刘杰' ? 'teacher-liujie' : ''} ${currentTeacher?.name === '孙应战' ? 'teacher-sunyingzhan' : ''} ${isLock ? 'teacher-strategy' : ''}`}
style={{ backgroundColor: getAvatarBackground(currentTeacher?.name) }}
>
<img
alt="avatar"
src={currentTeacher?.avatar || ''}
/>
</Avatar>
{displayCourse?.current && <div className="living-icon" />}
</div>
<span className="teacher-name">{currentTeacher?.name || ''}老师</span>
<span className="teacher-tag">{displayCourse?.unitName || unitName}</span>
<div className="living-data">
<div className="living-data-item">
<span>开始</span>
<span>{formatDateTime(displayCourse?.date || courseDate)} - 14:00</span>
</div>
<div className="living-data-item">
<span>时长</span>
<span>{isLock ? '120分钟' : '60分钟'}</span>
</div>
<div className="living-data-item">
<span>观看</span>
<span>{isLock ? '1人' : `${
selectedCourse?.courseType === 'vertical'
? 50 + Math.floor(Math.random() * 11) // 垂直课50-60人
: 180 + Math.floor(Math.random() * 21) // 复合课180-200人
}`}</span>
</div>
</div>
</div>
<div className="living-data-item">
<span>时长</span>
<span>60分钟</span>
</div>
<div className="living-data-item">
<span>观看</span>
<span>{selectedCourse ? '197人' : '1928人'}</span>
{/* 直播教师信息 */}
<div className="courses-video-player-teacher-info">
<div className="courses-video-player-teacher-introduce">
<p className="title icon1">导师介绍</p>
<p className="teacher-introduce">
{currentTeacher?.introduction || ''}
</p>
</div>
<div className="courses-video-player-teacher-tags">
<p className="title icon2">教师专长</p>
<Tooltip
content={
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
maxWidth: '320px',
padding: '8px'
}}>
{(currentTeacher?.specialties || []).map((specialty, index) => (
<span key={index} style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
{specialty}
</span>
))}
</div>
}
position="top"
color="#fff"
style={{
'--arco-color-bg-tooltip': 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
'--arco-color-text-tooltip': '#1d2129'
}}
>
<ul className="teacher-tags">
{(currentTeacher?.specialties || []).map((specialty, index) => (
<li key={index}>{specialty}</li>
))}
</ul>
</Tooltip>
</div>
</div>
</>
) : (
/* 未选中课程时显示空白 */
<div style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#86909c',
fontSize: '14px'
}}>
请选择课程查看导师信息
</div>
</div>
{/* 直播教师信息 */}
<div className="courses-video-player-teacher-info">
<div className="courses-video-player-teacher-introduce">
<p className="title icon1">导师介绍</p>
<p className="teacher-introduce">
{currentTeacher.introduction}
</p>
</div>
<div className="courses-video-player-teacher-tags">
<p className="title icon2">教师专长</p>
<ul className="teacher-tags">
{currentTeacher.specialties.map((specialty, index) => (
<li key={index}>{specialty}</li>
))}
</ul>
</div>
</div>
)}
</div>
</div>
);

View File

@@ -192,6 +192,12 @@
gap: 5px;
}
.course-list-item .time-line-item .course-date {
font-size: 12px;
color: #86909c;
margin-left: auto;
}
.course-list-item .finish::before {
content: "已结束";
position: absolute;

View File

@@ -22,43 +22,7 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
if (res.success) {
const courseList = res.data || [];
setCourseLiveList(courseList);
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of courseList) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
// 触发课程选择事件
if (onCourseClick) {
onCourseClick({
...todayCourse,
unitName: unit.unitName
});
}
foundTodayCourse = true;
break;
}
}
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of courseList) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
if (onCourseClick) {
onCourseClick({
...activeCourse,
unitName: unit.unitName
});
}
break;
}
}
}
// 不设置默认选中,保持黑屏状态
}
} catch (error) {
console.error("Failed to fetch course list:", error);
@@ -122,7 +86,7 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
className="course-list"
bordered={false}
expandIconPosition="right"
defaultActiveKey={["1", "2", "3"]}
defaultActiveKey={[]}
>
{courseLiveList.map((unit, index) => (
<CollapseItem
@@ -155,9 +119,8 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
{course.courseName}
</p>
<div className="time-line-item-info">
<span>讲师{course.teacherName}</span>
{course.date && <span>{course.date}</span>}
{course.time && <span>{course.time}</span>}
<span>{course.teacherName}</span>
<span className="course-date">{course.date}</span>
</div>
</div>
</TimelineItem>

View File

@@ -17,11 +17,12 @@
height: 64px;
background-size: 100% 100%;
margin-right: -10px;
background-image: url("@/assets/images/StageProgress/step1_active.png");
background-image: url("@/assets/images/StageProgress/step1.png");
background-size: 100%;
color: #ffffff;
color: #0077ff;
font-size: 16px;
font-weight: 400;
position: relative;
}
.stage-progress-item4 {
@@ -39,11 +40,15 @@
}
.stage-progress-item4-active {
color: #ffffff;
color: #ffffff !important;
font-size: 16px;
font-weight: 400;
background-image: url("@/assets/images/StageProgress/step4_active.png");
}
.stage-progress-item4-active .stage-progress-item-text-active {
color: #ffffff !important;
}
.stage-progress-item {
flex-shrink: 0;
@@ -57,11 +62,12 @@
color: #86909c;
font-size: 16px;
font-weight: 400;
position: relative;
}
.stage-progress-item-active {
color: #ffffff;
background-image: url("@/assets/images/StageProgress/step_active.png");
color: #0077ff;
background-image: url("@/assets/images/StageProgress/step2-3.png");
}
.stage-progress-item-step {
@@ -93,6 +99,18 @@
width: 20px;
height: 20px;
}
.stage-progress-item-icon-blue {
filter: brightness(0) saturate(100%) invert(31%) sepia(98%) saturate(2000%) hue-rotate(201deg) brightness(95%) contrast(101%);
}
.stage-progress-item-icon-white {
filter: brightness(0) invert(1);
}
.stage-progress-item4-text {
color: #ffffff !important;
}
.stage-progress-item-icon2 {
width: 5px;
height: 10px;
@@ -100,7 +118,7 @@
}
.stage-progress-item-text-active {
color: #fff !important;
color: #0077ff !important;
}
.stage-progress-text {

View File

@@ -1,5 +1,6 @@
import { useNavigate } from "react-router-dom";
import IconFont from "@/components/IconFont";
import CareerStartIcon from "@/assets/images/StageProgress/career_start_icon_new.png";
import "./index.css";
const StageProgress = () => {
@@ -16,31 +17,35 @@ const StageProgress = () => {
step1
</span>
<span className="stage-progress-item-text stage-progress-item-text-active">
生涯
生涯
</span>
<IconFont src="recuUY5n0dIJKu" className="stage-progress-item-icon" />
<img
src={CareerStartIcon}
className="stage-progress-item-icon stage-progress-item-icon-blue"
alt="生涯启航"
/>
</li>
<li className="stage-progress-item">
<span className="stage-progress-item-step">step2</span>
<span className="stage-progress-item-text">能力跃升</span>
<IconFont src="recuUY5kKp4Qc5" className="stage-progress-item-icon" />
<li className="stage-progress-item stage-progress-item-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step2</span>
<span className="stage-progress-item-text stage-progress-item-text-active">能力跃升</span>
<IconFont src="recuUY5kKp4Qc5" className="stage-progress-item-icon stage-progress-item-icon-blue" />
</li>
<li className="stage-progress-text" onClick={handleClickStar}>
<span>垂直方向选择</span>
</li>
<li className="stage-progress-item">
<span className="stage-progress-item-step">step3</span>
<span className="stage-progress-item-text">垂直精进</span>
<IconFont src="recuUY5qlmzVhH" className="stage-progress-item-icon" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2" />
<IconFont src="recuUY5joxSk5C" className="stage-progress-item-icon" />
<li className="stage-progress-item stage-progress-item-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step3</span>
<span className="stage-progress-item-text stage-progress-item-text-active">垂直精进</span>
<IconFont src="recuUY5qlmzVhH" className="stage-progress-item-icon stage-progress-item-icon-blue" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2 stage-progress-item-icon-blue" />
<IconFont src="recuUY5joxSk5C" className="stage-progress-item-icon stage-progress-item-icon-blue" />
</li>
<li className="stage-progress-item4 ">
<span className="stage-progress-item-step">step4</span>
<span className="stage-progress-item-text">决胜求职</span>
<IconFont src="recuUY5lTOco3Q" className="stage-progress-item-icon" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2" />
<IconFont src="recuUY5luVMCPc" className="stage-progress-item-icon" />
<li className="stage-progress-item4 stage-progress-item4-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step4</span>
<span className="stage-progress-item-text stage-progress-item-text-active stage-progress-item4-text">决胜求职</span>
<IconFont src="recuUY5lTOco3Q" className="stage-progress-item-icon stage-progress-item-icon-white" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2 stage-progress-item-icon-white" />
<IconFont src="recuUY5luVMCPc" className="stage-progress-item-icon stage-progress-item-icon-white" />
</li>
</ul>
);