feat(ActivitiesDetail): 创建活动详情页面
- 基于 ActivitiesCover 创建新页面 - 完全使用 map_activity.js 新接口(detailAPI) - 支持从地图活动列表跳转 - 动态渲染积分规则 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
4 changed files
with
1420 additions
and
115 deletions
| ... | @@ -22,19 +22,23 @@ | ... | @@ -22,19 +22,23 @@ |
| 22 | <!-- 底部按钮区域 --> | 22 | <!-- 底部按钮区域 --> |
| 23 | <view class="bottom-section"> | 23 | <view class="bottom-section"> |
| 24 | <!-- 积分规则说明 --> | 24 | <!-- 积分规则说明 --> |
| 25 | - <view | 25 | + <view class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4 opacity-90"> |
| 26 | - v-if="activityData.rules && activityData.rules.length" | ||
| 27 | - class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4 opacity-90" | ||
| 28 | - > | ||
| 29 | <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> | 26 | <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> |
| 30 | <text | 27 | <text |
| 31 | - v-for="(rule, index) in activityData.rules" | ||
| 32 | - :key="index" | ||
| 33 | class="text-blue-500 text-sm leading-relaxed block mb-1" | 28 | class="text-blue-500 text-sm leading-relaxed block mb-1" |
| 34 | style="padding-left: 20rpx; text-indent: -20rpx" | 29 | style="padding-left: 20rpx; text-indent: -20rpx" |
| 30 | + >• 打卡任意1关,视为参与,奖励1000积分</text | ||
| 31 | + > | ||
| 32 | + <text | ||
| 33 | + class="text-blue-500 text-sm leading-relaxed block mb-1" | ||
| 34 | + style="padding-left: 20rpx; text-indent: -20rpx" | ||
| 35 | + >• 打卡任意7关,视为完成,奖励5000积分</text | ||
| 36 | + > | ||
| 37 | + <text | ||
| 38 | + class="text-blue-500 text-sm leading-relaxed block mb-1" | ||
| 39 | + style="padding-left: 20rpx; text-indent: -20rpx" | ||
| 40 | + >• 不需要区分打卡点的先后次序</text | ||
| 35 | > | 41 | > |
| 36 | - • {{ rule }} | ||
| 37 | - </text> | ||
| 38 | </view> | 42 | </view> |
| 39 | <!-- 未授权定位提示 - 仅在用户点击参加活动且未授权时显示 --> | 43 | <!-- 未授权定位提示 - 仅在用户点击参加活动且未授权时显示 --> |
| 40 | <view | 44 | <view |
| ... | @@ -147,27 +151,18 @@ import PosterBuilder from '../../components/PosterBuilder/index.vue' | ... | @@ -147,27 +151,18 @@ import PosterBuilder from '../../components/PosterBuilder/index.vue' |
| 147 | import ShareButton from '../../components/ShareButton/index.vue' | 151 | import ShareButton from '../../components/ShareButton/index.vue' |
| 148 | // 接口信息 | 152 | // 接口信息 |
| 149 | import { getMyFamiliesAPI } from '@/api/family' | 153 | import { getMyFamiliesAPI } from '@/api/family' |
| 150 | -import { detailAPI } from '@/api/map_activity' | 154 | +import { getActivityStatusAPI } from '@/api/map' |
| 151 | import { handleSharePageAuth, addShareFlag } from '@/utils/authRedirect' | 155 | import { handleSharePageAuth, addShareFlag } from '@/utils/authRedirect' |
| 152 | // 导入主题颜色 | 156 | // 导入主题颜色 |
| 153 | import { THEME_COLORS } from '@/utils/config' | 157 | import { THEME_COLORS } from '@/utils/config' |
| 154 | -// Mock 数据 | ||
| 155 | -import { mockMapActivityDetailAPI } from '@/utils/mockData' | ||
| 156 | - | ||
| 157 | -// 环境变量:是否使用 mock 数据 | ||
| 158 | -const USE_MOCK_DATA = process.env.NODE_ENV === 'development' | ||
| 159 | 158 | ||
| 160 | // 默认海报图 | 159 | // 默认海报图 |
| 161 | -const defaultPoster = ref( | 160 | +const defaultPoster = |
| 162 | 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60' | 161 | 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60' |
| 163 | -) | ||
| 164 | 162 | ||
| 165 | // 系统信息 | 163 | // 系统信息 |
| 166 | const systemInfo = ref({}) | 164 | const systemInfo = ref({}) |
| 167 | 165 | ||
| 168 | -// 活动ID(从 URL 参数获取) | ||
| 169 | -const activityId = ref('') | ||
| 170 | - | ||
| 171 | /** | 166 | /** |
| 172 | * 获取系统信息 | 167 | * 获取系统信息 |
| 173 | */ | 168 | */ |
| ... | @@ -937,87 +932,6 @@ const savePoster = () => { | ... | @@ -937,87 +932,6 @@ const savePoster = () => { |
| 937 | } | 932 | } |
| 938 | 933 | ||
| 939 | /** | 934 | /** |
| 940 | - * 将 API 数据转换为页面需要的 activityData 格式 | ||
| 941 | - * @param {Object} apiData - API 返回的活动详情数据 | ||
| 942 | - * @returns {Object} 页面活动数据对象 | ||
| 943 | - */ | ||
| 944 | -const transformApiDataToActivityData = apiData => { | ||
| 945 | - if (!apiData) { | ||
| 946 | - return null | ||
| 947 | - } | ||
| 948 | - | ||
| 949 | - // 生成日期范围字符串 | ||
| 950 | - const dateRange = `${apiData.begin_date} - ${apiData.end_date}` | ||
| 951 | - | ||
| 952 | - // 根据积分规则生成规则描述 | ||
| 953 | - const rules = [ | ||
| 954 | - `打卡任意1关,视为参与,奖励${apiData.first_checkin_points}积分`, | ||
| 955 | - `打卡任意${apiData.required_checkin_count}关,视为完成,奖励${apiData.complete_points}积分`, | ||
| 956 | - '不需要区分打卡点的先后次序', | ||
| 957 | - ] | ||
| 958 | - | ||
| 959 | - // 生成奖励描述 | ||
| 960 | - const rewards = [ | ||
| 961 | - `首次打卡获得${apiData.first_checkin_points}积分`, | ||
| 962 | - `完成${apiData.required_checkin_count}个打卡点获得${apiData.complete_points}积分`, | ||
| 963 | - apiData.discount_title || '打卡点专属优惠', | ||
| 964 | - ] | ||
| 965 | - | ||
| 966 | - return { | ||
| 967 | - title: apiData.tittle || '活动标题', | ||
| 968 | - subtitle: '探索城市魅力,感受时尚脉搏', | ||
| 969 | - dateRange: dateRange, | ||
| 970 | - posterUrl: apiData.cover || defaultPoster.value, | ||
| 971 | - description: `欢迎参加${apiData.tittle}活动!`, | ||
| 972 | - rules: rules, | ||
| 973 | - rewards: rewards, | ||
| 974 | - } | ||
| 975 | -} | ||
| 976 | - | ||
| 977 | -/** | ||
| 978 | - * 获取活动详情 | ||
| 979 | - */ | ||
| 980 | -const fetchActivityDetail = async () => { | ||
| 981 | - try { | ||
| 982 | - // 如果没有活动ID,不获取详情 | ||
| 983 | - if (!activityId.value) { | ||
| 984 | - console.warn('[ActivitiesCover] 未提供活动ID,跳过详情获取') | ||
| 985 | - return | ||
| 986 | - } | ||
| 987 | - | ||
| 988 | - console.log('[ActivitiesCover] 开始获取活动详情, ID:', activityId.value) | ||
| 989 | - | ||
| 990 | - // 根据环境选择真实 API 或 mock API | ||
| 991 | - const response = USE_MOCK_DATA | ||
| 992 | - ? await mockMapActivityDetailAPI({ id: activityId.value }) | ||
| 993 | - : await detailAPI({ id: activityId.value }) | ||
| 994 | - | ||
| 995 | - if (response.code === 1 && response.data) { | ||
| 996 | - console.log('[ActivitiesCover] 活动详情获取成功:', response.data) | ||
| 997 | - | ||
| 998 | - // 转换 API 数据为页面格式 | ||
| 999 | - const transformedData = transformApiDataToActivityData(response.data) | ||
| 1000 | - if (transformedData) { | ||
| 1001 | - activityData.value = transformedData | ||
| 1002 | - | ||
| 1003 | - // 更新默认海报图 | ||
| 1004 | - if (response.data.cover) { | ||
| 1005 | - defaultPoster.value = response.data.cover | ||
| 1006 | - } | ||
| 1007 | - | ||
| 1008 | - // 更新活动状态 | ||
| 1009 | - activityStatus.value.is_begin = Boolean(response.data.is_begin) | ||
| 1010 | - activityStatus.value.is_ended = Boolean(response.data.is_ended) | ||
| 1011 | - } | ||
| 1012 | - } else { | ||
| 1013 | - console.warn('[ActivitiesCover] 获取活动详情失败:', response.msg) | ||
| 1014 | - } | ||
| 1015 | - } catch (error) { | ||
| 1016 | - console.error('[ActivitiesCover] 获取活动详情异常:', error) | ||
| 1017 | - } | ||
| 1018 | -} | ||
| 1019 | - | ||
| 1020 | -/** | ||
| 1021 | * 初始化页面数据 | 935 | * 初始化页面数据 |
| 1022 | */ | 936 | */ |
| 1023 | const initPageData = async () => { | 937 | const initPageData = async () => { |
| ... | @@ -1030,28 +944,46 @@ const initPageData = async () => { | ... | @@ -1030,28 +944,46 @@ const initPageData = async () => { |
| 1030 | } | 944 | } |
| 1031 | } | 945 | } |
| 1032 | 946 | ||
| 1033 | - // 获取活动详情(包含活动状态) | 947 | + // 获取活动状态 |
| 1034 | - await fetchActivityDetail() | 948 | + await fetchActivityStatus() |
| 1035 | 949 | ||
| 1036 | // 检查定位授权状态(不获取位置,只检查权限) | 950 | // 检查定位授权状态(不获取位置,只检查权限) |
| 1037 | await checkLocationAuth() | 951 | await checkLocationAuth() |
| 1038 | } | 952 | } |
| 1039 | 953 | ||
| 1040 | -// 处理页面加载时的授权检查 | 954 | +/** |
| 1041 | -useLoad(options => { | 955 | + * 获取活动状态 |
| 1042 | - console.log('[ActivitiesCover] 页面加载, 参数:', options) | 956 | + */ |
| 1043 | - | 957 | +const fetchActivityStatus = async () => { |
| 1044 | - // 获取活动 ID(如果有) | 958 | + try { |
| 1045 | - if (options.id) { | 959 | + activityStatus.value.loading = true |
| 1046 | - activityId.value = options.id | 960 | + const { code, data } = await getActivityStatusAPI() |
| 1047 | - } else if (options.activity_id) { | 961 | + |
| 1048 | - activityId.value = options.activity_id | 962 | + if (code === 1 && data) { |
| 1049 | - } else { | 963 | + activityStatus.value.is_begin = Boolean(data.is_begin) |
| 1050 | - // 如果没有活动ID,使用默认ID | 964 | + activityStatus.value.is_ended = Boolean(data.is_ended) |
| 1051 | - activityId.value = '1' | 965 | + console.log('活动状态:', { |
| 1052 | - console.warn('[ActivitiesCover] 未提供活动ID,使用默认ID: 1') | 966 | + is_begin: activityStatus.value.is_begin ? '已开始' : '未开始', |
| 967 | + is_ended: activityStatus.value.is_ended ? '已结束' : '进行中', | ||
| 968 | + }) | ||
| 969 | + } else { | ||
| 970 | + console.warn('获取活动状态失败:', data) | ||
| 971 | + // 默认认为活动未开始且未结束,避免影响用户体验 | ||
| 972 | + activityStatus.value.is_begin = false | ||
| 973 | + activityStatus.value.is_ended = false | ||
| 974 | + } | ||
| 975 | + } catch (error) { | ||
| 976 | + console.error('获取活动状态异常:', error) | ||
| 977 | + // 默认认为活动未开始且未结束,避免影响用户体验 | ||
| 978 | + activityStatus.value.is_begin = false | ||
| 979 | + activityStatus.value.is_ended = false | ||
| 980 | + } finally { | ||
| 981 | + activityStatus.value.loading = false | ||
| 1053 | } | 982 | } |
| 983 | +} | ||
| 1054 | 984 | ||
| 985 | +// 处理页面加载时的授权检查 | ||
| 986 | +useLoad(options => { | ||
| 1055 | // 处理分享页面的授权逻辑 | 987 | // 处理分享页面的授权逻辑 |
| 1056 | handleSharePageAuth(options, () => { | 988 | handleSharePageAuth(options, () => { |
| 1057 | initPageData() | 989 | initPageData() | ... | ... |
src/pages/ActivitiesDetail/index.config.js
0 → 100755
| 1 | +/* | ||
| 2 | + * @Date: 2026-02-09 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2026-02-09 | ||
| 5 | + * @FilePath: /lls_program/src/pages/ActivitiesDetail/index.config.js | ||
| 6 | + * @Description: 活动详情页面配置 - 支持多活动详情展示 | ||
| 7 | + */ | ||
| 8 | +export default { | ||
| 9 | + navigationBarTitleText: '活动详情', | ||
| 10 | + enableShareAppMessage: true, | ||
| 11 | + usingComponents: {}, | ||
| 12 | +} |
src/pages/ActivitiesDetail/index.less
0 → 100644
| 1 | +.activities-cover-container { | ||
| 2 | + position: relative; | ||
| 3 | + width: 100%; | ||
| 4 | + height: 100vh; | ||
| 5 | + overflow: hidden; | ||
| 6 | +} | ||
| 7 | + | ||
| 8 | +// 背景图片 | ||
| 9 | +.background-image { | ||
| 10 | + position: absolute; | ||
| 11 | + top: 0; | ||
| 12 | + left: 0; | ||
| 13 | + width: 100vw; | ||
| 14 | + height: calc(100vh - 150rpx); // 减去底部区域的高度,确保背景图不被遮挡 | ||
| 15 | + // height: calc(100vh); // 减去底部区域的高度,确保背景图不被遮挡 | ||
| 16 | + object-fit: cover; | ||
| 17 | + object-position: top center; | ||
| 18 | + z-index: 1; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +// 为容器添加背景色,避免下方空白 | ||
| 22 | +// .activities-cover-container::before { | ||
| 23 | +// content: ''; | ||
| 24 | +// position: absolute; | ||
| 25 | +// top: 0; | ||
| 26 | +// left: 0; | ||
| 27 | +// width: 100%; | ||
| 28 | +// height: 100%; | ||
| 29 | +// background: linear-gradient(180deg, #f0f8ff 0%, #e6f3ff 50%, #ddeeff 100%); | ||
| 30 | +// z-index: 0; | ||
| 31 | +// } | ||
| 32 | + | ||
| 33 | +// 分享按钮包装器 | ||
| 34 | +.share-button-wrapper { | ||
| 35 | + position: absolute; | ||
| 36 | + top: 40rpx; | ||
| 37 | + right: 40rpx; | ||
| 38 | + z-index: 10; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +// 底部区域 | ||
| 42 | +.bottom-section { | ||
| 43 | + position: absolute; | ||
| 44 | + bottom: 0; | ||
| 45 | + left: 0; | ||
| 46 | + right: 0; | ||
| 47 | + padding: 40rpx; | ||
| 48 | + padding-bottom: 180rpx; // 为底部导航留出空间 | ||
| 49 | + // background: linear-gradient( | ||
| 50 | + // transparent 0%, | ||
| 51 | + // rgba(0, 0, 0, 0.1) 20%, | ||
| 52 | + // rgba(0, 0, 0, 0.3) 50%, | ||
| 53 | + // rgba(0, 0, 0, 0.6) 80%, | ||
| 54 | + // rgba(0, 0, 0, 0.8) 100% | ||
| 55 | + // ); | ||
| 56 | + // backdrop-filter: blur(30rpx); | ||
| 57 | + // -webkit-backdrop-filter: blur(30rpx); | ||
| 58 | + z-index: 5; | ||
| 59 | + | ||
| 60 | + // 增加渐变高度,让过渡更自然 | ||
| 61 | + min-height: 300rpx; | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +.location-tip { | ||
| 65 | + display: flex; | ||
| 66 | + flex-direction: column; | ||
| 67 | + align-items: center; | ||
| 68 | + justify-content: center; | ||
| 69 | + padding: 24rpx; | ||
| 70 | + background-color: rgba(255, 247, 230, 0.95); | ||
| 71 | + border: 1rpx solid rgba(255, 213, 145, 0.9); | ||
| 72 | + border-radius: 16rpx; | ||
| 73 | + margin-bottom: 32rpx; | ||
| 74 | + backdrop-filter: blur(20rpx); | ||
| 75 | + -webkit-backdrop-filter: blur(20rpx); | ||
| 76 | + cursor: pointer; | ||
| 77 | + transition: all 0.3s ease; | ||
| 78 | + box-shadow: | ||
| 79 | + 0 4rpx 16rpx rgba(255, 213, 145, 0.3), | ||
| 80 | + 0 2rpx 8rpx rgba(0, 0, 0, 0.1); | ||
| 81 | + | ||
| 82 | + &:active { | ||
| 83 | + background-color: rgba(255, 247, 230, 0.8); | ||
| 84 | + transform: scale(0.98); | ||
| 85 | + box-shadow: | ||
| 86 | + 0 2rpx 8rpx rgba(255, 213, 145, 0.2), | ||
| 87 | + 0 1rpx 4rpx rgba(0, 0, 0, 0.1); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + .tip-content { | ||
| 91 | + display: flex; | ||
| 92 | + align-items: center; | ||
| 93 | + justify-content: center; | ||
| 94 | + margin-bottom: 8rpx; | ||
| 95 | + } | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +.tip-icon { | ||
| 99 | + font-size: 32rpx; | ||
| 100 | + margin-right: 12rpx; | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +.tip-text { | ||
| 104 | + font-size: 26rpx; | ||
| 105 | + color: #d46b08; | ||
| 106 | + font-weight: 500; | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +.tip-retry { | ||
| 110 | + font-size: 22rpx; | ||
| 111 | + color: #1890ff; | ||
| 112 | + text-decoration: underline; | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +.location-error { | ||
| 116 | + background-color: rgba(255, 241, 240, 0.95); | ||
| 117 | + border: 1rpx solid rgba(255, 163, 158, 0.9); | ||
| 118 | + box-shadow: | ||
| 119 | + 0 4rpx 16rpx rgba(255, 163, 158, 0.3), | ||
| 120 | + 0 2rpx 8rpx rgba(0, 0, 0, 0.1); | ||
| 121 | + | ||
| 122 | + &:active { | ||
| 123 | + background-color: rgba(255, 241, 240, 0.8); | ||
| 124 | + box-shadow: | ||
| 125 | + 0 2rpx 8rpx rgba(255, 163, 158, 0.2), | ||
| 126 | + 0 1rpx 4rpx rgba(0, 0, 0, 0.1); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + .tip-text { | ||
| 130 | + color: #cf1322; | ||
| 131 | + } | ||
| 132 | +} | ||
| 133 | + | ||
| 134 | +.join-button { | ||
| 135 | + width: 100%; | ||
| 136 | + height: 88rpx; | ||
| 137 | + border-radius: 44rpx; | ||
| 138 | + font-size: 32rpx; | ||
| 139 | + font-weight: 600; | ||
| 140 | + box-shadow: | ||
| 141 | + 0 8rpx 32rpx rgba(84, 171, 174, 0.5), | ||
| 142 | + 0 4rpx 16rpx rgba(0, 0, 0, 0.2); | ||
| 143 | + backdrop-filter: blur(15rpx); | ||
| 144 | + -webkit-backdrop-filter: blur(15rpx); | ||
| 145 | + border: 1rpx solid rgba(255, 255, 255, 0.2); | ||
| 146 | + | ||
| 147 | + &:active { | ||
| 148 | + transform: translateY(2rpx); | ||
| 149 | + box-shadow: | ||
| 150 | + 0 4rpx 16rpx rgba(84, 171, 174, 0.4), | ||
| 151 | + 0 2rpx 8rpx rgba(0, 0, 0, 0.2); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + &.nut-button--primary { | ||
| 155 | + background: linear-gradient(135deg, rgba(84, 171, 174, 0.95) 0%, rgba(74, 151, 154, 0.95) 100%); | ||
| 156 | + border: 1rpx solid rgba(255, 255, 255, 0.3); | ||
| 157 | + box-shadow: | ||
| 158 | + 0 8rpx 32rpx rgba(84, 171, 174, 0.4), | ||
| 159 | + 0 4rpx 16rpx rgba(0, 0, 0, 0.2); | ||
| 160 | + color: white; | ||
| 161 | + text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2); | ||
| 162 | + | ||
| 163 | + &:active { | ||
| 164 | + transform: translateY(2rpx); | ||
| 165 | + box-shadow: | ||
| 166 | + 0 4rpx 16rpx rgba(84, 171, 174, 0.3), | ||
| 167 | + 0 2rpx 8rpx rgba(0, 0, 0, 0.2); | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + // 禁用状态样式 - 覆盖NutUI默认的半透明效果 | ||
| 172 | + &.nut-button--disabled { | ||
| 173 | + opacity: 1 !important; // 覆盖默认的透明度 | ||
| 174 | + background: #cccccc !important; // 实心灰色背景 | ||
| 175 | + color: #fff !important; // 深灰色文字 | ||
| 176 | + border: 1rpx solid #ccc !important; | ||
| 177 | + box-shadow: 0 4rpx 16rpx rgba(204, 204, 204, 0.3) !important; | ||
| 178 | + | ||
| 179 | + &:active { | ||
| 180 | + transform: none; // 禁用时不响应点击效果 | ||
| 181 | + box-shadow: 0 4rpx 16rpx rgba(204, 204, 204, 0.3) !important; | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +// 弹窗样式 | ||
| 187 | +.share-popup { | ||
| 188 | + .nut-popup__content { | ||
| 189 | + border-radius: 24rpx 24rpx 0 0; | ||
| 190 | + padding: 40rpx; | ||
| 191 | + } | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +.share-title { | ||
| 195 | + font-size: 32rpx; | ||
| 196 | + font-weight: bold; | ||
| 197 | + text-align: center; | ||
| 198 | + margin-bottom: 40rpx; | ||
| 199 | + color: #333; | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +.share-options { | ||
| 203 | + display: flex; | ||
| 204 | + justify-content: space-around; | ||
| 205 | + margin-bottom: 40rpx; | ||
| 206 | +} | ||
| 207 | + | ||
| 208 | +.share-option { | ||
| 209 | + display: flex; | ||
| 210 | + flex-direction: column; | ||
| 211 | + align-items: center; | ||
| 212 | + padding: 20rpx; | ||
| 213 | + border-radius: 12rpx; | ||
| 214 | + | ||
| 215 | + &:active { | ||
| 216 | + background-color: #f5f5f5; | ||
| 217 | + } | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +.share-icon { | ||
| 221 | + width: 80rpx; | ||
| 222 | + height: 80rpx; | ||
| 223 | + margin-bottom: 16rpx; | ||
| 224 | + border-radius: 12rpx; | ||
| 225 | + background-color: #1890ff; | ||
| 226 | + display: flex; | ||
| 227 | + align-items: center; | ||
| 228 | + justify-content: center; | ||
| 229 | + color: white; | ||
| 230 | + font-size: 36rpx; | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +.share-text { | ||
| 234 | + font-size: 24rpx; | ||
| 235 | + color: #666; | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +.cancel-button { | ||
| 239 | + width: 100%; | ||
| 240 | + height: 88rpx; | ||
| 241 | + border-radius: 44rpx; | ||
| 242 | + font-size: 32rpx; | ||
| 243 | + background-color: #f5f5f5; | ||
| 244 | + color: #666; | ||
| 245 | + border: none; | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +// 海报预览弹窗 | ||
| 249 | +.poster-preview-popup { | ||
| 250 | + .nut-popup__content { | ||
| 251 | + width: 90%; | ||
| 252 | + max-width: 600rpx; | ||
| 253 | + border-radius: 24rpx; | ||
| 254 | + padding: 40rpx; | ||
| 255 | + background-color: white; | ||
| 256 | + } | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +.poster-preview { | ||
| 260 | + width: 100%; | ||
| 261 | + border-radius: 12rpx; | ||
| 262 | + margin-bottom: 40rpx; | ||
| 263 | +} | ||
| 264 | + | ||
| 265 | +.preview-actions { | ||
| 266 | + display: flex; | ||
| 267 | + gap: 20rpx; | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +.preview-button { | ||
| 271 | + flex: 1; | ||
| 272 | + height: 80rpx; | ||
| 273 | + border-radius: 40rpx; | ||
| 274 | + font-size: 28rpx; | ||
| 275 | + | ||
| 276 | + &.primary { | ||
| 277 | + background-color: #1890ff; | ||
| 278 | + color: white; | ||
| 279 | + border: none; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + &.secondary { | ||
| 283 | + background-color: #f5f5f5; | ||
| 284 | + color: #666; | ||
| 285 | + border: none; | ||
| 286 | + } | ||
| 287 | +} |
src/pages/ActivitiesDetail/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2026-02-09 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2026-02-09 | ||
| 5 | + * @FilePath: /lls_program/src/pages/ActivitiesDetail/index.vue | ||
| 6 | + * @Description: 活动详情页面 - 完全使用 map_activity.js 新接口,支持多活动详情展示 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <view class="activities-cover-container"> | ||
| 10 | + <!-- 背景图片 --> | ||
| 11 | + <image :src="defaultPoster" class="background-image" :mode="imageDisplayMode" /> | ||
| 12 | + | ||
| 13 | + <!-- 分享按钮组件 --> | ||
| 14 | + <ShareButton | ||
| 15 | + :activity-data="activityData" | ||
| 16 | + :share-config="shareConfig" | ||
| 17 | + @share-activity="onShareActivity" | ||
| 18 | + @share-poster="onSharePoster" | ||
| 19 | + class="share-button-wrapper" | ||
| 20 | + /> | ||
| 21 | + | ||
| 22 | + <!-- 底部按钮区域 --> | ||
| 23 | + <view class="bottom-section"> | ||
| 24 | + <!-- 积分规则说明 --> | ||
| 25 | + <view | ||
| 26 | + v-if="activityData.rules && activityData.rules.length" | ||
| 27 | + class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4 opacity-90" | ||
| 28 | + > | ||
| 29 | + <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> | ||
| 30 | + <text | ||
| 31 | + v-for="(rule, index) in activityData.rules" | ||
| 32 | + :key="index" | ||
| 33 | + class="text-blue-500 text-sm leading-relaxed block mb-1" | ||
| 34 | + style="padding-left: 20rpx; text-indent: -20rpx" | ||
| 35 | + > | ||
| 36 | + • {{ rule }} | ||
| 37 | + </text> | ||
| 38 | + </view> | ||
| 39 | + <!-- 未授权定位提示 - 仅在用户点击参加活动且未授权时显示 --> | ||
| 40 | + <view | ||
| 41 | + v-if="showLocationPrompt && !hasLocationAuth && !locationError" | ||
| 42 | + class="location-tip" | ||
| 43 | + @click="retryGetLocation" | ||
| 44 | + > | ||
| 45 | + <view class="tip-content"> | ||
| 46 | + <view class="tip-icon">📍</view> | ||
| 47 | + <view class="tip-text">点击获取您的位置信息来参与活动</view> | ||
| 48 | + </view> | ||
| 49 | + <!-- <view class="tip-retry">点击重新获取</view> --> | ||
| 50 | + </view> | ||
| 51 | + | ||
| 52 | + <!-- 位置获取失败提示 --> | ||
| 53 | + <view | ||
| 54 | + v-if="hasLocationAuth && locationError" | ||
| 55 | + class="location-tip location-error" | ||
| 56 | + @click="retryGetLocation" | ||
| 57 | + > | ||
| 58 | + <view class="tip-content"> | ||
| 59 | + <view class="tip-icon">⚠️</view> | ||
| 60 | + <view class="tip-text">可能是网络问题,获取位置信息失败</view> | ||
| 61 | + </view> | ||
| 62 | + <!-- <view class="tip-retry">点击重新获取</view> --> | ||
| 63 | + </view> | ||
| 64 | + | ||
| 65 | + <nut-button | ||
| 66 | + type="primary" | ||
| 67 | + size="large" | ||
| 68 | + class="join-button" | ||
| 69 | + :color=" | ||
| 70 | + activityStatus.is_ended || !activityStatus.is_begin ? '#cccccc' : THEME_COLORS.PRIMARY | ||
| 71 | + " | ||
| 72 | + :loading="isJoining || activityStatus.loading" | ||
| 73 | + :disabled="activityStatus.is_ended || !activityStatus.is_begin" | ||
| 74 | + @click="checkFamilyStatusAndJoinActivity" | ||
| 75 | + > | ||
| 76 | + {{ getButtonText() }} | ||
| 77 | + </nut-button> | ||
| 78 | + </view> | ||
| 79 | + | ||
| 80 | + <!-- 底部导航 --> | ||
| 81 | + <BottomNav /> | ||
| 82 | + | ||
| 83 | + <!-- 海报预览弹窗 --> | ||
| 84 | + <nut-popup v-model:visible="show_post" position="center" class="poster-preview-popup"> | ||
| 85 | + <view class="wrapper"> | ||
| 86 | + <view class="preview-area" @click="onClickPost"> | ||
| 87 | + <image v-if="posterPath" :src="posterPath" mode="widthFix" /> | ||
| 88 | + </view> | ||
| 89 | + </view> | ||
| 90 | + </nut-popup> | ||
| 91 | + | ||
| 92 | + <!-- 海报生成组件 --> | ||
| 93 | + <PosterBuilder | ||
| 94 | + v-if="startDraw" | ||
| 95 | + custom-style="position: fixed; left: 200%;" | ||
| 96 | + :config="base" | ||
| 97 | + @success="drawSuccess" | ||
| 98 | + @fail="drawFail" | ||
| 99 | + /> | ||
| 100 | + | ||
| 101 | + <!-- 保存选项弹窗 --> | ||
| 102 | + <nut-action-sheet | ||
| 103 | + v-model:visible="show_save" | ||
| 104 | + :menu-items="actions_save" | ||
| 105 | + @choose="onSelectSave" | ||
| 106 | + @cancel="onCancelSave" | ||
| 107 | + cancel-txt="取消" | ||
| 108 | + /> | ||
| 109 | + | ||
| 110 | + <!-- 位置权限申请弹窗 --> | ||
| 111 | + <nut-dialog v-model:visible="showLocationDialog" title="位置权限申请"> | ||
| 112 | + <template #default> | ||
| 113 | + <view class="text-gray-700 leading-loose text-sm text-left"> | ||
| 114 | + {{ locationContent }} | ||
| 115 | + </view> | ||
| 116 | + </template> | ||
| 117 | + <template #footer> | ||
| 118 | + <nut-row :gutter="10"> | ||
| 119 | + <nut-col :span="12"> | ||
| 120 | + <nut-button @click="onLocationCancel" type="default" size="normal" block> | ||
| 121 | + 暂不授权 | ||
| 122 | + </nut-button> | ||
| 123 | + </nut-col> | ||
| 124 | + <nut-col :span="12"> | ||
| 125 | + <nut-button | ||
| 126 | + @click="onLocationConfirm" | ||
| 127 | + type="primary" | ||
| 128 | + size="normal" | ||
| 129 | + :color="THEME_COLORS.PRIMARY" | ||
| 130 | + block | ||
| 131 | + > | ||
| 132 | + 同意授权 | ||
| 133 | + </nut-button> | ||
| 134 | + </nut-col> | ||
| 135 | + </nut-row> | ||
| 136 | + </template> | ||
| 137 | + </nut-dialog> | ||
| 138 | + </view> | ||
| 139 | +</template> | ||
| 140 | + | ||
| 141 | +<script setup> | ||
| 142 | +import { ref, onMounted, computed } from 'vue' | ||
| 143 | +import Taro, { useLoad } from '@tarojs/taro' | ||
| 144 | +import './index.less' | ||
| 145 | +import BottomNav from '../../components/BottomNav.vue' | ||
| 146 | +import PosterBuilder from '../../components/PosterBuilder/index.vue' | ||
| 147 | +import ShareButton from '../../components/ShareButton/index.vue' | ||
| 148 | +// 接口信息 | ||
| 149 | +import { getMyFamiliesAPI } from '@/api/family' | ||
| 150 | +import { detailAPI } from '@/api/map_activity' | ||
| 151 | +import { handleSharePageAuth, addShareFlag } from '@/utils/authRedirect' | ||
| 152 | +// 导入主题颜色 | ||
| 153 | +import { THEME_COLORS } from '@/utils/config' | ||
| 154 | +// Mock 数据 | ||
| 155 | +import { mockMapActivityDetailAPI } from '@/utils/mockData' | ||
| 156 | + | ||
| 157 | +// 环境变量:是否使用 mock 数据 | ||
| 158 | +const USE_MOCK_DATA = process.env.NODE_ENV === 'development' | ||
| 159 | + | ||
| 160 | +// 默认海报图 | ||
| 161 | +const defaultPoster = ref( | ||
| 162 | + 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60' | ||
| 163 | +) | ||
| 164 | + | ||
| 165 | +// 系统信息 | ||
| 166 | +const systemInfo = ref({}) | ||
| 167 | + | ||
| 168 | +// 活动ID(从 URL 参数获取) | ||
| 169 | +const activityId = ref('') | ||
| 170 | + | ||
| 171 | +/** | ||
| 172 | + * 获取系统信息 | ||
| 173 | + */ | ||
| 174 | +const getSystemInfo = () => { | ||
| 175 | + try { | ||
| 176 | + const info = Taro.getWindowInfo() | ||
| 177 | + systemInfo.value = info | ||
| 178 | + } catch (error) { | ||
| 179 | + console.error('获取系统信息失败:', error) | ||
| 180 | + } | ||
| 181 | +} | ||
| 182 | + | ||
| 183 | +/** | ||
| 184 | + * 检测是否为 iPad 类型设备 | ||
| 185 | + */ | ||
| 186 | +const isTabletDevice = computed(() => { | ||
| 187 | + if (!systemInfo.value.screenWidth) { | ||
| 188 | + return false | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + const { screenWidth, screenHeight } = systemInfo.value | ||
| 192 | + const screenRatio = screenWidth / screenHeight | ||
| 193 | + | ||
| 194 | + // iPad 类型设备通常屏幕比例在 0.7-0.8 之间(4:3 约为 0.75) | ||
| 195 | + // 普通手机设备比例通常在 0.4-0.6 之间 | ||
| 196 | + return screenRatio > 0.65 | ||
| 197 | +}) | ||
| 198 | + | ||
| 199 | +/** | ||
| 200 | + * 计算图片显示模式 | ||
| 201 | + */ | ||
| 202 | +const imageDisplayMode = computed(() => { | ||
| 203 | + // iPad 类型设备使用 widthFix 模式,普通设备使用 aspectFill | ||
| 204 | + return isTabletDevice.value ? 'widthFix' : 'aspectFill' | ||
| 205 | +}) | ||
| 206 | + | ||
| 207 | +/** | ||
| 208 | + * 活动海报页面组件 | ||
| 209 | + * 功能:展示活动信息、处理定位授权、跳转到活动页面 | ||
| 210 | + */ | ||
| 211 | + | ||
| 212 | +// 页面状态 | ||
| 213 | +const hasLocationAuth = ref(false) // 是否已授权定位 | ||
| 214 | +const locationError = ref(false) // 位置获取是否失败 | ||
| 215 | +const isJoining = ref(false) // 是否正在加入活动 | ||
| 216 | +const userLocation = ref({ lng: null, lat: null }) // 用户位置信息 | ||
| 217 | +const hasJoinedFamily = ref(false) | ||
| 218 | +const showLocationPrompt = ref(false) // 是否显示定位权限提示 | ||
| 219 | + | ||
| 220 | +// 活动状态相关 | ||
| 221 | +const activityStatus = ref({ | ||
| 222 | + is_begin: false, // 活动是否已开始 | ||
| 223 | + is_ended: false, // 活动是否已结束 | ||
| 224 | + loading: false, // 是否正在加载活动状态 | ||
| 225 | +}) | ||
| 226 | + | ||
| 227 | +// Dialog 相关状态 | ||
| 228 | +const showLocationDialog = ref(false) // 是否显示位置权限申请弹窗 | ||
| 229 | +const pendingLocationCallback = ref(null) // 待执行的位置获取回调 | ||
| 230 | + | ||
| 231 | +// 位置权限申请说明内容 | ||
| 232 | +const locationContent = | ||
| 233 | + '为了提供更好的活动体验,我们需要获取您的位置信息:验证您是否在活动区域内, 我们承诺严格保护您的位置隐私,仅用于活动相关功能。' | ||
| 234 | + | ||
| 235 | +// 海报生成相关状态 | ||
| 236 | +const show_post = ref(false) // 显示海报预览 | ||
| 237 | +const show_save = ref(false) // 显示保存弹窗 | ||
| 238 | +const startDraw = ref(false) // 开始绘制海报 | ||
| 239 | +const posterPath = ref('') // 海报路径 | ||
| 240 | +const nickname = ref('老来赛用户') // 用户昵称 | ||
| 241 | +// const avatar = ref('https://cdn.ipadbiz.cn/icon/tou@2x.png') // 用户头像 | ||
| 242 | + | ||
| 243 | +// 保存选项 | ||
| 244 | +const actions_save = ref([ | ||
| 245 | + { | ||
| 246 | + name: '保存至相册', | ||
| 247 | + }, | ||
| 248 | +]) | ||
| 249 | + | ||
| 250 | +// 海报配置 | ||
| 251 | +const base = {} | ||
| 252 | +const qrcode_url = 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png' // Mock二维码 | ||
| 253 | + | ||
| 254 | +// Mock活动数据 | ||
| 255 | +const activityData = ref({ | ||
| 256 | + title: '南京路商圈时尚Citywalk', | ||
| 257 | + subtitle: '探索城市魅力,感受时尚脉搏', | ||
| 258 | + dateRange: '2024年1月15日 - 2024年1月31日', | ||
| 259 | + posterUrl: 'https://img.yzcdn.cn/vant/cat.jpeg', // 临时使用示例图片 | ||
| 260 | + description: | ||
| 261 | + '漫步南京路,感受上海的繁华与历史交融。从外滩到人民广场,体验这座城市独特的魅力和时尚气息。', | ||
| 262 | + rules: [ | ||
| 263 | + '年满60岁的老年人可参与活动', | ||
| 264 | + '需要在指定时间内完成所有打卡点', | ||
| 265 | + '每个打卡点需上传照片验证', | ||
| 266 | + '完成全部打卡可获得电子勋章和积分奖励', | ||
| 267 | + ], | ||
| 268 | + rewards: [ | ||
| 269 | + '完成打卡获得500积分', | ||
| 270 | + '获得专属电子勋章', | ||
| 271 | + '有机会获得商户优惠券', | ||
| 272 | + '参与月度积分排行榜', | ||
| 273 | + ], | ||
| 274 | +}) | ||
| 275 | + | ||
| 276 | +// 分享配置 | ||
| 277 | +const shareConfig = ref({ | ||
| 278 | + title: '主题路线打卡活动等你参与', | ||
| 279 | + path: '/pages/ActivitiesCover/index', | ||
| 280 | + imageUrl: '', | ||
| 281 | +}) | ||
| 282 | + | ||
| 283 | +/** | ||
| 284 | + * 检查定位授权状态 | ||
| 285 | + */ | ||
| 286 | +const checkLocationAuth = async () => { | ||
| 287 | + try { | ||
| 288 | + const authSetting = await Taro.getSetting() | ||
| 289 | + hasLocationAuth.value = authSetting.authSetting['scope.userLocation'] === true | ||
| 290 | + console.log('定位授权状态:', hasLocationAuth.value) | ||
| 291 | + } catch (error) { | ||
| 292 | + console.error('检查定位授权失败:', error) | ||
| 293 | + hasLocationAuth.value = false | ||
| 294 | + } | ||
| 295 | +} | ||
| 296 | + | ||
| 297 | +/** | ||
| 298 | + * 获取用户位置信息 | ||
| 299 | + * @param {boolean} skipAuthCheck - 是否跳过授权检查(当调用方已经检查过授权状态时) | ||
| 300 | + */ | ||
| 301 | +const getUserLocation = async (skipAuthCheck = false) => { | ||
| 302 | + try { | ||
| 303 | + locationError.value = false // 重置错误状态 | ||
| 304 | + | ||
| 305 | + // 如果没有跳过授权检查,则检查权限状态 | ||
| 306 | + if (!skipAuthCheck) { | ||
| 307 | + const authSetting = await Taro.getSetting() | ||
| 308 | + const hasLocationAuth = authSetting.authSetting['scope.userLocation'] | ||
| 309 | + | ||
| 310 | + // 如果没有授权,先显示数据用途说明 | ||
| 311 | + if (hasLocationAuth !== true) { | ||
| 312 | + return new Promise(resolve => { | ||
| 313 | + pendingLocationCallback.value = resolve | ||
| 314 | + showLocationDialog.value = true | ||
| 315 | + }) | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + const location = await Taro.getLocation({ | ||
| 320 | + type: 'gcj02', | ||
| 321 | + altitude: false, // 不需要海拔信息,提高获取速度 | ||
| 322 | + isHighAccuracy: true, // 开启高精度定位 | ||
| 323 | + highAccuracyExpireTime: 4000, // 高精度定位超时时间 | ||
| 324 | + }) | ||
| 325 | + | ||
| 326 | + userLocation.value = { | ||
| 327 | + lng: location.longitude, | ||
| 328 | + lat: location.latitude, | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + console.log('获取到用户位置:', userLocation.value) | ||
| 332 | + // 获取位置成功后隐藏提示 | ||
| 333 | + showLocationPrompt.value = false | ||
| 334 | + hasLocationAuth.value = true | ||
| 335 | + return true | ||
| 336 | + } catch (error) { | ||
| 337 | + console.error('获取位置失败:', error) | ||
| 338 | + | ||
| 339 | + if (error.errMsg && error.errMsg.includes('auth deny')) { | ||
| 340 | + // 用户拒绝授权,引导用户手动开启 | ||
| 341 | + hasLocationAuth.value = false | ||
| 342 | + locationError.value = false | ||
| 343 | + await Taro.showModal({ | ||
| 344 | + title: '需要位置权限', | ||
| 345 | + content: '参与活动需要获取您的位置信息,请在设置中开启位置权限', | ||
| 346 | + confirmText: '去设置', | ||
| 347 | + success: res => { | ||
| 348 | + if (res.confirm) { | ||
| 349 | + Taro.openSetting() | ||
| 350 | + } | ||
| 351 | + }, | ||
| 352 | + }) | ||
| 353 | + } else if (error.errMsg && error.errMsg.includes('timeout')) { | ||
| 354 | + // 定位超时 | ||
| 355 | + locationError.value = true | ||
| 356 | + Taro.showToast({ | ||
| 357 | + title: '定位超时,请检查网络或GPS', | ||
| 358 | + icon: 'none', | ||
| 359 | + duration: 3000, | ||
| 360 | + }) | ||
| 361 | + } else if (error.errMsg && error.errMsg.includes('fail')) { | ||
| 362 | + // 定位失败,可能是GPS关闭或网络问题 | ||
| 363 | + locationError.value = true | ||
| 364 | + await Taro.showModal({ | ||
| 365 | + title: '定位失败', | ||
| 366 | + content: '请确保已开启GPS定位服务,并检查网络连接是否正常', | ||
| 367 | + showCancel: false, | ||
| 368 | + confirmText: '我知道了', | ||
| 369 | + }) | ||
| 370 | + } else { | ||
| 371 | + // 其他未知错误 | ||
| 372 | + locationError.value = true | ||
| 373 | + Taro.showToast({ | ||
| 374 | + title: '获取位置失败,请重试', | ||
| 375 | + icon: 'none', | ||
| 376 | + }) | ||
| 377 | + } | ||
| 378 | + | ||
| 379 | + return false | ||
| 380 | + } | ||
| 381 | +} | ||
| 382 | + | ||
| 383 | +/** | ||
| 384 | + * 获取按钮显示文本 | ||
| 385 | + */ | ||
| 386 | +const getButtonText = () => { | ||
| 387 | + // 如果活动已结束,显示"活动已结束" | ||
| 388 | + if (activityStatus.value.is_ended) { | ||
| 389 | + return '活动已结束' | ||
| 390 | + } | ||
| 391 | + | ||
| 392 | + // 如果活动未开始,显示"活动未开始" | ||
| 393 | + if (!activityStatus.value.is_begin) { | ||
| 394 | + return '活动未开始' | ||
| 395 | + } | ||
| 396 | + | ||
| 397 | + // 如果没有加入家庭,显示"加入家庭" | ||
| 398 | + if (!hasJoinedFamily.value) { | ||
| 399 | + return '立即参加' | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + // 如果位置获取失败,显示"重新定位" | ||
| 403 | + if (locationError.value) { | ||
| 404 | + return '重新定位' | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + // 如果需要显示位置提示,说明需要授权 | ||
| 408 | + if (showLocationPrompt.value) { | ||
| 409 | + return '授权定位' | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + // 如果已有定位授权且有位置信息,显示"进入活动" | ||
| 413 | + if (hasLocationAuth.value && userLocation.value.lng && userLocation.value.lat) { | ||
| 414 | + return '立即进入' | ||
| 415 | + } | ||
| 416 | + | ||
| 417 | + // 默认显示"立即参加" | ||
| 418 | + return '立即参加' | ||
| 419 | +} | ||
| 420 | + | ||
| 421 | +/** | ||
| 422 | + * 检查用户是否加入家庭并处理参加活动按钮点击 | ||
| 423 | + */ | ||
| 424 | +const checkFamilyStatusAndJoinActivity = async () => { | ||
| 425 | + // 如果活动已结束,显示提示 | ||
| 426 | + if (activityStatus.value.is_ended) { | ||
| 427 | + Taro.showToast({ | ||
| 428 | + title: '活动已结束', | ||
| 429 | + icon: 'none', | ||
| 430 | + }) | ||
| 431 | + return | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + // 如果活动未开始,显示提示 | ||
| 435 | + if (!activityStatus.value.is_begin) { | ||
| 436 | + Taro.showToast({ | ||
| 437 | + title: '活动尚未开始,请耐心等待', | ||
| 438 | + icon: 'none', | ||
| 439 | + }) | ||
| 440 | + return | ||
| 441 | + } | ||
| 442 | + | ||
| 443 | + // 如果没有加入家庭,引导用户加入家庭 | ||
| 444 | + if (!hasJoinedFamily.value) { | ||
| 445 | + Taro.showModal({ | ||
| 446 | + title: '提示', | ||
| 447 | + content: '没有加入家庭是无法参加活动的', | ||
| 448 | + cancelText: '关闭', | ||
| 449 | + confirmText: '前往加入', | ||
| 450 | + success: res => { | ||
| 451 | + if (res.confirm) { | ||
| 452 | + Taro.redirectTo({ | ||
| 453 | + url: '/pages/Welcome/index', | ||
| 454 | + }) | ||
| 455 | + } | ||
| 456 | + }, | ||
| 457 | + }) | ||
| 458 | + return | ||
| 459 | + } | ||
| 460 | + | ||
| 461 | + // 如果位置获取失败,重新获取位置 | ||
| 462 | + if (locationError.value) { | ||
| 463 | + await retryGetLocation() | ||
| 464 | + return | ||
| 465 | + } | ||
| 466 | + | ||
| 467 | + // 如果需要显示位置提示,说明需要授权 | ||
| 468 | + if (showLocationPrompt.value) { | ||
| 469 | + showLocationPrompt.value = false // 隐藏提示,直接尝试获取位置 | ||
| 470 | + await handleJoinActivity() | ||
| 471 | + return | ||
| 472 | + } | ||
| 473 | + | ||
| 474 | + // 正常参加活动流程 | ||
| 475 | + await handleJoinActivity() | ||
| 476 | +} | ||
| 477 | + | ||
| 478 | +/** | ||
| 479 | + * 重新获取位置信息 | ||
| 480 | + */ | ||
| 481 | +const retryGetLocation = async () => { | ||
| 482 | + try { | ||
| 483 | + const success = await getUserLocation(false) // 不跳过授权检查,重新处理授权逻辑 | ||
| 484 | + if (success) { | ||
| 485 | + hasLocationAuth.value = true | ||
| 486 | + locationError.value = false | ||
| 487 | + Taro.showToast({ | ||
| 488 | + title: '位置获取成功', | ||
| 489 | + icon: 'success', | ||
| 490 | + }) | ||
| 491 | + } | ||
| 492 | + } catch (error) { | ||
| 493 | + console.error('重新获取位置失败:', error) | ||
| 494 | + } | ||
| 495 | +} | ||
| 496 | + | ||
| 497 | +/** | ||
| 498 | + * 位置权限申请弹窗 - 取消操作 | ||
| 499 | + */ | ||
| 500 | +const onLocationCancel = () => { | ||
| 501 | + showLocationDialog.value = false | ||
| 502 | + if (pendingLocationCallback.value) { | ||
| 503 | + Taro.showToast({ | ||
| 504 | + title: '需要位置权限才能参与活动', | ||
| 505 | + icon: 'none', | ||
| 506 | + }) | ||
| 507 | + pendingLocationCallback.value(false) | ||
| 508 | + pendingLocationCallback.value = null | ||
| 509 | + } | ||
| 510 | +} | ||
| 511 | + | ||
| 512 | +/** | ||
| 513 | + * 位置权限申请弹窗 - 同意授权 | ||
| 514 | + */ | ||
| 515 | +const onLocationConfirm = async () => { | ||
| 516 | + showLocationDialog.value = false | ||
| 517 | + | ||
| 518 | + try { | ||
| 519 | + const location = await Taro.getLocation({ | ||
| 520 | + type: 'gcj02', | ||
| 521 | + altitude: false, | ||
| 522 | + isHighAccuracy: true, | ||
| 523 | + highAccuracyExpireTime: 4000, | ||
| 524 | + }) | ||
| 525 | + | ||
| 526 | + userLocation.value = { | ||
| 527 | + lng: location.longitude, | ||
| 528 | + lat: location.latitude, | ||
| 529 | + } | ||
| 530 | + | ||
| 531 | + console.log('获取到用户位置:', userLocation.value) | ||
| 532 | + showLocationPrompt.value = false | ||
| 533 | + hasLocationAuth.value = true | ||
| 534 | + | ||
| 535 | + if (pendingLocationCallback.value) { | ||
| 536 | + pendingLocationCallback.value(true) | ||
| 537 | + pendingLocationCallback.value = null | ||
| 538 | + } | ||
| 539 | + } catch (error) { | ||
| 540 | + console.error('获取位置失败:', error) | ||
| 541 | + if (pendingLocationCallback.value) { | ||
| 542 | + pendingLocationCallback.value(false) | ||
| 543 | + pendingLocationCallback.value = null | ||
| 544 | + } | ||
| 545 | + } | ||
| 546 | +} | ||
| 547 | + | ||
| 548 | +/** | ||
| 549 | + * 处理参加活动按钮点击 | ||
| 550 | + */ | ||
| 551 | +const handleJoinActivity = async () => { | ||
| 552 | + isJoining.value = true | ||
| 553 | + | ||
| 554 | + try { | ||
| 555 | + // 检查定位授权状态 | ||
| 556 | + const authSetting = await Taro.getSetting() | ||
| 557 | + const hasLocationPermission = authSetting.authSetting['scope.userLocation'] | ||
| 558 | + | ||
| 559 | + if (hasLocationPermission === false) { | ||
| 560 | + // 用户之前拒绝过授权,显示提示让用户手动开启 | ||
| 561 | + showLocationPrompt.value = true | ||
| 562 | + isJoining.value = false | ||
| 563 | + return | ||
| 564 | + } else if (hasLocationPermission === undefined) { | ||
| 565 | + // 未请求过授权,直接尝试获取位置(会触发授权弹窗) | ||
| 566 | + const success = await getUserLocation(false) // 不跳过授权检查,让getUserLocation处理授权逻辑 | ||
| 567 | + if (!success) { | ||
| 568 | + showLocationPrompt.value = true | ||
| 569 | + isJoining.value = false | ||
| 570 | + return | ||
| 571 | + } | ||
| 572 | + } else { | ||
| 573 | + // 已有授权,直接获取位置 | ||
| 574 | + const success = await getUserLocation(true) // 跳过授权检查,直接获取位置 | ||
| 575 | + if (!success) { | ||
| 576 | + isJoining.value = false | ||
| 577 | + return | ||
| 578 | + } | ||
| 579 | + } | ||
| 580 | + | ||
| 581 | + // 跳转到Activities页面,并传递位置参数 | ||
| 582 | + await Taro.navigateTo({ | ||
| 583 | + url: `/pages/Activities/index?current_lng=${userLocation.value.lng}¤t_lat=${userLocation.value.lat}`, | ||
| 584 | + }) | ||
| 585 | + } catch (error) { | ||
| 586 | + console.error('参加活动失败:', error) | ||
| 587 | + Taro.showToast({ | ||
| 588 | + title: '参加活动失败', | ||
| 589 | + icon: 'none', | ||
| 590 | + }) | ||
| 591 | + } finally { | ||
| 592 | + isJoining.value = false | ||
| 593 | + } | ||
| 594 | +} | ||
| 595 | + | ||
| 596 | +/** | ||
| 597 | + * 处理分享活动事件 | ||
| 598 | + */ | ||
| 599 | +const onShareActivity = () => { | ||
| 600 | + console.log('分享活动海报') | ||
| 601 | + // 分享给朋友 | ||
| 602 | + // Taro.showToast({ | ||
| 603 | + // title: '请点击右上角分享给朋友', | ||
| 604 | + // icon: 'none', | ||
| 605 | + // duration: 2000 | ||
| 606 | + // }); | ||
| 607 | +} | ||
| 608 | + | ||
| 609 | +/** | ||
| 610 | + * 定义分享给朋友的内容 | ||
| 611 | + * @returns {Object} 分享配置对象 | ||
| 612 | + */ | ||
| 613 | +const onShareAppMessage = () => { | ||
| 614 | + return { | ||
| 615 | + title: '主题路线打卡活动等你参与', | ||
| 616 | + path: addShareFlag('/pages/ActivitiesCover/index'), | ||
| 617 | + success: res => { | ||
| 618 | + // 分享成功 | ||
| 619 | + }, | ||
| 620 | + fail: err => { | ||
| 621 | + // 分享失败 | ||
| 622 | + }, | ||
| 623 | + } | ||
| 624 | +} | ||
| 625 | + | ||
| 626 | +// 导出分享方法供Taro使用 | ||
| 627 | +defineExpose({ | ||
| 628 | + onShareAppMessage, | ||
| 629 | +}) | ||
| 630 | + | ||
| 631 | +/** | ||
| 632 | + * 处理分享海报事件 | ||
| 633 | + */ | ||
| 634 | +const onSharePoster = () => { | ||
| 635 | + console.log('分享海报') | ||
| 636 | + Taro.navigateTo({ | ||
| 637 | + url: '/pages/PosterCheckin/index', | ||
| 638 | + }) | ||
| 639 | +} | ||
| 640 | + | ||
| 641 | +/** | ||
| 642 | + * 点击海报预览 | ||
| 643 | + */ | ||
| 644 | +const onClickPost = () => { | ||
| 645 | + show_save.value = true | ||
| 646 | +} | ||
| 647 | + | ||
| 648 | +/** | ||
| 649 | + * 取消保存 | ||
| 650 | + */ | ||
| 651 | +const onCancelSave = () => { | ||
| 652 | + show_save.value = false | ||
| 653 | + show_post.value = false | ||
| 654 | +} | ||
| 655 | + | ||
| 656 | +/** | ||
| 657 | + * 选择保存方式 | ||
| 658 | + */ | ||
| 659 | +const onSelectSave = item => { | ||
| 660 | + if (item.name === '保存至相册') { | ||
| 661 | + show_save.value = false | ||
| 662 | + show_post.value = false | ||
| 663 | + savePoster() | ||
| 664 | + } | ||
| 665 | +} | ||
| 666 | + | ||
| 667 | +/** | ||
| 668 | + * 开始生成海报 | ||
| 669 | + */ | ||
| 670 | +// const startGeneratePoster = async () => { | ||
| 671 | +// // 配置海报参数 | ||
| 672 | +// base = { | ||
| 673 | +// width: 1024, | ||
| 674 | +// height: 1334, | ||
| 675 | +// backgroundColor: '', | ||
| 676 | +// debug: false, | ||
| 677 | +// blocks: [ | ||
| 678 | +// { // 上部分canvas画布高度 | ||
| 679 | +// x: 40, | ||
| 680 | +// y: 20, | ||
| 681 | +// width: 950, | ||
| 682 | +// height: 950, | ||
| 683 | +// paddingLeft: 0, | ||
| 684 | +// paddingRight: 0, | ||
| 685 | +// borderWidth: 1, | ||
| 686 | +// borderColor: '#fff', | ||
| 687 | +// backgroundColor: '#fff', | ||
| 688 | +// borderRadiusGroup: [16, 16, 0, 0], | ||
| 689 | +// }, | ||
| 690 | +// { // 活动时间背景图 | ||
| 691 | +// x: 40, | ||
| 692 | +// y: 730, | ||
| 693 | +// height: 75, | ||
| 694 | +// paddingLeft: 80, | ||
| 695 | +// paddingRight: 0, | ||
| 696 | +// borderWidth: 0, | ||
| 697 | +// text: { | ||
| 698 | +// x: 0, | ||
| 699 | +// y: 0, | ||
| 700 | +// text: activityData.value.dateRange, | ||
| 701 | +// fontSize: 40, | ||
| 702 | +// color: '#222', | ||
| 703 | +// opacity: 1, | ||
| 704 | +// baseLine: 'top', | ||
| 705 | +// lineHeight: 48, | ||
| 706 | +// lineNum: 2, | ||
| 707 | +// textAlign: 'left', | ||
| 708 | +// zIndex: 0, | ||
| 709 | +// }, | ||
| 710 | +// backgroundColor: '#FFF9F3', | ||
| 711 | +// borderRadiusGroup: [0, 25, 25, 0], | ||
| 712 | +// }, | ||
| 713 | +// { // 活动地点背景图 | ||
| 714 | +// x: 40, | ||
| 715 | +// y: 830, | ||
| 716 | +// height: 75, | ||
| 717 | +// paddingLeft: 80, | ||
| 718 | +// paddingRight: 0, | ||
| 719 | +// borderWidth: 0, | ||
| 720 | +// text: { | ||
| 721 | +// x: 0, | ||
| 722 | +// y: 0, | ||
| 723 | +// text: '上海市黄浦区南京东路', | ||
| 724 | +// fontSize: 40, | ||
| 725 | +// color: '#222', | ||
| 726 | +// opacity: 1, | ||
| 727 | +// baseLine: 'top', | ||
| 728 | +// lineHeight: 48, | ||
| 729 | +// lineNum: 2, | ||
| 730 | +// textAlign: 'left', | ||
| 731 | +// zIndex: 0, | ||
| 732 | +// }, | ||
| 733 | +// backgroundColor: '#FFF9F3', | ||
| 734 | +// borderRadiusGroup: [0, 25, 25, 0], | ||
| 735 | +// }, | ||
| 736 | +// { // 下部分canvas画布高度 | ||
| 737 | +// x: 40, | ||
| 738 | +// y: 1060, | ||
| 739 | +// width: 950, | ||
| 740 | +// height: 250, | ||
| 741 | +// paddingLeft: 0, | ||
| 742 | +// paddingRight: 0, | ||
| 743 | +// borderWidth: 1, | ||
| 744 | +// borderColor: '#fff', | ||
| 745 | +// backgroundColor: '#fff', | ||
| 746 | +// borderRadiusGroup: [0, 0, 16, 16], | ||
| 747 | +// } | ||
| 748 | +// ], | ||
| 749 | +// texts: [ | ||
| 750 | +// { | ||
| 751 | +// x: 80, | ||
| 752 | +// y: 630, | ||
| 753 | +// text: activityData.value.title, | ||
| 754 | +// fontSize: 50, | ||
| 755 | +// color: '#000', | ||
| 756 | +// opacity: 1, | ||
| 757 | +// baseLine: 'middle', | ||
| 758 | +// lineHeight: 60, | ||
| 759 | +// lineNum: 2, | ||
| 760 | +// textAlign: 'left', | ||
| 761 | +// width: 800, | ||
| 762 | +// zIndex: 999, | ||
| 763 | +// fontFamily: 'Monospace', | ||
| 764 | +// }, | ||
| 765 | +// { | ||
| 766 | +// x: 135, | ||
| 767 | +// y: 770, | ||
| 768 | +// text: activityData.value.dateRange, | ||
| 769 | +// fontSize: 40, | ||
| 770 | +// color: '#222', | ||
| 771 | +// opacity: 1, | ||
| 772 | +// baseLine: 'middle', | ||
| 773 | +// lineHeight: 48, | ||
| 774 | +// lineNum: 2, | ||
| 775 | +// textAlign: 'left', | ||
| 776 | +// zIndex: 999, | ||
| 777 | +// }, | ||
| 778 | +// { | ||
| 779 | +// x: 135, | ||
| 780 | +// y: 870, | ||
| 781 | +// text: '上海市黄浦区南京东路', | ||
| 782 | +// fontSize: 40, | ||
| 783 | +// color: '#222', | ||
| 784 | +// opacity: 1, | ||
| 785 | +// baseLine: 'middle', | ||
| 786 | +// lineHeight: 48, | ||
| 787 | +// lineNum: 2, | ||
| 788 | +// textAlign: 'left', | ||
| 789 | +// zIndex: 999, | ||
| 790 | +// }, | ||
| 791 | +// { | ||
| 792 | +// x: 300, | ||
| 793 | +// y: 1150, | ||
| 794 | +// text: nickname.value, | ||
| 795 | +// fontSize: 50, | ||
| 796 | +// color: '#333', | ||
| 797 | +// opacity: 1, | ||
| 798 | +// baseLine: 'middle', | ||
| 799 | +// textAlign: 'left', | ||
| 800 | +// lineHeight: 50, | ||
| 801 | +// lineNum: 1, | ||
| 802 | +// zIndex: 999, | ||
| 803 | +// }, | ||
| 804 | +// { | ||
| 805 | +// x: 300, | ||
| 806 | +// y: 1220, | ||
| 807 | +// text: '邀请你一起来活动!', | ||
| 808 | +// fontSize: 42, | ||
| 809 | +// color: '#8F9399', | ||
| 810 | +// opacity: 1, | ||
| 811 | +// baseLine: 'middle', | ||
| 812 | +// textAlign: 'left', | ||
| 813 | +// lineHeight: 42, | ||
| 814 | +// lineNum: 1, | ||
| 815 | +// zIndex: 999, | ||
| 816 | +// } | ||
| 817 | +// ], | ||
| 818 | +// images: [ | ||
| 819 | +// { | ||
| 820 | +// url: qrcode_url, | ||
| 821 | +// width: 949, | ||
| 822 | +// height: 108, | ||
| 823 | +// x: 40, | ||
| 824 | +// y: 960, | ||
| 825 | +// zIndex: 10, | ||
| 826 | +// }, | ||
| 827 | +// { | ||
| 828 | +// url: qrcode_url, | ||
| 829 | +// width: 950, | ||
| 830 | +// height: 500, | ||
| 831 | +// x: 40, | ||
| 832 | +// y: 20, | ||
| 833 | +// borderRadiusGroup: [18, 18, 0, 0], | ||
| 834 | +// zIndex: 10, | ||
| 835 | +// }, | ||
| 836 | +// { | ||
| 837 | +// url: qrcode_url, | ||
| 838 | +// width: 40, | ||
| 839 | +// height: 40, | ||
| 840 | +// x: 80, | ||
| 841 | +// y: 750, | ||
| 842 | +// borderRadius: 100, | ||
| 843 | +// borderWidth: 0, | ||
| 844 | +// zIndex: 10, | ||
| 845 | +// }, | ||
| 846 | +// { | ||
| 847 | +// url: qrcode_url, | ||
| 848 | +// width: 35, | ||
| 849 | +// height: 40, | ||
| 850 | +// x: 80, | ||
| 851 | +// y: 850, | ||
| 852 | +// borderRadius: 100, | ||
| 853 | +// borderWidth: 0, | ||
| 854 | +// zIndex: 10, | ||
| 855 | +// }, | ||
| 856 | +// { | ||
| 857 | +// url: qrcode_url, | ||
| 858 | +// width: 170, | ||
| 859 | +// height: 170, | ||
| 860 | +// x: 80, | ||
| 861 | +// y: 1090, | ||
| 862 | +// borderRadius: 100, | ||
| 863 | +// borderWidth: 0, | ||
| 864 | +// zIndex: 10, | ||
| 865 | +// }, | ||
| 866 | +// { | ||
| 867 | +// url: qrcode_url, | ||
| 868 | +// width: 170, | ||
| 869 | +// height: 170, | ||
| 870 | +// x: 750, | ||
| 871 | +// y: 1090, | ||
| 872 | +// borderRadius: 100, | ||
| 873 | +// borderWidth: 0, | ||
| 874 | +// zIndex: 10, | ||
| 875 | +// }, | ||
| 876 | +// ], | ||
| 877 | +// lines: [] | ||
| 878 | +// } | ||
| 879 | + | ||
| 880 | +// startDraw.value = true | ||
| 881 | +// if (!posterPath.value) Taro.showLoading({ title: '生成海报中...' }) | ||
| 882 | +// } | ||
| 883 | + | ||
| 884 | +/** | ||
| 885 | + * 海报绘制成功回调 | ||
| 886 | + */ | ||
| 887 | +const drawSuccess = result => { | ||
| 888 | + console.log('绘制成功', result) | ||
| 889 | + const { tempFilePath, errMsg } = result | ||
| 890 | + if (errMsg === 'canvasToTempFilePath:ok') { | ||
| 891 | + posterPath.value = tempFilePath | ||
| 892 | + Taro.hideLoading() | ||
| 893 | + } else { | ||
| 894 | + Taro.hideLoading() | ||
| 895 | + Taro.showToast({ | ||
| 896 | + title: '生成失败,请稍后重试', | ||
| 897 | + icon: 'none', | ||
| 898 | + duration: 2500, | ||
| 899 | + }) | ||
| 900 | + } | ||
| 901 | +} | ||
| 902 | + | ||
| 903 | +/** | ||
| 904 | + * 海报绘制失败回调 | ||
| 905 | + */ | ||
| 906 | +const drawFail = result => { | ||
| 907 | + console.log('绘制失败', result) | ||
| 908 | + Taro.hideLoading() | ||
| 909 | + Taro.showToast({ | ||
| 910 | + title: '生成失败,请稍后重试', | ||
| 911 | + icon: 'none', | ||
| 912 | + duration: 2500, | ||
| 913 | + }) | ||
| 914 | +} | ||
| 915 | + | ||
| 916 | +/** | ||
| 917 | + * 保存海报到相册 | ||
| 918 | + */ | ||
| 919 | +const savePoster = () => { | ||
| 920 | + Taro.saveImageToPhotosAlbum({ | ||
| 921 | + filePath: posterPath.value, | ||
| 922 | + success() { | ||
| 923 | + Taro.showToast({ | ||
| 924 | + title: '已保存到相册', | ||
| 925 | + icon: 'success', | ||
| 926 | + duration: 2000, | ||
| 927 | + }) | ||
| 928 | + }, | ||
| 929 | + fail() { | ||
| 930 | + Taro.showToast({ | ||
| 931 | + title: '保存失败', | ||
| 932 | + icon: 'none', | ||
| 933 | + duration: 2000, | ||
| 934 | + }) | ||
| 935 | + }, | ||
| 936 | + }) | ||
| 937 | +} | ||
| 938 | + | ||
| 939 | +/** | ||
| 940 | + * 将 API 数据转换为页面需要的 activityData 格式 | ||
| 941 | + * @param {Object} apiData - API 返回的活动详情数据 | ||
| 942 | + * @returns {Object} 页面活动数据对象 | ||
| 943 | + */ | ||
| 944 | +const transformApiDataToActivityData = apiData => { | ||
| 945 | + if (!apiData) { | ||
| 946 | + return null | ||
| 947 | + } | ||
| 948 | + | ||
| 949 | + // 生成日期范围字符串 | ||
| 950 | + const dateRange = `${apiData.begin_date} - ${apiData.end_date}` | ||
| 951 | + | ||
| 952 | + // 根据积分规则生成规则描述 | ||
| 953 | + const rules = [ | ||
| 954 | + `打卡任意1关,视为参与,奖励${apiData.first_checkin_points}积分`, | ||
| 955 | + `打卡任意${apiData.required_checkin_count}关,视为完成,奖励${apiData.complete_points}积分`, | ||
| 956 | + '不需要区分打卡点的先后次序', | ||
| 957 | + ] | ||
| 958 | + | ||
| 959 | + // 生成奖励描述 | ||
| 960 | + const rewards = [ | ||
| 961 | + `首次打卡获得${apiData.first_checkin_points}积分`, | ||
| 962 | + `完成${apiData.required_checkin_count}个打卡点获得${apiData.complete_points}积分`, | ||
| 963 | + apiData.discount_title || '打卡点专属优惠', | ||
| 964 | + ] | ||
| 965 | + | ||
| 966 | + return { | ||
| 967 | + title: apiData.tittle || '活动标题', | ||
| 968 | + subtitle: '探索城市魅力,感受时尚脉搏', | ||
| 969 | + dateRange: dateRange, | ||
| 970 | + posterUrl: apiData.cover || defaultPoster.value, | ||
| 971 | + description: `欢迎参加${apiData.tittle}活动!`, | ||
| 972 | + rules: rules, | ||
| 973 | + rewards: rewards, | ||
| 974 | + } | ||
| 975 | +} | ||
| 976 | + | ||
| 977 | +/** | ||
| 978 | + * 获取活动详情 | ||
| 979 | + */ | ||
| 980 | +const fetchActivityDetail = async () => { | ||
| 981 | + try { | ||
| 982 | + // 如果没有活动ID,不获取详情 | ||
| 983 | + if (!activityId.value) { | ||
| 984 | + console.warn('[ActivitiesCover] 未提供活动ID,跳过详情获取') | ||
| 985 | + return | ||
| 986 | + } | ||
| 987 | + | ||
| 988 | + console.log('[ActivitiesCover] 开始获取活动详情, ID:', activityId.value) | ||
| 989 | + | ||
| 990 | + // 根据环境选择真实 API 或 mock API | ||
| 991 | + const response = USE_MOCK_DATA | ||
| 992 | + ? await mockMapActivityDetailAPI({ id: activityId.value }) | ||
| 993 | + : await detailAPI({ id: activityId.value }) | ||
| 994 | + | ||
| 995 | + if (response.code === 1 && response.data) { | ||
| 996 | + console.log('[ActivitiesCover] 活动详情获取成功:', response.data) | ||
| 997 | + | ||
| 998 | + // 转换 API 数据为页面格式 | ||
| 999 | + const transformedData = transformApiDataToActivityData(response.data) | ||
| 1000 | + if (transformedData) { | ||
| 1001 | + activityData.value = transformedData | ||
| 1002 | + | ||
| 1003 | + // 更新默认海报图 | ||
| 1004 | + if (response.data.cover) { | ||
| 1005 | + defaultPoster.value = response.data.cover | ||
| 1006 | + } | ||
| 1007 | + | ||
| 1008 | + // 更新活动状态 | ||
| 1009 | + activityStatus.value.is_begin = Boolean(response.data.is_begin) | ||
| 1010 | + activityStatus.value.is_ended = Boolean(response.data.is_ended) | ||
| 1011 | + } | ||
| 1012 | + } else { | ||
| 1013 | + console.warn('[ActivitiesCover] 获取活动详情失败:', response.msg) | ||
| 1014 | + } | ||
| 1015 | + } catch (error) { | ||
| 1016 | + console.error('[ActivitiesCover] 获取活动详情异常:', error) | ||
| 1017 | + } | ||
| 1018 | +} | ||
| 1019 | + | ||
| 1020 | +/** | ||
| 1021 | + * 初始化页面数据 | ||
| 1022 | + */ | ||
| 1023 | +const initPageData = async () => { | ||
| 1024 | + // 获取用户是否加入家庭 | ||
| 1025 | + const { code, data } = await getMyFamiliesAPI() | ||
| 1026 | + if (code) { | ||
| 1027 | + // 如果加入家庭 | ||
| 1028 | + if (data?.families?.length) { | ||
| 1029 | + hasJoinedFamily.value = true | ||
| 1030 | + } | ||
| 1031 | + } | ||
| 1032 | + | ||
| 1033 | + // 获取活动详情(包含活动状态) | ||
| 1034 | + await fetchActivityDetail() | ||
| 1035 | + | ||
| 1036 | + // 检查定位授权状态(不获取位置,只检查权限) | ||
| 1037 | + await checkLocationAuth() | ||
| 1038 | +} | ||
| 1039 | + | ||
| 1040 | +// 处理页面加载时的授权检查 | ||
| 1041 | +useLoad(options => { | ||
| 1042 | + console.log('[ActivitiesCover] 页面加载, 参数:', options) | ||
| 1043 | + | ||
| 1044 | + // 获取活动 ID(如果有) | ||
| 1045 | + if (options.id) { | ||
| 1046 | + activityId.value = options.id | ||
| 1047 | + } else if (options.activity_id) { | ||
| 1048 | + activityId.value = options.activity_id | ||
| 1049 | + } else { | ||
| 1050 | + // 如果没有活动ID,使用默认ID | ||
| 1051 | + activityId.value = '1' | ||
| 1052 | + console.warn('[ActivitiesCover] 未提供活动ID,使用默认ID: 1') | ||
| 1053 | + } | ||
| 1054 | + | ||
| 1055 | + // 处理分享页面的授权逻辑 | ||
| 1056 | + handleSharePageAuth(options, () => { | ||
| 1057 | + initPageData() | ||
| 1058 | + }) | ||
| 1059 | +}) | ||
| 1060 | + | ||
| 1061 | +// 页面挂载时检查定位授权状态 | ||
| 1062 | +onMounted(async () => { | ||
| 1063 | + // 获取系统信息 | ||
| 1064 | + getSystemInfo() | ||
| 1065 | + | ||
| 1066 | + initPageData() | ||
| 1067 | +}) | ||
| 1068 | +</script> | ||
| 1069 | + | ||
| 1070 | +<script> | ||
| 1071 | +export default { | ||
| 1072 | + name: 'ActivitiesCover', | ||
| 1073 | +} | ||
| 1074 | +</script> |
-
Please register or login to post a comment