blob: e366843f89de11b9fb1ad73c402866908d89a656 [file] [log] [blame]
LaoeGaociee7c5772025-05-28 12:34:47 +08001'use client';
2
3import React, { useEffect, useState, useRef } from "react";
4import { InputText } from 'primereact/inputtext';
5import { Button } from 'primereact/button';
6import { Card } from 'primereact/card';
7import { Image } from 'primereact/image';
8import { Dropdown } from 'primereact/dropdown';
9// 页面跳转
10import { useParams } from 'next/navigation'
11import { useRouter } from 'next/navigation';
12// 分页
13import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
14// 评分图标
15import { Fire } from '@icon-park/react';
16// 消息提醒
17import { Toast } from 'primereact/toast';
18// 发布帖子
19import { Dialog } from 'primereact/dialog';
20import { FileUpload } from 'primereact/fileupload';
21import { InputTextarea } from 'primereact/inputtextarea';
22// 接口传输
23import axios from 'axios';
24// 防抖函数
25import { debounce } from 'lodash';
LaoeGaocid0773912025-06-09 00:38:40 +080026import { useLocalStorage } from '../../../hook/useLocalStorage';
LaoeGaociee7c5772025-05-28 12:34:47 +080027// 样式
28import './resource-community.scss';
29
LaoeGaocid0773912025-06-09 00:38:40 +080030interface User {
LaoeGaoci36b5bc82025-06-09 20:52:39 +080031 Id: number;
LaoeGaocid0773912025-06-09 00:38:40 +080032}
LaoeGaociee7c5772025-05-28 12:34:47 +080033// 帖子列表数据
34interface Thread {
35 threadId: number;
36 userId: number;
37 threadPicture: string;
38 title: string;
39 createdAt: string;
40 communityId: string;
41 likes: number;
42}
43interface ThreadList {
44 records: Thread[];
45}
46// 社区信息
47interface CommunityInfo {
48 communityId: string;
49 communityName: string;
50 communityPicture: string;
51 description: string;
52 hot: number;
53 type: string;
54 threadNumber: number;
55}
56
57
58// 社区详情页面
59export default function CommunityDetailPage() {
LaoeGaocid0773912025-06-09 00:38:40 +080060 const user = useLocalStorage<User>('user');
61 const userId: number = user?.Id ?? -1;
LaoeGaociee7c5772025-05-28 12:34:47 +080062 // 获取URL参数,页面跳转
63 const params = useParams<{ communityId: string }>()
64 const communityId = decodeURIComponent(params.communityId); // 防止中文路径乱码
65 const router = useRouter();
66 // 社区数据
67 const [communityInfo, setCommunityInfo] = useState<CommunityInfo | null>(null);
68 // 帖子列表数据
69 const [threads, setThreads] = useState<Thread[]>([]);
70 const [totalThreads, setTotalThreads] = useState<number>(0);
71 // 搜索框
72 const [searchValue, setSearchValue] = useState("");
73 const debouncedSearch = useRef(
74 debounce((value: string) => {
75 setSearchValue(value);
76 }, 600)
77 ).current;
78 // 消息提醒
79 const toast = useRef<Toast>(null);
80 // 筛选器选项数据
81 const [selectedOption, setSelectedOption] = useState({ name: '热度最高' });
82 const options = [
83 { name: '来自好友' },
84 { name: '热度最高' }
85 ];
86 // 分页
87 const [first, setFirst] = useState(0);
88 const [rows, setRows] = useState(6);
89 const onPageChange = (event: PaginatorPageChangeEvent) => {
90 setFirst(event.first);
91 setRows(event.rows);
92 };
93 // 获取社区信息
94 useEffect(() => {
95 const fetchThreadInfo = async () => {
96 try {
LaoeGaoci388f7762025-05-29 22:24:35 +080097 const { data } = await axios.get(process.env.PUBLIC_URL + `/community/info?communityId=${communityId}`);
LaoeGaociee7c5772025-05-28 12:34:47 +080098 setCommunityInfo(data);
99 setTotalThreads(data.threadNumber);
100 } catch (err) {
101 console.error(err);
102 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区信息失败' });
103 }
104 };
105 fetchThreadInfo();
106 }, [communityId]);
107
108 // 获取帖子列表
109 useEffect(() => {
110 fetchThreads();
111 }, [communityId, first, rows, selectedOption, searchValue]);
112
113 const fetchThreads = async () => {
114 try {
LaoeGaoci388f7762025-05-29 22:24:35 +0800115 const pageNumber = first / rows + 1;
116 console.log("当前页" + pageNumber + "size" + rows + "搜索内容" + searchValue);
LaoeGaociee7c5772025-05-28 12:34:47 +0800117 const option = selectedOption.name // 添加排序参数
118 const response = await axios.get<ThreadList>(
LaoeGaoci388f7762025-05-29 22:24:35 +0800119 process.env.PUBLIC_URL + `/community/threads`, {
LaoeGaocid0773912025-06-09 00:38:40 +0800120 params: { userId, communityId, pageNumber, rows, option, searchValue }
LaoeGaociee7c5772025-05-28 12:34:47 +0800121 }
122 );
123 console.log('获取帖子列表:', response.data.records);
124 setThreads(response.data.records);
125 } catch (err) {
126 console.error('获取帖子失败', err);
127 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取帖子失败' });
128 }
129 };
130
131 // 发布帖子弹窗
132 const [visible, setVisible] = useState(false);
133 const [formData, setFormData] = useState({
134 title: '',
135 content: '',
136 threadPicture: ''
137 });
138 // 图片上传消息通知
LaoeGaoci36b5bc82025-06-09 20:52:39 +0800139
LaoeGaociee7c5772025-05-28 12:34:47 +0800140
141 // 发帖接口
142 const handleSubmit = async () => {
143 try {
144 const currentDate = new Date().toISOString();
145 const postData = {
LaoeGaocid0773912025-06-09 00:38:40 +0800146 userId, // 记得用户登录状态获取
LaoeGaociee7c5772025-05-28 12:34:47 +0800147 threadPicture: formData.threadPicture,
148 title: formData.title,
149 content: formData.content,
150 createdAt: currentDate,
151 communityId: communityId // 从URL参数获取的社区ID
152 };
153 // 发送POST请求
LaoeGaoci388f7762025-05-29 22:24:35 +0800154 const response = await axios.post(process.env.PUBLIC_URL + '/thread', postData);
LaoeGaociee7c5772025-05-28 12:34:47 +0800155
156 if (response.status === 200) {
157 toast.current?.show({ severity: 'success', summary: 'Success', detail: '帖子发布成功' });
158 // 发帖成功
159 setVisible(false);
160 // 重置表单
161 setFormData({
162 title: '',
163 content: '',
164 threadPicture: ''
165 });
166 // 可以刷新帖子列表
167 fetchThreads();
168 }
169 } catch (error) {
170 console.error('发帖失败:', error);
171 toast.current?.show({ severity: 'error', summary: 'error', detail: '帖子发布失败' });
172 }
173 };
174 return (
175 <div className="resource-community">
176 <Toast ref={toast}></Toast>
177 {/* 社区标题和介绍 */}
178 <div className="community-header">
179 <div className="title-section">
180 <h1>{communityInfo?.communityName}</h1>
181 <p className="subtitle">{communityInfo?.description}</p>
182 <div className="community-states">
183 <div className="state-item">
184 <Fire theme="outline" size="16" fill="#FF8D1A" />
185 <span>热度: {communityInfo?.hot}</span>
186 </div>
187 <div className="state-item">
188 <i className="pi pi-book" />
189 <span>帖子数: {totalThreads}</span>
190 </div>
191 </div>
192 </div>
193 <div className="input">
LaoeGaociee7c5772025-05-28 12:34:47 +0800194 <div className="action-section">
LaoeGaoci388f7762025-05-29 22:24:35 +0800195 <div className="searchBar">
LaoeGaociee7c5772025-05-28 12:34:47 +0800196 <i className="pi pi-search" />
197 <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
198 </div>
199 <Dropdown
200 value={selectedOption}
201 onChange={(e) => setSelectedOption(e.value)}
202 options={options}
203 optionLabel="name"
204 className="select-dropdown"
205 />
206 </div>
207 </div>
208 </div>
209
210 {/* 帖子列表 */}
211 <div className="thread-list">
212 <div className="resource-grid">
213 {threads.map((thread) => (
214 <Card key={thread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${thread.threadId}`)}>
215 <Image
216 src={process.env.NEXT_PUBLIC_NGINX_URL + thread.threadPicture}
217 alt={thread.title}
218 width="368"
219 height="200"
220 />
221 <div className="card-content">
222 <h3>{thread.title}</h3>
223 <div className="view-count">
224 <i className="pi pi-face-smile" />
225 <span>{thread.likes}</span>
226 </div>
227 </div>
228 </Card>
229 ))}
230 </div>
231 {totalThreads > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalThreads} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)}
232 </div>
233
234 {/* 添加按钮 */}
235 <Button className="add-resource-button" icon="pi pi-plus" rounded aria-label="add-thread" onClick={() => setVisible(true)} />
236
237 {/* 发布帖子弹窗 */}
238 <Dialog
239 header="发布新帖子"
240 visible={visible}
241 onHide={() => setVisible(false)}
242 className="publish-dialog"
243 modal
244 footer={
245 <div className="dialog-footer">
246 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
247 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
248 </div>
249 }
250 >
251 <div className="publish-form">
252 <div className="form-field">
253 <label htmlFor="title">标题</label>
254 <InputText
255 id="title"
256 value={formData.title}
257 onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
258 placeholder="请输入帖子标题"
259 className="w-full"
260 />
261 </div>
262
263 <div className="form-field">
264 <label htmlFor="content">内容</label>
265 <InputTextarea
266 id="content"
267 value={formData.content}
268 onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
269 rows={5}
270 placeholder="请输入帖子内容"
271 className="w-full"
272 />
273 </div>
274
275 <div className="form-field">
276 <label>封面图片</label>
277 <FileUpload
LaoeGaoci36b5bc82025-06-09 20:52:39 +0800278 mode="advanced"
279 name="file"
280 customUpload
281 uploadHandler={async (e) => {
282 const formData = new FormData();
283 formData.append("file", e.files[0]);
284
285 try {
286 const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
287
288 const fileUrl = res.data.url;
289 console.log(fileUrl);
290 toast.current?.show({ severity: 'success', summary: '上传成功' });
291 } catch (error) {
292 console.log(error);
293 toast.current?.show({ severity: 'error', summary: '上传失败' });
294 }
295 }}
296 auto
LaoeGaociee7c5772025-05-28 12:34:47 +0800297 accept="image/*"
LaoeGaoci36b5bc82025-06-09 20:52:39 +0800298 chooseLabel="上传帖子封面"
LaoeGaociee7c5772025-05-28 12:34:47 +0800299 />
300 </div>
301 </div>
302 </Dialog>
303 </div>
304 );
305}