Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
1f60884703
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface CollapsibleContentProps {
|
||||||
|
content: string;
|
||||||
|
maxHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollapsibleContent: React.FC<CollapsibleContentProps> = ({
|
||||||
|
content,
|
||||||
|
maxHeight = 150,
|
||||||
|
}) => {
|
||||||
|
const contentWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
|
// Determine if content needs to be collapsed
|
||||||
|
const shouldCollapse = contentWrapperRef.current
|
||||||
|
? contentWrapperRef.current.scrollHeight > maxHeight
|
||||||
|
: false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
ref={contentWrapperRef}
|
||||||
|
className={`duration-300 ${
|
||||||
|
shouldCollapse && !isExpanded
|
||||||
|
? `max-h-[${maxHeight}px] overflow-hidden relative`
|
||||||
|
: ""
|
||||||
|
}`}>
|
||||||
|
<div
|
||||||
|
className="ql-editor p-0 space-y-1 leading-relaxed"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: content || "",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Gradient overlay */}
|
||||||
|
{shouldCollapse && !isExpanded && (
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-white to-transparent" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Expand/Collapse button */}
|
||||||
|
{shouldCollapse && (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="mt-2 text-blue-500 hover:text-blue-700">
|
||||||
|
{isExpanded ? "收起" : "展开"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollapsibleContent;
|
|
@ -1,10 +1,11 @@
|
||||||
// components/CourseDetailDisplayArea.tsx
|
// components/CourseDetailDisplayArea.tsx
|
||||||
import { motion, useScroll, useTransform } from "framer-motion";
|
import { motion, useScroll, useTransform } from "framer-motion";
|
||||||
import React, { useContext } from "react";
|
import React, { useContext, useRef, useState } from "react";
|
||||||
import { VideoPlayer } from "@web/src/components/presentation/video-player/VideoPlayer";
|
import { VideoPlayer } from "@web/src/components/presentation/video-player/VideoPlayer";
|
||||||
import { CourseDetailDescription } from "./CourseDetailDescription/CourseDetailDescription";
|
import { CourseDetailDescription } from "./CourseDetailDescription/CourseDetailDescription";
|
||||||
import { Course, PostType } from "@nice/common";
|
import { Course, LectureType, PostType } from "@nice/common";
|
||||||
import { CourseDetailContext } from "./CourseDetailContext";
|
import { CourseDetailContext } from "./CourseDetailContext";
|
||||||
|
import CollapsibleContent from "@web/src/components/common/container/CollapsibleContent";
|
||||||
|
|
||||||
interface CourseDetailDisplayAreaProps {
|
interface CourseDetailDisplayAreaProps {
|
||||||
// course: Course;
|
// course: Course;
|
||||||
|
@ -21,23 +22,34 @@ export const CourseDetailDisplayArea: React.FC<
|
||||||
const { scrollY } = useScroll();
|
const { scrollY } = useScroll();
|
||||||
const videoScale = useTransform(scrollY, [0, 200], [1, 0.8]);
|
const videoScale = useTransform(scrollY, [0, 200], [1, 0.8]);
|
||||||
const videoOpacity = useTransform(scrollY, [0, 200], [1, 0.8]);
|
const videoOpacity = useTransform(scrollY, [0, 200], [1, 0.8]);
|
||||||
|
const contentWrapperRef = useRef(null);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [shouldCollapse, setShouldCollapse] = useState(false);
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* 固定的视频区域 */}
|
{/* 固定的视频区域 */}
|
||||||
{/* 移除 sticky 定位,让视频区域随页面滚动 */}
|
{/* 移除 sticky 定位,让视频区域随页面滚动 */}
|
||||||
<motion.div
|
{lecture?.meta?.type === LectureType.VIDEO && (
|
||||||
style={{
|
<motion.div
|
||||||
opacity: videoOpacity,
|
style={{
|
||||||
}}
|
opacity: videoOpacity,
|
||||||
className="w-full bg-black">
|
}}
|
||||||
{lecture.type === PostType.LECTURE && (
|
className="w-full bg-black">
|
||||||
<div className=" w-full ">
|
<div className=" w-full ">
|
||||||
<VideoPlayer src={videoSrc} poster={videoPoster} />
|
<VideoPlayer src={videoSrc} poster={videoPoster} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
</motion.div>
|
||||||
</motion.div>
|
)}
|
||||||
|
{lecture?.meta?.type === LectureType.ARTICLE && (
|
||||||
|
<div className="flex justify-center w-full my-2">
|
||||||
|
<div className="w-4/5 bg-white shadow-md rounded-lg border border-gray-200 p-6 ">
|
||||||
|
<CollapsibleContent
|
||||||
|
content={lecture?.content || ""}
|
||||||
|
maxHeight={150} // Optional, defaults to 150
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* 课程内容区域 */}
|
{/* 课程内容区域 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
|
|
@ -51,7 +51,6 @@ export const CourseDetailHeader = () => {
|
||||||
123
|
123
|
||||||
</Button>
|
</Button>
|
||||||
<nav className="flex items-center space-x-4">
|
<nav className="flex items-center space-x-4">
|
||||||
{/* 添加你的导航项目 */}
|
|
||||||
<button className="px-4 py-2 rounded-full bg-blue-500 text-white hover:bg-blue-600 transition-colors">
|
<button className="px-4 py-2 rounded-full bg-blue-500 text-white hover:bg-blue-600 transition-colors">
|
||||||
开始学习
|
开始学习
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -83,7 +83,7 @@ export const CourseSyllabus: React.FC<CourseSyllabusProps> = ({
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{sections.map((section) => (
|
{sections.map((section, index) => (
|
||||||
<SectionItem
|
<SectionItem
|
||||||
key={section.id}
|
key={section.id}
|
||||||
ref={(el) =>
|
ref={(el) =>
|
||||||
|
@ -91,6 +91,7 @@ export const CourseSyllabus: React.FC<CourseSyllabusProps> = ({
|
||||||
section.id
|
section.id
|
||||||
] = el)
|
] = el)
|
||||||
}
|
}
|
||||||
|
index={index + 1}
|
||||||
section={section}
|
section={section}
|
||||||
isExpanded={expandedSections.includes(
|
isExpanded={expandedSections.includes(
|
||||||
section.id
|
section.id
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { LectureItem } from "./LectureItem";
|
||||||
// components/CourseSyllabus/SectionItem.tsx
|
// components/CourseSyllabus/SectionItem.tsx
|
||||||
interface SectionItemProps {
|
interface SectionItemProps {
|
||||||
section: SectionDto;
|
section: SectionDto;
|
||||||
|
index?: number;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
onToggle: (sectionId: string) => void;
|
onToggle: (sectionId: string) => void;
|
||||||
onLectureClick: (lectureId: string) => void;
|
onLectureClick: (lectureId: string) => void;
|
||||||
|
@ -14,7 +15,7 @@ interface SectionItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SectionItem = React.forwardRef<HTMLDivElement, SectionItemProps>(
|
export const SectionItem = React.forwardRef<HTMLDivElement, SectionItemProps>(
|
||||||
({ section, isExpanded, onToggle, onLectureClick }, ref) => (
|
({ section, index, isExpanded, onToggle, onLectureClick }, ref) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
@ -26,9 +27,9 @@ export const SectionItem = React.forwardRef<HTMLDivElement, SectionItemProps>(
|
||||||
onClick={() => onToggle(section.id)}>
|
onClick={() => onToggle(section.id)}>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-lg font-medium text-gray-700">
|
<span className="text-lg font-medium text-gray-700">
|
||||||
第{Math.floor(section.order)}章
|
第{index}章
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div className="flex flex-col items-start">
|
||||||
<h3 className="text-left font-medium text-gray-900">
|
<h3 className="text-left font-medium text-gray-900">
|
||||||
{section.title}
|
{section.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
@ -43,6 +43,7 @@ export type PostDto = Post & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LectureMeta = {
|
export type LectureMeta = {
|
||||||
|
type?: string;
|
||||||
videoUrl?: string;
|
videoUrl?: string;
|
||||||
videoThumbnail?: string;
|
videoThumbnail?: string;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue