index.vue 4.54 KB
<template>
  <view class="scan-checkin-detail-page">
    <view class="scan-checkin-detail-cover">
      <image class="scan-checkin-detail-cover-image" :src="detail.cover" mode="aspectFill" />
    </view>

    <view class="scan-checkin-detail-card">
      <view class="scan-checkin-detail-heading">
        <text class="scan-checkin-detail-title">{{ detail.code }} {{ detail.title }}</text>
        <view class="scan-checkin-detail-status" :class="detail.isChecked ? 'done' : 'pending'">
          {{ detail.isChecked ? '已打卡' : '未打卡' }}
        </view>
      </view>

      <text class="scan-checkin-detail-subtitle">{{ detail.guideText }}</text>

      <view class="scan-checkin-detail-section">
        <view class="scan-checkin-detail-section-header">
          <text class="scan-checkin-detail-section-title">{{ detail.discountTitle }}</text>
        </view>
        <view class="scan-checkin-detail-content">
          <rich-text class="scan-checkin-detail-rich-text" :nodes="formattedDiscountContent" />
        </view>
      </view>
    </view>

    <view class="scan-checkin-detail-button-wrap">
      <nut-button
        type="primary"
        class="scan-checkin-detail-button"
        color="#DF7750"
        :loading="scanSubmitting"
        @click="handleScanCheckin"
      >
        扫码打卡
      </nut-button>
    </view>
  </view>
</template>

<script setup>
import { reactive, computed } from 'vue'
import Taro, { useLoad } from '@tarojs/taro'
import './index.less'
import { getMockScanCheckinDetail } from '@/utils/mockQrCheckin'

const detail = reactive({
  id: '',
  code: 'W2D01',
  title: '泰康之家经营管理有限公司上海分公司',
  guideText: '在点位现场扫码打卡并推荐好物',
  discountTitle: '打卡点专属优惠',
  discountContentRaw: '',
  cover: 'https://cdn.ipadbiz.cn/lls_prog/images/check_detail_img.png?imageMogr2/strip/quality/60',
  isChecked: false,
  lastScanCode: '',
  scanSubmitting: false,
})

const scanSubmitting = computed(() => detail.scanSubmitting === true)

const formattedDiscountContent = computed(() => {
  const content = detail.discountContentRaw

  if (!content) {
    return ''
  }

  if (Array.isArray(content)) {
    return content
      .map(
        item =>
          `<p style="margin: 0 0 20rpx; line-height: 1.8; color: #4b5563; font-size: 30rpx;">${item}</p>`
      )
      .join('')
  }

  let formattedContent = content

  formattedContent = formattedContent.replace(/\n/g, '<br>')
  formattedContent = formattedContent.replace(
    /<img/g,
    '<img style="max-width:100%;height:auto;display:block;border-radius:16rpx;margin:24rpx 0;"'
  )

  return formattedContent
})

const mockSubmitScanCode = async code => {
  await new Promise(resolve => {
    setTimeout(resolve, 500)
  })

  return {
    code: 1,
    msg: '打卡成功',
    data: {
      scan_code: code,
    },
  }
}

const handleScanCheckin = async () => {
  detail.scanSubmitting = true

  try {
    const scanResult = await Taro.scanCode({
      onlyFromCamera: false,
      scanType: ['qrCode', 'barCode'],
    })

    const scannedCode = scanResult.result || ''

    if (!scannedCode) {
      Taro.showToast({
        title: '未识别到扫码结果',
        icon: 'none',
      })
      return
    }

    const submitResult = await mockSubmitScanCode(scannedCode)

    if (submitResult.code === 1) {
      detail.isChecked = true
      detail.lastScanCode = scannedCode

      await Taro.showModal({
        title: '模拟提交成功',
        content: `扫码结果:${scannedCode}`,
        showCancel: false,
        confirmText: '知道了',
      })
      return
    }

    Taro.showToast({
      title: submitResult.msg || '提交失败',
      icon: 'none',
    })
  } catch (error) {
    if (error?.errMsg && error.errMsg.includes('cancel')) {
      return
    }

    console.error('扫码打卡失败:', error)
    Taro.showToast({
      title: '扫码失败,请重试',
      icon: 'none',
    })
  } finally {
    detail.scanSubmitting = false
  }
}

const handleMockDataLoaded = mockDetail => {
  detail.scanSubmitting = false

  Object.assign(detail, {
    ...mockDetail,
    discountContentRaw: mockDetail.discountContent,
    isChecked: mockDetail.status === '已打卡',
  })
}

useLoad(options => {
  const detailId = options.detailId || options.id || ''
  const mockDetail = getMockScanCheckinDetail(detailId)

  if (!mockDetail) {
    Taro.showToast({
      title: '未找到打卡点',
      icon: 'none',
    })
    return
  }

  handleMockDataLoaded(mockDetail)
})
</script>

<script>
export default {
  name: 'ScanCheckinDetail',
}
</script>