Files
ALL-teach_sys/frontend_环保/src/components/PublicCourseList/index.jsx
KQL 38350dca36 更新12个教务系统并优化项目大小
主要更新:
- 更新所有12个产业的教务系统数据和功能
- 删除所有 node_modules 文件夹(节省3.7GB)
- 删除所有 .yoyo 缓存文件夹(节省1.2GB)
- 删除所有 dist 构建文件(节省55MB)

项目优化:
- 项目大小从 8.1GB 减少到 3.2GB(节省60%空间)
- 保留完整的源代码和配置文件
- .gitignore 已配置,防止再次提交大文件

启动脚本:
- start-industry.sh/bat/ps1 脚本会自动检测并安装依赖
- 首次启动时自动运行 npm install
- 支持单个或批量启动产业系统

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 14:36:25 +08:00

355 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
import "./index.css";
// 单元海报数据将从服务器返回的数据中获取
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
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({
'ai': true, // 终生学习系统
'public': true, // 企业高管公开课
'marketing': true // 营销能力课
});
useEffect(() => {
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 {
const res = await getPublicCourseLiveList();
if (res.success) {
const courseList = res.data || [];
setCourseLiveList(courseList);
// 不设置默认选中,保持黑屏状态
}
} catch (error) {
console.error("Failed to fetch course list:", 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
className="course-list"
bordered={false}
expandIconPosition="right"
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 index = prevKeys.indexOf(keyStr);
if (index > -1) {
// 如果已展开,则收起
console.log('Closing panel:', keyStr);
return [];
} else {
// 如果已收起,则展开(并自动收起其他面板)
console.log('Opening panel:', keyStr, 'and closing others');
return [keyStr];
}
});
} else if (Array.isArray(keys)) {
// 直接设置新的展开状态
setActiveKeys(keys);
} else {
// 处理 undefined/null 的情况
setActiveKeys([]);
}
}}
>
{courseLiveList.map((unit, index) => {
// 判断当前单元属于哪个分类
const isAIUnit = [
"AI 入门与工具环境",
"RAG 与检索增强",
"AI 自动化与任务编排",
"AI 项目开发与前端交互",
"AI 大模型与核心原理",
"AI 行业应用与综合实战"
].includes(unit.unitName);
const isPublicUnit = [
"沟通与协作能力",
"问题解决与思维能力",
"职场基础与个人发展",
"职业发展与管理技能"
].includes(unit.unitName);
const isMarketingUnit = [
"必备营销技能",
"自我营销课"
].includes(unit.unitName);
// 判断是否需要显示分割线
// 终生学习系统第一个AI单元前显示
const showAIDivider = isAIUnit && index === 0;
// 企业高管公开课:第一个企业高管单元前显示(前一个不是企业高管单元)
const showPublicDivider = isPublicUnit && index > 0 &&
!["沟通与协作能力", "问题解决与思维能力", "职场基础与个人发展", "职业发展与管理技能"]
.includes(courseLiveList[index - 1]?.unitName);
// 营销能力课:第一个营销单元前显示(前一个不是营销单元)
const showMarketingDivider = isMarketingUnit && index > 0 &&
!["必备营销技能", "自我营销课"]
.includes(courseLiveList[index - 1]?.unitName);
return (
<div key={unit.unitId}>
{/* 终生学习系统分割线 */}
{showAIDivider && (
<div
className="course-divider clickable"
onClick={() => setCategoryExpanded(prev => ({ ...prev, ai: !prev.ai }))}
>
<span className="divider-line"></span>
<span className="divider-text">
终生学习系统
</span>
<span className="divider-line"></span>
</div>
)}
{/* 企业高管公开课分割线 */}
{showPublicDivider && (
<div
className="course-divider clickable"
onClick={() => setCategoryExpanded(prev => ({ ...prev, public: !prev.public }))}
>
<span className="divider-line"></span>
<span className="divider-text">
企业高管公开课
</span>
<span className="divider-line"></span>
</div>
)}
{/* 营销能力课分割线 */}
{showMarketingDivider && (
<div
className="course-divider clickable"
onClick={() => setCategoryExpanded(prev => ({ ...prev, marketing: !prev.marketing }))}
>
<span className="divider-line"></span>
<span className="divider-text">
营销能力课
</span>
<span className="divider-line"></span>
</div>
)}
{/* 根据分类的展开状态决定是否显示单元 */}
<div
className={`course-category-wrapper ${
(isAIUnit && !categoryExpanded.ai) ||
(isPublicUnit && !categoryExpanded.public) ||
(isMarketingUnit && !categoryExpanded.marketing)
? 'collapsed' : 'expanded'
}`}
>
<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,
unitPoster: unit.unitPoster || "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuW7gMz6sRee.jpg"
});
}}
style={{ cursor: 'pointer' }}
>
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word'
}}>
{course.courseName}
</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span className="course-date">{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
</div>
</div>
);
})}
</Collapse>
</div>
</div>
);
});
export default PublicCourseList;