Browse Source
- 新增优惠券领取中心页面,包含热门优惠券轮播、优惠券列表、筛选功能等 - 实现优惠券数据加载、搜索、下拉刷新、加载更多等功能 - 添加优惠券领取逻辑,支持用户领取优惠券 - 优化邀请小程序码生成和分享功能 -调整首页和用户订单组件的样式dev
14 changed files with 695 additions and 162 deletions
@ -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; |
Loading…
Reference in new issue