feat(海报打卡): 添加加载状态和错误处理,集成API获取海报数据
实现海报打卡页面的加载状态和错误处理逻辑 集成API获取海报详情数据并更新页面状态 添加重新加载功能,优化用户体验 处理海报背景图片上传和保存逻辑
Showing
1 changed file
with
191 additions
and
82 deletions
| 1 | <template> | 1 | <template> |
| 2 | <view class="poster-checkin-page bg-gray-50 h-screen flex flex-col"> | 2 | <view class="poster-checkin-page bg-gray-50 h-screen flex flex-col"> |
| 3 | + <!-- 加载状态 --> | ||
| 4 | + <view v-if="isLoading" class="flex-1 flex items-center justify-center"> | ||
| 5 | + <view class="text-center"> | ||
| 6 | + <view class="text-gray-500 mb-2">加载中...</view> | ||
| 7 | + </view> | ||
| 8 | + </view> | ||
| 9 | + | ||
| 10 | + <!-- 错误状态 --> | ||
| 11 | + <view v-else-if="apiError" class="flex-1 flex items-center justify-center"> | ||
| 12 | + <view class="text-center"> | ||
| 13 | + <view class="text-red-500 mb-2">{{ apiError }}</view> | ||
| 14 | + <view @tap="fetchPosterDetail" class="bg-blue-500 text-white px-4 py-2 rounded"> | ||
| 15 | + 重新加载 | ||
| 16 | + </view> | ||
| 17 | + </view> | ||
| 18 | + </view> | ||
| 19 | + | ||
| 20 | + <!-- 正常内容 --> | ||
| 21 | + <template v-else> | ||
| 3 | <!-- 活动信息区域 --> | 22 | <!-- 活动信息区域 --> |
| 4 | <view v-if="pageState === 'normal' || pageState === 'no-checkin'" class="bg-white mx-4 mt-4 mb-2 rounded-lg shadow-sm p-4"> | 23 | <view v-if="pageState === 'normal' || pageState === 'no-checkin'" class="bg-white mx-4 mt-4 mb-2 rounded-lg shadow-sm p-4"> |
| 5 | <!-- 活动主题 --> | 24 | <!-- 活动主题 --> |
| ... | @@ -97,6 +116,7 @@ | ... | @@ -97,6 +116,7 @@ |
| 97 | </view> | 116 | </view> |
| 98 | </view> | 117 | </view> |
| 99 | </view> | 118 | </view> |
| 119 | + </template> | ||
| 100 | 120 | ||
| 101 | <!-- 海报生成组件 - 仅在正常状态显示 --> | 121 | <!-- 海报生成组件 - 仅在正常状态显示 --> |
| 102 | <PosterBuilder | 122 | <PosterBuilder |
| ... | @@ -126,7 +146,7 @@ import { Left, Right } from '@nutui/icons-vue-taro' | ... | @@ -126,7 +146,7 @@ import { Left, Right } from '@nutui/icons-vue-taro' |
| 126 | import PosterBuilder from '@/components/PosterBuilder/index.vue' | 146 | import PosterBuilder from '@/components/PosterBuilder/index.vue' |
| 127 | import BASE_URL from '@/utils/config' | 147 | import BASE_URL from '@/utils/config' |
| 128 | // 导入获取海报详情的API | 148 | // 导入获取海报详情的API |
| 129 | -import { getPosterDetailAPI } from '@/api/map' | 149 | +import { getPosterDetailAPI, savePosterBackgroundAPI } from '@/api/map' |
| 130 | // 默认背景图 | 150 | // 默认背景图 |
| 131 | const defaultBackground = 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF%E5%9B%BE1.png' | 151 | const defaultBackground = 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF%E5%9B%BE1.png' |
| 132 | 152 | ||
| ... | @@ -145,86 +165,158 @@ const pageParams = ref({ | ... | @@ -145,86 +165,158 @@ const pageParams = ref({ |
| 145 | id: '', | 165 | id: '', |
| 146 | }) | 166 | }) |
| 147 | 167 | ||
| 168 | +// API数据状态 | ||
| 169 | +const apiData = ref(null) | ||
| 170 | +const isLoading = ref(false) | ||
| 171 | +const apiError = ref(null) | ||
| 172 | + | ||
| 148 | // TODO: 有真实数据的时候, 通过ID获取具体地点的海报信息, 赋值currentPosterIndex获取特定的海报 | 173 | // TODO: 有真实数据的时候, 通过ID获取具体地点的海报信息, 赋值currentPosterIndex获取特定的海报 |
| 149 | 174 | ||
| 150 | // 图片预览相关 | 175 | // 图片预览相关 |
| 151 | const previewVisible = ref(false) | 176 | const previewVisible = ref(false) |
| 152 | const previewImages = ref([]) | 177 | const previewImages = ref([]) |
| 153 | 178 | ||
| 154 | -// 活动信息数据 - 模拟不同状态,实际使用时从API获取 | 179 | +/** |
| 155 | -const activityInfo = ref({ | 180 | + * 获取海报详情数据 |
| 156 | - title: '南京路乐龄时尚消费主题路线', | 181 | + */ |
| 157 | - checkPoints: [ | 182 | +const fetchPosterDetail = async () => { |
| 158 | - { id: 1, name: '起点签到', completed: true }, | 183 | + try { |
| 159 | - { id: 2, name: '商圈探索', completed: true }, | 184 | + isLoading.value = true |
| 160 | - { id: 3, name: '文化体验', completed: true }, | 185 | + apiError.value = null |
| 161 | - { id: 4, name: '美食品鉴', completed: false }, | ||
| 162 | - { id: 5, name: '终点打卡', completed: false } | ||
| 163 | - ], | ||
| 164 | - completedCount: 3, | ||
| 165 | - totalCount: 5, | ||
| 166 | - endDate: '2025年9月7日' | ||
| 167 | -}) | ||
| 168 | 186 | ||
| 169 | -// activityInfo.value = {}; | 187 | + const response = await getPosterDetailAPI({}) |
| 170 | 188 | ||
| 171 | -// 海报数据列表 - 合并海报基础信息和内容数据,实际使用时从API获取 | 189 | + if (response.code === 1) { |
| 172 | -const posterList = ref([ | 190 | + apiData.value = response.data |
| 173 | - { | 191 | + console.log('获取海报详情成功:', response.data) |
| 174 | - id: 1, | 192 | + |
| 175 | - title: '起点签到', | 193 | + // 根据API数据更新活动信息 |
| 176 | - path: '', | 194 | + updateActivityInfo() |
| 177 | - checkPointId: 1, | 195 | + |
| 178 | - backgroundImage: 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF%E5%9B%BE1.png', | 196 | + // 根据API数据更新海报列表 |
| 179 | - // 海报内容数据 | 197 | + updatePosterList() |
| 180 | - user: { | 198 | + |
| 181 | - avatar: 'https://cdn.ipadbiz.cn/icon/tou@2x.png', | 199 | + // 根据pageParams.id设置当前海报索引 |
| 182 | - nickname: '张大爷' | 200 | + setCurrentPosterIndex() |
| 183 | - }, | 201 | + |
| 184 | - family: { | 202 | + } else { |
| 185 | - name: '幸福之家', | 203 | + apiError.value = response.msg || '获取海报详情失败' |
| 186 | - description: '一家人整整齐齐最重要' | 204 | + console.error('获取海报详情失败:', response.msg) |
| 187 | - }, | 205 | + } |
| 188 | - activity: { | 206 | + } catch (error) { |
| 189 | - logo: 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%B7%A6%E4%B8%8A%E8%A7%92logo.png', | 207 | + apiError.value = '网络请求失败' |
| 190 | - name: '南京路时尚citywalk' | 208 | + console.error('获取海报详情异常:', error) |
| 191 | - }, | 209 | + } finally { |
| 192 | - level: { | 210 | + isLoading.value = false |
| 193 | - logo: 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%8F%B3%E4%B8%8B%E8%A7%92icon.png', | 211 | + } |
| 194 | - name: '卡点: 上海市第一百货商店' | 212 | +} |
| 195 | - }, | 213 | + |
| 196 | - qrcode: 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png', | 214 | +/** |
| 197 | - qrcodeDesc: '长按识别,来,我们一起打卡!' | 215 | + * 根据API数据更新活动信息 |
| 198 | - }, | 216 | + */ |
| 199 | - { | 217 | +const updateActivityInfo = () => { |
| 200 | - id: 2, | 218 | + if (!apiData.value) return |
| 201 | - title: '商圈探索', | 219 | + |
| 220 | + const { title, end_date, details, show_detail_index } = apiData.value | ||
| 221 | + | ||
| 222 | + // 转换details为checkPoints格式 | ||
| 223 | + const checkPoints = details.map((detail, index) => ({ | ||
| 224 | + id: detail.id, | ||
| 225 | + name: detail.name, | ||
| 226 | + completed: detail.is_checked === true | ||
| 227 | + })) | ||
| 228 | + | ||
| 229 | + // 计算已完成数量 | ||
| 230 | + const completedCount = checkPoints.filter(point => point.completed).length | ||
| 231 | + | ||
| 232 | + activityInfo.value = { | ||
| 233 | + title: title || '海报打卡活动', | ||
| 234 | + checkPoints, | ||
| 235 | + completedCount, | ||
| 236 | + totalCount: checkPoints.length, | ||
| 237 | + endDate: end_date || '', | ||
| 238 | + showDetailIndex: show_detail_index || 0 | ||
| 239 | + } | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +/** | ||
| 243 | + * 根据API数据更新海报列表 | ||
| 244 | + */ | ||
| 245 | +const updatePosterList = () => { | ||
| 246 | + if (!apiData.value) return | ||
| 247 | + | ||
| 248 | + const { details, family, qrcode_url } = apiData.value | ||
| 249 | + | ||
| 250 | + // 只显示is_checked为真的关卡 | ||
| 251 | + const checkedDetails = details.filter(detail => | ||
| 252 | + detail.is_checked === true | ||
| 253 | + ) | ||
| 254 | + | ||
| 255 | + posterList.value = checkedDetails.map((detail, index) => ({ | ||
| 256 | + id: detail.id, | ||
| 257 | + title: detail.name, | ||
| 202 | path: '', | 258 | path: '', |
| 203 | - checkPointId: 2, | 259 | + checkPointId: detail.id, |
| 204 | - backgroundImage: 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%B3%E5%8D%A12-%E5%9B%BE%E5%B1%82%201.png', | 260 | + backgroundImage: detail.background_url || defaultBackground, |
| 205 | // 海报内容数据 | 261 | // 海报内容数据 |
| 206 | user: { | 262 | user: { |
| 207 | - avatar: 'https://cdn.ipadbiz.cn/icon/tou@2x.png', | 263 | + avatar: family?.avatar_url || 'https://cdn.ipadbiz.cn/icon/tou@2x.png', // 默认头像,后续可从用户信息获取 |
| 208 | - nickname: '李奶奶' | 264 | + nickname: '用户昵称' // 默认昵称,后续可从用户信息获取 |
| 209 | }, | 265 | }, |
| 210 | family: { | 266 | family: { |
| 211 | - name: '温馨小家', | 267 | + name: family?.name || '我的家庭', |
| 212 | - description: '健康快乐每一天' | 268 | + description: '' |
| 213 | }, | 269 | }, |
| 214 | activity: { | 270 | activity: { |
| 215 | - logo: 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%B7%A6%E4%B8%8A%E8%A7%92logo.png', | 271 | + logo: detail.main_slogan || 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%B7%A6%E4%B8%8A%E8%A7%92logo.png', |
| 216 | - name: '南京路时尚citywalk' | ||
| 217 | }, | 272 | }, |
| 218 | level: { | 273 | level: { |
| 219 | - logo: 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%B3%E5%8D%A12-%E4%BB%8A%E6%9C%9D%E8%B7%9F%E6%97%81%E5%8F%8B%20%E4%B8%80%E9%81%93%E6%9D%A5%E4%B9%B0%E4%B9%B0%E4%B9%B0_1.png', | 274 | + logo: detail.sub_slogan || 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%8F%B3%E4%B8%8B%E8%A7%92icon.png', |
| 220 | - name: '第二关卡' | 275 | + name: detail.name || '海报打卡活动', |
| 221 | }, | 276 | }, |
| 222 | - qrcode: 'https://cdn.ipadbiz.cn/space/068a790496c87cb8d2ed6e551401c544.png', | 277 | + qrcode: qrcode_url, |
| 223 | qrcodeDesc: '长按识别,来,我们一起打卡!' | 278 | qrcodeDesc: '长按识别,来,我们一起打卡!' |
| 224 | - }, | 279 | + })) |
| 225 | -]) | 280 | +} |
| 281 | + | ||
| 282 | +/** | ||
| 283 | + * 根据pageParams.id设置当前海报索引 | ||
| 284 | + */ | ||
| 285 | +const setCurrentPosterIndex = () => { | ||
| 286 | + if (!posterList.value.length) { | ||
| 287 | + currentPosterIndex.value = 0 | ||
| 288 | + return | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + // 如果有指定的ID,查找对应的海报索引 | ||
| 292 | + if (pageParams.value.id) { | ||
| 293 | + const targetIndex = posterList.value.findIndex(poster => | ||
| 294 | + poster.checkPointId.toString() === pageParams.value.id.toString() | ||
| 295 | + ) | ||
| 296 | + | ||
| 297 | + if (targetIndex !== -1) { | ||
| 298 | + currentPosterIndex.value = targetIndex | ||
| 299 | + return | ||
| 300 | + } | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + // 如果没有ID或没找到对应的关卡ID,默认显示第一个已打卡的项(索引0) | ||
| 304 | + // 因为posterList已经过滤了is_checked=true的项,所以第一个就是我们要的 | ||
| 305 | + currentPosterIndex.value = 0 | ||
| 306 | +} | ||
| 226 | 307 | ||
| 227 | -// posterList.value = [] | 308 | +// 活动信息数据 - 将由API数据填充 |
| 309 | +const activityInfo = ref({ | ||
| 310 | + title: '', | ||
| 311 | + checkPoints: [], | ||
| 312 | + completedCount: 0, | ||
| 313 | + totalCount: 0, | ||
| 314 | + endDate: '', | ||
| 315 | + showDetailIndex: 0 | ||
| 316 | +}) | ||
| 317 | + | ||
| 318 | +// 海报数据列表 - 将由API数据填充 | ||
| 319 | +const posterList = ref([]) | ||
| 228 | 320 | ||
| 229 | // 数据状态检查 | 321 | // 数据状态检查 |
| 230 | const hasActivityInfo = computed(() => { | 322 | const hasActivityInfo = computed(() => { |
| ... | @@ -416,7 +508,7 @@ const posterConfig = computed(() => { | ... | @@ -416,7 +508,7 @@ const posterConfig = computed(() => { |
| 416 | /** | 508 | /** |
| 417 | * 页面加载时初始化 | 509 | * 页面加载时初始化 |
| 418 | */ | 510 | */ |
| 419 | -onMounted(() => { | 511 | +onMounted(async () => { |
| 420 | Taro.setNavigationBarTitle({ title: '海报打卡' }) | 512 | Taro.setNavigationBarTitle({ title: '海报打卡' }) |
| 421 | 513 | ||
| 422 | // 获取页面参数 | 514 | // 获取页面参数 |
| ... | @@ -429,6 +521,16 @@ onMounted(() => { | ... | @@ -429,6 +521,16 @@ onMounted(() => { |
| 429 | 521 | ||
| 430 | console.log('海报打卡页面接收到的参数:', pageParams.value) | 522 | console.log('海报打卡页面接收到的参数:', pageParams.value) |
| 431 | 523 | ||
| 524 | + // 获取海报详情数据 | ||
| 525 | + await fetchPosterDetail() | ||
| 526 | + | ||
| 527 | + // 数据获取完成后检查页面状态 | ||
| 528 | + if (pageState.value === 'no-activity') { | ||
| 529 | + // 没有活动信息,显示确认对话框 | ||
| 530 | + showNoActivityConfirm() | ||
| 531 | + return | ||
| 532 | + } | ||
| 533 | + | ||
| 432 | // 页面加载时检查是否需要生成当前海报 | 534 | // 页面加载时检查是否需要生成当前海报 |
| 433 | generateCurrentPosterIfNeeded() | 535 | generateCurrentPosterIfNeeded() |
| 434 | }) | 536 | }) |
| ... | @@ -576,9 +678,10 @@ const chooseBackgroundImage = () => { | ... | @@ -576,9 +678,10 @@ const chooseBackgroundImage = () => { |
| 576 | 678 | ||
| 577 | uploadBackgroundImage(tempFile.path) | 679 | uploadBackgroundImage(tempFile.path) |
| 578 | }, | 680 | }, |
| 579 | - fail: () => { | 681 | + fail: (error) => { |
| 682 | + console.error('选择图片失败:', error) | ||
| 580 | // Taro.showToast({ | 683 | // Taro.showToast({ |
| 581 | - // title: '选择图片失败', | 684 | + // title: '选择图片失败,请重试', |
| 582 | // icon: 'none' | 685 | // icon: 'none' |
| 583 | // }) | 686 | // }) |
| 584 | } | 687 | } |
| ... | @@ -595,15 +698,33 @@ const uploadBackgroundImage = (filePath) => { | ... | @@ -595,15 +698,33 @@ const uploadBackgroundImage = (filePath) => { |
| 595 | url: BASE_URL + '/admin/?m=srv&a=upload', | 698 | url: BASE_URL + '/admin/?m=srv&a=upload', |
| 596 | filePath, | 699 | filePath, |
| 597 | name: 'file', | 700 | name: 'file', |
| 598 | - success: (uploadRes) => { | 701 | + success: async (uploadRes) => { |
| 599 | - Taro.hideLoading() | ||
| 600 | const data = JSON.parse(uploadRes.data) | 702 | const data = JSON.parse(uploadRes.data) |
| 601 | if (data.code === 0 && data.data) { | 703 | if (data.code === 0 && data.data) { |
| 602 | const currentIndex = currentPosterIndex.value | 704 | const currentIndex = currentPosterIndex.value |
| 705 | + const currentPosterData = posterList.value[currentIndex] | ||
| 603 | 706 | ||
| 604 | // 为当前海报设置背景图 | 707 | // 为当前海报设置背景图 |
| 605 | backgroundImages.value[currentIndex] = data.data.src | 708 | backgroundImages.value[currentIndex] = data.data.src |
| 606 | 709 | ||
| 710 | + // 调用保存海报背景接口 | ||
| 711 | + try { | ||
| 712 | + const saveResult = await savePosterBackgroundAPI({ | ||
| 713 | + detail_id: currentPosterData.checkPointId, | ||
| 714 | + poster_background_url: data.data.src | ||
| 715 | + }) | ||
| 716 | + | ||
| 717 | + if (saveResult.code === 1) { | ||
| 718 | + console.log('海报背景保存成功:', saveResult.data) | ||
| 719 | + } else { | ||
| 720 | + console.warn('海报背景保存失败:', saveResult.msg) | ||
| 721 | + // 即使保存失败,也继续生成海报,不影响用户体验 | ||
| 722 | + } | ||
| 723 | + } catch (error) { | ||
| 724 | + console.error('保存海报背景异常:', error) | ||
| 725 | + // 即使保存失败,也继续生成海报,不影响用户体验 | ||
| 726 | + } | ||
| 727 | + | ||
| 607 | // 强制标记当前海报需要重新生成 | 728 | // 强制标记当前海报需要重新生成 |
| 608 | posterGeneratedFlags.value[currentIndex] = false | 729 | posterGeneratedFlags.value[currentIndex] = false |
| 609 | delete posterConfigHashes.value[currentIndex] | 730 | delete posterConfigHashes.value[currentIndex] |
| ... | @@ -614,12 +735,15 @@ const uploadBackgroundImage = (filePath) => { | ... | @@ -614,12 +735,15 @@ const uploadBackgroundImage = (filePath) => { |
| 614 | // 立即重新生成海报 | 735 | // 立即重新生成海报 |
| 615 | generateCurrentPoster() | 736 | generateCurrentPoster() |
| 616 | 737 | ||
| 738 | + Taro.hideLoading() | ||
| 617 | Taro.showToast({ title: '上传成功', icon: 'success' }) | 739 | Taro.showToast({ title: '上传成功', icon: 'success' }) |
| 618 | } else { | 740 | } else { |
| 741 | + Taro.hideLoading() | ||
| 619 | Taro.showToast({ title: data.msg || '上传失败', icon: 'none' }) | 742 | Taro.showToast({ title: data.msg || '上传失败', icon: 'none' }) |
| 620 | } | 743 | } |
| 621 | }, | 744 | }, |
| 622 | - fail: () => { | 745 | + fail: (error) => { |
| 746 | + console.error('上传文件失败:', error) | ||
| 623 | Taro.hideLoading() | 747 | Taro.hideLoading() |
| 624 | Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' }) | 748 | Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' }) |
| 625 | } | 749 | } |
| ... | @@ -755,22 +879,7 @@ const showNoActivityConfirm = () => { | ... | @@ -755,22 +879,7 @@ const showNoActivityConfirm = () => { |
| 755 | }) | 879 | }) |
| 756 | } | 880 | } |
| 757 | 881 | ||
| 758 | -/** | ||
| 759 | - * 页面初始化 | ||
| 760 | - */ | ||
| 761 | -onMounted(() => { | ||
| 762 | - // 获取页面参数 | ||
| 763 | - const instance = Taro.getCurrentInstance() | ||
| 764 | - if (instance.router && instance.router.params) { | ||
| 765 | - pageParams.value = instance.router.params | ||
| 766 | - } | ||
| 767 | 882 | ||
| 768 | - // 检查页面状态 | ||
| 769 | - if (pageState.value === 'no-activity') { | ||
| 770 | - // 没有活动信息,显示确认对话框 | ||
| 771 | - showNoActivityConfirm() | ||
| 772 | - } | ||
| 773 | -}) | ||
| 774 | </script> | 883 | </script> |
| 775 | 884 | ||
| 776 | <style scoped> | 885 | <style scoped> | ... | ... |
-
Please register or login to post a comment