rht02242053
This commit is contained in:
parent
c1fd27061e
commit
ce086bc97d
|
@ -1,9 +1,10 @@
|
|||
import { Card, Rate, Tag } from 'antd';
|
||||
import { Course } from '../mockData';
|
||||
import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons';
|
||||
import { CourseDto } from '@nice/common';
|
||||
|
||||
interface CourseCardProps {
|
||||
course: Course;
|
||||
course: CourseDto;
|
||||
}
|
||||
|
||||
export default function CourseCard({ course }: CourseCardProps) {
|
||||
|
@ -14,7 +15,7 @@ export default function CourseCard({ course }: CourseCardProps) {
|
|||
cover={
|
||||
<img
|
||||
alt={course.title}
|
||||
src={course.thumbnail}
|
||||
src={course?.meta?.thumbnail}
|
||||
className="object-cover w-full h-40"
|
||||
/>
|
||||
}
|
||||
|
@ -23,7 +24,7 @@ export default function CourseCard({ course }: CourseCardProps) {
|
|||
<h3 className="text-lg font-semibold line-clamp-2 hover:text-blue-600 transition-colors">
|
||||
{course.title}
|
||||
</h3>
|
||||
<p className="text-gray-500 text-sm">{course.instructor}</p>
|
||||
<p className="text-gray-500 text-sm">{course.subTitle}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Rate disabled defaultValue={course.rating} className="text-sm" />
|
||||
<span className="text-gray-500 text-sm">{course.rating}</span>
|
||||
|
@ -31,7 +32,7 @@ export default function CourseCard({ course }: CourseCardProps) {
|
|||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<div className="flex items-center space-x-1">
|
||||
<UserOutlined className="text-gray-400" />
|
||||
<span>{course.enrollments} 人在学</span>
|
||||
<span>{course.enrollments?.length} 人在学</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<ClockCircleOutlined className="text-gray-400" />
|
||||
|
@ -39,8 +40,8 @@ export default function CourseCard({ course }: CourseCardProps) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
<Tag color="blue" className="rounded-full px-3">{course.category}</Tag>
|
||||
<Tag color="green" className="rounded-full px-3">{course.level}</Tag>
|
||||
<Tag color="blue" className="rounded-full px-3">{course.terms[0].name}</Tag>
|
||||
<Tag color="green" className="rounded-full px-3">{course.terms[1].name}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Pagination, Empty } from 'antd';
|
||||
import { Course } from '../mockData';
|
||||
import CourseCard from './CourseCard';
|
||||
|
||||
import {CourseDto} from '@nice/common'
|
||||
interface CourseListProps {
|
||||
courses: Course[];
|
||||
courses: CourseDto[];
|
||||
total: number;
|
||||
pageSize: number;
|
||||
currentPage: number;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { useState, useMemo } from "react";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { mockCourses } from "./mockData";
|
||||
import FilterSection from "./components/FilterSection";
|
||||
import CourseList from "./components/CourseList";
|
||||
import { api } from "@nice/client";
|
||||
import { LectureType, PostType } from "@nice/common";
|
||||
import { courseDetailSelect, CourseDto, LectureType, PostType } from "@nice/common";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { set } from "idb-keyval";
|
||||
|
||||
|
||||
export default function CoursesPage() {
|
||||
|
@ -11,43 +13,57 @@ export default function CoursesPage() {
|
|||
const [selectedCategory, setSelectedCategory] = useState("");
|
||||
const [selectedLevel, setSelectedLevel] = useState("");
|
||||
const pageSize = 12;
|
||||
const [isAll,setIsAll] = useState(true)
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
let coursesData = []
|
||||
let isCourseLoading = false
|
||||
if(!searchParams.get('searchValue')){
|
||||
console.log('no category')
|
||||
const {data,isLoading} = api.post.findManyWithPagination.useQuery({
|
||||
where: {
|
||||
type: PostType.COURSE,
|
||||
terms: {
|
||||
terms:isAll?{}:{
|
||||
some: {
|
||||
AND: [
|
||||
...(selectedCategory
|
||||
? [
|
||||
{
|
||||
name: selectedCategory,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(selectedLevel
|
||||
? [
|
||||
{
|
||||
name: selectedLevel,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
OR : [
|
||||
selectedCategory?{name:selectedCategory}:{},
|
||||
selectedLevel?{name:selectedLevel}:{}
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const filteredCourses = useMemo(() => {
|
||||
return mockCourses.filter((course) => {
|
||||
const matchCategory =
|
||||
!selectedCategory || course.category === selectedCategory;
|
||||
const matchLevel = !selectedLevel || course.level === selectedLevel;
|
||||
return matchCategory && matchLevel;
|
||||
});
|
||||
}, [selectedCategory, selectedLevel]);
|
||||
|
||||
const paginatedCourses = useMemo(() => {
|
||||
},
|
||||
select:courseDetailSelect
|
||||
});
|
||||
coursesData = data?.items
|
||||
isCourseLoading = isLoading
|
||||
}else{
|
||||
console.log('searchValue:'+searchParams.get('searchValue'))
|
||||
const searchValue = searchParams.get('searchValue')
|
||||
const {data,isLoading} = api.post.findManyWithPagination.useQuery({
|
||||
where: {
|
||||
type: PostType.COURSE,
|
||||
OR:[
|
||||
{ title: { contains: searchValue, mode: 'insensitive' } },
|
||||
{ subTitle: { contains: searchValue, mode: 'insensitive' } },
|
||||
{ content: { contains: searchValue, mode: 'insensitive' } },
|
||||
{ terms: { some: { name: { contains: searchValue, mode: 'insensitive' } } } }
|
||||
]
|
||||
},
|
||||
select:courseDetailSelect
|
||||
})
|
||||
coursesData = data?.items
|
||||
isCourseLoading = isLoading
|
||||
}
|
||||
useEffect(() => {
|
||||
console.log(coursesData)
|
||||
}, [coursesData]);
|
||||
const filteredCourses = useMemo(() => {
|
||||
return isCourseLoading ? [] : coursesData;
|
||||
}, [isCourseLoading, coursesData, selectedCategory, selectedLevel]);
|
||||
|
||||
const paginatedCourses :CourseDto[]= useMemo(() => {
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
return filteredCourses.slice(startIndex, startIndex + pageSize);
|
||||
return isCourseLoading ? [] : (filteredCourses.slice(startIndex, startIndex + pageSize) as any as CourseDto[]);
|
||||
}, [filteredCourses, currentPage]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
|
@ -55,6 +71,8 @@ export default function CoursesPage() {
|
|||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
|
@ -69,10 +87,14 @@ export default function CoursesPage() {
|
|||
console.log(category);
|
||||
setSelectedCategory(category);
|
||||
setCurrentPage(1);
|
||||
setIsAll(!category)
|
||||
setSearchParams({ searchValue: ''});
|
||||
}}
|
||||
onLevelChange={(level) => {
|
||||
setSelectedLevel(level);
|
||||
setCurrentPage(1);
|
||||
setIsAll(!level)
|
||||
setSearchParams({ searchValue: ''});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -35,6 +35,10 @@ export function MainHeader() {
|
|||
className="w-72 rounded-full"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
onPressEnter={()=>{
|
||||
//console.log(searchValue)
|
||||
navigate(`/courses/?searchValue=${searchValue}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{isAuthenticated && (
|
||||
|
|
|
@ -77,5 +77,5 @@ export type Course = Post & {
|
|||
export type CourseDto = Course & {
|
||||
enrollments?: Enrollment[];
|
||||
sections?: SectionDto[];
|
||||
terms: Term[];
|
||||
terms: TermDto[];
|
||||
};
|
||||
|
|
|
@ -70,28 +70,27 @@ export const courseDetailSelect: Prisma.PostSelect = {
|
|||
title: true,
|
||||
subTitle: true,
|
||||
content: true,
|
||||
|
||||
level: true,
|
||||
// requirements: true,
|
||||
// objectives: true,
|
||||
// skills: true,
|
||||
// audiences: true,
|
||||
// totalDuration: true,
|
||||
// totalLectures: true,
|
||||
// averageRating: true,
|
||||
// numberOfReviews: true,
|
||||
// numberOfStudents: true,
|
||||
// completionRate: true,
|
||||
state: true,
|
||||
// isFeatured: true,
|
||||
createdAt: true,
|
||||
publishedAt: true,
|
||||
updatedAt: true,
|
||||
// 关联表选择
|
||||
children: {
|
||||
include: {
|
||||
children: true,
|
||||
terms:{
|
||||
select:{
|
||||
id:true,
|
||||
name:true,
|
||||
taxonomy:{
|
||||
select:{
|
||||
id:true,
|
||||
slug:true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
enrollments: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
enrollments: true,
|
||||
meta: true,
|
||||
rating: true,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue