blob: 6beb423d526cfda5268edb71349257b59376102f [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 {
31 Id: number;
32}
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 // 图片上传消息通知
139 const onUpload = () => {
140 toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
141 };
142
143 // 发帖接口
144 const handleSubmit = async () => {
145 try {
146 const currentDate = new Date().toISOString();
147 const postData = {
LaoeGaocid0773912025-06-09 00:38:40 +0800148 userId, // 记得用户登录状态获取
LaoeGaociee7c5772025-05-28 12:34:47 +0800149 threadPicture: formData.threadPicture,
150 title: formData.title,
151 content: formData.content,
152 createdAt: currentDate,
153 communityId: communityId // 从URL参数获取的社区ID
154 };
155 // 发送POST请求
LaoeGaoci388f7762025-05-29 22:24:35 +0800156 const response = await axios.post(process.env.PUBLIC_URL + '/thread', postData);
LaoeGaociee7c5772025-05-28 12:34:47 +0800157
158 if (response.status === 200) {
159 toast.current?.show({ severity: 'success', summary: 'Success', detail: '帖子发布成功' });
160 // 发帖成功
161 setVisible(false);
162 // 重置表单
163 setFormData({
164 title: '',
165 content: '',
166 threadPicture: ''
167 });
168 // 可以刷新帖子列表
169 fetchThreads();
170 }
171 } catch (error) {
172 console.error('发帖失败:', error);
173 toast.current?.show({ severity: 'error', summary: 'error', detail: '帖子发布失败' });
174 }
175 };
176 return (
177 <div className="resource-community">
178 <Toast ref={toast}></Toast>
179 {/* 社区标题和介绍 */}
180 <div className="community-header">
181 <div className="title-section">
182 <h1>{communityInfo?.communityName}</h1>
183 <p className="subtitle">{communityInfo?.description}</p>
184 <div className="community-states">
185 <div className="state-item">
186 <Fire theme="outline" size="16" fill="#FF8D1A" />
187 <span>热度: {communityInfo?.hot}</span>
188 </div>
189 <div className="state-item">
190 <i className="pi pi-book" />
191 <span>帖子数: {totalThreads}</span>
192 </div>
193 </div>
194 </div>
195 <div className="input">
LaoeGaociee7c5772025-05-28 12:34:47 +0800196 <div className="action-section">
LaoeGaoci388f7762025-05-29 22:24:35 +0800197 <div className="searchBar">
LaoeGaociee7c5772025-05-28 12:34:47 +0800198 <i className="pi pi-search" />
199 <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
200 </div>
201 <Dropdown
202 value={selectedOption}
203 onChange={(e) => setSelectedOption(e.value)}
204 options={options}
205 optionLabel="name"
206 className="select-dropdown"
207 />
208 </div>
209 </div>
210 </div>
211
212 {/* 帖子列表 */}
213 <div className="thread-list">
214 <div className="resource-grid">
215 {threads.map((thread) => (
216 <Card key={thread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${thread.threadId}`)}>
217 <Image
218 src={process.env.NEXT_PUBLIC_NGINX_URL + thread.threadPicture}
219 alt={thread.title}
220 width="368"
221 height="200"
222 />
223 <div className="card-content">
224 <h3>{thread.title}</h3>
225 <div className="view-count">
226 <i className="pi pi-face-smile" />
227 <span>{thread.likes}</span>
228 </div>
229 </div>
230 </Card>
231 ))}
232 </div>
233 {totalThreads > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalThreads} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)}
234 </div>
235
236 {/* 添加按钮 */}
237 <Button className="add-resource-button" icon="pi pi-plus" rounded aria-label="add-thread" onClick={() => setVisible(true)} />
238
239 {/* 发布帖子弹窗 */}
240 <Dialog
241 header="发布新帖子"
242 visible={visible}
243 onHide={() => setVisible(false)}
244 className="publish-dialog"
245 modal
246 footer={
247 <div className="dialog-footer">
248 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
249 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
250 </div>
251 }
252 >
253 <div className="publish-form">
254 <div className="form-field">
255 <label htmlFor="title">标题</label>
256 <InputText
257 id="title"
258 value={formData.title}
259 onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
260 placeholder="请输入帖子标题"
261 className="w-full"
262 />
263 </div>
264
265 <div className="form-field">
266 <label htmlFor="content">内容</label>
267 <InputTextarea
268 id="content"
269 value={formData.content}
270 onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
271 rows={5}
272 placeholder="请输入帖子内容"
273 className="w-full"
274 />
275 </div>
276
277 <div className="form-field">
278 <label>封面图片</label>
279 <FileUpload
280 mode="basic"
281 name="thread-image"
LaoeGaoci388f7762025-05-29 22:24:35 +0800282 url={process.env.PUBLIC_URL + "/file"} // 与后端交互的URL
LaoeGaociee7c5772025-05-28 12:34:47 +0800283 accept="image/*"
284 maxFileSize={10000000000}
285 chooseLabel="选择图片"
286 className="w-full"
287 onUpload={onUpload}
288 />
289 </div>
290 </div>
291 </Dialog>
292 </div>
293 );
294}