docs: 更新优惠券相关文档- 新增优惠券API集成文档

- 新增优惠券卡片对齐修复文档
- 新增优惠券状态显示调试文档
- 新增优惠券组件警告修复文档- 更新用ShopInfo Hook字段迁移文档
- 更新Arguments关键字修复文档
This commit is contained in:
2025-08-15 01:52:36 +08:00
parent dc87f644c9
commit 1b24a611a8
50 changed files with 6530 additions and 595 deletions

View File

@@ -1,8 +1,8 @@
import {useState, useEffect, CSSProperties} from 'react'
import Taro from '@tarojs/taro'
import {Cell, InfiniteLoading, Tabs, TabPane, Tag, Empty, ConfigProvider} from '@nutui/nutui-react-taro'
import {pageUserCoupon, getUserCouponCount} from "@/api/user/coupon";
import {UserCoupon as UserCouponType} from "@/api/user/coupon/model";
import {pageShopUserCoupon as pageUserCoupon, getMyAvailableCoupons, getMyUsedCoupons, getMyExpiredCoupons} from "@/api/shop/shopUserCoupon";
import {ShopUserCoupon as UserCouponType} from "@/api/shop/shopUserCoupon/model";
import {View} from '@tarojs/components'
const InfiniteUlStyle: CSSProperties = {
@@ -71,17 +71,26 @@ const UserCoupon = () => {
})
}
const loadCouponCount = () => {
const loadCouponCount = async () => {
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)
try {
// 并行获取各种状态的优惠券数量
const [availableCoupons, usedCoupons, expiredCoupons] = await Promise.all([
getMyAvailableCoupons().catch(() => []),
getMyUsedCoupons().catch(() => []),
getMyExpiredCoupons().catch(() => [])
])
setCouponCount({
unused: availableCoupons.length || 0,
used: usedCoupons.length || 0,
expired: expiredCoupons.length || 0
})
} catch (error) {
console.error('Coupon count error:', error)
}
}
const onTabChange = (index: string) => {
@@ -97,9 +106,9 @@ const UserCoupon = () => {
const getCouponTypeText = (type?: number) => {
switch (type) {
case 1: return '满减券'
case 2: return '折扣券'
case 3: return '免费券'
case 10: return '满减券'
case 20: return '折扣券'
case 30: return '免费券'
default: return '优惠券'
}
}

View File

@@ -1,10 +1,20 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Empty, ConfigProvider, SearchBar, InfiniteLoading, Loading, PullToRefresh, Tabs, TabPane} from '@nutui/nutui-react-taro'
import {
Button,
Empty,
ConfigProvider,
SearchBar,
InfiniteLoading,
Loading,
PullToRefresh,
Tabs,
TabPane
} from '@nutui/nutui-react-taro'
import {Plus, Filter} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {ShopUserCoupon} from "@/api/shop/shopUserCoupon/model";
import {pageShopUserCoupon} from "@/api/shop/shopUserCoupon";
import {pageShopUserCoupon, getMyAvailableCoupons, getMyUsedCoupons, getMyExpiredCoupons} from "@/api/shop/shopUserCoupon";
import CouponList from "@/components/CouponList";
import CouponStats from "@/components/CouponStats";
import CouponGuide from "@/components/CouponGuide";
@@ -12,6 +22,7 @@ import CouponFilter from "@/components/CouponFilter";
import CouponExpireNotice, {ExpiringSoon} from "@/components/CouponExpireNotice";
import {CouponCardProps} from "@/components/CouponCard";
import dayjs from "dayjs";
import {transformCouponData} from "@/utils/couponUtils";
const CouponManage = () => {
const [list, setList] = useState<ShopUserCoupon[]>([])
@@ -20,7 +31,7 @@ const CouponManage = () => {
const [searchValue, setSearchValue] = useState('')
const [page, setPage] = useState(1)
const [total, setTotal] = useState(0)
console.log('total = ',total)
console.log('total = ', total)
const [activeTab, setActiveTab] = useState('0') // 0-可用 1-已使用 2-已过期
const [stats, setStats] = useState({
available: 0,
@@ -39,77 +50,9 @@ const CouponManage = () => {
})
// 获取优惠券状态过滤条件
const getStatusFilter = () => {
switch (activeTab) {
case '0': // 可用
return { status: 0, isExpire: 0 }
case '1': // 已使用
return { status: 1 }
case '2': // 已过期
return { isExpire: 1 }
default:
return {}
}
}
const reload = async (isRefresh = false) => {
if (isRefresh) {
setPage(1)
setList([])
setHasMore(true)
}
setLoading(true)
try {
const currentPage = isRefresh ? 1 : page
const statusFilter = getStatusFilter()
const res = await pageShopUserCoupon({
page: currentPage,
limit: 10,
keywords: searchValue,
...statusFilter,
// 应用筛选条件
...(filters.type.length > 0 && { type: filters.type[0] }),
...(filters.minAmount && { minAmount: filters.minAmount }),
sortBy: filters.sortBy,
sortOrder: filters.sortOrder
})
if (res && res.list) {
const newList = isRefresh ? res.list : [...list, ...res.list]
console.log('优惠券数据加载成功:', {
isRefresh,
currentPage,
statusFilter,
responseData: res,
newListLength: newList.length,
activeTab
})
setList(newList)
setTotal(res.count || 0)
// 判断是否还有更多数据
setHasMore(res.list.length === 10) // 如果返回的数据等于limit说明可能还有更多
if (!isRefresh) {
setPage(currentPage + 1)
} else {
setPage(2) // 刷新后下一页是第2页
}
} else {
console.log('优惠券数据为空:', res)
setHasMore(false)
setTotal(0)
}
} catch (error) {
console.error('获取优惠券失败:', error)
Taro.showToast({
title: '获取优惠券失败',
icon: 'error'
});
} finally {
setLoading(false)
}
// 直接调用reloadWithTab使用当前的activeTab
await reloadWithTab(activeTab, isRefresh)
}
// 搜索功能
@@ -126,84 +69,140 @@ const CouponManage = () => {
// Tab切换
const handleTabChange = (value: string | number) => {
const tabValue = String(value)
console.log('Tab切换:', { from: activeTab, to: tabValue })
console.log('Tab切换:', {from: activeTab, to: tabValue})
setActiveTab(tabValue)
setPage(1)
setList([])
setHasMore(true)
// 延迟执行reload确保状态更新完成
setTimeout(() => {
reload(true)
}, 100)
// 直接调用reload传入新的tab值
reloadWithTab(tabValue)
}
// 转换优惠券数据为CouponCard组件所需格式
const transformCouponData = (coupon: ShopUserCoupon): CouponCardProps => {
console.log('转换优惠券数据:', coupon)
// 判断优惠券状态
let status: 0 | 1 | 2 = 0 // 默认未使用
if (coupon.isExpire === 1) {
status = 2 // 已过期
} else if (coupon.status === 1) {
status = 1 // 已使用
// 根据指定tab加载数据
const reloadWithTab = async (tab: string, isRefresh = true) => {
if (isRefresh) {
setPage(1)
setList([])
setHasMore(true)
}
// 根据优惠券类型计算金额显示
let amount = 0
let type: 1 | 2 | 3 = 1
setLoading(true)
try {
let res: any = null
if (coupon.type === 10) { // 满减券
type = 1
amount = parseFloat(coupon.reducePrice || '0')
} else if (coupon.type === 20) { // 折扣券
type = 2
amount = coupon.discount || 0
} else if (coupon.type === 30) { // 免费券
type = 3
amount = 0
// 根据tab选择对应的API
switch (tab) {
case '0': // 可用优惠券
res = await getMyAvailableCoupons()
break
case '1': // 已使用优惠券
res = await getMyUsedCoupons()
break
case '2': // 已过期优惠券
res = await getMyExpiredCoupons()
break
default:
res = await getMyAvailableCoupons()
}
console.log('使用Tab加载数据:', { tab, data: res })
if (res && res.length > 0) {
// 应用搜索过滤
let filteredList = res
if (searchValue) {
filteredList = res.filter((item: any) =>
item.name?.includes(searchValue) ||
item.description?.includes(searchValue)
)
}
// 应用其他筛选条件
if (filters.type.length > 0) {
filteredList = filteredList.filter((item: any) =>
filters.type.includes(item.type)
)
}
if (filters.minAmount) {
filteredList = filteredList.filter((item: any) =>
parseFloat(item.minPrice || '0') >= filters.minAmount!
)
}
// 排序
filteredList.sort((a: any, b: any) => {
const aValue = getValueForSort(a, filters.sortBy)
const bValue = getValueForSort(b, filters.sortBy)
if (filters.sortOrder === 'asc') {
return aValue - bValue
} else {
return bValue - aValue
}
})
setList(filteredList)
setTotal(filteredList.length)
setHasMore(false) // 一次性加载所有数据,不需要分页
} else {
setList([])
setTotal(0)
setHasMore(false)
}
} catch (error) {
console.error('获取优惠券失败:', error)
Taro.showToast({
title: '获取优惠券失败',
icon: 'error'
});
setList([])
setTotal(0)
setHasMore(false)
} finally {
setLoading(false)
}
}
// 获取排序值的辅助函数
const getValueForSort = (item: any, sortBy: string) => {
switch (sortBy) {
case 'amount':
return parseFloat(item.reducePrice || item.discount || '0')
case 'expireTime':
return new Date(item.endTime || '').getTime()
case 'createTime':
default:
return new Date(item.createTime || '').getTime()
}
}
// 转换优惠券数据并添加使用按钮
const transformCouponDataWithAction = (coupon: ShopUserCoupon): CouponCardProps => {
console.log('原始优惠券数据:', coupon)
// 使用统一的转换函数
const transformedCoupon = transformCouponData(coupon)
console.log('转换后的优惠券数据:', transformedCoupon)
// 添加使用按钮和点击事件
const result = {
amount,
type,
status,
minAmount: parseFloat(coupon.minPrice || '0'),
title: coupon.name || '优惠券',
startTime: coupon.startTime,
endTime: coupon.endTime,
showUseBtn: status === 0, // 只有未使用的券显示使用按钮
onUse: () => handleUseCoupon(coupon),
theme: getThemeByType(coupon.type)
...transformedCoupon,
showUseBtn: transformedCoupon.status === 0, // 只有未使用的券显示使用按钮
onUse: () => handleUseCoupon(coupon)
}
console.log('转换后的数据:', result)
console.log('最终优惠券数据:', result)
return result
}
// 根据优惠券类型获取主题色
const getThemeByType = (type?: number): 'red' | 'orange' | 'blue' | 'purple' | 'green' => {
switch (type) {
case 10: return 'red' // 满减券
case 20: return 'orange' // 折扣券
case 30: return 'green' // 免费券
default: return 'blue'
}
}
// 使用优惠券
const handleUseCoupon = (coupon: ShopUserCoupon) => {
Taro.showModal({
title: '使用优惠券',
content: `确定要使用"${coupon.name}"吗?`,
success: (res) => {
if (res.confirm) {
// 这里可以跳转到商品页面或购物车页面
Taro.navigateTo({
url: '/pages/index/index'
})
}
}
const handleUseCoupon = (_: ShopUserCoupon) => {
// 这里可以跳转到商品页面或购物车页面
Taro.navigateTo({
url: '/shop/category/index?id=4326'
})
}
@@ -229,18 +228,24 @@ const CouponManage = () => {
try {
// 并行获取各状态的优惠券数量
const [availableRes, usedRes, expiredRes] = await Promise.all([
pageShopUserCoupon({ page: 1, limit: 1, status: 0, isExpire: 0 }),
pageShopUserCoupon({ page: 1, limit: 1, status: 1 }),
pageShopUserCoupon({ page: 1, limit: 1, isExpire: 1 })
getMyAvailableCoupons(),
getMyUsedCoupons(),
getMyExpiredCoupons()
])
setStats({
available: availableRes?.count || 0,
used: usedRes?.count || 0,
expired: expiredRes?.count || 0
available: availableRes?.length || 0,
used: usedRes?.length || 0,
expired: expiredRes?.length || 0
})
} catch (error) {
console.error('获取优惠券统计失败:', error)
// 设置默认值
setStats({
available: 0,
used: 0,
expired: 0
})
}
}
@@ -265,7 +270,7 @@ const CouponManage = () => {
try {
// 获取即将过期的优惠券3天内过期
const res = await pageShopUserCoupon({
page: 1,
page: page,
limit: 50,
status: 0, // 未使用
isExpire: 0 // 未过期
@@ -283,7 +288,7 @@ const CouponManage = () => {
name: coupon.name || '',
type: coupon.type || 10,
amount: coupon.type === 10 ? coupon.reducePrice || '0' :
coupon.type === 20 ? coupon.discount?.toString() || '0' : '0',
coupon.type === 20 ? coupon.discount?.toString() || '0' : '0',
minAmount: coupon.minPrice,
endTime: coupon.endTime || '',
daysLeft
@@ -348,7 +353,7 @@ const CouponManage = () => {
<Button
size="small"
type="primary"
icon={<Plus />}
icon={<Plus/>}
onClick={() => Taro.navigateTo({url: '/user/coupon/receive'})}
>
@@ -356,7 +361,7 @@ const CouponManage = () => {
<Button
size="small"
fill="outline"
icon={<Filter />}
icon={<Filter/>}
onClick={() => setShowFilter(true)}
>
@@ -382,9 +387,9 @@ const CouponManage = () => {
{/* Tab切换 */}
<View className="bg-white">
<Tabs value={activeTab} onChange={handleTabChange}>
<TabPane title="可用" value="0" />
<TabPane title="已使用" value="1" />
<TabPane title="已过期" value="2" />
<TabPane title="可用" value="0"/>
<TabPane title="已使用" value="1"/>
<TabPane title="已过期" value="2"/>
</Tabs>
</View>
@@ -393,50 +398,42 @@ const CouponManage = () => {
onRefresh={handleRefresh}
headHeight={60}
>
<View style={{ height: 'calc(100vh - 200px)', overflowY: 'auto' }} id="coupon-scroll">
{/* 调试信息 */}
<View className="p-2 bg-yellow-100 text-xs text-gray-600">
调试信息: list.length={list.length}, loading={loading.toString()}, activeTab={activeTab}
</View>
{list.length === 0 && !loading ? (
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 300px)'}}>
<Empty
description={
activeTab === '0' ? "暂无可用优惠券" :
activeTab === '1' ? "暂无已使用优惠券" :
"暂无已过期优惠券"
}
style={{backgroundColor: 'transparent'}}
/>
</View>
) : (
<InfiniteLoading
target="coupon-scroll"
hasMore={hasMore}
onLoadMore={loadMore}
loadingText={
<View className="flex justify-center items-center py-4">
<Loading />
<View className="ml-2">...</View>
</View>
}
loadMoreText={
<View className="text-center py-4 text-gray-500">
{list.length === 0 ? "暂无数据" : "没有更多了"}
</View>
}
>
<CouponList
coupons={list.map(transformCouponData)}
onCouponClick={handleCouponClick}
showEmpty={false}
/>
{/* 调试:显示转换后的数据 */}
<View className="p-2 bg-blue-100 text-xs text-gray-600">
: {JSON.stringify(list.map(transformCouponData).slice(0, 2), null, 2)}
<View style={{height: 'calc(100vh - 200px)', overflowY: 'auto'}} id="coupon-scroll">
{list.length === 0 && !loading ? (
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 300px)'}}>
<Empty
description={
activeTab === '0' ? "暂无可用优惠券" :
activeTab === '1' ? "暂无已使用优惠券" :
"暂无已过期优惠券"
}
style={{backgroundColor: 'transparent'}}
/>
</View>
</InfiniteLoading>
)}
) : (
<InfiniteLoading
target="coupon-scroll"
hasMore={hasMore}
onLoadMore={loadMore}
loadingText={
<View className="flex justify-center items-center py-4">
<Loading/>
<View className="ml-2">...</View>
</View>
}
loadMoreText={
<View className="text-center py-4 text-gray-500">
{list.length === 0 ? "暂无数据" : "没有更多了"}
</View>
}
>
<CouponList
coupons={list.map(transformCouponDataWithAction)}
onCouponClick={handleCouponClick}
showEmpty={false}
/>
</InfiniteLoading>
)}
</View>
</PullToRefresh>

View File

@@ -76,16 +76,16 @@ const CouponReceive = () => {
// 转换优惠券数据为CouponCard组件所需格式
const transformCouponData = (coupon: ShopCoupon): CouponCardProps => {
let amount = 0
let type: 1 | 2 | 3 = 1
let type: 10 | 20 | 30 = 10 // 使用新的类型值
if (coupon.type === 10) { // 满减券
type = 1
type = 10
amount = parseFloat(coupon.reducePrice || '0')
} else if (coupon.type === 20) { // 折扣券
type = 2
type = 20
amount = coupon.discount || 0
} else if (coupon.type === 30) { // 免费券
type = 3
type = 30
amount = 0
}

View File

@@ -17,7 +17,7 @@ const GiftCardManage = () => {
const [searchValue, setSearchValue] = useState('')
const [page, setPage] = useState(1)
// const [total, setTotal] = useState(0)
const [activeTab, setActiveTab] = useState('0') // 0-可用 1-已使用 2-已过期
const [activeTab, setActiveTab] = useState<string | number>('0') // 0-可用 1-已使用 2-已过期
const [stats, setStats] = useState({
available: 0,
used: 0,
@@ -34,7 +34,7 @@ const GiftCardManage = () => {
// 获取礼品卡状态过滤条件
const getStatusFilter = () => {
switch (activeTab) {
switch (String(activeTab)) {
case '0': // 可用
return { useStatus: 0 }
case '1': // 已使用
@@ -108,7 +108,7 @@ const GiftCardManage = () => {
}
// Tab切换
const handleTabChange = (value: string) => {
const handleTabChange = (value: string | number) => {
setActiveTab(value)
setPage(1)
setList([])

View File

@@ -1,7 +1,7 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Empty, ConfigProvider, SearchBar, InfiniteLoading, Loading, PullToRefresh} from '@nutui/nutui-react-taro'
import {Gift, Search} from '@nutui/icons-react-taro'
import {Gift} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {ShopCoupon} from "@/api/shop/shopCoupon/model";
import {pageShopCoupon} from "@/api/shop/shopCoupon";
@@ -31,7 +31,7 @@ const CouponReceive = () => {
page: currentPage,
limit: 10,
keywords: searchValue,
enabled: '1', // 启用状态
enabled: 1, // 启用状态
isExpire: 0 // 未过期
})
@@ -76,16 +76,16 @@ const CouponReceive = () => {
// 转换优惠券数据为CouponCard组件所需格式
const transformCouponData = (coupon: ShopCoupon): CouponCardProps => {
let amount = 0
let type: 1 | 2 | 3 = 1
let type: 10 | 20 | 30 = 10 // 使用新的类型值
if (coupon.type === 10) { // 满减券
type = 1
type = 10
amount = parseFloat(coupon.reducePrice || '0')
} else if (coupon.type === 20) { // 折扣券
type = 2
type = 20
amount = coupon.discount || 0
} else if (coupon.type === 30) { // 免费券
type = 3
type = 30
amount = 0
}
@@ -114,16 +114,16 @@ const CouponReceive = () => {
}
// 领取优惠券
const handleReceiveCoupon = async (coupon: ShopCoupon) => {
const handleReceiveCoupon = async (_: ShopCoupon) => {
try {
// 这里应该调用领取优惠券的API
// await receiveCoupon(coupon.id)
Taro.showToast({
title: '领取成功',
icon: 'success'
})
// 刷新列表
reload(true)
} catch (error) {
@@ -136,7 +136,7 @@ const CouponReceive = () => {
}
// 优惠券点击事件
const handleCouponClick = (coupon: CouponCardProps, index: number) => {
const handleCouponClick = (_: CouponCardProps, index: number) => {
const originalCoupon = list[index]
if (originalCoupon) {
// 显示优惠券详情
@@ -172,7 +172,6 @@ const CouponReceive = () => {
value={searchValue}
onChange={setSearchValue}
onSearch={handleSearch}
leftIcon={<Search />}
/>
</View>