feat: 优化课程列表功能和班级排名样式
- 修复课程列表单元展开/收起功能 - 优化日历页面跳转到课程列表的延迟时间 - 恢复复合技能课和垂直技能课的分割线及收缩功能 - 添加班级排名第一二三名的特殊样式图标 - 修复Collapse组件onChange事件处理 - 优化课程自动选中和滚动定位功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
@@ -56,7 +56,8 @@
|
||||
"Bash(xxd:*)",
|
||||
"Bash(kill:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(pgrep:*)"
|
||||
"Bash(pgrep:*)",
|
||||
"Bash(npm start)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
src/assets/images/Rank/icon1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/images/Rank/icon2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/Rank/icon3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -19,10 +19,9 @@ const Rank = ({ className, data, loading }) => {
|
||||
|
||||
return (
|
||||
<div className={`module-class-rank ${className}`}>
|
||||
<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 }} />
|
||||
<p className="module-class-rank-title">
|
||||
<IconFont className="title-icon" src="recuUY5nNf7DWT" />
|
||||
<span style={{ fontWeight: 'bold' }}>班级排名</span>
|
||||
<span>班级排名</span>
|
||||
</p>
|
||||
{loading ? (
|
||||
<Spin size={40} className="module-class-rank-spin" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 }) => {
|
||||
<p className="course-list-title">课程列表</p>
|
||||
<div className="course-list-content">
|
||||
<Collapse
|
||||
lazyload
|
||||
className="course-list"
|
||||
bordered={false}
|
||||
expandIconPosition="right"
|
||||
defaultActiveKey={[]}
|
||||
activeKey={activeKeys}
|
||||
onChange={(keys) => {
|
||||
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 }))}
|
||||
>
|
||||
<span className="divider-line"></span>
|
||||
<span className="divider-text">
|
||||
复合能力课
|
||||
</span>
|
||||
<span className="divider-text">复合技能课</span>
|
||||
<span className="divider-line"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 复合能力课部分 */}
|
||||
<div className={`course-category-wrapper ${!categoryExpanded.compound ? 'collapsed' : 'expanded'}`}>
|
||||
<div className={`course-category-wrapper ${categoryExpanded.compound ? 'expanded' : 'collapsed'}`}>
|
||||
{compoundCourseList.map((unit, index) => (
|
||||
<CollapseItem
|
||||
key={unit.unitId}
|
||||
@@ -195,7 +323,7 @@ const CourseList = ({ className = "", onCourseClick }) => {
|
||||
))}
|
||||
</Timeline>
|
||||
</CollapseItem>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 垂直能力课分割线 */}
|
||||
@@ -205,15 +333,13 @@ const CourseList = ({ className = "", onCourseClick }) => {
|
||||
onClick={() => setCategoryExpanded(prev => ({ ...prev, vertical: !prev.vertical }))}
|
||||
>
|
||||
<span className="divider-line"></span>
|
||||
<span className="divider-text">
|
||||
垂直能力课
|
||||
</span>
|
||||
<span className="divider-text">垂直技能课</span>
|
||||
<span className="divider-line"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 垂直能力课部分 */}
|
||||
<div className={`course-category-wrapper ${!categoryExpanded.vertical ? 'collapsed' : 'expanded'}`}>
|
||||
<div className={`course-category-wrapper ${categoryExpanded.vertical ? 'expanded' : 'collapsed'}`}>
|
||||
{verticalCourseList.map((unit, index) => {
|
||||
// 检查单元是否包含可试看课程
|
||||
const hasPreviewCourse = unit.courses.some(course => course.canPreview);
|
||||
@@ -299,6 +425,6 @@ const CourseList = ({ className = "", onCourseClick }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default CourseList;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 }) => {
|
||||
<p className="course-list-title">公共课程列表</p>
|
||||
<div className="course-list-content">
|
||||
<Collapse
|
||||
lazyload
|
||||
className="course-list"
|
||||
bordered={false}
|
||||
expandIconPosition="right"
|
||||
defaultActiveKey={[]}
|
||||
activeKey={activeKeys}
|
||||
onChange={(keys) => {
|
||||
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 }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default PublicCourseList;
|
||||
@@ -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':
|
||||
|
||||
@@ -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 = () => {
|
||||
<div className="live-page">
|
||||
<div className="live-page-content">
|
||||
<CoursesVideoPlayer selectedCourse={selectedCourse} teacherData={mockData.teacherData} unitPosters={mockData.unitPosters} />
|
||||
<CourseList onCourseClick={handleCourseClick} />
|
||||
<CourseList
|
||||
ref={courseListRef}
|
||||
onCourseClick={handleCourseClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<PublicCourseList onCourseClick={handleCourseClick} />
|
||||
<PublicCourseList
|
||||
ref={publicCourseListRef}
|
||||
onCourseClick={handleCourseClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
100
test-arco-collapse.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Arco Collapse Behavior</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@arco-design/web-react@2.66.5/dist/css/arco.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/@arco-design/web-react@2.66.5/dist/arco.min.js"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useState } = React;
|
||||
const { Collapse, Button, Space } = arco;
|
||||
const CollapseItem = Collapse.Item;
|
||||
|
||||
function App() {
|
||||
// 测试受控模式
|
||||
const [controlledKeys, setControlledKeys] = useState([]);
|
||||
|
||||
const handleControlledChange = (keys, extra, type) => {
|
||||
console.log('=== Controlled Mode ===');
|
||||
console.log('onChange keys:', keys);
|
||||
console.log('keys type:', typeof keys);
|
||||
console.log('Is Array:', Array.isArray(keys));
|
||||
console.log('extra:', extra);
|
||||
console.log('type:', type);
|
||||
setControlledKeys(keys);
|
||||
};
|
||||
|
||||
const programmaticallyOpen = (key) => {
|
||||
setControlledKeys(prev => {
|
||||
if (!prev.includes(key)) {
|
||||
console.log('Programmatically opening:', key);
|
||||
return [...prev, key];
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h2>Controlled Mode Test</h2>
|
||||
<p>Active Keys: {JSON.stringify(controlledKeys)}</p>
|
||||
|
||||
<Space style={{ marginBottom: 20 }}>
|
||||
<Button onClick={() => programmaticallyOpen('1')}>Open Unit 1</Button>
|
||||
<Button onClick={() => programmaticallyOpen('2')}>Open Unit 2</Button>
|
||||
<Button onClick={() => programmaticallyOpen('vertical-1')}>Open Vertical 1</Button>
|
||||
<Button onClick={() => setControlledKeys([])}>Close All</Button>
|
||||
<Button onClick={() => setControlledKeys(['1', '2', 'vertical-1'])}>Open All</Button>
|
||||
</Space>
|
||||
|
||||
<Collapse
|
||||
activeKey={controlledKeys}
|
||||
onChange={handleControlledChange}
|
||||
expandIconPosition="right"
|
||||
>
|
||||
<CollapseItem header="复合能力课 - 单元 1" name="1">
|
||||
<div>课程内容 1</div>
|
||||
</CollapseItem>
|
||||
<CollapseItem header="复合能力课 - 单元 2" name="2">
|
||||
<div>课程内容 2</div>
|
||||
</CollapseItem>
|
||||
<CollapseItem header="垂直能力课 - 单元 1" name="vertical-1">
|
||||
<div>垂直课程内容</div>
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
|
||||
<hr style={{ margin: '40px 0' }} />
|
||||
|
||||
<h2>Uncontrolled Mode Test (for comparison)</h2>
|
||||
<Collapse
|
||||
defaultActiveKey={['1']}
|
||||
onChange={(keys) => {
|
||||
console.log('=== Uncontrolled Mode ===');
|
||||
console.log('onChange keys:', keys);
|
||||
console.log('keys type:', typeof keys);
|
||||
console.log('Is Array:', Array.isArray(keys));
|
||||
}}
|
||||
expandIconPosition="right"
|
||||
>
|
||||
<CollapseItem header="Uncontrolled - Panel 1" name="u1">
|
||||
<div>Content 1</div>
|
||||
</CollapseItem>
|
||||
<CollapseItem header="Uncontrolled - Panel 2" name="u2">
|
||||
<div>Content 2</div>
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
88
test-collapse-controlled.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Controlled Collapse</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@arco-design/web-react@2.60.0/dist/css/arco.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/@arco-design/web-react@2.60.0/dist/arco.min.js"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect } = React;
|
||||
const { Collapse, Button } = arco;
|
||||
const CollapseItem = Collapse.Item;
|
||||
|
||||
function App() {
|
||||
const [activeKeys, setActiveKeys] = useState([]);
|
||||
|
||||
const handleChange = (keys, extra) => {
|
||||
console.log('onChange received:', keys, 'type:', typeof keys, 'extra:', extra);
|
||||
|
||||
// Log the current state
|
||||
console.log('Current activeKeys before update:', activeKeys);
|
||||
|
||||
// Arco Design Collapse in controlled mode always returns an array
|
||||
// But we need to handle it properly
|
||||
setActiveKeys(keys || []);
|
||||
|
||||
console.log('New activeKeys to be set:', keys);
|
||||
};
|
||||
|
||||
const programmaticallyOpen = (key) => {
|
||||
console.log('Programmatically opening:', key);
|
||||
setActiveKeys(prev => {
|
||||
if (!prev.includes(key)) {
|
||||
return [...prev, key];
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h2>Test Controlled Collapse</h2>
|
||||
<p>Active Keys: {JSON.stringify(activeKeys)}</p>
|
||||
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<Button onClick={() => programmaticallyOpen('1')} style={{ marginRight: 10 }}>
|
||||
Open Panel 1
|
||||
</Button>
|
||||
<Button onClick={() => programmaticallyOpen('2')} style={{ marginRight: 10 }}>
|
||||
Open Panel 2
|
||||
</Button>
|
||||
<Button onClick={() => programmaticallyOpen('vertical-1')} style={{ marginRight: 10 }}>
|
||||
Open Panel 3
|
||||
</Button>
|
||||
<Button onClick={() => setActiveKeys([])}>
|
||||
Close All
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Collapse
|
||||
activeKey={activeKeys}
|
||||
onChange={handleChange}
|
||||
expandIconPosition="right"
|
||||
>
|
||||
<CollapseItem header="Panel 1" name="1">
|
||||
Content 1
|
||||
</CollapseItem>
|
||||
<CollapseItem header="Panel 2" name="2">
|
||||
Content 2
|
||||
</CollapseItem>
|
||||
<CollapseItem header="Panel 3 (vertical)" name="vertical-1">
|
||||
Content 3
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
54
test-collapse.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Collapse</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@arco-design/web-react@2.60.0/dist/css/arco.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script src="https://unpkg.com/@arco-design/web-react@2.60.0/dist/arco.min.js"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useState } = React;
|
||||
const { Collapse } = arco;
|
||||
const CollapseItem = Collapse.Item;
|
||||
|
||||
function App() {
|
||||
const [activeKeys, setActiveKeys] = useState([]);
|
||||
|
||||
const handleChange = (keys) => {
|
||||
console.log('onChange:', keys);
|
||||
setActiveKeys(keys);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h2>Test Collapse with activeKey and onChange</h2>
|
||||
<p>Active Keys: {JSON.stringify(activeKeys)}</p>
|
||||
|
||||
<Collapse
|
||||
activeKey={activeKeys}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<CollapseItem header="Panel 1" name="1">
|
||||
Content 1
|
||||
</CollapseItem>
|
||||
<CollapseItem header="Panel 2" name="2">
|
||||
Content 2
|
||||
</CollapseItem>
|
||||
<CollapseItem header="Panel 3" name="vertical-1">
|
||||
Content 3
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||