Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
c031e4f1c1
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export class GenDevService {
|
|||
terms: Record<TaxonomySlug, Term[]> = {
|
||||
[TaxonomySlug.CATEGORY]: [],
|
||||
[TaxonomySlug.TAG]: [],
|
||||
[TaxonomySlug.LEVEL]: [],
|
||||
[TaxonomySlug.LEVEL]: []
|
||||
};
|
||||
depts: Department[] = [];
|
||||
domains: Department[] = [];
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import type { MenuProps } from 'antd';
|
||||
import { Menu } from 'antd';
|
||||
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
|
||||
export function CourseCatalog(){
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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,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"
|
||||
/>
|
||||
<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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export interface CoursePreviewMsg{
|
||||
videoPreview: string;
|
||||
Title: string;
|
||||
SubTitle:string;
|
||||
Description:string;
|
||||
ToCourseUrl:string;
|
||||
isLoading:Boolean
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
}
|
|
@ -93,9 +93,10 @@ export const routes: CustomRouteObject[] = [
|
|||
},
|
||||
// 课程预览页面
|
||||
{
|
||||
path: "coursePreview",
|
||||
element: <CoursePreview></CoursePreview>,
|
||||
},
|
||||
path: "coursePreview/:id?",
|
||||
element:<CoursePreview></CoursePreview>
|
||||
}
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue