feat(ActivitiesCover): 集成 map_activity detail API
- 集成 detailAPI 获取活动详情数据 - 添加数据转换函数 transformApiDataToActivityData - 移除单独的 fetchActivityStatus,统一通过 fetchActivityDetail 获取 - 积分规则改为动态渲染(v-for) - 支持开发环境使用 mock 数据测试 - 创建测试指南文档 影响文件: - src/pages/ActivitiesCover/index.vue - docs/ActivitiesCover-测试指南.md - .gitignore (添加 .tmp/ 目录) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
3 changed files
with
539 additions
and
130 deletions
docs/ActivitiesCover-测试指南.md
0 → 100644
| 1 | +# ActivitiesCover 页面测试指南 | ||
| 2 | + | ||
| 3 | +**测试日期**: 2026-02-09 | ||
| 4 | +**测试目标**: 验证 map_activity detail API 是否能覆盖 ActivitiesCover 页面的数据需求 | ||
| 5 | + | ||
| 6 | +--- | ||
| 7 | + | ||
| 8 | +## 📊 数据覆盖分析 | ||
| 9 | + | ||
| 10 | +### ✅ API 完全覆盖的字段 | ||
| 11 | + | ||
| 12 | +| 页面需求 | API 字段 | 数据类型 | 状态 | | ||
| 13 | +|---------|---------|---------|------| | ||
| 14 | +| 活动标题 | `tittle` | string | ✅ 完全匹配 | | ||
| 15 | +| 封面图 | `cover` | string | ✅ 完全匹配 | | ||
| 16 | +| 开始时间 | `begin_date` | string | ✅ 完全匹配 | | ||
| 17 | +| 结束时间 | `end_date` | string | ✅ 完全匹配 | | ||
| 18 | +| 活动是否开始 | `is_begin` | boolean | ✅ 完全匹配 | | ||
| 19 | +| 活动是否结束 | `is_ended` | boolean | ✅ 完全匹配 | | ||
| 20 | +| 首次打卡积分 | `first_checkin_points` | integer | ✅ 完全匹配 | | ||
| 21 | +| 完成打卡积分 | `complete_points` | integer | ✅ 完全匹配 | | ||
| 22 | +| 需要打卡次数 | `required_checkin_count` | integer | ✅ 完全匹配 | | ||
| 23 | + | ||
| 24 | +### ⚠️ 需要转换的字段 | ||
| 25 | + | ||
| 26 | +| 页面需求 | 数据来源 | 转换逻辑 | 状态 | | ||
| 27 | +|---------|---------|---------|------| | ||
| 28 | +| 副标题 (`subtitle`) | 硬编码 | 固定文案 | ✅ 已处理 | | ||
| 29 | +| 日期范围 (`dateRange`) | `begin_date` + `end_date` | 字符串拼接 | ✅ 已处理 | | ||
| 30 | +| 活动描述 (`description`) | API 数据 | 模板生成 | ✅ 已处理 | | ||
| 31 | +| 活动规则 (`rules`) | 积分相关字段 | 数组生成 | ✅ 已处理 | | ||
| 32 | +| 奖励列表 (`rewards`) | 积分相关字段 | 数组生成 | ✅ 已处理 | | ||
| 33 | + | ||
| 34 | +--- | ||
| 35 | + | ||
| 36 | +## 🔧 代码修改说明 | ||
| 37 | + | ||
| 38 | +### 1. 新增导入 | ||
| 39 | + | ||
| 40 | +```javascript | ||
| 41 | +import { detailAPI } from '@/api/map_activity' | ||
| 42 | +import { mockMapActivityDetailAPI } from '@/utils/mockData' | ||
| 43 | +``` | ||
| 44 | + | ||
| 45 | +### 2. 添加环境变量 | ||
| 46 | + | ||
| 47 | +```javascript | ||
| 48 | +// 开发环境使用 mock 数据,生产环境使用真实 API | ||
| 49 | +const USE_MOCK_DATA = process.env.NODE_ENV === 'development' | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +### 3. 数据转换函数 | ||
| 53 | + | ||
| 54 | +```javascript | ||
| 55 | +/** | ||
| 56 | + * 将 API 数据转换为页面需要的 activityData 格式 | ||
| 57 | + * @param {Object} apiData - API 返回的活动详情数据 | ||
| 58 | + * @returns {Object} 页面活动数据对象 | ||
| 59 | + */ | ||
| 60 | +const transformApiDataToActivityData = (apiData) => { | ||
| 61 | + if (!apiData) return null | ||
| 62 | + | ||
| 63 | + // 生成日期范围字符串 | ||
| 64 | + const dateRange = `${apiData.begin_date} - ${apiData.end_date}` | ||
| 65 | + | ||
| 66 | + // 根据积分规则生成规则描述 | ||
| 67 | + const rules = [ | ||
| 68 | + `打卡任意1关,视为参与,奖励${apiData.first_checkin_points}积分`, | ||
| 69 | + `打卡任意${apiData.required_checkin_count}关,视为完成,奖励${apiData.complete_points}积分`, | ||
| 70 | + '不需要区分打卡点的先后次序' | ||
| 71 | + ] | ||
| 72 | + | ||
| 73 | + // 生成奖励描述 | ||
| 74 | + const rewards = [ | ||
| 75 | + `首次打卡获得${apiData.first_checkin_points}积分`, | ||
| 76 | + `完成${apiData.required_checkin_count}个打卡点获得${apiData.complete_points}积分`, | ||
| 77 | + apiData.discount_title || '打卡点专属优惠' | ||
| 78 | + ] | ||
| 79 | + | ||
| 80 | + return { | ||
| 81 | + title: apiData.tittle || '活动标题', | ||
| 82 | + subtitle: '探索城市魅力,感受时尚脉搏', | ||
| 83 | + dateRange: dateRange, | ||
| 84 | + posterUrl: apiData.cover || defaultPoster.value, | ||
| 85 | + description: `欢迎参加${apiData.tittle}活动!`, | ||
| 86 | + rules: rules, | ||
| 87 | + rewards: rewards | ||
| 88 | + } | ||
| 89 | +} | ||
| 90 | +``` | ||
| 91 | + | ||
| 92 | +### 4. 获取活动详情 | ||
| 93 | + | ||
| 94 | +```javascript | ||
| 95 | +/** | ||
| 96 | + * 获取活动详情 | ||
| 97 | + */ | ||
| 98 | +const fetchActivityDetail = async () => { | ||
| 99 | + try { | ||
| 100 | + if (!activityId.value) { | ||
| 101 | + console.warn('[ActivitiesCover] 未提供活动ID,跳过详情获取') | ||
| 102 | + return | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + console.log('[ActivitiesCover] 开始获取活动详情, ID:', activityId.value) | ||
| 106 | + | ||
| 107 | + // 根据环境选择真实 API 或 mock API | ||
| 108 | + const response = USE_MOCK_DATA | ||
| 109 | + ? await mockMapActivityDetailAPI({ id: activityId.value }) | ||
| 110 | + : await detailAPI({ id: activityId.value }) | ||
| 111 | + | ||
| 112 | + if (response.code === 1 && response.data) { | ||
| 113 | + console.log('[ActivitiesCover] 活动详情获取成功:', response.data) | ||
| 114 | + | ||
| 115 | + // 转换 API 数据为页面格式 | ||
| 116 | + const transformedData = transformApiDataToActivityData(response.data) | ||
| 117 | + if (transformedData) { | ||
| 118 | + activityData.value = transformedData | ||
| 119 | + | ||
| 120 | + // 更新默认海报图 | ||
| 121 | + if (response.data.cover) { | ||
| 122 | + defaultPoster.value = response.data.cover | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + // 更新活动状态 | ||
| 126 | + activityStatus.value.is_begin = Boolean(response.data.is_begin) | ||
| 127 | + activityStatus.value.is_ended = Boolean(response.data.is_ended) | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + } catch (error) { | ||
| 131 | + console.error('[ActivitiesCover] 获取活动详情异常:', error) | ||
| 132 | + } | ||
| 133 | +} | ||
| 134 | +``` | ||
| 135 | + | ||
| 136 | +### 5. 移除旧逻辑 | ||
| 137 | + | ||
| 138 | +- ❌ 移除了 `fetchActivityStatus` 函数(不再需要单独获取活动状态) | ||
| 139 | +- ❌ 移除了 `getActivityStatusAPI` 导入 | ||
| 140 | +- ✅ 活动状态现在通过 `fetchActivityDetail` 统一获取 | ||
| 141 | + | ||
| 142 | +### 6. 动态积分规则显示 | ||
| 143 | + | ||
| 144 | +```vue | ||
| 145 | +<!-- 积分规则说明 - 使用 v-for 动态渲染 --> | ||
| 146 | +<view v-if="activityData.rules && activityData.rules.length" class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4 opacity-90"> | ||
| 147 | + <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> | ||
| 148 | + <text | ||
| 149 | + v-for="(rule, index) in activityData.rules" | ||
| 150 | + :key="index" | ||
| 151 | + class="text-blue-500 text-sm leading-relaxed block mb-1" | ||
| 152 | + style="padding-left: 20rpx; text-indent: -20rpx;" | ||
| 153 | + > | ||
| 154 | + • {{ rule }} | ||
| 155 | + </text> | ||
| 156 | +</view> | ||
| 157 | +``` | ||
| 158 | + | ||
| 159 | +--- | ||
| 160 | + | ||
| 161 | +## 🧪 测试步骤 | ||
| 162 | + | ||
| 163 | +### 方式 1: Mock 数据测试(开发环境) | ||
| 164 | + | ||
| 165 | +```bash | ||
| 166 | +# 1. 启动开发服务器 | ||
| 167 | +pnpm run dev:weapp | ||
| 168 | + | ||
| 169 | +# 2. 打开微信开发者工具,导入 dist 目录 | ||
| 170 | + | ||
| 171 | +# 3. 访问页面(带活动 ID 参数) | ||
| 172 | +/pages/ActivitiesCover/index?id=1 | ||
| 173 | +# 或 | ||
| 174 | +/pages/ActivitiesCover/index?activity_id=1 | ||
| 175 | + | ||
| 176 | +# 4. 检查控制台日志,应该看到: | ||
| 177 | +# [ActivitiesCover] 页面加载, 参数: {id: "1"} | ||
| 178 | +# [ActivitiesCover] 开始获取活动详情, ID: 1 | ||
| 179 | +# [Mock] detailAPI - 活动详情,ID:1 | ||
| 180 | +# [ActivitiesCover] 活动详情获取成功: {...} | ||
| 181 | +``` | ||
| 182 | + | ||
| 183 | +**预期结果**: | ||
| 184 | +- ✅ 页面显示活动标题、封面图 | ||
| 185 | +- ✅ 日期范围正确显示 | ||
| 186 | +- ✅ 积分规则动态显示(不是硬编码) | ||
| 187 | +- ✅ "立即参加" 按钮状态正确(根据活动状态) | ||
| 188 | + | ||
| 189 | +### 方式 2: 真实 API 测试(生产环境) | ||
| 190 | + | ||
| 191 | +```bash | ||
| 192 | +# 1. 修改环境变量(确保不是开发环境) | ||
| 193 | +# NODE_ENV=production | ||
| 194 | + | ||
| 195 | +# 2. 重新构建 | ||
| 196 | +pnpm run build:weapp | ||
| 197 | + | ||
| 198 | +# 3. 在微信开发者工具中测试 | ||
| 199 | +``` | ||
| 200 | + | ||
| 201 | +**预期结果**: | ||
| 202 | +- ✅ 真实 API 数据正确显示 | ||
| 203 | +- ✅ 所有字段正确映射 | ||
| 204 | + | ||
| 205 | +--- | ||
| 206 | + | ||
| 207 | +## 📝 Mock 数据示例 | ||
| 208 | + | ||
| 209 | +### 输入参数 | ||
| 210 | + | ||
| 211 | +```javascript | ||
| 212 | +{ | ||
| 213 | + id: "1" | ||
| 214 | +} | ||
| 215 | +``` | ||
| 216 | + | ||
| 217 | +### Mock 返回数据 | ||
| 218 | + | ||
| 219 | +```javascript | ||
| 220 | +{ | ||
| 221 | + code: 1, | ||
| 222 | + msg: "success", | ||
| 223 | + data: { | ||
| 224 | + url: "https://example.com/map", | ||
| 225 | + id: "1", | ||
| 226 | + cover: "https://picsum.photos/400/300?random=1", | ||
| 227 | + tittle: "公园晨跑打卡", | ||
| 228 | + begin_date: "2025.01.15", | ||
| 229 | + end_date: "2025.02.28", | ||
| 230 | + is_ended: false, | ||
| 231 | + is_begin: true, | ||
| 232 | + first_checkin_points: 10, | ||
| 233 | + required_checkin_count: 5, | ||
| 234 | + complete_points: 50, | ||
| 235 | + discount_title: "打卡点优惠信息" | ||
| 236 | + } | ||
| 237 | +} | ||
| 238 | +``` | ||
| 239 | + | ||
| 240 | +### 转换后的页面数据 | ||
| 241 | + | ||
| 242 | +```javascript | ||
| 243 | +{ | ||
| 244 | + title: "公园晨跑打卡", | ||
| 245 | + subtitle: "探索城市魅力,感受时尚脉搏", | ||
| 246 | + dateRange: "2025.01.15 - 2025.02.28", | ||
| 247 | + posterUrl: "https://picsum.photos/400/300?random=1", | ||
| 248 | + description: "欢迎参加公园晨跑打卡活动!", | ||
| 249 | + rules: [ | ||
| 250 | + "打卡任意1关,视为参与,奖励10积分", | ||
| 251 | + "打卡任意5关,视为完成,奖励50积分", | ||
| 252 | + "不需要区分打卡点的先后次序" | ||
| 253 | + ], | ||
| 254 | + rewards: [ | ||
| 255 | + "首次打卡获得10积分", | ||
| 256 | + "完成5个打卡点获得50积分", | ||
| 257 | + "打卡点优惠信息" | ||
| 258 | + ] | ||
| 259 | +} | ||
| 260 | +``` | ||
| 261 | + | ||
| 262 | +--- | ||
| 263 | + | ||
| 264 | +## ✅ 验收标准 | ||
| 265 | + | ||
| 266 | +### 功能验收 | ||
| 267 | + | ||
| 268 | +- [ ] 页面能正确加载活动详情 | ||
| 269 | +- [ ] 活动标题正确显示 | ||
| 270 | +- [ ] 封面图正确显示 | ||
| 271 | +- [ ] 日期范围格式正确 | ||
| 272 | +- [ ] 积分规则动态生成并显示 | ||
| 273 | +- [ ] 活动状态(已开始/已结束)正确反映在按钮状态上 | ||
| 274 | + | ||
| 275 | +### 技术验收 | ||
| 276 | + | ||
| 277 | +- [ ] 开发环境使用 mock 数据 | ||
| 278 | +- [ ] 生产环境使用真实 API | ||
| 279 | +- [ ] 数据转换逻辑正确 | ||
| 280 | +- [ ] 错误处理完善 | ||
| 281 | +- [ ] 控制台日志清晰 | ||
| 282 | + | ||
| 283 | +--- | ||
| 284 | + | ||
| 285 | +## 🐛 已知问题 | ||
| 286 | + | ||
| 287 | +### 1. API 字段拼写问题 | ||
| 288 | + | ||
| 289 | +**问题描述**: API 返回的字段是 `tittle`(拼写错误),不是 `title` | ||
| 290 | + | ||
| 291 | +**解决方案**: 代码中使用 `apiData.tittle`,与 API 保持一致 | ||
| 292 | + | ||
| 293 | +### 2. 活动 ID 参数 | ||
| 294 | + | ||
| 295 | +**问题描述**: 可能使用 `id` 或 `activity_id` 作为参数名 | ||
| 296 | + | ||
| 297 | +**解决方案**: | ||
| 298 | +```javascript | ||
| 299 | +// 支持两种参数名 | ||
| 300 | +if (options.id) { | ||
| 301 | + activityId.value = options.id | ||
| 302 | +} else if (options.activity_id) { | ||
| 303 | + activityId.value = options.activity_id | ||
| 304 | +} else { | ||
| 305 | + // 默认使用 ID: 1 | ||
| 306 | + activityId.value = '1' | ||
| 307 | +} | ||
| 308 | +``` | ||
| 309 | + | ||
| 310 | +--- | ||
| 311 | + | ||
| 312 | +## 📚 相关文件 | ||
| 313 | + | ||
| 314 | +- **页面代码**: `src/pages/ActivitiesCover/index.vue` | ||
| 315 | +- **API 定义**: `src/api/map_activity.js` | ||
| 316 | +- **Mock 数据**: `src/utils/mockData.js` | ||
| 317 | +- **API 文档**: `docs/api-specs/map_activity/detail.md` | ||
| 318 | + | ||
| 319 | +--- | ||
| 320 | + | ||
| 321 | +## 🎯 下一步 | ||
| 322 | + | ||
| 323 | +1. ✅ 完成功能开发 | ||
| 324 | +2. ⏳ 进行真机测试 | ||
| 325 | +3. ⏳ 测试不同活动 ID | ||
| 326 | +4. ⏳ 测试边界情况(无网络、API 错误等) | ||
| 327 | +5. ⏳ 优化用户体验 | ||
| 328 | + | ||
| 329 | +--- | ||
| 330 | + | ||
| 331 | +**测试人员**: Claude Code | ||
| 332 | +**测试日期**: 2026-02-09 | ||
| 333 | +**测试状态**: ✅ 开发完成,等待测试 |
| ... | @@ -8,11 +8,7 @@ | ... | @@ -8,11 +8,7 @@ |
| 8 | <template> | 8 | <template> |
| 9 | <view class="activities-cover-container"> | 9 | <view class="activities-cover-container"> |
| 10 | <!-- 背景图片 --> | 10 | <!-- 背景图片 --> |
| 11 | - <image | 11 | + <image :src="defaultPoster" class="background-image" :mode="imageDisplayMode" /> |
| 12 | - :src="defaultPoster" | ||
| 13 | - class="background-image" | ||
| 14 | - :mode="imageDisplayMode" | ||
| 15 | - /> | ||
| 16 | 12 | ||
| 17 | <!-- 分享按钮组件 --> | 13 | <!-- 分享按钮组件 --> |
| 18 | <ShareButton | 14 | <ShareButton |
| ... | @@ -26,14 +22,26 @@ | ... | @@ -26,14 +22,26 @@ |
| 26 | <!-- 底部按钮区域 --> | 22 | <!-- 底部按钮区域 --> |
| 27 | <view class="bottom-section"> | 23 | <view class="bottom-section"> |
| 28 | <!-- 积分规则说明 --> | 24 | <!-- 积分规则说明 --> |
| 29 | - <view class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4 opacity-90"> | 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 | + > | ||
| 30 | <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> | 29 | <text class="text-blue-500 text-base font-medium block mb-2">积分规则说明:</text> |
| 31 | - <text class="text-blue-500 text-sm leading-relaxed block mb-1" style="padding-left: 20rpx; text-indent: -20rpx;">• 打卡任意1关,视为参与,奖励1000积分</text> | 30 | + <text |
| 32 | - <text class="text-blue-500 text-sm leading-relaxed block mb-1" style="padding-left: 20rpx; text-indent: -20rpx;">• 打卡任意7关,视为完成,奖励5000积分</text> | 31 | + v-for="(rule, index) in activityData.rules" |
| 33 | - <text class="text-blue-500 text-sm leading-relaxed block mb-1" style="padding-left: 20rpx; text-indent: -20rpx;">• 不需要区分打卡点的先后次序</text> | 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> | ||
| 34 | </view> | 38 | </view> |
| 35 | <!-- 未授权定位提示 - 仅在用户点击参加活动且未授权时显示 --> | 39 | <!-- 未授权定位提示 - 仅在用户点击参加活动且未授权时显示 --> |
| 36 | - <view v-if="showLocationPrompt && !hasLocationAuth && !locationError" class="location-tip" @click="retryGetLocation"> | 40 | + <view |
| 41 | + v-if="showLocationPrompt && !hasLocationAuth && !locationError" | ||
| 42 | + class="location-tip" | ||
| 43 | + @click="retryGetLocation" | ||
| 44 | + > | ||
| 37 | <view class="tip-content"> | 45 | <view class="tip-content"> |
| 38 | <view class="tip-icon">📍</view> | 46 | <view class="tip-icon">📍</view> |
| 39 | <view class="tip-text">点击获取您的位置信息来参与活动</view> | 47 | <view class="tip-text">点击获取您的位置信息来参与活动</view> |
| ... | @@ -42,7 +50,11 @@ | ... | @@ -42,7 +50,11 @@ |
| 42 | </view> | 50 | </view> |
| 43 | 51 | ||
| 44 | <!-- 位置获取失败提示 --> | 52 | <!-- 位置获取失败提示 --> |
| 45 | - <view v-if="hasLocationAuth && locationError" class="location-tip location-error" @click="retryGetLocation"> | 53 | + <view |
| 54 | + v-if="hasLocationAuth && locationError" | ||
| 55 | + class="location-tip location-error" | ||
| 56 | + @click="retryGetLocation" | ||
| 57 | + > | ||
| 46 | <view class="tip-content"> | 58 | <view class="tip-content"> |
| 47 | <view class="tip-icon">⚠️</view> | 59 | <view class="tip-icon">⚠️</view> |
| 48 | <view class="tip-text">可能是网络问题,获取位置信息失败</view> | 60 | <view class="tip-text">可能是网络问题,获取位置信息失败</view> |
| ... | @@ -54,7 +66,9 @@ | ... | @@ -54,7 +66,9 @@ |
| 54 | type="primary" | 66 | type="primary" |
| 55 | size="large" | 67 | size="large" |
| 56 | class="join-button" | 68 | class="join-button" |
| 57 | - :color="activityStatus.is_ended || !activityStatus.is_begin ? '#cccccc' : THEME_COLORS.PRIMARY" | 69 | + :color=" |
| 70 | + activityStatus.is_ended || !activityStatus.is_begin ? '#cccccc' : THEME_COLORS.PRIMARY | ||
| 71 | + " | ||
| 58 | :loading="isJoining || activityStatus.loading" | 72 | :loading="isJoining || activityStatus.loading" |
| 59 | :disabled="activityStatus.is_ended || !activityStatus.is_begin" | 73 | :disabled="activityStatus.is_ended || !activityStatus.is_begin" |
| 60 | @click="checkFamilyStatusAndJoinActivity" | 74 | @click="checkFamilyStatusAndJoinActivity" |
| ... | @@ -66,14 +80,8 @@ | ... | @@ -66,14 +80,8 @@ |
| 66 | <!-- 底部导航 --> | 80 | <!-- 底部导航 --> |
| 67 | <BottomNav /> | 81 | <BottomNav /> |
| 68 | 82 | ||
| 69 | - | ||
| 70 | - | ||
| 71 | <!-- 海报预览弹窗 --> | 83 | <!-- 海报预览弹窗 --> |
| 72 | - <nut-popup | 84 | + <nut-popup v-model:visible="show_post" position="center" class="poster-preview-popup"> |
| 73 | - v-model:visible="show_post" | ||
| 74 | - position="center" | ||
| 75 | - class="poster-preview-popup" | ||
| 76 | - > | ||
| 77 | <view class="wrapper"> | 85 | <view class="wrapper"> |
| 78 | <view class="preview-area" @click="onClickPost"> | 86 | <view class="preview-area" @click="onClickPost"> |
| 79 | <image v-if="posterPath" :src="posterPath" mode="widthFix" /> | 87 | <image v-if="posterPath" :src="posterPath" mode="widthFix" /> |
| ... | @@ -100,24 +108,16 @@ | ... | @@ -100,24 +108,16 @@ |
| 100 | /> | 108 | /> |
| 101 | 109 | ||
| 102 | <!-- 位置权限申请弹窗 --> | 110 | <!-- 位置权限申请弹窗 --> |
| 103 | - <nut-dialog | 111 | + <nut-dialog v-model:visible="showLocationDialog" title="位置权限申请"> |
| 104 | - v-model:visible="showLocationDialog" | ||
| 105 | - title="位置权限申请" | ||
| 106 | - > | ||
| 107 | <template #default> | 112 | <template #default> |
| 108 | - <view class=" text-gray-700 leading-loose text-sm text-left"> | 113 | + <view class="text-gray-700 leading-loose text-sm text-left"> |
| 109 | {{ locationContent }} | 114 | {{ locationContent }} |
| 110 | </view> | 115 | </view> |
| 111 | </template> | 116 | </template> |
| 112 | <template #footer> | 117 | <template #footer> |
| 113 | <nut-row :gutter="10"> | 118 | <nut-row :gutter="10"> |
| 114 | <nut-col :span="12"> | 119 | <nut-col :span="12"> |
| 115 | - <nut-button | 120 | + <nut-button @click="onLocationCancel" type="default" size="normal" block> |
| 116 | - @click="onLocationCancel" | ||
| 117 | - type="default" | ||
| 118 | - size="normal" | ||
| 119 | - block | ||
| 120 | - > | ||
| 121 | 暂不授权 | 121 | 暂不授权 |
| 122 | </nut-button> | 122 | </nut-button> |
| 123 | </nut-col> | 123 | </nut-col> |
| ... | @@ -139,60 +139,70 @@ | ... | @@ -139,60 +139,70 @@ |
| 139 | </template> | 139 | </template> |
| 140 | 140 | ||
| 141 | <script setup> | 141 | <script setup> |
| 142 | -import { ref, onMounted, computed } from "vue" | 142 | +import { ref, onMounted, computed } from 'vue' |
| 143 | import Taro, { useLoad } from '@tarojs/taro' | 143 | import Taro, { useLoad } from '@tarojs/taro' |
| 144 | -import "./index.less" | 144 | +import './index.less' |
| 145 | import BottomNav from '../../components/BottomNav.vue' | 145 | import BottomNav from '../../components/BottomNav.vue' |
| 146 | import PosterBuilder from '../../components/PosterBuilder/index.vue' | 146 | import PosterBuilder from '../../components/PosterBuilder/index.vue' |
| 147 | import ShareButton from '../../components/ShareButton/index.vue' | 147 | import ShareButton from '../../components/ShareButton/index.vue' |
| 148 | // 接口信息 | 148 | // 接口信息 |
| 149 | import { getMyFamiliesAPI } from '@/api/family' | 149 | import { getMyFamiliesAPI } from '@/api/family' |
| 150 | -import { getActivityStatusAPI } from '@/api/map' | 150 | +import { detailAPI } from '@/api/map_activity' |
| 151 | import { handleSharePageAuth, addShareFlag } from '@/utils/authRedirect' | 151 | import { handleSharePageAuth, addShareFlag } from '@/utils/authRedirect' |
| 152 | // 导入主题颜色 | 152 | // 导入主题颜色 |
| 153 | -import { THEME_COLORS } from '@/utils/config'; | 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' | ||
| 154 | 159 | ||
| 155 | // 默认海报图 | 160 | // 默认海报图 |
| 156 | -const defaultPoster = 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60'; | 161 | +const defaultPoster = ref( |
| 162 | + 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60' | ||
| 163 | +) | ||
| 157 | 164 | ||
| 158 | // 系统信息 | 165 | // 系统信息 |
| 159 | -const systemInfo = ref({}); | 166 | +const systemInfo = ref({}) |
| 167 | + | ||
| 168 | +// 活动ID(从 URL 参数获取) | ||
| 169 | +const activityId = ref('') | ||
| 160 | 170 | ||
| 161 | /** | 171 | /** |
| 162 | * 获取系统信息 | 172 | * 获取系统信息 |
| 163 | */ | 173 | */ |
| 164 | const getSystemInfo = () => { | 174 | const getSystemInfo = () => { |
| 165 | try { | 175 | try { |
| 166 | - const info = Taro.getWindowInfo(); | 176 | + const info = Taro.getWindowInfo() |
| 167 | - systemInfo.value = info; | 177 | + systemInfo.value = info |
| 168 | } catch (error) { | 178 | } catch (error) { |
| 169 | - console.error('获取系统信息失败:', error); | 179 | + console.error('获取系统信息失败:', error) |
| 170 | } | 180 | } |
| 171 | -}; | 181 | +} |
| 172 | 182 | ||
| 173 | /** | 183 | /** |
| 174 | * 检测是否为 iPad 类型设备 | 184 | * 检测是否为 iPad 类型设备 |
| 175 | */ | 185 | */ |
| 176 | const isTabletDevice = computed(() => { | 186 | const isTabletDevice = computed(() => { |
| 177 | if (!systemInfo.value.screenWidth) { | 187 | if (!systemInfo.value.screenWidth) { |
| 178 | - return false; | 188 | + return false |
| 179 | } | 189 | } |
| 180 | 190 | ||
| 181 | - const { screenWidth, screenHeight } = systemInfo.value; | 191 | + const { screenWidth, screenHeight } = systemInfo.value |
| 182 | - const screenRatio = screenWidth / screenHeight; | 192 | + const screenRatio = screenWidth / screenHeight |
| 183 | 193 | ||
| 184 | // iPad 类型设备通常屏幕比例在 0.7-0.8 之间(4:3 约为 0.75) | 194 | // iPad 类型设备通常屏幕比例在 0.7-0.8 之间(4:3 约为 0.75) |
| 185 | // 普通手机设备比例通常在 0.4-0.6 之间 | 195 | // 普通手机设备比例通常在 0.4-0.6 之间 |
| 186 | - return screenRatio > 0.65; | 196 | + return screenRatio > 0.65 |
| 187 | -}); | 197 | +}) |
| 188 | 198 | ||
| 189 | /** | 199 | /** |
| 190 | * 计算图片显示模式 | 200 | * 计算图片显示模式 |
| 191 | */ | 201 | */ |
| 192 | const imageDisplayMode = computed(() => { | 202 | const imageDisplayMode = computed(() => { |
| 193 | // iPad 类型设备使用 widthFix 模式,普通设备使用 aspectFill | 203 | // iPad 类型设备使用 widthFix 模式,普通设备使用 aspectFill |
| 194 | - return isTabletDevice.value ? 'widthFix' : 'aspectFill'; | 204 | + return isTabletDevice.value ? 'widthFix' : 'aspectFill' |
| 195 | -}); | 205 | +}) |
| 196 | 206 | ||
| 197 | /** | 207 | /** |
| 198 | * 活动海报页面组件 | 208 | * 活动海报页面组件 |
| ... | @@ -204,14 +214,14 @@ const hasLocationAuth = ref(false) // 是否已授权定位 | ... | @@ -204,14 +214,14 @@ const hasLocationAuth = ref(false) // 是否已授权定位 |
| 204 | const locationError = ref(false) // 位置获取是否失败 | 214 | const locationError = ref(false) // 位置获取是否失败 |
| 205 | const isJoining = ref(false) // 是否正在加入活动 | 215 | const isJoining = ref(false) // 是否正在加入活动 |
| 206 | const userLocation = ref({ lng: null, lat: null }) // 用户位置信息 | 216 | const userLocation = ref({ lng: null, lat: null }) // 用户位置信息 |
| 207 | -const hasJoinedFamily = ref(false); | 217 | +const hasJoinedFamily = ref(false) |
| 208 | const showLocationPrompt = ref(false) // 是否显示定位权限提示 | 218 | const showLocationPrompt = ref(false) // 是否显示定位权限提示 |
| 209 | 219 | ||
| 210 | // 活动状态相关 | 220 | // 活动状态相关 |
| 211 | const activityStatus = ref({ | 221 | const activityStatus = ref({ |
| 212 | is_begin: false, // 活动是否已开始 | 222 | is_begin: false, // 活动是否已开始 |
| 213 | is_ended: false, // 活动是否已结束 | 223 | is_ended: false, // 活动是否已结束 |
| 214 | - loading: false // 是否正在加载活动状态 | 224 | + loading: false, // 是否正在加载活动状态 |
| 215 | }) | 225 | }) |
| 216 | 226 | ||
| 217 | // Dialog 相关状态 | 227 | // Dialog 相关状态 |
| ... | @@ -219,7 +229,8 @@ const showLocationDialog = ref(false) // 是否显示位置权限申请弹窗 | ... | @@ -219,7 +229,8 @@ const showLocationDialog = ref(false) // 是否显示位置权限申请弹窗 |
| 219 | const pendingLocationCallback = ref(null) // 待执行的位置获取回调 | 229 | const pendingLocationCallback = ref(null) // 待执行的位置获取回调 |
| 220 | 230 | ||
| 221 | // 位置权限申请说明内容 | 231 | // 位置权限申请说明内容 |
| 222 | -const locationContent = '为了提供更好的活动体验,我们需要获取您的位置信息:验证您是否在活动区域内, 我们承诺严格保护您的位置隐私,仅用于活动相关功能。' | 232 | +const locationContent = |
| 233 | + '为了提供更好的活动体验,我们需要获取您的位置信息:验证您是否在活动区域内, 我们承诺严格保护您的位置隐私,仅用于活动相关功能。' | ||
| 223 | 234 | ||
| 224 | // 海报生成相关状态 | 235 | // 海报生成相关状态 |
| 225 | const show_post = ref(false) // 显示海报预览 | 236 | const show_post = ref(false) // 显示海报预览 |
| ... | @@ -230,13 +241,15 @@ const nickname = ref('老来赛用户') // 用户昵称 | ... | @@ -230,13 +241,15 @@ const nickname = ref('老来赛用户') // 用户昵称 |
| 230 | // const avatar = ref('https://cdn.ipadbiz.cn/icon/tou@2x.png') // 用户头像 | 241 | // const avatar = ref('https://cdn.ipadbiz.cn/icon/tou@2x.png') // 用户头像 |
| 231 | 242 | ||
| 232 | // 保存选项 | 243 | // 保存选项 |
| 233 | -const actions_save = ref([{ | 244 | +const actions_save = ref([ |
| 234 | - name: '保存至相册' | 245 | + { |
| 235 | -}]) | 246 | + name: '保存至相册', |
| 247 | + }, | ||
| 248 | +]) | ||
| 236 | 249 | ||
| 237 | // 海报配置 | 250 | // 海报配置 |
| 238 | -let base = {} | 251 | +const base = {} |
| 239 | -let qrcode_url = 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png' // Mock二维码 | 252 | +const qrcode_url = 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png' // Mock二维码 |
| 240 | 253 | ||
| 241 | // Mock活动数据 | 254 | // Mock活动数据 |
| 242 | const activityData = ref({ | 255 | const activityData = ref({ |
| ... | @@ -244,19 +257,20 @@ const activityData = ref({ | ... | @@ -244,19 +257,20 @@ const activityData = ref({ |
| 244 | subtitle: '探索城市魅力,感受时尚脉搏', | 257 | subtitle: '探索城市魅力,感受时尚脉搏', |
| 245 | dateRange: '2024年1月15日 - 2024年1月31日', | 258 | dateRange: '2024年1月15日 - 2024年1月31日', |
| 246 | posterUrl: 'https://img.yzcdn.cn/vant/cat.jpeg', // 临时使用示例图片 | 259 | posterUrl: 'https://img.yzcdn.cn/vant/cat.jpeg', // 临时使用示例图片 |
| 247 | - description: '漫步南京路,感受上海的繁华与历史交融。从外滩到人民广场,体验这座城市独特的魅力和时尚气息。', | 260 | + description: |
| 261 | + '漫步南京路,感受上海的繁华与历史交融。从外滩到人民广场,体验这座城市独特的魅力和时尚气息。', | ||
| 248 | rules: [ | 262 | rules: [ |
| 249 | '年满60岁的老年人可参与活动', | 263 | '年满60岁的老年人可参与活动', |
| 250 | '需要在指定时间内完成所有打卡点', | 264 | '需要在指定时间内完成所有打卡点', |
| 251 | '每个打卡点需上传照片验证', | 265 | '每个打卡点需上传照片验证', |
| 252 | - '完成全部打卡可获得电子勋章和积分奖励' | 266 | + '完成全部打卡可获得电子勋章和积分奖励', |
| 253 | ], | 267 | ], |
| 254 | rewards: [ | 268 | rewards: [ |
| 255 | '完成打卡获得500积分', | 269 | '完成打卡获得500积分', |
| 256 | '获得专属电子勋章', | 270 | '获得专属电子勋章', |
| 257 | '有机会获得商户优惠券', | 271 | '有机会获得商户优惠券', |
| 258 | - '参与月度积分排行榜' | 272 | + '参与月度积分排行榜', |
| 259 | - ] | 273 | + ], |
| 260 | }) | 274 | }) |
| 261 | 275 | ||
| 262 | // 分享配置 | 276 | // 分享配置 |
| ... | @@ -295,7 +309,7 @@ const getUserLocation = async (skipAuthCheck = false) => { | ... | @@ -295,7 +309,7 @@ const getUserLocation = async (skipAuthCheck = false) => { |
| 295 | 309 | ||
| 296 | // 如果没有授权,先显示数据用途说明 | 310 | // 如果没有授权,先显示数据用途说明 |
| 297 | if (hasLocationAuth !== true) { | 311 | if (hasLocationAuth !== true) { |
| 298 | - return new Promise((resolve) => { | 312 | + return new Promise(resolve => { |
| 299 | pendingLocationCallback.value = resolve | 313 | pendingLocationCallback.value = resolve |
| 300 | showLocationDialog.value = true | 314 | showLocationDialog.value = true |
| 301 | }) | 315 | }) |
| ... | @@ -306,12 +320,12 @@ const getUserLocation = async (skipAuthCheck = false) => { | ... | @@ -306,12 +320,12 @@ const getUserLocation = async (skipAuthCheck = false) => { |
| 306 | type: 'gcj02', | 320 | type: 'gcj02', |
| 307 | altitude: false, // 不需要海拔信息,提高获取速度 | 321 | altitude: false, // 不需要海拔信息,提高获取速度 |
| 308 | isHighAccuracy: true, // 开启高精度定位 | 322 | isHighAccuracy: true, // 开启高精度定位 |
| 309 | - highAccuracyExpireTime: 4000 // 高精度定位超时时间 | 323 | + highAccuracyExpireTime: 4000, // 高精度定位超时时间 |
| 310 | }) | 324 | }) |
| 311 | 325 | ||
| 312 | userLocation.value = { | 326 | userLocation.value = { |
| 313 | lng: location.longitude, | 327 | lng: location.longitude, |
| 314 | - lat: location.latitude | 328 | + lat: location.latitude, |
| 315 | } | 329 | } |
| 316 | 330 | ||
| 317 | console.log('获取到用户位置:', userLocation.value) | 331 | console.log('获取到用户位置:', userLocation.value) |
| ... | @@ -330,11 +344,11 @@ const getUserLocation = async (skipAuthCheck = false) => { | ... | @@ -330,11 +344,11 @@ const getUserLocation = async (skipAuthCheck = false) => { |
| 330 | title: '需要位置权限', | 344 | title: '需要位置权限', |
| 331 | content: '参与活动需要获取您的位置信息,请在设置中开启位置权限', | 345 | content: '参与活动需要获取您的位置信息,请在设置中开启位置权限', |
| 332 | confirmText: '去设置', | 346 | confirmText: '去设置', |
| 333 | - success: (res) => { | 347 | + success: res => { |
| 334 | if (res.confirm) { | 348 | if (res.confirm) { |
| 335 | Taro.openSetting() | 349 | Taro.openSetting() |
| 336 | } | 350 | } |
| 337 | - } | 351 | + }, |
| 338 | }) | 352 | }) |
| 339 | } else if (error.errMsg && error.errMsg.includes('timeout')) { | 353 | } else if (error.errMsg && error.errMsg.includes('timeout')) { |
| 340 | // 定位超时 | 354 | // 定位超时 |
| ... | @@ -342,7 +356,7 @@ const getUserLocation = async (skipAuthCheck = false) => { | ... | @@ -342,7 +356,7 @@ const getUserLocation = async (skipAuthCheck = false) => { |
| 342 | Taro.showToast({ | 356 | Taro.showToast({ |
| 343 | title: '定位超时,请检查网络或GPS', | 357 | title: '定位超时,请检查网络或GPS', |
| 344 | icon: 'none', | 358 | icon: 'none', |
| 345 | - duration: 3000 | 359 | + duration: 3000, |
| 346 | }) | 360 | }) |
| 347 | } else if (error.errMsg && error.errMsg.includes('fail')) { | 361 | } else if (error.errMsg && error.errMsg.includes('fail')) { |
| 348 | // 定位失败,可能是GPS关闭或网络问题 | 362 | // 定位失败,可能是GPS关闭或网络问题 |
| ... | @@ -351,14 +365,14 @@ const getUserLocation = async (skipAuthCheck = false) => { | ... | @@ -351,14 +365,14 @@ const getUserLocation = async (skipAuthCheck = false) => { |
| 351 | title: '定位失败', | 365 | title: '定位失败', |
| 352 | content: '请确保已开启GPS定位服务,并检查网络连接是否正常', | 366 | content: '请确保已开启GPS定位服务,并检查网络连接是否正常', |
| 353 | showCancel: false, | 367 | showCancel: false, |
| 354 | - confirmText: '我知道了' | 368 | + confirmText: '我知道了', |
| 355 | }) | 369 | }) |
| 356 | } else { | 370 | } else { |
| 357 | // 其他未知错误 | 371 | // 其他未知错误 |
| 358 | locationError.value = true | 372 | locationError.value = true |
| 359 | Taro.showToast({ | 373 | Taro.showToast({ |
| 360 | title: '获取位置失败,请重试', | 374 | title: '获取位置失败,请重试', |
| 361 | - icon: 'none' | 375 | + icon: 'none', |
| 362 | }) | 376 | }) |
| 363 | } | 377 | } |
| 364 | 378 | ||
| ... | @@ -412,7 +426,7 @@ const checkFamilyStatusAndJoinActivity = async () => { | ... | @@ -412,7 +426,7 @@ const checkFamilyStatusAndJoinActivity = async () => { |
| 412 | if (activityStatus.value.is_ended) { | 426 | if (activityStatus.value.is_ended) { |
| 413 | Taro.showToast({ | 427 | Taro.showToast({ |
| 414 | title: '活动已结束', | 428 | title: '活动已结束', |
| 415 | - icon: 'none' | 429 | + icon: 'none', |
| 416 | }) | 430 | }) |
| 417 | return | 431 | return |
| 418 | } | 432 | } |
| ... | @@ -421,7 +435,7 @@ const checkFamilyStatusAndJoinActivity = async () => { | ... | @@ -421,7 +435,7 @@ const checkFamilyStatusAndJoinActivity = async () => { |
| 421 | if (!activityStatus.value.is_begin) { | 435 | if (!activityStatus.value.is_begin) { |
| 422 | Taro.showToast({ | 436 | Taro.showToast({ |
| 423 | title: '活动尚未开始,请耐心等待', | 437 | title: '活动尚未开始,请耐心等待', |
| 424 | - icon: 'none' | 438 | + icon: 'none', |
| 425 | }) | 439 | }) |
| 426 | return | 440 | return |
| 427 | } | 441 | } |
| ... | @@ -433,14 +447,14 @@ const checkFamilyStatusAndJoinActivity = async () => { | ... | @@ -433,14 +447,14 @@ const checkFamilyStatusAndJoinActivity = async () => { |
| 433 | content: '没有加入家庭是无法参加活动的', | 447 | content: '没有加入家庭是无法参加活动的', |
| 434 | cancelText: '关闭', | 448 | cancelText: '关闭', |
| 435 | confirmText: '前往加入', | 449 | confirmText: '前往加入', |
| 436 | - success: (res) => { | 450 | + success: res => { |
| 437 | if (res.confirm) { | 451 | if (res.confirm) { |
| 438 | Taro.redirectTo({ | 452 | Taro.redirectTo({ |
| 439 | url: '/pages/Welcome/index', | 453 | url: '/pages/Welcome/index', |
| 440 | - }); | 454 | + }) |
| 441 | } | 455 | } |
| 442 | }, | 456 | }, |
| 443 | - }); | 457 | + }) |
| 444 | return | 458 | return |
| 445 | } | 459 | } |
| 446 | 460 | ||
| ... | @@ -459,8 +473,7 @@ const checkFamilyStatusAndJoinActivity = async () => { | ... | @@ -459,8 +473,7 @@ const checkFamilyStatusAndJoinActivity = async () => { |
| 459 | 473 | ||
| 460 | // 正常参加活动流程 | 474 | // 正常参加活动流程 |
| 461 | await handleJoinActivity() | 475 | await handleJoinActivity() |
| 462 | -}; | 476 | +} |
| 463 | - | ||
| 464 | 477 | ||
| 465 | /** | 478 | /** |
| 466 | * 重新获取位置信息 | 479 | * 重新获取位置信息 |
| ... | @@ -473,7 +486,7 @@ const retryGetLocation = async () => { | ... | @@ -473,7 +486,7 @@ const retryGetLocation = async () => { |
| 473 | locationError.value = false | 486 | locationError.value = false |
| 474 | Taro.showToast({ | 487 | Taro.showToast({ |
| 475 | title: '位置获取成功', | 488 | title: '位置获取成功', |
| 476 | - icon: 'success' | 489 | + icon: 'success', |
| 477 | }) | 490 | }) |
| 478 | } | 491 | } |
| 479 | } catch (error) { | 492 | } catch (error) { |
| ... | @@ -489,7 +502,7 @@ const onLocationCancel = () => { | ... | @@ -489,7 +502,7 @@ const onLocationCancel = () => { |
| 489 | if (pendingLocationCallback.value) { | 502 | if (pendingLocationCallback.value) { |
| 490 | Taro.showToast({ | 503 | Taro.showToast({ |
| 491 | title: '需要位置权限才能参与活动', | 504 | title: '需要位置权限才能参与活动', |
| 492 | - icon: 'none' | 505 | + icon: 'none', |
| 493 | }) | 506 | }) |
| 494 | pendingLocationCallback.value(false) | 507 | pendingLocationCallback.value(false) |
| 495 | pendingLocationCallback.value = null | 508 | pendingLocationCallback.value = null |
| ... | @@ -507,12 +520,12 @@ const onLocationConfirm = async () => { | ... | @@ -507,12 +520,12 @@ const onLocationConfirm = async () => { |
| 507 | type: 'gcj02', | 520 | type: 'gcj02', |
| 508 | altitude: false, | 521 | altitude: false, |
| 509 | isHighAccuracy: true, | 522 | isHighAccuracy: true, |
| 510 | - highAccuracyExpireTime: 4000 | 523 | + highAccuracyExpireTime: 4000, |
| 511 | }) | 524 | }) |
| 512 | 525 | ||
| 513 | userLocation.value = { | 526 | userLocation.value = { |
| 514 | lng: location.longitude, | 527 | lng: location.longitude, |
| 515 | - lat: location.latitude | 528 | + lat: location.latitude, |
| 516 | } | 529 | } |
| 517 | 530 | ||
| 518 | console.log('获取到用户位置:', userLocation.value) | 531 | console.log('获取到用户位置:', userLocation.value) |
| ... | @@ -567,14 +580,13 @@ const handleJoinActivity = async () => { | ... | @@ -567,14 +580,13 @@ const handleJoinActivity = async () => { |
| 567 | 580 | ||
| 568 | // 跳转到Activities页面,并传递位置参数 | 581 | // 跳转到Activities页面,并传递位置参数 |
| 569 | await Taro.navigateTo({ | 582 | await Taro.navigateTo({ |
| 570 | - url: `/pages/Activities/index?current_lng=${userLocation.value.lng}¤t_lat=${userLocation.value.lat}` | 583 | + url: `/pages/Activities/index?current_lng=${userLocation.value.lng}¤t_lat=${userLocation.value.lat}`, |
| 571 | }) | 584 | }) |
| 572 | - | ||
| 573 | } catch (error) { | 585 | } catch (error) { |
| 574 | console.error('参加活动失败:', error) | 586 | console.error('参加活动失败:', error) |
| 575 | Taro.showToast({ | 587 | Taro.showToast({ |
| 576 | title: '参加活动失败', | 588 | title: '参加活动失败', |
| 577 | - icon: 'none' | 589 | + icon: 'none', |
| 578 | }) | 590 | }) |
| 579 | } finally { | 591 | } finally { |
| 580 | isJoining.value = false | 592 | isJoining.value = false |
| ... | @@ -602,12 +614,12 @@ const onShareAppMessage = () => { | ... | @@ -602,12 +614,12 @@ const onShareAppMessage = () => { |
| 602 | return { | 614 | return { |
| 603 | title: '主题路线打卡活动等你参与', | 615 | title: '主题路线打卡活动等你参与', |
| 604 | path: addShareFlag('/pages/ActivitiesCover/index'), | 616 | path: addShareFlag('/pages/ActivitiesCover/index'), |
| 605 | - success: (res) => { | 617 | + success: res => { |
| 606 | // 分享成功 | 618 | // 分享成功 |
| 607 | }, | 619 | }, |
| 608 | - fail: (err) => { | 620 | + fail: err => { |
| 609 | // 分享失败 | 621 | // 分享失败 |
| 610 | - } | 622 | + }, |
| 611 | } | 623 | } |
| 612 | } | 624 | } |
| 613 | 625 | ||
| ... | @@ -644,7 +656,7 @@ const onCancelSave = () => { | ... | @@ -644,7 +656,7 @@ const onCancelSave = () => { |
| 644 | /** | 656 | /** |
| 645 | * 选择保存方式 | 657 | * 选择保存方式 |
| 646 | */ | 658 | */ |
| 647 | -const onSelectSave = (item) => { | 659 | +const onSelectSave = item => { |
| 648 | if (item.name === '保存至相册') { | 660 | if (item.name === '保存至相册') { |
| 649 | show_save.value = false | 661 | show_save.value = false |
| 650 | show_post.value = false | 662 | show_post.value = false |
| ... | @@ -872,7 +884,7 @@ const onSelectSave = (item) => { | ... | @@ -872,7 +884,7 @@ const onSelectSave = (item) => { |
| 872 | /** | 884 | /** |
| 873 | * 海报绘制成功回调 | 885 | * 海报绘制成功回调 |
| 874 | */ | 886 | */ |
| 875 | -const drawSuccess = (result) => { | 887 | +const drawSuccess = result => { |
| 876 | console.log('绘制成功', result) | 888 | console.log('绘制成功', result) |
| 877 | const { tempFilePath, errMsg } = result | 889 | const { tempFilePath, errMsg } = result |
| 878 | if (errMsg === 'canvasToTempFilePath:ok') { | 890 | if (errMsg === 'canvasToTempFilePath:ok') { |
| ... | @@ -883,7 +895,7 @@ const drawSuccess = (result) => { | ... | @@ -883,7 +895,7 @@ const drawSuccess = (result) => { |
| 883 | Taro.showToast({ | 895 | Taro.showToast({ |
| 884 | title: '生成失败,请稍后重试', | 896 | title: '生成失败,请稍后重试', |
| 885 | icon: 'none', | 897 | icon: 'none', |
| 886 | - duration: 2500 | 898 | + duration: 2500, |
| 887 | }) | 899 | }) |
| 888 | } | 900 | } |
| 889 | } | 901 | } |
| ... | @@ -891,13 +903,13 @@ const drawSuccess = (result) => { | ... | @@ -891,13 +903,13 @@ const drawSuccess = (result) => { |
| 891 | /** | 903 | /** |
| 892 | * 海报绘制失败回调 | 904 | * 海报绘制失败回调 |
| 893 | */ | 905 | */ |
| 894 | -const drawFail = (result) => { | 906 | +const drawFail = result => { |
| 895 | console.log('绘制失败', result) | 907 | console.log('绘制失败', result) |
| 896 | Taro.hideLoading() | 908 | Taro.hideLoading() |
| 897 | Taro.showToast({ | 909 | Taro.showToast({ |
| 898 | title: '生成失败,请稍后重试', | 910 | title: '生成失败,请稍后重试', |
| 899 | icon: 'none', | 911 | icon: 'none', |
| 900 | - duration: 2500 | 912 | + duration: 2500, |
| 901 | }) | 913 | }) |
| 902 | } | 914 | } |
| 903 | 915 | ||
| ... | @@ -911,20 +923,101 @@ const savePoster = () => { | ... | @@ -911,20 +923,101 @@ const savePoster = () => { |
| 911 | Taro.showToast({ | 923 | Taro.showToast({ |
| 912 | title: '已保存到相册', | 924 | title: '已保存到相册', |
| 913 | icon: 'success', | 925 | icon: 'success', |
| 914 | - duration: 2000 | 926 | + duration: 2000, |
| 915 | }) | 927 | }) |
| 916 | }, | 928 | }, |
| 917 | fail() { | 929 | fail() { |
| 918 | Taro.showToast({ | 930 | Taro.showToast({ |
| 919 | title: '保存失败', | 931 | title: '保存失败', |
| 920 | icon: 'none', | 932 | icon: 'none', |
| 921 | - duration: 2000 | 933 | + duration: 2000, |
| 922 | }) | 934 | }) |
| 923 | - } | 935 | + }, |
| 924 | }) | 936 | }) |
| 925 | } | 937 | } |
| 926 | 938 | ||
| 927 | /** | 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 | +/** | ||
| 928 | * 初始化页面数据 | 1021 | * 初始化页面数据 |
| 929 | */ | 1022 | */ |
| 930 | const initPageData = async () => { | 1023 | const initPageData = async () => { |
| ... | @@ -937,56 +1030,38 @@ const initPageData = async () => { | ... | @@ -937,56 +1030,38 @@ const initPageData = async () => { |
| 937 | } | 1030 | } |
| 938 | } | 1031 | } |
| 939 | 1032 | ||
| 940 | - // 获取活动状态 | 1033 | + // 获取活动详情(包含活动状态) |
| 941 | - await fetchActivityStatus() | 1034 | + await fetchActivityDetail() |
| 942 | 1035 | ||
| 943 | // 检查定位授权状态(不获取位置,只检查权限) | 1036 | // 检查定位授权状态(不获取位置,只检查权限) |
| 944 | await checkLocationAuth() | 1037 | await checkLocationAuth() |
| 945 | } | 1038 | } |
| 946 | 1039 | ||
| 947 | -/** | 1040 | +// 处理页面加载时的授权检查 |
| 948 | - * 获取活动状态 | 1041 | +useLoad(options => { |
| 949 | - */ | 1042 | + console.log('[ActivitiesCover] 页面加载, 参数:', options) |
| 950 | -const fetchActivityStatus = async () => { | 1043 | + |
| 951 | - try { | 1044 | + // 获取活动 ID(如果有) |
| 952 | - activityStatus.value.loading = true | 1045 | + if (options.id) { |
| 953 | - const { code, data } = await getActivityStatusAPI() | 1046 | + activityId.value = options.id |
| 954 | - | 1047 | + } else if (options.activity_id) { |
| 955 | - if (code === 1 && data) { | 1048 | + activityId.value = options.activity_id |
| 956 | - activityStatus.value.is_begin = Boolean(data.is_begin) | ||
| 957 | - activityStatus.value.is_ended = Boolean(data.is_ended) | ||
| 958 | - console.log('活动状态:', { | ||
| 959 | - is_begin: activityStatus.value.is_begin ? '已开始' : '未开始', | ||
| 960 | - is_ended: activityStatus.value.is_ended ? '已结束' : '进行中' | ||
| 961 | - }) | ||
| 962 | } else { | 1049 | } else { |
| 963 | - console.warn('获取活动状态失败:', data) | 1050 | + // 如果没有活动ID,使用默认ID |
| 964 | - // 默认认为活动未开始且未结束,避免影响用户体验 | 1051 | + activityId.value = '1' |
| 965 | - activityStatus.value.is_begin = false | 1052 | + console.warn('[ActivitiesCover] 未提供活动ID,使用默认ID: 1') |
| 966 | - activityStatus.value.is_ended = false | ||
| 967 | } | 1053 | } |
| 968 | - } catch (error) { | ||
| 969 | - console.error('获取活动状态异常:', error) | ||
| 970 | - // 默认认为活动未开始且未结束,避免影响用户体验 | ||
| 971 | - activityStatus.value.is_begin = false | ||
| 972 | - activityStatus.value.is_ended = false | ||
| 973 | - } finally { | ||
| 974 | - activityStatus.value.loading = false | ||
| 975 | - } | ||
| 976 | -} | ||
| 977 | 1054 | ||
| 978 | -// 处理页面加载时的授权检查 | ||
| 979 | -useLoad((options) => { | ||
| 980 | // 处理分享页面的授权逻辑 | 1055 | // 处理分享页面的授权逻辑 |
| 981 | handleSharePageAuth(options, () => { | 1056 | handleSharePageAuth(options, () => { |
| 982 | - initPageData(); | 1057 | + initPageData() |
| 983 | - }); | 1058 | + }) |
| 984 | -}); | 1059 | +}) |
| 985 | 1060 | ||
| 986 | // 页面挂载时检查定位授权状态 | 1061 | // 页面挂载时检查定位授权状态 |
| 987 | onMounted(async () => { | 1062 | onMounted(async () => { |
| 988 | // 获取系统信息 | 1063 | // 获取系统信息 |
| 989 | - getSystemInfo(); | 1064 | + getSystemInfo() |
| 990 | 1065 | ||
| 991 | initPageData() | 1066 | initPageData() |
| 992 | }) | 1067 | }) |
| ... | @@ -994,6 +1069,6 @@ onMounted(async () => { | ... | @@ -994,6 +1069,6 @@ onMounted(async () => { |
| 994 | 1069 | ||
| 995 | <script> | 1070 | <script> |
| 996 | export default { | 1071 | export default { |
| 997 | - name: "ActivitiesCover", | 1072 | + name: 'ActivitiesCover', |
| 998 | -}; | 1073 | +} |
| 999 | </script> | 1074 | </script> | ... | ... |
-
Please register or login to post a comment