feat(hot-resource): add hot resource page with line chart for popularity trends

Change-Id: I13aa76ff02f7f43225bf1736630eec3286bb75a6
diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx
index 764e358..81c8a6b 100644
--- a/src/app/community/page.tsx
+++ b/src/app/community/page.tsx
@@ -118,7 +118,7 @@
       </div>
 
       {/* 全部分类 */}
-      <h1>全部分类</h1>
+      <h1>分类</h1>
       <div className="all-communities-classifications">
         <Link href="/community/resource-community-list/材质包">
           <Image
diff --git a/src/app/globals.scss b/src/app/globals.scss
index 097d350..a2f8cf4 100644
--- a/src/app/globals.scss
+++ b/src/app/globals.scss
@@ -50,20 +50,21 @@
 
 }
 
+.no-underline {
+  text-decoration: none;
+}
+
 .tools {
   display: flex;
   align-items: center;
   gap: 2rem;
 
-  .no-underline {
-    text-decoration: none;
-  }
-
   .tool-item {
     display: flex;
     flex-direction: column;
     align-items: center;
     gap: 0.25rem;
+    transition: color 0.5s ease;
 
     i {
       font-size: 1.25rem;
@@ -74,6 +75,16 @@
       font-size: 0.75rem;
       color: #333;
     }
+
+    &:active {
+      i {
+        color: #14b8a6; // 点击时图标变色
+      }
+
+      span {
+        color: #14b8a6; // 点击时文字变色
+      }
+    }
   }
 
   .p-avatar {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 8d2a66a..e29b4f6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -28,9 +28,12 @@
               <div className="logo-name">
                 <Link href="/" className="no-underline">
                   <img src="/logo.png" alt="Logo" className="logo" />
+
+                </Link>
+                <Link href="/" className="no-underline">
+                  <span className="name">MCPT</span>
                 </Link>
 
-                <span className="name">MCPT</span>
               </div>
               <div className="tools">
                 <Link href="/resource/hot-resource" className="no-underline">
diff --git a/src/app/main.scss b/src/app/main.scss
index 90bf67d..f10a723 100644
--- a/src/app/main.scss
+++ b/src/app/main.scss
@@ -34,6 +34,7 @@
       img {
         border-radius: 8px;
         object-fit: cover;
+        cursor: pointer;
       }
     }
 
@@ -47,8 +48,8 @@
       width: 2.5rem;
       height: 2.5rem;
       border-radius: 50%;
-      background: rgba(255, 255, 255, 0);
-      color: #fff;
+      background: rgba(213, 244, 235, 0.5);
+      color: #ffffff;
       display: flex;
       align-items: center;
       justify-content: center;
@@ -63,7 +64,7 @@
 
     /* 右箭头靠右 */
     .p-carousel-next {
-      right: 0.5rem;
+      right: 1rem;
     }
   }
 }
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a994e7c..fb42dac 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -61,9 +61,6 @@
   resourceId: number;
   resourceName: string;
   resourcePicture: string;
