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 (
-

- icon +

- 班级排名 + 班级排名

{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 = () => {
- +
); diff --git a/src/pages/PublicCourses/index.jsx b/src/pages/PublicCourses/index.jsx index 7bafff8..5dd5781 100644 --- a/src/pages/PublicCourses/index.jsx +++ b/src/pages/PublicCourses/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 PublicCourseList from "@/components/PublicCourseList"; @@ -9,22 +9,25 @@ const PublicCourses = () => { // 默认不选中任何课程,显示黑屏状态 const [selectedCourse, setSelectedCourse] = useState(null); const [searchParams] = useSearchParams(); + const publicCourseListRef = useRef(null); // 检查URL参数,如果有courseId则自动打开对应课程 useEffect(() => { const courseId = searchParams.get('courseId'); const courseTitle = searchParams.get('courseTitle'); + console.log('PublicCourses - URL params:', { courseId, courseTitle }); + if (courseId || courseTitle) { - // 查找对应的课程 - const publicCourses = mockData.publicCourses || []; - const targetCourse = publicCourses.find(course => - course.id === courseId || course.title === courseTitle - ); + // 需要给组件时间加载数据 + const timer = setTimeout(() => { + if (publicCourseListRef.current) { + console.log('PublicCourses - Calling selectCourse via ref'); + publicCourseListRef.current.selectCourse(courseId, courseTitle); + } + }, 500); // 等待数据加载 - if (targetCourse) { - setSelectedCourse(targetCourse); - } + return () => clearTimeout(timer); } }, [searchParams]); @@ -41,7 +44,10 @@ const PublicCourses = () => { unitPosters={mockData.publicCourseBackgrounds} isPublicCourse={true} /> - + ); diff --git a/test-arco-collapse.html b/test-arco-collapse.html new file mode 100644 index 0000000..35031ed --- /dev/null +++ b/test-arco-collapse.html @@ -0,0 +1,100 @@ + + + + Test Arco Collapse Behavior + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/test-collapse-controlled.html b/test-collapse-controlled.html new file mode 100644 index 0000000..a04f50d --- /dev/null +++ b/test-collapse-controlled.html @@ -0,0 +1,88 @@ + + + + Test Controlled Collapse + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/test-collapse.html b/test-collapse.html new file mode 100644 index 0000000..8afec70 --- /dev/null +++ b/test-collapse.html @@ -0,0 +1,54 @@ + + + + Test Collapse + + + +
+ + + + + + + + + \ No newline at end of file