| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- <template>
- <view class="page">
- <view v-if="loading && !qrToken" class="state">加载中…</view>
- <view v-else-if="loadError" class="state error">{{ loadError }}</view>
- <template v-else>
- <view class="canvas-wrap">
- <canvas
- canvas-id="identityQr"
- class="qr-canvas"
- :style="canvasStyle"
- :width="canvasPx"
- :height="canvasPx"
- />
- </view>
- <text class="expire-line">{{ expireHint }}</text>
- <text class="tip">请让核验端扫描此码。码内为加密身份令牌,请勿自行解析。</text>
- <view class="btn-refresh" @click="refreshQr">刷新二维码</view>
- </template>
- </view>
- </template>
- <script>
- import UQRCode from 'uqrcodejs'
- import { getToken, getIdentityQrPayload } from '../../utils/api'
- const CANVAS_PX = 300
- export default {
- data() {
- return {
- loading: false,
- loadError: '',
- qrToken: '',
- expireAtMs: 0,
- nowMs: Date.now(),
- canvasPx: CANVAS_PX,
- canvasReady: false,
- countdownTimer: null,
- refreshTimer: null
- }
- },
- computed: {
- canvasStyle() {
- const n = this.canvasPx
- return `width:${n}px;height:${n}px;`
- },
- expireHint() {
- if (!this.expireAtMs) return ''
- const left = Math.max(0, Math.floor((this.expireAtMs - this.nowMs) / 1000))
- if (left <= 0) return '已过期,请刷新'
- return `剩余有效时间约 ${left} 秒`
- }
- },
- onReady() {
- this.canvasReady = true
- this.tryDrawQr()
- },
- onShow() {
- this.fetchQr()
- },
- onUnload() {
- this.clearTimers()
- },
- methods: {
- clearTimers() {
- if (this.countdownTimer) {
- clearInterval(this.countdownTimer)
- this.countdownTimer = null
- }
- if (this.refreshTimer) {
- clearTimeout(this.refreshTimer)
- this.refreshTimer = null
- }
- },
- scheduleRefreshBeforeExpire() {
- if (this.refreshTimer) {
- clearTimeout(this.refreshTimer)
- this.refreshTimer = null
- }
- if (!this.expireAtMs) return
- const ms = this.expireAtMs - Date.now() - 5000
- if (ms < 200) return
- this.refreshTimer = setTimeout(() => {
- this.refreshTimer = null
- if (getToken()) this.fetchQr(true)
- }, ms)
- },
- startCountdown() {
- if (this.countdownTimer) clearInterval(this.countdownTimer)
- this.nowMs = Date.now()
- this.countdownTimer = setInterval(() => {
- this.nowMs = Date.now()
- }, 1000)
- },
- normalizePayload(raw) {
- if (!raw || typeof raw !== 'object') return null
- const o =
- raw.data != null && typeof raw.data === 'object' && !raw.token
- ? raw.data
- : raw
- const token = o.token
- const expiresAt = o.expires_at != null ? o.expires_at : o.expiresAt
- if (!token || typeof token !== 'string') return null
- return { token, expiresAt }
- },
- async fetchQr(isRefresh) {
- const token = getToken()
- if (!token) {
- this.loadError = '请先登录'
- return
- }
- if (!isRefresh) this.loadError = ''
- this.loading = true
- try {
- const raw = await getIdentityQrPayload(token)
- const parsed = this.normalizePayload(raw)
- if (!parsed) {
- throw new Error('接口返回无效')
- }
- this.qrToken = parsed.token
- this.expireAtMs = parsed.expiresAt ? new Date(parsed.expiresAt).getTime() : 0
- this.clearTimers()
- this.startCountdown()
- this.scheduleRefreshBeforeExpire()
- this.$nextTick(() => this.tryDrawQr())
- } catch (e) {
- this.qrToken = ''
- this.expireAtMs = 0
- this.loadError = (e && e.message) || '获取失败'
- } finally {
- this.loading = false
- }
- },
- refreshQr() {
- this.fetchQr(true)
- },
- tryDrawQr() {
- if (!this.canvasReady || !this.qrToken) return
- this.drawQr()
- },
- drawQr() {
- const text = this.qrToken
- const size = this.canvasPx
- try {
- const qr = new UQRCode()
- qr.data = text
- qr.size = size
- qr.errorCorrectLevel = UQRCode.errorCorrectLevel.L
- qr.margin = 4
- qr.make()
- const ctx = uni.createCanvasContext('identityQr', this)
- qr.canvasContext = ctx
- qr.drawCanvas().catch(() => {
- uni.showToast({ title: '二维码绘制失败', icon: 'none' })
- })
- } catch (e) {
- uni.showToast({ title: '二维码生成失败', icon: 'none' })
- }
- }
- }
- }
- </script>
- <style scoped>
- .page {
- min-height: 100vh;
- background: #f5f5f5;
- padding: 48rpx 32rpx 80rpx;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .state {
- font-size: 28rpx;
- color: #666;
- margin-top: 120rpx;
- }
- .state.error {
- color: #e54d42;
- padding: 0 32rpx;
- text-align: center;
- line-height: 1.6;
- }
- .canvas-wrap {
- background: #fff;
- padding: 24rpx;
- border-radius: 16rpx;
- box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
- margin-bottom: 32rpx;
- }
- .qr-canvas {
- display: block;
- }
- .expire-line {
- font-size: 28rpx;
- color: #259653;
- margin-bottom: 24rpx;
- }
- .tip {
- display: block;
- font-size: 24rpx;
- color: #999;
- line-height: 1.6;
- text-align: center;
- padding: 0 16rpx;
- margin-bottom: 48rpx;
- }
- .btn-refresh {
- background: #fff;
- color: #259653;
- font-size: 30rpx;
- padding: 24rpx 56rpx;
- border-radius: 12rpx;
- border: 1rpx solid #259653;
- }
- </style>
|