hookehuyr

feat(核销): 重构义工核销流程,使用新接口和权限检查

- 替换模拟接口为真实核销接口
- 修改义工权限判断逻辑,使用 can_redeem 字段
- 优化登录流程,增加权限检查
- 更新核销结果页面显示逻辑
1 /* 1 /*
2 * @Date: 2023-08-24 09:42:27 2 * @Date: 2023-08-24 09:42:27
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2026-01-13 23:13:04 4 + * @LastEditTime: 2026-01-14 20:43:23
5 * @FilePath: /xyxBooking-weapp/src/api/index.js 5 * @FilePath: /xyxBooking-weapp/src/api/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -28,32 +28,25 @@ const Api = { ...@@ -28,32 +28,25 @@ const Api = {
28 QUERY_QR_CODE: '/srv/?a=api&t=id_number_query_qr_code', 28 QUERY_QR_CODE: '/srv/?a=api&t=id_number_query_qr_code',
29 ICBC_ORDER_QRY: '/srv/?a=icbc_orderqry', 29 ICBC_ORDER_QRY: '/srv/?a=icbc_orderqry',
30 WX_PAY: '/srv/?a=icbc_pay_wxamp', 30 WX_PAY: '/srv/?a=icbc_pay_wxamp',
31 - // TODO: 3个关于核销的接口都是假的, 等待真实接口 31 + REDEEM_LOGIN: '/srv/?a=redeem&t=login',
32 - VOLUNTEER_LOGIN: '/srv/?a=api&t=volunteer_login', 32 + REDEEM_CHECK_AUTH: '/srv/?a=redeem&t=check_auth',
33 - VERIFY_TICKET: '/srv/?a=api&t=verify_ticket', 33 + REDEEM_REDEEM: '/srv/?a=redeem&t=redeem',
34 - GET_USER_INFO: '/srv/?a=api&t=get_user_info',
35 }; 34 };
36 35
37 /** 36 /**
38 - * @description: 获取用户信息 (Mock) 37 + * @description: 义工登录
39 */ 38 */
40 -export const getUserInfoAPI = () => { 39 +export const volunteerLoginAPI = (params) => fn(fetch.post(Api.REDEEM_LOGIN, params));
41 - return fn(fetch.get(Api.GET_USER_INFO));
42 -};
43 40
44 /** 41 /**
45 - * @description: 义工登录 (Mock) 42 + * @description: 检查核销权限
46 */ 43 */
47 -export const volunteerLoginAPI = (params) => { 44 +export const checkRedeemPermissionAPI = (params) => fn(fetch.get(Api.REDEEM_CHECK_AUTH, params));
48 - return fn(fetch.post(Api.VOLUNTEER_LOGIN, params));
49 -};
50 45
51 /** 46 /**
52 - * @description: 核销门票 (Mock) 47 + * @description: 核销
53 */ 48 */
54 -export const verifyTicketAPI = (params) => { 49 +export const verifyTicketAPI = (params) => fn(fetch.post(Api.REDEEM_REDEEM, params));
55 - return fn(fetch.post(Api.VERIFY_TICKET, params));
56 -};
57 50
58 /** 51 /**
59 * @description: 可预约日期列表 52 * @description: 可预约日期列表
......
...@@ -19,23 +19,16 @@ ...@@ -19,23 +19,16 @@
19 </view> 19 </view>
20 </view> 20 </view>
21 21
22 - <view v-if="verify_result_text" class="mt-4 rounded-2xl bg-white p-5 shadow-sm"> 22 + <view class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
23 - <view class="text-xs text-gray-500">核销结果</view> 23 + <view class="text-xs text-gray-500">核销权限</view>
24 - <view class="mt-2 break-all whitespace-pre-wrap text-sm font-medium text-gray-900">{{ verify_result_text }}</view> 24 + <view class="mt-2 text-sm font-medium text-gray-900">{{ can_redeem_text }}</view>
25 - </view>
26 - <view v-else class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
27 - <view class="text-xs text-gray-500">核销结果</view>
28 - <view class="mt-2 text-sm text-gray-400">暂无核销结果,点击下方核销按钮开始扫码</view>
29 </view> 25 </view>
30 26
31 - <!-- <view v-if="verify_code" class="mt-4 rounded-2xl bg-white p-5 shadow-sm"> 27 + <view class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
32 - <view class="text-xs text-gray-500">预约码</view> 28 + <view class="text-xs text-gray-500">最近一次扫码内容</view>
33 - <view class="mt-2 break-all text-sm font-medium text-gray-900">{{ verify_code }}</view> 29 + <view v-if="verify_code" class="mt-2 break-all whitespace-pre-wrap text-sm font-medium text-gray-900">{{ verify_code }}</view>
30 + <view v-else class="mt-2 text-sm text-gray-400">暂无扫码内容</view>
34 </view> 31 </view>
35 - <view v-else class="mt-4 rounded-2xl bg-white p-5 shadow-sm">
36 - <view class="text-xs text-gray-500">预约码</view>
37 - <view class="mt-2 text-sm text-gray-400">暂无预约码</view>
38 - </view> -->
39 32
40 <view class="verify-footer"> 33 <view class="verify-footer">
41 <nut-button 34 <nut-button
...@@ -56,14 +49,17 @@ ...@@ -56,14 +49,17 @@
56 <script setup> 49 <script setup>
57 import { ref, computed } from 'vue' 50 import { ref, computed } from 'vue'
58 import { useRouter } from '@tarojs/taro' 51 import { useRouter } from '@tarojs/taro'
59 -import { verifyTicketAPI } from '@/api/index' 52 +import { verifyTicketAPI, checkRedeemPermissionAPI } from '@/api/index'
60 import Taro, { useDidShow } from '@tarojs/taro' 53 import Taro, { useDidShow } from '@tarojs/taro'
54 +import { mainStore } from '@/stores/main'
55 +import { useReplace } from '@/hooks/useGo'
61 56
62 const router = useRouter() 57 const router = useRouter()
63 const verify_code = ref('') 58 const verify_code = ref('')
64 -const verify_result = ref(null)
65 const verify_status = ref('idle') 59 const verify_status = ref('idle')
66 const msg = ref('请点击下方按钮进行核销') 60 const msg = ref('请点击下方按钮进行核销')
61 +const store = mainStore()
62 +const replace = useReplace()
67 63
68 // TODO: 还没有真实字段信息, 如果有需要修改, 这个页面涉及verify_status的功能都要注意 64 // TODO: 还没有真实字段信息, 如果有需要修改, 这个页面涉及verify_status的功能都要注意
69 const status_title = computed(() => { 65 const status_title = computed(() => {
...@@ -85,16 +81,9 @@ const status_icon_color = computed(() => { ...@@ -85,16 +81,9 @@ const status_icon_color = computed(() => {
85 return '#A67939' 81 return '#A67939'
86 }) 82 })
87 83
88 -const verify_result_text = computed(() => { 84 +const can_redeem_text = computed(() => {
89 - const data = verify_result.value 85 + if (store?.appUserInfo?.can_redeem === true) return '已授权'
90 - if (data === null || data === undefined) return '' 86 + return '未授权'
91 - if (typeof data === 'string') return data
92 - if (typeof data === 'number' || typeof data === 'boolean') return String(data)
93 - try {
94 - return JSON.stringify(data, null, 2)
95 - } catch (e) {
96 - return ''
97 - }
98 }) 87 })
99 88
100 const verify_ticket = async (code) => { 89 const verify_ticket = async (code) => {
...@@ -102,26 +91,19 @@ const verify_ticket = async (code) => { ...@@ -102,26 +91,19 @@ const verify_ticket = async (code) => {
102 if (verify_status.value === 'verifying') return 91 if (verify_status.value === 'verifying') return
103 92
104 verify_code.value = code 93 verify_code.value = code
105 - verify_result.value = null
106 verify_status.value = 'verifying' 94 verify_status.value = 'verifying'
107 msg.value = '核销中...' 95 msg.value = '核销中...'
108 96
109 Taro.showLoading({ title: '核销中...' }) 97 Taro.showLoading({ title: '核销中...' })
110 try { 98 try {
111 - const res = await verifyTicketAPI({ code }) 99 + const res = await verifyTicketAPI({ qr_code: code })
112 if (res?.code === 1) { 100 if (res?.code === 1) {
113 verify_status.value = 'success' 101 verify_status.value = 'success'
114 msg.value = res?.msg || '核销成功' 102 msg.value = res?.msg || '核销成功'
115 - if (res?.data !== undefined && res?.data !== null && res?.data !== '') {
116 - verify_result.value = res.data
117 - }
118 return 103 return
119 } 104 }
120 verify_status.value = 'fail' 105 verify_status.value = 'fail'
121 msg.value = res?.msg || '核销失败' 106 msg.value = res?.msg || '核销失败'
122 - if (res?.data !== undefined && res?.data !== null && res?.data !== '') {
123 - verify_result.value = res.data
124 - }
125 Taro.showToast({ title: msg.value, icon: 'none' }) 107 Taro.showToast({ title: msg.value, icon: 'none' })
126 } catch (e) { 108 } catch (e) {
127 verify_status.value = 'fail' 109 verify_status.value = 'fail'
...@@ -133,10 +115,21 @@ const verify_ticket = async (code) => { ...@@ -133,10 +115,21 @@ const verify_ticket = async (code) => {
133 } 115 }
134 116
135 useDidShow(async () => { 117 useDidShow(async () => {
118 + const permission_res = await checkRedeemPermissionAPI()
119 + if (permission_res?.code !== 1) {
120 + replace('volunteerLogin')
121 + return
122 + }
123 +
124 + if (permission_res?.data) store.changeUserInfo(permission_res.data)
125 + if (permission_res?.data?.can_redeem !== true) {
126 + replace('volunteerLogin')
127 + return
128 + }
129 +
136 const code = router?.params?.result || '' 130 const code = router?.params?.result || ''
137 if (!code) { 131 if (!code) {
138 verify_code.value = '' 132 verify_code.value = ''
139 - verify_result.value = null
140 verify_status.value = 'idle' 133 verify_status.value = 'idle'
141 msg.value = '请点击下方按钮进行核销' 134 msg.value = '请点击下方按钮进行核销'
142 return 135 return
......
1 <!-- 1 <!--
2 * @Date: 2026-01-08 13:01:56 2 * @Date: 2026-01-08 13:01:56
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2026-01-14 10:18:59 4 + * @LastEditTime: 2026-01-14 20:49:12
5 * @FilePath: /xyxBooking-weapp/src/pages/volunteerLogin/index.vue 5 * @FilePath: /xyxBooking-weapp/src/pages/volunteerLogin/index.vue
6 * @Description: 义工登录页面 6 * @Description: 义工登录页面
7 --> 7 -->
...@@ -17,23 +17,13 @@ ...@@ -17,23 +17,13 @@
17 17
18 <view class="input-group"> 18 <view class="input-group">
19 <text class="label">账号</text> 19 <text class="label">账号</text>
20 - <input 20 + <input v-model="username" placeholder="请输入账号" placeholder-class="input-placeholder" cursorSpacing="40rpx" />
21 - v-model="username"
22 - placeholder="请输入账号"
23 - placeholder-class="input-placeholder"
24 - cursorSpacing="40rpx"
25 - />
26 </view> 21 </view>
27 22
28 <view class="input-group"> 23 <view class="input-group">
29 <text class="label">密码</text> 24 <text class="label">密码</text>
30 - <input 25 + <input v-model="password" password placeholder="请输入密码" placeholder-class="input-placeholder"
31 - v-model="password" 26 + cursorSpacing="40rpx" />
32 - password
33 - placeholder="请输入密码"
34 - placeholder-class="input-placeholder"
35 - cursorSpacing="40rpx"
36 - />
37 </view> 27 </view>
38 28
39 <button class="login-btn" @tap="handleLogin">立即登录</button> 29 <button class="login-btn" @tap="handleLogin">立即登录</button>
...@@ -45,7 +35,7 @@ ...@@ -45,7 +35,7 @@
45 import { ref } from 'vue' 35 import { ref } from 'vue'
46 import Taro, { useDidShow } from '@tarojs/taro' 36 import Taro, { useDidShow } from '@tarojs/taro'
47 import { mainStore } from '@/stores/main' 37 import { mainStore } from '@/stores/main'
48 -import { volunteerLoginAPI, getUserInfoAPI } from '@/api/index' 38 +import { volunteerLoginAPI, checkRedeemPermissionAPI } from '@/api/index'
49 import { useReplace } from '@/hooks/useGo' 39 import { useReplace } from '@/hooks/useGo'
50 import logo from '@/assets/images/logo.png' 40 import logo from '@/assets/images/logo.png'
51 41
...@@ -55,26 +45,22 @@ const username = ref('') ...@@ -55,26 +45,22 @@ const username = ref('')
55 const password = ref('') 45 const password = ref('')
56 46
57 /** 47 /**
58 - * @description: 检查用户权限并根据角色重定向 48 + * @description: 检查核销权限并重定向
59 */ 49 */
60 50
61 const check_permission_and_redirect = async () => { 51 const check_permission_and_redirect = async () => {
62 try { 52 try {
63 - const user_res = await getUserInfoAPI() 53 + const permission_res = await checkRedeemPermissionAPI()
64 - if (user_res?.code === 1 && user_res?.data) { 54 + if (permission_res?.code !== 1) return
65 - store.changeUserInfo(user_res.data) 55 + if (permission_res?.data) store.changeUserInfo(permission_res.data)
66 - if (store.isVolunteer) { 56 + if (permission_res?.data?.can_redeem === true) replace('verificationResult')
67 - replace('verificationResult') 57 + } catch (e) { }
68 - }
69 - }
70 - } catch (e) {}
71 } 58 }
72 59
73 useDidShow(() => { 60 useDidShow(() => {
74 check_permission_and_redirect() 61 check_permission_and_redirect()
75 }) 62 })
76 63
77 -// TODO: 功能需要联调, 登录功能都是模拟, 实际登录需要后端接口支持
78 const handleLogin = async () => { 64 const handleLogin = async () => {
79 if (!username.value || !password.value) { 65 if (!username.value || !password.value) {
80 Taro.showToast({ title: '请输入账号密码', icon: 'none' }) 66 Taro.showToast({ title: '请输入账号密码', icon: 'none' })
...@@ -82,34 +68,32 @@ const handleLogin = async () => { ...@@ -82,34 +68,32 @@ const handleLogin = async () => {
82 } 68 }
83 69
84 Taro.showLoading({ title: '登录中...' }) 70 Taro.showLoading({ title: '登录中...' })
85 - // 1. 执行登录 71 + const login_res = await volunteerLoginAPI({ username: username.value, password: password.value })
86 - const loginRes = await volunteerLoginAPI({ username: username.value, password: password.value })
87 -
88 - if (loginRes.code === 1) {
89 - // 2. 登录成功后,获取用户信息来验证和更新状态
90 - const userRes = await getUserInfoAPI()
91 Taro.hideLoading() 72 Taro.hideLoading()
92 73
93 - if (userRes.code === 1 && userRes.data) { 74 + if (login_res?.code !== 1) {
94 - // 更新 store 中的用户信息 (isVolunteer 会自动通过 getter 更新) 75 + Taro.showToast({ title: login_res?.msg || '登录失败', icon: 'none' })
95 - store.changeUserInfo(userRes.data) 76 + return
96 -
97 - // TODO: 这是模拟功能, 检查是否为义工账号
98 - if (store.isVolunteer) {
99 - Taro.showToast({ title: '登录成功', icon: 'success' })
100 - setTimeout(() => {
101 - replace('verificationResult')
102 - }, 1500)
103 - } else {
104 - Taro.showToast({ title: '非义工账号', icon: 'none' })
105 - }
106 - } else {
107 - Taro.showToast({ title: '获取用户信息失败', icon: 'none' })
108 } 77 }
109 - } else { 78 +
79 + Taro.showLoading({ title: '校验权限中...' })
80 + const permission_res = await checkRedeemPermissionAPI()
110 Taro.hideLoading() 81 Taro.hideLoading()
111 - Taro.showToast({ title: loginRes.msg || '登录失败', icon: 'none' }) 82 +
83 + if (permission_res?.code !== 1) {
84 + Taro.showToast({ title: permission_res?.msg || '权限校验失败', icon: 'none' })
85 + return
86 + }
87 +
88 + if (permission_res?.data) store.changeUserInfo(permission_res.data)
89 +
90 + if (permission_res?.data?.can_redeem === true) {
91 + Taro.showToast({ title: permission_res?.msg || login_res?.msg || '登录成功', icon: 'success' })
92 + setTimeout(() => replace('verificationResult'), 1200)
93 + return
112 } 94 }
95 +
96 + Taro.showToast({ title: permission_res?.msg || '暂无核销权限', icon: 'none' })
113 } 97 }
114 </script> 98 </script>
115 99
...@@ -127,14 +111,16 @@ const handleLogin = async () => { ...@@ -127,14 +111,16 @@ const handleLogin = async () => {
127 flex-direction: column; 111 flex-direction: column;
128 align-items: center; 112 align-items: center;
129 margin-bottom: 60rpx; 113 margin-bottom: 60rpx;
114 +
130 image { 115 image {
131 width: 120rpx; 116 width: 120rpx;
132 height: 120rpx; 117 height: 120rpx;
133 border-radius: 50%; 118 border-radius: 50%;
134 margin-bottom: 24rpx; 119 margin-bottom: 24rpx;
135 - box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.1); 120 + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
136 background-color: #fff; 121 background-color: #fff;
137 } 122 }
123 +
138 .app-name { 124 .app-name {
139 font-size: 36rpx; 125 font-size: 36rpx;
140 font-weight: 600; 126 font-weight: 600;
...@@ -148,7 +134,7 @@ const handleLogin = async () => { ...@@ -148,7 +134,7 @@ const handleLogin = async () => {
148 background: #fff; 134 background: #fff;
149 border-radius: 24rpx; 135 border-radius: 24rpx;
150 padding: 60rpx 40rpx; 136 padding: 60rpx 40rpx;
151 - box-shadow: 0 12rpx 40rpx rgba(0,0,0,0.04); 137 + box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.04);
152 box-sizing: border-box; 138 box-sizing: border-box;
153 139
154 .title { 140 .title {
......
1 /* 1 /*
2 * @Date: 2022-04-18 15:59:42 2 * @Date: 2022-04-18 15:59:42
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2024-01-30 15:26:30 4 + * @LastEditTime: 2026-01-14 20:43:54
5 * @FilePath: /xyxBooking-weapp/src/stores/main.js 5 * @FilePath: /xyxBooking-weapp/src/stores/main.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -18,9 +18,9 @@ export const mainStore = defineStore('main', { ...@@ -18,9 +18,9 @@ export const mainStore = defineStore('main', {
18 }; 18 };
19 }, 19 },
20 getters: { 20 getters: {
21 - // 判断是否为义工 (基于 appUserInfo 中的 role 字段) 21 + // 判断是否为义工
22 isVolunteer: (state) => { 22 isVolunteer: (state) => {
23 - return state.appUserInfo && state.appUserInfo.role === 'volunteer'; 23 + return !!(state.appUserInfo && (state.appUserInfo.can_redeem === true));
24 }, 24 },
25 }, 25 },
26 actions: { 26 actions: {
......