Update comment pagination and ignore data files
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
back/data
|
back/data/
|
||||||
back/__pycache__
|
back/__pycache__/
|
||||||
|
**/__pycache__/
|
||||||
|
**/*.pyc
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 239 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 280 KiB |
21
back/main.py
21
back/main.py
@@ -340,14 +340,23 @@ def get_comments():
|
|||||||
if not submission or submission.status != "Pass":
|
if not submission or submission.status != "Pass":
|
||||||
return jsonify({"code": 2002, "data": "投稿不存在"})
|
return jsonify({"code": 2002, "data": "投稿不存在"})
|
||||||
|
|
||||||
|
page = request.args.get("page", 1, type=int)
|
||||||
|
if page < 1:
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
per_page = 5
|
||||||
|
pagination = Comment.query.filter_by(submission_id=submission_id)\
|
||||||
|
.order_by(Comment.id.asc())\
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
data = [{
|
data = [{
|
||||||
"id": c.id,
|
"id": c.id,
|
||||||
"nickname": c.nickname,
|
"nickname": c.nickname,
|
||||||
"content": c.content,
|
"content": c.content,
|
||||||
"parent_comment_id": c.parent_comment_id if c.parent_comment_id is not None else 0
|
"parent_comment_id": c.parent_comment_id if c.parent_comment_id is not None else 0
|
||||||
} for c in submission.comments]
|
} for c in pagination.items]
|
||||||
|
|
||||||
return jsonify({"code": 1000, "data": data})
|
return jsonify({"code": 1000, "data": {"comments": data, "total_pages": pagination.pages}})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"code": 2003, "data": str(e)})
|
return jsonify({"code": 2003, "data": str(e)})
|
||||||
|
|
||||||
@@ -469,8 +478,8 @@ def get_statics():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"code": 2003, "data": str(e)})
|
return jsonify({"code": 2003, "data": str(e)})
|
||||||
|
|
||||||
@app.route('/api/10_info', methods=['GET'])
|
@app.route('/api/posts_info', methods=['GET'])
|
||||||
def get_10_info():
|
def get_posts_info():
|
||||||
try:
|
try:
|
||||||
page = request.args.get("page", 1, type=int)
|
page = request.args.get("page", 1, type=int)
|
||||||
if page < 1:
|
if page < 1:
|
||||||
@@ -503,8 +512,8 @@ def get_10_info():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"code": 2003, "data": str(e)})
|
return jsonify({"code": 2003, "data": str(e)})
|
||||||
|
|
||||||
# --- 菜单 ---
|
# --- 彩蛋 ---
|
||||||
@app.route('/get/teapot', methods=['GET'])
|
@app.route('/api/teapot', methods=['GET'])
|
||||||
def return_418():
|
def return_418():
|
||||||
abort(418)
|
abort(418)
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export interface Article {
|
|||||||
|
|
||||||
export const fetchArticles = async (page: number, signal?: AbortSignal): Promise<Article[]> => {
|
export const fetchArticles = async (page: number, signal?: AbortSignal): Promise<Article[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/10_info?page=${page}`, { signal });
|
const response = await fetch(`/api/posts_info?page=${page}`, { signal });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -219,15 +219,23 @@ export interface Comment {
|
|||||||
parent_comment_id: number;
|
parent_comment_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getComments = async (id: string | number): Promise<Comment[]> => {
|
export interface CommentPage {
|
||||||
|
comments: Comment[];
|
||||||
|
total_pages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getComments = async (id: string | number, page: number = 1): Promise<CommentPage> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/get/comment?id=${id}`);
|
const response = await fetch(`/api/get/comment?id=${id}&page=${page}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.code === 1000 && Array.isArray(json.data)) {
|
if (json.code === 1000 && json.data && Array.isArray(json.data.comments)) {
|
||||||
return json.data as Comment[];
|
return {
|
||||||
|
comments: json.data.comments as Comment[],
|
||||||
|
total_pages: Number(json.data.total_pages) || 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new Error('Invalid response code or missing data');
|
throw new Error('Invalid response code or missing data');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Toast,
|
Toast,
|
||||||
ToastTitle,
|
ToastTitle,
|
||||||
} from '@fluentui/react-components';
|
} from '@fluentui/react-components';
|
||||||
import { Dismiss24Regular, ArrowReply24Regular } from '@fluentui/react-icons';
|
import { Dismiss24Regular, ArrowReply24Regular, ArrowClockwise24Regular } from '@fluentui/react-icons';
|
||||||
import { getComments, postComment } from '../api';
|
import { getComments, postComment } from '../api';
|
||||||
import type { Comment as CommentType } from '../api';
|
import type { Comment as CommentType } from '../api';
|
||||||
import { useLayout } from '../context/LayoutContext';
|
import { useLayout } from '../context/LayoutContext';
|
||||||
@@ -91,6 +91,15 @@ const useStyles = makeStyles({
|
|||||||
color: tokens.colorBrandForeground1,
|
color: tokens.colorBrandForeground1,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
},
|
},
|
||||||
|
loadMore: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: tokens.spacingHorizontalXS,
|
||||||
|
color: tokens.colorBrandForeground1,
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginTop: tokens.spacingVerticalS,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 自定义 remark 插件,用于高亮 #tag
|
// 自定义 remark 插件,用于高亮 #tag
|
||||||
@@ -137,17 +146,29 @@ const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
|
|||||||
const [nickname, setNickname] = useState('');
|
const [nickname, setNickname] = useState('');
|
||||||
const [replyTo, setReplyTo] = useState<CommentType | null>(null);
|
const [replyTo, setReplyTo] = useState<CommentType | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
const commentCardRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
const commentCardRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchComments();
|
setComments([]);
|
||||||
|
setPage(1);
|
||||||
|
setTotalPages(0);
|
||||||
|
fetchComments(1, false);
|
||||||
}, [postId]);
|
}, [postId]);
|
||||||
|
|
||||||
const fetchComments = async () => {
|
const fetchComments = async (targetPage: number, append: boolean) => {
|
||||||
try {
|
try {
|
||||||
|
if (append) {
|
||||||
|
setLoadingMore(true);
|
||||||
|
} else {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await getComments(postId);
|
}
|
||||||
setComments(data as CommentType[]);
|
const data = await getComments(postId, targetPage);
|
||||||
|
setComments(prev => (append ? [...prev, ...data.comments] : data.comments));
|
||||||
|
setPage(targetPage);
|
||||||
|
setTotalPages(data.total_pages || 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatchToast(
|
dispatchToast(
|
||||||
<Toast>
|
<Toast>
|
||||||
@@ -157,8 +178,12 @@ const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
|
|||||||
);
|
);
|
||||||
console.error('Error fetching comments:', error);
|
console.error('Error fetching comments:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (append) {
|
||||||
|
setLoadingMore(false);
|
||||||
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitComment = async () => {
|
const handleSubmitComment = async () => {
|
||||||
@@ -188,7 +213,7 @@ const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
|
|||||||
);
|
);
|
||||||
setContent('');
|
setContent('');
|
||||||
if (replyTo) setReplyTo(null);
|
if (replyTo) setReplyTo(null);
|
||||||
fetchComments();
|
fetchComments(1, false);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
dispatchToast(
|
dispatchToast(
|
||||||
<Toast>
|
<Toast>
|
||||||
@@ -300,6 +325,18 @@ const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
|
|||||||
{loading && <Text>加载评论中..</Text>}
|
{loading && <Text>加载评论中..</Text>}
|
||||||
{!loading && comments.length === 0 && <Text>暂无评论</Text>}
|
{!loading && comments.length === 0 && <Text>暂无评论</Text>}
|
||||||
{renderComments()}
|
{renderComments()}
|
||||||
|
{totalPages > 1 && page < totalPages && (
|
||||||
|
<div
|
||||||
|
className={styles.loadMore}
|
||||||
|
onClick={() => {
|
||||||
|
if (loading || loadingMore) return;
|
||||||
|
fetchComments(page + 1, true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowClockwise24Regular />
|
||||||
|
<Text size={200}>加载更多</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user