帖子的相关用户前端与管理员后端

Change-Id: I957181738817aeeaa89fabe23ceaf59d00c37a71
diff --git a/src/components/PostAdminPanel.jsx b/src/components/PostAdminPanel.jsx
new file mode 100644
index 0000000..a01df08
--- /dev/null
+++ b/src/components/PostAdminPanel.jsx
@@ -0,0 +1,232 @@
+import React, { useEffect, useState } from 'react';
+import {
+    Table,
+    Button,
+    Modal,
+    Image,
+    message,
+    Tag,
+    Space,
+    Input,
+    Tooltip,
+} from 'antd';
+import { ExclamationCircleOutlined, SearchOutlined } from '@ant-design/icons';
+import {
+    getAllPostsSorted,
+    searchPosts,
+    deletePost,
+    togglePinPost,
+} from '../api/post';
+
+const { confirm } = Modal;
+
+const PostAdminPanel = () => {
+    const [posts, setPosts] = useState([]);
+    const [loading, setLoading] = useState(false);
+    const [keyword, setKeyword] = useState('');
+
+    const fetchPosts = async () => {
+        setLoading(true);
+        try {
+            const data = keyword.trim()
+                ? await searchPosts(keyword.trim())
+                : await getAllPostsSorted();
+            setPosts(data);
+        } catch (error) {
+            message.error('获取帖子失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchPosts();
+    }, []);
+
+    const handleSearch = () => {
+        fetchPosts();
+    };
+
+    const handleDelete = (postid) => {
+        confirm({
+            title: '确认删除该帖子吗?',
+            icon: <ExclamationCircleOutlined />,
+            okText: '删除',
+            okType: 'danger',
+            cancelText: '取消',
+            onOk: async () => {
+                try {
+                    const res = await deletePost(postid);
+                    if (res) {
+                        message.success('删除成功');
+                        fetchPosts();
+                    } else {
+                        message.error('删除失败');
+                    }
+                } catch {
+                    message.error('删除请求失败');
+                }
+            },
+        });
+    };
+
+    const handlePinToggle = async (postid) => {
+        try {
+            const res = await togglePinPost(postid);
+            if (res === true || res === false) {
+                // ✅ 用后端返回的状态更新 UI
+                const newPosts = posts.map(post => {
+                    if (post.postid === postid) {
+                        return {
+                            ...post,
+                            is_pinned: res ? 1 : 0,
+                        };
+                    }
+                    return post;
+                });
+                setPosts(newPosts);
+                message.success(`帖子${res ? '已置顶' : '已取消置顶'}`);
+            } else {
+                message.error('更新失败');
+            }
+        } catch {
+            message.error('更新置顶状态失败');
+        }
+    };
+
+
+
+
+    const columns = [
+        {
+            title: 'ID',
+            dataIndex: 'postid',
+            key: 'postid',
+            width: 80,
+            fixed: 'left',
+        },
+        {
+            title: '用户ID',
+            dataIndex: 'userid',
+            key: 'userid',
+            width: 100,
+        },
+        {
+            title: '标题',
+            dataIndex: 'postTitle',
+            key: 'postTitle',
+            width: 200,
+            ellipsis: true,
+        },
+        {
+            title: '内容',
+            dataIndex: 'postContent',
+            key: 'postContent',
+            ellipsis: { showTitle: false },
+            render: (text) => (
+                <Tooltip placement="topLeft" title={text}>
+                    {text}
+                </Tooltip>
+            ),
+        },
+        {
+            title: '标签',
+            dataIndex: 'tags',
+            key: 'tags',
+            render: (tags) =>
+                tags ? tags.split(',').map((tag) => <Tag key={tag}>{tag}</Tag>) : <Tag color="default">无</Tag>,
+        },
+        {
+            title: '图片',
+            dataIndex: 'photo',
+            key: 'photo',
+            width: 100,
+            render: (url) =>
+                url ? (
+                    <Image
+                        src={`http://localhost:8080${url}`}
+                        width={80}
+                        height={80}
+                        style={{ objectFit: 'cover' }}
+                    />
+                ) : (
+                    <Tag color="default">无</Tag>
+                ),
+        },
+        {
+            title: '点赞数',
+            dataIndex: 'likes',
+            key: 'likes',
+            width: 100,
+            render: (likes) => <Tag color="blue">{likes}</Tag>,
+        },
+        {
+            title: '置顶状态',
+            dataIndex: 'is_pinned',
+            key: 'is_pinned',
+            width: 100,
+            render: (val) =>
+                val ? <Tag color="green">已置顶</Tag> : <Tag color="default">未置顶</Tag>,
+        },
+        {
+            title: '发布时间',
+            dataIndex: 'postCreatedTime',
+            key: 'postCreatedTime',
+            width: 180,
+            render: (time) =>
+                time ? new Date(time).toLocaleString() : <Tag color="default">暂无</Tag>,
+        },
+        {
+            title: '操作',
+            key: 'action',
+            width: 180,
+            fixed: 'right',
+            render: (_, record) => (
+                <Space size="middle">
+                    <Button
+                        type="primary"
+                        onClick={() => handlePinToggle(record.postid, record.is_pinned)}
+                    >
+                        {Boolean(record.is_pinned) ? '取消置顶' : '置顶'}
+                    </Button>
+
+                    <Button danger onClick={() => handleDelete(record.postid)}>
+                        删除
+                    </Button>
+                </Space>
+            ),
+        },
+    ];
+
+    return (
+        <div style={{ padding: 20 }}>
+            <h2 style={{ marginBottom: 20 }}>帖子管理面板</h2>
+            <Space style={{ marginBottom: 16 }}>
+                <Input
+                    placeholder="请输入关键词"
+                    value={keyword}
+                    onChange={(e) => setKeyword(e.target.value)}
+                    style={{ width: 300 }}
+                />
+                <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
+                    搜索
+                </Button>
+                <Button onClick={() => { setKeyword(''); fetchPosts(); }}>
+                    重置
+                </Button>
+            </Space>
+            <Table
+                rowKey="postid"
+                columns={columns}
+                dataSource={posts}
+                loading={loading}
+                scroll={{ x: 1600 }}
+                pagination={{ pageSize: 10 }}
+                bordered
+                size="middle"
+            />
+        </div>
+    );
+};
+
+export default PostAdminPanel;