Browse Source

新增:优惠券、积分明细

master
科技小王子 2 weeks ago
parent
commit
c82a56eef7
  1. 113
      src/api/shop/shopGoodsReview/model/index.ts
  2. 102
      src/api/user/balance-log/index.ts
  3. 31
      src/api/user/balance-log/model/index.ts
  4. 78
      src/api/user/coupon/index.ts
  5. 45
      src/api/user/coupon/model/index.ts
  6. 73
      src/api/user/points/index.ts
  7. 49
      src/api/user/points/model/index.ts
  8. 5
      src/app.config.ts
  9. 42
      src/pages/user/components/UserCard.tsx
  10. 4
      src/user/coupon/coupon.ts
  11. 213
      src/user/coupon/coupon.tsx
  12. 4
      src/user/points/points.config.ts
  13. 199
      src/user/points/points.tsx
  14. 4
      src/user/wallet/wallet.config.ts
  15. 0
      src/user/wallet/wallet.scss
  16. 134
      src/user/wallet/wallet.tsx
  17. 14
      src/utils/common.ts
  18. 4
      src/utils/server.ts

113
src/api/shop/shopGoodsReview/model/index.ts

@ -0,0 +1,113 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface ShopGoodsReview {
// 评价ID
reviewId?: number;
// 商品ID
goodsId?: number;
// 订单ID
orderId?: number;
// 用户ID
userId?: number;
// 用户昵称
nickname?: string;
// 用户头像
avatar?: string;
// 评价内容
content?: string;
// 评分 1-5星
rating?: number;
// 评价图片,JSON数组格式
images?: string;
// 是否匿名评价
isAnonymous?: boolean;
// 商家回复
reply?: string;
// 商家回复时间
replyTime?: string;
// 评价状态 0待审核 1已通过 2已拒绝
status?: number;
// 是否置顶
isTop?: boolean;
// 点赞数
likeCount?: number;
// 创建时间
createTime?: string;
// 更新时间
updateTime?: string;
// 商品信息
goodsName?: string;
goodsImage?: string;
goodsPrice?: string;
// SKU信息
skuId?: number;
specInfo?: string;
}
/**
*
*/
export interface ReviewStats {
// 总评价数
totalCount: number;
// 好评数
goodCount: number;
// 中评数
mediumCount: number;
// 差评数
badCount: number;
// 好评率
goodRate: number;
// 平均评分
avgRating: number;
// 各星级统计
ratingStats: {
[key: number]: number; // 星级 -> 数量
};
}
/**
*
*/
export interface ShopGoodsReviewParam extends PageParam {
// 商品ID
goodsId?: number;
// 用户ID
userId?: number;
// 订单ID
orderId?: number;
// 评分筛选
rating?: number;
// 状态筛选
status?: number;
// 是否有图片
hasImages?: boolean;
// 排序方式 time:时间 rating:评分 like:点赞数
sortBy?: string;
// 排序方向 asc:升序 desc:降序
sortOrder?: string;
}
/**
*
*/
export interface SubmitReviewRequest {
// 商品ID
goodsId: number;
// 订单ID
orderId: number;
// 评价内容
content: string;
// 评分
rating: number;
// 评价图片
images?: string[];
// 是否匿名
isAnonymous?: boolean;
// SKU信息
skuId?: number;
specInfo?: string;
}

102
src/api/user/balance-log/index.ts

