blob: 962cc0f626c4ed1e7f8e173390c252c3c4bca479 [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';
26// 样式
27import './resource-community.scss';
28
29// 帖子列表数据
30interface Thread {
31 threadId: number;
32 userId: number;
33 threadPicture: string;
34 title: string;
35 createdAt: string;
36 communityId: string;
37 likes: number;
38}
39interface ThreadList {
40 records: Thread[];
41}
42// 社区信息
43interface CommunityInfo {
44 communityId: string;
45 communityName: string;
46 communityPicture: string;
47 description: string;
48 hot: number;
49 type: string;
50 threadNumber: number;
51}
52
53
54// 社区详情页面
55export default function CommunityDetailPage() {
56 // 获取URL参数,页面跳转
57 const params = useParams<{ communityId: string }>()
58 const communityId = decodeURIComponent(params.communityId); // 防止中文路径乱码
59 const router = useRouter();
60 // 社区数据
61 const [communityInfo, setCommunityInfo] = useState<CommunityInfo | null>(null);
62 // 帖子列表数据
63 const [threads, setThreads] = useState<Thread[]>([]);
64 const [totalThreads, setTotalThreads] = useState<number>(0);
65 // 搜索框
66 const [searchValue, setSearchValue] = useState("");
67 const debouncedSearch = useRef(
68 debounce((value: string) => {
69 setSearchValue(value);
70 }, 600)
71 ).current;
72 // 消息提醒
73 const toast = useRef<Toast>(null);
74 // 筛选器选项数据
75 const [selectedOption, setSelectedOption] = useState({ name: '热度最高' });
76 const options = [
77 { name: '来自好友' },
78 { name: '热度最高' }
79 ];
80 // 分页
81 const [first, setFirst] = useState(0);
82 const [rows, setRows] = useState(6);
83 const onPageChange = (event: PaginatorPageChangeEvent) => {
84 setFirst(event.first);
85 setRows(event.rows);
86 };
87 // 获取社区信息
88 useEffect(() => {
89 const fetchThreadInfo = async () => {
90 try {
LaoeGaoci388f7762025-05-29 22:24:35 +080091 const { data } = await axios.get(process.env.PUBLIC_URL + `/community/info?communityId=${communityId}`);
LaoeGaociee7c5772025-05-28 12:34:47 +080092 setCommunityInfo(data);
93 setTotalThreads(data.threadNumber);
94 } catch (err) {
95 console.error(err);
96 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区信息失败' });
97 }
98 };
99 fetchThreadInfo();
100 }, [communityId]);
101
102 // 获取帖子列表
103 useEffect(() => {
104 fetchThreads();
105 }, [communityId, first, rows, selectedOption, searchValue]);
106
107 const fetchThreads = async () => {
108 try {
LaoeGaoci388f7762025-05-29 22:24:35 +0800109 const pageNumber = first / rows + 1;
110 console.log("当前页" + pageNumber + "size" + rows + "搜索内容" + searchValue);
LaoeGaociee7c5772025-05-28 12:34:47 +0800111 const option = selectedOption.name // 添加排序参数
112 const response = await axios.get<ThreadList>(
LaoeGaoci388f7762025-05-29 22:24:35 +0800113 process.env.PUBLIC_URL + `/community/threads`, {
LaoeGaocif6e5c962025-06-08 23:39:13 +0800114 params: { userId: 22301145, communityId, pageNumber, rows, option, searchValue }
LaoeGaociee7c5772025-05-28 12:34:47 +0800115 }
116 );
117 console.log('获取帖子列表:', response.data.records);
118 setThreads(response.data.records);
119 } catch (err) {
120 console.error('获取帖子失败', err);
121 toast.current?.show({ severity: 'error', summary: 'error', detail: '获取帖子失败' });
122 }
123 };
124
125 // 发布帖子弹窗
126 const [visible, setVisible] = useState(false);
127 const [formData, setFormData] = useState({
128 title: '',
129 content: '',
130 threadPicture: ''
131 });
132 // 图片上传消息通知
133 const onUpload = () => {
134 toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
135 };
136
137 // 发帖接口
138 const handleSubmit = async () => {
139 try {
140 const currentDate = new Date().toISOString();
141 const postData = {
142 userId: 22301145, // 记得用户登录状态获取
143 threadPicture: formData.threadPicture,
144 title: formData.title,
145 content: formData.content,
146 createdAt: currentDate,
147 communityId: communityId // 从URL参数获取的社区ID
148 };
149 // 发送POST请求
LaoeGaoci388f7762025-05-29 22:24:35 +0800150 const response = await axios.post(process.env.PUBLIC_URL + '/thread', postData);
LaoeGaociee7c5772025-05-28 12:34:47 +0800151
152 if (response.status === 200) {
153 toast.current?.show({ severity: 'success', summary: 'Success', detail: '帖子发布成功' });
154 // 发帖成功
155 setVisible(false);
156 // 重置表单
157 setFormData({
158 title: '',
159 content: '',
160 threadPicture: ''
161 });
162 // 可以刷新帖子列表
163 fetchThreads();
164 }
165 } catch (error) {
166 console.error('发帖失败:', error);
167 toast.current?.show({ severity: 'error', summary: 'error', detail: '帖子发布失败' });
168 }
169 };
170 return (
171 <div className="resource-community">
172 <Toast ref={toast}></Toast>
173 {/* 社区标题和介绍 */}
174 <div className="community-header">
175 <div className="title-section">
176 <h1>{communityInfo?.communityName}</h1>
177 <p className="subtitle">{communityInfo?.description}</p>
178 <div className="community-states">
179 <div className="state-item">
180 <Fire theme="outline" size="16" fill="#FF8D1A" />
181 <span>热度: {communityInfo?.hot}</span>
182 </div>
183 <div className="state-item">
184 <i className="pi pi-book" />
185 <span>帖子数: {totalThreads}</span>
186 </div>
187 </div>
188 </div>
189 <div className="input">
LaoeGaociee7c5772025-05-28 12:34:47 +0800190 <div className="action-section">
LaoeGaoci388f7762025-05-29 22:24:35 +0800191 <div className="searchBar">
LaoeGaociee7c5772025-05-28 12:34:47 +0800192 <i className="pi pi-search" />
193 <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
194 </div>
195 <Dropdown
196 value={selectedOption}
197 onChange={(e) => setSelectedOption(e.value)}
198 options={options}
199 optionLabel="name"
200 className="select-dropdown"
201 />
202 </div>
203 </div>
204 </div>
205
206 {/* 帖子列表 */}
207 <div className="thread-list">
208 <div className="resource-grid">
209 {threads.map((thread) => (
210 <Card key={thread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${thread.threadId}`)}>
211 <Image
212 src={process.env.NEXT_PUBLIC_NGINX_URL + thread.threadPicture}
213 alt={thread.title}
214 width="368"
215 height="200"
216 />
217 <div className="card-content">
218 <h3>{thread.title}</h3>
219 <div className="view-count">
220 <i className="pi pi-face-smile" />
221 <span>{thread.likes}</span>
222 </div>
223 </div>
224 </Card>
225 ))}
226 </div>
227 {totalThreads > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalThreads} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)}
228 </div>
229
230 {/* 添加按钮 */}
231 <Button className="add-resource-button" icon="pi pi-plus" rounded aria-label="add-thread" onClick={() => setVisible(true)} />
232
233 {/* 发布帖子弹窗 */}
234 <Dialog
235 header="发布新帖子"
236 visible={visible}
237 onHide={() => setVisible(false)}
238 className="publish-dialog"
239 modal
240 footer={
241 <div className="dialog-footer">
242 <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
243 <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
244 </div>
245 }
246 >
247 <div className="publish-form">
248 <div className="form-field">
249 <label htmlFor="title">标题</label>
250 <InputText
251 id="title"
252 value={formData.title}
253 onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
254 placeholder="请输入帖子标题"
255 className="w-full"
256 />
257 </div>
258
259 <div className="form-field">
260 <label htmlFor="content">内容</label>
261 <InputTextarea
262 id="content"
263 value={formData.content}
264 onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
265 rows={5}
266 placeholder="请输入帖子内容"
267 className="w-full"
268 />
269 </div>
270
271 <div className="form-field">
272 <label>封面图片</label>
273 <FileUpload
274 mode="basic"
275 name="thread-image"
LaoeGaoci388f7762025-05-29 22:24:35 +0800276 url={process.env.PUBLIC_URL + "/file"} // 与后端交互的URL
LaoeGaociee7c5772025-05-28 12:34:47 +0800277 accept="image/*"
278 maxFileSize={10000000000}
279 chooseLabel="选择图片"
280 className="w-full"
281 onUpload={onUpload}
282 />
283 </div>
284 </div>
285 </Dialog>
286 </div>
287 );
288}