Browse Source

新增:优惠券、积分明细

master
科技小王子 2 weeks ago
parent
commit
815f020c50
  1. 2
      config/env.ts
  2. 65
      src/api/shop/shopCoupon/model/index.ts
  3. 35
      src/api/shop/shopGoodsCoupon/model/index.ts
  4. 1
      src/api/user/balance-log/model/index.ts
  5. 7
      src/cms/category/components/ArticleTabs.tsx
  6. 133
      src/user/coupon/coupon.tsx
  7. 108
      src/user/points/points.tsx
  8. 137
      src/user/wallet/wallet.tsx
  9. 4
      src/utils/server.ts

2
config/env.ts

@ -2,7 +2,7 @@
export const ENV_CONFIG = { export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'https://cms-api.websoft.top/api',
API_BASE_URL: 'http://127.0.0.1:9200/api',
APP_NAME: '时里院子市集', APP_NAME: '时里院子市集',
DEBUG: 'true', DEBUG: 'true',
}, },

65
src/api/shop/shopCoupon/model/index.ts

@ -0,0 +1,65 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface ShopCoupon {
// id
id?: number;
// 优惠券名称
name?: string;
// 优惠券描述
description?: string;
// 优惠券类型(10满减券 20折扣券 30免费劵)
type?: number;
// 满减券-减免金额
reducePrice?: string;
// 折扣券-折扣率(0-100)
discount?: number;
// 最低消费金额
minPrice?: string;
// 到期类型(10领取后生效 20固定时间)
expireType?: number;
// 领取后生效-有效天数
expireDay?: number;
// 有效期开始时间
startTime?: string;
// 有效期结束时间
endTime?: string;
// 适用范围(10全部商品 20指定商品 30指定分类)
applyRange?: number;
// 适用范围配置(json格式)
applyRangeConfig?: string;
// 是否过期(0未过期 1已过期)
isExpire?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1禁用
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 创建用户ID
userId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 发放总数量(-1表示无限制)
totalCount?: number;
// 已发放数量
issuedCount?: number;
// 每人限领数量(-1表示无限制)
limitPerUser?: number;
// 是否启用(0禁用 1启用)
enabled?: string;
}
/**
*
*/
export interface ShopCouponParam extends PageParam {
id?: number;
keywords?: string;
}

35
src/api/shop/shopGoodsCoupon/model/index.ts

