merge

Change-Id: I5227831adac7f85854cbe7321c2a3aa39d8c1d7a
diff --git a/src/views/postDetail/postDetail.module.css b/src/views/postDetail/postDetail.module.css
index e69de29..46c2176 100644
--- a/src/views/postDetail/postDetail.module.css
+++ b/src/views/postDetail/postDetail.module.css
@@ -0,0 +1,89 @@
+.commentList .ant-list-item {
+    min-height: 300px;
+    height: 300px;
+    box-sizing: border-box;
+    display: flex;
+    align-items: flex-start;
+    /* 可选:让内容垂直居中可用 align-items: center; */
+}
+.contentArea {
+  width: 100%;
+  max-width: 900px;
+  margin: 32px auto 0 auto;
+  padding: 0 16px 32px 16px;
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+.card {
+  border-radius: 10px;
+  background: var(--card-bg);
+}
+
+.metaRow {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  margin-bottom: 8px;
+}
+
+.locked {
+  margin: 12px 0;
+  color: #d4380d;
+  font-weight: bold;
+}
+
+.contentText {
+  font-size: 17px;
+  color: var(--text-color);
+  line-height: 1.8;
+  margin-top: 12px;
+}
+
+.addCommentCard {
+  border-radius: 10px;
+  background: var(--card-bg);
+}
+
+.textarea {
+  font-size: 15px;
+}
+
+.commentListCard {
+  border-radius: 10px;
+  background: var(--card-bg);
+}
+
+.commentList .ant-list-item,
+.replyList .ant-list-item {
+  min-height: 120px;
+  height: auto;
+  box-sizing: border-box;
+  display: flex;
+  align-items: flex-start;
+  border-bottom: 1px solid var(--border-color);
+  padding: 24px 16px;
+}
+
+.replyList {
+  margin-left: 32px;
+  background: transparent;
+}
+
+.replyItem {
+  background: #f6f8fa;
+  border-radius: 6px;
+  margin-bottom: 8px;
+  padding: 12px 16px;
+}
+
+@media (max-width: 600px) {
+  .contentArea {
+    max-width: 100%;
+    padding: 0 4px 24px 4px;
+  }
+  .card, .addCommentCard, .commentListCard {
+    padding: 0;
+  }
+}
\ No newline at end of file
diff --git a/src/views/postDetail/postDetail.tsx b/src/views/postDetail/postDetail.tsx
index a40fb68..d6029a8 100644
--- a/src/views/postDetail/postDetail.tsx
+++ b/src/views/postDetail/postDetail.tsx
@@ -1,114 +1,206 @@
 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 { Card, List, Typography, Button, Input, Spin, Empty, Divider } from 'antd';
 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';
+import { DownloadOutlined, LikeOutlined, LikeFilled } from '@ant-design/icons';
 
 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;
+  postId?: number;
+  userId?: number;
+  postTitle?: string;
+  postContent?: string;
+  createdAt?: number;
+  postType?: string;
+  isLocked?: boolean;
+  lockedReason?: string;
+  lockedAt?: string;
+  lockedBy?: number;
+  viewCount?: number;
+  hotScore?: number;
+  lastCalculated?: 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;
+  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);
+  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);
+  const [liked, setLiked] = useState(false);
 
-	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]);
+  useEffect(() => {
+    if (!postId) return;
+    const fetchData = async () => {
+      setLoading(true);
+      const postRes = await getPostDetailRefresh();
+      if (!postRes || (postRes as any).error) {
+        setLoading(false);
+        return;
+      }
+      setPost(postRes as PostResponse);
 
-	if (loading) return <div className={styles.center}><Spin /></div>;
-	if (!post) return <div className={styles.center}><Empty description="未找到帖子" /></div>;
+      const commentsRes = await getPostCommentsRefresh();
+      setComments(commentsRes as CommentResponse[]);
+      setLoading(false);
+    };
+    fetchData();
+  }, [postId]);
 
-	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>
+  if (loading) return <div className={styles.center}><Spin /></div>;
+  if (!post) return <div className={styles.center}><Empty description="未找到帖子" /></div>;
 
