This commit is contained in:
ditiqi 2025-02-24 08:54:15 +08:00
commit c031e4f1c1
12 changed files with 236 additions and 47 deletions

View File

@ -15,7 +15,7 @@ async function bootstrap() {
const trpc = app.get(TrpcRouter);
trpc.applyMiddleware(app);
const port = process.env.SERVER_PORT || 3000;
const port = process.env.SERVER_PORT || 3001;
await app.listen(port);
}

View File

@ -22,7 +22,7 @@ export class GenDevService {
terms: Record<TaxonomySlug, Term[]> = {
[TaxonomySlug.CATEGORY]: [],
[TaxonomySlug.TAG]: [],
[TaxonomySlug.LEVEL]: [],
[TaxonomySlug.LEVEL]: []
};
depts: Department[] = [];
domains: Department[] = [];

View File

@ -0,0 +1,25 @@
import { Checkbox, List } from 'antd';
import React from 'react';
export function CoursePreviewTabmsg({data}){
const renderItem = (item) => (
<List.Item>
<List.Item.Meta
title={item.title}
description={item.description}
/>
</List.Item>
);
return(
<div className='my-2'>
<List
dataSource={data}
split={false}
renderItem={renderItem}
/>
</div>
)
}

View File

@ -0,0 +1,11 @@
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
export function CourseCatalog(){
return (
<>
</>
)
}

View File

@ -1,7 +1,51 @@
export function coursePreviewAllmsg() {
import { useEffect } from 'react';
import { CoursePreviewMsg } from '@web/src/app/main/course/preview/type.ts';
import { Button , Tabs , Image, Skeleton } from 'antd';
import type { TabsProps } from 'antd';
import { PlayCircleOutlined } from "@ant-design/icons";
export function CoursePreviewAllmsg({previewMsg,items,isLoading}: {previewMsg?:CoursePreviewMsg,items:TabsProps['items'],isLoading:Boolean}){
useEffect(() => {
console.log(previewMsg)
})
const TapOnChange = (key: string) => {
console.log(key);
};
return (
<div className="min-h-screen">
helloword
<div className="min-h-screen max-w-7xl mx-auto px-6 lg:px-8">
<div className="overflow-auto flex justify-around align-items-center w-full mx-auto my-8">
<div className="relative w-[600px] h-[340px] m-4 overflow-hidden flex justify-center items-center">
<Image
src={previewMsg.isLoading ? 'error' : previewMsg.videoPreview}
alt="example"
preview = {false}
className="w-full h-full object-cover z-0"
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
/>
<div className='w-[600px] h-[360px] absolute top-0 z-10 bg-black opacity-30 transition-opacity duration-300 ease-in-out hover:opacity-70 cursor-pointer'>
<PlayCircleOutlined
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-4xl z-10"
/>
</div>
</div>
<div className="flex flex-col justify-between w-2/5 content-start h-[340px] my-4 overflow-hidden">
{
isLoading ? <Skeleton className='my-5' active />
:(
<>
<span className="text-3xl font-bold my-3 ">{previewMsg.Title}</span>
<span className="text-xl font-semibold my-3 text-gray-700">{previewMsg.SubTitle}</span>
<span className="text-lg font-light my-3 text-gray-500 text-clip">{previewMsg.Description}</span>
</>
)
}
<Button block type="primary" size='large'> </Button>
</div>
</div>
<div className="overflow-auto w-11/12 mx-auto my-8">
<Tabs defaultActiveKey="1" tabBarGutter={100} items={items} onChange={TapOnChange} />
</div>
</div>
)
}

View File

@ -1,7 +1,81 @@
import { Skeleton, type TabsProps } from 'antd';
import { CoursePreviewAllmsg } from "./components/coursePreviewAllmsg";
import { CoursePreviewTabmsg } from "./components/couresPreviewTabmsg";
import { CoursePreviewMsg } from "./type";
import { api } from '@nice/client'
import { useNavigate, useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { courseDetailSelect, CourseDto } from '@nice/common';
export function CoursePreview(){
const { id } = useParams()
const { data:course,isLoading:courseIsLoading}:{data:CourseDto,isLoading:boolean}= api.post.findFirst.useQuery({
where:{
id
},
select:courseDetailSelect
})
// course.sections[0].lectures[0]
// `/course/${course.id}/detail/${Lecture.id}`
useEffect(() => {
if(!courseIsLoading){
setPreviewMsg({
videoPreview: course?.meta?.thumbnail,
Title: course?.title,
SubTitle:course?.subTitle,
Description:course?.content,
ToCourseUrl:`/course/${id}`,
isLoading:courseIsLoading
})
}
},[courseIsLoading])
const [previewMsg,setPreviewMsg] = useState({
videoPreview: '',
Title: '',
SubTitle:'',
Description:'',
ToCourseUrl:'',
isLoading:courseIsLoading
})
const tapData = [
{
title: '掌握R语言的基本概念语法',
description: '学生将学习R语言和RStudio的基本知识',
},
{
title: '掌握R语言的基本概念语法',
description: '学生将学习R语言的变量、数据类型、循环和条件语句等',
},
{
title: '掌握R语言的数据导入管理',
description: '学生将学会如何将数据导入R环境并且使用各种类型的数据',
},
{
title: '掌握R语言的基本数据清洗',
description: '学生将学会使用R语言进行数据清洗、整理和管理',
},
{
title: '掌握R语言的基本数据统计',
description: '学生将学会使用R语言进行基本的数据统计',
},
{
title: '掌握R语言的基本绘图功能',
description: '学生将学会使用R语言基本的绘图功能和ggplot2的应用',
},
];
const isLoading = false
const items: TabsProps['items'] = [
{
key: '1',
label: '课程学习目标',
children: isLoading ? <Skeleton className='my-5' active />: <CoursePreviewTabmsg data={tapData}/>,
}
];
return(
<div className="min-h-screen">
helloword
<CoursePreviewAllmsg previewMsg= {previewMsg} isLoading={courseIsLoading } items = {items}></CoursePreviewAllmsg>
</div>
)
}

View File

@ -0,0 +1,8 @@
export interface CoursePreviewMsg{
videoPreview: string;
Title: string;
SubTitle:string;
Description:string;
ToCourseUrl:string;
isLoading:Boolean
}

View File

@ -1,8 +1,9 @@
import { Checkbox, Divider, Radio, Space , Spin} from 'antd';
import { categories, levels } from '../mockData';
import { TaxonomySlug, TermDto } from '@nice/common';
import { GetTaxonomyProps, useGetTaxonomy } from '@web/src/hooks/useGetTaxonomy';
import { useMemo } from 'react';
import { api } from '@nice/client';
interface FilterSectionProps {
selectedCategory: string;
@ -11,6 +12,35 @@ interface FilterSectionProps {
onLevelChange: (level: string) => void;
}
interface GetTaxonomyProps {
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}
}
export default function FilterSection({
selectedCategory,
selectedLevel,

View File

@ -11,7 +11,35 @@ import {
} from '@ant-design/icons';
import { TaxonomySlug, TermDto } from '@nice/common';
import { api } from '@nice/client';
import { GetTaxonomyProps, useGetTaxonomy } from '@web/src/hooks/useGetTaxonomy';
interface GetTaxonomyProps {
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 { Title, Text } = Typography;

View File

@ -105,20 +105,19 @@ const HomePage = () => {
return (
<div className="min-h-screen">
<HeroSection />
{/* <TusUploader></TusUploader> */}
<CoursesSection
title="推荐课程"
description="最受欢迎的精品课程,助你快速成长"
courses={mockCourses}
/>
<CoursesSection
{/* <CoursesSection
title="热门课程"
description="最受欢迎的精品课程,助你快速成长"
courses={mockCourses}
/>
/> */}
<CategorySection />
<FeaturedTeachersSection />
{/* <FeaturedTeachersSection /> */}
</div>
);
};

View File

@ -1,31 +0,0 @@
import { api } from "@nice/client/"
import { TaxonomySlug, TermDto } from "@nice/common"
import { useMemo } from 'react';
export interface GetTaxonomyProps {
categories: string[];
isLoading: boolean;
}
export 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}
}

View File

@ -93,9 +93,10 @@ export const routes: CustomRouteObject[] = [
},
// 课程预览页面
{
path: "coursePreview",
element: <CoursePreview></CoursePreview>,
},
path: "coursePreview/:id?",
element:<CoursePreview></CoursePreview>
}
],
},
{