Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
b9bbf5c55b
|
@ -5,16 +5,13 @@ import { useMemo } from "react";
|
||||||
import CourseCard from "./CourseCard";
|
import CourseCard from "./CourseCard";
|
||||||
|
|
||||||
export function CoursesContainer() {
|
export function CoursesContainer() {
|
||||||
const { searchValue, selectedTerms } = useMainContext();
|
const { selectedTerms, searchCondition } = useMainContext();
|
||||||
const termFilters = useMemo(() => {
|
const termFilters = useMemo(() => {
|
||||||
return Object.entries(selectedTerms)
|
return Object.entries(selectedTerms)
|
||||||
.filter(([, terms]) => terms.length > 0)
|
.filter(([, terms]) => terms.length > 0)
|
||||||
.map(([, terms]) => terms);
|
.map(([, terms]) => terms);
|
||||||
}, [selectedTerms]);
|
}, [selectedTerms]);
|
||||||
const searchCondition: Prisma.StringNullableFilter = {
|
|
||||||
contains: searchValue,
|
|
||||||
mode: "insensitive" as Prisma.QueryMode, // 使用类型断言
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PostList
|
<PostList
|
||||||
|
@ -32,18 +29,7 @@ export function CoursesContainer() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
OR: [
|
...searchCondition,
|
||||||
{ title: searchCondition },
|
|
||||||
{ subTitle: searchCondition },
|
|
||||||
{ content: searchCondition },
|
|
||||||
{
|
|
||||||
terms: {
|
|
||||||
some: {
|
|
||||||
name: searchCondition,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
cols={4}></PostList>
|
cols={4}></PostList>
|
||||||
|
|
|
@ -19,11 +19,11 @@ const CategorySection = () => {
|
||||||
taxonomy: {
|
taxonomy: {
|
||||||
slug: TaxonomySlug.CATEGORY,
|
slug: TaxonomySlug.CATEGORY,
|
||||||
},
|
},
|
||||||
parentId : null
|
parentId: null,
|
||||||
},
|
},
|
||||||
take: 8,
|
take: 8,
|
||||||
});
|
});
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleMouseEnter = useCallback((index: number) => {
|
const handleMouseEnter = useCallback((index: number) => {
|
||||||
setHoveredIndex(index);
|
setHoveredIndex(index);
|
||||||
|
@ -33,13 +33,13 @@ const CategorySection = () => {
|
||||||
setHoveredIndex(null);
|
setHoveredIndex(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleMouseClick = useCallback((categoryId:string) => {
|
const handleMouseClick = useCallback((categoryId: string) => {
|
||||||
setSelectedTerms({
|
setSelectedTerms({
|
||||||
[TaxonomySlug.CATEGORY] : [categoryId]
|
[TaxonomySlug.CATEGORY]: [categoryId],
|
||||||
})
|
});
|
||||||
navigate('/courses')
|
navigate("/courses");
|
||||||
window.scrollTo({top: 0,behavior: "smooth",})
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
},[]);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<section className="py-8 relative overflow-hidden">
|
<section className="py-8 relative overflow-hidden">
|
||||||
<div className="max-w-screen-2xl mx-auto px-4 relative">
|
<div className="max-w-screen-2xl mx-auto px-4 relative">
|
||||||
|
@ -57,7 +57,7 @@ const CategorySection = () => {
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Skeleton paragraph={{ rows: 4 }}></Skeleton>
|
<Skeleton paragraph={{ rows: 4 }}></Skeleton>
|
||||||
) : (
|
) : (
|
||||||
courseCategoriesData.map((category, index) => {
|
courseCategoriesData?.map((category, index) => {
|
||||||
const categoryColor = stringToColor(category.name);
|
const categoryColor = stringToColor(category.name);
|
||||||
const isHovered = hoveredIndex === index;
|
const isHovered = hoveredIndex === index;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { Input, Layout, Avatar, Button, Dropdown } from "antd";
|
import { Input, Layout, Avatar, Button, Dropdown } from "antd";
|
||||||
import { EditFilled, PlusOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
EditFilled,
|
||||||
|
PlusOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { UserMenu } from "./UserMenu/UserMenu";
|
import { UserMenu } from "./UserMenu/UserMenu";
|
||||||
import { NavigationMenu } from "./NavigationMenu";
|
import { NavigationMenu } from "./NavigationMenu";
|
||||||
import { useMainContext } from "./MainProvider";
|
import { useMainContext } from "./MainProvider";
|
||||||
|
import { Header } from "antd/es/layout/layout";
|
||||||
|
|
||||||
export function MainHeader() {
|
export function MainHeader() {
|
||||||
const { isAuthenticated, user } = useAuth();
|
const { isAuthenticated, user } = useAuth();
|
||||||
|
@ -12,11 +18,10 @@ export function MainHeader() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { searchValue, setSearchValue } = useMainContext();
|
const { searchValue, setSearchValue } = useMainContext();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="select-none flex items-center justify-between p-4 bg-white shadow-md border-b border-gray-100 fixed w-full z-30">
|
<div className="select-none flex items-center justify-center bg-white shadow-md border-b border-gray-100 fixed w-full z-30">
|
||||||
|
<div className="w-full max-w-screen-3xl px-4 md:px-6 mx-auto flex items-center justify-between h-full">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center space-x-8">
|
||||||
<div
|
<div
|
||||||
onClick={() => navigate("/")}
|
onClick={() => navigate("/")}
|
||||||
className="text-2xl font-bold bg-gradient-to-r from-primary-600 via-primary-500 to-primary-400 bg-clip-text text-transparent hover:scale-105 transition-transform cursor-pointer">
|
className="text-2xl font-bold bg-gradient-to-r from-primary-600 via-primary-500 to-primary-400 bg-clip-text text-transparent hover:scale-105 transition-transform cursor-pointer">
|
||||||
|
@ -24,6 +29,8 @@ export function MainHeader() {
|
||||||
</div>
|
</div>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=" flex justify-end gap-4 mr-2">
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size="large"
|
||||||
prefix={
|
prefix={
|
||||||
|
@ -35,9 +42,8 @@ export function MainHeader() {
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
onPressEnter={(e) => {
|
onPressEnter={(e) => {
|
||||||
if (
|
if (
|
||||||
!window.location.pathname.startsWith(
|
!window.location.pathname.startsWith("/courses/") &&
|
||||||
"/courses/"
|
!window.location.pathname.startsWith("my")
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
navigate(`/courses/`);
|
navigate(`/courses/`);
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
|
@ -63,15 +69,15 @@ export function MainHeader() {
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{
|
{isAuthenticated && (
|
||||||
isAuthenticated && <Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
||||||
window.location.href = "/path/editor";
|
window.location.href = "/path/editor";
|
||||||
}}
|
}}
|
||||||
|
icon={<PlusOutlined></PlusOutlined>}>
|
||||||
icon={<PlusOutlined></PlusOutlined>} >创建学习路径</Button>
|
创建学习路径
|
||||||
}
|
</Button>
|
||||||
|
)}
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
) : (
|
) : (
|
||||||
|
@ -84,5 +90,6 @@ export function MainHeader() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import React, { createContext, ReactNode, useContext, useState } from "react";
|
import { Prisma } from "packages/common/dist";
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
interface SelectedTerms {
|
interface SelectedTerms {
|
||||||
[key: string]: string[]; // 每个 slug 对应一个 string 数组
|
[key: string]: string[]; // 每个 slug 对应一个 string 数组
|
||||||
}
|
}
|
||||||
|
@ -8,6 +15,7 @@ interface MainContextType {
|
||||||
selectedTerms?: SelectedTerms;
|
selectedTerms?: SelectedTerms;
|
||||||
setSearchValue?: React.Dispatch<React.SetStateAction<string>>;
|
setSearchValue?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setSelectedTerms?: React.Dispatch<React.SetStateAction<SelectedTerms>>;
|
setSelectedTerms?: React.Dispatch<React.SetStateAction<SelectedTerms>>;
|
||||||
|
searchCondition?: Prisma.PostWhereInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MainContext = createContext<MainContextType | null>(null);
|
const MainContext = createContext<MainContextType | null>(null);
|
||||||
|
@ -18,6 +26,29 @@ interface MainProviderProps {
|
||||||
export function MainProvider({ children }: MainProviderProps) {
|
export function MainProvider({ children }: MainProviderProps) {
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [selectedTerms, setSelectedTerms] = useState<SelectedTerms>({}); // 初始化状态
|
const [selectedTerms, setSelectedTerms] = useState<SelectedTerms>({}); // 初始化状态
|
||||||
|
|
||||||
|
const searchCondition: Prisma.PostWhereInput = useMemo(() => {
|
||||||
|
const containTextCondition: Prisma.StringNullableFilter = {
|
||||||
|
contains: searchValue,
|
||||||
|
mode: "insensitive" as Prisma.QueryMode, // 使用类型断言
|
||||||
|
};
|
||||||
|
return searchValue
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ title: containTextCondition },
|
||||||
|
{ subTitle: containTextCondition },
|
||||||
|
{ content: containTextCondition },
|
||||||
|
{
|
||||||
|
terms: {
|
||||||
|
some: {
|
||||||
|
name: containTextCondition,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
}, [searchValue]);
|
||||||
return (
|
return (
|
||||||
<MainContext.Provider
|
<MainContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -25,6 +56,7 @@ export function MainProvider({ children }: MainProviderProps) {
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
selectedTerms,
|
selectedTerms,
|
||||||
setSelectedTerms,
|
setSelectedTerms,
|
||||||
|
searchCondition,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</MainContext.Provider>
|
</MainContext.Provider>
|
||||||
|
|
|
@ -90,14 +90,14 @@ export function UserMenu() {
|
||||||
icon: <UserOutlined className="text-lg" />,
|
icon: <UserOutlined className="text-lg" />,
|
||||||
label: "我创建的课程",
|
label: "我创建的课程",
|
||||||
action: () => {
|
action: () => {
|
||||||
navigate("/my/duty");
|
navigate("/my-duty");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <UserOutlined className="text-lg" />,
|
icon: <UserOutlined className="text-lg" />,
|
||||||
label: "我学习的课程",
|
label: "我学习的课程",
|
||||||
action: () => {
|
action: () => {
|
||||||
navigate("/my/learning");
|
navigate("/my-learning");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
canManageAnyStaff && {
|
canManageAnyStaff && {
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
import PostList from "@web/src/components/models/course/list/PostList";
|
import PostList from "@web/src/components/models/course/list/PostList";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
import { useMainContext } from "../layout/MainProvider";
|
||||||
import CourseCard from "../courses/components/CourseCard";
|
import CourseCard from "../courses/components/CourseCard";
|
||||||
|
|
||||||
export default function MyDutyPage() {
|
export default function MyDutyPage() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { searchCondition } = useMainContext();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<PostList
|
<PostList
|
||||||
renderItem={post=><CourseCard edit course={post}></CourseCard>}
|
renderItem={(post) => (
|
||||||
|
<CourseCard edit course={post}></CourseCard>
|
||||||
|
)}
|
||||||
params={{
|
params={{
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
where: {
|
where: {
|
||||||
authorId: user.id,
|
authorId: user.id,
|
||||||
|
...searchCondition,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
cols={4}></PostList>
|
cols={4}></PostList>
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import PostList from "@web/src/components/models/course/list/PostList";
|
import PostList from "@web/src/components/models/course/list/PostList";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
import { useMainContext } from "../layout/MainProvider";
|
||||||
import CourseCard from "../courses/components/CourseCard";
|
import CourseCard from "../courses/components/CourseCard";
|
||||||
|
|
||||||
export default function MyLearningPage() {
|
export default function MyLearningPage() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { searchCondition } = useMainContext();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<PostList
|
<PostList
|
||||||
renderItem={post => <CourseCard edit={false} course={post}></CourseCard>}
|
renderItem={(post) => (
|
||||||
|
<CourseCard edit={false} course={post}></CourseCard>
|
||||||
|
)}
|
||||||
params={{
|
params={{
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
where: {
|
where: {
|
||||||
|
@ -17,6 +21,7 @@ export default function MyLearningPage() {
|
||||||
id: user?.id,
|
id: user?.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...searchCondition,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
cols={4}></PostList>
|
cols={4}></PostList>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default function CourseDetailLayout() {
|
||||||
const [isSyllabusOpen, setIsSyllabusOpen] = useState(true);
|
const [isSyllabusOpen, setIsSyllabusOpen] = useState(true);
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<CourseDetailHeader />
|
{/* <CourseDetailHeader /> */}
|
||||||
|
|
||||||
{/* 添加 Header 组件 */}
|
{/* 添加 Header 组件 */}
|
||||||
{/* 主内容区域 */}
|
{/* 主内容区域 */}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { CourseDetailContext } from "./CourseDetailContext";
|
import { CourseDetailContext } from "./CourseDetailContext";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { BookOutlined, CalendarOutlined, EditTwoTone, EyeOutlined, ReloadOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
BookOutlined,
|
||||||
|
CalendarOutlined,
|
||||||
|
EditTwoTone,
|
||||||
|
EyeOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import CourseOperationBtns from "./JoinLearingButton";
|
||||||
|
|
||||||
export default function CourseDetailTitle() {
|
export default function CourseDetailTitle() {
|
||||||
const {
|
const {
|
||||||
|
@ -19,14 +26,14 @@ export default function CourseDetailTitle() {
|
||||||
<div className="flex justify-start w-full text-2xl font-bold">
|
<div className="flex justify-start w-full text-2xl font-bold">
|
||||||
{course?.title}
|
{course?.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-600 flex w-full justify-start gap-5">
|
<div className="text-gray-600 flex w-full justify-start items-center gap-5">
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<CalendarOutlined></CalendarOutlined>
|
<CalendarOutlined></CalendarOutlined>
|
||||||
{"创建于:"}
|
{"创建于:"}
|
||||||
{dayjs(course?.createdAt).format("YYYY年M月D日")}
|
{dayjs(course?.createdAt).format("YYYY年M月D日")}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<ReloadOutlined></ReloadOutlined>
|
<ReloadOutlined spin></ReloadOutlined>
|
||||||
{"更新于:"}
|
{"更新于:"}
|
||||||
{dayjs(course?.updatedAt).format("YYYY年M月D日")}
|
{dayjs(course?.updatedAt).format("YYYY年M月D日")}
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,19 +45,7 @@ export default function CourseDetailTitle() {
|
||||||
<BookOutlined />
|
<BookOutlined />
|
||||||
<div>{`学习人数${course?.studentIds?.length || 0}`}</div>
|
<div>{`学习人数${course?.studentIds?.length || 0}`}</div>
|
||||||
</div>
|
</div>
|
||||||
{canEdit && (
|
<CourseOperationBtns />
|
||||||
<div
|
|
||||||
className="flex gap-1 text-primary hover:cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
const url = course?.id
|
|
||||||
? `/course/${course?.id}/editor`
|
|
||||||
: "/course/editor";
|
|
||||||
navigate(url);
|
|
||||||
}}>
|
|
||||||
<EditTwoTone></EditTwoTone>
|
|
||||||
{"点击编辑课程"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { CourseDetailContext } from "./CourseDetailContext";
|
||||||
|
import { useStaff } from "@nice/client";
|
||||||
|
import {
|
||||||
|
CheckCircleFilled,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
CloseCircleFilled,
|
||||||
|
CloseCircleOutlined,
|
||||||
|
EditFilled,
|
||||||
|
EditTwoTone,
|
||||||
|
LoginOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
|
export default function CourseOperationBtns() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { isAuthenticated, user, hasSomePermissions, hasEveryPermissions } =
|
||||||
|
useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { course, canEdit, userIsLearning } = useContext(CourseDetailContext);
|
||||||
|
const { update } = useStaff();
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const toggleLearning = async () => {
|
||||||
|
if (!userIsLearning) {
|
||||||
|
await update.mutateAsync({
|
||||||
|
where: { id: user?.id },
|
||||||
|
data: {
|
||||||
|
learningPosts: {
|
||||||
|
connect: { id: course.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await update.mutateAsync({
|
||||||
|
where: { id: user?.id },
|
||||||
|
data: {
|
||||||
|
learningPosts: {
|
||||||
|
disconnect: {
|
||||||
|
id: course.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isAuthenticated && (
|
||||||
|
<div
|
||||||
|
onClick={toggleLearning}
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
className={`flex px-1 py-0.5 gap-1 hover:cursor-pointer transition-all ${
|
||||||
|
userIsLearning
|
||||||
|
? isHovered
|
||||||
|
? "text-red-500 border-red-500 rounded-md "
|
||||||
|
: "text-green-500 "
|
||||||
|
: "text-primary "
|
||||||
|
}`}>
|
||||||
|
{userIsLearning ? (
|
||||||
|
isHovered ? (
|
||||||
|
<CloseCircleOutlined />
|
||||||
|
) : (
|
||||||
|
<CheckCircleOutlined />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<LoginOutlined />
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{userIsLearning
|
||||||
|
? isHovered
|
||||||
|
? "退出学习"
|
||||||
|
: "正在学习"
|
||||||
|
: "加入学习"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{canEdit && (
|
||||||
|
<div
|
||||||
|
className="flex gap-1 px-1 py-0.5 text-primary hover:cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const url = course?.id
|
||||||
|
? `/course/${course?.id}/editor`
|
||||||
|
: "/course/editor";
|
||||||
|
navigate(url);
|
||||||
|
}}>
|
||||||
|
<EditTwoTone></EditTwoTone>
|
||||||
|
{"编辑课程"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -92,6 +92,10 @@ export const routes: CustomRouteObject[] = [
|
||||||
</WithAuth>
|
</WithAuth>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "course/:id?/detail/:lectureId?", // 使用 ? 表示 id 参数是可选的
|
||||||
|
element: <CourseDetailPage />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -125,10 +129,6 @@ export const routes: CustomRouteObject[] = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: ":id?/detail/:lectureId?", // 使用 ? 表示 id 参数是可选的
|
|
||||||
element: <CourseDetailPage />,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
adminRoute,
|
adminRoute,
|
||||||
|
|
Loading…
Reference in New Issue