feat(hot-resource): add hot resource page with line chart for popularity trends
Change-Id: I13aa76ff02f7f43225bf1736630eec3286bb75a6
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;
+}