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