Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
49464054dc
|
@ -69,3 +69,4 @@ yarn-error.log*
|
||||||
**/.idea/
|
**/.idea/
|
||||||
uploads
|
uploads
|
||||||
packages/mind-elixir-core
|
packages/mind-elixir-core
|
||||||
|
config/nginx/conf.d/web.conf
|
|
@ -60,24 +60,6 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* <Tag
|
|
||||||
color="blue"
|
|
||||||
className="px-3 py-1 rounded-full bg-blue-100 text-blue-600 border-0">
|
|
||||||
{course.terms?.[0].name}
|
|
||||||
|
|
||||||
</Tag>
|
|
||||||
|
|
||||||
<Tag
|
|
||||||
color={
|
|
||||||
course.terms?.[1].name === "入门"
|
|
||||||
? "green"
|
|
||||||
: course.terms?.[1].name === "中级"
|
|
||||||
? "blue"
|
|
||||||
: "purple"
|
|
||||||
}
|
|
||||||
className="px-3 py-1 rounded-full border-0">
|
|
||||||
{course.terms?.[1].name}
|
|
||||||
</Tag> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Title
|
<Title
|
||||||
|
@ -103,7 +85,6 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
: null}
|
: null}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4 border-t border-gray-100 text-center">
|
<div className="pt-4 border-t border-gray-100 text-center">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
@ -41,7 +41,7 @@ const CategorySection = () => {
|
||||||
window.scrollTo({top: 0,behavior: "smooth",})
|
window.scrollTo({top: 0,behavior: "smooth",})
|
||||||
},[]);
|
},[]);
|
||||||
return (
|
return (
|
||||||
<section className="py-32 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">
|
||||||
<div className="text-center mb-24">
|
<div className="text-center mb-24">
|
||||||
<Title
|
<Title
|
||||||
|
|
|
@ -6,37 +6,37 @@ import { CoursesSectionTag } from "./CoursesSectionTag";
|
||||||
import CourseList from "@web/src/components/models/course/list/CourseList";
|
import CourseList from "@web/src/components/models/course/list/CourseList";
|
||||||
import LookForMore from "./LookForMore";
|
import LookForMore from "./LookForMore";
|
||||||
interface GetTaxonomyProps {
|
interface GetTaxonomyProps {
|
||||||
categories: string[];
|
categories: string[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
function useGetTaxonomy({ type }): GetTaxonomyProps {
|
function useGetTaxonomy({ type }): GetTaxonomyProps {
|
||||||
const { data, isLoading }: { data: TermDto[]; isLoading: boolean } =
|
const { data, isLoading }: { data: TermDto[]; isLoading: boolean } =
|
||||||
api.term.findMany.useQuery({
|
api.term.findMany.useQuery({
|
||||||
where: {
|
where: {
|
||||||
taxonomy: {
|
taxonomy: {
|
||||||
slug: type,
|
slug: type,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
take: 10, // 只取前10个
|
take: 10, // 只取前10个
|
||||||
});
|
});
|
||||||
const categories = useMemo(() => {
|
const categories = useMemo(() => {
|
||||||
const allCategories = isLoading
|
const allCategories = isLoading
|
||||||
? []
|
? []
|
||||||
: data?.map((course) => course.name);
|
: data?.map((course) => course.name);
|
||||||
return [...Array.from(new Set(allCategories))];
|
return [...Array.from(new Set(allCategories))];
|
||||||
}, [data]);
|
}, [data]);
|
||||||
return { categories, isLoading };
|
return { categories, isLoading };
|
||||||
}
|
}
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
interface CoursesSectionProps {
|
interface CoursesSectionProps {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
initialVisibleCoursesCount?: number;
|
initialVisibleCoursesCount?: number;
|
||||||
}
|
}
|
||||||
const CoursesSection: React.FC<CoursesSectionProps> = ({
|
const CoursesSection: React.FC<CoursesSectionProps> = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
initialVisibleCoursesCount = 8,
|
initialVisibleCoursesCount = 8,
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
|
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
|
||||||
const gateGory: GetTaxonomyProps = useGetTaxonomy({
|
const gateGory: GetTaxonomyProps = useGetTaxonomy({
|
||||||
|
|
|
@ -9,9 +9,9 @@ const { Content } = Layout;
|
||||||
export function MainLayout() {
|
export function MainLayout() {
|
||||||
return (
|
return (
|
||||||
<MainProvider>
|
<MainProvider>
|
||||||
<Layout className="min-h-screen">
|
<Layout className="min-h-screen bg-gray-100">
|
||||||
<MainHeader />
|
<MainHeader />
|
||||||
<Content className="mt-16 bg-gray-50">
|
<Content className="mt-16 bg-gray-50 ">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Content>
|
</Content>
|
||||||
<MainFooter />
|
<MainFooter />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import MindEditor from "@web/src/components/common/editor/MindEditor";
|
import MindEditor from "@web/src/components/common/editor/MindEditor";
|
||||||
|
|
||||||
export default function PathsPage() {
|
export default function PathsPage() {
|
||||||
return <MindEditor></MindEditor>;
|
// return <MindEditor></MindEditor>;
|
||||||
|
return <>123</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,42 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useLocation, Link, useMatches } from 'react-router-dom';
|
import { useLocation, Link, useMatches } from "react-router-dom";
|
||||||
import { theme } from 'antd';
|
import { theme } from "antd";
|
||||||
import { RightOutlined } from '@ant-design/icons';
|
import { RightOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
export default function Breadcrumb() {
|
export default function Breadcrumb() {
|
||||||
let matches = useMatches();
|
const matches = useMatches();
|
||||||
const { token } = theme.useToken()
|
const { token } = theme.useToken();
|
||||||
|
|
||||||
let crumbs = matches
|
const crumbs = matches
|
||||||
// first get rid of any matches that don't have handle and crumb
|
// first get rid of any matches that don't have handle and crumb
|
||||||
.filter((match) => Boolean((match.handle as any)?.crumb))
|
.filter((match) => Boolean((match.handle as any)?.crumb))
|
||||||
// now map them into an array of elements, passing the loader
|
// now map them into an array of elements, passing the loader
|
||||||
// data to each one
|
// data to each one
|
||||||
.map((match) => (match.handle as any).crumb(match.data));
|
.map((match) => (match.handle as any).crumb(match.data));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ol className='flex items-center space-x-2 text-gray-600'>
|
<ol className="flex items-center space-x-2 text-gray-600">
|
||||||
{crumbs.map((crumb, index) => (
|
{crumbs.map((crumb, index) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<li className={`inline-flex items-center `}
|
<li
|
||||||
style={{
|
className={`inline-flex items-center `}
|
||||||
color: (index === crumbs.length - 1) ? token.colorPrimaryText : token.colorTextSecondary,
|
style={{
|
||||||
fontWeight: (index === crumbs.length - 1) ? "bold" : "normal",
|
color:
|
||||||
}}
|
index === crumbs.length - 1
|
||||||
>
|
? token.colorPrimaryText
|
||||||
{crumb}
|
: token.colorTextSecondary,
|
||||||
</li>
|
fontWeight:
|
||||||
{index < crumbs.length - 1 && (
|
index === crumbs.length - 1 ? "bold" : "normal",
|
||||||
<li className='mx-2'>
|
}}>
|
||||||
<RightOutlined></RightOutlined>
|
{crumb}
|
||||||
</li>
|
</li>
|
||||||
)}
|
{index < crumbs.length - 1 && (
|
||||||
</React.Fragment>
|
<li className="mx-2">
|
||||||
))}
|
<RightOutlined></RightOutlined>
|
||||||
</ol>
|
</li>
|
||||||
);
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,17 @@ import {
|
||||||
courseDetailSelect,
|
courseDetailSelect,
|
||||||
CourseDto,
|
CourseDto,
|
||||||
Lecture,
|
Lecture,
|
||||||
|
RolePerms,
|
||||||
VisitType,
|
VisitType,
|
||||||
} from "@nice/common";
|
} from "@nice/common";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import React, { createContext, ReactNode, useEffect, useState } from "react";
|
import React, {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
interface CourseDetailContextType {
|
interface CourseDetailContextType {
|
||||||
|
@ -19,11 +26,14 @@ interface CourseDetailContextType {
|
||||||
lectureIsLoading?: boolean;
|
lectureIsLoading?: boolean;
|
||||||
isHeaderVisible: boolean; // 新增
|
isHeaderVisible: boolean; // 新增
|
||||||
setIsHeaderVisible: (visible: boolean) => void; // 新增
|
setIsHeaderVisible: (visible: boolean) => void; // 新增
|
||||||
|
canEdit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CourseFormProviderProps {
|
interface CourseFormProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
editId?: string; // 添加 editId 参数
|
editId?: string; // 添加 editId 参数
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CourseDetailContext =
|
export const CourseDetailContext =
|
||||||
createContext<CourseDetailContextType | null>(null);
|
createContext<CourseDetailContextType | null>(null);
|
||||||
export function CourseDetailProvider({
|
export function CourseDetailProvider({
|
||||||
|
@ -32,8 +42,9 @@ export function CourseDetailProvider({
|
||||||
}: CourseFormProviderProps) {
|
}: CourseFormProviderProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { read } = useVisitor();
|
const { read } = useVisitor();
|
||||||
const { user } = useAuth();
|
const { user, hasSomePermissions } = useAuth();
|
||||||
const { lectureId } = useParams();
|
const { lectureId } = useParams();
|
||||||
|
|
||||||
const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } =
|
const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } =
|
||||||
(api.post as any).findFirst.useQuery(
|
(api.post as any).findFirst.useQuery(
|
||||||
{
|
{
|
||||||
|
@ -45,7 +56,14 @@ export function CourseDetailProvider({
|
||||||
},
|
},
|
||||||
{ enabled: Boolean(editId) }
|
{ enabled: Boolean(editId) }
|
||||||
);
|
);
|
||||||
|
const canEdit = useMemo(() => {
|
||||||
|
const isAuthor = user?.id === course?.authorId;
|
||||||
|
const isDept = course?.depts
|
||||||
|
?.map((dept) => dept.id)
|
||||||
|
.includes(user?.deptId);
|
||||||
|
const isRoot = hasSomePermissions(RolePerms?.MANAGE_ANY_POST);
|
||||||
|
return isAuthor || isDept || isRoot;
|
||||||
|
}, [user, course]);
|
||||||
const [selectedLectureId, setSelectedLectureId] = useState<
|
const [selectedLectureId, setSelectedLectureId] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>(lectureId || undefined);
|
>(lectureId || undefined);
|
||||||
|
@ -57,9 +75,9 @@ export function CourseDetailProvider({
|
||||||
},
|
},
|
||||||
{ enabled: Boolean(editId) }
|
{ enabled: Boolean(editId) }
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (course) {
|
if (course) {
|
||||||
console.log("read");
|
|
||||||
read.mutateAsync({
|
read.mutateAsync({
|
||||||
data: {
|
data: {
|
||||||
visitorId: user?.id || null,
|
visitorId: user?.id || null,
|
||||||
|
@ -85,6 +103,7 @@ export function CourseDetailProvider({
|
||||||
lectureIsLoading,
|
lectureIsLoading,
|
||||||
isHeaderVisible,
|
isHeaderVisible,
|
||||||
setIsHeaderVisible,
|
setIsHeaderVisible,
|
||||||
|
canEdit,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</CourseDetailContext.Provider>
|
</CourseDetailContext.Provider>
|
||||||
|
|
|
@ -11,14 +11,16 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu";
|
import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu";
|
||||||
import { CourseDetailContext } from "../CourseDetailContext";
|
import { CourseDetailContext } from "../CourseDetailContext";
|
||||||
|
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
|
||||||
export function CourseDetailHeader() {
|
export function CourseDetailHeader() {
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
const { isAuthenticated, user, hasSomePermissions, hasEveryPermissions } =
|
||||||
|
useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { course } = useContext(CourseDetailContext);
|
const { course, canEdit } = useContext(CourseDetailContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header className="select-none flex items-center justify-center bg-white shadow-md border-b border-gray-100 fixed w-full z-30">
|
<Header className="select-none flex items-center justify-center bg-white shadow-md border-b border-gray-100 fixed w-full z-30">
|
||||||
|
@ -49,7 +51,7 @@ export function CourseDetailHeader() {
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isAuthenticated && (
|
{canEdit && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -65,18 +67,7 @@ export function CourseDetailHeader() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
<Dropdown
|
<UserMenu />
|
||||||
overlay={<UserMenu />}
|
|
||||||
trigger={["click"]}
|
|
||||||
placement="bottomRight">
|
|
||||||
<Avatar
|
|
||||||
size="large"
|
|
||||||
className="cursor-pointer hover:scale-105 transition-all bg-gradient-to-r from-blue-500 to-blue-600 text-white font-semibold">
|
|
||||||
{(user?.showname ||
|
|
||||||
user?.username ||
|
|
||||||
"")[0]?.toUpperCase()}
|
|
||||||
</Avatar>
|
|
||||||
</Dropdown>
|
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/login")}
|
onClick={() => navigate("/login")}
|
||||||
|
|
|
@ -9,9 +9,7 @@ import { CourseDetailHeader } from "./CourseDetailHeader/CourseDetailHeader";
|
||||||
export default function CourseDetailLayout() {
|
export default function CourseDetailLayout() {
|
||||||
const {
|
const {
|
||||||
course,
|
course,
|
||||||
selectedLectureId,
|
|
||||||
lecture,
|
|
||||||
isLoading,
|
|
||||||
setSelectedLectureId,
|
setSelectedLectureId,
|
||||||
} = useContext(CourseDetailContext);
|
} = useContext(CourseDetailContext);
|
||||||
|
|
||||||
|
@ -38,10 +36,7 @@ export default function CourseDetailLayout() {
|
||||||
}}
|
}}
|
||||||
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||||
className="relative">
|
className="relative">
|
||||||
<CourseDetailDisplayArea
|
<CourseDetailDisplayArea />
|
||||||
// course={course}
|
|
||||||
// isLoading={isLoading}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
{/* 课程大纲侧边栏 */}
|
{/* 课程大纲侧边栏 */}
|
||||||
<CourseSyllabus
|
<CourseSyllabus
|
||||||
|
|
|
@ -24,7 +24,12 @@ export const LectureItem: React.FC<LectureItemProps> = ({
|
||||||
}, [lectureId, lecture]);
|
}, [lectureId, lecture]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex items-center gap-4 p-4 hover:bg-gray-200 text-left transition-colors cursor-pointer"
|
className={`w-full flex items-center gap-4 p-4 text-left transition-colors cursor-pointer
|
||||||
|
${
|
||||||
|
isReading
|
||||||
|
? "bg-blue-50 border-l-4 border-blue-500 hover:bg-blue-50"
|
||||||
|
: "hover:bg-gray-200"
|
||||||
|
}`}
|
||||||
onClick={() => onClick(lecture.id)}>
|
onClick={() => onClick(lecture.id)}>
|
||||||
{lecture?.meta?.type === LectureType.VIDEO && (
|
{lecture?.meta?.type === LectureType.VIDEO && (
|
||||||
<div className="text-blue-500 flex items-center">
|
<div className="text-blue-500 flex items-center">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createContext, useContext, ReactNode, useEffect } from "react";
|
import { createContext, useContext, ReactNode, useEffect } from "react";
|
||||||
import { Form, FormInstance, message } from "antd";
|
import { Form, FormInstance, message } from "antd";
|
||||||
import {
|
import {
|
||||||
|
courseDetailSelect,
|
||||||
CourseDto,
|
CourseDto,
|
||||||
CourseMeta,
|
CourseMeta,
|
||||||
CourseStatus,
|
CourseStatus,
|
||||||
|
@ -48,9 +49,7 @@ export function CourseFormProvider({
|
||||||
const { data: course }: { data: CourseDto } = api.post.findFirst.useQuery(
|
const { data: course }: { data: CourseDto } = api.post.findFirst.useQuery(
|
||||||
{
|
{
|
||||||
where: { id: editId },
|
where: { id: editId },
|
||||||
include: {
|
select: courseDetailSelect,
|
||||||
terms: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ enabled: Boolean(editId) }
|
{ enabled: Boolean(editId) }
|
||||||
);
|
);
|
||||||
|
@ -65,10 +64,12 @@ export function CourseFormProvider({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (course) {
|
if (course) {
|
||||||
|
const deptIds = (course?.depts || [])?.map((dept) => dept.id);
|
||||||
const formData = {
|
const formData = {
|
||||||
title: course.title,
|
title: course.title,
|
||||||
subTitle: course.subTitle,
|
subTitle: course.subTitle,
|
||||||
content: course.content,
|
content: course.content,
|
||||||
|
deptIds: deptIds,
|
||||||
meta: {
|
meta: {
|
||||||
thumbnail: course?.meta?.thumbnail,
|
thumbnail: course?.meta?.thumbnail,
|
||||||
},
|
},
|
||||||
|
@ -91,7 +92,10 @@ export function CourseFormProvider({
|
||||||
const formattedValues = {
|
const formattedValues = {
|
||||||
...values,
|
...values,
|
||||||
meta: {
|
meta: {
|
||||||
thumbnail: values?.meta?.thumbnail,
|
...((course?.meta as CourseMeta) || {}),
|
||||||
|
...(values?.meta?.thumbnail !== undefined && {
|
||||||
|
thumbnail: values?.meta?.thumbnail,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
terms: {
|
terms: {
|
||||||
connect: termIds.map((id) => ({ id })), // 转换成 connect 格式
|
connect: termIds.map((id) => ({ id })), // 转换成 connect 格式
|
||||||
|
@ -106,12 +110,9 @@ export function CourseFormProvider({
|
||||||
});
|
});
|
||||||
delete formattedValues.sections;
|
delete formattedValues.sections;
|
||||||
delete formattedValues.deptIds;
|
delete formattedValues.deptIds;
|
||||||
if (course) {
|
|
||||||
formattedValues.meta = {
|
console.log(course.meta);
|
||||||
...(course?.meta as CourseMeta),
|
console.log(formattedValues?.meta);
|
||||||
thumbnail: values?.meta?.thumbnail,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (editId) {
|
if (editId) {
|
||||||
const result = await update.mutateAsync({
|
const result = await update.mutateAsync({
|
||||||
|
|
|
@ -69,6 +69,9 @@ export const SortableSection: React.FC<SortableSectionProps> = ({
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = await update.mutateAsync({
|
result = await update.mutateAsync({
|
||||||
|
where: {
|
||||||
|
id: field?.id,
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
title: values?.title,
|
title: values?.title,
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { CourseStatus, CourseStatusLabel } from "@nice/common";
|
import { CourseStatus, CourseStatusLabel } from "@nice/common";
|
||||||
import { useCourseEditor } from "../context/CourseEditorContext";
|
import { useCourseEditor } from "../context/CourseEditorContext";
|
||||||
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ const courseStatusVariant: Record<CourseStatus, string> = {
|
||||||
|
|
||||||
export default function CourseEditorHeader() {
|
export default function CourseEditorHeader() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user, hasSomePermissions } = useAuth();
|
||||||
|
|
||||||
const { onSubmit, course, form } = useCourseEditor();
|
const { onSubmit, course, form } = useCourseEditor();
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
@ -34,7 +37,13 @@ export default function CourseEditorHeader() {
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Button
|
<Button
|
||||||
icon={<ArrowLeftOutlined />}
|
icon={<ArrowLeftOutlined />}
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => {
|
||||||
|
if (course?.id) {
|
||||||
|
navigate(`/course/${course?.id}/detail`);
|
||||||
|
} else {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
|
@ -63,16 +63,6 @@ export const routes: CustomRouteObject[] = [
|
||||||
path: "courses",
|
path: "courses",
|
||||||
element: <CoursesPage></CoursesPage>,
|
element: <CoursesPage></CoursesPage>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
path: "profiles",
|
|
||||||
},
|
|
||||||
|
|
||||||
// // 课程预览页面
|
|
||||||
// {
|
|
||||||
// path: "coursePreview/:id?",
|
|
||||||
// element: <CoursePreview></CoursePreview>,
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -103,12 +93,6 @@ export const routes: CustomRouteObject[] = [
|
||||||
</WithAuth>
|
</WithAuth>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// path: "setting",
|
|
||||||
// element: (
|
|
||||||
// <CourseSettingForm></CourseSettingForm>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -100,7 +100,7 @@ server {
|
||||||
# 仅供内部使用
|
# 仅供内部使用
|
||||||
internal;
|
internal;
|
||||||
# 代理到认证服务
|
# 代理到认证服务
|
||||||
proxy_pass http://host.docker.internal:3000/auth/file;
|
proxy_pass http://host.docker.internal:/auth/file;
|
||||||
|
|
||||||
# 请求优化:不传递请求体
|
# 请求优化:不传递请求体
|
||||||
proxy_pass_request_body off;
|
proxy_pass_request_body off;
|
||||||
|
|
|
@ -8,6 +8,7 @@ export enum PostType {
|
||||||
COURSE = "couse",
|
COURSE = "couse",
|
||||||
SECTION = "section",
|
SECTION = "section",
|
||||||
LECTURE = "lecture",
|
LECTURE = "lecture",
|
||||||
|
PATH = "path",
|
||||||
}
|
}
|
||||||
export enum LectureType {
|
export enum LectureType {
|
||||||
VIDEO = "video",
|
VIDEO = "video",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit b911b4ba7629da9d6c622abe241fd25299baf1a5
|
|
Loading…
Reference in New Issue