hookehuyr

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

新增活动页和活动详情页的路由配置,以便用户可以访问活动相关页面
/*
* @Date: 2025-03-20 20:36:36
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-03-20 21:16:30
* @LastEditTime: 2025-03-20 21:47:32
* @FilePath: /mlaj/src/router/index.js
* @Description: 文件描述
*/
......@@ -44,6 +44,19 @@ const routes = [
component: () => import('../views/auth/RegisterPage.vue'),
meta: { title: 'Register' }
},
{
path: '/activities',
name: 'Activities',
component: () => import('../views/activities/ActivitiesPage.vue'),
meta: { title: 'Activities' }
},
{
path: '/activities/:id',
name: 'ActivityDetail',
component: () => import('../views/activities/ActivityDetailPage.vue'),
props: true,
meta: { title: 'Home' }
},
]
const router = createRouter({
......
<template>
<AppLayout title="最新活动" :showBackButton="true" :rightContent="RightContent">
<div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
<!-- Top filter section -->
<div class="sticky top-0 z-10 bg-white/80 backdrop-blur-sm shadow-sm">
<!-- Category filters -->
<div class="px-4 pt-3 pb-2 overflow-x-auto">
<div class="flex space-x-6 whitespace-nowrap">
<button
v-for="filter in filterCategories"
:key="filter"
:class="[
'pb-1',
activeFilter === filter
? 'text-green-600 border-b-2 border-green-600 font-medium'
: 'text-gray-500'
]"
@click="activeFilter = filter"
>
{{ filter }}
</button>
</div>
</div>
<!-- Additional filters -->
<div class="px-4 py-2 flex justify-between items-center border-b border-gray-100">
<div class="flex space-x-2">
<div
class="flex items-center px-3 py-1 bg-white/70 backdrop-blur-sm rounded-full text-sm border border-gray-200 shadow-sm"
@click="showFilters = !showFilters"
>
<span class="text-gray-700">{{ locationFilter || '活动地点' }}</span>
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<div
class="flex items-center px-3 py-1 bg-white/70 backdrop-blur-sm rounded-full text-sm border border-gray-200 shadow-sm"
@click="showFilters = !showFilters"
>
<span class="text-gray-700">{{ statusFilter || '活动状态' }}</span>
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
<button
:class="[
'p-1 rounded-lg',
showFilters ? 'bg-green-100 text-green-600' : 'bg-white/70 text-gray-500'
]"
@click="toggleFilters"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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" />
</svg>
</button>
</div>
</div>
<!-- Filter panel -->
<div v-if="showFilters" class="bg-white/90 backdrop-blur-md p-4 shadow-md">
<div class="mb-4">
<h3 class="font-medium text-gray-700 mb-2">活动地点</h3>
<div class="flex flex-wrap gap-2">
<button
v-for="location in locationOptions"
:key="location"
:class="[
'px-3 py-1 rounded-full text-sm',
(location === '全部' && !locationFilter) || locationFilter === location
? 'bg-green-100 text-green-600 border border-green-200'
: 'bg-gray-100 text-gray-600 border border-gray-200'
]"
@click="applyLocationFilter(location)"
>
{{ location }}
</button>
</div>
</div>
<div class="mb-4">
<h3 class="font-medium text-gray-700 mb-2">活动状态</h3>
<div class="flex flex-wrap gap-2">
<button
v-for="status in statusOptions"
:key="status"
:class="[
'px-3 py-1 rounded-full text-sm',
(status === '全部' && !statusFilter) || statusFilter === status
? 'bg-green-100 text-green-600 border border-green-200'
: 'bg-gray-100 text-gray-600 border border-gray-200'
]"
@click="applyStatusFilter(status)"
>
{{ status }}
</button>
</div>
</div>
<div class="flex justify-between">
<button
class="px-4 py-2 text-gray-600 bg-gray-100 rounded-lg"
@click="resetFilters"
>
重置
</button>
<button
class="px-4 py-2 text-white bg-green-600 rounded-lg"
@click="showFilters = false"
>
确认
</button>
</div>
</div>
<!-- Activity count -->
<div class="px-4 py-3 text-sm text-gray-500">
共 {{ filteredActivities.length }} 个活动
</div>
<!-- Activity list -->
<div class="px-4 pb-4 space-y-4">
<template v-if="filteredActivities.length > 0">
<ActivityCard
v-for="activity in filteredActivities"
:key="activity.id"
:activity="activity"
/>
</template>
<div v-else class="flex flex-col items-center justify-center py-10">
<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">
<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" />
</svg>
<p class="mt-4 text-gray-500">没有找到符合条件的活动</p>
<button
class="mt-2 px-4 py-2 text-green-600 border border-green-600 rounded-full text-sm"
@click="resetFilters"
>
重置筛选条件
</button>
</div>
</div>
</div>
</AppLayout>
</template>
<script setup>
import { ref, computed, defineComponent, h } from 'vue'
import AppLayout from '@/components/layout/AppLayout.vue'
import ActivityCard from '@/components/ui/ActivityCard.vue'
import { activities } from '@/utils/mockData'
// 响应式状态
const activeFilter = ref('全部')
const locationFilter = ref('')
const statusFilter = ref('')
const showFilters = ref(false)
// 过滤选项
const filterCategories = ['全部', '游学', '共读', '营地', '线上讲座']
const locationOptions = ['全部', '线上', '北京', '上海', '广州', '成都']
const statusOptions = ['全部', '活动中', '即将开始', '已结束']
// 右侧内容组件
const RightContent = defineComponent({
setup() {
return () => h('button', { class: 'p-2' }, [
h('svg', {
xmlns: 'http://www.w3.org/2000/svg',
class: 'h-6 w-6 text-gray-700',
fill: 'none',
viewBox: '0 0 24 24',
stroke: 'currentColor'
}, [
h('path', {
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2',
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'
})
])
])
}
})
// 切换筛选面板
const toggleFilters = () => {
showFilters.value = !showFilters.value
}
// 应用地点筛选
const applyLocationFilter = (location) => {
locationFilter.value = location === '全部' ? '' : location
}
// 应用状态筛选
const applyStatusFilter = (status) => {
statusFilter.value = status === '全部' ? '' : status
}
// 重置所有筛选
const resetFilters = () => {
locationFilter.value = ''
statusFilter.value = ''
activeFilter.value = '全部'
showFilters.value = false
}
// 计算筛选后的活动列表
const filteredActivities = computed(() => {
return activities.filter(activity => {
// 地点筛选
if (locationFilter.value && activity.location !== locationFilter.value) {
return false
}
// 状态筛选
if (statusFilter.value && activity.status !== statusFilter.value) {
return false
}
return true
})
})
</script>
This diff is collapsed. Click to expand it.