feat: 优化课程列表功能和班级排名样式

- 修复课程列表单元展开/收起功能
- 优化日历页面跳转到课程列表的延迟时间
- 恢复复合技能课和垂直技能课的分割线及收缩功能
- 添加班级排名第一二三名的特殊样式图标
- 修复Collapse组件onChange事件处理
- 优化课程自动选中和滚动定位功能

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-11 15:51:09 +08:00
parent 561d5c286d
commit c969677ef6
18 changed files with 599 additions and 47 deletions

View File

@@ -56,7 +56,8 @@
"Bash(xxd:*)",
"Bash(kill:*)",
"Bash(find:*)",
"Bash(pgrep:*)"
"Bash(pgrep:*)",
"Bash(npm start)"
],
"deny": [],
"ask": []

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -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" />

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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':

View File

@@ -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>
);

View File

@@ -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
View 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>

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