forked from gxwebsoft/mp-10550
feat(rider): 实现水票核销页面自动扫描功能
- 在水票核销页面添加自动扫描模式支持 - 添加路由参数检测实现自动开启扫码功能 - 添加首次展示时自动触发扫码逻辑 - 修改用户卡片组件显示实际水票总数而非礼品卡数量 - 添加独立的水票总数刷新机制 - 在用户登录和信息刷新时同步更新水票总数
This commit is contained in:
@@ -12,12 +12,14 @@ import {getStoredInviteParams} from "@/utils/invite";
|
|||||||
import UnifiedQRButton from "@/components/UnifiedQRButton";
|
import UnifiedQRButton from "@/components/UnifiedQRButton";
|
||||||
import {useThemeStyles} from "@/hooks/useTheme";
|
import {useThemeStyles} from "@/hooks/useTheme";
|
||||||
import {getRootDomain} from "@/utils/domain";
|
import {getRootDomain} from "@/utils/domain";
|
||||||
|
import { getMyGltUserTicketTotal } from '@/api/glt/gltUserTicket'
|
||||||
|
|
||||||
const UserCard = forwardRef<any, any>((_, ref) => {
|
const UserCard = forwardRef<any, any>((_, ref) => {
|
||||||
const {data, refresh} = useUserData()
|
const {data, refresh} = useUserData()
|
||||||
const {getDisplayName} = useUser();
|
const {getDisplayName} = useUser();
|
||||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||||
const [userInfo, setUserInfo] = useState<User>()
|
const [userInfo, setUserInfo] = useState<User>()
|
||||||
|
const [ticketTotal, setTicketTotal] = useState<number>(0)
|
||||||
|
|
||||||
const themeStyles = useThemeStyles();
|
const themeStyles = useThemeStyles();
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
await refresh()
|
await refresh()
|
||||||
|
reloadTicketTotal()
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '刷新成功',
|
title: '刷新成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
@@ -41,6 +44,9 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 独立于用户信息授权:只要有登录 token,就可以拉取水票总数
|
||||||
|
reloadTicketTotal()
|
||||||
|
|
||||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||||
Taro.getSetting({
|
Taro.getSetting({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
@@ -57,6 +63,20 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const reloadTicketTotal = () => {
|
||||||
|
const token = Taro.getStorageSync('access_token')
|
||||||
|
if (!token) {
|
||||||
|
setTicketTotal(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getMyGltUserTicketTotal()
|
||||||
|
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('个人中心水票总数加载失败:', err)
|
||||||
|
setTicketTotal(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
Taro.getUserInfo({
|
Taro.getUserInfo({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
@@ -73,6 +93,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
Taro.setStorageSync('UserId', data.userId)
|
Taro.setStorageSync('UserId', data.userId)
|
||||||
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
||||||
refresh().then()
|
refresh().then()
|
||||||
|
reloadTicketTotal()
|
||||||
|
|
||||||
// 获取openId
|
// 获取openId
|
||||||
if (!data.openid) {
|
if (!data.openid) {
|
||||||
@@ -172,6 +193,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
setIsLogin(true)
|
setIsLogin(true)
|
||||||
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
||||||
refresh().then()
|
refresh().then()
|
||||||
|
reloadTicketTotal()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -282,7 +304,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
<View className={'item flex justify-center flex-col items-center'}
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
onClick={() => navTo('/user/gift/index', true)}>
|
onClick={() => navTo('/user/gift/index', true)}>
|
||||||
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>水票</Text>
|
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>水票</Text>
|
||||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.giftCards || 0}</Text>
|
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{ticketTotal}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
|
||||||
<Grid.Item text={'水票核销'} onClick={() => navigateToPage('/rider/ticket/verification/index')}>
|
<Grid.Item text={'水票核销'} onClick={() => navigateToPage('/rider/ticket/verification/index?auto=1')}>
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
<View className="w-12 h-12 bg-cyan-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
<View className="w-12 h-12 bg-cyan-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
<Scan color="#06b6d4" size="20" />
|
<Scan color="#06b6d4" size="20" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
|
||||||
import { Button, Card, ConfigProvider } from '@nutui/nutui-react-taro'
|
import { Button, Card, ConfigProvider } from '@nutui/nutui-react-taro'
|
||||||
import { Scan, Success, Failure, Tips } from '@nutui/icons-react-taro'
|
import { Scan, Success, Failure, Tips } from '@nutui/icons-react-taro'
|
||||||
|
|
||||||
@@ -29,11 +29,14 @@ type VerifyRecord = {
|
|||||||
|
|
||||||
const RiderTicketVerificationPage: React.FC = () => {
|
const RiderTicketVerificationPage: React.FC = () => {
|
||||||
const { hasRole, isAdmin } = useUser()
|
const { hasRole, isAdmin } = useUser()
|
||||||
|
const router = useRouter()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [lastTicket, setLastTicket] = useState<GltUserTicket | null>(null)
|
const [lastTicket, setLastTicket] = useState<GltUserTicket | null>(null)
|
||||||
const [lastQty, setLastQty] = useState<number>(1)
|
const [lastQty, setLastQty] = useState<number>(1)
|
||||||
const [records, setRecords] = useState<VerifyRecord[]>([])
|
const [records, setRecords] = useState<VerifyRecord[]>([])
|
||||||
|
|
||||||
|
const autoScanOnceRef = useRef(false)
|
||||||
|
|
||||||
const canVerify = useMemo(() => {
|
const canVerify = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
hasRole('rider') ||
|
hasRole('rider') ||
|
||||||
@@ -44,6 +47,11 @@ const RiderTicketVerificationPage: React.FC = () => {
|
|||||||
)
|
)
|
||||||
}, [hasRole, isAdmin])
|
}, [hasRole, isAdmin])
|
||||||
|
|
||||||
|
const autoScanEnabled = useMemo(() => {
|
||||||
|
const p: any = router?.params || {}
|
||||||
|
return p.auto === '1' || p.auto === 'true'
|
||||||
|
}, [router])
|
||||||
|
|
||||||
const addRecord = (rec: Omit<VerifyRecord, 'id' | 'time'>) => {
|
const addRecord = (rec: Omit<VerifyRecord, 'id' | 'time'>) => {
|
||||||
const item: VerifyRecord = {
|
const item: VerifyRecord = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@@ -162,6 +170,25 @@ const RiderTicketVerificationPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If navigated in "auto" mode, open scan on first show when user has permission.
|
||||||
|
useDidShow(() => {
|
||||||
|
// Reset the flag when user manually re-enters the page via navigation again.
|
||||||
|
// (This runs on every show; only the first show with auto enabled will trigger scan.)
|
||||||
|
if (!autoScanEnabled) autoScanOnceRef.current = false
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoScanEnabled) return
|
||||||
|
if (autoScanOnceRef.current) return
|
||||||
|
if (!canVerify) return
|
||||||
|
autoScanOnceRef.current = true
|
||||||
|
// Defer to ensure page is fully mounted before opening camera.
|
||||||
|
setTimeout(() => {
|
||||||
|
handleScan().catch(() => {})
|
||||||
|
}, 80)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [autoScanEnabled, canVerify])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<View className="min-h-screen bg-gray-50 p-4">
|
<View className="min-h-screen bg-gray-50 p-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user