blob: 6b419696ec5b2343b06fa8799f2b2bd3b0d49cb3 [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>('');
LaoeGaoci68253852025-06-09 22:42:18 +080064
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080065 const debouncedSearch = useRef(
66 debounce((value: string) => {
67 setSearchValue(value);
68 }, 600)
69 ).current;
70 // 分页
71 const [first, setFirst] = useState(0);
72 const [rows, setRows] = useState(5);
73 const onPageChange = (event: PaginatorPageChangeEvent) => {
74 setFirst(event.first);
75 setRows(event.rows);
76 };
77 // 获取帖子列表
78 useEffect(() => {
79 handleSearch();
LaoeGaocid0773912025-06-09 00:38:40 +080080 }, [first, rows, searchValue]);
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080081
82 const handleSearch = async () => {
83 try {
84 const pageNumber = first / rows + 1;
85 console.log(searchValue + " 当前页: " + pageNumber + "rows: " + rows + "selectedClassify: " + selectedClassify + "selectedGameplay: " + selectedGameplay + "selectedVersions: " + selectedVersions);
86 const response = await axios.get<HotResourceList>(process.env.PUBLIC_URL + `/resource/search`, {
87 params: {
LaoeGaocid0773912025-06-09 00:38:40 +080088 userId,
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080089 pageNumber,
90 rows,
91 classify: selectedClassify,
Seamherbb14ecb2025-06-09 22:37:20 +080092 gameplayList: selectedGameplay.join(',') ,
93 gameVersionList: selectedVersions.join(','),
LaoeGaocia8f7d6c2025-06-04 15:39:49 +080094 searchValue
95 }
96 });
97 setHotResources(response.data.records);
98 setTotalHotResource(response.data.total);
99 setVisibleRight(false);
100 } catch (err) {
101 console.error('搜索资源失败', err);
102 toast.current?.show({ severity: 'error', summary: 'error', detail: '搜索资源失败' });
103 }
104 };
105
106 return (
107 <div className="HotResource">
108 <div className="header">
109 <div className="searchBar">
110 <i className="pi pi-search" />
111 <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
112 </div>
113 <Button label="查看分类" className="classificationButton" onClick={() => setVisibleRight(true)} />
114 </div>
115 {/* 全部社区 */}
116 <div className="all-resources-list">
117 {hotResources.map((hotResource) => (
118 <Card key={hotResource.resourceId} className="all-resources-card" onClick={() => router.push(`/resource/resource-detail/${hotResource.resourceId}`)}>
Seamherbb14ecb2025-06-09 22:37:20 +0800119 <Image alt="avatar" src={ "hotResource/" + hotResource.resourcePicture} className="resource-avatar" width="250" height="140" />
LaoeGaocia8f7d6c2025-06-04 15:39:49 +0800120 <div className="resource-header">
121 <div className="resource-content">
122 <h3>{hotResource.resourceName}</h3>
123 <div className="tags">
LaoeGaoci24bc0a52025-06-09 16:52:40 +0800124 {hotResource.gameplayList.map((tag, index) => (
125 <Tag key={index} value={tag} />
LaoeGaocia8f7d6c2025-06-04 15:39:49 +0800126 ))}
127 </div>
128 </div>
129 <div className="resources-states">
130 <div className="state-item">
131 <Fire theme="outline" size="16" fill="#FF8D1A" />
132 <span>热度: {hotResource.hot}</span>
133 </div>
134 <div className="state-item">
135 <span>最新更新时间: {hotResource.lastUpdateTime}</span>
136 </div>
137 </div>
138 </div>
139 </Card>
140 ))}
141 </div>
142 {totalHotResource > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalHotResource} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
143 <Sidebar className="sidebar" visible={visibleRight} position="right" onHide={() => setVisibleRight(false)}>
144 <div className="sidebar-content">
145 <h3>分类</h3>
146 <div className="filter-section">
147 {['模组', '地图', '整合包', '材质包'].map((item) => (
148 <Button key={item} label={item} className={selectedClassify === item ? 'p-button-primary' : 'p-button-text'} onClick={() => setSelectedClassify(item)} />
149 ))}
150 </div>
151
152 <h3>主要玩法</h3>
153 <div className="filter-section">
154 {['不限', '科技', '魔法', '建筑', '风景', '竞技', '生存', '冒险', '跑酷', '艺术', '剧情', '社交', '策略', '极限'].map((gameplay) => (
155 <Button
156 key={gameplay}
157 label={gameplay}
158 className={selectedGameplay.includes(gameplay) ? 'p-button-primary' : 'p-button-text'}
159 onClick={() => {
160 if (selectedGameplay.includes(gameplay)) {
161 setSelectedGameplay(selectedGameplay.filter((g) => g !== gameplay));
162 } else {
163 setSelectedGameplay([...selectedGameplay, gameplay]);
164 }
165 }}
166 />
167 ))}
168 </div>
169
170 <h3>游戏版本</h3>
171 <div className="filter-section">
172 {['不限', '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) => (
173 <Button
174 key={ver}
175 label={ver}
176 className={selectedVersions.includes(ver) ? 'p-button-primary' : 'p-button-text'}
177 onClick={() => {
178 if (selectedVersions.includes(ver)) {
179 setSelectedVersions(selectedVersions.filter((v) => v !== ver));
180 } else {
181 setSelectedVersions([...selectedVersions, ver]);
182 }
183 }}
184 />
185 ))}
186 </div>
187 <div className="filter-confirm">
188 <Button label="确定" icon="pi pi-check" onClick={handleSearch} />
189 </div>
190 </div>
191 </Sidebar>
192 </div>
193 );
194}