feat(coupon): 添加优惠券领取中心功能
- 新增优惠券领取中心页面,包含热门优惠券轮播、优惠券列表、筛选功能等 - 实现优惠券数据加载、搜索、下拉刷新、加载更多等功能 - 添加优惠券领取逻辑,支持用户领取优惠券 - 优化邀请小程序码生成和分享功能 -调整首页和用户订单组件的样式
This commit is contained in:
510
src/coupon/index.tsx
Normal file
510
src/coupon/index.tsx
Normal file
@@ -0,0 +1,510 @@
|
||||
import {useState} from "react";
|
||||
import Taro, {useDidShow} from '@tarojs/taro'
|
||||
import {
|
||||
Button,
|
||||
Empty,
|
||||
ConfigProvider,
|
||||
SearchBar,
|
||||
InfiniteLoading,
|
||||
Loading,
|
||||
PullToRefresh,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Swiper,
|
||||
SwiperItem
|
||||
} from '@nutui/nutui-react-taro'
|
||||
import {Filter, Board, Gift} from '@nutui/icons-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopCoupon} from "@/api/shop/shopCoupon/model";
|
||||
import {pageShopCoupon, receiveCoupon} from "@/api/shop/shopCoupon";
|
||||
import CouponList from "@/components/CouponList";
|
||||
import CouponGuide from "@/components/CouponGuide";
|
||||
import CouponFilter from "@/components/CouponFilter";
|
||||
import {CouponCardProps} from "@/components/CouponCard";
|
||||
|
||||
const CouponReceiveCenter = () => {
|
||||
const [list, setList] = useState<ShopCoupon[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [activeTab, setActiveTab] = useState('0') // 0-全部 1-满减券 2-折扣券 3-免费券
|
||||
const [hotCoupons, setHotCoupons] = useState<ShopCoupon[]>([]) // 热门优惠券
|
||||
const [showGuide, setShowGuide] = useState(false)
|
||||
const [showFilter, setShowFilter] = useState(false)
|
||||
const [filters, setFilters] = useState({
|
||||
type: [] as number[],
|
||||
minAmount: undefined as number | undefined,
|
||||
sortBy: 'createTime' as 'createTime' | 'amount' | 'expireTime',
|
||||
sortOrder: 'desc' as 'asc' | 'desc'
|
||||
})
|
||||
|
||||
// 获取优惠券类型过滤条件
|
||||
const getTypeFilter = () => {
|
||||
switch (String(activeTab)) {
|
||||
case '0': // 全部
|
||||
return {}
|
||||
case '1': // 满减券
|
||||
return { type: 10 }
|
||||
case '2': // 折扣券
|
||||
return { type: 20 }
|
||||
case '3': // 免费券
|
||||
return { type: 30 }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据传入的值获取类型过滤条件
|
||||
const getTypeFilterByValue = (value: string | number) => {
|
||||
switch (String(value)) {
|
||||
case '0': // 全部
|
||||
return {}
|
||||
case '1': // 满减券
|
||||
return { type: 10 }
|
||||
case '2': // 折扣券
|
||||
return { type: 20 }
|
||||
case '3': // 免费券
|
||||
return { type: 30 }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据类型过滤条件加载优惠券
|
||||
const loadCouponsByType = async (typeFilter: any) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = 1
|
||||
// 获取可领取的优惠券(启用状态且未过期)
|
||||
const res = await pageShopCoupon({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: searchValue,
|
||||
enabled: 1, // 启用状态
|
||||
isExpire: 0, // 未过期
|
||||
...typeFilter
|
||||
})
|
||||
|
||||
console.log('API返回数据:', res)
|
||||
if (res && res.list) {
|
||||
setList(res.list)
|
||||
setHasMore(res.list.length === 10)
|
||||
setPage(2)
|
||||
} else {
|
||||
setList([])
|
||||
setHasMore(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取优惠券失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = isRefresh ? 1 : page
|
||||
const typeFilter = getTypeFilter()
|
||||
console.log('reload - 当前activeTab:', activeTab, '类型过滤:', typeFilter)
|
||||
|
||||
// 获取可领取的优惠券(启用状态且未过期)
|
||||
const res = await pageShopCoupon({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: searchValue,
|
||||
enabled: 1, // 启用状态
|
||||
isExpire: 0, // 未过期
|
||||
...typeFilter,
|
||||
// 应用筛选条件
|
||||
...(filters.type.length > 0 && { type: filters.type[0] }),
|
||||
sortBy: filters.sortBy,
|
||||
sortOrder: filters.sortOrder
|
||||
})
|
||||
|
||||
console.log('reload - API返回数据:', res)
|
||||
if (res && res.list) {
|
||||
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||
setList(newList)
|
||||
|
||||
// 判断是否还有更多数据
|
||||
setHasMore(res.list.length === 10)
|
||||
|
||||
if (!isRefresh) {
|
||||
setPage(currentPage + 1)
|
||||
} else {
|
||||
setPage(2)
|
||||
}
|
||||
} else {
|
||||
setHasMore(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取优惠券失败',
|
||||
icon: 'error'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchValue(value)
|
||||
reload(true)
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
await reload(true)
|
||||
}
|
||||
|
||||
// Tab切换
|
||||
const handleTabChange = (value: string | number) => {
|
||||
console.log('Tab切换到:', value)
|
||||
setActiveTab(String(value))
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
|
||||
// 直接传递类型值,避免异步状态更新问题
|
||||
const typeFilter = getTypeFilterByValue(value)
|
||||
console.log('类型过滤条件:', typeFilter)
|
||||
|
||||
// 立即加载数据
|
||||
loadCouponsByType(typeFilter)
|
||||
}
|
||||
|
||||
// 加载热门优惠券
|
||||
const loadHotCoupons = async () => {
|
||||
try {
|
||||
const res = await pageShopCoupon({
|
||||
page: 1,
|
||||
limit: 5,
|
||||
enabled: 1,
|
||||
isExpire: 0,
|
||||
sortBy: 'createTime',
|
||||
sortOrder: 'desc'
|
||||
})
|
||||
|
||||
if (res && res.list) {
|
||||
setHotCoupons(res.list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取热门优惠券失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换优惠券数据为CouponCard组件所需格式
|
||||
const transformCouponData = (coupon: ShopCoupon): CouponCardProps => {
|
||||
let amount = 0
|
||||
let type: 10 | 20 | 30 = 10
|
||||
|
||||
if (coupon.type === 10) { // 满减券
|
||||
type = 10
|
||||
amount = parseFloat(coupon.reducePrice || '0')
|
||||
} else if (coupon.type === 20) { // 折扣券
|
||||
type = 20
|
||||
amount = coupon.discount || 0
|
||||
} else if (coupon.type === 30) { // 免费券
|
||||
type = 30
|
||||
amount = 0
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id?.toString(),
|
||||
amount,
|
||||
type,
|
||||
status: 0, // 可领取状态
|
||||
minAmount: parseFloat(coupon.minPrice || '0'),
|
||||
title: coupon.name || '优惠券',
|
||||
description: coupon.description,
|
||||
startTime: coupon.startTime,
|
||||
endTime: coupon.endTime,
|
||||
showReceiveBtn: true, // 显示领取按钮
|
||||
onReceive: () => handleReceiveCoupon(coupon),
|
||||
theme: getThemeByType(coupon.type)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据优惠券类型获取主题色
|
||||
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 handleReceiveCoupon = async (coupon: ShopCoupon) => {
|
||||
try {
|
||||
// 检查是否已登录
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if (!userId) {
|
||||
Taro.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用领取接口
|
||||
await receiveCoupon({
|
||||
couponId: coupon.id!,
|
||||
userId: userId
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: '领取成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 刷新列表
|
||||
reload(true)
|
||||
} catch (error: any) {
|
||||
console.error('领取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: error.message || '领取失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 优惠券点击事件
|
||||
const handleCouponClick = (_: CouponCardProps, index: number) => {
|
||||
const originalCoupon = list[index]
|
||||
if (originalCoupon) {
|
||||
// 显示优惠券详情
|
||||
handleCouponDetail(originalCoupon)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示优惠券详情
|
||||
const handleCouponDetail = (coupon: ShopCoupon) => {
|
||||
// 可以显示优惠券详情弹窗或跳转到详情页
|
||||
Taro.showModal({
|
||||
title: coupon.name || '优惠券详情',
|
||||
content: `${coupon.description || ''}
|
||||
|
||||
优惠类型:${coupon.type === 10 ? '满减券' : coupon.type === 20 ? '折扣券' : '免费券'}
|
||||
${coupon.minPrice ? `最低消费:¥${coupon.minPrice}` : ''}
|
||||
有效期:${coupon.startTime} 至 ${coupon.endTime}`,
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
}
|
||||
|
||||
// 筛选条件变更
|
||||
const handleFiltersChange = (newFilters: any) => {
|
||||
setFilters(newFilters)
|
||||
reload(true)
|
||||
}
|
||||
|
||||
// 查看我的优惠券
|
||||
const handleViewMyCoupons = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/user/coupon/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
if (!loading && hasMore) {
|
||||
await reload(false) // 不刷新,追加数据
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload(true)
|
||||
loadHotCoupons()
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
{/* 搜索栏和功能入口 */}
|
||||
<View className="bg-white px-4 py-3">
|
||||
<View className="flex items-center justify-between gap-3">
|
||||
<View className="flex-1">
|
||||
<SearchBar
|
||||
placeholder="搜索优惠券"
|
||||
value={searchValue}
|
||||
className={'border'}
|
||||
onChange={setSearchValue}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
icon={<Filter />}
|
||||
onClick={() => setShowFilter(true)}
|
||||
>
|
||||
筛选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
icon={<Board />}
|
||||
onClick={() => setShowGuide(true)}
|
||||
>
|
||||
帮助
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 热门优惠券轮播 */}
|
||||
{hotCoupons.length > 0 && (
|
||||
<View className="bg-white mb-2">
|
||||
<View className="px-4 py-2 text-sm font-medium text-gray-700">🔥 热门推荐</View>
|
||||
<Swiper
|
||||
height={120}
|
||||
autoPlay
|
||||
loop
|
||||
indicator
|
||||
className="px-4 pb-3"
|
||||
>
|
||||
{hotCoupons.map((coupon, index) => (
|
||||
<SwiperItem key={index}>
|
||||
<View
|
||||
className="bg-gradient-to-r from-red-400 to-pink-500 rounded-lg p-4 text-white mr-4"
|
||||
onClick={() => handleCouponDetail(coupon)}
|
||||
>
|
||||
<View className="flex justify-between items-center">
|
||||
<View>
|
||||
<View className="text-lg font-bold">
|
||||
{coupon.type === 10 ? `¥${coupon.reducePrice}` :
|
||||
coupon.type === 20 ? `${coupon.discount}折` : '免费'}
|
||||
</View>
|
||||
<View className="text-sm opacity-90">
|
||||
{coupon.name}
|
||||
</View>
|
||||
{coupon.minPrice && (
|
||||
<View className="text-xs opacity-80">
|
||||
满¥{coupon.minPrice}可用
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Button
|
||||
size="small"
|
||||
className="bg-white text-red-500 border-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleReceiveCoupon(coupon)
|
||||
}}
|
||||
>
|
||||
立即领取
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</SwiperItem>
|
||||
))}
|
||||
</Swiper>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Tab切换 */}
|
||||
<View className="bg-white">
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<TabPane title="全部" value="0">
|
||||
</TabPane>
|
||||
<TabPane title="满减券" value="1">
|
||||
</TabPane>
|
||||
<TabPane title="折扣券" value="2">
|
||||
</TabPane>
|
||||
<TabPane title="免费券" value="3">
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</View>
|
||||
|
||||
{/* 优惠券列表 */}
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<View style={{ height: '600px', overflowY: 'auto' }} id="coupon-scroll">
|
||||
{list.length === 0 && !loading ? (
|
||||
<View className="flex flex-col justify-center items-center" style={{height: '500px'}}>
|
||||
<Empty
|
||||
description="暂无可领取的优惠券"
|
||||
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}
|
||||
/>
|
||||
</InfiniteLoading>
|
||||
)}
|
||||
</View>
|
||||
</PullToRefresh>
|
||||
|
||||
{/* 底部提示 */}
|
||||
{list.length === 0 && !loading && (
|
||||
<View className="text-center py-8">
|
||||
<View className="text-gray-400 mb-4">
|
||||
<Gift size="48" />
|
||||
</View>
|
||||
<View className="text-gray-500 mb-2">暂无可领取的优惠券</View>
|
||||
<View className="flex gap-2 justify-center">
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={handleViewMyCoupons}
|
||||
>
|
||||
查看我的优惠券
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 使用指南弹窗 */}
|
||||
<CouponGuide
|
||||
visible={showGuide}
|
||||
onClose={() => setShowGuide(false)}
|
||||
/>
|
||||
|
||||
{/* 筛选弹窗 */}
|
||||
<CouponFilter
|
||||
visible={showFilter}
|
||||
filters={filters}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
onClose={() => setShowFilter(false)}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default CouponReceiveCenter;
|
||||
Reference in New Issue
Block a user