diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index b218974..4714660 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,5 +1,7 @@
+
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from './context/AuthContext';
import Layout from './components/Layout';
import Home from './pages/Home';
@@ -12,29 +14,35 @@ import VCCourseDetail from './pages/VCCourseDetail';
import MyOrders from './pages/MyOrders';
import ForumList from './pages/ForumList';
import ForumDetail from './pages/ForumDetail';
+import ActivityDetail from './pages/activity/Detail';
import 'antd/dist/reset.css';
import './App.css';
+const queryClient = new QueryClient();
+
function App() {
return (
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
)
}
diff --git a/frontend/src/api.js b/frontend/src/api.js
index 03ef2f8..f9908aa 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -68,5 +68,6 @@ export const getAnnouncements = () => api.get('/community/announcements/');
export const getActivities = () => api.get('/community/activities/');
export const getActivityDetail = (id) => api.get(`/community/activities/${id}/`);
export const signUpActivity = (id) => api.post(`/community/activities/${id}/signup/`);
+export const getMySignups = () => api.get('/community/activities/my_signups/');
export default api;
diff --git a/frontend/src/components/activity/ActivityCard.stories.jsx b/frontend/src/components/activity/ActivityCard.stories.jsx
new file mode 100644
index 0000000..afd33af
--- /dev/null
+++ b/frontend/src/components/activity/ActivityCard.stories.jsx
@@ -0,0 +1,67 @@
+
+import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import ActivityCard from './ActivityCard';
+import '../../index.css'; // Global styles
+import '../../App.css';
+
+export default {
+ title: 'Components/Activity/ActivityCard',
+ component: ActivityCard,
+ decorators: [
+ (Story) => (
+
+
+
+
+
+ ),
+ ],
+ tags: ['autodocs'],
+};
+
+const Template = (args) => ;
+
+export const NotStarted = Template.bind({});
+NotStarted.args = {
+ activity: {
+ id: 1,
+ title: 'Future AI Hardware Summit 2026',
+ start_time: '2026-12-01T09:00:00',
+ status: '即将开始',
+ cover_image: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?auto=format&fit=crop&q=80',
+ },
+};
+
+export const Ongoing = Template.bind({});
+Ongoing.args = {
+ activity: {
+ id: 2,
+ title: 'Edge Computing Hackathon',
+ start_time: '2025-10-20T10:00:00',
+ status: '报名中',
+ cover_image: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?auto=format&fit=crop&q=80',
+ },
+};
+
+export const Ended = Template.bind({});
+Ended.args = {
+ activity: {
+ id: 3,
+ title: 'Deep Learning Workshop',
+ start_time: '2023-05-15T14:00:00',
+ status: '已结束',
+ cover_image: 'https://images.unsplash.com/photo-1518770660439-4636190af475?auto=format&fit=crop&q=80',
+ },
+};
+
+export const SignedUp = Template.bind({});
+SignedUp.args = {
+ activity: {
+ id: 4,
+ title: 'Exclusive Developer Meetup',
+ start_time: '2025-11-11T18:00:00',
+ status: '已报名',
+ cover_image: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&q=80',
+ },
+};
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
index 94253e8..e73f3a5 100644
--- a/frontend/src/pages/Home.jsx
+++ b/frontend/src/pages/Home.jsx
@@ -4,6 +4,7 @@ import { RocketOutlined, RightOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { getConfigs } from '../api';
+import ActivityList from '../components/activity/ActivityList';
import './Home.css';
const { Title, Paragraph } = Typography;
@@ -107,6 +108,10 @@ const Home = () => {
+
+
diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx
new file mode 100644
index 0000000..3c3a789
--- /dev/null
+++ b/frontend/src/pages/activity/Detail.jsx
@@ -0,0 +1,212 @@
+
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { motion, useScroll, useTransform } from 'framer-motion';
+import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined } from '@ant-design/icons';
+import confetti from 'canvas-confetti';
+import { message, Spin, Button, Result } from 'antd';
+import { getActivityDetail, signUpActivity } from '../../api';
+import styles from '../../components/activity/activity.module.less';
+import { pageTransition, buttonTap } from '../../animation';
+
+const ActivityDetail = () => {
+ const { id } = useParams();
+ const navigate = useNavigate();
+ const queryClient = useQueryClient();
+ const { scrollY } = useScroll();
+
+ // Header animation: transparent to white with shadow
+ const headerBg = useTransform(scrollY, [0, 60], ['rgba(255,255,255,0)', 'rgba(255,255,255,1)']);
+ const headerShadow = useTransform(scrollY, [0, 60], ['none', '0 2px 8px rgba(0,0,0,0.1)']);
+ const headerColor = useTransform(scrollY, [0, 60], ['rgba(255,255,255,1)', 'rgba(0,0,0,0.85)']);
+
+ const { data: activity, isLoading, error } = useQuery({
+ queryKey: ['activity', id],
+ queryFn: async () => {
+ try {
+ const res = await getActivityDetail(id);
+ return res.data;
+ } catch (err) {
+ throw new Error(err.response?.data?.detail || 'Failed to load activity');
+ }
+ },
+ staleTime: 5 * 60 * 1000,
+ });
+
+ const signUpMutation = useMutation({
+ mutationFn: () => signUpActivity(id),
+ onSuccess: () => {
+ message.success('报名成功!');
+ confetti({
+ particleCount: 150,
+ spread: 70,
+ origin: { y: 0.6 },
+ colors: ['#00b96b', '#1890ff', '#ffffff']
+ });
+ queryClient.invalidateQueries(['activity', id]);
+ queryClient.invalidateQueries(['activities']);
+ },
+ onError: (err) => {
+ message.error(err.response?.data?.detail || '报名失败,请稍后重试');
+ }
+ });
+
+ const handleShare = async () => {
+ const url = window.location.href;
+ if (navigator.share) {
+ try {
+ await navigator.share({
+ title: activity?.title,
+ text: '来看看这个精彩活动!',
+ url: url
+ });
+ } catch (err) {
+ console.log('Share canceled');
+ }
+ } else {
+ navigator.clipboard.writeText(url);
+ message.success('链接已复制到剪贴板');
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ navigate(-1)}>
+ 返回列表
+
+ ]}
+ />
+
+ );
+ }
+
+ return (
+
+ {/* Sticky Header */}
+
+ navigate(-1)}
+ style={{ cursor: 'pointer', color: headerColor, fontSize: 20 }}
+ >
+
+
+
+ {activity.title}
+
+
+
+
+
+
+ {/* Hero Image with LayoutId for shared transition */}
+
+
+ {/* Content */}
+
+
+
{activity.title}
+
+
+
+
+ {activity.start_time ? new Date(activity.start_time).toLocaleDateString() : 'TBD'}
+
+
+
+ {activity.location || '线上活动'}
+
+
+
+
+
+ {activity.status || (new Date() < new Date(activity.start_time) ? '报名中' : '已结束')}
+
+
+
+
+
+
活动详情
+
暂无详情描述' }} />
+
+
+
+ {/* Fixed Footer */}
+
+
+ 距离报名截止
+
+ {/* Simple countdown placeholder */}
+ 3天 12小时
+
+
+
+ {signUpMutation.isPending ? '提交中...' : activity.is_signed_up ? '已报名' : '立即报名'}
+
+
+
+ );
+};
+
+export default ActivityDetail;