blob: f147ed2067b838efad7f313cd0e62e34fd4af04c [file] [log] [blame]
LaoeGaocia8f7d6c2025-06-04 15:39:49 +08001'use client';
2
3import React, { useEffect, useState, useRef } from "react";
4import { Card } from 'primereact/card';
5import { Image } from 'primereact/image';
6import { Button } from 'primereact/button';
7import { InputText } from 'primereact/inputtext';
8// 页面跳转
9import { useRouter } from 'next/navigation';
10// 消息提醒
11import { Toast } from 'primereact/toast';
12// 分页
13import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
14// 评分图标
15import { Fire } from '@icon-park/react';
16// 接口传输
17import axios from 'axios';
18// 标签
19import { Tag } from 'primereact/tag';
20import { Sidebar } from 'primereact/sidebar';
21// 防抖函数
22import { debounce } from 'lodash';
LaoeGaocid0773912025-06-09 00:38:40 +080023import { useLocalStorage } from '../../hook/useLocalStorage';
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080024// 样式
25import './classification.scss';
26
LaoeGaocid0773912025-06-09 00:38:40 +080027interface User {
28 Id: number;
29}
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080030// 热门资源数据
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080031interface HotResource {
32 resourceId: number;
33 resourceName: string;
34 resourcePicture: string;
35 resourceSummary: string;
36 lastUpdateTime: string;
37 hot: number;
LaoeGaoci24bc0a52025-06-09 16:52:40 +080038 gameplayList: string[];
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080039}
40
41interface HotResourceList {
42 total: number;
43 records: HotResource[];
44}
45// 主页
46export default function ClassificationResource() {
LaoeGaocid0773912025-06-09 00:38:40 +080047 const user = useLocalStorage<User>('user');
48 const userId: number = user?.Id ?? -1;
49
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080050 // 热门资源列表
51 const [hotResources, setHotResources] = useState<HotResource[]>([]);
52 const [totalHotResource, setTotalHotResource] = useState(0);
53 const [visibleRight, setVisibleRight] = useState<boolean>(false);
54
55 // 消息提醒
56 const toast = useRef<Toast>(null);
57 const router = useRouter();
58
59 // 当前选中的 classify
60 const [selectedClassify, setSelectedClassify] = useState<string>('模组');
61 const [selectedGameplay, setSelectedGameplay] = useState<string[]>([]);
62 const [selectedVersions, setSelectedVersions] = useState<string[]>([]);
63 const [searchValue, setSearchValue] = useState<string>('');
64 const debouncedSearch = useRef(
65 debounce((value: string) => {
66 setSearchValue(value);
67 }, 600)
68 ).current;
69 // 分页
70 const [first, setFirst] = useState(0);
71 const [rows, setRows] = useState(5);
72 const onPageChange = (event: PaginatorPageChangeEvent) => {
73 setFirst(event.first);
74 setRows(event.rows);
75 };
76 // 获取帖子列表
77 useEffect(() => {
78 handleSearch();
LaoeGaocid0773912025-06-09 00:38:40 +080079 }, [first, rows, searchValue]);
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080080
81 const handleSearch = async () => {
82 try {
83 const pageNumber = first / rows + 1;
84 console.log(searchValue + " 当前页: " + pageNumber + "rows: " + rows + "selectedClassify: " + selectedClassify + "selectedGameplay: " + selectedGameplay + "selectedVersions: " + selectedVersions);
85 const response = await axios.get<HotResourceList>(process.env.PUBLIC_URL + `/resource/search`, {
86 params: {
LaoeGaocid0773912025-06-09 00:38:40 +080087 userId,
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080088 pageNumber,
89 rows,
90 classify: selectedClassify,
91 gameplayList: selectedGameplay.join(','),
92 versionList: selectedVersions.join(','),
93 searchValue
94 }
95 });
96 setHotResources(response.data.records);
97 setTotalHotResource(response.data.total);
98 setVisibleRight(false);
99 } catch (err) {
100 console.error('搜索资源失败', err);
101 toast.current?.show({ severity: 'error', summary: 'error', detail: '搜索资源失败' });
102 }
103 };
104
105 return (
106 <div className="HotResource">
107 <div className="header">
108 <div className="searchBar">
109 <i className="pi pi-search" />
110 <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
111 </div>
112 <Button label="查看分类" className="classificationButton" onClick={() => setVisibleRight(true)} />
113 </div>
114 {/* 全部社区 */}
115 <div className="all-resources-list">
116 {hotResources.map((hotResource) => (
117 <Card key={hotResource.resourceId} className="all-resources-card" onClick={() => router.push(`/resource/resource-detail/${hotResource.resourceId}`)}>
118 <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + "hotResource/" + hotResource.resourcePicture} className="resource-avatar" width="250" height="140" />
119 <div className="resource-header">
120 <div className="resource-content">
121 <h3>{hotResource.resourceName}</h3>
122 <div className="tags">
LaoeGaoci24bc0a52025-06-09 16:52:40 +0800123 {hotResource.gameplayList.map((tag, index) => (
124 <Tag key={index} value={tag} />
LaoeGaocia8f7d6c2025-06-04 15:39:49 +0800125 ))}
126 </div>
127 </div>
128 <div className="resources-states">
129 <div className="state-item">
130 <Fire theme="outline" size="16" fill="#FF8D1A" />
131 <span>热度: {hotResource.hot}</span>
132 </div>
133 <div className="state-item">
134 <span>最新更新时间: {hotResource.lastUpdateTime}</span>
135 </div>
136 </div>
137 </div>
138 </Card>
139 ))}
140 </div>
141 {totalHotResource > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalHotResource} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
142 <Sidebar className="sidebar" visible={visibleRight} position="right" onHide={() => setVisibleRight(false)}>
143 <div className="sidebar-content">
144 <h3>分类</h3>
145 <div className="filter-section">
146 {['模组', '地图', '整合包', '材质包'].map((item) => (
147 <Button key={item} label={item} className={selectedClassify === item ? 'p-button-primary' : 'p-button-text'} onClick={() => setSelectedClassify(item)} />
148 ))}
149 </div>
150
151 <h3>主要玩法</h3>
152 <div className="filter-section">
153 {['不限', '科技', '魔法', '建筑', '风景', '竞技', '生存', '冒险', '跑酷', '艺术', '剧情', '社交', '策略', '极限'].map((gameplay) => (
154 <Button
155 key={gameplay}
156 label={gameplay}
157 className={selectedGameplay.includes(gameplay) ? 'p-button-primary' : 'p-button-text'}
158 onClick={() => {
159 if (selectedGameplay.includes(gameplay)) {
160 setSelectedGameplay(selectedGameplay.filter((g) => g !== gameplay));
161 } else {
162 setSelectedGameplay([...selectedGameplay, gameplay]);
163 }
164 }}
165 />
166 ))}
167 </div>
168
169 <h3>游戏版本</h3>
170 <div className="filter-section">
171 {['不限', '1.20.1', '1.20.4', '1.7.3', '1.12.1', '1.19.2', '1.18.3', '1.20.3', '1.19.1', '1.18.6'].map((ver) => (
172 <Button
173 key={ver}
174 label={ver}
175 className={selectedVersions.includes(ver) ? 'p-button-primary' : 'p-button-text'}
176 onClick={() => {
177 if (selectedVersions.includes(ver)) {
178 setSelectedVersions(selectedVersions.filter((v) => v !== ver));
179 } else {
180 setSelectedVersions([...selectedVersions, ver]);
181 }
182 }}
183 />
184 ))}
185 </div>
186 <div className="filter-confirm">
187 <Button label="确定" icon="pi pi-check" onClick={handleSearch} />
188 </div>
189 </div>
190 </Sidebar>
191 </div>
192 );
193}