From 0bce1d49da0a1d6dda439983d6a29014b6a1be2e Mon Sep 17 00:00:00 2001 From: LeonspaceX Date: Tue, 27 Jan 2026 13:19:18 +0800 Subject: [PATCH] Add report flow and post_info by id --- back/main.py | 70 +++++++++++++++++++ front/src/api.ts | 31 +++++++++ front/src/components/CommentSection.tsx | 3 +- front/src/components/PostCard.tsx | 22 +++++- front/src/components/ReportPost.tsx | 92 +++++++++++++++++++++++++ front/src/components/StatusDisplay.tsx | 8 ++- front/src/context/LayoutContext.tsx | 6 +- 7 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 front/src/components/ReportPost.tsx diff --git a/back/main.py b/back/main.py index d94665f..483d9c9 100644 --- a/back/main.py +++ b/back/main.py @@ -67,6 +67,16 @@ class Comment(db.Model): created_at = db.Column(db.DateTime, default=lambda: datetime.now()) parent_comment_id = db.Column(db.Integer, db.ForeignKey('comments.id'), nullable=True) +class Report(db.Model): + __tablename__ = 'reports' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + submission_id = db.Column(db.Integer, nullable=False) + title = db.Column(db.String(200), nullable=False) + content = db.Column(db.Text, nullable=False) + identity_token = db.Column(db.String(36), nullable=True) + status = db.Column(db.String(20), default='Pending') + created_at = db.Column(db.DateTime, default=lambda: datetime.now()) + class Hashtag(db.Model): __tablename__ = 'hashtags' id = db.Column(db.Integer, primary_key=True, autoincrement=True) @@ -329,6 +339,42 @@ def submit_comment(): except Exception as e: return jsonify({"code": 2003, "data": f"评论失败: {str(e)}"}) +@app.route('/api/report', methods=['POST']) +def submit_report(): + try: + data = request.get_json() + if not data or 'id' not in data or 'title' not in data or 'content' not in data: + return jsonify({"code": 2000, "data": "参数错误"}) + + submission_id = data.get('id') + title = data.get('title') + content = data.get('content') + + submission = db.session.get(Submission, submission_id) + if not submission: + return jsonify({"code": 2002, "data": "投稿不存在"}) + + identity_token = data.get('identity') + if identity_token: + if not Identity.query.filter_by(token=identity_token).first(): + return jsonify({"code": 2004, "data": "无效的Identity Token"}) + else: + identity_token = None + + report = Report( + submission_id=submission_id, + title=str(title).strip() if title is not None else '', + content=str(content).strip() if content is not None else '', + identity_token=identity_token, + status='Pending', + ) + db.session.add(report) + db.session.commit() + + return jsonify({"code": 1001, "data": {"id": report.id}}) + except Exception as e: + return jsonify({"code": 2003, "data": f"投诉失败: {str(e)}"}) + @app.route('/api/get/comment', methods=['GET']) def get_comments(): try: @@ -511,6 +557,30 @@ def get_posts_info(): }) except Exception as e: return jsonify({"code": 2003, "data": str(e)}) + +@app.route('/api/post_info', methods=['GET']) +def get_post_info(): + try: + post_id = request.args.get("id", type=int) + if not post_id: + return jsonify({"code": 2000, "data": "参数错误"}) + + submission = Submission.query.filter_by(id=post_id, status='Pass').first() + if not submission: + return jsonify({"code": 2002, "data": "投稿不存在"}) + + data = { + "id": submission.id, + "content": submission.content, + "upvotes": submission.upvotes, + "downvotes": submission.downvotes, + "created_at": submission.created_at.isoformat() if submission.created_at else None, + "comment_count": len(submission.comments), + } + + return jsonify({"code": 1000, "data": data}) + except Exception as e: + return jsonify({"code": 2003, "data": str(e)}) # --- 彩蛋 --- @app.route('/api/teapot', methods=['GET']) diff --git a/front/src/api.ts b/front/src/api.ts index 2f74851..d1d73a7 100644 --- a/front/src/api.ts +++ b/front/src/api.ts @@ -299,6 +299,37 @@ export const postComment = async (commentData: PostCommentRequest): Promise => { + try { + const identity = await get_id_token(); + const response = await fetch('/api/report', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...reportData, + identity, + }), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const json = await response.json(); + if (json.code === 1001 && json.data?.id !== undefined) { + return { id: Number(json.data.id) }; + } + throw new Error(json.data || 'Report failed'); + } catch (error) { + console.error('Failed to report post:', error); + throw error; + } +}; + export const uploadImage = async (file: File): Promise => { try { const identity_token = await get_id_token(); diff --git a/front/src/components/CommentSection.tsx b/front/src/components/CommentSection.tsx index 0a19edd..eab9768 100644 --- a/front/src/components/CommentSection.tsx +++ b/front/src/components/CommentSection.tsx @@ -139,7 +139,7 @@ interface CommentSectionProps { const CommentSection: React.FC = ({ postId }) => { const styles = useStyles(); - const { toasterId } = useLayout(); + const { toasterId, triggerStaticsRefresh } = useLayout(); const { dispatchToast } = useToastController(toasterId); const [comments, setComments] = useState([]); const [content, setContent] = useState(''); @@ -214,6 +214,7 @@ useEffect(() => { setContent(''); if (replyTo) setReplyTo(null); fetchComments(1, false); + triggerStaticsRefresh(); } catch (error: any) { dispatchToast( diff --git a/front/src/components/PostCard.tsx b/front/src/components/PostCard.tsx index ed40617..a38693a 100644 --- a/front/src/components/PostCard.tsx +++ b/front/src/components/PostCard.tsx @@ -21,6 +21,7 @@ import { } from '@fluentui/react-icons'; import { useLayout } from '../context/LayoutContext'; import CommentSection from './CommentSection'; +import ReportPost from './ReportPost'; // 自定义 remark 插件,用于高亮 #tag const remarkTagPlugin = () => { @@ -150,6 +151,19 @@ const useStyles = makeStyles({ justifyItems: 'center', gap: '0 8px', }, + modalOverlay: { + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + backdropFilter: 'blur(5px)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 999, + }, commentSection: { marginTop: tokens.spacingVerticalM, borderTop: `1px solid ${tokens.colorNeutralStroke1}`, @@ -178,6 +192,7 @@ const PostCard = ({ const [votes, setVotes] = React.useState({ upvotes, downvotes }); const [hasVoted, setHasVoted] = React.useState(false); const [showComments, setShowComments] = React.useState(false); + const [showReportModal, setShowReportModal] = React.useState(false); React.useEffect(() => { setVotes({ upvotes, downvotes }); @@ -279,7 +294,7 @@ const PostCard = ({