-  likes: number;
-  dowloads: number;
-  seeds: number;
 }
 interface HotResourceList {
   records: HotResource[];
@@ -143,9 +140,7 @@
   // 获取热门资源幻灯片
   const fetchHotResources = async () => {
     try {
-      const response = await axios.get<HotResourceList>(process.env.PUBLIC_URL +`/resource/hot`, {
-        params: { pageNumber: 1, rows: 3, searchValue: '', type: '' }
-      });
+      const response = await axios.get<HotResourceList>(process.env.PUBLIC_URL +`/resource/hot/slide`);
       console.log('获取热门社区幻灯片:', response.data.records);
       setHotResources(response.data.records);
     } catch (err) {
diff --git a/src/app/resource/hot-resource/hot-resource.scss b/src/app/resource/hot-resource/hot-resource.scss
new file mode 100644
index 0000000..587eb15
--- /dev/null
+++ b/src/app/resource/hot-resource/hot-resource.scss
@@ -0,0 +1,192 @@
+.HotResource {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 2rem;
+}
+
+.main-header {
+  display: flex;
+  flex-direction: row;
+  gap: 12px;
+  margin: 0 auto;
+  margin-bottom: 2rem;
+  margin-top: 2rem;
+}
+
+.chart-wrapper {
+  flex: 1.2; // 右侧占一半
+  min-width: 0;
+  display: flex;
+  align-items: center;
+  padding: 0.5rem;
+  justify-content: center;
+  background: #fff;
+  border-radius: 10px;
+
+  canvas {
+    width: 100% !important;
+    height: 100% !important;
+  }
+}
+
+.carousel-wrapper {
+  flex: 0.8;
+
+  .custom-carousel {
+    position: relative;
+
+    .carousel-item {
+      position: relative;
+      max-width: 100px;
+
+      img {
+        border-radius: 10px 10px 10px 10px;
+        border-top-right-radius: 10px;
+        border-bottom-left-radius: 10px;
+        object-fit: cover;
+        cursor: pointer;
+      }
+
+      h3 {
+        width: 480px;
+        text-align: center;
+        bottom: 0;
+        left: 0;
+        color: #fff;
+        background: #14b8a6;
+        padding: 0.5rem 1rem;
+        font-size: 1.75rem;
+        margin: 0;
+        border-radius: 0 0 10px 10px;
+        position: absolute;
+      }
+    }
+
+    .p-carousel-prev,
+    .p-carousel-next {
+      position: absolute;
+      top: 50%;
+      transform: translateY(-50%);
+      z-index: 10;
+      /* 保证在图片之上 */
+      width: 2.5rem;
+      height: 2.5rem;
+      border-radius: 50%;
+      background: rgba(213, 244, 235, 0.5);
+      color: #ffffff;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      pointer-events: auto;
+      /* 确保按钮可点击 */
+    }
+
+    /* 左箭头靠左 */
+    .p-carousel-prev {
+      left: 0.5rem;
+    }
+
+    /* 右箭头靠右 */
+    .p-carousel-next {
+      right: 0.6rem;
+    }
+  }
+}
+
+
+// 全部社区样式
+.all-resources {
+  width: 100%;
+  padding: 1rem;
+
+  &-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  &-card {
+    height: 140px;
+    padding: 1.5rem;
+    margin-bottom: 1rem;
+    border-radius: 0.5rem;
+    transition: transform 0.3s ease;
+    box-shadow: none !important; // 取消阴影
+    cursor: pointer;
+
+    //填充卡片
+    &.p-card.p-component {
+      padding: 0;
+    }
+
+    .p-card-body {
+      padding: 0;
+    }
+
+    &:hover {
+      transform: translateY(-3px);
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    }
+
+    .p-card-content {
+      height: 140px;
+      display: flex;
+      justify-content: space-between;
+      padding: 0;
+    }
+
+    img {
+      border-radius: 0.5rem 0 0 0.5rem;
+      object-fit: cover;
+    }
+
+    .resource-header {
+      display: flex;
+      flex: 1;
+      max-width: 850px;
+      padding-left: 20px;
+      padding-right: 20px;
+      margin-bottom: 20px;
+    }
+
+    .resource-content {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+
+      h3 {
+        font-size: 1.5rem;
+        font-weight: bold;
+        color: #2c3e50;
+      }
+
+      .tags {
+        display: flex;
+        gap: 0.5rem;
+      }
+
+      .resource-introduction {
+        color: #666;
+        font-size: 1rem;
+        margin-bottom: 0;
+      }
+    }
+
+    .resources-states {
+      min-width: 120px;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-end;
+      align-items: flex-end;
+      gap: 0.5rem;
+
+      .state-item {
+        display: flex;
+        align-items: center;
+        gap: 0.5rem;
+        color: #666;
+        font-size: 1rem;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/app/resource/hot-resource/page.tsx b/src/app/resource/hot-resource/page.tsx
index c48b626..022ebe3 100644
--- a/src/app/resource/hot-resource/page.tsx
+++ b/src/app/resource/hot-resource/page.tsx
@@ -1,12 +1,271 @@
 'use client';
-import React from 'react';
 
-const EmptyPage: React.FC = () => {
+import React, { useEffect, useState, useRef } from "react";
+import { Card } from 'primereact/card';
+import { Image } from 'primereact/image';
+import { Carousel } from 'primereact/carousel';
+// 页面跳转
+import { useRouter } from 'next/navigation';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 分页
+import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+// 评分图标
+import { Fire } from '@icon-park/react';
+// 接口传输
+import axios from 'axios';
+// 标签
+import { Tag } from 'primereact/tag';
+
+import { Chart } from 'primereact/chart';
+
+import { TabView, TabPanel } from 'primereact/tabview';
+// 样式
+import './hot-resource.scss';
+
+// 热门资源数据
+
+interface HotResourceSlide {
+  resourceId: number;
+  resourceName: string;
+  resourcePicture: string;
+}
+
+interface HotResourceSlideList {
+  records: HotResourceSlide[];
+}
+interface HotResource {
+  resourceId: number;
+  resourceName: string;
+  resourcePicture: string;
+  resourceSummary: string;
+  lastUpdateTime: string;
+  hot: number;
+  gamePlayList: { gameplayName: string }[];
+}
+interface HotEntry {
+  hot: number;
+}
+interface HotInfo {
+  resourceId: number;
+  resourceName: string;
+  hotList: HotEntry[];
+}
+interface HotInfoResponse {
+  hotInfoList: HotInfo[];
+}
+interface HotResourceList {
+  total: number;
+  records: HotResource[];
+}
+// 主页
+export default function HotResource() {
+  // 热门资源列表
+  const [hotResources, setHotResources] = useState<HotResource[]>([]);
+  const [totalHotResource, setTotalHotResource] = useState(0);
+  const [hotResourceSlide, setHotResourceSlide] = useState<HotResourceSlide[]>([]);
+  // 图表数据
+  const [chartData, setChartData] = useState({});
+  const [chartOptions, setChartOptions] = useState({});
+
+  // 消息提醒
+  const toast = useRef<Toast>(null);
+  const router = useRouter();
+  const [activeTabIndex, setActiveTabIndex] = useState(0);
+  const resourceTabs = [
+    { title: '模组' },
+    { title: '地图' },
+    { title: '整合包' },
+    { title: '材质包' }
+  ];
+  // 当前选中的 classify
+  const [classify, setClassify] = useState<string>(resourceTabs[0].title);
+
+  // 分页
+  const [first, setFirst] = useState(0);
+  const [rows, setRows] = useState(6);
+  const onPageChange = (event: PaginatorPageChangeEvent) => {
+    setFirst(event.first);
+    setRows(event.rows);
+  };
+  // 获取帖子列表
+  useEffect(() => {
+    fetchHotResources(classify);
+  }, [first, rows, classify]);
+
+  useEffect(() => {
+    fetchHotInfo();
+    // 组件加载时获取热门资源幻灯片
+    fetchHotResourcesSlide();
+  }, []);
+
+  const generateColor = (index: number): string => {
+    const colors = [
+      '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0',
+      '#9966FF', '#FF9F40', '#C9CBCF', '#009688'
+    ];
+    return colors[index % colors.length];
+  };
+
+  const fetchHotInfo = async () => {
+    try {
+      const response = await axios.get<HotInfoResponse>(process.env.PUBLIC_URL + `/resource/hot-info`);
+      const hotInfoList = response.data.hotInfoList;
+
+      // 获取最近七天的日期标签(格式:MM-DD)
+      const labels = Array.from({ length: 7 }, (_, i) => {
+        const date = new Date();
+        date.setDate(date.getDate() - (6 - i)); // 向前推6~0天,按时间顺序排列
+        return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+      });
+
+      const datasets = hotInfoList.map((info, idx) => ({
+        label: info.resourceName,
+        data: info.hotList.map(h => h.hot),
+        fill: false,
+        tension: 0.4,
+        borderColor: generateColor(idx),
+        backgroundColor: generateColor(idx)
+      }));
+
+      setChartData({
+        labels,
+        datasets
+      });
+
+      setChartOptions({
+        responsive: true,
+        plugins: {
+          legend: {
+            position: 'right'
+          },
+          title: {
+            display: true,
+            text: '资源热度变化趋势图'
+          }
+        },
+        scales: {
+          x: {
+            title: {
+              display: true,
+              text: '日期'
+            }
+          },
+          y: {
+            title: {
+              display: true,
+              text: '热度'
+            },
+            beginAtZero: true,
+            min: 4,
+            max: 10,
+            ticks: {
+              stepSize: 2,  // ← 每 1 个单位显示一个刻度
+              font: {
+                size: 12
+              }
+            }
+          }
+        }
+      });
+    } catch (err) {
+      console.error('获取资源热度信息失败', err);
+      toast.current?.show({ severity: 'error', summary: 'error', detail: '获取热度信息失败' });
+    }
+  };
+
+  // 获取热门资源幻灯片
+  const fetchHotResourcesSlide = async () => {
+    try {
+      const response = await axios.get<HotResourceSlideList>(process.env.PUBLIC_URL + `/resource/hot/slide`);
+      console.log('获取热门社区幻灯片:', response.data.records);
+      setHotResourceSlide(response.data.records);
+    } catch (err) {
+      console.error('获取Mod失败', err);
+      toast.current?.show({ severity: 'error', summary: 'error', detail: '获取Mod失败' });
+    }
+  };
+  // 获取热门资源
+  const fetchHotResources = async (classify: string) => {
+    try {
+      const pageNumber = first / rows + 1;
+      console.log("当前页" + pageNumber + "size" + rows + "分类" + classify);
+      const response = await axios.get<HotResourceList>(process.env.PUBLIC_URL + `/resource/hot`, {
+        params: { pageNumber, rows, classify }
+      });
+      console.log('获取热门社区:', response.data.records);
+
+      setHotResources(response.data.records);
+      setTotalHotResource(response.data.total);
+    } catch (err) {
+      console.error('获取Mod失败', err);
+      toast.current?.show({ severity: 'error', summary: 'error', detail: '获取Mod失败' });
+    }
+  };
   return (
-    <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
-      {"一个空页面"}
+    <div className="HotResource">
+      {/* 轮播图部分 */}
+      <div className="main-header">
+        <div className="carousel-wrapper">
+          <Carousel
+            value={hotResourceSlide}
+            numVisible={1}
+            numScroll={1}
+            showIndicators={false}
+            showNavigators={true}
+            className="custom-carousel"
+            itemTemplate={(hotResource) => (
+              <div className="carousel-item" onClick={() => router.push(`/resource/resource-detail/${hotResource.resourceId}`)}>
+                <Image alt="slide" src={process.env.NEXT_PUBLIC_NGINX_URL + hotResource.resourcePicture} className="carousel-avatar" width="480" height="350" />
+                <h3>{hotResource.resourceName}</h3>
+              </div>
+            )}
+          />
+        </div>
+        <div className="chart-wrapper">
+          <Chart type="line" data={chartData} options={chartOptions} />
+        </div>
+      </div>
+      {/* 全部社区 */}
+      <TabView scrollable className="all-resources" activeIndex={activeTabIndex} onTabChange={(e) => {
+        setActiveTabIndex(e.index);
+        const newClassify = resourceTabs[e.index].title;
+        setClassify(newClassify);
+      }}>
+        {resourceTabs.map((tab) => {
+          return (
+            <TabPanel key={tab.title} header={tab.title}>
+              <div className="all-resources-list">
+                {hotResources.map((hotResource) => (
+                  <Card key={hotResource.resourceId} className="all-resources-card" onClick={() => router.push(`/resource/resource-detail/${hotResource.resourceId}`)}>
+                    <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + "hotResource/" + hotResource.resourcePicture} className="resource-avatar" width="250" height="140" />
+                    <div className="resource-header">
+                      <div className="resource-content">
+                        <h3>{hotResource.resourceName}</h3>
+                        <div className="tags">
+                          {hotResource.gamePlayList.map((tag, index) => (
+                            <Tag key={index} value={tag.gameplayName} />
+                          ))}
+                        </div>
+                      </div>
+                      <div className="resources-states">
+                        <div className="state-item">
+                          <Fire theme="outline" size="16" fill="#FF8D1A" />
+                          <span>热度: {hotResource.hot}</span>
+                        </div>
+                        <div className="state-item">
+                          <span>最新更新时间: {hotResource.lastUpdateTime}</span>
+                        </div>
+                      </div>
+                    </div>
+                  </Card>
+                ))}
+              </div>
+            </TabPanel>
+          );
+        })}
+        {totalHotResource > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalHotResource} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)}
+      </TabView>
     </div>
   );
-};
-
-export default EmptyPage;
+}