-				<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>
-					)}
-				/>
+  return (
+    <div className={styles.container}>
+      {/* 固定导航栏 */}
+      <div className={styles.nav}>
+        <Navbar current={post.postType} />
+      </div>
+      {/* 内容区域 */}
+      <div className={styles.contentArea}>
+        <Card
+          title={<Title level={3} style={{ margin: 0 }}>{post.postTitle || "帖子标题"}</Title>}
+          className={styles.card}
+          bordered={false}
+          style={{ marginBottom: 24, boxShadow: '0 4px 24px rgba(0,0,0,0.08)' }}
+          extra={
+            <div style={{ display: 'flex', gap: 16 }}>
+              <Button
+                type="primary"
+                icon={<DownloadOutlined />}
+                onClick={() => {
+                  // 下载逻辑
+                  window.open(`/api/download/post/${post.postId}`, '_blank');
+                }}
+              >
+                下载
+              </Button>
+              <Button
+                type="primary"
+                icon={liked ? <LikeFilled /> : <LikeOutlined />}
+                style={liked ? { background: '#ccc', borderColor: '#ccc', color: '#888', cursor: 'not-allowed' } : {}}
+                disabled={liked}
+                onClick={() => setLiked(true)}
+              >
+                {liked ? '已点赞' : '点赞'}
+              </Button>
+            </div>
+          }
+        >
+          <div className={styles.metaRow}>
+            <Text type="secondary">作者ID: {post.userId}</Text>
+            <Text type="secondary">发布时间: {post.createdAt ? new Date(post.createdAt).toLocaleString() : "未知"}</Text>
+            <Text type="secondary">浏览量: {post.viewCount}</Text>
+            <Text type="secondary">类型: {post.postType}</Text>
+            <Text type="secondary">热度: {post.hotScore}</Text>
+            <Text type="secondary">最后计算: {post.lastCalculated ? new Date(post.lastCalculated).toLocaleString() : "无"}</Text>
+          </div>
+          {post.isLocked && (
+            <div className={styles.locked}>
+              <Text type="danger">本帖已锁定</Text>
+              {post.lockedReason && <Text type="secondary">(原因:{post.lockedReason})</Text>}
+              {post.lockedAt && <Text style={{ marginLeft: 8 }}>锁定时间: {post.lockedAt}</Text>}
+              {post.lockedBy !== 0 && <Text style={{ marginLeft: 8 }}>锁定人ID: {post.lockedBy}</Text>}
+            </div>
+          )}
+          <Divider style={{ margin: '16px 0' }} />
+          <Paragraph className={styles.contentText}>{post.postContent || "暂无内容"}</Paragraph>
+        </Card>
 
-				<TextArea
-					rows={4}
-					value={newComment}
-					onChange={(e) => setNewComment(e.target.value)}
-					placeholder="写下你的评论..."
-				/>
-				</div>
-		</div>
-	);
+        {/* 发布评论区域 */}
+        <Card className={styles.addCommentCard} style={{ marginBottom: 32, boxShadow: '0 2px 12px rgba(0,0,0,0.06)' }}>
+          <Title level={5} style={{ marginBottom: 12 }}>发布评论</Title>
+          <TextArea
+            rows={4}
+            value={newComment}
+            onChange={(e) => setNewComment(e.target.value)}
+            placeholder="写下你的评论..."
+            className={styles.textarea}
+          />
+          <Button
+            type="primary"
+            style={{ marginTop: 12, float: 'right' }}
+            onClick={() => setNewComment('')}
+            disabled={!newComment.trim()}
+          >
+            评论
+          </Button>
+          <div style={{ clear: 'both' }} />
+        </Card>
+
+        <Card
+          className={styles.commentListCard}
+          title={<Title level={4} style={{ margin: 0 }}>评论区</Title>}
+          bodyStyle={{ padding: 0 }}
+        >
+          <List
+            className={styles.commentList}
+            dataSource={comments}
+            locale={{ emptyText: <Empty description="暂无评论" /> }}
+            renderItem={(item) => (
+              <List.Item className={styles.commentItem} key={item.commentId}>
+                <List.Item.Meta
+                  title={<Text strong>用户ID: {item.userId}</Text>}
+                  description={
+                    <>
+                      <Text>{item.content}</Text>
+                      <div style={{ fontSize: 12, color: '#888', marginTop: 8 }}>
+                        {item.createdAt && new Date(item.createdAt).toLocaleString()}
+                      </div>
+                    </>
+                  }
+                />
+                {/* 可递归渲染子评论 */}
+                {item.replies && item.replies.length > 0 && (
+                  <List
+                    className={styles.replyList}
+                    dataSource={item.replies}
+                    renderItem={reply => (
+                      <List.Item className={styles.replyItem} key={reply.commentId}>
+                        <List.Item.Meta
+                          title={<Text strong>用户ID: {reply.userId}</Text>}
+                          description={
+                            <>
+                              <Text>{reply.content}</Text>
+                              <div style={{ fontSize: 12, color: '#888', marginTop: 8 }}>
+                                {reply.createdAt && new Date(reply.createdAt).toLocaleString()}
+                              </div>
+                            </>
+                          }
+                        />
+                      </List.Item>
+                    )}
+                  />
+                )}
+              </List.Item>
+            )}
+          />
+        </Card>
+      </div>
+    </div>
+  );
 };
 
 export default PostDetail;
\ No newline at end of file