@ -0,0 +1,35 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface ShopGoodsCoupon {
//
id?: number;
// 商品id
goodsId?: number;
// 优惠劵id
issueCouponId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
*
*/
export interface ShopGoodsCouponParam extends PageParam {
id?: number;
keywords?: string;
}

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

@ -8,6 +8,7 @@ export interface UserBalanceLog {
userId?: number; userId?: number;
scene?: number; scene?: number;
money?: string; money?: string;
balance?: number;
describe?: string; describe?: string;
remark?: string; remark?: string;
sortNumber?: number; sortNumber?: number;

7
src/cms/category/components/ArticleTabs.tsx

@ -11,7 +11,12 @@ const ArticleTabs = (props: any) => {
const reload = async (value) => { const reload = async (value) => {
const {data} = props const {data} = props
pageCmsArticle({categoryId: data[value].navigationId, page: 1, limit: 10}).then((res) => {
pageCmsArticle({
categoryId: data[value].navigationId,
page: 1,
status: 0,
limit: 10
}).then((res) => {
res && setList(res?.list || []) res && setList(res?.list || [])
}) })
.catch(err => { .catch(err => {

133
src/user/coupon/coupon.tsx

@ -1,13 +1,22 @@
import {useEffect, useState} from "react";
import {useState, useEffect, CSSProperties} from 'react'
import Taro from '@tarojs/taro' 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 {Cell, InfiniteLoading, Tabs, TabPane, Tag, Empty, ConfigProvider} from '@nutui/nutui-react-taro'
import {pageUserCoupon, getUserCouponCount} from "@/api/user/coupon"; import {pageUserCoupon, getUserCouponCount} from "@/api/user/coupon";
import {UserCoupon as UserCouponType} from "@/api/user/coupon/model"; import {UserCoupon as UserCouponType} from "@/api/user/coupon/model";
import {View} from '@tarojs/components'
const InfiniteUlStyle: CSSProperties = {
height: '100vh',
width: '100%',
padding: '0',
overflowY: 'auto',
overflowX: 'hidden',
}
const UserCoupon = () => { const UserCoupon = () => {
const [list, setList] = useState<UserCouponType[]>([]) const [list, setList] = useState<UserCouponType[]>([])
const [loading, setLoading] = useState(false)
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [activeTab, setActiveTab] = useState('0') const [activeTab, setActiveTab] = useState('0')
const [couponCount, setCouponCount] = useState({ const [couponCount, setCouponCount] = useState({
total: 0, total: 0,
@ -23,42 +32,43 @@ const UserCoupon = () => {
{ key: '3', title: '已过期', status: 2 } { key: '3', title: '已过期', status: 2 }
] ]
const reload = (status?: number) => {
setLoading(true)
const userId = Taro.getStorageSync('UserId')
useEffect(() => {
reload()
loadCouponCount()
}, [])
console.log('Loading coupons for userId:', userId, 'status:', status)
const loadMore = async () => {
setPage(page + 1)
reload();
}
const reload = () => {
const userId = Taro.getStorageSync('UserId')
if (!userId) { if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({ Taro.showToast({
title: '请先登录', title: '请先登录',
icon: 'error' icon: 'error'
}); });
setLoading(false)
return return
} }
const tab = tabs.find(t => t.key === activeTab)
pageUserCoupon({ pageUserCoupon({
userId: parseInt(userId), userId: parseInt(userId),
status: status,
page: 1,
limit: 20
})
.then((res: any) => {
console.log('Coupon response:', res)
setList(res?.list || [])
})
.catch((error: any) => {
status: tab?.status,
page
}).then(res => {
console.log(res)
const newList = res?.list || [];
setList([...list, ...newList])
setHasMore(newList.length > 0)
}).catch(error => {
console.error('Coupon error:', error) console.error('Coupon error:', error)
Taro.showToast({ Taro.showToast({
title: error?.message || '获取失败', title: error?.message || '获取失败',
icon: 'error' icon: 'error'
}); });
}) })
.finally(() => {
setLoading(false)
})
} }
const loadCouponCount = () => { const loadCouponCount = () => {
@ -74,15 +84,15 @@ const UserCoupon = () => {
}) })
} }
useEffect(() => {
reload()
loadCouponCount()
}, []);
const onTabChange = (index: string) => { const onTabChange = (index: string) => {
setActiveTab(index) setActiveTab(index)
const tab = tabs.find(t => t.key === index)
reload(tab?.status)
setList([]) // 清空列表
setPage(1) // 重置页码
setHasMore(true) // 重置hasMore
// 延迟执行reload,确保状态更新完成
setTimeout(() => {
reload()
}, 0)
} }
const getCouponTypeText = (type?: number) => { const getCouponTypeText = (type?: number) => {
@ -122,23 +132,38 @@ const UserCoupon = () => {
} }
} }
if (loading) {
return ( return (
<ConfigProvider> <ConfigProvider>
<div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<div>...</div>
</div>
</ConfigProvider>
)
<View className="h-screen">
<Tabs value={activeTab} onChange={onTabChange}>
{tabs.map(tab => (
<TabPane key={tab.key} title={tab.title}>
<ul style={InfiniteUlStyle} id="scroll">
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={loadMore}
onScroll={() => {
console.log('onScroll')
}}
onScrollToUpper={() => {
console.log('onScrollToUpper')
}}
loadingText={
<>
</>
} }
if (list.length == 0) {
return (
<ConfigProvider>
loadMoreText={
<>
</>
}
>
<View className="p-4">
{list.length === 0 ? (
<div className={'h-full flex flex-col justify-center items-center'} style={{ <div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
height: 'calc(100vh - 400px)',
}}> }}>
<Empty <Empty
style={{ style={{
@ -146,23 +171,10 @@ const UserCoupon = () => {
}} }}
description="您还没有优惠券" description="您还没有优惠券"
/> />
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div> </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">
) : (
list.map((item, index) => (
<Cell.Group key={`${item.couponId}-${index}`} className="mb-4">
<Cell className="coupon-item p-4"> <Cell className="coupon-item p-4">
<View className="flex justify-between items-center"> <View className="flex justify-between items-center">
<View className="flex-1"> <View className="flex-1">
@ -200,8 +212,11 @@ const UserCoupon = () => {
</View> </View>
</Cell> </Cell>
</Cell.Group> </Cell.Group>
))}
))
)}
</View> </View>
</InfiniteLoading>
</ul>
</TabPane> </TabPane>
))} ))}
</Tabs> </Tabs>

108
src/user/points/points.tsx

@ -1,50 +1,59 @@
import {useEffect, useState} from "react";
import {useState, useEffect, CSSProperties} from 'react'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {Button, Cell, Space, Empty, ConfigProvider, Card} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components'
import {Cell, InfiniteLoading, Card, Empty, ConfigProvider} from '@nutui/nutui-react-taro'
import {pageUserPointsLog, getUserPointsStats} from "@/api/user/points"; import {pageUserPointsLog, getUserPointsStats} from "@/api/user/points";
import {UserPointsLog as UserPointsLogType, UserPointsStats} from "@/api/user/points/model"; import {UserPointsLog as UserPointsLogType, UserPointsStats} from "@/api/user/points/model";
import {View} from '@tarojs/components'
const InfiniteUlStyle: CSSProperties = {
height: '100vh',
width: '100%',
padding: '0',
overflowY: 'auto',
overflowX: 'hidden',
}
const UserPoints = () => { const UserPoints = () => {
const [list, setList] = useState<UserPointsLogType[]>([]) const [list, setList] = useState<UserPointsLogType[]>([])
const [loading, setLoading] = useState(false)
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [stats, setStats] = useState<UserPointsStats>({}) const [stats, setStats] = useState<UserPointsStats>({})
const reload = () => {
setLoading(true)
const userId = Taro.getStorageSync('UserId')
useEffect(() => {
reload()
loadPointsStats()
}, [])
console.log('Loading points log for userId:', userId)
const loadMore = async () => {
setPage(page + 1)
reload();
}
const reload = () => {
const userId = Taro.getStorageSync('UserId')
if (!userId) { if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({ Taro.showToast({
title: '请先登录', title: '请先登录',
icon: 'error' icon: 'error'
}); });
setLoading(false)
return return
} }
pageUserPointsLog({ pageUserPointsLog({
userId: parseInt(userId), userId: parseInt(userId),
page: 1,
limit: 20
})
.then((res: any) => {
console.log('Points log response:', res)
setList(res?.list || [])
})
.catch((error: any) => {
page
}).then(res => {
console.log(res)
const newList = res?.list || [];
setList([...list, ...newList])
setHasMore(newList.length > 0)
}).catch(error => {
console.error('Points log error:', error) console.error('Points log error:', error)
Taro.showToast({ Taro.showToast({
title: error?.message || '获取失败', title: error?.message || '获取失败',
icon: 'error' icon: 'error'
}); });
}) })
.finally(() => {
setLoading(false)
})
} }
const loadPointsStats = () => { const loadPointsStats = () => {
@ -60,11 +69,6 @@ const UserPoints = () => {
}) })
} }
useEffect(() => {
reload()
loadPointsStats()
}, []);
const getPointsTypeText = (type?: number) => { const getPointsTypeText = (type?: number) => {
switch (type) { switch (type) {
case 1: return '获得积分' case 1: return '获得积分'
@ -85,21 +89,9 @@ const UserPoints = () => {
} }
} }
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 ( return (
<ConfigProvider> <ConfigProvider>
<View className="bg-gray-50 min-h-screen">
<View className="bg-gray-50 h-screen">
{/* 积分统计卡片 */} {/* 积分统计卡片 */}
<View className="p-4"> <View className="p-4">
<Card className="points-stats-card"> <Card className="points-stats-card">
@ -134,12 +126,34 @@ const UserPoints = () => {
</View> </View>
{/* 积分记录 */} {/* 积分记录 */}
<View className="px-4">
<View className="text-base font-medium text-gray-800 mb-3"></View>
<View className="px-4 flex-1">
<ul style={{...InfiniteUlStyle, height: 'calc(100vh - 200px)'}} id="scroll">
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={loadMore}
onScroll={() => {
console.log('onScroll')
}}
onScrollToUpper={() => {
console.log('onScrollToUpper')
}}
loadingText={
<>
</>
}
loadMoreText={
<>
</>
}
>
<View className="p-4">
{list.length === 0 ? ( {list.length === 0 ? (
<div className={'h-full flex flex-col justify-center items-center'} style={{ <div className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 400px)',
height: 'calc(100vh - 500px)',
}}> }}>
<Empty <Empty
style={{ style={{
@ -147,13 +161,10 @@ const UserPoints = () => {
}} }}
description="您还没有积分记录" description="您还没有积分记录"
/> />
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div> </div>
) : ( ) : (
list.map((item, index) => ( list.map((item, index) => (
<Cell.Group key={index} className="mb-3">
<Cell.Group key={`${item.logId}-${index}`} className="mb-3">
<Cell className="flex flex-col gap-2 p-4"> <Cell className="flex flex-col gap-2 p-4">
<View className="flex justify-between items-start"> <View className="flex justify-between items-start">
<View className="flex-1"> <View className="flex-1">
@ -191,6 +202,9 @@ const UserPoints = () => {
)) ))
)} )}
</View> </View>
</InfiniteLoading>
</ul>
</View>
</View> </View>
</ConfigProvider> </ConfigProvider>
); );

137
src/user/wallet/wallet.tsx

@ -1,99 +1,68 @@
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 {useState, useEffect, CSSProperties} from 'react'
import {Cell, InfiniteLoading} from '@nutui/nutui-react-taro'
import {pageUserBalanceLog} from "@/api/user/balance-log"; import {pageUserBalanceLog} from "@/api/user/balance-log";
import {UserBalanceLog} from "@/api/user/balance-log/model"; import {UserBalanceLog} from "@/api/user/balance-log/model";
import {formatCurrency} from "@/utils/common"; import {formatCurrency} from "@/utils/common";
import {View} from '@tarojs/components'
const InfiniteUlStyle: CSSProperties = {
height: '100vh',
width: '100%',
padding: '0',
overflowY: 'auto',
overflowX: 'hidden',
}
const Wallet = () => { const Wallet = () => {
const [list, setList] = useState<UserBalanceLog[]>([]) const [list, setList] = useState<UserBalanceLog[]>([])
const [loading, setLoading] = useState(false)
const [loadingMore, setLoadingMore] = useState(false)
const [refreshing, setRefreshing] = useState(false)
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true) 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)
useEffect(() => {
reload()
}, [])
if (!userId) {
console.warn('No userId found in storage')
Taro.showToast({
title: '请先登录',
icon: 'error'
});
setLoading(false)
return
const loadMore = async () => {
setPage(page + 1)
reload();
} }
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)
const reload = () => {
pageUserBalanceLog({page}).then(res => {
console.log(res)
const newList = res?.list || [];
setList([...list, ...newList])
setHasMore(newList.length > 0)
}) })
} }
useEffect(() => {
reload()
}, []);
if (loading) {
return ( 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'
<>
<ul style={InfiniteUlStyle} id="scroll">
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={loadMore}
onScroll={() => {
console.log('onScroll')
}} }}
description="您还没有消费记录"
/>
<Space>
<Button onClick={() => reload()}></Button>
</Space>
</div>
</ConfigProvider>
)
onScrollToUpper={() => {
console.log('onScrollToUpper')
}}
loadingText={
<>
</>
} }
return (
<ConfigProvider>
loadMoreText={
<>
</>
}
>
<View className="p-4"> <View className="p-4">
{list.map((item, index) => ( {list.map((item, index) => (
<Cell.Group key={index} className="mb-4">
<Cell.Group key={`${item.logId}-${index}`} className="mb-4">
<Cell className="flex flex-col gap-2 p-4"> <Cell className="flex flex-col gap-2 p-4">
<View className="flex justify-between items-start w-full"> <View className="flex justify-between items-start w-full">
<View className="flex-1"> <View className="flex-1">
@ -105,7 +74,7 @@ const Wallet = () => {
</View> </View>
</View> </View>
<View className={`text-lg font-bold ${ <View className={`text-lg font-bold ${
item.scene === 10 ? 'text-green-500' : ''
item.scene === 10 ? 'text-orange-500' : ''
}`}> }`}>
{item.scene === 10 ? '+' : '-'} {item.scene === 10 ? '+' : '-'}
{formatCurrency(Number(item.money), 'CNY') || '0.00'} {formatCurrency(Number(item.money), 'CNY') || '0.00'}
@ -116,6 +85,7 @@ const Wallet = () => {
<View> <View>
{item.createTime} {item.createTime}
</View> </View>
<View>{item?.balance}</View>
</View> </View>
{item.remark && ( {item.remark && (
@ -127,8 +97,9 @@ const Wallet = () => {
</Cell.Group> </Cell.Group>
))} ))}
</View> </View>
</ConfigProvider>
);
};
export default Wallet;
</InfiniteLoading>
</ul>
</>
)
}
export default Wallet

4
src/utils/server.ts

@ -4,8 +4,8 @@ import {User} from "@/api/system/user/model";
// 模版套餐ID - 请根据实际情况修改 // 模版套餐ID - 请根据实际情况修改
export const TEMPLATE_ID = '10550'; 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 * @param token

Loading…
Cancel
Save