This commit is contained in:
ditiqi 2025-02-26 11:39:53 +08:00
parent f8659b90ca
commit c9199a7709
5 changed files with 66 additions and 65 deletions

View File

@ -1,13 +1,13 @@
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
@ -15,19 +15,23 @@ export default function Breadcrumb() {
.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
className={`inline-flex items-center `}
style={{ style={{
color: (index === crumbs.length - 1) ? token.colorPrimaryText : token.colorTextSecondary, color:
fontWeight: (index === crumbs.length - 1) ? "bold" : "normal", index === crumbs.length - 1
}} ? token.colorPrimaryText
> : token.colorTextSecondary,
fontWeight:
index === crumbs.length - 1 ? "bold" : "normal",
}}>
{crumb} {crumb}
</li> </li>
{index < crumbs.length - 1 && ( {index < crumbs.length - 1 && (
<li className='mx-2'> <li className="mx-2">
<RightOutlined></RightOutlined> <RightOutlined></RightOutlined>
</li> </li>
)} )}

View File

@ -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>

View File

@ -10,7 +10,7 @@ import { useAuth } from "@web/src/providers/auth-provider";
import { useNavigate, useParams } from "react-router-dom"; 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";
import { Department, RolePerms } from "@nice/common";
const { Header } = Layout; const { Header } = Layout;
@ -20,9 +20,8 @@ export function CourseDetailHeader() {
const { isAuthenticated, user, hasSomePermissions, hasEveryPermissions } = const { isAuthenticated, user, hasSomePermissions, hasEveryPermissions } =
useAuth(); useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const { course } = useContext(CourseDetailContext); const { course, canEdit } = useContext(CourseDetailContext);
hasSomePermissions(RolePerms.MANAGE_ANY_POST, RolePerms.MANAGE_DOM_POST);
hasEveryPermissions(RolePerms.MANAGE_ANY_POST, RolePerms.MANAGE_DOM_POST);
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">
<div className="w-full flex items-center justify-between h-full"> <div className="w-full flex items-center justify-between h-full">
@ -52,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={() => {

View File

@ -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

View File

@ -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>
// ),
// },
], ],
}, },
{ {