feat(checkin): 添加打卡地图列表页面
- 创建打卡地图列表页面,展示6个打卡活动 - 修改底部导航栏,将"乐在重阳"改为"打卡地图" - 实现海报式卡片布局(一行两个) - 卡片包含封面图、标题、活动日期、进入按钮 - 点击按钮跳转到活动封面页,并传递活动参数 - 在 app.config.js 中注册新页面 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
5 changed files
with
282 additions
and
30 deletions
| ... | @@ -36,22 +36,21 @@ export default { | ... | @@ -36,22 +36,21 @@ export default { |
| 36 | 'pages/FamilyRank/index', | 36 | 'pages/FamilyRank/index', |
| 37 | 'pages/PosterCheckin/index', | 37 | 'pages/PosterCheckin/index', |
| 38 | 'pages/CheckinList/index', | 38 | 'pages/CheckinList/index', |
| 39 | + 'pages/CheckinMap/index', | ||
| 39 | 'pages/JoinOrganization/index', | 40 | 'pages/JoinOrganization/index', |
| 40 | ], | 41 | ], |
| 41 | window: { | 42 | window: { |
| 42 | backgroundTextStyle: 'light', | 43 | backgroundTextStyle: 'light', |
| 43 | navigationBarBackgroundColor: '#fff', | 44 | navigationBarBackgroundColor: '#fff', |
| 44 | navigationBarTitleText: 'WeChat', | 45 | navigationBarTitleText: 'WeChat', |
| 45 | - navigationBarTextStyle: 'black' | 46 | + navigationBarTextStyle: 'black', |
| 46 | }, | 47 | }, |
| 47 | // 声明需要使用的私有信息 | 48 | // 声明需要使用的私有信息 |
| 48 | - requiredPrivateInfos: [ | 49 | + requiredPrivateInfos: ['getLocation'], |
| 49 | - 'getLocation' | ||
| 50 | - ], | ||
| 51 | // 权限配置 | 50 | // 权限配置 |
| 52 | permission: { | 51 | permission: { |
| 53 | 'scope.userLocation': { | 52 | 'scope.userLocation': { |
| 54 | - desc: '您的位置信息将用于参与奖励和位置相关服务' | 53 | + desc: '您的位置信息将用于参与奖励和位置相关服务', |
| 55 | }, | 54 | }, |
| 56 | // 'scope.werun': { | 55 | // 'scope.werun': { |
| 57 | // desc: '您的微信运动数据将用于记录步数和健康统计' | 56 | // desc: '您的微信运动数据将用于记录步数和健康统计' |
| ... | @@ -59,5 +58,5 @@ export default { | ... | @@ -59,5 +58,5 @@ export default { |
| 59 | // 'scope.userInfo': { | 58 | // 'scope.userInfo': { |
| 60 | // desc: '您的用户信息将用于个人中心和参与奖励' | 59 | // desc: '您的用户信息将用于个人中心和参与奖励' |
| 61 | // } | 60 | // } |
| 62 | - } | 61 | + }, |
| 63 | } | 62 | } | ... | ... |
| ... | @@ -6,12 +6,17 @@ | ... | @@ -6,12 +6,17 @@ |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | - <view class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50"> | 9 | + <view |
| 10 | + class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50" | ||
| 11 | + > | ||
| 10 | <view | 12 | <view |
| 11 | v-for="item in navItems" | 13 | v-for="item in navItems" |
| 12 | :key="item.path" | 14 | :key="item.path" |
| 13 | @click="navigate(item.path)" | 15 | @click="navigate(item.path)" |
| 14 | - :class="['flex flex-col items-center py-2 px-5', isActive(item.path) ? 'icon-text-color-active' : 'icon-text-color-default']" | 16 | + :class="[ |
| 17 | + 'flex flex-col items-center py-2 px-5', | ||
| 18 | + isActive(item.path) ? 'icon-text-color-active' : 'icon-text-color-default', | ||
| 19 | + ]" | ||
| 15 | > | 20 | > |
| 16 | <image :src="isActive(item.path) ? item.activeIcon : item.icon" class="w-6 h-6" /> | 21 | <image :src="isActive(item.path) ? item.activeIcon : item.icon" class="w-6 h-6" /> |
| 17 | <span class="text-xs mt-1">{{ item.label }}</span> | 22 | <span class="text-xs mt-1">{{ item.label }}</span> |
| ... | @@ -21,37 +26,47 @@ | ... | @@ -21,37 +26,47 @@ |
| 21 | </template> | 26 | </template> |
| 22 | 27 | ||
| 23 | <script setup> | 28 | <script setup> |
| 24 | -import { computed, shallowRef } from 'vue'; | 29 | +import { computed, shallowRef } from 'vue' |
| 25 | -import Taro from '@tarojs/taro'; | 30 | +import Taro from '@tarojs/taro' |
| 26 | // | 31 | // |
| 27 | -const homeIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/home.svg'; | 32 | +const homeIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/home.svg' |
| 28 | -const homeIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/home_active1.svg'; | 33 | +const homeIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/home_active1.svg' |
| 29 | -const rewardsIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards.svg'; | 34 | +const rewardsIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards.svg' |
| 30 | -const rewardsIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards_active1.svg'; | 35 | +const rewardsIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards_active1.svg' |
| 31 | -const activitiesIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities.svg'; | 36 | +const activitiesIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities.svg' |
| 32 | -const activitiesIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities_active1.svg'; | 37 | +const activitiesIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities_active1.svg' |
| 33 | -const meIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/me.svg'; | 38 | +const meIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/me.svg' |
| 34 | -const meIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/me_active1.svg'; | 39 | +const meIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/me_active1.svg' |
| 35 | 40 | ||
| 36 | const navItems = shallowRef([ | 41 | const navItems = shallowRef([ |
| 37 | { path: '/pages/Dashboard/index', icon: homeIcon, activeIcon: homeIconActive, label: '首页' }, | 42 | { path: '/pages/Dashboard/index', icon: homeIcon, activeIcon: homeIconActive, label: '首页' }, |
| 38 | - { path: '/pages/ActivitiesCover/index', icon: activitiesIcon, activeIcon: activitiesIconActive, label: '乐在重阳' }, | 43 | + { |
| 44 | + path: '/pages/CheckinMap/index', | ||
| 45 | + icon: activitiesIcon, | ||
| 46 | + activeIcon: activitiesIconActive, | ||
| 47 | + label: '打卡地图', | ||
| 48 | + }, | ||
| 39 | // { path: '/pages/RewardCategories/index', icon: rewardsIcon, activeIcon: rewardsIconActive, label: '兑换' }, | 49 | // { path: '/pages/RewardCategories/index', icon: rewardsIcon, activeIcon: rewardsIconActive, label: '兑换' }, |
| 40 | // TAG: 暂时写死以后可能会改变 | 50 | // TAG: 暂时写死以后可能会改变 |
| 41 | - { path: '/pages/Rewards/index?id=health&category=health', icon: rewardsIcon, activeIcon: rewardsIconActive, label: '兑换' }, | 51 | + { |
| 52 | + path: '/pages/Rewards/index?id=health&category=health', | ||
| 53 | + icon: rewardsIcon, | ||
| 54 | + activeIcon: rewardsIconActive, | ||
| 55 | + label: '兑换', | ||
| 56 | + }, | ||
| 42 | { path: '/pages/Profile/index', icon: meIcon, activeIcon: meIconActive, label: '我的' }, | 57 | { path: '/pages/Profile/index', icon: meIcon, activeIcon: meIconActive, label: '我的' }, |
| 43 | -]); | 58 | +]) |
| 44 | 59 | ||
| 45 | const currentPage = computed(() => { | 60 | const currentPage = computed(() => { |
| 46 | - const pages = Taro.getCurrentPages(); | 61 | + const pages = Taro.getCurrentPages() |
| 47 | - return pages.length > 0 ? '/' + pages[pages.length - 1].route : ''; | 62 | + return pages.length > 0 ? '/' + pages[pages.length - 1].route : '' |
| 48 | -}); | 63 | +}) |
| 49 | 64 | ||
| 50 | -const isActive = (path) => { | 65 | +const isActive = path => { |
| 51 | - return path.includes(currentPage.value); | 66 | + return path.includes(currentPage.value) |
| 52 | -}; | 67 | +} |
| 53 | 68 | ||
| 54 | -const navigate = (path) => { | 69 | +const navigate = path => { |
| 55 | // 获取当前页面栈 | 70 | // 获取当前页面栈 |
| 56 | const pages = Taro.getCurrentPages() | 71 | const pages = Taro.getCurrentPages() |
| 57 | const currentPage = pages[pages.length - 1] | 72 | const currentPage = pages[pages.length - 1] |
| ... | @@ -60,9 +75,9 @@ const navigate = (path) => { | ... | @@ -60,9 +75,9 @@ const navigate = (path) => { |
| 60 | if (currentRoute === 'pages/Welcome/index' && path === '/pages/Dashboard/index') { | 75 | if (currentRoute === 'pages/Welcome/index' && path === '/pages/Dashboard/index') { |
| 61 | return | 76 | return |
| 62 | } else { | 77 | } else { |
| 63 | - Taro.redirectTo({ url: path }); | 78 | + Taro.redirectTo({ url: path }) |
| 64 | } | 79 | } |
| 65 | -}; | 80 | +} |
| 66 | </script> | 81 | </script> |
| 67 | 82 | ||
| 68 | <style lang="less"> | 83 | <style lang="less"> | ... | ... |
src/pages/CheckinMap/CLAUDE.md
0 → 100644
| 1 | +<claude-mem-context> | ||
| 2 | +# Recent Activity | ||
| 3 | + | ||
| 4 | +<!-- This section is auto-generated by claude-mem. Edit content outside the tags. --> | ||
| 5 | + | ||
| 6 | +### Feb 5, 2026 | ||
| 7 | + | ||
| 8 | +| ID | Time | T | Title | Read | | ||
| 9 | +|----|------|---|-------|------| | ||
| 10 | +| #1550 | 8:11 PM | 🔄 | Increased Card Cover Image Height | ~155 | | ||
| 11 | +| #1548 | " | 🔄 | Enhanced Activity Time Display with Label Wrapper | ~230 | | ||
| 12 | +| #1545 | " | 🟣 | Implemented CheckinMap Activity Listing Page | ~325 | | ||
| 13 | +| #1538 | 8:03 PM | 🟣 | Created CheckinMap Page Configuration | ~171 | | ||
| 14 | +</claude-mem-context> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/pages/CheckinMap/index.config.js
0 → 100644
src/pages/CheckinMap/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2026-02-05 19:48:00 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2026-02-05 20:09:34 | ||
| 5 | + * @FilePath: /lls_program/src/pages/CheckinMap/index.vue | ||
| 6 | + * @Description: 打卡地图列表页 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <view class="checkin-map-page"> | ||
| 10 | + <!-- 列表内容 --> | ||
| 11 | + <view class="map-list"> | ||
| 12 | + <view v-for="item in mapList" :key="item.id" class="map-card" @tap="handleCardClick(item)"> | ||
| 13 | + <!-- 封面图 --> | ||
| 14 | + <view class="card-cover"> | ||
| 15 | + <image :src="item.cover" mode="aspectFill" class="cover-image" /> | ||
| 16 | + </view> | ||
| 17 | + | ||
| 18 | + <!-- 卡片内容 --> | ||
| 19 | + <view class="card-content"> | ||
| 20 | + <text class="card-title">{{ item.title }}</text> | ||
| 21 | + <view class="card-time-wrapper"> | ||
| 22 | + <text class="card-time-label">活动日期</text> | ||
| 23 | + <text class="card-time">{{ item.timeRange }}</text> | ||
| 24 | + </view> | ||
| 25 | + <view class="enter-btn" @tap.stop="handleEnter(item)"> | ||
| 26 | + <text class="enter-btn-text">立即进入</text> | ||
| 27 | + </view> | ||
| 28 | + </view> | ||
| 29 | + </view> | ||
| 30 | + </view> | ||
| 31 | + | ||
| 32 | + <BottomNav /> | ||
| 33 | + </view> | ||
| 34 | +</template> | ||
| 35 | + | ||
| 36 | +<script setup> | ||
| 37 | +import { ref } from 'vue' | ||
| 38 | +import Taro from '@tarojs/taro' | ||
| 39 | +import BottomNav from '@/components/BottomNav.vue' | ||
| 40 | + | ||
| 41 | +/** | ||
| 42 | + * Mock 打卡地图数据 | ||
| 43 | + */ | ||
| 44 | +const mapList = ref([ | ||
| 45 | + { | ||
| 46 | + id: 1, | ||
| 47 | + title: '重阳登高打卡', | ||
| 48 | + cover: 'https://picsum.photos/400/300?random=1', | ||
| 49 | + timeRange: '2025.09.06~2025.10.31', | ||
| 50 | + activityId: 'chongyang_2024', | ||
| 51 | + }, | ||
| 52 | + { | ||
| 53 | + id: 2, | ||
| 54 | + title: '公园晨跑打卡', | ||
| 55 | + cover: 'https://picsum.photos/400/300?random=2', | ||
| 56 | + timeRange: '2025.09.01~2025.12.31', | ||
| 57 | + activityId: 'morning_run_2024', | ||
| 58 | + }, | ||
| 59 | + { | ||
| 60 | + id: 3, | ||
| 61 | + title: '社区健身打卡', | ||
| 62 | + cover: 'https://picsum.photos/400/300?random=3', | ||
| 63 | + timeRange: '2025.08.01~2025.12.31', | ||
| 64 | + activityId: 'community_fitness', | ||
| 65 | + }, | ||
| 66 | + { | ||
| 67 | + id: 4, | ||
| 68 | + title: '周末徒步打卡', | ||
| 69 | + cover: 'https://picsum.photos/400/300?random=4', | ||
| 70 | + timeRange: '2025.09.15~2025.11.30', | ||
| 71 | + activityId: 'weekend_hike', | ||
| 72 | + }, | ||
| 73 | + { | ||
| 74 | + id: 5, | ||
| 75 | + title: '秋日赏菊打卡', | ||
| 76 | + cover: 'https://picsum.photos/400/300?random=5', | ||
| 77 | + timeRange: '2025.10.15~2025.11.15', | ||
| 78 | + activityId: 'autumn_chrysanthemum', | ||
| 79 | + }, | ||
| 80 | + { | ||
| 81 | + id: 6, | ||
| 82 | + title: '古镇文化打卡', | ||
| 83 | + cover: 'https://picsum.photos/400/300?random=6', | ||
| 84 | + timeRange: '2025.10.01~2025.10.31', | ||
| 85 | + activityId: 'ancient_town', | ||
| 86 | + }, | ||
| 87 | +]) | ||
| 88 | + | ||
| 89 | +/** | ||
| 90 | + * 处理卡片点击事件 | ||
| 91 | + * @param {Object} item - 卡片数据 | ||
| 92 | + */ | ||
| 93 | +const handleCardClick = item => { | ||
| 94 | + console.log('点击卡片:', item) | ||
| 95 | + // 可以在这里添加预览或其他交互 | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +/** | ||
| 99 | + * 进入打卡活动 | ||
| 100 | + * @param {Object} item - 活动数据 | ||
| 101 | + */ | ||
| 102 | +const handleEnter = item => { | ||
| 103 | + console.log('进入活动:', item) | ||
| 104 | + | ||
| 105 | + // 跳转到活动封面页,带上活动ID参数 | ||
| 106 | + Taro.navigateTo({ | ||
| 107 | + url: `/pages/ActivitiesCover/index?activityId=${item.activityId}&title=${encodeURIComponent(item.title)}`, | ||
| 108 | + }) | ||
| 109 | +} | ||
| 110 | +</script> | ||
| 111 | + | ||
| 112 | +<style lang="less"> | ||
| 113 | +.checkin-map-page { | ||
| 114 | + min-height: 100vh; | ||
| 115 | + background-color: #54abae; /* 项目主色调 */ | ||
| 116 | + padding-bottom: 160px; /* 为底部导航留出空间 */ | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +.page-header { | ||
| 120 | + padding: 40px 30px 20px; | ||
| 121 | + text-align: center; | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +.page-title { | ||
| 125 | + font-size: 48px; | ||
| 126 | + font-weight: bold; | ||
| 127 | + color: #ffffff; | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +.map-list { | ||
| 131 | + display: grid; | ||
| 132 | + grid-template-columns: repeat(2, 1fr); /* 一行两个 */ | ||
| 133 | + gap: 20px; | ||
| 134 | + padding: 20px 30px; | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +.map-card { | ||
| 138 | + background-color: #ffffff; | ||
| 139 | + border-radius: 16px; | ||
| 140 | + overflow: hidden; | ||
| 141 | + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | ||
| 142 | + transition: transform 0.2s; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +.map-card:active { | ||
| 146 | + transform: scale(0.98); | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +.card-cover { | ||
| 150 | + width: 100%; | ||
| 151 | + height: 360px; | ||
| 152 | + overflow: hidden; | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +.cover-image { | ||
| 156 | + width: 100%; | ||
| 157 | + height: 100%; | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +.card-content { | ||
| 161 | + padding: 20px; | ||
| 162 | + display: flex; | ||
| 163 | + flex-direction: column; | ||
| 164 | + gap: 12px; | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +.card-title { | ||
| 168 | + font-size: 28px; | ||
| 169 | + font-weight: bold; | ||
| 170 | + color: #1f2937; | ||
| 171 | + overflow: hidden; | ||
| 172 | + text-overflow: ellipsis; | ||
| 173 | + display: -webkit-box; | ||
| 174 | + -webkit-line-clamp: 2; | ||
| 175 | + -webkit-box-orient: vertical; | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +.card-time-wrapper { | ||
| 179 | + display: flex; | ||
| 180 | + flex-direction: column; | ||
| 181 | + gap: 4px; | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +.card-time-label { | ||
| 185 | + font-size: 22px; | ||
| 186 | + color: #9ca3af; | ||
| 187 | + font-weight: 500; | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +.card-time { | ||
| 191 | + font-size: 24px; | ||
| 192 | + color: #1f2937; | ||
| 193 | + font-weight: 600; | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +.enter-btn { | ||
| 197 | + margin-top: 8px; | ||
| 198 | + background-color: #54abae; | ||
| 199 | + color: #ffffff; | ||
| 200 | + padding: 16px; | ||
| 201 | + border-radius: 8px; | ||
| 202 | + text-align: center; | ||
| 203 | + font-size: 26px; | ||
| 204 | + font-weight: 500; | ||
| 205 | +} | ||
| 206 | + | ||
| 207 | +.enter-btn:active { | ||
| 208 | + background-color: #4a979a; | ||
| 209 | +} | ||
| 210 | + | ||
| 211 | +.enter-btn-text { | ||
| 212 | + color: #ffffff; | ||
| 213 | +} | ||
| 214 | +</style> |
-
Please register or login to post a comment