Files
xinlong-shop-taro/src/pages/user/points-record.tsx
赵忠林 f3886664f7 fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top
- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api
- 更新图片上传接口地址为新的 guilixu-api 域名
- 修改用户推广页面中邀请码链接和二维码接口的域名
- 更改注册页微信登录接口请求的域名为 guilixu-api
2026-06-16 17:15:59 +08:00

261 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useRequest } from '@/hooks/useRequest'
import { pageUserPointsLog, getUserPointsInfo, type UserPointsInfo } from '@/api/system/user/points'
definePageConfig({
navigationBarTitleText: '积分明细',
enablePullDownRefresh: true,
})
// 积分类型映射
const TYPE_MAP: Record<number, { label: string; icon: string; color: string; bgColor: string }> = {
1: { label: '获得', icon: '📈', color: 'text-green-600', bgColor: 'bg-green-50' },
2: { label: '消费', icon: '📉', color: 'text-red-500', bgColor: 'bg-red-50' },
3: { label: '过期', icon: '⏰', color: 'text-gray-500', bgColor: 'bg-gray-50' },
4: { label: '调整', icon: '🔧', color: 'text-blue-500', bgColor: 'bg-blue-50' },
}
const tabs = [
{ label: '全部', value: -1 },
{ label: '获得', value: 1 },
{ label: '消费', value: 2 },
{ label: '过期', value: 3 },
]
const PointsRecordPage: React.FC = () => {
const [activeTab, setActiveTab] = useState(-1)
const [page, setPage] = useState(1)
const [logs, setLogs] = useState<UserPointsLog[]>([])
const [hasMore, setHasMore] = useState(true)
const [pointsInfo, setPointsInfo] = useState<UserPointsInfo>({ points: 0, totalEarned: 0, totalUsed: 0, expiringSoon: 0 })
// 获取积分统计信息
const { run: fetchPointsInfo, loading: infoLoading } = useRequest(getUserPointsInfo, {
manual: true,
onSuccess: (data) => {
setPointsInfo(data)
},
onError: (err) => {
console.error('获取积分信息失败:', err)
}
})
// 获取积分记录
const { run: fetchLogs, loading } = useRequest(pageUserPointsLog, {
manual: true,
onSuccess: (data) => {
if (data?.list) {
if (page === 1) {
setLogs(data.list)
} else {
setLogs(prev => [...prev, ...data.list])
}
setHasMore(data.list.length >= 20)
}
Taro.stopPullDownRefresh()
},
onError: (err) => {
Taro.showToast({ title: err.message || '加载失败', icon: 'none' })
Taro.stopPullDownRefresh()
}
})
// 加载数据
const loadData = (pageNum: number = 1) => {
const params: any = { page: pageNum, limit: 20 }
if (activeTab !== -1) {
params.type = activeTab
}
fetchLogs(params)
}
// 初始化加载
useEffect(() => {
setPage(1)
loadData(1)
fetchPointsInfo() // 加载积分统计信息
}, [activeTab])
// 下拉刷新Taro 自动识别此函数)
function onPullDownRefresh() {
setPage(1)
loadData(1)
}
// 加载更多
const loadMore = () => {
if (loading || !hasMore) return
const nextPage = page + 1
setPage(nextPage)
loadData(nextPage)
}
// 格式化积分
const formatPoints = (points?: number) => {
if (!points && points !== 0) return '0'
return points > 0 ? `+${points}` : String(points)
}
// 获取类型信息
const getTypeInfo = (type?: number) => {
return TYPE_MAP[type || 0] || { label: '未知', icon: '❓', color: 'text-gray-500', bgColor: 'bg-gray-50' }
}
// 格式化时间
const formatTime = (timeStr?: string) => {
if (!timeStr) return ''
const date = new Date(timeStr)
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
return `${month}-${day} ${hour}:${minute}`
}
// 按日期分组
const groupLogsByDate = () => {
const groups: { date: string; logs: UserPointsLog[] }[] = []
let currentDate = ''
let currentGroup: UserPointsLog[] = []
logs.forEach(log => {
const date = log.createTime ? log.createTime.split(' ')[0] : ''
if (date !== currentDate) {
if (currentGroup.length > 0) {
groups.push({ date: currentDate, logs: currentGroup })
}
currentDate = date
currentGroup = [log]
} else {
currentGroup.push(log)
}
})
if (currentGroup.length > 0) {
groups.push({ date: currentDate, logs: currentGroup })
}
return groups
}
const groupedLogs = groupLogsByDate()
return (
<View className='min-h-screen' style={{ background: 'linear-gradient(to bottom, #fff7ed, #ffffff)' }}>
{/* 顶部统计卡片 */}
<View className='bg-white mx-3 mt-3 rounded-xl p-4 shadow-sm'>
<View className='flex items-center justify-between'>
<View className='text-center flex-1'>
<Text className='text-2xl font-bold text-orange-500 block'>{infoLoading ? '-' : pointsInfo.points.toLocaleString()}</Text>
<Text className='text-xs text-gray-400 mt-1 block'></Text>
</View>
<View className='h-10 w-px bg-gray-200' />
<View className='text-center flex-1'>
<Text className='text-2xl font-bold text-green-500 block'>{infoLoading ? '-' : `+${pointsInfo.totalEarned.toLocaleString()}`}</Text>
<Text className='text-xs text-gray-400 mt-1 block'></Text>
</View>
<View className='h-10 w-px bg-gray-200' />
<View className='text-center flex-1'>
<Text className='text-2xl font-bold text-red-500 block'>{infoLoading ? '-' : `-${pointsInfo.totalUsed.toLocaleString()}`}</Text>
<Text className='text-xs text-gray-400 mt-1 block'>使</Text>
</View>
</View>
</View>
{/* Tab 栏 */}
<View className='bg-white flex mt-3'>
{tabs.map(tab => (
<View
key={tab.value}
className={`flex-1 text-center py-3 relative ${
activeTab === tab.value ? 'text-orange-500 font-medium' : 'text-gray-600'
}`}
onClick={() => setActiveTab(tab.value)}
>
<Text className='text-sm'>{tab.label}</Text>
{activeTab === tab.value && (
<View className='absolute bottom-0 w-8 h-px bg-orange-500 rounded' style={{ left: '50%', transform: 'translateX(-50%)' }} />
)}
</View>
))}
</View>
{/* 日志列表 */}
<ScrollView
scrollY
className='h-full'
onScrollToLower={loadMore}
>
{logs.length === 0 && !loading ? (
<View className='text-center py-16'>
<Text className='text-6xl mb-4 block'>📊</Text>
<Text className='text-base text-gray-400 mb-2 block'></Text>
<Text className='text-sm text-gray-300'></Text>
</View>
) : (
<View className='p-3'>
{groupedLogs.map(group => (
<View key={group.date} className='mb-3'>
{/* 日期分隔 */}
<View className='flex items-center mb-2'>
<View className='flex-1 h-px bg-gray-200' />
<Text className='text-xs text-gray-400 mx-3'>{group.date}</Text>
<View className='flex-1 h-px bg-gray-200' />
</View>
{/* 当日记录 */}
{group.logs.map(log => {
const typeInfo = getTypeInfo(log.type)
return (
<View key={log.logId} className='bg-white rounded-lg p-4 mb-2 shadow-sm'>
<View className='flex justify-between items-center'>
<View className='flex-1'>
<View className='flex items-center gap-2'>
<View className={`w-8 h-8 rounded-full ${typeInfo.bgColor} flex items-center justify-center`}>
<Text className='text-base'>{typeInfo.icon}</Text>
</View>
<View className='flex-1'>
<Text className='text-sm text-gray-800 block font-medium'>{log.reason || typeInfo.label}</Text>
<Text className='text-xs text-gray-400 mt-1 block'>{formatTime(log.createTime)}</Text>
{log.orderId && (
<Text className='text-xs text-gray-400 mt-0 block'>: {log.orderId}</Text>
)}
</View>
</View>
</View>
<View className='text-right ml-3'>
<Text className={`text-lg font-bold ${typeInfo.color}`}>
{formatPoints(log.points)}
</Text>
</View>
</View>
</View>
)
})}
</View>
))}
{/* 加载更多 */}
{hasMore && (
<View className='text-center py-4'>
<Text className='text-xs text-gray-400'>
{loading ? '加载中...' : '上拉加载更多'}
</Text>
</View>
)}
{!hasMore && logs.length > 0 && (
<View className='text-center py-4'>
<Text className='text-xs text-gray-400'> </Text>
</View>
)}
</View>
)}
</ScrollView>
</View>
)
}
export default PointsRecordPage