From 94610142afeb0a7eda678d16f3d83d90fad68e1e Mon Sep 17 00:00:00 2001 From: KQL Date: Sat, 6 Sep 2025 16:43:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E5=85=B1=E8=AF=BE=E7=9B=B4=E6=92=AD?= =?UTF-8?q?=E9=97=B4=E9=A1=B5=E9=9D=A2=E6=94=B9=E9=80=A0=EF=BC=9A=E5=B0=86?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E7=BA=AA=E8=A6=81=E6=9B=BF=E6=8D=A2=E4=B8=BA?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能更新: - 创建了PublicCourseList组件,与课程直播间保持一致的课程列表样式 - 实现了公共课程数据结构,包含三个单元:AI课、企业高管公开课、营销能力课 - 添加了generatePublicCourseLiveList函数,自动将课程分类到对应单元 - 基于关键词智能分类课程(AI/AIGC、企业/管理、营销/运营等) - 垂直方向课程保留在原未分类单元中,不显示在公共课列表 UI优化: - 课程列表支持折叠展开,显示单元完成进度 - 时间轴样式显示课程进度状态 - 支持课程选择联动视频播放器 - 响应式布局,与课程直播间保持一致的交互体验 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/PublicCourseList/index.css | 167 ++++++++++++++++++++++ src/components/PublicCourseList/index.jsx | 156 ++++++++++++++++++++ src/data/mockData.js | 77 ++++++++++ src/pages/PublicCourses/index.css | 11 +- src/pages/PublicCourses/index.jsx | 19 ++- src/services/courseLive.js | 9 ++ 6 files changed, 432 insertions(+), 7 deletions(-) create mode 100644 src/components/PublicCourseList/index.css create mode 100644 src/components/PublicCourseList/index.jsx diff --git a/src/components/PublicCourseList/index.css b/src/components/PublicCourseList/index.css new file mode 100644 index 0000000..73eda9e --- /dev/null +++ b/src/components/PublicCourseList/index.css @@ -0,0 +1,167 @@ +.public-course-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: #fff; + border-radius: 8px; + overflow: hidden; +} + +.course-list-header { + padding: 16px 20px; + border-bottom: 1px solid #e5e6eb; + background: #fafafa; +} + +.course-list-header h3 { + margin: 0; + font-size: 16px; + font-weight: 500; + color: #1d2129; +} + +.course-list-collapse { + flex: 1; + overflow-y: auto; + padding: 12px; +} + +.course-list-collapse .arco-collapse-item { + margin-bottom: 8px; + border: 1px solid #e5e6eb; + border-radius: 4px; + overflow: hidden; +} + +.course-list-collapse .arco-collapse-item-header { + background: #f7f8fa; + padding: 12px 16px; +} + +.unit-header { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.unit-name { + font-size: 14px; + font-weight: 500; + color: #1d2129; +} + +.unit-course-count { + font-size: 12px; + color: #86909c; + margin-right: 20px; +} + +.course-timeline { + padding: 16px 0; +} + +.timeline-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #e5e6eb; +} + +.timeline-dot.completed { + background: #00b42a; +} + +.timeline-dot.current { + background: #ff7d00; + box-shadow: 0 0 0 4px rgba(255, 125, 0, 0.2); +} + +.timeline-dot.upcoming { + background: #165dff; +} + +.course-item { + padding: 12px; + background: #f7f8fa; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s; + display: flex; + justify-content: space-between; + align-items: center; +} + +.course-item:hover { + background: #e8f3ff; + transform: translateX(4px); +} + +.course-item.selected { + background: #e8f3ff; + border-left: 3px solid #165dff; +} + +.course-item.completed { + opacity: 0.7; +} + +.course-info { + flex: 1; +} + +.course-name { + font-size: 14px; + font-weight: 500; + color: #1d2129; + margin-bottom: 4px; +} + +.course-meta { + display: flex; + gap: 16px; + font-size: 12px; + color: #86909c; +} + +.course-status { + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + white-space: nowrap; +} + +.course-status.completed { + background: #e8ffea; + color: #00b42a; +} + +.course-status.current { + background: #fff7e8; + color: #ff7d00; +} + +.course-status.upcoming { + background: #e8f3ff; + color: #165dff; +} + +/* 自定义滚动条 */ +.course-list-collapse::-webkit-scrollbar { + width: 6px; +} + +.course-list-collapse::-webkit-scrollbar-track { + background: #f2f3f5; + border-radius: 3px; +} + +.course-list-collapse::-webkit-scrollbar-thumb { + background: #c9cdd4; + border-radius: 3px; +} + +.course-list-collapse::-webkit-scrollbar-thumb:hover { + background: #86909c; +} \ No newline at end of file diff --git a/src/components/PublicCourseList/index.jsx b/src/components/PublicCourseList/index.jsx new file mode 100644 index 0000000..bd91c58 --- /dev/null +++ b/src/components/PublicCourseList/index.jsx @@ -0,0 +1,156 @@ +import { useState, useEffect } from "react"; +import { Collapse, Timeline, Spin } from "@arco-design/web-react"; +import { getPublicCourseLiveList } from "@/services/courseLive"; +import "./index.css"; + +const TimelineItem = Timeline.Item; +const CollapseItem = Collapse.Item; + +const PublicCourseList = ({ className = "", onCourseClick }) => { + const [courseLiveList, setCourseLiveList] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedCourseId, setSelectedCourseId] = useState(null); + + useEffect(() => { + fetchCourseList(); + }, []); + + const fetchCourseList = async () => { + setLoading(true); + try { + const res = await getPublicCourseLiveList(); + if (res.success) { + const courseList = res.data || []; + setCourseLiveList(courseList); + + // 设置默认选中今天的课程(如果有) + const todayStr = new Date().toISOString().split('T')[0]; + let foundTodayCourse = false; + + for (const unit of courseList) { + const todayCourse = unit.courses.find(c => c.date === todayStr); + if (todayCourse) { + setSelectedCourseId(todayCourse.courseId); + // 触发课程选择事件 + if (onCourseClick) { + onCourseClick({ + ...todayCourse, + unitName: unit.unitName + }); + } + foundTodayCourse = true; + break; + } + } + + // 如果没有今天的课程,选中第一个current或upcoming的课程 + if (!foundTodayCourse) { + for (const unit of courseList) { + const activeCourse = unit.courses.find(c => c.current || c.upcoming); + if (activeCourse) { + setSelectedCourseId(activeCourse.courseId); + if (onCourseClick) { + onCourseClick({ + ...activeCourse, + unitName: unit.unitName + }); + } + break; + } + } + } + } + } catch (error) { + console.error("Failed to fetch course list:", error); + } finally { + setLoading(false); + } + }; + + const handleCourseClick = (course, unitName) => { + setSelectedCourseId(course.courseId); + if (onCourseClick) { + onCourseClick({ + ...course, + unitName: unitName + }); + } + }; + + const renderCourseStatus = (course) => { + if (course.completed) { + return 已完成; + } else if (course.current) { + return 正在进行; + } else if (course.upcoming) { + return 即将开始; + } + return null; + }; + + return ( +
+
+

