| 'use client'; |
| |
| import React, { useEffect, useState, useRef } from "react"; |
| import { InputText } from 'primereact/inputtext'; |
| import { Button } from 'primereact/button'; |
| import { Card } from 'primereact/card'; |
| import { Image } from 'primereact/image'; |
| import { Dropdown } from 'primereact/dropdown'; |
| // 页面跳转 |
| import { useParams } from 'next/navigation' |
| import { useRouter } from 'next/navigation'; |
| // 分页 |
| import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator'; |
| // 评分图标 |
| import { Fire } from '@icon-park/react'; |
| // 消息提醒 |
| import { Toast } from 'primereact/toast'; |
| // 发布帖子 |
| import { Dialog } from 'primereact/dialog'; |
| import { FileUpload } from 'primereact/fileupload'; |
| import { InputTextarea } from 'primereact/inputtextarea'; |
| // 接口传输 |
| import axios from 'axios'; |
| // 防抖函数 |
| import { debounce } from 'lodash'; |
| import { useLocalStorage } from '../../../hook/useLocalStorage'; |
| // 样式 |
| import './resource-community.scss'; |
| |
| interface User { |
| Id: number; |
| } |
| // 帖子列表数据 |
| interface Thread { |
| threadId: number; |
| userId: number; |
| threadPicture: string; |
| title: string; |
| createdAt: string; |
| communityId: string; |
| likes: number; |
| } |
| interface ThreadList { |
| records: Thread[]; |
| } |
| // 社区信息 |
| interface CommunityInfo { |
| communityId: string; |
| communityName: string; |
| communityPicture: string; |
| description: string; |
| hot: number; |
| type: string; |
| threadNumber: number; |
| } |
| |
| |
| // 社区详情页面 |
| export default function CommunityDetailPage() { |
| const user = useLocalStorage<User>('user'); |
| const userId: number = user?.Id ?? -1; |
| // 获取URL参数,页面跳转 |
| const params = useParams<{ communityId: string }>() |
| const communityId = decodeURIComponent(params.communityId); // 防止中文路径乱码 |
| const router = useRouter(); |
| // 社区数据 |
| const [communityInfo, setCommunityInfo] = useState<CommunityInfo | null>(null); |
| // 帖子列表数据 |
| const [threads, setThreads] = useState<Thread[]>([]); |
| const [totalThreads, setTotalThreads] = useState<number>(0); |
| // 搜索框 |
| const [searchValue, setSearchValue] = useState(""); |
| const debouncedSearch = useRef( |
| debounce((value: string) => { |
| setSearchValue(value); |
| }, 600) |
| ).current; |
| // 消息提醒 |
| const toast = useRef<Toast>(null); |
| // 筛选器选项数据 |
| const [selectedOption, setSelectedOption] = useState({ name: '热度最高' }); |
| const options = [ |
| { name: '来自好友' }, |
| { name: '热度最高' } |
| ]; |
| // 分页 |
| const [first, setFirst] = useState(0); |
| const [rows, setRows] = useState(6); |
| const onPageChange = (event: PaginatorPageChangeEvent) => { |
| setFirst(event.first); |
| setRows(event.rows); |
| }; |
| // 获取社区信息 |
| useEffect(() => { |
| const fetchThreadInfo = async () => { |
| try { |
| const { data } = await axios.get(process.env.PUBLIC_URL + `/community/info?communityId=${communityId}`); |
| setCommunityInfo(data); |
| setTotalThreads(data.threadNumber); |
| } catch (err) { |
| console.error(err); |
| toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区信息失败' }); |
| } |
| }; |
| fetchThreadInfo(); |
| }, [communityId]); |
| |
| // 获取帖子列表 |
| useEffect(() => { |
| fetchThreads(); |
| }, [communityId, first, rows, selectedOption, searchValue]); |
| |
| const fetchThreads = async () => { |
| try { |
| const pageNumber = first / rows + 1; |
| console.log("当前页" + pageNumber + "size" + rows + "搜索内容" + searchValue); |
| const option = selectedOption.name // 添加排序参数 |
| const response = await axios.get<ThreadList>( |
| process.env.PUBLIC_URL + `/community/threads`, { |
| params: { userId, communityId, pageNumber, rows, option, searchValue } |
| } |
| ); |
| console.log('获取帖子列表:', response.data.records); |
| setThreads(response.data.records); |
| } catch (err) { |
| console.error('获取帖子失败', err); |
| toast.current?.show({ severity: 'error', summary: 'error', detail: '获取帖子失败' }); |
| } |
| }; |
| |
| // 发布帖子弹窗 |
| const [visible, setVisible] = useState(false); |
| const [formData, setFormData] = useState({ |
| title: '', |
| content: '', |
| threadPicture: '' |
| }); |
| // 图片上传消息通知 |
| const onUpload = () => { |
| toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' }); |
| }; |
| |
| // 发帖接口 |
| const handleSubmit = async () => { |
| try { |
| const currentDate = new Date().toISOString(); |
| const postData = { |
| userId, // 记得用户登录状态获取 |
| threadPicture: formData.threadPicture, |
| title: formData.title, |
| content: formData.content, |
| createdAt: currentDate, |
| communityId: communityId // 从URL参数获取的社区ID |
| }; |
| // 发送POST请求 |
| const response = await axios.post(process.env.PUBLIC_URL + '/thread', postData); |
| |
| if (response.status === 200) { |
| toast.current?.show({ severity: 'success', summary: 'Success', detail: '帖子发布成功' }); |
| // 发帖成功 |
| setVisible(false); |
| // 重置表单 |
| setFormData({ |
| title: '', |
| content: '', |
| threadPicture: '' |
| }); |
| // 可以刷新帖子列表 |
| fetchThreads(); |
| } |
| } catch (error) { |
| console.error('发帖失败:', error); |
| toast.current?.show({ severity: 'error', summary: 'error', detail: '帖子发布失败' }); |
| } |
| }; |
| return ( |
| <div className="resource-community"> |
| <Toast ref={toast}></Toast> |
| {/* 社区标题和介绍 */} |
| <div className="community-header"> |
| <div className="title-section"> |
| <h1>{communityInfo?.communityName}</h1> |
| <p className="subtitle">{communityInfo?.description}</p> |
| <div className="community-states"> |
| <div className="state-item"> |
| <Fire theme="outline" size="16" fill="#FF8D1A" /> |
| <span>热度: {communityInfo?.hot}</span> |
| </div> |
| <div className="state-item"> |
| <i className="pi pi-book" /> |
| <span>帖子数: {totalThreads}</span> |
| </div> |
| </div> |
| </div> |
| <div className="input"> |
| <div className="action-section"> |
| <div className="searchBar"> |
| <i className="pi pi-search" /> |
| <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} /> |
| </div> |
| <Dropdown |
| value={selectedOption} |
| onChange={(e) => setSelectedOption(e.value)} |
| options={options} |
| optionLabel="name" |
| className="select-dropdown" |
| /> |
| </div> |
| </div> |
| </div> |
| |
| {/* 帖子列表 */} |
| <div className="thread-list"> |
| <div className="resource-grid"> |
| {threads.map((thread) => ( |
| <Card key={thread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${thread.threadId}`)}> |
| <Image |
| src={process.env.NEXT_PUBLIC_NGINX_URL + thread.threadPicture} |
| alt={thread.title} |
| width="368" |
| height="200" |
| /> |
| <div className="card-content"> |
| <h3>{thread.title}</h3> |
| <div className="view-count"> |
| <i className="pi pi-face-smile" /> |
| <span>{thread.likes}</span> |
| </div> |
| </div> |
| </Card> |
| ))} |
| </div> |
| {totalThreads > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalThreads} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)} |
| </div> |
| |
| {/* 添加按钮 */} |
| <Button className="add-resource-button" icon="pi pi-plus" rounded aria-label="add-thread" onClick={() => setVisible(true)} /> |
| |
| {/* 发布帖子弹窗 */} |
| <Dialog |
| header="发布新帖子" |
| visible={visible} |
| onHide={() => setVisible(false)} |
| className="publish-dialog" |
| modal |
| footer={ |
| <div className="dialog-footer"> |
| <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus /> |
| <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" /> |
| </div> |
| } |
| > |
| <div className="publish-form"> |
| <div className="form-field"> |
| <label htmlFor="title">标题</label> |
| <InputText |
| id="title" |
| value={formData.title} |
| onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))} |
| placeholder="请输入帖子标题" |
| className="w-full" |
| /> |
| </div> |
| |
| <div className="form-field"> |
| <label htmlFor="content">内容</label> |
| <InputTextarea |
| id="content" |
| value={formData.content} |
| onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))} |
| rows={5} |
| placeholder="请输入帖子内容" |
| className="w-full" |
| /> |
| </div> |
| |
| <div className="form-field"> |
| <label>封面图片</label> |
| <FileUpload |
| mode="basic" |
| name="thread-image" |
| url={process.env.PUBLIC_URL + "/file"} // 与后端交互的URL |
| accept="image/*" |
| maxFileSize={10000000000} |
| chooseLabel="选择图片" |
| className="w-full" |
| onUpload={onUpload} |
| /> |
| </div> |
| </div> |
| </Dialog> |
| </div> |
| ); |
| } |