hookehuyr

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

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