帖子分类
Change-Id: I17bafbfe3c1c8fd26c1e12499cb3c17cd1738e23
diff --git a/src/views/forum/index.tsx b/src/views/forum/index.tsx
index 345f880..83d791f 100644
--- a/src/views/forum/index.tsx
+++ b/src/views/forum/index.tsx
@@ -5,7 +5,7 @@
import style from "./index.module.css";
import Navbar from "@/components/navbar/navbar";
import PostsPanel from "@/components/postsPanel/postsPanel";
-import { hotPosts } from "@/api/post";
+import { getHotPosts, getLikePosts} from "@/api/post";
import { Carousel } from 'antd';
import ad1 from '&/assets/ad1.png'
import ad2 from '&/assets/ad2.png'
@@ -42,7 +42,7 @@
</Carousel>
</div>
<div className={style.hotPosts}>
- <PostsPanel name='热门种子' url={hotPosts} limit={5}/>
+ <PostsPanel name='热门种子' url={getHotPosts} limit={5}/>
</div>
</div>
</div>
@@ -54,13 +54,13 @@
</div>
<div className={style.down}>
<div className={style.newPost}>
- <PostsPanel name='最新发布' url={hotPosts} limit={5}/>
+ <PostsPanel name='最新发布' url={getHotPosts} limit={5}/>
</div>
<div className={style.likePost}>
- <PostsPanel name='猜你喜欢' url={hotPosts} limit={5}/>
+ <PostsPanel name='猜你喜欢' url={getLikePosts} limit={5}/>
</div>
<div className={style.forsalePost}>
- <PostsPanel name='促销种子' url={hotPosts} limit={5}/>
+ <PostsPanel name='促销种子' url={getHotPosts} limit={5}/>
</div>
</div>
</div>
diff --git a/src/views/frame/frame.tsx b/src/views/frame/frame.tsx
index c0c2e0e..f41e53f 100644
--- a/src/views/frame/frame.tsx
+++ b/src/views/frame/frame.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { use } from "react";
import { Outlet } from "react-router";
import { useEffect, useState } from "react";
import {
@@ -12,8 +12,13 @@
import logo from "&/assets/logo.png";
import { useAppDispatch } from "@/hooks/store";
import { useSelector } from "react-redux";
+
+import { checkAndRefreshToken } from "@/utils/jwt";
const Frame:React.FC = () => {
+ useEffect(() => {
+ checkAndRefreshToken();
+ }, []);
const dispatch = useAppDispatch();
const showSearch = useSelector((state: any) => state.setting.showSearch);
diff --git a/src/views/login/login.tsx b/src/views/login/login.tsx
index bd429c7..562f178 100644
--- a/src/views/login/login.tsx
+++ b/src/views/login/login.tsx
@@ -11,38 +11,39 @@
import logo from '&/assets/logo.png';
import { getUserInfo } from '@/api/user';
import debounce from 'lodash/debounce';
+import {message} from 'antd';
+
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useAppDispatch();
- const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {}), false);
+ const { refresh: postUserLoginRefresh } = useApi(() => request.post(postUserLogin, {email, password}), false);
const { refresh: getUserInfoRefresh } = useApi(() => request.get(getUserInfo), false);
+ const [messageApi, contextHolder] = message.useMessage();
const nav = useNavigate();
+ const showErrorMessage = async (message: string) => {
+ messageApi.error(message);
+ };
const handleLogin = debounce(async () => {
try {
- const res =await postUserLoginRefresh();
- if (res==null ||(res as any).error) {
- alert('Login failed. Please check your credentials.');
- return;
+ const res = await postUserLoginRefresh();
+ console.log(res);
+ if (res == null || (res as any).error) {
+ throw new Error('登录失败');
}
dispatch({ type: "user/login", payload: res });
-
+
const userInfo = await getUserInfoRefresh();
- if (userInfo==null || (userInfo as any).error) {
- alert('Failed to fetch user information.');
- return;
+ if (userInfo == null || (userInfo as any).error) {
+ throw new Error('获取用户信息失败');
}
dispatch({ type: "user/getUserInfo", payload: userInfo });
nav('/');
} catch (error) {
- alert('An unexpected error occurred. Please try again later.');
- if (error instanceof Error) {
- console.error(error.message); // 明确访问 message 属性
- } else {
- console.error('Unknown error occurred');
- }
+ // 将错误信息传递给一个异步函数
+ showErrorMessage('登录失败,请检查您的用户名和密码');
}
}, 1000) as () => void;
@@ -51,6 +52,7 @@
}
return (
<div className={style.form}>
+ {contextHolder}
<img className={style.logo} src={logo} alt="logo" onClick={handleLogoClick}></img>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className={style.email} placeholder="Enter your email" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} className={style.password} placeholder="Enter your password" />
diff --git a/src/views/postDetail/postDetail.module.css b/src/views/postDetail/postDetail.module.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/views/postDetail/postDetail.module.css
diff --git a/src/views/postDetail/postDetail.tsx b/src/views/postDetail/postDetail.tsx
new file mode 100644
index 0000000..a40fb68
--- /dev/null
+++ b/src/views/postDetail/postDetail.tsx
@@ -0,0 +1,114 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import styles from './PostDetail.module.css';
+import { Card, List, Typography, Button, Input, Spin, Empty } from 'antd';
+type CommentProps = {
+ children?: React.ReactNode;
+};
+import { getPostDetail } from '@/api/post';
+import { getPostComments } from '@/api/comment';
+import { useSearchParams } from 'react-router-dom';
+import request from '@/utils/request';
+import { useApi } from '@/hooks/request';
+import Navbar from '@/components/navbar/navbar';
+
+const { Title, Text, Paragraph } = Typography;
+const { TextArea } = Input;
+
+export interface PostResponse {
+ createdAt?: number;
+ hotScore?: number;
+ lastCalculated?: number;
+ postContent?: string;
+ postId?: number;
+ postTitle?: string;
+ postType?: string;
+ userId?: number;
+ viewCount?: number;
+ [property: string]: any;
+}
+
+export interface CommentResponse {
+ commentId?: number;
+ content?: string;
+ createdAt?: number;
+ parentCommentId?: number | null;
+ postId?: number;
+ replies?: CommentResponse[];
+ userId?: number;
+ [property: string]: any;
+}
+
+const PostDetail: React.FC = () => {
+ const [searchParams] = useSearchParams();
+ const postId = searchParams.get('postId');
+ const { refresh: getPostDetailRefresh } = useApi(() => request.get(getPostDetail + `/${postId}`), false);
+ const { refresh: getPostCommentsRefresh } = useApi(() => request.get(getPostComments + `/${postId}`), false);
+ const [post, setPost] = useState<PostResponse | null>(null);
+ const [comments, setComments] = useState<CommentResponse[]>([]);
+ const [newComment, setNewComment] = useState<string>('');
+ const [loading, setLoading] = useState<boolean>(true);
+
+ useEffect(() => {
+ console.log('postId', postId);
+ if (!postId) return;
+ const fetchData = async () => {
+ setLoading(true);
+ const res = await getPostDetailRefresh();
+ if (res == null || (res as any).error) {
+ setLoading(false);
+ return;
+ }
+ setPost(res as PostResponse);
+ await getPostCommentsRefresh();
+ setComments(res as CommentResponse[]);
+ setLoading(false);
+ };
+ fetchData();
+ }, [postId]);
+
+ if (loading) return <div className={styles.center}><Spin /></div>;
+ if (!post) return <div className={styles.center}><Empty description="未找到帖子" /></div>;
+
+ return (
+ <div className={styles.container}>
+ <div className={styles.nav}>
+ <Navbar current={post.postType} />
+ </div>
+ <div className={styles.content}>
+ <div className={styles.postDetail}>
+
+ </div >
+ <Card title={post.postTitle} className={styles.card}>
+ <Paragraph>{post.postContent}</Paragraph>
+ <div className={styles.actions}>
+ <Button type="primary" onClick={() => setNewComment('')}>评论</Button>
+ </div>
+ </Card>
+
+ <List
+ className={styles.commentList}
+ header={<Title level={4}>评论区</Title>}
+ dataSource={comments}
+ renderItem={(item) => (
+ <List.Item key={item.commentId}>
+ <List.Item.Meta
+ title={<Text strong>{item.userId}</Text>}
+ description={<Text>{item.content}</Text>}
+ />
+ </List.Item>
+ )}
+ />
+
+ <TextArea
+ rows={4}
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ placeholder="写下你的评论..."
+ />
+ </div>
+ </div>
+ );
+};
+
+export default PostDetail;
\ No newline at end of file
diff --git a/src/views/postDetail/posterInfo.tsx b/src/views/postDetail/posterInfo.tsx
new file mode 100644
index 0000000..8fe381c
--- /dev/null
+++ b/src/views/postDetail/posterInfo.tsx
@@ -0,0 +1,37 @@
+import { PositionType } from 'antd/es/image/style';
+import request from '@/utils/request';
+import { useApi } from '@/hooks/request';
+import React from 'react';
+import { useNavigate } from 'react-router';
+import { postFollowUser } from '@/api/user';
+interface PosterInfoProps {
+ userId: number;
+ userName: string;
+ role:string;
+ avatar: string;
+}
+
+const PosterInfo: React.FC<PosterInfoProps> = (prop:PosterInfoProps) => {
+ const nav = useNavigate();
+ const { refresh } = useApi(()=>request.post(postFollowUser+`userId=${prop.userId}`), false);
+ const handleClick = () => {
+ nav(`/homepage?userId=${prop.userId}`);
+ }
+
+ const handleFollow = () => {
+ refresh()
+ }
+ const { userId, userName, role, avatar } = prop;
+ return (
+ <>
+ <div className="poster-info" onClick={handleClick}>
+ <img src={avatar}></img>
+ <p>{userName}</p>
+ <p>{role}</p>
+ </div>
+ <button className="poster-info-button" />关注
+
+ </>
+
+ );
+}
\ No newline at end of file
diff --git a/src/views/postList/postList.module.css b/src/views/postList/postList.module.css
new file mode 100644
index 0000000..6290106
--- /dev/null
+++ b/src/views/postList/postList.module.css
@@ -0,0 +1,88 @@
+.container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ position: relative;
+}
+
+.left {
+ flex: 3;
+ display: flex;
+ flex-direction: column;
+ margin: 5px;
+}
+
+.navbar {
+ height: 60px;
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+.content {
+ flex: 1;
+ overflow-y: auto; /* 允许垂直滚动 */
+ padding: 10px;
+ background-color: #fff;
+ border-radius: 8px;
+ border: 1px solid #ddd;
+}
+
+.contentItem {
+ position: relative;
+ margin-bottom: 15px;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ background-color: #f9f9f9;
+}
+
+.contentItem h3 {
+ margin: 0 0 5px;
+ font-size: 18px;
+ color: #333;
+}
+
+.contentItem p {
+ margin: 0;
+ font-size: 14px;
+ color: #666;
+}
+
+.contentItem .createDate {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ font-size: 12px;
+ color: #999;
+}
+
+.noData {
+ text-align: center;
+ color: #999;
+ margin-top: 20px;
+}
+
+.right {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ margin: 5px;
+}
+
+.selfStatus {
+ margin-bottom: 20px;
+ background-color: #fff;
+ border-radius: 8px;
+ border: 1px solid #ddd;
+ padding: 10px;
+}
+
+.filter {
+ flex: 1;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 10px;
+}
\ No newline at end of file
diff --git a/src/views/postList/postList.tsx b/src/views/postList/postList.tsx
new file mode 100644
index 0000000..c08b7cf
--- /dev/null
+++ b/src/views/postList/postList.tsx
@@ -0,0 +1,82 @@
+import React from "react";
+import style from "./postList.module.css";
+import SelfStatus from "@/components/selfStatus/selfStatus";
+import Navbar from "@/components/navbar/navbar";
+import PostsPanel from "@/components/postsPanel/postsPanel";
+import { getPosts, unknownAPI } from "@/api/post";
+import { Form } from "antd"
+import { useApi } from "@/hooks/request";
+import request from "@/utils/request";
+import { Pagination, PaginationProps } from "antd";
+import { set } from "lodash";
+import { useEffect } from "react";
+import { useNavigate, useSearchParams } from "react-router";
+
+
+const PostList:React.FC = () => {
+ const [searchParams] = useSearchParams();
+ const type = searchParams.get("type") || "";
+ const nav = useNavigate();
+
+ if(type in ['video', 'music', 'game', 'software']) {
+ nav('/')
+ }
+
+ const {data:postList, refresh:getPostList} = useApi(() => request.get(getPosts + `?tags=${[type]}&page=${currentPage}&pageSize=${pageSize}`), false);
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const [pageSize, setPageSize] = React.useState(10);
+ const handlePageChange = (page:number, size?:number) => {
+ setCurrentPage(page);
+ if(size) setPageSize(size);
+ console.log(page, size);
+ };
+
+ const handlePostClick = (postId:number) => {
+ nav(`/postsDetail?postId=${postId}`);
+ }
+
+ useEffect(() => {
+ getPostList();
+ },[currentPage, pageSize]);
+
+ return (
+ <div className={style.container}>
+ <div className={style.left}>
+ <div className={style.navbar}>
+ <Navbar current={type}/>
+ </div>
+ <div className={style.content}>
+ {postList && postList.length > 0 ? (
+ postList.map((post: { postId: number; postTitle: string; postContent: string; createdAt:string }) => (
+ <div key={post.postId} className={style.contentItem} onClick={() => handlePostClick(post.postId)}>
+ <h3>{post.postTitle}</h3>
+ <p>{post.postContent.substring(0, 20)}</p>
+ <p className={style.createDate}>{post.createdAt}</p>
+ </div>
+ ))
+ ) : (
+ <div className={style.noData}>未查询到相关帖子</div>
+ )}
+
+ <Pagination
+ showSizeChanger
+ onShowSizeChange={handlePageChange}
+ defaultCurrent={1}
+ total={500}
+ onChange={handlePageChange}
+ />
+ </div>
+ </div>
+ <div className={style.right}>
+ <div className={style.selfStatus}>
+ <SelfStatus/>
+ </div>
+ <div className={style.filter}>
+
+ </div>
+ </div>
+ </div>
+ )
+}
+
+export default PostList;
\ No newline at end of file