公共课程列表

+
+ + + unit.unitId)} + className="course-list-collapse" + > + {courseLiveList.map((unit) => ( + + {unit.unitName} + + {unit.courses.filter(c => c.completed).length}/{unit.courses.length} 已完成 + +
+ } + name={unit.unitId} + > + + {unit.courses.map((course) => ( + + } + label={course.date} + > +
handleCourseClick(course, unit.unitName)} + > +
+
{course.courseName}
+
+ 讲师:{course.teacherName} + {course.time && 时间:{course.time}} +
+
+ {renderCourseStatus(course)} +
+
+ ))} +
+ + ))} + + + + ); +}; + +export default PublicCourseList; \ No newline at end of file diff --git a/src/data/mockData.js b/src/data/mockData.js index 523d6eb..3b9fa44 100644 --- a/src/data/mockData.js +++ b/src/data/mockData.js @@ -148,6 +148,80 @@ const generateCourseLiveListFromCalendar = (calendarEvents) => { }); }; +// 生成公共课程列表:从课程直播列表中提取公共课程 +const generatePublicCourseLiveList = (courseLiveList) => { + // 定义公共课单元 + const publicUnits = { + "AI课": { + unitId: "public-ai", + unitName: "AI课", + courses: [] + }, + "企业高管公开课": { + unitId: "public-executive", + unitName: "企业高管公开课", + courses: [] + }, + "营销能力课": { + unitId: "public-marketing", + unitName: "营销能力课", + courses: [] + } + }; + + // 定义关键词映射到单元 + const keywordToUnit = { + "AI": "AI课", + "AIGC": "AI课", + "人工智能": "AI课", + "ChatGPT": "AI课", + "Midjourney": "AI课", + "企业": "企业高管公开课", + "高管": "企业高管公开课", + "管理": "企业高管公开课", + "领导": "企业高管公开课", + "战略": "企业高管公开课", + "营销": "营销能力课", + "销售": "营销能力课", + "品牌": "营销能力课", + "推广": "营销能力课", + "市场": "营销能力课", + "运营": "营销能力课", + "新媒体": "营销能力课" + }; + + // 遍历所有课程,分类到相应单元 + courseLiveList.forEach(unit => { + unit.courses.forEach(course => { + let assigned = false; + + // 检查课程名称是否包含关键词 + for (const [keyword, unitName] of Object.entries(keywordToUnit)) { + if (course.courseName.includes(keyword) || unit.unitName.includes(keyword)) { + publicUnits[unitName].courses.push({ + ...course, + originalUnit: unit.unitName + }); + assigned = true; + break; + } + } + + // 如果没有匹配到任何公共课单元,检查是否是公开课类型 + if (!assigned && (unit.unitName.includes("公开课") || unit.unitName.includes("公共"))) { + // 默认归类到企业高管公开课 + publicUnits["企业高管公开课"].courses.push({ + ...course, + originalUnit: unit.unitName + }); + } + }); + }); + + // 转换为数组格式并过滤空单元 + return Object.values(publicUnits).filter(unit => unit.courses.length > 0); +}; + // 更新导师课程信息:根据日历事件更新导师的课程列表 const updateTeacherCourses = (teacherData, calendarEvents) => { // 深拷贝教师数据 @@ -4228,6 +4302,9 @@ mockData.profileOverview = { // 课程直播间的课程列表数据(从CSV生成) mockData.courseLiveList = generateCourseLiveListFromCalendar(transformCalendarCourses(calendarCoursesData)); +// 生成公共课程列表 +mockData.publicCourseLiveList = generatePublicCourseLiveList(mockData.courseLiveList); + // 在courseLiveList定义后,更新dashboardStatistics的课程和任务数据 const dashboardCourseData = generateDashboardCourses(mockData.courseLiveList); diff --git a/src/pages/PublicCourses/index.css b/src/pages/PublicCourses/index.css index 0121044..80bb0ac 100644 --- a/src/pages/PublicCourses/index.css +++ b/src/pages/PublicCourses/index.css @@ -1,9 +1,12 @@ .public-courses-page { width: 100%; height: 100%; - box-sizing: border-box; padding: 20px; - position: relative; - display: flex; - justify-content: space-between; +} + +.public-courses-content { + width: 100%; + height: 100%; + display: flex; + gap: 20px; } diff --git a/src/pages/PublicCourses/index.jsx b/src/pages/PublicCourses/index.jsx index c6340a4..3547ccd 100644 --- a/src/pages/PublicCourses/index.jsx +++ b/src/pages/PublicCourses/index.jsx @@ -1,13 +1,26 @@ +import { useState } from "react"; import CoursesVideoPlayer from "@/components/CoursesVideoPlayer"; -import LiveSummary from "@/components/LiveSummary"; +import PublicCourseList from "@/components/PublicCourseList"; import { mockData } from "@/data/mockData"; import "./index.css"; const PublicCourses = () => { + const [selectedCourse, setSelectedCourse] = useState(null); + + const handleCourseClick = (course) => { + setSelectedCourse(course); + }; + return (
- - +
+ + +
); }; diff --git a/src/services/courseLive.js b/src/services/courseLive.js index db5e5ca..ec96162 100644 --- a/src/services/courseLive.js +++ b/src/services/courseLive.js @@ -1,5 +1,14 @@ import { mockData } from "@/data/mockData"; +// 获取公共课程直播列表 +export async function getPublicCourseLiveList() { + // 模拟异步请求 + return Promise.resolve({ + success: true, + data: mockData.publicCourseLiveList || [], + }); +} + // 获取课程直播列表 export async function getCourseLiveList() { // 模拟异步请求