blob: 33011eb7903d80b7e726f40eadb012021ac54968 [file] [log] [blame]
'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 handleSubmit = async () => {
try {
const currentDate = new Date().toISOString();
console.log(formData);
const postData = {
userId, // 记得用户登录状态获取
threadPicture: formData.threadPicture,
title: formData.title,
content: formData.content,
createAt: currentDate.slice(0, 10),
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={ 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="advanced"
name="file"
customUpload
uploadHandler={async (e) => {
const formData = new FormData();
formData.append("file", e.files[0]);
try {
const res = await axios.post(`${process.env.PUBLIC_URL}/file`, formData);
const fileUrl = res.data;
console.log(fileUrl);
setFormData(prev => ({ ...prev, threadPicture: fileUrl }))
toast.current?.show({ severity: 'success', summary: '上传成功' });
} catch (error) {
console.log(error);
toast.current?.show({ severity: 'error', summary: '上传失败' });
}
}}
auto
accept="image/*"
chooseLabel="上传帖子封面"
/>
</div>
</div>
</Dialog>
</div>
);
}