You need to sign in or sign up before continuing.
hookehuyr

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

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -36,22 +36,21 @@ export default {
'pages/FamilyRank/index',
'pages/PosterCheckin/index',
'pages/CheckinList/index',
'pages/CheckinMap/index',
'pages/JoinOrganization/index',
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
navigationBarTextStyle: 'black',
},
// 声明需要使用的私有信息
requiredPrivateInfos: [
'getLocation'
],
requiredPrivateInfos: ['getLocation'],
// 权限配置
permission: {
'scope.userLocation': {
desc: '您的位置信息将用于参与奖励和位置相关服务'
desc: '您的位置信息将用于参与奖励和位置相关服务',
},
// 'scope.werun': {
// desc: '您的微信运动数据将用于记录步数和健康统计'
......@@ -59,5 +58,5 @@ export default {
// 'scope.userInfo': {
// desc: '您的用户信息将用于个人中心和参与奖励'
// }
}
},
}
......
......@@ -6,12 +6,17 @@
* @Description: 文件描述
-->
<template>
<view class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50">
<view
class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2 z-50"
>
<view
v-for="item in navItems"
:key="item.path"
@click="navigate(item.path)"
:class="['flex flex-col items-center py-2 px-5', isActive(item.path) ? 'icon-text-color-active' : 'icon-text-color-default']"
:class="[
'flex flex-col items-center py-2 px-5',
isActive(item.path) ? 'icon-text-color-active' : 'icon-text-color-default',
]"
>
<image :src="isActive(item.path) ? item.activeIcon : item.icon" class="w-6 h-6" />
<span class="text-xs mt-1">{{ item.label }}</span>
......@@ -21,37 +26,47 @@
</template>
<script setup>
import { computed, shallowRef } from 'vue';
import Taro from '@tarojs/taro';
import { computed, shallowRef } from 'vue'
import Taro from '@tarojs/taro'
//
const homeIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/home.svg';
const homeIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/home_active1.svg';
const rewardsIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards.svg';
const rewardsIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards_active1.svg';
const activitiesIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities.svg';
const activitiesIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities_active1.svg';
const meIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/me.svg';
const meIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/me_active1.svg';
const homeIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/home.svg'
const homeIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/home_active1.svg'
const rewardsIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards.svg'
const rewardsIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/rewards_active1.svg'
const activitiesIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities.svg'
const activitiesIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/activities_active1.svg'
const meIcon = 'https://cdn.ipadbiz.cn/lls_prog/icon/me.svg'
const meIconActive = 'https://cdn.ipadbiz.cn/lls_prog/icon/me_active1.svg'
const navItems = shallowRef([
{ path: '/pages/Dashboard/index', icon: homeIcon, activeIcon: homeIconActive, label: '首页' },
{ path: '/pages/ActivitiesCover/index', icon: activitiesIcon, activeIcon: activitiesIconActive, label: '乐在重阳' },
{
path: '/pages/CheckinMap/index',
icon: activitiesIcon,
activeIcon: activitiesIconActive,
label: '打卡地图',
},
// { path: '/pages/RewardCategories/index', icon: rewardsIcon, activeIcon: rewardsIconActive, label: '兑换' },
// TAG: 暂时写死以后可能会改变
{ path: '/pages/Rewards/index?id=health&category=health', icon: rewardsIcon, activeIcon: rewardsIconActive, label: '兑换' },
{
path: '/pages/Rewards/index?id=health&category=health',
icon: rewardsIcon,
activeIcon: rewardsIconActive,
label: '兑换',
},
{ path: '/pages/Profile/index', icon: meIcon, activeIcon: meIconActive, label: '我的' },
]);
])
const currentPage = computed(() => {
const pages = Taro.getCurrentPages();
return pages.length > 0 ? '/' + pages[pages.length - 1].route : '';
});
const pages = Taro.getCurrentPages()
return pages.length > 0 ? '/' + pages[pages.length - 1].route : ''
})
const isActive = (path) => {
return path.includes(currentPage.value);
};
const isActive = path => {
return path.includes(currentPage.value)
}
const navigate = (path) => {
const navigate = path => {
// 获取当前页面栈
const pages = Taro.getCurrentPages()
const currentPage = pages[pages.length - 1]
......@@ -60,9 +75,9 @@ const navigate = (path) => {
if (currentRoute === 'pages/Welcome/index' && path === '/pages/Dashboard/index') {
return
} else {
Taro.redirectTo({ url: path });
Taro.redirectTo({ url: path })
}
};
}
</script>
<style lang="less">
......
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
### Feb 5, 2026
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #1550 | 8:11 PM | 🔄 | Increased Card Cover Image Height | ~155 |
| #1548 | " | 🔄 | Enhanced Activity Time Display with Label Wrapper | ~230 |
| #1545 | " | 🟣 | Implemented CheckinMap Activity Listing Page | ~325 |
| #1538 | 8:03 PM | 🟣 | Created CheckinMap Page Configuration | ~171 |
</claude-mem-context>
\ No newline at end of file
/*
* @Date: 2026-02-05 20:03:58
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-02-05 20:08:25
* @FilePath: /lls_program/src/pages/CheckinMap/index.config.js
* @Description: 打卡地图配置文件
*/
export default {
navigationBarTitleText: '打卡地图',
}
<!--
* @Date: 2026-02-05 19:48:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-02-05 20:09:34
* @FilePath: /lls_program/src/pages/CheckinMap/index.vue
* @Description: 打卡地图列表页
-->
<template>
<view class="checkin-map-page">
<!-- 列表内容 -->
<view class="map-list">
<view v-for="item in mapList" :key="item.id" class="map-card" @tap="handleCardClick(item)">
<!-- 封面图 -->
<view class="card-cover">
<image :src="item.cover" mode="aspectFill" class="cover-image" />
</view>
<!-- 卡片内容 -->
<view class="card-content">
<text class="card-title">{{ item.title }}</text>
<view class="card-time-wrapper">
<text class="card-time-label">活动日期</text>
<text class="card-time">{{ item.timeRange }}</text>
</view>
<view class="enter-btn" @tap.stop="handleEnter(item)">
<text class="enter-btn-text">立即进入</text>
</view>
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import BottomNav from '@/components/BottomNav.vue'
/**
* Mock 打卡地图数据
*/
const mapList = ref([
{
id: 1,
title: '重阳登高打卡',
cover: 'https://picsum.photos/400/300?random=1',
timeRange: '2025.09.06~2025.10.31',
activityId: 'chongyang_2024',
},
{
id: 2,
title: '公园晨跑打卡',
cover: 'https://picsum.photos/400/300?random=2',
timeRange: '2025.09.01~2025.12.31',
activityId: 'morning_run_2024',
},
{
id: 3,
title: '社区健身打卡',
cover: 'https://picsum.photos/400/300?random=3',
timeRange: '2025.08.01~2025.12.31',
activityId: 'community_fitness',
},
{
id: 4,
title: '周末徒步打卡',
cover: 'https://picsum.photos/400/300?random=4',
timeRange: '2025.09.15~2025.11.30',
activityId: 'weekend_hike',
},
{
id: 5,
title: '秋日赏菊打卡',
cover: 'https://picsum.photos/400/300?random=5',
timeRange: '2025.10.15~2025.11.15',
activityId: 'autumn_chrysanthemum',
},
{
id: 6,
title: '古镇文化打卡',
cover: 'https://picsum.photos/400/300?random=6',
timeRange: '2025.10.01~2025.10.31',
activityId: 'ancient_town',
},
])
/**
* 处理卡片点击事件
* @param {Object} item - 卡片数据
*/
const handleCardClick = item => {
console.log('点击卡片:', item)
// 可以在这里添加预览或其他交互
}
/**
* 进入打卡活动
* @param {Object} item - 活动数据
*/
const handleEnter = item => {
console.log('进入活动:', item)
// 跳转到活动封面页,带上活动ID参数
Taro.navigateTo({
url: `/pages/ActivitiesCover/index?activityId=${item.activityId}&title=${encodeURIComponent(item.title)}`,
})
}
</script>
<style lang="less">
.checkin-map-page {
min-height: 100vh;
background-color: #54abae; /* 项目主色调 */
padding-bottom: 160px; /* 为底部导航留出空间 */
}
.page-header {
padding: 40px 30px 20px;
text-align: center;
}
.page-title {
font-size: 48px;
font-weight: bold;
color: #ffffff;
}
.map-list {
display: grid;
grid-template-columns: repeat(2, 1fr); /* 一行两个 */
gap: 20px;
padding: 20px 30px;
}
.map-card {
background-color: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.map-card:active {
transform: scale(0.98);
}
.card-cover {
width: 100%;
height: 360px;
overflow: hidden;
}
.cover-image {
width: 100%;
height: 100%;
}
.card-content {
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.card-title {
font-size: 28px;
font-weight: bold;
color: #1f2937;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.card-time-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
.card-time-label {
font-size: 22px;
color: #9ca3af;
font-weight: 500;
}
.card-time {
font-size: 24px;
color: #1f2937;
font-weight: 600;
}
.enter-btn {
margin-top: 8px;
background-color: #54abae;
color: #ffffff;
padding: 16px;
border-radius: 8px;
text-align: center;
font-size: 26px;
font-weight: 500;
}
.enter-btn:active {
background-color: #4a979a;
}
.enter-btn-text {
color: #ffffff;
}
</style>