hookehuyr

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

添加活动海报展示页面,包含活动详情、规则和奖励信息
实现定位授权检查和位置获取功能
修改底部导航和路由配置以支持新页面
在活动页面添加位置参数处理逻辑
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 11:22:42
* @LastEditTime: 2025-08-28 14:52:14
* @FilePath: /lls_program/src/app.config.js
* @Description: 文件描述
*/
......@@ -27,11 +27,22 @@ export default {
'pages/EditProfile/index',
'pages/EditFamily/index',
'pages/AlbumList/index',
'pages/ActivitiesCover/index',
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
},
// 声明需要使用的私有信息
requiredPrivateInfos: [
'getLocation'
],
// 权限配置
permission: {
'scope.userLocation': {
desc: '您的位置信息将用于活动打卡和位置相关服务'
}
}
}
......
......@@ -30,7 +30,7 @@ import meIcon from '@/assets/images/icon/me.svg';
const navItems = shallowRef([
{ path: '/pages/Dashboard/index', icon: homeIcon, label: '首页' },
{ path: '/pages/Activities/index', icon: activitiesIcon, label: '活动' },
{ path: '/pages/ActivitiesCover/index', icon: activitiesIcon, label: '活动' },
{ path: '/pages/RewardCategories/index', icon: rewardsIcon, label: '兑换' },
{ path: '/pages/Profile/index', icon: meIcon, label: '我的' },
]);
......
......@@ -39,9 +39,10 @@ import BottomNav from '../../components/BottomNav.vue'
*/
// 页面状态
const webUrl = ref('https://oa.onwall.cn/f/map/#/checkin/?id=2400107') // TODO: 临时设置一个URL
const webUrl = ref('') // 动态设置URL
const loading = ref(true)
const error = ref(false)
const baseUrl = 'https://oa.onwall.cn/f/map/#/checkin/?id=2400107' // 基础URL
/**
* 处理WebView加载完成
......@@ -88,10 +89,31 @@ const handleRetry = () => {
}, 100)
}
/**
* 构建包含位置参数的URL
*/
const buildUrlWithLocation = (lng, lat) => {
if (lng && lat) {
const separator = baseUrl.includes('?') ? '&' : '?'
return `${baseUrl}${separator}current_lng=${lng}&current_lat=${lat}`
}
return baseUrl
}
// 页面挂载时初始化
onMounted(() => {
// 这里可以根据需要动态设置URL
console.log('活动页面WebView初始化')
// 获取路由参数中的经纬度信息
const instance = Taro.getCurrentInstance()
const { current_lng, current_lat } = instance.router?.params || {}
console.log('接收到的位置参数:', { current_lng, current_lat })
// 构建完整的URL
webUrl.value = buildUrlWithLocation(current_lng, current_lat)
console.log('最终WebView URL:', webUrl.value)
})
</script>
......
/*
* @Date: 2025-08-28 14:50:55
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 14:51:20
* @FilePath: /lls_program/src/pages/ActivitiesCover/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '活动海报',
usingComponents: {
},
}
.activities-cover-container {
width: 100%;
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
// 海报区域
.poster-section {
position: relative;
width: 100%;
height: 500rpx;
overflow: hidden;
}
.poster-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.poster-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
padding: 60rpx 40rpx 40rpx;
color: white;
}
.activity-title {
font-size: 48rpx;
font-weight: bold;
margin-bottom: 16rpx;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
}
.activity-subtitle {
font-size: 28rpx;
margin-bottom: 12rpx;
opacity: 0.9;
}
.activity-date {
font-size: 24rpx;
opacity: 0.8;
display: flex;
align-items: center;
&::before {
content: '📅';
margin-right: 8rpx;
}
}
// 详情区域
.details-section {
flex: 1;
padding: 40rpx;
background-color: white;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin: 40rpx 0 24rpx 0;
position: relative;
&:first-child {
margin-top: 0;
}
&::before {
content: '';
position: absolute;
left: -16rpx;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 24rpx;
background-color: #1890ff;
border-radius: 3rpx;
}
}
.activity-description {
font-size: 28rpx;
line-height: 1.6;
color: #666;
margin-bottom: 20rpx;
}
.rules-list {
margin-bottom: 20rpx;
}
.rule-item {
font-size: 26rpx;
line-height: 1.5;
color: #666;
margin-bottom: 16rpx;
padding-left: 20rpx;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 20rpx;
width: 8rpx;
height: 8rpx;
background-color: #1890ff;
border-radius: 50%;
}
}
.rewards-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.reward-item {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
border-left: 6rpx solid #52c41a;
}
.reward-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.reward-text {
font-size: 26rpx;
color: #333;
flex: 1;
}
// 底部区域
.bottom-section {
padding: 40rpx;
padding-bottom: 180rpx; // 为底部导航留出空间
background-color: white;
border-top: 1rpx solid #f0f0f0;
}
.location-tip {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
background-color: #fff7e6;
border: 1rpx solid #ffd591;
border-radius: 12rpx;
margin-bottom: 32rpx;
}
.tip-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.tip-text {
font-size: 26rpx;
color: #d46b08;
}
.join-button {
width: 100%;
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: bold;
&.nut-button--primary {
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
border: none;
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
&:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
}
}
&.nut-button--loading {
opacity: 0.7;
}
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-28 15:16:04
* @FilePath: /lls_program/src/pages/ActivitiesCover/index.vue
* @Description: 活动海报页面 - 展示活动信息并处理定位授权
-->
<template>
<view class="activities-cover-container">
<!-- 活动海报区域 -->
<view class="poster-section">
<image
:src="activityData.posterUrl"
class="poster-image"
mode="aspectFill"
/>
<!-- 活动信息覆盖层 -->
<view class="poster-overlay">
<view class="activity-title">{{ activityData.title }}</view>
<view class="activity-subtitle">{{ activityData.subtitle }}</view>
<view class="activity-date">{{ activityData.dateRange }}</view>
</view>
</view>
<!-- 活动详情区域 -->
<view class="details-section">
<view class="section-title">活动详情</view>
<view class="activity-description">{{ activityData.description }}</view>
<view class="section-title">参与规则</view>
<view class="rules-list">
<view
v-for="(rule, index) in activityData.rules"
:key="index"
class="rule-item"
>
{{ index + 1 }}. {{ rule }}
</view>
</view>
<view class="section-title">活动奖励</view>
<view class="rewards-list">
<view
v-for="(reward, index) in activityData.rewards"
:key="index"
class="reward-item"
>
<view class="reward-icon">🏆</view>
<view class="reward-text">{{ reward }}</view>
</view>
</view>
</view>
<!-- 底部按钮区域 -->
<view class="bottom-section">
<view v-if="!hasLocationAuth" class="location-tip">
<view class="tip-icon">📍</view>
<view class="tip-text">需要获取您的位置信息来参与活动</view>
</view>
<nut-button
type="primary"
size="large"
class="join-button"
color="#3B82F6"
:loading="isJoining"
@click="handleJoinActivity"
>
{{ hasLocationAuth ? '进入活动' : '参加活动' }}
</nut-button>
</view>
<!-- 底部导航 -->
<BottomNav />
</view>
</template>
<script setup>
import '@tarojs/taro/html.css'
import { ref, onMounted } from "vue"
import Taro from '@tarojs/taro'
import "./index.less"
import BottomNav from '../../components/BottomNav.vue'
/**
* 活动海报页面组件
* 功能:展示活动信息、处理定位授权、跳转到活动页面
*/
// 页面状态
const hasLocationAuth = ref(false) // 是否已授权定位
const isJoining = ref(false) // 是否正在加入活动
const userLocation = ref({ lng: null, lat: null }) // 用户位置信息
// Mock活动数据
const activityData = ref({
title: '南京路商圈时尚Citywalk',
subtitle: '探索城市魅力,感受时尚脉搏',
dateRange: '2024年1月15日 - 2024年1月31日',
posterUrl: 'https://img.yzcdn.cn/vant/cat.jpeg', // 临时使用示例图片
description: '漫步南京路,感受上海的繁华与历史交融。从外滩到人民广场,体验这座城市独特的魅力和时尚气息。',
rules: [
'年满60岁的老年人可参与活动',
'需要在指定时间内完成所有打卡点',
'每个打卡点需上传照片验证',
'完成全部打卡可获得电子勋章和积分奖励'
],
rewards: [
'完成打卡获得500积分',
'获得专属电子勋章',
'有机会获得商户优惠券',
'参与月度积分排行榜'
]
})
/**
* 检查定位授权状态
*/
const checkLocationAuth = async () => {
try {
const authSetting = await Taro.getSetting()
hasLocationAuth.value = authSetting.authSetting['scope.userLocation'] === true
console.log('定位授权状态:', hasLocationAuth.value)
} catch (error) {
console.error('检查定位授权失败:', error)
hasLocationAuth.value = false
}
}
/**
* 获取用户位置信息
*/
const getUserLocation = async () => {
try {
const location = await Taro.getLocation({
type: 'gcj02'
})
userLocation.value = {
lng: location.longitude,
lat: location.latitude
}
console.log('获取到用户位置:', userLocation.value)
return true
} catch (error) {
console.error('获取位置失败:', error)
if (error.errMsg && error.errMsg.includes('auth deny')) {
// 用户拒绝授权,引导用户手动开启
await Taro.showModal({
title: '需要位置权限',
content: '参与活动需要获取您的位置信息,请在设置中开启位置权限',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
Taro.openSetting()
}
}
})
} else {
Taro.showToast({
title: '获取位置失败',
icon: 'none'
})
}
return false
}
}
/**
* 处理参加活动按钮点击
*/
const handleJoinActivity = async () => {
isJoining.value = true
try {
// 如果没有定位授权,先获取授权和位置信息
if (!hasLocationAuth.value) {
const success = await getUserLocation()
if (!success) {
isJoining.value = false
return
}
hasLocationAuth.value = true
} else {
// 已有授权,直接获取位置
const success = await getUserLocation()
if (!success) {
isJoining.value = false
return
}
}
// 跳转到Activities页面,并传递位置参数
await Taro.navigateTo({
url: `/pages/Activities/index?current_lng=${userLocation.value.lng}&current_lat=${userLocation.value.lat}`
})
} catch (error) {
console.error('参加活动失败:', error)
Taro.showToast({
title: '参加活动失败',
icon: 'none'
})
} finally {
isJoining.value = false
}
}
// 页面挂载时检查定位授权状态
onMounted(() => {
checkLocationAuth()
})
</script>
<script>
export default {
name: "ActivitiesCover",
};
</script>