hookehuyr

feat(checkin): 添加打卡地图列表页面

- 创建打卡地图列表页面,展示6个打卡活动
- 修改底部导航栏,将"乐在重阳"改为"打卡地图"
- 实现海报式卡片布局(一行两个)
- 卡片包含封面图、标题、活动日期、进入按钮
- 点击按钮跳转到活动封面页,并传递活动参数
- 在 app.config.js 中注册新页面

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -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">
......
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
1 +/*
2 + * @Date: 2026-02-05 20:03:58
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-02-05 20:08:25
5 + * @FilePath: /lls_program/src/pages/CheckinMap/index.config.js
6 + * @Description: 打卡地图配置文件
7 + */
8 +export default {
9 + navigationBarTitleText: '打卡地图',
10 +}
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>