@@ -76,7 +67,7 @@ const HeroSection = () => {
dots={{
className: "carousel-dots !bottom-32 !z-20",
}}>
- {Array.isArray(slides)?
+ {Array.isArray(slides) ?
(slides.map((item, index) => (
-
- {platformStats.map((stat, index) => (
-
-
- {stat.icon}
-
-
- {stat.value}
-
-
- {stat.label}
-
+ {
+ countStatistics > 1 && (
+
+
+ {platformStats.map((stat, index) => {
+ return stat.value
+ ? (
+
+ {stat.icon}
+
+
+ {stat.value}
+
+
+ {stat.label}
+
+
+ ) : null
+ })}
- ))}
-
-
+
+ )
+ }
);
};
diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx
index 47503b5..1461b4e 100755
--- a/apps/web/src/app/main/layout/MainHeader.tsx
+++ b/apps/web/src/app/main/layout/MainHeader.tsx
@@ -2,7 +2,7 @@ import { useContext, useState } from "react";
import { Input, Layout, Avatar, Button, Dropdown } from "antd";
import { EditFilled, SearchOutlined, UserOutlined } from "@ant-design/icons";
import { useAuth } from "@web/src/providers/auth-provider";
-import { useNavigate, useSearchParams } from "react-router-dom";
+import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { UserMenu } from "./UserMenu/UserMenu";
import { NavigationMenu } from "./NavigationMenu";
import { useMainContext } from "./MainProvider";
@@ -10,6 +10,7 @@ const { Header } = Layout;
export function MainHeader() {
const { isAuthenticated, user } = useAuth();
+ const { id } = useParams();
const navigate = useNavigate();
const { searchValue, setSearchValue } = useMainContext();
return (
@@ -52,10 +53,15 @@ export function MainHeader() {
{isAuthenticated && (
<>
>
)}
diff --git a/apps/web/src/components/common/container/CollapsibleContent.tsx b/apps/web/src/components/common/container/CollapsibleContent.tsx
index 831bdad..9352183 100644
--- a/apps/web/src/components/common/container/CollapsibleContent.tsx
+++ b/apps/web/src/components/common/container/CollapsibleContent.tsx
@@ -28,9 +28,15 @@ const CollapsibleContent: React.FC
= ({
{/* 包装整个内容区域的容器 */}
{/* 内容区域 */}
diff --git a/apps/web/src/components/common/uploader/MultiAvatarUploader.tsx b/apps/web/src/components/common/uploader/MultiAvatarUploader.tsx
index 1ff09b5..16f389d 100644
--- a/apps/web/src/components/common/uploader/MultiAvatarUploader.tsx
+++ b/apps/web/src/components/common/uploader/MultiAvatarUploader.tsx
@@ -3,66 +3,81 @@ import { useEffect, useState } from "react";
import { Upload, Progress, Button, Image, Form } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import AvatarUploader from "./AvatarUploader";
-import { isEqual } from 'lodash';
+import { isEqual } from "lodash";
interface MultiAvatarUploaderProps {
- value?: string[];
- onChange?: (value: string[]) => void;
+ value?: string[];
+ onChange?: (value: string[]) => void;
}
export function MultiAvatarUploader({
- value,
- onChange,
+ value,
+ onChange,
}: MultiAvatarUploaderProps) {
- const [imageList, setImageList] = useState
(value || [])
- const [previewImage, setPreviewImage] = useState("");
- useEffect(() => {
- if (!isEqual(value, imageList)) {
- setImageList(value || []);
- }
- }, [value]);
- useEffect(() => {
- onChange?.(imageList)
- }, [imageList])
- return <>
-
- {imageList.map((image, index) => {
- return (
-
-
- setPreviewImage(visible ? image || "" : "")
- }} >
-
- }
- onClick={() => image && setImageList(imageList.filter((_, i) => i !== index))}
- style={{
- position: "absolute", // 绝对定位
- top: "0", // 顶部对齐
- right: "0", // 右侧对齐
- zIndex: 1, // 确保按钮在图片上方
- padding: "4px", // 调整按钮内边距
- backgroundColor: "rgba(255, 255, 255, 0.2)", // 半透明背景
- borderRadius: "50%", // 圆形按钮
- }}
-
- />
-
- )
- })}
-
-
-
{
- console.log(value);
- setImageList([...imageList, value])
- }}>
-
-
- >;
+ const [imageList, setImageList] = useState(value || []);
+ const [previewImage, setPreviewImage] = useState("");
+ useEffect(() => {
+ if (!isEqual(value, imageList)) {
+ setImageList(value || []);
+ }
+ }, [value]);
+ useEffect(() => {
+ onChange?.(imageList);
+ }, [imageList]);
+ return (
+ <>
+
+ {(imageList || [])?.map((image, index) => {
+ return (
+
+
+ setPreviewImage(
+ visible ? image || "" : ""
+ ),
+ }}>
+ }
+ onClick={() =>
+ image &&
+ setImageList(
+ imageList.filter((_, i) => i !== index)
+ )
+ }
+ style={{
+ position: "absolute", // 绝对定位
+ top: "0", // 顶部对齐
+ right: "0", // 右侧对齐
+ zIndex: 1, // 确保按钮在图片上方
+ padding: "4px", // 调整按钮内边距
+ backgroundColor: "rgba(255, 255, 255, 0.2)", // 半透明背景
+ borderRadius: "50%", // 圆形按钮
+ }}
+ />
+
+ );
+ })}
+
+
+
{
+ console.log(value);
+ setImageList([...imageList, value]);
+ }}>
+
+ >
+ );
}
-export default MultiAvatarUploader;
\ No newline at end of file
+export default MultiAvatarUploader;
diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx
index 8702d19..6787e13 100755
--- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx
+++ b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx
@@ -7,7 +7,7 @@ import {
} from "@nice/common";
import { useAuth } from "@web/src/providers/auth-provider";
import React, { createContext, ReactNode, useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
interface CourseDetailContextType {
editId?: string; // 添加 editId
@@ -33,6 +33,7 @@ export function CourseDetailProvider({
const navigate = useNavigate();
const { read } = useVisitor();
const { user } = useAuth();
+ const { lectureId } = useParams();
const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } =
(api.post as any).findFirst.useQuery(
{
@@ -47,7 +48,7 @@ export function CourseDetailProvider({
const [selectedLectureId, setSelectedLectureId] = useState<
string | undefined
- >(undefined);
+ >(lectureId || undefined);
const { data: lecture, isLoading: lectureIsLoading } = (
api.post as any
).findFirst.useQuery(
diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx b/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx
index feba41d..6573753 100755
--- a/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx
+++ b/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx
@@ -61,7 +61,6 @@ export const CourseDetailDescription: React.FC = () => {
expandable: true,
symbol: "展开",
onExpand: () => console.log("展开"),
- // collapseText: "收起",
}}>
{course?.content}
diff --git a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx
index 142abde..447ecda 100755
--- a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx
+++ b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx
@@ -51,7 +51,7 @@ export const CourseDetailDisplayArea: React.FC = () => {
diff --git a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx
index 7e708e1..acb8d70 100755
--- a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx
+++ b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx
@@ -7,7 +7,7 @@ import {
UserOutlined,
} from "@ant-design/icons";
import { useAuth } from "@web/src/providers/auth-provider";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu";
import { CourseDetailContext } from "../CourseDetailContext";
@@ -15,7 +15,8 @@ const { Header } = Layout;
export function CourseDetailHeader() {
const [searchValue, setSearchValue] = useState("");
- const { isAuthenticated, user } = useAuth();
+ const { id } = useParams();
+ const { isAuthenticated, user, hasSomePermissions } = useAuth();
const navigate = useNavigate();
const { course } = useContext(CourseDetailContext);
@@ -51,10 +52,15 @@ export function CourseDetailHeader() {
{isAuthenticated && (
<>
>
)}
diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx
index ed73745..f4276bf 100755
--- a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx
+++ b/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx
@@ -1,7 +1,7 @@
// components/CourseSyllabus/LectureItem.tsx
-import { Lecture, LectureType } from "@nice/common";
-import React from "react";
+import { Lecture, LectureType, LessonTypeLabel } from "@nice/common";
+import React, { useMemo } from "react";
import {
ClockCircleOutlined,
FileTextOutlined,
@@ -19,15 +19,24 @@ export const LectureItem: React.FC = ({
onClick,
}) => {
const { lectureId } = useParams();
+ const isReading = useMemo(() => {
+ return lecture?.id === lectureId;
+ }, [lectureId, lecture]);
return (
onClick(lecture.id)}>
- {lecture.type === LectureType.VIDEO && (
-
+ {lecture?.meta?.type === LectureType.VIDEO && (
+
+
+
{LessonTypeLabel[lecture?.meta?.type]}
+
)}
- {lecture.type === LectureType.ARTICLE && (
-
// 为文章类型添加图标
+ {lecture?.meta?.type === LectureType.ARTICLE && (
+
+ {" "}
+ {LessonTypeLabel[lecture?.meta?.type]}
+
)}
{lecture.title}
@@ -37,10 +46,6 @@ export const LectureItem: React.FC = ({
)}
- {/*
-
- {lecture.duration}分钟
-
*/}
);
};
diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx
index 6cd1627..3c0a4c5 100755
--- a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx
+++ b/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx
@@ -1,10 +1,9 @@
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { SectionDto } from "@nice/common";
import { AnimatePresence, motion } from "framer-motion";
-import React from "react";
+import React, { useMemo } from "react";
import { LectureItem } from "./LectureItem";
-
-// components/CourseSyllabus/SectionItem.tsx
+import { useParams } from "react-router-dom";
interface SectionItemProps {
section: SectionDto;
index?: number;
@@ -13,57 +12,68 @@ interface SectionItemProps {
onLectureClick: (lectureId: string) => void;
ref: React.RefObject;
}
-
export const SectionItem = React.forwardRef(
- ({ section, index, isExpanded, onToggle, onLectureClick }, ref) => (
-
-