Showing
3 changed files
with
557 additions
and
1 deletions
| 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({ | ... | ... |
src/views/activities/ActivitiesPage.vue
0 → 100644
| 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> |
src/views/activities/ActivityDetailPage.vue
0 → 100644
| 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> |
-
Please register or login to post a comment