forked from gxwebsoft/mp-10550
feat(glt): 完善水票总数获取逻辑并优化用户体验
- 新增 normalizeTotal 函数处理多种数据格式的总数解析 - 支持通过 userId 参数查询指定用户的水票总数 - 添加多服务器地址尝试机制提高接口可用性 - 优化首页和用户中心页面的水票总数加载逻辑 - 修复水票页面滚动区域高度计算问题 - 移除自动跳转登录页面的定时器逻辑 - 个人中心页面支持下拉刷新统计数据 - 统一请求参数传递方式简化代码结构
This commit is contained in:
@@ -1,6 +1,30 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { GltUserTicket, GltUserTicketParam } from './model';
|
import type { GltUserTicket, GltUserTicketParam } from './model';
|
||||||
|
import { SERVER_API_URL } from '@/utils/server'
|
||||||
|
|
||||||
|
function normalizeTotal(input: unknown): number {
|
||||||
|
if (typeof input === 'number' && Number.isFinite(input)) return input;
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
const n = Number(input);
|
||||||
|
if (Number.isFinite(n)) return n;
|
||||||
|
}
|
||||||
|
if (input && typeof input === 'object') {
|
||||||
|
const obj: any = input;
|
||||||
|
// Common shapes from different backends.
|
||||||
|
for (const key of ['total', 'count', 'value', 'num', 'ticketTotal', 'totalQty']) {
|
||||||
|
const v = obj?.[key];
|
||||||
|
const n = normalizeTotal(v);
|
||||||
|
if (n) return n;
|
||||||
|
}
|
||||||
|
// Sometimes nested: { data: { total: ... } } / { data: 12 }
|
||||||
|
if ('data' in obj) {
|
||||||
|
const n = normalizeTotal(obj.data);
|
||||||
|
if (n) return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询我的水票
|
* 分页查询我的水票
|
||||||
@@ -8,9 +32,7 @@ import type { GltUserTicket, GltUserTicketParam } from './model';
|
|||||||
export async function pageGltUserTicket(params: GltUserTicketParam) {
|
export async function pageGltUserTicket(params: GltUserTicketParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<GltUserTicket>>>(
|
const res = await request.get<ApiResult<PageResult<GltUserTicket>>>(
|
||||||
'/glt/glt-user-ticket/page',
|
'/glt/glt-user-ticket/page',
|
||||||
{
|
params
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -24,9 +46,7 @@ export async function pageGltUserTicket(params: GltUserTicketParam) {
|
|||||||
export async function listGltUserTicket(params?: GltUserTicketParam) {
|
export async function listGltUserTicket(params?: GltUserTicketParam) {
|
||||||
const res = await request.get<ApiResult<GltUserTicket[]>>(
|
const res = await request.get<ApiResult<GltUserTicket[]>>(
|
||||||
'/glt/glt-user-ticket',
|
'/glt/glt-user-ticket',
|
||||||
{
|
params
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -107,15 +127,46 @@ export async function getGltUserTicket(id: number) {
|
|||||||
/**
|
/**
|
||||||
* 获取我的水票总数
|
* 获取我的水票总数
|
||||||
*/
|
*/
|
||||||
export async function getMyGltUserTicketTotal() {
|
export async function getMyGltUserTicketTotal(userId?: number) {
|
||||||
const res = await request.get<ApiResult<number | { total?: number }>>(
|
const params = userId ? { userId } : undefined
|
||||||
'/glt/glt-user-ticket/my-total'
|
|
||||||
);
|
const extract = (res: any) => {
|
||||||
if (res.code === 0) {
|
// Some backends may return a raw number instead of ApiResult.
|
||||||
const data: any = res.data;
|
if (typeof res === 'number' || typeof res === 'string') return normalizeTotal(res)
|
||||||
if (typeof data === 'number') return data;
|
if (res && typeof res === 'object' && 'code' in res) {
|
||||||
if (data && typeof data.total === 'number') return data.total;
|
const apiRes = res as ApiResult<unknown>
|
||||||
return 0;
|
if (apiRes.code === 0) return normalizeTotal(apiRes.data)
|
||||||
|
throw new Error(apiRes.message)
|
||||||
|
}
|
||||||
|
return normalizeTotal(res)
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
|
// Try both the configured BaseUrl host and the auth-server host.
|
||||||
|
// If the first one returns 0, keep trying; some tenants deploy GLT on a different host.
|
||||||
|
const urls = [
|
||||||
|
'/glt/glt-user-ticket/my-total',
|
||||||
|
`${SERVER_API_URL}/glt/glt-user-ticket/my-total`,
|
||||||
|
]
|
||||||
|
|
||||||
|
let lastError: unknown
|
||||||
|
let firstTotal: number | undefined
|
||||||
|
for (const url of urls) {
|
||||||
|
try {
|
||||||
|
const res = await request.get<any>(url, params)
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('[getMyGltUserTicketTotal] response:', { url, res })
|
||||||
|
}
|
||||||
|
const total = extract(res)
|
||||||
|
if (firstTotal === undefined) firstTotal = total
|
||||||
|
if (total) return total
|
||||||
|
} catch (e) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[getMyGltUserTicketTotal] failed:', { url, error: e })
|
||||||
|
}
|
||||||
|
lastError = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstTotal !== undefined) return firstTotal
|
||||||
|
return Promise.reject(lastError instanceof Error ? lastError : new Error('获取水票总数失败'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import type { GltUserTicketLog, GltUserTicketLogParam } from './model';
|
|||||||
export async function pageGltUserTicketLog(params: GltUserTicketLogParam) {
|
export async function pageGltUserTicketLog(params: GltUserTicketLogParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<GltUserTicketLog>>>(
|
const res = await request.get<ApiResult<PageResult<GltUserTicketLog>>>(
|
||||||
'/glt/glt-user-ticket-log/page',
|
'/glt/glt-user-ticket-log/page',
|
||||||
{
|
params
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -24,9 +22,7 @@ export async function pageGltUserTicketLog(params: GltUserTicketLogParam) {
|
|||||||
export async function listGltUserTicketLog(params?: GltUserTicketLogParam) {
|
export async function listGltUserTicketLog(params?: GltUserTicketLogParam) {
|
||||||
const res = await request.get<ApiResult<GltUserTicketLog[]>>(
|
const res = await request.get<ApiResult<GltUserTicketLog[]>>(
|
||||||
'/glt/glt-user-ticket-log',
|
'/glt/glt-user-ticket-log',
|
||||||
{
|
params
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ export interface GltUserTicketLog {
|
|||||||
export interface GltUserTicketLogParam extends PageParam {
|
export interface GltUserTicketLogParam extends PageParam {
|
||||||
id?: number;
|
id?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
|
userId?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,14 @@ function Home() {
|
|||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
const token = Taro.getStorageSync('access_token')
|
const token = Taro.getStorageSync('access_token')
|
||||||
if (!token) {
|
const userIdRaw = Taro.getStorageSync('UserId')
|
||||||
|
const userId = Number(userIdRaw)
|
||||||
|
const hasUserId = Number.isFinite(userId) && userId > 0
|
||||||
|
if (!token && !hasUserId) {
|
||||||
setTicketTotal(0)
|
setTicketTotal(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getMyGltUserTicketTotal()
|
getMyGltUserTicketTotal(hasUserId ? userId : undefined)
|
||||||
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('首页水票总数加载失败:', err)
|
console.error('首页水票总数加载失败:', err)
|
||||||
|
|||||||
@@ -29,18 +29,21 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
const handleRefresh = async () => {
|
const reloadStats = async (showToast = false) => {
|
||||||
await refresh()
|
await refresh()
|
||||||
reloadTicketTotal()
|
reloadTicketTotal()
|
||||||
Taro.showToast({
|
if (showToast) {
|
||||||
title: '刷新成功',
|
Taro.showToast({
|
||||||
icon: 'success'
|
title: '刷新成功',
|
||||||
})
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleRefresh
|
handleRefresh: () => reloadStats(true),
|
||||||
|
reloadStats
|
||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,11 +68,14 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
|
|
||||||
const reloadTicketTotal = () => {
|
const reloadTicketTotal = () => {
|
||||||
const token = Taro.getStorageSync('access_token')
|
const token = Taro.getStorageSync('access_token')
|
||||||
if (!token) {
|
const userIdRaw = Taro.getStorageSync('UserId')
|
||||||
|
const userId = Number(userIdRaw)
|
||||||
|
const hasUserId = Number.isFinite(userId) && userId > 0
|
||||||
|
if (!token && !hasUserId) {
|
||||||
setTicketTotal(0)
|
setTicketTotal(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
getMyGltUserTicketTotal()
|
getMyGltUserTicketTotal(hasUserId ? userId : undefined)
|
||||||
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('个人中心水票总数加载失败:', err)
|
console.error('个人中心水票总数加载失败:', err)
|
||||||
@@ -302,7 +308,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
|||||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.coupons || 0}</Text>
|
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.coupons || 0}</Text>
|
||||||
</View>
|
</View>
|
||||||
<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/ticket/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}>{ticketTotal}</Text>
|
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{ticketTotal}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import './user.scss'
|
|||||||
import IsDealer from "./components/IsDealer";
|
import IsDealer from "./components/IsDealer";
|
||||||
import {useThemeStyles} from "@/hooks/useTheme";
|
import {useThemeStyles} from "@/hooks/useTheme";
|
||||||
import UserGrid from "@/pages/user/components/UserGrid";
|
import UserGrid from "@/pages/user/components/UserGrid";
|
||||||
|
import { useDidShow } from '@tarojs/taro'
|
||||||
|
|
||||||
function User() {
|
function User() {
|
||||||
|
|
||||||
@@ -24,6 +25,11 @@ function User() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 每次进入/切回个人中心都刷新一次统计(包含水票数量)
|
||||||
|
useDidShow(() => {
|
||||||
|
userCardRef.current?.reloadStats?.()
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PullToRefresh
|
<PullToRefresh
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
|
|||||||
@@ -156,6 +156,14 @@ const UserTicketList = () => {
|
|||||||
const reloadLogs = async (isRefresh = true, keywords?: string) => {
|
const reloadLogs = async (isRefresh = true, keywords?: string) => {
|
||||||
if (logLoading) return;
|
if (logLoading) return;
|
||||||
|
|
||||||
|
const userId = getUserId();
|
||||||
|
if (!userId) {
|
||||||
|
setLogList([]);
|
||||||
|
setLogTotal(0);
|
||||||
|
setLogHasMore(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
setLogPage(1);
|
setLogPage(1);
|
||||||
setLogList([]);
|
setLogList([]);
|
||||||
@@ -168,6 +176,7 @@ const UserTicketList = () => {
|
|||||||
const res = await pageGltUserTicketLog({
|
const res = await pageGltUserTicketLog({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
|
userId,
|
||||||
keywords: (keywords ?? searchValue) || undefined
|
keywords: (keywords ?? searchValue) || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -285,11 +294,11 @@ const UserTicketList = () => {
|
|||||||
|
|
||||||
{/* 列表 */}
|
{/* 列表 */}
|
||||||
<PullToRefresh onRefresh={handleRefresh} headHeight={60}>
|
<PullToRefresh onRefresh={handleRefresh} headHeight={60}>
|
||||||
<View style={{ height: 'calc(100vh - 200px)', overflowY: 'auto' }} id="ticket-scroll">
|
<View style={{ height: 'calc(100vh)', overflowY: 'auto' }} id="ticket-scroll">
|
||||||
{activeTab === 'ticket' && ticketList.length === 0 && !ticketLoading ? (
|
{activeTab === 'ticket' && ticketList.length === 0 && !ticketLoading ? (
|
||||||
<View
|
<View
|
||||||
className="flex flex-col justify-center items-center"
|
className="flex flex-col justify-center items-center"
|
||||||
style={{ height: 'calc(100vh - 260px)' }}
|
style={{ height: 'calc(100vh - 160px)' }}
|
||||||
>
|
>
|
||||||
<Empty
|
<Empty
|
||||||
description="暂无水票"
|
description="暂无水票"
|
||||||
@@ -299,7 +308,7 @@ const UserTicketList = () => {
|
|||||||
) : activeTab === 'log' && logList.length === 0 && !logLoading ? (
|
) : activeTab === 'log' && logList.length === 0 && !logLoading ? (
|
||||||
<View
|
<View
|
||||||
className="flex flex-col justify-center items-center"
|
className="flex flex-col justify-center items-center"
|
||||||
style={{ height: 'calc(100vh - 260px)' }}
|
style={{ height: 'calc(100vh - 160px)' }}
|
||||||
>
|
>
|
||||||
<Empty description="暂无核销记录" style={{ backgroundColor: 'transparent' }} />
|
<Empty description="暂无核销记录" style={{ backgroundColor: 'transparent' }} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -182,10 +182,10 @@ const handleAuthError = () => {
|
|||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
});
|
});
|
||||||
|
//
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
Taro.reLaunch({ url: '/passport/login' });
|
// Taro.reLaunch({ url: '/passport/login' });
|
||||||
}, 2000);
|
// }, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 错误处理
|
// 错误处理
|
||||||
|
|||||||
Reference in New Issue
Block a user