@ -0,0 +1,102 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { UserBalanceLog, UserBalanceLogParam } from './model';
import {SERVER_API_URL} from "@/utils/server";
/**
*
*/
export async function pageUserBalanceLog(params: UserBalanceLogParam) {
const res = await request.get<ApiResult<PageResult<UserBalanceLog>>>(
SERVER_API_URL + '/sys/user-balance-log/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function listUserBalanceLog(params?: UserBalanceLogParam) {
const res = await request.get<ApiResult<UserBalanceLog[]>>(
SERVER_API_URL + '/sys/user-balance-log',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* id查询余额明细
*/
export async function getUserBalanceLog(id: number) {
const res = await request.get<ApiResult<UserBalanceLog>>(
SERVER_API_URL + '/sys/user-balance-log/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function addUserBalanceLog(data: UserBalanceLog) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-balance-log',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function updateUserBalanceLog(data: UserBalanceLog) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-balance-log',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function removeUserBalanceLog(id?: number) {
const res = await request.del<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-balance-log/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function removeUserBalanceLogs(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-balance-log/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}

31
src/api/user/balance-log/model/index.ts

@ -0,0 +1,31 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface UserBalanceLog {
logId?: number;
userId?: number;
scene?: number;
money?: string;
describe?: string;
remark?: string;
sortNumber?: number;
comments?: string;
status?: number;
deleted?: number;
tenantId?: number;
createTime?: string;
updateTime?: string;
}
/**
*
*/
export interface UserBalanceLogParam extends PageParam {
logId?: number;
userId?: number;
scene?: number;
createTimeStart?: string;
createTimeEnd?: string;
}

78
src/api/user/coupon/index.ts

@ -0,0 +1,78 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { UserCoupon, UserCouponParam } from './model';
import {SERVER_API_URL} from "@/utils/server";
/**
*
*/
export async function pageUserCoupon(params: UserCouponParam) {
const res = await request.get<ApiResult<PageResult<UserCoupon>>>(
SERVER_API_URL + '/sys/user-coupon/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function listUserCoupon(params?: UserCouponParam) {
const res = await request.get<ApiResult<UserCoupon[]>>(
SERVER_API_URL + '/sys/user-coupon',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function getUserCouponCount(userId: number) {
const res = await request.get<ApiResult<{
total: number;
unused: number;
used: number;
expired: number;
}>>(
SERVER_API_URL + '/sys/user-coupon/count',
{ userId }
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* id查询用户优惠券
*/
export async function getUserCoupon(id: number) {
const res = await request.get<ApiResult<UserCoupon>>(
SERVER_API_URL + '/sys/user-coupon/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 使
*/
export async function useCoupon(couponId: number, orderId: number) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-coupon/use',
{ couponId, orderId }
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}

45
src/api/user/coupon/model/index.ts

@ -0,0 +1,45 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface UserCoupon {
// 优惠券ID
couponId?: number;
// 用户ID
userId?: number;
// 优惠券名称
name?: string;
// 优惠券类型 1-满减券 2-折扣券 3-免费券
type?: number;
// 优惠券金额/折扣
value?: string;
// 使用门槛金额
minAmount?: string;
// 有效期开始时间
startTime?: string;
// 有效期结束时间
endTime?: string;
// 使用状态 0-未使用 1-已使用 2-已过期
status?: number;
// 使用时间
useTime?: string;
// 关联订单ID
orderId?: number;
// 备注
comments?: string;
// 创建时间
createTime?: string;
// 更新时间
updateTime?: string;
}
/**
*
*/
export interface UserCouponParam extends PageParam {
userId?: number;
type?: number;
status?: number;
name?: string;
}

73
src/api/user/points/index.ts

@ -0,0 +1,73 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { UserPointsLog, UserPointsLogParam, UserPointsStats } from './model';
import {SERVER_API_URL} from "@/utils/server";
/**
*
*/
export async function pageUserPointsLog(params: UserPointsLogParam) {
const res = await request.get<ApiResult<PageResult<UserPointsLog>>>(
SERVER_API_URL + '/sys/user-points-log/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function listUserPointsLog(params?: UserPointsLogParam) {
const res = await request.get<ApiResult<UserPointsLog[]>>(
SERVER_API_URL + '/sys/user-points-log',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function getUserPointsStats(userId: number) {
const res = await request.get<ApiResult<UserPointsStats>>(
SERVER_API_URL + '/sys/user-points-log/stats',
{ userId }
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* id查询积分记录
*/
export async function getUserPointsLog(id: number) {
const res = await request.get<ApiResult<UserPointsLog>>(
SERVER_API_URL + '/sys/user-points-log/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 使
*/
export async function usePoints(userId: number, points: number, reason: string, orderId?: number) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/sys/user-points-log/use',
{ userId, points, reason, orderId }
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}

49
src/api/user/points/model/index.ts

@ -0,0 +1,49 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface UserPointsLog {
// 积分记录ID
logId?: number;
// 用户ID
userId?: number;
// 积分变动类型 1-获得 2-消费 3-过期 4-管理员调整
type?: number;
// 积分变动数量
points?: number;
// 变动原因
reason?: string;
// 关联订单ID
orderId?: number;
// 备注
comments?: string;
// 创建时间
createTime?: string;
// 更新时间
updateTime?: string;
}
/**
*
*/
export interface UserPointsStats {
// 当前积分
currentPoints?: number;
// 累计获得积分
totalEarned?: number;
// 累计消费积分
totalUsed?: number;
// 即将过期积分
expiringSoon?: number;
}
/**
*
*/
export interface UserPointsLogParam extends PageParam {
userId?: number;
type?: number;
startTime?: string;
endTime?: string;
}

5
src/app.config.ts

@ -36,7 +36,10 @@ export default defineAppConfig({
"address/add",
"address/wxAddress",
"help/index",
"about/index"
"about/index",
"wallet/wallet",
"coupon/coupon",
"points/points"
]
},
{

42
src/pages/user/components/UserCard.tsx

@ -6,11 +6,15 @@ import {useEffect, useState} from "react";
import {User} from "@/api/system/user/model";
import navTo from "@/utils/common";
import {TenantId} from "@/config/app";
import {getUserCouponCount} from "@/api/user/coupon";
import {getUserPointsStats} from "@/api/user/points";
function UserCard() {
const [IsLogin, setIsLogin] = useState<boolean>(false)
const [userInfo, setUserInfo] = useState<User>()
const [roleName, setRoleName] = useState<string>('注册用户')
const [couponCount, setCouponCount] = useState(0)
const [pointsCount, setPointsCount] = useState(0)
useEffect(() => {
@ -30,6 +34,26 @@ function UserCard() {
});
}, []);
const loadUserStats = (userId: number) => {
// 加载优惠券数量
getUserCouponCount(userId)
.then((res: any) => {
setCouponCount(res.unused || 0)
})
.catch((error: any) => {
console.error('Coupon count error:', error)
})
// 加载积分数量
getUserPointsStats(userId)
.then((res: any) => {
setPointsCount(res.currentPoints || 0)
})
.catch((error: any) => {
console.error('Points stats error:', error)
})
}
const reload = () => {
Taro.getUserInfo({
success: (res) => {
@ -44,6 +68,12 @@ function UserCard() {
setUserInfo(data)
setIsLogin(true);
Taro.setStorageSync('UserId', data.userId)
// 加载用户统计数据
if (data.userId) {
loadUserStats(data.userId)
}
// 获取openId
if (!data.openid) {
Taro.login({
@ -186,17 +216,17 @@ function UserCard() {
</div>
</div>
<div className={'flex justify-around mt-5'}>
<div className={'item flex justify-center flex-col items-center'}>
<div className={'item flex justify-center flex-col items-center'} onClick={() => navTo('/user/wallet/wallet', true)}>
<span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>¥ {userInfo?.balance}</span>
<span className={'text-xl'}>¥ {userInfo?.balance || '0.00'}</span>
</div>
<div className={'item flex justify-center flex-col items-center'}>
<div className={'item flex justify-center flex-col items-center'} onClick={() => navTo('/user/coupon/coupon', true)}>
<span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>0</span>
<span className={'text-xl'}>{couponCount}</span>
</div>
<div className={'item flex justify-center flex-col items-center'}>
<div className={'item flex justify-center flex-col items-center'} onClick={() => navTo('/user/points/points', true)}>
<span className={'text-sm text-gray-500'}></span>
<span className={'text-xl'}>100</span>
<span className={'text-xl'}>{pointsCount}</span>
</div>
</div>
</div>

4
src/user/coupon/coupon.ts

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '我的优惠券',
navigationBarTextStyle: 'black'
})

213
src/user/coupon/coupon.tsx

@ -0,0 +1,213 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Button, Cell, Space, Empty, ConfigProvider, Tabs, TabPane, Tag} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components'
import {pageUserCoupon, getUserCouponCount} from "@/api/user/coupon";
import {UserCoupon as UserCouponType} from "@/api/user/coupon/model";
const UserCoupon = () => {
const [list, setList] = useState<UserCouponType[]>([])
const [loading, setLoading] = useState(false)
const [activeTab, setActiveTab] = useState('0')
const [couponCount, setCouponCount] = useState({
total: 0,
unused: 0,
used: 0,
expired: 0
})
const tabs = [
{ key: '0', title: '全部', status: undefined },
{ key: '1', title: '未使用', status: 0 },
{ key: '2', title: '已使用', status: 1 },
{ key: '3', title: '已过期', status: 2 }
]
const reload = (status?: number) => {
setLoading(true)
const userId = Taro.getStorageSync('UserId')
console.log('Loading coupons for userId:', userId, 'status:', status)
if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({
title: '请先登录',
icon: 'error'
});
setLoading(false)
return
}
pageUserCoupon({
userId: parseInt(userId),
status: status,
page: 1,
limit: 20
})
.then((res: any) => {
console.log('Coupon response:', res)
setList(res?.list || [])
})
.catch((error: any) => {
console.error('Coupon error:', error)
Taro.showToast({
title: error?.message || '获取失败',
icon: 'error'
});
})
.finally(() => {
setLoading(false)
})
}
const loadCouponCount = () => {
const userId = Taro.getStorageSync('UserId')
if (!userId) return
getUserCouponCount(parseInt(userId))
.then((res: any) => {
setCouponCount(res)
})
.catch((error: any) => {
console.error('Coupon count error:', error)
})
}
useEffect(() => {
reload()
loadCouponCount()
}, []);
const onTabChange = (index: string) => {
setActiveTab(index)
const tab = tabs.find(t => t.key === index)
reload(tab?.status)
}
const getCouponTypeText = (type?: number) => {
switch (type) {
case 1: return '满减券'
case 2: return '折扣券'
case 3: return '免费券'
default: return '优惠券'
}
}
const getCouponStatusText = (status?: number) => {
switch (status) {
case 0: return '未使用'
case 1: return '已使用'
case 2: return '已过期'
default: return '未知'
}
}
const getCouponStatusColor = (status?: number) => {
switch (status) {
case 0: return 'success'
case 1: return 'default'
case 2: return 'danger'
default: return 'default'
}
}
const formatCouponValue = (type?: number, value?: string) => {
if (!value) return '0'
switch (type) {
case 1: return `¥${value}`
case 2: return `${parseFloat(value) * 10}`
case 3: return '免费'
default: return value
}
}
if (loading) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<div>...</div>
</div>
</ConfigProvider>
)
}
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="您还没有优惠券"
/>
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<ConfigProvider>
<View>
<Tabs value={activeTab} onChange={onTabChange}>
{tabs.map(tab => (
<TabPane key={tab.key} title={tab.title}>
<View className="p-4">
{list.map((item, index) => (
<Cell.Group key={index} className="mb-4">
<Cell className="coupon-item p-4">
<View className="flex justify-between items-center">
<View className="flex-1">
<View className="flex items-center mb-2">
<View className="coupon-value text-2xl font-bold text-red-500 mr-3">
{formatCouponValue(item.type, item.value)}
</View>
<View className="flex flex-col">
<View className="text-base font-medium text-gray-800">
{item.name || getCouponTypeText(item.type)}
</View>
{item.minAmount && parseFloat(item.minAmount) > 0 && (
<View className="text-sm text-gray-500">
¥{item.minAmount}
</View>
)}
</View>
</View>
<View className="flex justify-between items-center text-xs text-gray-400">
<View>
: {item.startTime ? new Date(item.startTime).toLocaleDateString() : ''} - {item.endTime ? new Date(item.endTime).toLocaleDateString() : ''}
</View>
<Tag type={getCouponStatusColor(item.status)} size="small">
{getCouponStatusText(item.status)}
</Tag>
</View>
{item.comments && (
<View className="text-xs text-gray-500 mt-2 p-2 bg-gray-50 rounded">
{item.comments}
</View>
)}
</View>
</View>
</Cell>
</Cell.Group>
))}
</View>
</TabPane>
))}
</Tabs>
</View>
</ConfigProvider>
);
};
export default UserCoupon;

4
src/user/points/points.config.ts

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '我的积分',
navigationBarTextStyle: 'black'
})

199
src/user/points/points.tsx

@ -0,0 +1,199 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Button, Cell, Space, Empty, ConfigProvider, Card} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components'
import {pageUserPointsLog, getUserPointsStats} from "@/api/user/points";
import {UserPointsLog as UserPointsLogType, UserPointsStats} from "@/api/user/points/model";
const UserPoints = () => {
const [list, setList] = useState<UserPointsLogType[]>([])
const [loading, setLoading] = useState(false)
const [stats, setStats] = useState<UserPointsStats>({})
const reload = () => {
setLoading(true)
const userId = Taro.getStorageSync('UserId')
console.log('Loading points log for userId:', userId)
if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({
title: '请先登录',
icon: 'error'
});
setLoading(false)
return
}
pageUserPointsLog({
userId: parseInt(userId),
page: 1,
limit: 20
})
.then((res: any) => {
console.log('Points log response:', res)
setList(res?.list || [])
})
.catch((error: any) => {
console.error('Points log error:', error)
Taro.showToast({
title: error?.message || '获取失败',
icon: 'error'
});
})
.finally(() => {
setLoading(false)
})
}
const loadPointsStats = () => {
const userId = Taro.getStorageSync('UserId')
if (!userId) return
getUserPointsStats(parseInt(userId))
.then((res: any) => {
setStats(res)
})
.catch((error: any) => {
console.error('Points stats error:', error)
})
}
useEffect(() => {
reload()
loadPointsStats()
}, []);
const getPointsTypeText = (type?: number) => {
switch (type) {
case 1: return '获得积分'
case 2: return '消费积分'
case 3: return '积分过期'
case 4: return '管理员调整'
default: return '积分变动'
}
}
const getPointsTypeColor = (type?: number) => {
switch (type) {
case 1: return 'text-green-500'
case 2: return 'text-red-500'
case 3: return 'text-gray-500'
case 4: return 'text-blue-500'
default: return 'text-gray-500'
}
}
if (loading) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<div>...</div>
</div>
</ConfigProvider>
)
}
return (
<ConfigProvider>
<View className="bg-gray-50 min-h-screen">
{/* 积分统计卡片 */}
<View className="p-4">
<Card className="points-stats-card">
<View className="text-center py-4">
<View className="text-3xl font-bold text-orange-500 mb-2">
{stats.currentPoints || 0}
</View>
<View className="text-sm text-gray-500 mb-4"></View>
<View className="flex justify-around text-center">
<View>
<View className="text-lg font-medium text-gray-800">
{stats.totalEarned || 0}
</View>
<View className="text-xs text-gray-500"></View>
</View>
<View>
<View className="text-lg font-medium text-gray-800">
{stats.totalUsed || 0}
</View>
<View className="text-xs text-gray-500"></View>
</View>
<View>
<View className="text-lg font-medium text-gray-800">
{stats.expiringSoon || 0}
</View>
<View className="text-xs text-gray-500"></View>
</View>
</View>
</View>
</Card>
</View>
{/* 积分记录 */}
<View className="px-4">
<View className="text-base font-medium text-gray-800 mb-3"></View>
{list.length === 0 ? (
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 400px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="您还没有积分记录"
/>
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div>
) : (
list.map((item, index) => (
<Cell.Group key={index} className="mb-3">
<Cell className="flex flex-col gap-2 p-4">
<View className="flex justify-between items-start">
<View className="flex-1">
<View className="font-medium text-base text-gray-800 mb-1">
{getPointsTypeText(item.type)}
</View>
<View className="text-sm text-gray-500">
{item.reason || '无备注'}
</View>
</View>
<View className={`text-lg font-bold ${getPointsTypeColor(item.type)}`}>
{item.type === 1 ? '+' : item.type === 2 ? '-' : ''}
{item.points || 0}
</View>
</View>
<View className="flex justify-between items-center text-xs text-gray-400 mt-2">
<View>
{item.createTime ? new Date(item.createTime).toLocaleString() : ''}
</View>
{item.orderId && (
<View>
: {item.orderId}
</View>
)}
</View>
{item.comments && (
<View className="text-xs text-gray-500 mt-1 p-2 bg-gray-50 rounded">
: {item.comments}
</View>
)}
</Cell>
</Cell.Group>
))
)}
</View>
</View>
</ConfigProvider>
);
};
export default UserPoints;

4
src/user/wallet/wallet.config.ts

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '余额明细',
navigationBarTextStyle: 'black'
})

0
src/user/wallet/wallet.scss

134
src/user/wallet/wallet.tsx

@ -0,0 +1,134 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Button, Cell, Space, Empty, ConfigProvider, InfiniteLoading} from '@nutui/nutui-react-taro'
import {View, ScrollView} from '@tarojs/components'
import {pageUserBalanceLog} from "@/api/user/balance-log";
import {UserBalanceLog} from "@/api/user/balance-log/model";
import {formatCurrency} from "@/utils/common";
const Wallet = () => {
const [list, setList] = useState<UserBalanceLog[]>([])
const [loading, setLoading] = useState(false)
const [loadingMore, setLoadingMore] = useState(false)
const [refreshing, setRefreshing] = useState(false)
const [hasMore, setHasMore] = useState(true)
const [currentPage, setCurrentPage] = useState(1)
const [total, setTotal] = useState(0)
const pageSize = 20
const reload = () => {
setLoading(true)
const userId = Taro.getStorageSync('UserId')
console.log('Loading balance log for userId:', userId)
if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({
title: '请先登录',
icon: 'error'
});
setLoading(false)
return
}
pageUserBalanceLog({
userId: parseInt(userId),
page: 1,
limit: 20
})
.then((res: any) => {
console.log('Balance log response:', res)
setList(res?.list || [])
})
.catch((error: any) => {
console.error('Balance log error:', error)
Taro.showToast({
title: error?.message || '获取失败',
icon: 'error'
});
})
.finally(() => {
setLoading(false)
})
}
useEffect(() => {
reload()
}, []);
if (loading) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<div>...</div>
</div>
</ConfigProvider>
)
}
if (list.length == 0) {
return (
<ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="您还没有消费记录"
/>
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div>
</ConfigProvider>
)
}
return (
<ConfigProvider>
<View className="p-4">
{list.map((item, index) => (
<Cell.Group key={index} className="mb-4">
<Cell className="flex flex-col gap-2 p-4">
<View className="flex justify-between items-start w-full">
<View className="flex-1">
<View className="font-medium text-base text-gray-800 mb-1">
{item.scene === 10 ? '会员充值' : item.scene === 20 ? '用户消费' : item.scene === 30 ? '管理员操作' : '订单退款'}
</View>
<View className="text-sm text-gray-500">
{item.comments}
</View>
</View>
<View className={`text-lg font-bold ${
item.scene === 10 ? 'text-green-500' : ''
}`}>
{item.scene === 10 ? '+' : '-'}
{formatCurrency(Number(item.money), 'CNY') || '0.00'}
</View>
</View>
<View className="flex justify-between w-full items-center text-xs text-gray-400 mt-2">
<View>
{item.createTime}
</View>
</View>
{item.remark && (
<View className="text-xs text-gray-500 mt-1 p-2 bg-gray-50 rounded">
: {item.remark}
</View>
)}
</Cell>
</Cell.Group>
))}
</View>
</ConfigProvider>
);
};
export default Wallet;

14
src/utils/common.ts

@ -108,6 +108,20 @@ export function truncateText(text: string, maxLength: number = 30): string {
return text.substring(0, maxLength);
}
/**
*
* @param amount
* @param currency
*/
export function formatCurrency(amount: number, currency: string = 'CNY'): string {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
}
/**
*
* @param goodsNames

4
src/utils/server.ts

@ -4,8 +4,8 @@ import {User} from "@/api/system/user/model";
// 模版套餐ID - 请根据实际情况修改
export const TEMPLATE_ID = '10550';
// 服务接口 - 请根据实际情况修改
export const SERVER_API_URL = 'https://server.websoft.top/api';
// export const SERVER_API_URL = 'http://127.0.0.1:8000/api';
// export const SERVER_API_URL = 'https://server.websoft.top/api';
export const SERVER_API_URL = 'http://127.0.0.1:8000/api';
/**
*
* @param token

Loading…
Cancel
Save