hookehuyr

feat(ActivitiesCover): 新增活动海报页面并集成定位功能

添加活动海报展示页面,包含活动详情、规则和奖励信息
实现定位授权检查和位置获取功能
修改底部导航和路由配置以支持新页面
在活动页面添加位置参数处理逻辑
1 /* 1 /*
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-08-28 11:22:42 4 + * @LastEditTime: 2025-08-28 14:52:14
5 * @FilePath: /lls_program/src/app.config.js 5 * @FilePath: /lls_program/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -27,11 +27,22 @@ export default { ...@@ -27,11 +27,22 @@ export default {
27 'pages/EditProfile/index', 27 'pages/EditProfile/index',
28 'pages/EditFamily/index', 28 'pages/EditFamily/index',
29 'pages/AlbumList/index', 29 'pages/AlbumList/index',
30 + 'pages/ActivitiesCover/index',
30 ], 31 ],
31 window: { 32 window: {
32 backgroundTextStyle: 'light', 33 backgroundTextStyle: 'light',
33 navigationBarBackgroundColor: '#fff', 34 navigationBarBackgroundColor: '#fff',
34 navigationBarTitleText: 'WeChat', 35 navigationBarTitleText: 'WeChat',
35 navigationBarTextStyle: 'black' 36 navigationBarTextStyle: 'black'
37 + },
38 + // 声明需要使用的私有信息
39 + requiredPrivateInfos: [
40 + 'getLocation'
41 + ],
42 + // 权限配置
43 + permission: {
44 + 'scope.userLocation': {
45 + desc: '您的位置信息将用于活动打卡和位置相关服务'
46 + }
36 } 47 }
37 } 48 }
......
...@@ -30,7 +30,7 @@ import meIcon from '@/assets/images/icon/me.svg'; ...@@ -30,7 +30,7 @@ import meIcon from '@/assets/images/icon/me.svg';
30 30
31 const navItems = shallowRef([ 31 const navItems = shallowRef([
32 { path: '/pages/Dashboard/index', icon: homeIcon, label: '首页' }, 32 { path: '/pages/Dashboard/index', icon: homeIcon, label: '首页' },
33 - { path: '/pages/Activities/index', icon: activitiesIcon, label: '活动' }, 33 + { path: '/pages/ActivitiesCover/index', icon: activitiesIcon, label: '活动' },
34 { path: '/pages/RewardCategories/index', icon: rewardsIcon, label: '兑换' }, 34 { path: '/pages/RewardCategories/index', icon: rewardsIcon, label: '兑换' },
35 { path: '/pages/Profile/index', icon: meIcon, label: '我的' }, 35 { path: '/pages/Profile/index', icon: meIcon, label: '我的' },
36 ]); 36 ]);
......
...@@ -39,9 +39,10 @@ import BottomNav from '../../components/BottomNav.vue' ...@@ -39,9 +39,10 @@ import BottomNav from '../../components/BottomNav.vue'
39 */ 39 */
40 40
41 // 页面状态 41 // 页面状态
42 -const webUrl = ref('https://oa.onwall.cn/f/map/#/checkin/?id=2400107') // TODO: 临时设置一个URL 42 +const webUrl = ref('') // 动态设置URL
43 const loading = ref(true) 43 const loading = ref(true)
44 const error = ref(false) 44 const error = ref(false)
45 +const baseUrl = 'https://oa.onwall.cn/f/map/#/checkin/?id=2400107' // 基础URL
45 46
46 /** 47 /**
47 * 处理WebView加载完成 48 * 处理WebView加载完成
...@@ -88,10 +89,31 @@ const handleRetry = () => { ...@@ -88,10 +89,31 @@ const handleRetry = () => {
88 }, 100) 89 }, 100)
89 } 90 }
90 91
92 +/**
93 + * 构建包含位置参数的URL
94 + */
95 +const buildUrlWithLocation = (lng, lat) => {
96 + if (lng && lat) {
97 + const separator = baseUrl.includes('?') ? '&' : '?'
98 + return `${baseUrl}${separator}current_lng=${lng}&current_lat=${lat}`
99 + }
100 + return baseUrl
101 +}
102 +
91 // 页面挂载时初始化 103 // 页面挂载时初始化
92 onMounted(() => { 104 onMounted(() => {
93 - // 这里可以根据需要动态设置URL
94 console.log('活动页面WebView初始化') 105 console.log('活动页面WebView初始化')
106 +
107 + // 获取路由参数中的经纬度信息
108 + const instance = Taro.getCurrentInstance()
109 + const { current_lng, current_lat } = instance.router?.params || {}
110 +
111 + console.log('接收到的位置参数:', { current_lng, current_lat })
112 +
113 + // 构建完整的URL
114 + webUrl.value = buildUrlWithLocation(current_lng, current_lat)
115 +
116 + console.log('最终WebView URL:', webUrl.value)
95 }) 117 })
96 </script> 118 </script>
97 119
......
1 +/*
2 + * @Date: 2025-08-28 14:50:55
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 14:51:20
5 + * @FilePath: /lls_program/src/pages/ActivitiesCover/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '活动海报',
10 + usingComponents: {
11 + },
12 +}
1 +.activities-cover-container {
2 + width: 100%;
3 + min-height: 100vh;
4 + background-color: #f5f5f5;
5 + display: flex;
6 + flex-direction: column;
7 +}
8 +
9 +// 海报区域
10 +.poster-section {
11 + position: relative;
12 + width: 100%;
13 + height: 500rpx;
14 + overflow: hidden;
15 +}
16 +
17 +.poster-image {
18 + width: 100%;
19 + height: 100%;
20 + object-fit: cover;
21 +}
22 +
23 +.poster-overlay {
24 + position: absolute;
25 + bottom: 0;
26 + left: 0;
27 + right: 0;
28 + background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
29 + padding: 60rpx 40rpx 40rpx;
30 + color: white;
31 +}
32 +
33 +.activity-title {
34 + font-size: 48rpx;
35 + font-weight: bold;
36 + margin-bottom: 16rpx;
37 + text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
38 +}
39 +
40 +.activity-subtitle {
41 + font-size: 28rpx;
42 + margin-bottom: 12rpx;
43 + opacity: 0.9;
44 +}
45 +
46 +.activity-date {
47 + font-size: 24rpx;
48 + opacity: 0.8;
49 + display: flex;
50 + align-items: center;
51 +
52 + &::before {
53 + content: '📅';
54 + margin-right: 8rpx;
55 + }
56 +}
57 +
58 +// 详情区域
59 +.details-section {
60 + flex: 1;
61 + padding: 40rpx;
62 + background-color: white;
63 +}
64 +
65 +.section-title {
66 + font-size: 32rpx;
67 + font-weight: bold;
68 + color: #333;
69 + margin: 40rpx 0 24rpx 0;
70 + position: relative;
71 +
72 + &:first-child {
73 + margin-top: 0;
74 + }
75 +
76 + &::before {
77 + content: '';
78 + position: absolute;
79 + left: -16rpx;
80 + top: 50%;
81 + transform: translateY(-50%);
82 + width: 6rpx;
83 + height: 24rpx;
84 + background-color: #1890ff;
85 + border-radius: 3rpx;
86 + }
87 +}
88 +
89 +.activity-description {
90 + font-size: 28rpx;
91 + line-height: 1.6;
92 + color: #666;
93 + margin-bottom: 20rpx;
94 +}
95 +
96 +.rules-list {
97 + margin-bottom: 20rpx;
98 +}
99 +
100 +.rule-item {
101 + font-size: 26rpx;
102 + line-height: 1.5;
103 + color: #666;
104 + margin-bottom: 16rpx;
105 + padding-left: 20rpx;
106 + position: relative;
107 +
108 + &::before {
109 + content: '';
110 + position: absolute;
111 + left: 0;
112 + top: 20rpx;
113 + width: 8rpx;
114 + height: 8rpx;
115 + background-color: #1890ff;
116 + border-radius: 50%;
117 + }
118 +}
119 +
120 +.rewards-list {
121 + display: flex;
122 + flex-direction: column;
123 + gap: 16rpx;
124 +}
125 +
126 +.reward-item {
127 + display: flex;
128 + align-items: center;
129 + padding: 20rpx;
130 + background-color: #f8f9fa;
131 + border-radius: 12rpx;
132 + border-left: 6rpx solid #52c41a;
133 +}
134 +
135 +.reward-icon {
136 + font-size: 32rpx;
137 + margin-right: 16rpx;
138 +}
139 +
140 +.reward-text {
141 + font-size: 26rpx;
142 + color: #333;
143 + flex: 1;
144 +}
145 +
146 +// 底部区域
147 +.bottom-section {
148 + padding: 40rpx;
149 + padding-bottom: 180rpx; // 为底部导航留出空间
150 + background-color: white;
151 + border-top: 1rpx solid #f0f0f0;
152 +}
153 +
154 +.location-tip {
155 + display: flex;
156 + align-items: center;
157 + justify-content: center;
158 + padding: 24rpx;
159 + background-color: #fff7e6;
160 + border: 1rpx solid #ffd591;
161 + border-radius: 12rpx;
162 + margin-bottom: 32rpx;
163 +}
164 +
165 +.tip-icon {
166 + font-size: 32rpx;
167 + margin-right: 12rpx;
168 +}
169 +
170 +.tip-text {
171 + font-size: 26rpx;
172 + color: #d46b08;
173 +}
174 +
175 +.join-button {
176 + width: 100%;
177 + height: 88rpx;
178 + border-radius: 44rpx;
179 + font-size: 32rpx;
180 + font-weight: bold;
181 +
182 + &.nut-button--primary {
183 + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
184 + border: none;
185 + box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
186 +
187 + &:active {
188 + transform: translateY(2rpx);
189 + box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
190 + }
191 + }
192 +
193 + &.nut-button--loading {
194 + opacity: 0.7;
195 + }
196 +}
1 +<!--
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 15:16:04
5 + * @FilePath: /lls_program/src/pages/ActivitiesCover/index.vue
6 + * @Description: 活动海报页面 - 展示活动信息并处理定位授权
7 +-->
8 +<template>
9 + <view class="activities-cover-container">
10 + <!-- 活动海报区域 -->
11 + <view class="poster-section">
12 + <image
13 + :src="activityData.posterUrl"
14 + class="poster-image"
15 + mode="aspectFill"
16 + />
17 +
18 + <!-- 活动信息覆盖层 -->
19 + <view class="poster-overlay">
20 + <view class="activity-title">{{ activityData.title }}</view>
21 + <view class="activity-subtitle">{{ activityData.subtitle }}</view>
22 + <view class="activity-date">{{ activityData.dateRange }}</view>
23 + </view>
24 + </view>
25 +
26 + <!-- 活动详情区域 -->
27 + <view class="details-section">
28 + <view class="section-title">活动详情</view>
29 + <view class="activity-description">{{ activityData.description }}</view>
30 +
31 + <view class="section-title">参与规则</view>
32 + <view class="rules-list">
33 + <view
34 + v-for="(rule, index) in activityData.rules"
35 + :key="index"
36 + class="rule-item"
37 + >
38 + {{ index + 1 }}. {{ rule }}
39 + </view>
40 + </view>
41 +
42 + <view class="section-title">活动奖励</view>
43 + <view class="rewards-list">
44 + <view
45 + v-for="(reward, index) in activityData.rewards"
46 + :key="index"
47 + class="reward-item"
48 + >
49 + <view class="reward-icon">🏆</view>
50 + <view class="reward-text">{{ reward }}</view>
51 + </view>
52 + </view>
53 + </view>
54 +
55 + <!-- 底部按钮区域 -->
56 + <view class="bottom-section">
57 + <view v-if="!hasLocationAuth" class="location-tip">
58 + <view class="tip-icon">📍</view>
59 + <view class="tip-text">需要获取您的位置信息来参与活动</view>
60 + </view>
61 +
62 + <nut-button
63 + type="primary"
64 + size="large"
65 + class="join-button"
66 + color="#3B82F6"
67 + :loading="isJoining"
68 + @click="handleJoinActivity"
69 + >
70 + {{ hasLocationAuth ? '进入活动' : '参加活动' }}
71 + </nut-button>
72 + </view>
73 +
74 + <!-- 底部导航 -->
75 + <BottomNav />
76 + </view>
77 +</template>
78 +
79 +<script setup>
80 +import '@tarojs/taro/html.css'
81 +import { ref, onMounted } from "vue"
82 +import Taro from '@tarojs/taro'
83 +import "./index.less"
84 +import BottomNav from '../../components/BottomNav.vue'
85 +
86 +/**
87 + * 活动海报页面组件
88 + * 功能:展示活动信息、处理定位授权、跳转到活动页面
89 + */
90 +
91 +// 页面状态
92 +const hasLocationAuth = ref(false) // 是否已授权定位
93 +const isJoining = ref(false) // 是否正在加入活动
94 +const userLocation = ref({ lng: null, lat: null }) // 用户位置信息
95 +
96 +// Mock活动数据
97 +const activityData = ref({
98 + title: '南京路商圈时尚Citywalk',
99 + subtitle: '探索城市魅力,感受时尚脉搏',
100 + dateRange: '2024年1月15日 - 2024年1月31日',
101 + posterUrl: 'https://img.yzcdn.cn/vant/cat.jpeg', // 临时使用示例图片
102 + description: '漫步南京路,感受上海的繁华与历史交融。从外滩到人民广场,体验这座城市独特的魅力和时尚气息。',
103 + rules: [
104 + '年满60岁的老年人可参与活动',
105 + '需要在指定时间内完成所有打卡点',
106 + '每个打卡点需上传照片验证',
107 + '完成全部打卡可获得电子勋章和积分奖励'
108 + ],
109 + rewards: [
110 + '完成打卡获得500积分',
111 + '获得专属电子勋章',
112 + '有机会获得商户优惠券',
113 + '参与月度积分排行榜'
114 + ]
115 +})
116 +
117 +/**
118 + * 检查定位授权状态
119 + */
120 +const checkLocationAuth = async () => {
121 + try {
122 + const authSetting = await Taro.getSetting()
123 + hasLocationAuth.value = authSetting.authSetting['scope.userLocation'] === true
124 + console.log('定位授权状态:', hasLocationAuth.value)
125 + } catch (error) {
126 + console.error('检查定位授权失败:', error)
127 + hasLocationAuth.value = false
128 + }
129 +}
130 +
131 +/**
132 + * 获取用户位置信息
133 + */
134 +const getUserLocation = async () => {
135 + try {
136 + const location = await Taro.getLocation({
137 + type: 'gcj02'
138 + })
139 +
140 + userLocation.value = {
141 + lng: location.longitude,
142 + lat: location.latitude
143 + }
144 +
145 + console.log('获取到用户位置:', userLocation.value)
146 + return true
147 + } catch (error) {
148 + console.error('获取位置失败:', error)
149 +
150 + if (error.errMsg && error.errMsg.includes('auth deny')) {
151 + // 用户拒绝授权,引导用户手动开启
152 + await Taro.showModal({
153 + title: '需要位置权限',
154 + content: '参与活动需要获取您的位置信息,请在设置中开启位置权限',
155 + confirmText: '去设置',
156 + success: (res) => {
157 + if (res.confirm) {
158 + Taro.openSetting()
159 + }
160 + }
161 + })
162 + } else {
163 + Taro.showToast({
164 + title: '获取位置失败',
165 + icon: 'none'
166 + })
167 + }
168 +
169 + return false
170 + }
171 +}
172 +
173 +/**
174 + * 处理参加活动按钮点击
175 + */
176 +const handleJoinActivity = async () => {
177 + isJoining.value = true
178 +
179 + try {
180 + // 如果没有定位授权,先获取授权和位置信息
181 + if (!hasLocationAuth.value) {
182 + const success = await getUserLocation()
183 + if (!success) {
184 + isJoining.value = false
185 + return
186 + }
187 + hasLocationAuth.value = true
188 + } else {
189 + // 已有授权,直接获取位置
190 + const success = await getUserLocation()
191 + if (!success) {
192 + isJoining.value = false
193 + return
194 + }
195 + }
196 +
197 + // 跳转到Activities页面,并传递位置参数
198 + await Taro.navigateTo({
199 + url: `/pages/Activities/index?current_lng=${userLocation.value.lng}&current_lat=${userLocation.value.lat}`
200 + })
201 +
202 + } catch (error) {
203 + console.error('参加活动失败:', error)
204 + Taro.showToast({
205 + title: '参加活动失败',
206 + icon: 'none'
207 + })
208 + } finally {
209 + isJoining.value = false
210 + }
211 +}
212 +
213 +// 页面挂载时检查定位授权状态
214 +onMounted(() => {
215 + checkLocationAuth()
216 +})
217 +</script>
218 +
219 +<script>
220 +export default {
221 + name: "ActivitiesCover",
222 +};
223 +</script>