hookehuyr

feat(router): 添加活动页和活动详情页路由

新增活动页和活动详情页的路由配置,以便用户可以访问活动相关页面
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-20 21:16:30 4 + * @LastEditTime: 2025-03-20 21:47:32
5 * @FilePath: /mlaj/src/router/index.js 5 * @FilePath: /mlaj/src/router/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -44,6 +44,19 @@ const routes = [ ...@@ -44,6 +44,19 @@ const routes = [
44 component: () => import('../views/auth/RegisterPage.vue'), 44 component: () => import('../views/auth/RegisterPage.vue'),
45 meta: { title: 'Register' } 45 meta: { title: 'Register' }
46 }, 46 },
47 + {
48 + path: '/activities',
49 + name: 'Activities',
50 + component: () => import('../views/activities/ActivitiesPage.vue'),
51 + meta: { title: 'Activities' }
52 + },
53 + {
54 + path: '/activities/:id',
55 + name: 'ActivityDetail',
56 + component: () => import('../views/activities/ActivityDetailPage.vue'),
57 + props: true,
58 + meta: { title: 'Home' }
59 + },
47 ] 60 ]
48 61
49 const router = createRouter({ 62 const router = createRouter({
......
1 +<template>
2 + <AppLayout title="最新活动" :showBackButton="true" :rightContent="RightContent">
3 + <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
4 + <!-- Top filter section -->
5 + <div class="sticky top-0 z-10 bg-white/80 backdrop-blur-sm shadow-sm">
6 + <!-- Category filters -->
7 + <div class="px-4 pt-3 pb-2 overflow-x-auto">
8 + <div class="flex space-x-6 whitespace-nowrap">
9 + <button
10 + v-for="filter in filterCategories"
11 + :key="filter"
12 + :class="[
13 + 'pb-1',
14 + activeFilter === filter
15 + ? 'text-green-600 border-b-2 border-green-600 font-medium'
16 + : 'text-gray-500'
17 + ]"
18 + @click="activeFilter = filter"
19 + >
20 + {{ filter }}
21 + </button>
22 + </div>
23 + </div>
24 +
25 + <!-- Additional filters -->
26 + <div class="px-4 py-2 flex justify-between items-center border-b border-gray-100">
27 + <div class="flex space-x-2">
28 + <div
29 + class="flex items-center px-3 py-1 bg-white/70 backdrop-blur-sm rounded-full text-sm border border-gray-200 shadow-sm"
30 + @click="showFilters = !showFilters"
31 + >
32 + <span class="text-gray-700">{{ locationFilter || '活动地点' }}</span>
33 + <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
34 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
35 + </svg>
36 + </div>
37 +
38 + <div
39 + class="flex items-center px-3 py-1 bg-white/70 backdrop-blur-sm rounded-full text-sm border border-gray-200 shadow-sm"
40 + @click="showFilters = !showFilters"
41 + >
42 + <span class="text-gray-700">{{ statusFilter || '活动状态' }}</span>
43 + <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
44 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
45 + </svg>
46 + </div>
47 + </div>
48 +
49 + <button
50 + :class="[
51 + 'p-1 rounded-lg',
52 + showFilters ? 'bg-green-100 text-green-600' : 'bg-white/70 text-gray-500'
53 + ]"
54 + @click="toggleFilters"
55 + >
56 + <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
57 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
58 + </svg>
59 + </button>
60 + </div>
61 + </div>
62 +
63 + <!-- Filter panel -->
64 + <div v-if="showFilters" class="bg-white/90 backdrop-blur-md p-4 shadow-md">
65 + <div class="mb-4">
66 + <h3 class="font-medium text-gray-700 mb-2">活动地点</h3>
67 + <div class="flex flex-wrap gap-2">
68 + <button
69 + v-for="location in locationOptions"
70 + :key="location"
71 + :class="[
72 + 'px-3 py-1 rounded-full text-sm',
73 + (location === '全部' && !locationFilter) || locationFilter === location
74 + ? 'bg-green-100 text-green-600 border border-green-200'
75 + : 'bg-gray-100 text-gray-600 border border-gray-200'
76 + ]"
77 + @click="applyLocationFilter(location)"
78 + >
79 + {{ location }}
80 + </button>
81 + </div>
82 + </div>
83 +
84 + <div class="mb-4">
85 + <h3 class="font-medium text-gray-700 mb-2">活动状态</h3>
86 + <div class="flex flex-wrap gap-2">
87 + <button
88 + v-for="status in statusOptions"
89 + :key="status"
90 + :class="[
91 + 'px-3 py-1 rounded-full text-sm',
92 + (status === '全部' && !statusFilter) || statusFilter === status
93 + ? 'bg-green-100 text-green-600 border border-green-200'
94 + : 'bg-gray-100 text-gray-600 border border-gray-200'
95 + ]"
96 + @click="applyStatusFilter(status)"
97 + >
98 + {{ status }}
99 + </button>
100 + </div>
101 + </div>
102 +
103 + <div class="flex justify-between">
104 + <button
105 + class="px-4 py-2 text-gray-600 bg-gray-100 rounded-lg"
106 + @click="resetFilters"
107 + >
108 + 重置
109 + </button>
110 + <button
111 + class="px-4 py-2 text-white bg-green-600 rounded-lg"
112 + @click="showFilters = false"
113 + >
114 + 确认
115 + </button>
116 + </div>
117 + </div>
118 +
119 + <!-- Activity count -->
120 + <div class="px-4 py-3 text-sm text-gray-500">
121 + 共 {{ filteredActivities.length }} 个活动
122 + </div>
123 +
124 + <!-- Activity list -->
125 + <div class="px-4 pb-4 space-y-4">
126 + <template v-if="filteredActivities.length > 0">
127 + <ActivityCard
128 + v-for="activity in filteredActivities"
129 + :key="activity.id"
130 + :activity="activity"
131 + />
132 + </template>
133 + <div v-else class="flex flex-col items-center justify-center py-10">
134 + <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
135 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
136 + </svg>
137 + <p class="mt-4 text-gray-500">没有找到符合条件的活动</p>
138 + <button
139 + class="mt-2 px-4 py-2 text-green-600 border border-green-600 rounded-full text-sm"
140 + @click="resetFilters"
141 + >
142 + 重置筛选条件
143 + </button>
144 + </div>
145 + </div>
146 + </div>
147 + </AppLayout>
148 +</template>
149 +
150 +<script setup>
151 +import { ref, computed, defineComponent, h } from 'vue'
152 +import AppLayout from '@/components/layout/AppLayout.vue'
153 +import ActivityCard from '@/components/ui/ActivityCard.vue'
154 +import { activities } from '@/utils/mockData'
155 +
156 +// 响应式状态
157 +const activeFilter = ref('全部')
158 +const locationFilter = ref('')
159 +const statusFilter = ref('')
160 +const showFilters = ref(false)
161 +
162 +// 过滤选项
163 +const filterCategories = ['全部', '游学', '共读', '营地', '线上讲座']
164 +const locationOptions = ['全部', '线上', '北京', '上海', '广州', '成都']
165 +const statusOptions = ['全部', '活动中', '即将开始', '已结束']
166 +
167 +// 右侧内容组件
168 +const RightContent = defineComponent({
169 + setup() {
170 + return () => h('button', { class: 'p-2' }, [
171 + h('svg', {
172 + xmlns: 'http://www.w3.org/2000/svg',
173 + class: 'h-6 w-6 text-gray-700',
174 + fill: 'none',
175 + viewBox: '0 0 24 24',
176 + stroke: 'currentColor'
177 + }, [
178 + h('path', {
179 + 'stroke-linecap': 'round',
180 + 'stroke-linejoin': 'round',
181 + 'stroke-width': '2',
182 + d: 'M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z'
183 + })
184 + ])
185 + ])
186 + }
187 +})
188 +
189 +// 切换筛选面板
190 +const toggleFilters = () => {
191 + showFilters.value = !showFilters.value
192 +}
193 +
194 +// 应用地点筛选
195 +const applyLocationFilter = (location) => {
196 + locationFilter.value = location === '全部' ? '' : location
197 +}
198 +
199 +// 应用状态筛选
200 +const applyStatusFilter = (status) => {
201 + statusFilter.value = status === '全部' ? '' : status
202 +}
203 +
204 +// 重置所有筛选
205 +const resetFilters = () => {
206 + locationFilter.value = ''
207 + statusFilter.value = ''
208 + activeFilter.value = '全部'
209 + showFilters.value = false
210 +}
211 +
212 +// 计算筛选后的活动列表
213 +const filteredActivities = computed(() => {
214 + return activities.filter(activity => {
215 + // 地点筛选
216 + if (locationFilter.value && activity.location !== locationFilter.value) {
217 + return false
218 + }
219 +
220 + // 状态筛选
221 + if (statusFilter.value && activity.status !== statusFilter.value) {
222 + return false
223 + }
224 +
225 + return true
226 + })
227 +})
228 +</script>
1 +<script setup>
2 +import { ref, onMounted, defineComponent, h } from 'vue'
3 +import { useRoute, useRouter } from 'vue-router'
4 +import AppLayout from '@/components/layout/AppLayout.vue'
5 +import FrostedGlass from '@/components/ui/FrostedGlass.vue'
6 +import { activities } from '@/utils/mockData'
7 +
8 +const route = useRoute()
9 +const router = useRouter()
10 +const activity = ref(null)
11 +const activeTab = ref('活动信息')
12 +
13 +// 获取活动数据
14 +onMounted(() => {
15 + const id = route.params.id
16 + const foundActivity = activities.find(a => a.id === id)
17 + if (foundActivity) {
18 + activity.value = foundActivity
19 + } else {
20 + // 活动未找到,重定向到活动列表页
21 + router.push('/activities')
22 + }
23 +})
24 +
25 +// 获取状态颜色类名
26 +const getStatusColorClass = (status) => {
27 + switch (status) {
28 + case '活动中':
29 + return 'bg-blue-100 text-blue-700'
30 + case '进行中':
31 + return 'bg-green-100 text-green-700'
32 + case '即将开始':
33 + return 'bg-orange-100 text-orange-700'
34 + case '已结束':
35 + return 'bg-gray-100 text-gray-700'
36 + default:
37 + return 'bg-gray-100 text-gray-700'
38 + }
39 +}
40 +
41 +// 相关活动
42 +const relatedActivities = ref([])
43 +onMounted(() => {
44 + if (activity.value) {
45 + relatedActivities.value = activities
46 + .filter(a => a.id !== activity.value.id)
47 + .slice(0, 3)
48 + }
49 +})
50 +
51 +// 页面导航
52 +const navigateTo = (path) => {
53 + router.push(path)
54 +}
55 +
56 +// 右侧内容组件
57 +const RightContent = defineComponent({
58 + setup() {
59 + return () => h('div', { class: 'flex' }, [
60 + h('button', { class: 'p-2' }, [
61 + h('svg', {
62 + xmlns: 'http://www.w3.org/2000/svg',
63 + class: 'h-6 w-6 text-gray-700',
64 + fill: 'none',
65 + viewBox: '0 0 24 24',
66 + stroke: 'currentColor'
67 + }, [
68 + h('path', {
69 + 'stroke-linecap': 'round',
70 + 'stroke-linejoin': 'round',
71 + 'stroke-width': '2',
72 + d: 'M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z'
73 + })
74 + ])
75 + ]),
76 + h('button', { class: 'p-2' }, [
77 + h('svg', {
78 + xmlns: 'http://www.w3.org/2000/svg',
79 + class: 'h-6 w-6 text-gray-700',
80 + fill: 'none',
81 + viewBox: '0 0 24 24',
82 + stroke: 'currentColor'
83 + }, [
84 + h('path', {
85 + 'stroke-linecap': 'round',
86 + 'stroke-linejoin': 'round',
87 + 'stroke-width': '2',
88 + d: 'M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z'
89 + })
90 + ])
91 + ])
92 + ])
93 + }
94 +})
95 +</script>
96 +
97 +<template>
98 + <AppLayout title="活动详情" :showBackButton="true" :rightContent="RightContent">
99 + <div class="pb-24">
100 + <!-- Activity Cover Image -->
101 + <div class="w-full h-56 relative">
102 + <img
103 + :src="activity?.imageUrl"
104 + :alt="activity?.title"
105 + class="w-full h-full object-cover"
106 + />
107 + <div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-4">
108 + <div class="inline-block px-2 py-1 rounded bg-white/20 backdrop-blur-sm text-white text-xs mb-2">
109 + {{ activity?.subtitle }}
110 + </div>
111 + <h1 class="text-xl text-white font-bold">{{ activity?.title }}</h1>
112 + </div>
113 + </div>
114 +
115 + <!-- Activity Tags and Status -->
116 + <div class="px-4 py-3 flex justify-between items-center">
117 + <div class="flex space-x-2">
118 + <span class="px-2 py-1 rounded-full bg-green-100 text-green-700 text-xs">
119 + 亲子活动
120 + </span>
121 + <span class="px-2 py-1 rounded-full bg-blue-100 text-blue-700 text-xs">
122 + 教育
123 + </span>
124 + <span :class="['px-2 py-1 rounded-full text-xs', getStatusColorClass(activity?.status)]">
125 + {{ activity?.status }}
126 + </span>
127 + </div>
128 + <div>
129 + <button class="p-1">
130 + <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-pink-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
131 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
132 + </svg>
133 + </button>
134 + </div>
135 + </div>
136 +
137 + <!-- Tab Navigation -->
138 + <div class="border-b border-gray-200 px-4">
139 + <div class="flex space-x-8">
140 + <button
141 + v-for="tab in ['活动信息', '参与人员', '活动说明']"
142 + :key="tab"
143 + @click="activeTab = tab"
144 + :class="[
145 + 'pb-3 font-medium',
146 + activeTab === tab
147 + ? 'text-green-600 border-b-2 border-green-600'
148 + : 'text-gray-500'
149 + ]"
150 + >
151 + {{ tab }}
152 + </button>
153 + </div>
154 + </div>
155 +
156 + <!-- Tab Content -->
157 + <div class="px-4 py-4">
158 + <div v-if="activeTab === '活动信息'">
159 + <!-- Activity Details -->
160 + <FrostedGlass class="p-4 mb-4 rounded-xl">
161 + <div class="space-y-4">
162 + <div class="flex items-start">
163 + <div class="p-2 rounded-full bg-green-100 mr-3">
164 + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
165 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
166 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
167 + </svg>
168 + </div>
169 + <div class="flex-1">
170 + <p class="font-medium">活动地点</p>
171 + <p class="text-gray-600 text-sm mt-1">{{ activity?.location }}</p>
172 + </div>
173 + </div>
174 +
175 + <div class="flex items-start">
176 + <div class="p-2 rounded-full bg-blue-100 mr-3">
177 + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
178 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
179 + </svg>
180 + </div>
181 + <div class="flex-1">
182 + <p class="font-medium">活动时间</p>
183 + <p class="text-gray-600 text-sm mt-1">{{ activity?.period }}</p>
184 + <p class="text-xs text-gray-500 mt-1">每周六、日 10:00-12:00</p>
185 + </div>
186 + </div>
187 +
188 + <div class="flex items-start">
189 + <div class="p-2 rounded-full bg-purple-100 mr-3">
190 + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
191 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
192 + </svg>
193 + </div>
194 + <div class="flex-1">
195 + <p class="font-medium">参与人数</p>
196 + <p class="text-gray-600 text-sm mt-1">{{ activity?.participantsCount }}人 / 上限{{ activity?.maxParticipants }}人</p>
197 + <div class="w-full bg-gray-200 rounded-full h-1.5 mt-2">
198 + <div class="bg-green-600 h-1.5 rounded-full" :style="{ width: (activity?.participantsCount / activity?.maxParticipants * 100) + '%' }"></div>
199 + </div>
200 + </div>
201 + </div>
202 +
203 + <div class="flex items-start">
204 + <div class="p-2 rounded-full bg-orange-100 mr-3">
205 + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
206 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
207 + </svg>
208 + </div>
209 + <div class="flex-1">
210 + <p class="font-medium">活动费用</p>
211 + <p class="text-gray-600 text-sm mt-1">¥{{ activity?.price }}/人(包含材料费、场地费)</p>
212 + </div>
213 + </div>
214 + </div>
215 + </FrostedGlass>
216 +
217 + <!-- Activity Description -->
218 + <FrostedGlass class="p-4 mb-4 rounded-xl">
219 + <h2 class="text-lg font-semibold mb-3">活动介绍</h2>
220 + <p class="text-gray-700 whitespace-pre-line">
221 + {{ activity?.title }} 是一个精心设计的亲子互动活动,旨在增强家长与孩子之间的沟通与理解。
222 +
223 + 通过一系列有趣的游戏和学习环节,让孩子在轻松愉快的氛围中学习知识,培养良好的学习习惯和价值观。同时,也给予家长更多指导和支持,帮助他们更好地理解孩子的需求和成长过程。
224 +
225 + 活动特色:
226 + - 专业导师全程引导
227 + - 互动性强,参与感高
228 + - 寓教于乐,收获满满
229 + - 结交志同道合的家庭
230 + </p>
231 + </FrostedGlass>
232 + </div>
233 +
234 + <div v-if="activeTab === '参与人员'">
235 + <FrostedGlass class="p-4 rounded-xl">
236 + <h2 class="text-lg font-semibold mb-3">活动参与者</h2>
237 + <div class="grid grid-cols-4 gap-4">
238 + <div v-for="index in 12" :key="index" class="flex flex-col items-center">
239 + <div class="w-14 h-14 rounded-full bg-gray-200 overflow-hidden mb-1">
240 + <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
241 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
242 + </svg>
243 + </div>
244 + <span class="text-xs text-gray-600">用户{{ index }}</span>
245 + </div>
246 + </div>
247 + <div class="mt-4 flex justify-center">
248 + <button class="text-green-600 text-sm">
249 + 查看全部参与者
250 + </button>
251 + </div>
252 + </FrostedGlass>
253 + </div>
254 +
255 + <div v-if="activeTab === '活动说明'">
256 + <FrostedGlass class="p-4 rounded-xl">
257 + <h2 class="text-lg font-semibold mb-3">活动说明</h2>
258 + <div class="space-y-4 text-gray-700">
259 + <div>
260 + <h3 class="font-medium mb-1">适合人群</h3>
261 + <p class="text-sm">5-12岁儿童及其家长</p>
262 + </div>
263 + <div>
264 + <h3 class="font-medium mb-1">注意事项</h3>
265 + <ul class="text-sm list-disc pl-5 space-y-1">
266 + <li>请提前15分钟到达活动地点</li>
267 + <li>携带必要的学习用具(铅笔、彩笔等)</li>
268 + <li>穿着舒适的衣物</li>
269 + <li>如需取消报名,请提前48小时通知我们</li>
270 + </ul>
271 + </div>
272 + <div>
273 + <h3 class="font-medium mb-1">活动流程</h3>
274 + <ol class="text-sm list-decimal pl-5 space-y-1">
275 + <li>签到入场(15分钟)</li>
276 + <li>活动介绍与热身(20分钟)</li>
277 + <li>主题活动(60分钟)</li>
278 + <li>小组分享(30分钟)</li>
279 + <li>总结与合影(15分钟)</li>
280 + </ol>
281 + </div>
282 + </div>
283 + </FrostedGlass>
284 + </div>
285 + </div>
286 +
287 + <!-- Related Activities -->
288 + <div class="px-4 py-2">
289 + <h2 class="text-lg font-semibold mb-3">相关活动</h2>
290 + <div class="space-y-3">
291 + <div
292 + v-for="relatedActivity in relatedActivities"
293 + :key="relatedActivity.id"
294 + class="flex items-center bg-white/80 p-2 rounded-lg shadow-sm"
295 + @click="navigateTo(`/activities/${relatedActivity.id}`)"
296 + >
297 + <div class="w-16 h-16 rounded-lg overflow-hidden mr-3">
298 + <img
299 + :src="relatedActivity.imageUrl"
300 + :alt="relatedActivity.title"
301 + class="w-full h-full object-cover"
302 + />
303 + </div>
304 + <div class="flex-1">
305 + <h3 class="font-medium line-clamp-1">{{ relatedActivity.title }}</h3>
306 + <p class="text-xs text-gray-500 mt-1">
307 + {{ relatedActivity.period }}
308 + </p>
309 + </div>
310 + </div>
311 + </div>
312 + </div>
313 + </div>
314 + </AppLayout>
315 +</template>