-
-
- {/* 左侧筛选区域 */}
-
-
- {
- console.log(category);
- setSelectedCategory(category);
- setCurrentPage(1);
- setIsAll(!category)
- setSearchParams(prev => {
- prev.delete('searchValue');
- return prev;
- });
- }}
- onLevelChange={(level) => {
- setSelectedLevel(level);
- setCurrentPage(1);
- setIsAll(!level)
- setSearchParams(prev => {
- prev.delete('searchValue');
- return prev;
- });
- }}
- />
-
-
-
- {/* 右侧课程列表区域 */}
-
-
-
-
- 共找到 {totalPagesNum * pageSize || 0} 门课程
-
-
-
-
-
-
+ <>
+
-
+ >
);
}
diff --git a/apps/web/src/app/main/courses/student/page.tsx b/apps/web/src/app/main/courses/student/page.tsx
deleted file mode 100755
index 2508c50..0000000
--- a/apps/web/src/app/main/courses/student/page.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { CourseList } from "@web/src/components/models/course/list/course-list";
-import { api } from "@nice/client";
-import { useState } from "react";
-import { CourseCard } from "@web/src/components/models/course/card/CourseCard";
-import { useAuth } from "@web/src/providers/auth-provider";
-
-export default function StudentCoursesPage() {
- const [currentPage, setCurrentPage] = useState(1);
- const { user } = useAuth();
-
- const { data: paginationRes, refetch } =
- api.post.findManyWithPagination.useQuery({
- page: currentPage,
- pageSize: 8,
- where: {
- enrollments: {
- some: {
- studentId: user?.id,
- },
- },
- },
- });
-
- const handlePageChange = (page: number) => {
- setCurrentPage(page);
- refetch();
- };
-
- return (
-
-
-
-
- 我参加的课程
-
-
-
(
-
- )}>
-
-
- );
-}
diff --git a/apps/web/src/app/main/home/components/CoursesSection.tsx b/apps/web/src/app/main/home/components/CoursesSection.tsx
index b4938b0..2117baa 100755
--- a/apps/web/src/app/main/home/components/CoursesSection.tsx
+++ b/apps/web/src/app/main/home/components/CoursesSection.tsx
@@ -1,270 +1,178 @@
-import React, { useState, useMemo, useEffect } from 'react';
-import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
-import { Button, Card, Typography, Tag, Progress, Spin, Empty } from 'antd';
+import React, { useState, useMemo, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { Button, Card, Typography, Tag, Spin, Empty } from "antd";
import {
- PlayCircleOutlined,
- UserOutlined,
- ClockCircleOutlined,
- TeamOutlined,
- StarOutlined,
- ArrowRightOutlined,
- EyeOutlined,
-} from '@ant-design/icons';
-import { CourseDto, TaxonomySlug, TermDto } from '@nice/common';
-import { api } from '@nice/client';
-// const {courseId} = useParams();
+ PlayCircleOutlined,
+ TeamOutlined,
+ ArrowRightOutlined,
+} from "@ant-design/icons";
+import { CourseDto, TaxonomySlug, TermDto } from "@nice/common";
+import { api } from "@nice/client";
+import CourseCard from "../../courses/components/CourseCard";
interface GetTaxonomyProps {
- categories: string[];
- isLoading: boolean;
+ categories: string[];
+ isLoading: boolean;
}
function useGetTaxonomy({ type }): GetTaxonomyProps {
- const { data, isLoading }: { data: TermDto[], isLoading: boolean } = api.term.findMany.useQuery({
- where: {
- taxonomy: {
- //TaxonomySlug.CATEGORY
- slug: type
- }
- },
- include: {
- children: true
- },
- take: 10, // 只取前10个
- orderBy: {
- createdAt: 'desc', // 按创建时间降序排列
- },
- })
- const categories = useMemo(() => {
- const allCategories = isLoading ? [] : data?.map((course) => course.name);
- return [...Array.from(new Set(allCategories))];
- }, [data]);
- return { categories, isLoading }
+ const { data, isLoading }: { data: TermDto[]; isLoading: boolean } =
+ api.term.findMany.useQuery({
+ where: {
+ taxonomy: {
+ slug: type,
+ },
+ },
+ include: {
+ children: true,
+ },
+ take: 10, // 只取前10个
+ orderBy: {},
+ });
+ const categories = useMemo(() => {
+ const allCategories = isLoading
+ ? []
+ : data?.map((course) => course.name);
+ return [...Array.from(new Set(allCategories))];
+ }, [data]);
+ return { categories, isLoading };
}
// 不同分类跳转
function useFetchCoursesByCategory(category: string) {
- const isAll = category === '全部';
- const { data, isLoading }: { data: CourseDto[], isLoading: boolean } = api.post.findMany.useQuery({
- where: isAll ? {} : {
- terms: {
- some: {
- name: category
- },
- },
- },
- take: 8,
- include: {
- terms: true
- }
- });
+ const isAll = category === "全部";
+ const { data, isLoading }: { data: CourseDto[]; isLoading: boolean } =
+ api.post.findMany.useQuery({
+ where: isAll
+ ? {}
+ : {
+ terms: {
+ some: {
+ name: category,
+ },
+ },
+ },
+ take: 8,
+ include: {
+ terms: true,
+ depts: true,
+ },
+ });
- return { data, isLoading };
+ return { data, isLoading };
}
-
-
-
const { Title, Text } = Typography;
-
-interface Course {
- id: number;
- title: string;
- instructor: string;
- students: number;
- rating: number;
- level: string;
- duration: string;
- category: string;
- progress: number;
- thumbnail: string;
-}
interface CoursesSectionProps {
- title: string;
- description: string;
- courses: Course[];
- isLoading: boolean
- initialVisibleCoursesCount?: number;
+ title: string;
+ description: string;
+ initialVisibleCoursesCount?: number;
}
-
-
const CoursesSection: React.FC
= ({
- title,
- description,
- courses,
- isLoading,
- initialVisibleCoursesCount = 8,
+ title,
+ description,
}) => {
- const navigate = useNavigate();
- const [selectedCategory, setSelectedCategory] = useState('全部');
- const [visibleCourses, setVisibleCourses] = useState(initialVisibleCoursesCount);
- const gateGory: GetTaxonomyProps = useGetTaxonomy({
- type: TaxonomySlug.CATEGORY,
- })
- const { data, isLoading: isDataLoading } = useFetchCoursesByCategory(selectedCategory);
- useEffect(() => {
- console.log('data:', data)
- })
- const handleClick = (course: CourseDto) => {
- navigate(`/course/${course.id}/detail`);
- }
+ const navigate = useNavigate();
+ const [selectedCategory, setSelectedCategory] = useState("全部");
+ const gateGory: GetTaxonomyProps = useGetTaxonomy({
+ type: TaxonomySlug.CATEGORY,
+ });
+ const { data, isLoading: isDataLoading } =
+ useFetchCoursesByCategory(selectedCategory);
+ const filteredCourses = useMemo(() => {
+ return selectedCategory === "全部"
+ ? data
+ : data?.filter((c) =>
+ c.terms.some((t) => t.name === selectedCategory)
+ );
+ }, [selectedCategory, data]);
+ const displayedCourses = isDataLoading ? [] : filteredCourses;
+ return (
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
- useEffect(() => {
- console.log('data:', data)
- })
- const filteredCourses = useMemo(() => {
- return selectedCategory === '全部'
- ? data
- : data?.filter(c => c.terms.some(t => t.name === selectedCategory));
- }, [selectedCategory, data]);
+
+ {gateGory.isLoading ? (
+
+ ) : (
+ <>
+ setSelectedCategory("全部")}
+ className={`px-6 py-2 text-base cursor-pointer rounded-full transition-all duration-300 ${
+ selectedCategory === "全部"
+ ? "bg-blue-600 text-white shadow-lg"
+ : "bg-white text-gray-600 hover:bg-gray-100"
+ }`}>
+ 全部
+
+ {gateGory.categories.map((category) => (
+ {
+ setSelectedCategory(category);
+ }}
+ className={`px-6 py-2 text-base cursor-pointer rounded-full transition-all duration-300 ${
+ selectedCategory === category
+ ? "bg-blue-600 text-white shadow-lg"
+ : "bg-white text-gray-600 hover:bg-gray-100"
+ }`}>
+ {category}
+
+ ))}
+ >
+ )}
+
- const displayedCourses = isDataLoading ? [] : filteredCourses?.slice(0, visibleCourses);
- return (
-
-
-
-
-
- {title}
-
-
- {description}
-
-
-
-
-
- {gateGory.isLoading ? :
- (
- <>
- setSelectedCategory("全部")}
- className={`px-6 py-2 text-base cursor-pointer rounded-full transition-all duration-300 ${selectedCategory === "全部"
- ? 'bg-blue-600 text-white shadow-lg'
- : 'bg-white text-gray-600 hover:bg-gray-100'
- }`}
- >全部
- {gateGory.categories.map((category) => (
- {
- setSelectedCategory(category)
- console.log(category)
- }}
- className={`px-6 py-2 text-base cursor-pointer rounded-full transition-all duration-300 ${selectedCategory === category
- ? 'bg-blue-600 text-white shadow-lg'
- : 'bg-white text-gray-600 hover:bg-gray-100'
- }`}
-
- >
- {category}
-
-
- ))
- }
-
- >
- )
- }
-
-
-
- {displayedCourses.length=== 0 ? (
-
-
-
- ) : displayedCourses?.map((course) => (
-
handleClick(course)}
- key={course.id}
- hoverable
- className="group overflow-hidden rounded-2xl border border-gray-200 bg-white
- shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2"
- cover={
-
- }
- >
-
-
-
- {course.terms[0].name}
-
-
- {course.terms[1].name}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 观看次数{course?.meta?.views}次
-
-
-
-
-
-
-
-
- ))}
-
-
- {filteredCourses?.length >= visibleCourses && (
-
-
-
-
-
-
- )}
-
-
- );
+
+ {displayedCourses.length === 0 ? (
+
+
+
+ ) : (
+ displayedCourses?.map((course) => (
+
+ ))
+ )}
+
+ {
+
+
+
+
+
+
+ }
+
+
+ );
};
-
export default CoursesSection;
diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx
index 7250975..0cf5123 100755
--- a/apps/web/src/app/main/home/page.tsx
+++ b/apps/web/src/app/main/home/page.tsx
@@ -1,163 +1,18 @@
import HeroSection from "./components/HeroSection";
import CategorySection from "./components/CategorySection";
import CoursesSection from "./components/CoursesSection";
-import FeaturedTeachersSection from "./components/FeaturedTeachersSection";
-import { api } from "@nice/client";
-import { useEffect, useState } from "react";
-import TermTree from "@web/src/components/models/term/term-tree";
-interface Courses {
- id: number;
- title: string;
- instructor: string;
- students: number;
- rating: number;
- level: string;
- duration: string;
- category: string;
- progress: number;
- thumbnail: string;
-}
-const HomePage = () => {
- // {
- // id: 1,
- // title: 'Python 零基础入门',
- // instructor: '张教授',
- // students: 12000,
- // rating: 4.8,
- // level: '入门',
- // duration: '36小时',
- // category: '编程语言',
- // progress: 16,
- // thumbnail: '/images/course1.jpg',
- // },
- // {
- // id: 2,
- // title: '数据结构与算法',
- // instructor: '李教授',
- // students: 8500,
- // rating: 4.9,
- // level: '进阶',
- // duration: '48小时',
- // category: '计算机基础',
- // progress: 35,
- // thumbnail: '/images/course2.jpg',
- // },
- // {
- // id: 3,
- // title: '前端开发实战',
- // instructor: '王教授',
- // students: 10000,
- // rating: 4.7,
- // level: '中级',
- // duration: '42小时',
- // category: '前端开发',
- // progress: 68,
- // thumbnail: '/images/course3.jpg',
- // },
- // {
- // id: 4,
- // title: 'Java企业级开发',
- // instructor: '刘教授',
- // students: 9500,
- // rating: 4.6,
- // level: '高级',
- // duration: '56小时',
- // category: '编程语言',
- // progress: 15,
- // thumbnail: '/images/course4.jpg',
- // },
- // {
- // id: 5,
- // title: '人工智能基础',
- // instructor: '陈教授',
- // students: 11000,
- // rating: 4.9,
- // level: '中级',
- // duration: '45小时',
- // category: '人工智能',
- // progress: 20,
- // thumbnail: '/images/course5.jpg',
- // },
- // {
- // id: 6,
- // title: '大数据分析',
- // instructor: '赵教授',
- // students: 8000,
- // rating: 4.8,
- // level: '进阶',
- // duration: '50小时',
- // category: '数据科学',
- // progress: 45,
- // thumbnail: '/images/course6.jpg',
- // },
- // {
- // id: 7,
- // title: '云计算实践',
- // instructor: '孙教授',
- // students: 7500,
- // rating: 4.7,
- // level: '高级',
- // duration: '48小时',
- // category: '云计算',
- // progress: 15,
- // thumbnail: '/images/course7.jpg',
- // },
- // {
- // id: 8,
- // title: '移动应用开发',
- // instructor: '周教授',
- // students: 9000,
- // rating: 4.8,
- // level: '中级',
- // duration: '40小时',
- // category: '移动开发',
- // progress: 70,
- // thumbnail: '/images/course8.jpg',
- // },
- // ];
- const { data, isLoading }: { data: Courses[]; isLoading: boolean } =
- api.post.findMany.useQuery({
- where: {},
- include: {
- instructors: true,
- },
- orderBy: {
- createdAt: "desc", // 按创建时间降序排列
- },
- take: 8, // 只获取前8个课程
- });
- useEffect(() => {
- if (data) {
- console.log("mockCourses data:", data);
- }
- }, [data]);
- // 数据处理逻辑
- // 修正依赖数组
+
+const HomePage = () => {
+
return (
-
- {/* {formattedCourses.map((course)=>{
- return (
- <>
- course.title
- >
- )
- })} */}
- {/* */}
- {/* */}
);
};
diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx
index 40053e1..47503b5 100755
--- a/apps/web/src/app/main/layout/MainHeader.tsx
+++ b/apps/web/src/app/main/layout/MainHeader.tsx
@@ -1,19 +1,17 @@
-import { useState } from "react";
+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 { UserMenu } from "./UserMenu/UserMenu";
import { NavigationMenu } from "./NavigationMenu";
-
+import { useMainContext } from "./MainProvider";
const { Header } = Layout;
export function MainHeader() {
- const [searchValue, setSearchValue] = useState("");
const { isAuthenticated, user } = useAuth();
const navigate = useNavigate();
- const [searchParams,setSearchParams] = useSearchParams();
-
+ const { searchValue, setSearchValue } = useMainContext();
return (
@@ -37,16 +35,17 @@ export function MainHeader() {
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onPressEnter={(e) => {
- //console.log(e)
- //setSearchValue("");
- setSearchParams((prev)=>{
- if(searchParams.get("category")) prev.delete("category")
- return prev
- })
- navigate(
- `/courses/?searchValue=${searchValue}`
- );
- window.scrollTo({ top: 0, behavior: "smooth" });
+ if (
+ !window.location.pathname.startsWith(
+ "/courses/"
+ )
+ ) {
+ navigate(`/courses/`);
+ window.scrollTo({
+ top: 0,
+ behavior: "smooth",
+ });
+ }
}}
/>
diff --git a/apps/web/src/app/main/layout/MainLayout.tsx b/apps/web/src/app/main/layout/MainLayout.tsx
index 09f5670..0a0074b 100755
--- a/apps/web/src/app/main/layout/MainLayout.tsx
+++ b/apps/web/src/app/main/layout/MainLayout.tsx
@@ -1,19 +1,21 @@
-import { Layout } from 'antd';
-import { Outlet } from 'react-router-dom';
-import { MainHeader } from './MainHeader';
-import { MainFooter } from './MainFooter';
+import { Layout } from "antd";
+import { Outlet } from "react-router-dom";
+import { MainHeader } from "./MainHeader";
+import { MainFooter } from "./MainFooter";
+import { MainProvider } from "./MainProvider";
const { Content } = Layout;
export function MainLayout() {
- return (
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/main/layout/MainProvider.tsx b/apps/web/src/app/main/layout/MainProvider.tsx
new file mode 100644
index 0000000..d503c39
--- /dev/null
+++ b/apps/web/src/app/main/layout/MainProvider.tsx
@@ -0,0 +1,31 @@
+import React, { createContext, ReactNode, useContext, useState } from "react";
+
+interface MainContextType {
+ searchValue?: string;
+ setSearchValue?: React.Dispatch>;
+}
+
+const MainContext = createContext(null);
+interface MainProviderProps {
+ children: ReactNode;
+}
+
+export function MainProvider({ children }: MainProviderProps) {
+ const [searchValue, setSearchValue] = useState("");
+ return (
+
+ {children}
+
+ );
+}
+export const useMainContext = () => {
+ const context = useContext(MainContext);
+ if (!context) {
+ throw new Error("useMainContext must be used within MainProvider");
+ }
+ return context;
+};
diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx
index 70f60a8..07c7c9c 100755
--- a/apps/web/src/routes/index.tsx
+++ b/apps/web/src/routes/index.tsx
@@ -8,8 +8,6 @@ import {
import ErrorPage from "../app/error";
import WithAuth from "../components/utils/with-auth";
import LoginPage from "../app/login";
-import StudentCoursesPage from "../app/main/courses/student/page";
-import InstructorCoursesPage from "../app/main/courses/instructor/page";
import HomePage from "../app/main/home/page";
import { CourseDetailPage } from "../app/main/course/detail/page";
import { CourseBasicForm } from "../components/models/course/editor/form/CourseBasicForm";
@@ -41,7 +39,6 @@ export type CustomRouteObject =
| CustomIndexRouteObject
| CustomNonIndexRouteObject;
export const routes: CustomRouteObject[] = [
-
{
path: "/",
errorElement: ,
@@ -66,39 +63,16 @@ export const routes: CustomRouteObject[] = [
path: "courses",
element: ,
},
- {
- path: "my-courses",
- },
+
{
path: "profiles",
},
- {
- path: "courses",
- children: [
- {
- path: "student",
- element: (
-
-
-
- ),
- },
- {
- path: "instructor",
- element: (
-
-
-
- ),
- },
- ],
- },
+
// 课程预览页面
{
path: "coursePreview/:id?",
- element:
- }
-
+ element: ,
+ },
],
},
{
@@ -106,20 +80,27 @@ export const routes: CustomRouteObject[] = [
children: [
{
path: ":id?/editor",
- element: ,
+ element: (
+
+
+
+ ),
children: [
{
index: true,
- element: ,
+ element: (
+
+
+
+ ),
},
- // {
- // path: "goal",
- // element: ,
- // },
+
{
path: "content",
element: (
-
+
+
+
),
},
// {
@@ -146,5 +127,4 @@ export const routes: CustomRouteObject[] = [
},
];
-
export const router = createBrowserRouter(routes);
diff --git a/packages/common/src/models/post.ts b/packages/common/src/models/post.ts
index de740d5..6994d4b 100755
--- a/packages/common/src/models/post.ts
+++ b/packages/common/src/models/post.ts
@@ -79,4 +79,5 @@ export type CourseDto = Course & {
sections?: SectionDto[];
terms: Term[];
lectureCount?: number;
+ depts:Department[]
};