diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 386668a..8b82ed1 100755 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -15,7 +15,7 @@ async function bootstrap() { const trpc = app.get(TrpcRouter); trpc.applyMiddleware(app); - const port = process.env.SERVER_PORT || 3001; + const port = process.env.SERVER_PORT || 3000; await app.listen(port); } diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts index df99714..eeb6e98 100755 --- a/apps/server/src/models/post/post.service.ts +++ b/apps/server/src/models/post/post.service.ts @@ -19,6 +19,7 @@ import { setCourseInfo, setPostRelation } from './utils'; import EventBus, { CrudOperation } from '@server/utils/event-bus'; import { BaseTreeService } from '../base/base.tree.service'; import { z } from 'zod'; +import { DefaultArgs } from '@prisma/client/runtime/library'; @Injectable() export class PostService extends BaseTreeService { @@ -215,7 +216,15 @@ export class PostService extends BaseTreeService { return { ...result, items }; }); } - + async findManyWithPagination(args: + { page?: number; + pageSize?: number; + where?: Prisma.PostWhereInput; + select?: Prisma.PostSelect; + }): Promise<{ items: { id: string; type: string | null; level: string | null; state: string | null; title: string | null; subTitle: string | null; content: string | null; important: boolean | null; domainId: string | null; order: number | null; duration: number | null; rating: number | null; createdAt: Date; publishedAt: Date | null; updatedAt: Date; deletedAt: Date | null; authorId: string | null; parentId: string | null; hasChildren: boolean | null; meta: Prisma.JsonValue | null; }[]; totalPages: number; }> + { + return super.findManyWithPagination(args); + } protected async setPerms(data: Post, staff?: UserProfile) { if (!staff) return; const perms: ResPerm = { diff --git a/apps/web/src/app/main/courses/components/FilterSection.tsx b/apps/web/src/app/main/courses/components/FilterSection.tsx index 32f41ba..6de4312 100755 --- a/apps/web/src/app/main/courses/components/FilterSection.tsx +++ b/apps/web/src/app/main/courses/components/FilterSection.tsx @@ -2,8 +2,9 @@ import { Checkbox, Divider, Radio, Space , Spin} from 'antd'; import { categories, levels } from '../mockData'; import { TaxonomySlug, TermDto } from '@nice/common'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { api } from '@nice/client'; +import { useSearchParams } from 'react-router-dom'; interface FilterSectionProps { selectedCategory: string; @@ -53,6 +54,12 @@ export default function FilterSection({ const levels : GetTaxonomyProps = useGetTaxonomy({ type: TaxonomySlug.LEVEL, }) + + const [searchParams,setSearchParams] = useSearchParams() + useEffect(() => { + if(searchParams.get('category')) onCategoryChange(searchParams.get('category')) + },[searchParams.get('category')]) + return (
diff --git a/apps/web/src/app/main/courses/page.tsx b/apps/web/src/app/main/courses/page.tsx index 30cd309..cfdd484 100755 --- a/apps/web/src/app/main/courses/page.tsx +++ b/apps/web/src/app/main/courses/page.tsx @@ -66,6 +66,7 @@ export default function CoursesPage() { selectedCategory={selectedCategory} selectedLevel={selectedLevel} onCategoryChange={(category) => { + console.log(category); setSelectedCategory(category); setCurrentPage(1); }} diff --git a/apps/web/src/app/main/home/components/CategorySection.tsx b/apps/web/src/app/main/home/components/CategorySection.tsx index a64b551..967336f 100755 --- a/apps/web/src/app/main/home/components/CategorySection.tsx +++ b/apps/web/src/app/main/home/components/CategorySection.tsx @@ -1,7 +1,9 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react'; -import { Typography, Button } from 'antd'; +import { Typography, Button, Spin } from 'antd'; import { stringToColor, TaxonomySlug, TermDto } from '@nice/common'; -import { api } from '@nice/client'; +import { api,} from '@nice/client'; +import { ControlOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; const { Title, Text } = Typography; @@ -11,45 +13,45 @@ interface CourseCategory { description: string; } -const courseCategories: CourseCategory[] = [ - { - name: '计算机基础', - count: 120, - description: '计算机组成原理、操作系统、网络等基础知识' - }, - { - name: '编程语言', - count: 85, - description: 'Python、Java、JavaScript等主流编程语言' - }, - { - name: '人工智能', - count: 65, - description: '机器学习、深度学习、自然语言处理等前沿技术' - }, - { - name: '数据科学', - count: 45, - description: '数据分析、数据可视化、商业智能等' - }, - { - name: '云计算', - count: 38, - description: '云服务、容器化、微服务架构等' - }, - { - name: '网络安全', - count: 42, - description: '网络安全基础、渗透测试、安全防护等' - } -]; +// const courseCategories: CourseCategory[] = [ +// { +// name: '计算机基础', +// count: 120, +// description: '计算机组成原理、操作系统、网络等基础知识' +// }, +// { +// name: '编程语言', +// count: 85, +// description: 'Python、Java、JavaScript等主流编程语言' +// }, +// { +// name: '人工智能', +// count: 65, +// description: '机器学习、深度学习、自然语言处理等前沿技术' +// }, +// { +// name: '数据科学', +// count: 45, +// description: '数据分析、数据可视化、商业智能等' +// }, +// { +// name: '云计算', +// count: 38, +// description: '云服务、容器化、微服务架构等' +// }, +// { +// name: '网络安全', +// count: 42, +// description: '网络安全基础、渗透测试、安全防护等' +// } +// ]; const CategorySection = () => { const [hoveredIndex, setHoveredIndex] = useState(null); const [showAll, setShowAll] = useState(false); - /** - * const {data,isLoading} :{data:TermDto[],isLoading:boolean}= api.term.findMany.useQuery({ + //获得分类 + const {data:courseCategoriesData,isLoading} :{data:TermDto[],isLoading:boolean}= api.term.findMany.useQuery({ where:{ taxonomy: { slug:TaxonomySlug.CATEGORY @@ -57,16 +59,32 @@ const CategorySection = () => { }, include:{ children :true - } + }, + orderBy: { + createdAt: 'desc', // 按创建时间降序排列 + }, + take:10 }) - const courseCategories: CourseCategory[] = useMemo(() => { - return data?.map((term) => ({ - name: term.name, - count: term.hasChildren ? term.children.length : 0, - description: term.description - })) || []; - },[data]) - */ + // 分类展示 + const [displayedCategories,setDisplayedCategories] = useState([]) + useEffect(() => { + console.log(courseCategoriesData); + if(!isLoading){ + if(showAll){ + setDisplayedCategories(courseCategoriesData) + }else{ + setDisplayedCategories(courseCategoriesData.slice(0,8)) + } + } + }, [courseCategoriesData,showAll]); + // const courseCategories: CourseCategory[] = useMemo(() => { + // return data?.map((term) => ({ + // name: term.name, + // count: term.hasChildren ? term.children.length : 0, + // description: term.description + // })) || []; + // },[data]) + const handleMouseEnter = useCallback((index: number) => { @@ -77,9 +95,7 @@ const CategorySection = () => { setHoveredIndex(null); }, []); - const displayedCategories = showAll - ? courseCategories - : courseCategories.slice(0, 8); + const navigate = useNavigate() return (
@@ -93,78 +109,86 @@ const CategorySection = () => {
- {displayedCategories.map((category, index) => { - const categoryColor = stringToColor(category.name); - const isHovered = hoveredIndex === index; - - return ( -
handleMouseEnter(index)} - onMouseLeave={handleMouseLeave} - role="button" - tabIndex={0} - aria-label={`查看${category.name}课程类别`} - > -
+ { + isLoading ? : + (displayedCategories.map((category, index) => { + const categoryColor = stringToColor(category.name); + const isHovered = hoveredIndex === index; + + return (
-
-
-
-
- - {category.name} - - - {category.count} 门课程 - -
- - {category.description} - + key={index} + className="group relative min-h-[130px] rounded-2xl transition-all duration-700 ease-out cursor-pointer will-change-transform hover:-translate-y-2" + onMouseEnter={() => handleMouseEnter(index)} + onMouseLeave={handleMouseLeave} + role="button" + tabIndex={0} + aria-label={`查看${category.name}课程类别`} + onClick={()=>{ + console.log(category.name) + navigate(`/courses?category=${category.name}`) + }} + > +
- 了解更多 - +
+
+
+
+ + {category.name} + + {/* + {category.children.length} 门课程 + */} +
+ + {category.description} + +
- → - + 了解更多 + + → + +
-
- ); - })} + ); + })) + } +
- {courseCategories.length > 8 && ( + {!isLoading && courseCategoriesData.length > 8 && (
{/* Stats Container */} -
-
+
+
{platformStats.map((stat, index) => (
{
{stat.value}
-
{stat.label}
+
+ {stat.label} +
))}
diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index 4d4be9e..956a2e4 100755 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -2,6 +2,8 @@ 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 } from 'react'; const HomePage = () => { const mockCourses = [ { diff --git a/apps/web/src/hooks/useLocalSetting.ts b/apps/web/src/hooks/useLocalSetting.ts index 03b4057..cd78328 100755 --- a/apps/web/src/hooks/useLocalSetting.ts +++ b/apps/web/src/hooks/useLocalSetting.ts @@ -6,8 +6,8 @@ export function useLocalSettings() { return `${protocol}://${env.SERVER_IP}:${port}`; }, []); const tusUrl = useMemo(() => getBaseUrl('http', 8080), [getBaseUrl]); - const apiUrl = useMemo(() => getBaseUrl('http', 3000), [getBaseUrl]); - const websocketUrl = useMemo(() => getBaseUrl('ws', 3000), [getBaseUrl]); + const apiUrl = useMemo(() => getBaseUrl('http', parseInt(env.SERVER_PORT)), [getBaseUrl]); + const websocketUrl = useMemo(() => getBaseUrl('ws', parseInt(env.SERVER_PORT)), [getBaseUrl]); const checkIsTusUrl = useCallback((url: string) => { return url.startsWith(tusUrl) }, [tusUrl]) diff --git a/apps/web/src/utils/axios-client.ts b/apps/web/src/utils/axios-client.ts index 23877df..a42ae46 100755 --- a/apps/web/src/utils/axios-client.ts +++ b/apps/web/src/utils/axios-client.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { env } from '../env'; -const BASE_URL = `http://${env.SERVER_IP}:3000` +const BASE_URL = `http://${env.SERVER_IP}:${env?.SERVER_PORT}` const apiClient = axios.create({ baseURL: BASE_URL, // withCredentials: true,