This commit is contained in:
ditiqi 2025-02-25 20:42:42 +08:00
commit ba6801898d
5 changed files with 107 additions and 139 deletions

View File

@ -1,22 +1,13 @@
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { Typography, Button, Spin } from "antd";
import { Typography, Skeleton } from "antd";
import { stringToColor, TaxonomySlug, TermDto } from "@nice/common";
import { api } from "@nice/client";
import { ControlOutlined } from "@ant-design/icons";
import { useNavigate, useSearchParams } from "react-router-dom";
import LookForMore from "./LookForMore";
import CategorySectionCard from "./CategorySectionCard";
const { Title, Text } = Typography;
interface CourseCategory {
name: string;
count: number;
description: string;
}
const CategorySection = () => {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [showAll, setShowAll] = useState(false);
//获得分类
const {
data: courseCategoriesData,
isLoading,
@ -26,31 +17,8 @@ const CategorySection = () => {
slug: TaxonomySlug.CATEGORY,
},
},
include: {
children: true,
},
orderBy: {
createdAt: "desc", // 按创建时间降序排列
},
take: 8,
});
// 分类展示
const [displayedCategories, setDisplayedCategories] = useState<TermDto[]>(
[]
);
useEffect(() => {
console.log(courseCategoriesData);
// 如果 showAll 为 true则显示所有分类数据
// 如果 showAll 为 false则只显示前 8 个分类数据,
if (!isLoading) {
if (showAll) {
setDisplayedCategories(courseCategoriesData);
} else {
setDisplayedCategories(courseCategoriesData.slice(0, 8));
}
}
}, [courseCategoriesData, showAll]);
const handleMouseEnter = useCallback((index: number) => {
setHoveredIndex(index);
}, []);
@ -58,9 +26,6 @@ const CategorySection = () => {
const handleMouseLeave = useCallback(() => {
setHoveredIndex(null);
}, []);
const navigate = useNavigate();
return (
<section className="py-32 relative overflow-hidden">
<div className="max-w-screen-2xl mx-auto px-4 relative">
@ -76,97 +41,27 @@ const CategorySection = () => {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{isLoading ? (
<Spin></Spin>
<Skeleton paragraph={{ rows: 4 }}></Skeleton>
) : (
displayedCategories.map((category, index) => {
courseCategoriesData.map((category, index) => {
const categoryColor = stringToColor(category.name);
const isHovered = hoveredIndex === index;
return (
<div
<CategorySectionCard
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}`
);
window.scrollTo({
top: 0,
behavior: "smooth",
});
}}>
<div className="absolute -inset-0.5 bg-gradient-to-r from-gray-200 to-gray-300 opacity-50 rounded-2xl transition-all duration-700 group-hover:opacity-75" />
<div
className={`absolute inset-0 rounded-2xl bg-gradient-to-br from-white to-gray-50 shadow-lg transition-all duration-700 ease-out ${
isHovered
? "scale-[1.02] bg-opacity-95"
: "scale-100 bg-opacity-90"
}`}
/>
<div
className={`absolute inset-0 rounded-2xl transition-all duration-700 ease-out ${
isHovered
? "shadow-[0_8px_30px_rgb(0,0,0,0.12)]"
: "shadow-none opacity-0"
}`}
/>
<div
className={`absolute top-0 left-1/2 -translate-x-1/2 h-1 rounded-full transition-all duration-500 ease-out `}
style={{
backgroundColor: categoryColor,
}}
/>
<div className="relative w-full h-full p-6">
<div className="flex w-2/3 absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 flex-col space-y-4 mb-4">
<Text
strong
className="text-xl font-medium tracking-wide">
{category.name}
</Text>
</div>
<Text
type="secondary"
className="block text-sm leading-relaxed opacity-90">
{category.description}
</Text>
<div
className={` mt-6 absolute bottom-4 right-6 text-sm font-medium flex items-center space-x-2 transition-all duration-500 ease-out `}
style={{ color: categoryColor }}>
<span></span>
<span
className={`transform transition-all duration-500 ease-out `}>
</span>
</div>
</div>
</div>
index={index}
category={category}
categoryColor={categoryColor}
isHovered={isHovered}
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
/>
);
})
)}
</div>
{!isLoading && (
<div className="flex justify-center mt-12">
<Button
type="default"
size="large"
className="px-8 h-12 text-base font-medium hover:shadow-md transition-all duration-300"
onClick={() => {
//setShowAll(!showAll)
navigate("/courses");
window.scrollTo({ top: 0, behavior: "smooth" });
}}>
{showAll ? "收起" : "查看更多分类"}
</Button>
</div>
)}
<LookForMore to={"/courses"}></LookForMore>
</div>
</section>
);

View File

@ -0,0 +1,64 @@
import { useNavigate } from "react-router-dom";
import { Typography } from "antd";
export default function CategorySectionCard({index,handleMouseEnter,handleMouseLeave,category,categoryColor,isHovered,}) {
const navigate = useNavigate()
const { Title, Text } = Typography;
return (
<div
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}`
);
window.scrollTo({
top: 0,
behavior: "smooth",
});
}}>
<div className="absolute -inset-0.5 bg-gradient-to-r from-gray-200 to-gray-300 opacity-50 rounded-2xl transition-all duration-700 group-hover:opacity-75" />
<div
className={`absolute inset-0 rounded-2xl bg-gradient-to-br from-white to-gray-50 shadow-lg transition-all duration-700 ease-out ${isHovered
? "scale-[1.02] bg-opacity-95"
: "scale-100 bg-opacity-90"
}`}
/>
<div
className={`absolute inset-0 rounded-2xl transition-all duration-700 ease-out ${isHovered
? "shadow-[0_8px_30px_rgb(0,0,0,0.12)]"
: "shadow-none opacity-0"
}`}
/>
<div
className={`absolute w-1/2 top-0 left-1/2 -translate-x-1/2 h-1 rounded-full transition-all duration-500 ease-out `}
style={{
backgroundColor: categoryColor,
}}
/>
<div className="relative w-full h-full p-6">
<div className="flex w-2/3 absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 flex-col space-y-4 mb-4">
<Text
strong
className="text-xl font-medium tracking-wide">
{category.name}
</Text>
</div>
<div
className={` mt-6 absolute bottom-4 right-6 text-sm font-medium flex items-center space-x-2 transition-all duration-500 ease-out `}
style={{ color: categoryColor }}>
<span></span>
<span
className={`transform transition-all duration-500 ease-out `}>
</span>
</div>
</div>
</div>
)
}

View File

@ -1,11 +1,10 @@
import React, { useState, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Typography, Skeleton } from "antd";
import { ArrowRightOutlined } from "@ant-design/icons";
import { Typography, Skeleton } from "antd";
import { TaxonomySlug, TermDto } from "@nice/common";
import { api } from "@nice/client";
import { CoursesSectionTag } from "./CoursesSectionTag";
import CourseList from "../../../../components/models/course/list/CourseList";
import CourseList from "@web/src/components/models/course/list/CourseList";
import LookForMore from "./LookForMore";
interface GetTaxonomyProps {
categories: string[];
isLoading: boolean;
@ -39,7 +38,6 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
description,
initialVisibleCoursesCount = 8,
}) => {
const navigate = useNavigate();
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
const gateGory: GetTaxonomyProps = useGetTaxonomy({
type: TaxonomySlug.CATEGORY,
@ -97,20 +95,7 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
}}
showPagination={false}
cols={4}></CourseList>
{
<div className="flex items-center gap-4 justify-between mt-12">
<div className="h-[1px] flex-grow bg-gradient-to-r from-transparent via-gray-300 to-transparent"></div>
<div className="flex justify-end">
<Button
type="link"
onClick={() => navigate("/courses")}
className="flex items-center gap-2 text-gray-600 hover:text-blue-600 font-medium transition-colors duration-300">
<ArrowRightOutlined />
</Button>
</div>
</div>
}
<LookForMore to={"/courses"}></LookForMore>
</div>
</section>
);

View File

@ -0,0 +1,24 @@
import { ArrowRightOutlined } from "@ant-design/icons";
import { Button } from "antd";
import { useNavigate } from "react-router-dom";
export default function LookForMore({to}:{to:string}) {
const navigate = useNavigate();
return (
<>
<div className="flex items-center gap-4 justify-between mt-12">
<div className="h-[1px] flex-grow bg-gradient-to-r from-transparent via-gray-300 to-transparent"></div>
<div className="flex justify-end">
<Button
type="link"
onClick={() => navigate(to)}
className="flex items-center gap-2 text-gray-600 hover:text-blue-600 font-medium transition-colors duration-300">
<ArrowRightOutlined />
</Button>
</div>
</div>
</>
)
}

View File

@ -58,7 +58,7 @@ export default function CourseList({
<div className="space-y-6">
{courses.length > 0 ? (
<>
<div className={`grid grid-cols-${cols} gap-6`}>
<div className={`grid lg:grid-cols-${cols} gap-6`}>
{isLoading ? (
<Skeleton paragraph={{ rows: 5 }}></Skeleton>
) : (