hookehuyr

feat(verificationResult): 重构核销结果页面并优化核销流程

重构核销结果页面UI,增加状态显示和更好的用户反馈
优化核销流程,移除直接扫码功能改为点击核销按钮触发
添加核销状态管理,包括核销中、成功和失败状态
export default {
navigationBarTitleText: '核销结果'
navigationBarTitleText: '核销'
}
......
<!--
* @Date: 2026-01-08 13:01:20
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-01-08 13:08:22
* @LastEditTime: 2026-01-14 10:07:56
* @FilePath: /xyxBooking-weapp/src/pages/verificationResult/index.vue
* @Description: 核销结果页面
-->
<template>
<view class="result-page">
<view class="result-content">
<icon type="success" size="64" color="#A67939"/>
<view class="msg">{{ msg }}</view>
<view class="info" v-if="resultContent">预约码: {{ resultContent }}</view>
<nut-button color="#A67939" class="back-btn" @tap="continueScan">继续核销</nut-button>
<!-- <nut-button type="default" class="home-btn" @tap="goHome">返回首页</nut-button> -->
<view class="min-h-screen bg-gray-100 p-4 verify-page">
<view class="rounded-2xl bg-white p-4 shadow-sm">
<view class="flex items-center">
<view class="flex h-11 w-11 items-center justify-center rounded-full bg-amber-50">
<icon :type="status_icon_type" size="24" :color="status_icon_color" />
</view>
<view class="ml-4 flex-1">
<view class="text-sm font-semibold text-gray-900">{{ status_title }}</view>
<view class="mt-1 text-xs text-gray-500">{{ msg }}</view>
</view>
</view>
</view>
<view v-if="result_content" class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
<view class="text-xs text-gray-500">核销结果</view>
<view class="mt-2 break-all text-sm font-medium text-gray-900">{{ result_content }}</view>
</view>
<view v-else class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
<view class="text-xs text-gray-500">核销结果</view>
<view class="mt-2 text-sm text-gray-400">暂无核销结果,点击下方核销按钮开始扫码</view>
</view>
<view class="verify-footer">
<nut-button
block
color="#A67939"
:loading="verify_status === 'verifying'"
:disabled="verify_status === 'verifying'"
class="verify-btn"
@tap="start_scan_and_verify"
>
核销
</nut-button>
<view class="mt-4 text-center text-xs text-gray-400">扫描预约码二维码进行核销</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { useRouter } from '@tarojs/taro'
// import { useGo } from '@/hooks/useGo'
import { verifyTicketAPI } from '@/api/index'
import Taro, { useDidShow } from '@tarojs/taro'
const router = useRouter()
// const go = useGo()
const resultContent = ref('')
const msg = ref('核销成功')
const result_content = ref('')
const verify_status = ref('idle')
const msg = ref('请点击下方按钮进行核销')
// TODO: 还没有真实字段信息, 如果有需要修改, 这个页面涉及verify_status的功能都要注意
const status_title = computed(() => {
if (verify_status.value === 'verifying') return '核销中'
if (verify_status.value === 'success') return '核销成功'
if (verify_status.value === 'fail') return '核销失败'
return '核销'
})
const status_icon_type = computed(() => {
if (verify_status.value === 'verifying') return 'waiting'
if (verify_status.value === 'success') return 'success'
if (verify_status.value === 'fail') return 'cancel'
return 'info'
})
const status_icon_color = computed(() => {
if (verify_status.value === 'fail') return '#E24A4A'
return '#A67939'
})
/******************* END **********************/
const verify_ticket = async (code) => {
if (!code) return
if (verify_status.value === 'verifying') return
result_content.value = code
verify_status.value = 'verifying'
msg.value = '核销中...'
useDidShow(async () => {
resultContent.value = router.params.result || ''
if (resultContent.value) {
Taro.showLoading({ title: '核销中...' })
const res = await verifyTicketAPI({ code: resultContent.value })
Taro.hideLoading()
if (res.code === 1) {
msg.value = res.msg || '核销成功'
} else {
msg.value = res.msg || '核销失败'
try {
const res = await verifyTicketAPI({ code })
if (res?.code) {
verify_status.value = 'success'
msg.value = res?.msg || '核销成功'
return
}
verify_status.value = 'fail'
msg.value = res?.msg || '核销失败'
Taro.showToast({ title: msg.value, icon: 'none' })
} catch (e) {
verify_status.value = 'fail'
msg.value = '核销失败'
Taro.showToast({ title: msg.value, icon: 'none' })
} finally {
Taro.hideLoading()
}
}
useDidShow(async () => {
/**
* 现在主要用于:外部渠道如果手动带参进入则自动核销;正常业务流程(从义工登录进入)是不会带参的,会走缺省态 + 点击“核销”扫码。
*/
const code = router?.params?.result || ''
if (!code) {
result_content.value = ''
verify_status.value = 'idle'
msg.value = '请点击下方按钮进行核销'
return
}
await verify_ticket(code)
})
const continueScan = () => {
const start_scan_and_verify = () => {
Taro.scanCode({
success: (res) => {
// Reload current page with new result
// Taro doesn't support easy reload with params change in same page easily without redirect
// So we redirect to self
Taro.redirectTo({
url: `/pages/verificationResult/index?result=${res.result}`
})
verify_ticket(res?.result || '')
},
fail: () => {
// Cancelled
}
})
}
// const goHome = () => {
// Taro.reLaunch({ url: '/pages/index/index' })
// }
</script>
<style lang="less">
.result-page {
padding: 64rpx 32rpx;
text-align: center;
min-height: 100vh;
background-color: #fff;
.verify-page {
padding-bottom: 220rpx;
}
.result-content {
display: flex;
flex-direction: column;
align-items: center;
}
.verify-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
width: 750rpx;
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
background-color: #FFFFFF;
box-sizing: border-box;
box-shadow: 0 -10rpx 8rpx 0 rgba(0,0,0,0.08);
}
.msg {
font-size: 40rpx;
margin-top: 32rpx;
font-weight: bold;
color: #333;
}
.info {
margin-top: 20rpx;
color: #999;
font-size: 28rpx;
word-break: break-all;
padding: 0 32rpx;
}
.back-btn {
margin-top: 80rpx;
background-color: #A67939;
color: #fff;
width: 80%;
border-radius: 44rpx;
}
.home-btn {
margin-top: 32rpx;
background-color: #fff;
color: #666;
width: 80%;
border-radius: 44rpx;
border: 1rpx solid #b0b0b0;
}
.verify-btn {
font-size: 36rpx;
height: 104rpx;
line-height: 104rpx;
border-radius: 16rpx;
font-weight: 600;
}
</style>
......
<!--
* @Date: 2026-01-08 13:01:56
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-01-13 22:11:30
* @LastEditTime: 2026-01-14 09:59:46
* @FilePath: /xyxBooking-weapp/src/pages/volunteerLogin/index.vue
* @Description: 义工登录页面
-->
......@@ -43,17 +43,37 @@
<script setup>
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import Taro, { useDidShow } from '@tarojs/taro'
import { mainStore } from '@/stores/main'
import { volunteerLoginAPI, getUserInfoAPI } from '@/api/index'
import { useGo } from '@/hooks/useGo'
import { useReplace } from '@/hooks/useGo'
import logo from '@/assets/images/logo.png'
const store = mainStore()
const go = useGo()
const replace = useReplace()
const username = ref('')
const password = ref('')
/**
* @description: 检查用户权限并根据角色重定向
*/
const check_permission_and_redirect = async () => {
try {
const user_res = await getUserInfoAPI()
if (user_res?.code === 1 && user_res?.data) {
store.changeUserInfo(user_res.data)
if (store.isVolunteer) {
replace('verificationResult')
}
}
} catch (e) {}
}
useDidShow(() => {
check_permission_and_redirect()
})
const handleLogin = async () => {
if (!username.value || !password.value) {
Taro.showToast({ title: '请输入账号密码', icon: 'none' })
......@@ -77,7 +97,7 @@ const handleLogin = async () => {
if (store.isVolunteer) {
Taro.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
triggerScan()
replace('verificationResult')
}, 1500)
} else {
Taro.showToast({ title: '非义工账号', icon: 'none' })
......@@ -90,17 +110,6 @@ const handleLogin = async () => {
Taro.showToast({ title: loginRes.msg || '登录失败', icon: 'none' })
}
}
const triggerScan = () => {
Taro.scanCode({
success: (res) => {
go(`/pages/verificationResult/index?result=${res.result}`)
},
fail: (err) => {
console.log('Scan failed', err)
}
})
}
</script>
<style lang="less">
......