feat(rider): 新增水票核销功能

- 添加水票核销扫码页面,支持扫描加密和明文二维码
- 实现水票验证逻辑,包括余额检查和核销确认
- 添加核销记录展示,最多保留最近10条记录
- 在骑手端界面增加水票核销入口
- 新增获取用户水票总数的API接口
- 优化首页轮播图加载,增加缓存和懒加载机制
- 添加门店选择功能,支持订单确认页切换门店
- 修复物流信息类型安全问题
- 更新用户中心门店相关文案显示
This commit is contained in:
2026-02-05 01:08:37 +08:00
parent fcbaa970d0
commit 8679b26f74
11 changed files with 454 additions and 39 deletions

View File

@@ -6,6 +6,7 @@ import {Image} from '@nutui/nutui-react-taro'
import {getCmsAdByCode} from "@/api/cms/cmsAd";
import navTo from "@/utils/common";
import {pageCmsArticle} from "@/api/cms/cmsArticle";
import Taro from '@tarojs/taro'
type AdImage = {
url?: string
@@ -41,41 +42,45 @@ const MyPage = () => {
const [loading, setLoading] = useState(true)
// const [disableSwiper, setDisableSwiper] = useState(false)
const CACHE_KEY = 'home_banner_mp-ad'
// 用于记录触摸开始位置
// const touchStartRef = useRef({x: 0, y: 0})
// 加载数据
const loadData = async () => {
setLoading(true)
const loadData = async (opts?: {silent?: boolean}) => {
if (!opts?.silent) setLoading(true)
try {
const [flashRes] = await Promise.allSettled([
getCmsAdByCode('mp-ad'),
getCmsAdByCode('hot_today'),
pageCmsArticle({ limit: 1, recommend: 1 }),
])
if (flashRes.status === 'fulfilled') {
console.log('flashflashflash', flashRes.value)
setCarouselData(flashRes.value)
} else {
console.error('Failed to fetch flash:', flashRes.reason)
}
// 只阻塞 banner 自己的数据;其他数据预热不应影响首屏展示速度
const flash = await getCmsAdByCode('mp-ad')
setCarouselData(flash)
void Taro.setStorage({ key: CACHE_KEY, data: flash }).catch(() => {})
} catch (error) {
console.error('Banner数据加载失败:', error)
} finally {
setLoading(false)
if (!opts?.silent) setLoading(false)
}
// 后台预热(不阻塞 banner 渲染)
void getCmsAdByCode('hot_today').catch(() => {})
void pageCmsArticle({ limit: 1, recommend: 1 }).catch(() => {})
}
useEffect(() => {
loadData().then()
const cached = Taro.getStorageSync(CACHE_KEY) as CmsAd | undefined
// 有缓存则先渲染缓存,避免首屏等待;再静默刷新
if (cached && normalizeAdImages(cached).length) {
setCarouselData(cached)
setLoading(false)
void loadData({ silent: true })
return
}
void loadData()
}, [])
// 轮播图高度默认300px
const carouselHeight = toNumberPx(carouselData?.height, 300)
const carouselImages = normalizeAdImages(carouselData)
console.log(carouselImages,'carouselImages')
// 骨架屏组件
const BannerSkeleton = () => (
@@ -149,7 +154,8 @@ const MyPage = () => {
src={src}
mode={'scaleToFill'}
onClick={() => (img.path ? navTo(`${img.path}`) : undefined)}
lazyLoad={false}
// 首张图优先加载,其余按需懒加载,避免并发图片请求拖慢首屏可见
lazyLoad={index !== 0}
style={{
height: `${carouselHeight}px`,
borderRadius: '4px',

View File

@@ -1,6 +1,6 @@
import Header from './Header'
import Banner from './Banner'
import Taro, { useShareAppMessage } from '@tarojs/taro'
import Taro, { useDidShow, useShareAppMessage } from '@tarojs/taro'
import { View, Text, Image, ScrollView } from '@tarojs/components'
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { Cart, Coupon, Gift, Ticket } from '@nutui/icons-react-taro'
@@ -8,11 +8,13 @@ import { getShopInfo } from '@/api/layout'
import { checkAndHandleInviteRelation, hasPendingInvite } from '@/utils/invite'
import { pageShopGoods } from '@/api/shop/shopGoods'
import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model'
import { getMyGltUserTicketTotal } from '@/api/glt/gltUserTicket'
import './index.scss'
function Home() {
const [activeTabKey, setActiveTabKey] = useState('recommend')
const [goodsList, setGoodsList] = useState<ShopGoods[]>([])
const [ticketTotal, setTicketTotal] = useState(0)
useShareAppMessage(() => {
// 获取当前用户ID用于生成邀请链接
@@ -87,9 +89,24 @@ function Home() {
// const handleTabsStickyChange = (isSticky: boolean) => {}
const reload = () => {
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)
})
};
// 回到首页/首次进入时都刷新一次(避免依赖 scope.userInfo 导致不触发 reload
useDidShow(() => {
reload()
})
useEffect(() => {
// 获取站点信息
getShopInfo().then(() => {
@@ -132,7 +149,6 @@ function Home() {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
@@ -227,7 +243,7 @@ function Home() {
<View className="ticket-card__head">
<Text className="ticket-card__title"></Text>
<Text className="ticket-card__count">
<Text className="ticket-card__countNum">0</Text>
<Text className="ticket-card__countNum">{ticketTotal}</Text>
</Text>
</View>