Showing
4 changed files
with
234 additions
and
40 deletions
| ... | @@ -36,7 +36,7 @@ | ... | @@ -36,7 +36,7 @@ |
| 36 | </view> | 36 | </view> |
| 37 | </view> | 37 | </view> |
| 38 | 38 | ||
| 39 | - <view class="scan-checkin-detail-button-wrap"> | 39 | + <view v-if="detail.isChecked !== true" class="scan-checkin-detail-button-wrap"> |
| 40 | <nut-button | 40 | <nut-button |
| 41 | type="primary" | 41 | type="primary" |
| 42 | class="scan-checkin-detail-button" | 42 | class="scan-checkin-detail-button" |
| ... | @@ -44,7 +44,7 @@ | ... | @@ -44,7 +44,7 @@ |
| 44 | :loading="scanSubmitting" | 44 | :loading="scanSubmitting" |
| 45 | @click="handleScanCheckin" | 45 | @click="handleScanCheckin" |
| 46 | > | 46 | > |
| 47 | - 扫码打卡 | 47 | + {{ actionButtonText }} |
| 48 | </nut-button> | 48 | </nut-button> |
| 49 | </view> | 49 | </view> |
| 50 | </view> | 50 | </view> |
| ... | @@ -58,8 +58,13 @@ import RichTextRenderer from '@/components/RichTextRenderer.vue' | ... | @@ -58,8 +58,13 @@ import RichTextRenderer from '@/components/RichTextRenderer.vue' |
| 58 | import { getCurrentPageFullPath } from '@/utils/authRedirect' | 58 | import { getCurrentPageFullPath } from '@/utils/authRedirect' |
| 59 | import { getMyFamiliesAPI } from '@/api/family' | 59 | import { getMyFamiliesAPI } from '@/api/family' |
| 60 | import { getScanStageDetailAPI, submitScanCheckinAPI } from '@/api/map' | 60 | import { getScanStageDetailAPI, submitScanCheckinAPI } from '@/api/map' |
| 61 | +import { getUserProfileAPI } from '@/api/user' | ||
| 61 | import { verifyCheckinRangeWithCurrentLocation } from '@/utils/checkinLocation' | 62 | import { verifyCheckinRangeWithCurrentLocation } from '@/utils/checkinLocation' |
| 62 | -import { parseScanCheckinParams, parseScanCheckinSceneParams } from '@/utils/scanCheckin' | 63 | +import { |
| 64 | + parseScanCheckinParams, | ||
| 65 | + parseScanCheckinSceneParams, | ||
| 66 | + isSameScanCheckinTarget, | ||
| 67 | +} from '@/utils/scanCheckin' | ||
| 63 | 68 | ||
| 64 | const defaultCover = | 69 | const defaultCover = |
| 65 | 'https://cdn.ipadbiz.cn/lls_prog/images/check_detail_img.png?imageMogr2/strip/quality/60' | 70 | 'https://cdn.ipadbiz.cn/lls_prog/images/check_detail_img.png?imageMogr2/strip/quality/60' |
| ... | @@ -70,6 +75,9 @@ const detail = reactive({ | ... | @@ -70,6 +75,9 @@ const detail = reactive({ |
| 70 | regSource: '', | 75 | regSource: '', |
| 71 | regStageId: '', | 76 | regStageId: '', |
| 72 | returnUrl: '', | 77 | returnUrl: '', |
| 78 | + openid: '', | ||
| 79 | + entryMode: 'scan_before_submit', | ||
| 80 | + entrySceneRaw: '', | ||
| 73 | banners: [defaultCover], | 81 | banners: [defaultCover], |
| 74 | title: '', | 82 | title: '', |
| 75 | guideText: '', | 83 | guideText: '', |
| ... | @@ -85,6 +93,10 @@ const detail = reactive({ | ... | @@ -85,6 +93,10 @@ const detail = reactive({ |
| 85 | }) | 93 | }) |
| 86 | 94 | ||
| 87 | const scanSubmitting = computed(() => detail.scanSubmitting === true) | 95 | const scanSubmitting = computed(() => detail.scanSubmitting === true) |
| 96 | +const actionButtonText = computed(() => | ||
| 97 | + detail.entryMode === 'direct_submit' ? '点击打卡' : '扫码打卡' | ||
| 98 | +) | ||
| 99 | +const isApiSuccess = code => Number(code) === 1 | ||
| 88 | 100 | ||
| 89 | const normalizedRichTextContent = computed(() => { | 101 | const normalizedRichTextContent = computed(() => { |
| 90 | const content = detail.discountContentRaw | 102 | const content = detail.discountContentRaw |
| ... | @@ -140,12 +152,54 @@ const promptNavigateToWelcome = async () => { | ... | @@ -140,12 +152,54 @@ const promptNavigateToWelcome = async () => { |
| 140 | } | 152 | } |
| 141 | } | 153 | } |
| 142 | 154 | ||
| 155 | +// 太阳码直达详情页时,当前页已经和目标打卡点对齐,可直接进入提交模式。 | ||
| 156 | +const resolveEntryMode = (sceneParams, options = {}) => { | ||
| 157 | + const hasSceneTarget = Boolean(sceneParams.detailId && sceneParams.activityId) | ||
| 158 | + const explicitMode = String(options.checkin_mode || '').trim() | ||
| 159 | + | ||
| 160 | + if (explicitMode === 'direct_submit' || hasSceneTarget) { | ||
| 161 | + return 'direct_submit' | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + return 'scan_before_submit' | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +// 打卡接口现在要求显式传 openid,这里统一从用户资料接口补齐并缓存到页面状态。 | ||
| 168 | +const ensureOpenid = async () => { | ||
| 169 | + if (detail.openid) { | ||
| 170 | + return detail.openid | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + const profileResult = await getUserProfileAPI() | ||
| 174 | + | ||
| 175 | + if (!isApiSuccess(profileResult?.code)) { | ||
| 176 | + Taro.showToast({ | ||
| 177 | + title: profileResult?.msg || '获取用户信息失败', | ||
| 178 | + icon: 'none', | ||
| 179 | + }) | ||
| 180 | + return '' | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + const openid = String(profileResult?.data?.user?.openid || '').trim() | ||
| 184 | + | ||
| 185 | + if (!openid) { | ||
| 186 | + Taro.showToast({ | ||
| 187 | + title: '未获取到用户openid', | ||
| 188 | + icon: 'none', | ||
| 189 | + }) | ||
| 190 | + return '' | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + detail.openid = openid | ||
| 194 | + return openid | ||
| 195 | +} | ||
| 196 | + | ||
| 143 | const handleScanCheckin = async () => { | 197 | const handleScanCheckin = async () => { |
| 144 | detail.scanSubmitting = true | 198 | detail.scanSubmitting = true |
| 145 | 199 | ||
| 146 | try { | 200 | try { |
| 147 | const familyResult = await getMyFamiliesAPI() | 201 | const familyResult = await getMyFamiliesAPI() |
| 148 | - const hasFamily = familyResult?.code && familyResult?.data?.families?.length > 0 | 202 | + const hasFamily = isApiSuccess(familyResult?.code) && familyResult?.data?.families?.length > 0 |
| 149 | 203 | ||
| 150 | if (!hasFamily) { | 204 | if (!hasFamily) { |
| 151 | detail.scanSubmitting = false | 205 | detail.scanSubmitting = false |
| ... | @@ -177,12 +231,21 @@ const handleScanCheckin = async () => { | ... | @@ -177,12 +231,21 @@ const handleScanCheckin = async () => { |
| 177 | return | 231 | return |
| 178 | } | 232 | } |
| 179 | 233 | ||
| 234 | + // 太阳码直达详情页时,不需要再扫一次码,直接按当前详情提交即可。 | ||
| 235 | + if (detail.entryMode === 'direct_submit') { | ||
| 236 | + await submitCheckin({ | ||
| 237 | + activityId: detail.activityId, | ||
| 238 | + detailId: detail.id, | ||
| 239 | + }) | ||
| 240 | + return | ||
| 241 | + } | ||
| 242 | + | ||
| 180 | const scanResult = await Taro.scanCode({ | 243 | const scanResult = await Taro.scanCode({ |
| 181 | onlyFromCamera: false, | 244 | onlyFromCamera: false, |
| 182 | - scanType: ['qrCode', 'barCode'], | 245 | + scanType: ['qrCode', 'barCode', 'WX_CODE'], |
| 183 | }) | 246 | }) |
| 184 | 247 | ||
| 185 | - const scannedCode = scanResult.result || '' | 248 | + const scannedCode = scanResult.path || scanResult.result || '' |
| 186 | 249 | ||
| 187 | if (!scannedCode) { | 250 | if (!scannedCode) { |
| 188 | Taro.showToast({ | 251 | Taro.showToast({ |
| ... | @@ -205,38 +268,31 @@ const handleScanCheckin = async () => { | ... | @@ -205,38 +268,31 @@ const handleScanCheckin = async () => { |
| 205 | return | 268 | return |
| 206 | } | 269 | } |
| 207 | 270 | ||
| 208 | - const submitResult = await submitScanCheckinAPI({ | 271 | + // 列表进入详情页时,扫码结果只负责确认目标打卡点,避免在错误详情里静默打错卡。 |
| 209 | - activity_id: submitActivityId, | 272 | + const matchedCurrentDetail = isSameScanCheckinTarget( |
| 210 | - detail_id: submitDetailId, | 273 | + { |
| 211 | - }) | 274 | + activityId: detail.activityId, |
| 212 | - | 275 | + detailId: detail.id, |
| 213 | - if (submitResult.code === 1) { | 276 | + }, |
| 214 | - detail.isChecked = true | 277 | + { |
| 215 | - detail.lastScanCode = scannedCode | 278 | + activityId: submitActivityId, |
| 279 | + detailId: submitDetailId, | ||
| 280 | + } | ||
| 281 | + ) | ||
| 216 | 282 | ||
| 217 | - const modalResult = await Taro.showModal({ | 283 | + if (matchedCurrentDetail) { |
| 218 | - title: '打卡成功', | 284 | + await submitCheckin({ |
| 219 | - content: '您已完成当前扫码打卡,可前往列表查看状态。', | 285 | + activityId: submitActivityId, |
| 220 | - showCancel: false, | 286 | + detailId: submitDetailId, |
| 221 | - confirmText: '查看列表', | 287 | + scannedCode, |
| 222 | }) | 288 | }) |
| 223 | - | ||
| 224 | - if (modalResult.confirm) { | ||
| 225 | - const params = new URLSearchParams({ | ||
| 226 | - activityId: detail.activityId, | ||
| 227 | - }) | ||
| 228 | - | ||
| 229 | - // 用户确认“查看列表”后回到列表页,方便继续处理同一活动下的其他关卡。 | ||
| 230 | - Taro.redirectTo({ | ||
| 231 | - url: `/pages/ScanCheckinList/index?${params.toString()}`, | ||
| 232 | - }) | ||
| 233 | - } | ||
| 234 | return | 289 | return |
| 235 | } | 290 | } |
| 236 | 291 | ||
| 237 | - Taro.showToast({ | 292 | + await promptNavigateToMatchedDetail({ |
| 238 | - title: submitResult.msg || '提交失败', | 293 | + activityId: submitActivityId, |
| 239 | - icon: 'none', | 294 | + detailId: submitDetailId, |
| 295 | + rawScene: parsedScanParams.rawScene, | ||
| 240 | }) | 296 | }) |
| 241 | } catch (error) { | 297 | } catch (error) { |
| 242 | if (error?.errMsg && error.errMsg.includes('cancel')) { | 298 | if (error?.errMsg && error.errMsg.includes('cancel')) { |
| ... | @@ -253,6 +309,91 @@ const handleScanCheckin = async () => { | ... | @@ -253,6 +309,91 @@ const handleScanCheckin = async () => { |
| 253 | } | 309 | } |
| 254 | } | 310 | } |
| 255 | 311 | ||
| 312 | +// 提交动作统一收口到这里,确保 openid、成功提示和列表回跳逻辑保持一致。 | ||
| 313 | +const submitCheckin = async ({ activityId, detailId, scannedCode = '' }) => { | ||
| 314 | + if (!activityId || !detailId) { | ||
| 315 | + Taro.showToast({ | ||
| 316 | + title: '缺少打卡参数', | ||
| 317 | + icon: 'none', | ||
| 318 | + }) | ||
| 319 | + return | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + const openid = await ensureOpenid() | ||
| 323 | + | ||
| 324 | + if (!openid) { | ||
| 325 | + return | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + const submitResult = await submitScanCheckinAPI({ | ||
| 329 | + activity_id: activityId, | ||
| 330 | + detail_id: detailId, | ||
| 331 | + openid, | ||
| 332 | + }) | ||
| 333 | + | ||
| 334 | + if (isApiSuccess(submitResult?.code)) { | ||
| 335 | + detail.isChecked = true | ||
| 336 | + detail.lastScanCode = scannedCode || detail.lastScanCode | ||
| 337 | + | ||
| 338 | + const modalResult = await Taro.showModal({ | ||
| 339 | + title: '打卡成功', | ||
| 340 | + content: '您已完成当前扫码打卡,可前往列表查看状态。', | ||
| 341 | + showCancel: false, | ||
| 342 | + confirmText: '查看列表', | ||
| 343 | + }) | ||
| 344 | + | ||
| 345 | + if (modalResult.confirm) { | ||
| 346 | + const params = new URLSearchParams({ | ||
| 347 | + activityId: activityId || detail.activityId, | ||
| 348 | + }) | ||
| 349 | + | ||
| 350 | + Taro.redirectTo({ | ||
| 351 | + url: `/pages/ScanCheckinList/index?${params.toString()}`, | ||
| 352 | + }) | ||
| 353 | + } | ||
| 354 | + return | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + Taro.showToast({ | ||
| 358 | + title: submitResult.msg || '提交失败', | ||
| 359 | + icon: 'none', | ||
| 360 | + }) | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +// 扫到别的点位时先征求用户确认,再把页面切到目标详情并改成直达提交模式。 | ||
| 364 | +const promptNavigateToMatchedDetail = async ({ activityId, detailId, rawScene }) => { | ||
| 365 | + const modalResult = await showMismatchConfirmModal() | ||
| 366 | + | ||
| 367 | + if (!modalResult.confirm) { | ||
| 368 | + return | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + const nextParams = new URLSearchParams({ | ||
| 372 | + activityId, | ||
| 373 | + detailId, | ||
| 374 | + checkin_mode: 'direct_submit', | ||
| 375 | + }) | ||
| 376 | + | ||
| 377 | + const nextScene = String(rawScene || '').trim() | ||
| 378 | + | ||
| 379 | + if (nextScene) { | ||
| 380 | + nextParams.set('scene', nextScene) | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + Taro.redirectTo({ | ||
| 384 | + url: `/pages/ScanCheckinDetail/index?${nextParams.toString()}`, | ||
| 385 | + }) | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +const showMismatchConfirmModal = async () => { | ||
| 389 | + return Taro.showModal({ | ||
| 390 | + title: '打卡点不匹配', | ||
| 391 | + content: '当前扫码点与本页不匹配,是否前往对应打卡点详情?', | ||
| 392 | + confirmText: '前往详情', | ||
| 393 | + cancelText: '取消', | ||
| 394 | + }) | ||
| 395 | +} | ||
| 396 | + | ||
| 256 | const applyStageDetail = stageDetail => { | 397 | const applyStageDetail = stageDetail => { |
| 257 | detail.scanSubmitting = false | 398 | detail.scanSubmitting = false |
| 258 | 399 | ||
| ... | @@ -282,6 +423,8 @@ useLoad(options => { | ... | @@ -282,6 +423,8 @@ useLoad(options => { |
| 282 | const detailId = sceneParams.detailId || options.detailId || options.id || '' | 423 | const detailId = sceneParams.detailId || options.detailId || options.id || '' |
| 283 | detail.regSource = options.reg_source || '' | 424 | detail.regSource = options.reg_source || '' |
| 284 | detail.regStageId = options.reg_stage_id || '' | 425 | detail.regStageId = options.reg_stage_id || '' |
| 426 | + detail.entrySceneRaw = sceneParams.rawScene || '' | ||
| 427 | + detail.entryMode = resolveEntryMode(sceneParams, options) | ||
| 285 | // 当前页路径会透传给补资料页,提交成功后用于回跳续扫。 | 428 | // 当前页路径会透传给补资料页,提交成功后用于回跳续扫。 |
| 286 | detail.returnUrl = `/${getCurrentPageFullPath()}` | 429 | detail.returnUrl = `/${getCurrentPageFullPath()}` |
| 287 | detail.id = detailId | 430 | detail.id = detailId |
| ... | @@ -294,7 +437,7 @@ const loadStageDetail = async () => { | ... | @@ -294,7 +437,7 @@ const loadStageDetail = async () => { |
| 294 | id: detail.id, | 437 | id: detail.id, |
| 295 | }) | 438 | }) |
| 296 | 439 | ||
| 297 | - if (result?.code !== 1 || !result?.data) { | 440 | + if (!isApiSuccess(result?.code) || !result?.data) { |
| 298 | Taro.showToast({ | 441 | Taro.showToast({ |
| 299 | title: result?.msg || '获取关卡详情失败', | 442 | title: result?.msg || '获取关卡详情失败', |
| 300 | icon: 'none', | 443 | icon: 'none', |
| ... | @@ -303,6 +446,11 @@ const loadStageDetail = async () => { | ... | @@ -303,6 +446,11 @@ const loadStageDetail = async () => { |
| 303 | } | 446 | } |
| 304 | 447 | ||
| 305 | applyStageDetail(result.data) | 448 | applyStageDetail(result.data) |
| 449 | + | ||
| 450 | + if (!detail.openid) { | ||
| 451 | + // 提前预取 openid,减少用户点击打卡时再补资料接口带来的等待感。 | ||
| 452 | + await ensureOpenid() | ||
| 453 | + } | ||
| 306 | } | 454 | } |
| 307 | </script> | 455 | </script> |
| 308 | 456 | ... | ... |
| 1 | .scan-checkin-list-page { | 1 | .scan-checkin-list-page { |
| 2 | min-height: 100vh; | 2 | min-height: 100vh; |
| 3 | - padding: 32rpx 24rpx 220rpx; | 3 | + padding: 32rpx 24rpx 260rpx; |
| 4 | background: linear-gradient(180deg, #f6f8fb 0%, #eef2f5 100%); | 4 | background: linear-gradient(180deg, #f6f8fb 0%, #eef2f5 100%); |
| 5 | box-sizing: border-box; | 5 | box-sizing: border-box; |
| 6 | position: relative; | 6 | position: relative; |
| ... | @@ -87,6 +87,17 @@ | ... | @@ -87,6 +87,17 @@ |
| 87 | flex-shrink: 0; | 87 | flex-shrink: 0; |
| 88 | } | 88 | } |
| 89 | 89 | ||
| 90 | +.scan-checkin-list-checked-tag { | ||
| 91 | + padding: 10rpx 18rpx; | ||
| 92 | + border-radius: 999rpx; | ||
| 93 | + background: rgba(54, 181, 187, 0.12); | ||
| 94 | + color: #2c9ca2; | ||
| 95 | + font-size: 24rpx; | ||
| 96 | + line-height: 1; | ||
| 97 | + font-weight: 500; | ||
| 98 | + flex-shrink: 0; | ||
| 99 | +} | ||
| 100 | + | ||
| 90 | .scan-checkin-list-load-more, | 101 | .scan-checkin-list-load-more, |
| 91 | .scan-checkin-list-no-more { | 102 | .scan-checkin-list-no-more { |
| 92 | padding: 24rpx 0 8rpx; | 103 | padding: 24rpx 0 8rpx; |
| ... | @@ -105,7 +116,7 @@ | ... | @@ -105,7 +116,7 @@ |
| 105 | .scan-checkin-list-floating-button { | 116 | .scan-checkin-list-floating-button { |
| 106 | position: fixed; | 117 | position: fixed; |
| 107 | right: 24rpx; | 118 | right: 24rpx; |
| 108 | - bottom: 120rpx; | 119 | + bottom: 160rpx; |
| 109 | width: 120rpx; | 120 | width: 120rpx; |
| 110 | height: 120rpx; | 121 | height: 120rpx; |
| 111 | border-radius: 50%; | 122 | border-radius: 50%; | ... | ... |
| ... | @@ -25,6 +25,8 @@ | ... | @@ -25,6 +25,8 @@ |
| 25 | <text class="scan-checkin-list-name">{{ point.title }}</text> | 25 | <text class="scan-checkin-list-name">{{ point.title }}</text> |
| 26 | </view> | 26 | </view> |
| 27 | 27 | ||
| 28 | + <text v-if="point.isChecked === true" class="scan-checkin-list-checked-tag">已打卡</text> | ||
| 29 | + | ||
| 28 | <view class="scan-checkin-list-action" @click="goToDetail(point)"> | 30 | <view class="scan-checkin-list-action" @click="goToDetail(point)"> |
| 29 | <Scan2 size="20" /> | 31 | <Scan2 size="20" /> |
| 30 | </view> | 32 | </view> |
| ... | @@ -55,6 +57,8 @@ | ... | @@ -55,6 +57,8 @@ |
| 55 | /> | 57 | /> |
| 56 | <text class="scan-checkin-list-floating-text">展位图</text> | 58 | <text class="scan-checkin-list-floating-text">展位图</text> |
| 57 | </view> | 59 | </view> |
| 60 | + | ||
| 61 | + <BottomNav /> | ||
| 58 | </view> | 62 | </view> |
| 59 | </template> | 63 | </template> |
| 60 | 64 | ||
| ... | @@ -63,6 +67,7 @@ import { ref } from 'vue' | ... | @@ -63,6 +67,7 @@ import { ref } from 'vue' |
| 63 | import Taro, { useLoad } from '@tarojs/taro' | 67 | import Taro, { useLoad } from '@tarojs/taro' |
| 64 | import { IconFont, Scan2 } from '@nutui/icons-vue-taro' | 68 | import { IconFont, Scan2 } from '@nutui/icons-vue-taro' |
| 65 | import './index.less' | 69 | import './index.less' |
| 70 | +import BottomNav from '@/components/BottomNav.vue' | ||
| 66 | import { getScanStageListAPI } from '@/api/map' | 71 | import { getScanStageListAPI } from '@/api/map' |
| 67 | 72 | ||
| 68 | const pointList = ref([]) | 73 | const pointList = ref([]) |
| ... | @@ -73,6 +78,7 @@ const hasMore = ref(true) | ... | @@ -73,6 +78,7 @@ const hasMore = ref(true) |
| 73 | const currentPage = ref(0) | 78 | const currentPage = ref(0) |
| 74 | const pageSize = ref(10) | 79 | const pageSize = ref(10) |
| 75 | 80 | ||
| 81 | +// 列表页只保留展示所需字段,避免模板层直接感知后端原始命名。 | ||
| 76 | const mapStageList = stageList => | 82 | const mapStageList = stageList => |
| 77 | stageList.map(stage => ({ | 83 | stageList.map(stage => ({ |
| 78 | id: stage.id, | 84 | id: stage.id, | ... | ... |
| ... | @@ -35,8 +35,8 @@ export const parseScanCheckinSceneParams = (rawScene = '') => { | ... | @@ -35,8 +35,8 @@ export const parseScanCheckinSceneParams = (rawScene = '') => { |
| 35 | 35 | ||
| 36 | /** | 36 | /** |
| 37 | * @description 从扫码结果中提取打卡接口所需参数 | 37 | * @description 从扫码结果中提取打卡接口所需参数 |
| 38 | - * @param {string} rawScanResult - 微信扫码返回的原始结果,可能是完整URL,也可能是纯查询串 | 38 | + * @param {string} rawScanResult - 微信扫码返回的原始结果,可能是 path、完整URL,也可能是纯查询串 |
| 39 | - * @returns {{activityId:string, detailId:string, rawParams:Object}} | 39 | + * @returns {{activityId:string, detailId:string, rawParams:Object, rawScene:string}} |
| 40 | */ | 40 | */ |
| 41 | export const parseScanCheckinParams = (rawScanResult = '') => { | 41 | export const parseScanCheckinParams = (rawScanResult = '') => { |
| 42 | const normalized = String(rawScanResult || '').trim() | 42 | const normalized = String(rawScanResult || '').trim() |
| ... | @@ -46,17 +46,44 @@ export const parseScanCheckinParams = (rawScanResult = '') => { | ... | @@ -46,17 +46,44 @@ export const parseScanCheckinParams = (rawScanResult = '') => { |
| 46 | activityId: '', | 46 | activityId: '', |
| 47 | detailId: '', | 47 | detailId: '', |
| 48 | rawParams: {}, | 48 | rawParams: {}, |
| 49 | + rawScene: '', | ||
| 49 | } | 50 | } |
| 50 | } | 51 | } |
| 51 | 52 | ||
| 52 | const querySource = extractQuerySource(normalized) | 53 | const querySource = extractQuerySource(normalized) |
| 53 | const rawParams = parseQueryString(querySource) | 54 | const rawParams = parseQueryString(querySource) |
| 55 | + // 兼容 `pages/xxx?scene=detailId,activityId` 这种太阳码 path 场景。 | ||
| 56 | + const sceneParams = parseScanCheckinSceneParams(rawParams.scene || '') | ||
| 54 | 57 | ||
| 55 | return { | 58 | return { |
| 56 | - activityId: pickFirstAvailableValue(rawParams, ['activity_id', 'activityId', 'id']), | 59 | + activityId: |
| 57 | - detailId: pickFirstAvailableValue(rawParams, ['detail_id', 'detailId', 'stage_id', 'stageId']), | 60 | + sceneParams.activityId || |
| 61 | + pickFirstAvailableValue(rawParams, ['activity_id', 'activityId', 'id']), | ||
| 62 | + detailId: | ||
| 63 | + sceneParams.detailId || | ||
| 64 | + pickFirstAvailableValue(rawParams, ['detail_id', 'detailId', 'stage_id', 'stageId']), | ||
| 58 | rawParams, | 65 | rawParams, |
| 66 | + rawScene: sceneParams.rawScene, | ||
| 67 | + } | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +/** | ||
| 71 | + * @description 判断当前详情页和扫码结果是否指向同一个打卡点 | ||
| 72 | + * @param {{activityId:string|number, detailId:string|number}} currentTarget | ||
| 73 | + * @param {{activityId:string|number, detailId:string|number}} scannedTarget | ||
| 74 | + * @returns {boolean} | ||
| 75 | + */ | ||
| 76 | +export const isSameScanCheckinTarget = (currentTarget = {}, scannedTarget = {}) => { | ||
| 77 | + const currentActivityId = normalizeTargetValue(currentTarget.activityId) | ||
| 78 | + const currentDetailId = normalizeTargetValue(currentTarget.detailId) | ||
| 79 | + const scannedActivityId = normalizeTargetValue(scannedTarget.activityId) | ||
| 80 | + const scannedDetailId = normalizeTargetValue(scannedTarget.detailId) | ||
| 81 | + | ||
| 82 | + if (!currentActivityId || !currentDetailId || !scannedActivityId || !scannedDetailId) { | ||
| 83 | + return false | ||
| 59 | } | 84 | } |
| 85 | + | ||
| 86 | + return currentActivityId === scannedActivityId && currentDetailId === scannedDetailId | ||
| 60 | } | 87 | } |
| 61 | 88 | ||
| 62 | const extractQuerySource = input => { | 89 | const extractQuerySource = input => { |
| ... | @@ -98,6 +125,8 @@ const pickFirstAvailableValue = (params, keys = []) => { | ... | @@ -98,6 +125,8 @@ const pickFirstAvailableValue = (params, keys = []) => { |
| 98 | return '' | 125 | return '' |
| 99 | } | 126 | } |
| 100 | 127 | ||
| 128 | +const normalizeTargetValue = value => String(value || '').trim() | ||
| 129 | + | ||
| 101 | const safeDecodeURIComponent = (value = '') => { | 130 | const safeDecodeURIComponent = (value = '') => { |
| 102 | try { | 131 | try { |
| 103 | return decodeURIComponent(String(value || '').replace(/\+/g, '%20')) | 132 | return decodeURIComponent(String(value || '').replace(/\+/g, '%20')) | ... | ... |
-
Please register or login to post a comment