公告用户前端以及管理员

Change-Id: I3ea4a7f62ae91ef97734d6d673cce28855d10eeb
diff --git a/src/components/ActivityBoard.jsx b/src/components/ActivityBoard.jsx
new file mode 100644
index 0000000..4cbfb52
--- /dev/null
+++ b/src/components/ActivityBoard.jsx
@@ -0,0 +1,98 @@
+// src/components/ActivityBoard.jsx
+import React, { useEffect, useState } from 'react';
+import { Card, Row, Col, Typography, Spin, message, Modal } from 'antd';
+import { getFullActivities } from '../api/activity';
+import './ActivityBoard.css';
+
+const { Paragraph, Text } = Typography;
+
+const ActivityBoard = () => {
+    const [activities, setActivities] = useState([]);
+    const [loading, setLoading] = useState(true);
+    const [modalVisible, setModalVisible] = useState(false);
+    const [selectedActivity, setSelectedActivity] = useState(null);
+
+    useEffect(() => {
+        getFullActivities()
+            .then((res) => {
+                setActivities(res.data);
+                setLoading(false);
+            })
+            .catch(() => {
+                message.error('获取公告失败');
+                setLoading(false);
+            });
+    }, []);
+
+    const showModal = (activity) => {
+        setSelectedActivity(activity);
+        setModalVisible(true);
+    };
+
+    const handleClose = () => {
+        setModalVisible(false);
+        setSelectedActivity(null);
+    };
+
+    return (
+        <>
+            {loading ? (
+                <div className="loading-container">
+                    <Spin size="large" />
+                </div>
+            ) : (
+                <Row gutter={[24, 24]}>
+                    {activities.map((activity) => (
+                        <Col xs={24} sm={12} md={8} lg={6} key={activity.activityid}>
+                            <Card
+                                hoverable
+                                className="activity-card"
+                                cover={
+                                    <img
+                                        alt="活动图片"
+                                        src={`http://localhost:8080${activity.photo}`}
+                                        className="activity-photo"
+                                        onClick={() => showModal(activity)}
+                                    />
+                                }
+                            >
+                                <Card.Meta
+                                    title={<div className="activity-title">{activity.title}</div>}
+                                />
+                            </Card>
+                        </Col>
+                    ))}
+                </Row>
+            )}
+
+            {/* 弹窗展示公告详情 */}
+            <Modal
+                open={modalVisible}
+                title={selectedActivity?.title}
+                footer={null}
+                onCancel={handleClose}
+                centered
+                width={700}
+                bodyStyle={{ padding: '24px', maxHeight: '80vh', overflowY: 'auto' }}
+            >
+                {selectedActivity && (
+                    <div>
+                        {selectedActivity.photo && (
+                            <img
+                                src={`http://localhost:8080${selectedActivity.photo}`}
+                                alt="公告图片"
+                                style={{ width: '100%', maxHeight: 400, objectFit: 'cover', borderRadius: 8, marginBottom: 20 }}
+                            />
+                        )}
+                        <Paragraph style={{ fontSize: 16 }}>{selectedActivity.content}</Paragraph>
+                        <Text type="secondary">
+                            发布时间:{new Date(selectedActivity.time).toLocaleString()}
+                        </Text>
+                    </div>
+                )}
+            </Modal>
+        </>
+    );
+};
+
+export default ActivityBoard;