diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 0b58d5a..0d04352 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -56,7 +56,8 @@
"Bash(xxd:*)",
"Bash(kill:*)",
"Bash(find:*)",
- "Bash(pgrep:*)"
+ "Bash(pgrep:*)",
+ "Bash(npm start)"
],
"deny": [],
"ask": []
diff --git a/src/assets/images/Rank/first_icon.png b/src/assets/images/Rank/first_icon.png
index 4f36b70..aa9584d 100644
Binary files a/src/assets/images/Rank/first_icon.png and b/src/assets/images/Rank/first_icon.png differ
diff --git a/src/assets/images/Rank/icon1.png b/src/assets/images/Rank/icon1.png
new file mode 100644
index 0000000..2e50702
Binary files /dev/null and b/src/assets/images/Rank/icon1.png differ
diff --git a/src/assets/images/Rank/icon2.png b/src/assets/images/Rank/icon2.png
new file mode 100644
index 0000000..b15a4f7
Binary files /dev/null and b/src/assets/images/Rank/icon2.png differ
diff --git a/src/assets/images/Rank/icon3.png b/src/assets/images/Rank/icon3.png
new file mode 100644
index 0000000..5b1f837
Binary files /dev/null and b/src/assets/images/Rank/icon3.png differ
diff --git a/src/assets/images/Rank/second_icon.png b/src/assets/images/Rank/second_icon.png
index 8c7c3d0..d05bd51 100644
Binary files a/src/assets/images/Rank/second_icon.png and b/src/assets/images/Rank/second_icon.png differ
diff --git a/src/assets/images/Rank/third_icon.png b/src/assets/images/Rank/third_icon.png
index 7e03d66..8c824f6 100644
Binary files a/src/assets/images/Rank/third_icon.png and b/src/assets/images/Rank/third_icon.png differ
diff --git a/src/components/ClassRank/index.jsx b/src/components/ClassRank/index.jsx
index 39f2e40..a228121 100644
--- a/src/components/ClassRank/index.jsx
+++ b/src/components/ClassRank/index.jsx
@@ -19,10 +19,9 @@ const Rank = ({ className, data, loading }) => {
return (
-
-
+
- 班级排名
+ 班级排名
{loading ? (
diff --git a/src/components/CourseList/index.css b/src/components/CourseList/index.css
index 44a2023..bc12dca 100644
--- a/src/components/CourseList/index.css
+++ b/src/components/CourseList/index.css
@@ -416,4 +416,34 @@
display: block !important;
-webkit-line-clamp: unset !important;
-webkit-box-orient: unset !important;
+}
+
+/* 高亮动画效果 */
+@keyframes highlightPulse {
+ 0% {
+ background-color: transparent;
+ box-shadow: none;
+ }
+ 25% {
+ background-color: rgba(102, 126, 234, 0.1);
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.3);
+ }
+ 50% {
+ background-color: rgba(102, 126, 234, 0.2);
+ box-shadow: 0 0 15px rgba(102, 126, 234, 0.4);
+ }
+ 75% {
+ background-color: rgba(102, 126, 234, 0.1);
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.3);
+ }
+ 100% {
+ background-color: transparent;
+ box-shadow: none;
+ }
+}
+
+.highlight-animation {
+ animation: highlightPulse 2s ease-in-out;
+ border-radius: 4px;
+ transition: all 0.3s ease;
}
\ No newline at end of file
diff --git a/src/components/CourseList/index.jsx b/src/components/CourseList/index.jsx
index d206b2e..7c73aa4 100644
--- a/src/components/CourseList/index.jsx
+++ b/src/components/CourseList/index.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
import { Collapse, Timeline, Spin } from "@arco-design/web-react";
import { IconDown, IconRight } from "@arco-design/web-react/icon";
import { getCourseLiveList } from "@/services/courseLive";
@@ -7,11 +7,17 @@ import "./index.css";
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
-const CourseList = ({ className = "", onCourseClick }) => {
+const CourseList = forwardRef(({ className = "", onCourseClick }, ref) => {
const [compoundCourseList, setCompoundCourseList] = useState([]);
const [verticalCourseList, setVerticalCourseList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
+ const [activeKeys, setActiveKeys] = useState([]); // 控制展开的单元
+
+ // 调试 activeKeys 变化
+ useEffect(() => {
+ console.log('CourseList - activeKeys changed:', activeKeys);
+ }, [activeKeys]);
// 控制各分类的展开/收缩状态,默认全部展开
const [categoryExpanded, setCategoryExpanded] = useState({
@@ -19,6 +25,101 @@ const CourseList = ({ className = "", onCourseClick }) => {
'vertical': true // 垂直提升课
});
+ // 暴露方法给父组件调用
+ useImperativeHandle(ref, () => ({
+ selectCourse: (courseId, courseName) => {
+ console.log('CourseList - selectCourse called:', courseId, courseName);
+ console.log('CourseList - compoundCourseList:', compoundCourseList);
+ console.log('CourseList - verticalCourseList:', verticalCourseList);
+
+ // 在两个列表中查找课程
+ const allLists = [
+ { list: compoundCourseList, type: 'compound' },
+ { list: verticalCourseList, type: 'vertical' }
+ ];
+
+ for (const { list, type } of allLists) {
+ for (let i = 0; i < list.length; i++) {
+ const unit = list[i];
+ console.log(`Checking ${type} unit ${i}:`, unit.unitName, 'courses:', unit.courses?.length);
+
+ if (!unit.courses) {
+ console.log(`Unit ${unit.unitName} has no courses`);
+ continue;
+ }
+
+ const course = unit.courses.find(c => {
+ const matches = c.courseId === courseId || c.courseName === courseName;
+ if (matches) {
+ console.log('Found matching course:', c);
+ }
+ return matches;
+ });
+
+ if (course) {
+ console.log('CourseList - Found course:', course.courseName, 'in', type, 'list');
+
+ // 展开对应的单元
+ // 计算正确的 activeKey
+ let activeKey;
+ if (type === 'compound') {
+ activeKey = String(i + 1);
+ } else {
+ // 垂直课程使用特定的前缀
+ activeKey = `vertical-${i + 1}`;
+ }
+
+ // 如果单元未展开,则添加到 activeKeys 中
+ setActiveKeys(prevKeys => {
+ if (!prevKeys.includes(activeKey)) {
+ console.log('Adding activeKey:', activeKey, 'to existing keys:', prevKeys);
+ return [...prevKeys, activeKey];
+ }
+ return prevKeys;
+ });
+
+ // 设置选中的课程
+ console.log('Setting selectedCourseId to:', course.courseId);
+ setSelectedCourseId(course.courseId);
+
+ // 触发点击事件
+ if (onCourseClick) {
+ console.log('Triggering onCourseClick with course:', course);
+ onCourseClick({
+ ...course,
+ unitName: unit.unitName,
+ unitPoster: unit.unitPoster
+ });
+ }
+
+ // 滚动到对应的课程位置
+ // 需要等待折叠面板展开动画完成
+ setTimeout(() => {
+ // 找到对应的课程元素并滚动到视图中
+ const courseElements = document.querySelectorAll('.time-line-item');
+ courseElements.forEach(element => {
+ const courseText = element.querySelector('p')?.textContent;
+ if (courseText === course.courseName) {
+ console.log('Scrolling to course element in CourseList');
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ // 添加高亮效果
+ element.classList.add('highlight-animation');
+ setTimeout(() => {
+ element.classList.remove('highlight-animation');
+ }, 2000);
+ }
+ });
+ }, 300); // 等待折叠面板展开
+
+ return; // 找到后退出
+ }
+ }
+ }
+
+ console.log('Course not found in any list:', courseId, courseName);
+ }
+ }), [compoundCourseList, verticalCourseList, onCourseClick]);
+
useEffect(() => {
fetchCourseList();
}, []);
@@ -132,11 +233,40 @@ const CourseList = ({ className = "", onCourseClick }) => {
课程列表
{
+ console.log('Collapse onChange received:', keys, 'type:', typeof keys);
+
+ // Arco Collapse 在受控模式下,当点击时:
+ // - 如果是字符串,表示点击了某个面板,需要切换它的展开/收起状态
+ // - 如果是数组,表示新的展开状态
+ if (typeof keys === 'string') {
+ // 切换单个面板的展开/收起状态
+ setActiveKeys(prevKeys => {
+ const keyStr = String(keys);
+ const newKeys = [...prevKeys];
+ const index = newKeys.indexOf(keyStr);
+ if (index > -1) {
+ // 如果已展开,则收起
+ newKeys.splice(index, 1);
+ } else {
+ // 如果已收起,则展开
+ newKeys.push(keyStr);
+ }
+ console.log('Toggling key:', keyStr, 'New activeKeys:', newKeys);
+ return newKeys;
+ });
+ } else if (Array.isArray(keys)) {
+ // 直接设置新的展开状态
+ setActiveKeys(keys);
+ } else {
+ // 处理 undefined/null 的情况
+ setActiveKeys([]);
+ }
+ }}
>
{/* 复合能力课分割线 */}
{compoundCourseList.length > 0 && (
@@ -145,15 +275,13 @@ const CourseList = ({ className = "", onCourseClick }) => {
onClick={() => setCategoryExpanded(prev => ({ ...prev, compound: !prev.compound }))}
>
-
- 复合能力课
-
+ 复合技能课
)}
{/* 复合能力课部分 */}
-
+
{compoundCourseList.map((unit, index) => (
{
))}
- ))}
+ ))}
{/* 垂直能力课分割线 */}
@@ -205,15 +333,13 @@ const CourseList = ({ className = "", onCourseClick }) => {
onClick={() => setCategoryExpanded(prev => ({ ...prev, vertical: !prev.vertical }))}
>
-
- 垂直能力课
-
+
垂直技能课
)}
{/* 垂直能力课部分 */}
-
+
{verticalCourseList.map((unit, index) => {
// 检查单元是否包含可试看课程
const hasPreviewCourse = unit.courses.some(course => course.canPreview);
@@ -299,6 +425,6 @@ const CourseList = ({ className = "", onCourseClick }) => {
);
-};
+});
export default CourseList;
diff --git a/src/components/PublicCourseList/index.css b/src/components/PublicCourseList/index.css
index b225e64..cbed9bc 100644
--- a/src/components/PublicCourseList/index.css
+++ b/src/components/PublicCourseList/index.css
@@ -411,4 +411,34 @@
display: block !important;
-webkit-line-clamp: unset !important;
-webkit-box-orient: unset !important;
+}
+
+/* 高亮动画效果 */
+@keyframes highlightPulse {
+ 0% {
+ background-color: transparent;
+ box-shadow: none;
+ }
+ 25% {
+ background-color: rgba(102, 126, 234, 0.1);
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.3);
+ }
+ 50% {
+ background-color: rgba(102, 126, 234, 0.2);
+ box-shadow: 0 0 15px rgba(102, 126, 234, 0.4);
+ }
+ 75% {
+ background-color: rgba(102, 126, 234, 0.1);
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.3);
+ }
+ 100% {
+ background-color: transparent;
+ box-shadow: none;
+ }
+}
+
+.highlight-animation {
+ animation: highlightPulse 2s ease-in-out;
+ border-radius: 4px;
+ transition: all 0.3s ease;
}
\ No newline at end of file
diff --git a/src/components/PublicCourseList/index.jsx b/src/components/PublicCourseList/index.jsx
index b25fda1..1bb526b 100644
--- a/src/components/PublicCourseList/index.jsx
+++ b/src/components/PublicCourseList/index.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
import { Collapse, Timeline, Spin } from "@arco-design/web-react";
import { IconDown, IconRight } from "@arco-design/web-react/icon";
import { getPublicCourseLiveList } from "@/services/courseLive";
@@ -9,10 +9,11 @@ import "./index.css";
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
-const PublicCourseList = ({ className = "", onCourseClick }) => {
+const PublicCourseList = forwardRef(({ className = "", onCourseClick }, ref) => {
const [courseLiveList, setCourseLiveList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
+ const [activeKeys, setActiveKeys] = useState([]);
// 控制各分类的展开/收缩状态,默认全部展开
const [categoryExpanded, setCategoryExpanded] = useState({
@@ -25,6 +26,88 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
fetchCourseList();
}, []);
+ // 暴露方法给父组件调用
+ useImperativeHandle(ref, () => ({
+ selectCourse: (courseId, courseName) => {
+ console.log('PublicCourseList - selectCourse called:', courseId, courseName);
+ console.log('PublicCourseList - Current courseLiveList:', courseLiveList);
+
+ // 查找课程并触发点击
+ for (let i = 0; i < courseLiveList.length; i++) {
+ const unit = courseLiveList[i];
+ console.log(`Checking unit ${i}:`, unit.unitName, 'courses:', unit.courses?.length);
+
+ if (!unit.courses) {
+ console.log(`Unit ${unit.unitName} has no courses`);
+ continue;
+ }
+
+ const courseIndex = unit.courses.findIndex(c => {
+ const matches = c.courseId === courseId || c.courseName === courseName;
+ if (matches) {
+ console.log('Found matching course:', c);
+ }
+ return matches;
+ });
+
+ if (courseIndex !== -1) {
+ const course = unit.courses[courseIndex];
+
+ // 展开对应的单元 - 使用正确的索引
+ const activeKey = String(i + 1);
+
+ // 如果单元未展开,则添加到 activeKeys 中
+ setActiveKeys(prevKeys => {
+ if (!prevKeys.includes(activeKey)) {
+ console.log('Adding activeKey:', activeKey, 'to existing keys:', prevKeys);
+ return [...prevKeys, activeKey];
+ }
+ return prevKeys;
+ });
+
+ // 设置选中的课程
+ console.log('Setting selectedCourseId to:', course.courseId);
+ setSelectedCourseId(course.courseId);
+
+ // 触发点击事件
+ if (onCourseClick) {
+ console.log('Triggering onCourseClick with course:', course);
+ onCourseClick({
+ ...course,
+ unitName: unit.unitName,
+ unitPoster: unit.unitPoster || "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuW7gMz6sRee.jpg"
+ });
+ }
+
+ // 滚动到对应的单元和课程位置
+ // 需要等待折叠面板展开动画完成
+ setTimeout(() => {
+ const unitElement = document.querySelector(`.course-list-item:nth-child(${i + 1})`);
+ if (unitElement) {
+ console.log('Scrolling to unit element');
+ unitElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+
+ // 查找并滚动到具体课程
+ const courseElements = document.querySelectorAll('.time-line-item');
+ courseElements.forEach(element => {
+ const courseText = element.querySelector('p')?.textContent;
+ if (courseText === course.courseName) {
+ console.log('Scrolling to course element');
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ });
+ }, 300); // 等待折叠面板展开
+
+ console.log('Course found and selected:', course.courseName);
+ return; // 找到后退出
+ }
+ }
+
+ console.log('Course not found:', courseId, courseName);
+ }
+ }), [courseLiveList, onCourseClick]);
+
const fetchCourseList = async () => {
setLoading(true);
try {
@@ -92,11 +175,40 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
公共课程列表
{
+ console.log('PublicCourseList Collapse onChange received:', keys, 'type:', typeof keys);
+
+ // Arco Collapse 在受控模式下,当点击时:
+ // - 如果是字符串,表示点击了某个面板,需要切换它的展开/收起状态
+ // - 如果是数组,表示新的展开状态
+ if (typeof keys === 'string') {
+ // 切换单个面板的展开/收起状态
+ setActiveKeys(prevKeys => {
+ const keyStr = String(keys);
+ const newKeys = [...prevKeys];
+ const index = newKeys.indexOf(keyStr);
+ if (index > -1) {
+ // 如果已展开,则收起
+ newKeys.splice(index, 1);
+ } else {
+ // 如果已收起,则展开
+ newKeys.push(keyStr);
+ }
+ console.log('Toggling key:', keyStr, 'New activeKeys:', newKeys);
+ return newKeys;
+ });
+ } else if (Array.isArray(keys)) {
+ // 直接设置新的展开状态
+ setActiveKeys(keys);
+ } else {
+ // 处理 undefined/null 的情况
+ setActiveKeys([]);
+ }
+ }}
>
{courseLiveList.map((unit, index) => {
// 判断当前单元属于哪个分类
@@ -239,6 +351,6 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
);
-};
+});
export default PublicCourseList;
\ No newline at end of file
diff --git a/src/pages/CalendarPage/components/EventDetailModal.jsx b/src/pages/CalendarPage/components/EventDetailModal.jsx
index 12fbf63..b90913d 100644
--- a/src/pages/CalendarPage/components/EventDetailModal.jsx
+++ b/src/pages/CalendarPage/components/EventDetailModal.jsx
@@ -110,6 +110,8 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
// 处理课程点击 - 跳转到对应的课程页面
const handleCourseClick = (eventItem) => {
+ console.log('EventDetailModal - Clicked event:', eventItem);
+
// 构建URL参数
const params = new URLSearchParams();
if (eventItem.id) {
@@ -119,6 +121,8 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
params.append('courseTitle', eventItem.title);
}
+ console.log('EventDetailModal - Navigate params:', params.toString());
+
// 根据课程类型跳转到不同页面
switch(eventItem.type) {
case 'compound-skill':
diff --git a/src/pages/LivePage/index.jsx b/src/pages/LivePage/index.jsx
index 3263c0a..6e58245 100644
--- a/src/pages/LivePage/index.jsx
+++ b/src/pages/LivePage/index.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useRef } from "react";
import { useSearchParams } from "react-router-dom";
import CoursesVideoPlayer from "@/components/CoursesVideoPlayer";
import CourseList from "@/components/CourseList";
@@ -8,26 +8,25 @@ import "./index.css";
const LivePage = () => {
const [selectedCourse, setSelectedCourse] = useState(null);
const [searchParams] = useSearchParams();
+ const courseListRef = useRef(null);
// 检查URL参数,如果有courseId或courseTitle则自动打开对应课程
useEffect(() => {
const courseId = searchParams.get('courseId');
const courseTitle = searchParams.get('courseTitle');
+ console.log('LivePage - URL params:', { courseId, courseTitle });
+
if (courseId || courseTitle) {
- // 查找对应的课程
- const allCourses = [
- ...(mockData.compoundSkillCourses || []),
- ...(mockData.verticalSkillCourses || [])
- ];
+ // 需要给组件时间加载数据
+ const timer = setTimeout(() => {
+ if (courseListRef.current) {
+ console.log('LivePage - Calling selectCourse via ref');
+ courseListRef.current.selectCourse(courseId, courseTitle);
+ }
+ }, 500); // 等待数据加载
- const targetCourse = allCourses.find(course =>
- course.id === courseId || course.title === courseTitle
- );
-
- if (targetCourse) {
- setSelectedCourse(targetCourse);
- }
+ return () => clearTimeout(timer);
}
}, [searchParams]);
@@ -39,7 +38,10 @@ const LivePage = () => {