feat(pages): 添加多个页面配置和功能模块
- 新增 .editorconfig、.eslintrc、.gitignore 配置文件 - 添加管理员文章管理页面配置和功能实现 - 添加经销商申请注册页面配置和功能实现 - 添加经销商银行卡管理页面配置和功能实现 - 添加经销商客户管理页面配置和功能实现 - 添加用户地址管理页面配置和功能实现 - 添加用户聊天消息页面配置和功能实现 - 添加用户礼品管理页面配置和功能实现
This commit is contained in:
4
src/user/coupon/coupon.ts
Normal file
4
src/user/coupon/coupon.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '我的优惠券',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
237
src/user/coupon/coupon.tsx
Normal file
237
src/user/coupon/coupon.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
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 {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 = {
|
||||
height: '100vh',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}
|
||||
|
||||
const UserCoupon = () => {
|
||||
const [list, setList] = useState<UserCouponType[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
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 }
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
loadCouponCount()
|
||||
}, [])
|
||||
|
||||
const loadMore = async () => {
|
||||
setPage(page + 1)
|
||||
reload();
|
||||
}
|
||||
|
||||
const reload = () => {
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if (!userId) {
|
||||
Taro.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'error'
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
const tab = tabs.find(t => t.key === activeTab)
|
||||
pageUserCoupon({
|
||||
userId: parseInt(userId),
|
||||
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)
|
||||
Taro.showToast({
|
||||
title: error?.message || '获取失败',
|
||||
icon: 'error'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const loadCouponCount = async () => {
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if (!userId) return
|
||||
|
||||
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) => {
|
||||
setActiveTab(index)
|
||||
setList([]) // 清空列表
|
||||
setPage(1) // 重置页码
|
||||
setHasMore(true) // 重置hasMore
|
||||
// 延迟执行reload,确保状态更新完成
|
||||
setTimeout(() => {
|
||||
reload()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const getCouponTypeText = (type?: number) => {
|
||||
switch (type) {
|
||||
case 10: return '满减券'
|
||||
case 20: return '折扣券'
|
||||
case 30: 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
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<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={
|
||||
<>
|
||||
加载中
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
<>
|
||||
没有更多了
|
||||
</>
|
||||
}
|
||||
>
|
||||
<View className="p-4">
|
||||
{list.length === 0 ? (
|
||||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
||||
height: 'calc(100vh - 400px)',
|
||||
}}>
|
||||
<Empty
|
||||
style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
description="您还没有优惠券"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
list.map((item, index) => (
|
||||
<Cell.Group key={`${item.couponId}-${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>
|
||||
</InfiniteLoading>
|
||||
</ul>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</View>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserCoupon;
|
||||
6
src/user/coupon/detail.config.ts
Normal file
6
src/user/coupon/detail.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '优惠券详情',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
259
src/user/coupon/detail.tsx
Normal file
259
src/user/coupon/detail.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
import {useState, useEffect} from "react";
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {Button, ConfigProvider, Tag, Divider} from '@nutui/nutui-react-taro'
|
||||
import {ArrowLeft, Gift, Clock, CartCheck, Share} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ShopCoupon} from "@/api/shop/shopCoupon/model";
|
||||
import {getShopCoupon} from "@/api/shop/shopCoupon";
|
||||
import CouponShare from "@/components/CouponShare";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const CouponDetail = () => {
|
||||
const router = useRouter()
|
||||
const [coupon, setCoupon] = useState<ShopCoupon | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showShare, setShowShare] = useState(false)
|
||||
const couponId = router.params.id
|
||||
|
||||
useEffect(() => {
|
||||
if (couponId) {
|
||||
loadCouponDetail()
|
||||
}
|
||||
}, [couponId])
|
||||
|
||||
const loadCouponDetail = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const data = await getShopCoupon(Number(couponId))
|
||||
setCoupon(data)
|
||||
} catch (error) {
|
||||
console.error('获取优惠券详情失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取优惠券详情失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取优惠券类型文本
|
||||
const getCouponTypeText = (type?: number) => {
|
||||
switch (type) {
|
||||
case 10: return '满减券'
|
||||
case 20: return '折扣券'
|
||||
case 30: return '免费券'
|
||||
default: return '优惠券'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取优惠券金额显示
|
||||
const getCouponAmountDisplay = () => {
|
||||
if (!coupon) return ''
|
||||
|
||||
switch (coupon.type) {
|
||||
case 10: // 满减券
|
||||
return `¥${coupon.reducePrice}`
|
||||
case 20: // 折扣券
|
||||
return `${coupon.discount}折`
|
||||
case 30: // 免费券
|
||||
return '免费'
|
||||
default:
|
||||
return `¥${coupon.reducePrice || 0}`
|
||||
}
|
||||
}
|
||||
|
||||
// 获取使用条件文本
|
||||
const getConditionText = () => {
|
||||
if (!coupon) return ''
|
||||
|
||||
if (coupon.type === 30) return '无门槛使用'
|
||||
if (coupon.minPrice && parseFloat(coupon.minPrice) > 0) {
|
||||
return `满${coupon.minPrice}元可用`
|
||||
}
|
||||
return '无门槛使用'
|
||||
}
|
||||
|
||||
// 获取有效期文本
|
||||
const getValidityText = () => {
|
||||
if (!coupon) return ''
|
||||
|
||||
if (coupon.expireType === 10) {
|
||||
return `领取后${coupon.expireDay}天内有效`
|
||||
} else {
|
||||
return `${dayjs(coupon.startTime).format('YYYY年MM月DD日')} 至 ${dayjs(coupon.endTime).format('YYYY年MM月DD日')}`
|
||||
}
|
||||
}
|
||||
|
||||
// 获取适用范围文本
|
||||
const getApplyRangeText = () => {
|
||||
if (!coupon) return ''
|
||||
|
||||
switch (coupon.applyRange) {
|
||||
case 10: return '全部商品'
|
||||
case 20: return '指定商品'
|
||||
case 30: return '指定分类'
|
||||
default: return '全部商品'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取优惠券状态
|
||||
const getCouponStatus = () => {
|
||||
if (!coupon) return { status: 0, text: '未知', color: 'default' }
|
||||
|
||||
if (coupon.isExpire === 1) {
|
||||
return { status: 2, text: '已过期', color: 'danger' }
|
||||
} else if (coupon.status === 1) {
|
||||
return { status: 1, text: '已使用', color: 'warning' }
|
||||
} else {
|
||||
return { status: 0, text: '可使用', color: 'success' }
|
||||
}
|
||||
}
|
||||
|
||||
// 使用优惠券
|
||||
const handleUseCoupon = () => {
|
||||
if (!coupon) return
|
||||
|
||||
Taro.showModal({
|
||||
title: '使用优惠券',
|
||||
content: `确定要使用"${coupon.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 跳转到商品页面或购物车页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
Taro.navigateBack()
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<View className="flex justify-center items-center h-screen">
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
|
||||
if (!coupon) {
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<View className="flex flex-col justify-center items-center h-screen">
|
||||
<Text className="text-gray-500 mb-4">优惠券不存在</Text>
|
||||
<Button onClick={handleBack}>返回</Button>
|
||||
</View>
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const statusInfo = getCouponStatus()
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
{/* 自定义导航栏 */}
|
||||
<View className="flex items-center justify-between p-4 bg-white border-b border-gray-100">
|
||||
<View className="flex items-center" onClick={handleBack}>
|
||||
<ArrowLeft size="20" />
|
||||
<Text className="ml-2 text-lg">优惠券详情</Text>
|
||||
</View>
|
||||
<View className="flex items-center gap-3">
|
||||
<View onClick={() => setShowShare(true)}>
|
||||
<Share size="20" className="text-gray-600" />
|
||||
</View>
|
||||
<Tag type={statusInfo.color as any}>{statusInfo.text}</Tag>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 优惠券卡片 */}
|
||||
<View className="m-4 p-6 bg-gradient-to-r from-red-400 to-red-500 rounded-2xl text-white">
|
||||
<View className="flex items-center justify-between mb-4">
|
||||
<View>
|
||||
<Text className="text-4xl font-bold">{getCouponAmountDisplay()}</Text>
|
||||
<Text className="text-lg opacity-90 mt-1">{getCouponTypeText(coupon.type)}</Text>
|
||||
</View>
|
||||
<Gift size="40" />
|
||||
</View>
|
||||
|
||||
<Text className="text-xl font-semibold mb-2">{coupon.name}</Text>
|
||||
<Text className="text-base opacity-90">{getConditionText()}</Text>
|
||||
</View>
|
||||
|
||||
{/* 详细信息 */}
|
||||
<View className="bg-white mx-4 rounded-xl p-4">
|
||||
<Text className="text-lg font-semibold mb-4">使用说明</Text>
|
||||
|
||||
<View className="gap-2">
|
||||
<View className="flex items-center">
|
||||
<Clock size="16" className="text-gray-400 mr-3" />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm">有效期</Text>
|
||||
<Text className="text-gray-900">{getValidityText()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Divider />
|
||||
|
||||
<View className="flex items-center">
|
||||
<CartCheck size="16" className="text-gray-400 mr-3" />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm">适用范围</Text>
|
||||
<Text className="text-gray-900">{getApplyRangeText()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{coupon.description && (
|
||||
<>
|
||||
<Divider />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm mb-2">使用说明</Text>
|
||||
<Text className="text-gray-900 leading-relaxed">{coupon.description}</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部操作按钮 */}
|
||||
{statusInfo.status === 0 && (
|
||||
<View className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
onClick={handleUseCoupon}
|
||||
>
|
||||
立即使用
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 分享弹窗 */}
|
||||
{coupon && (
|
||||
<CouponShare
|
||||
visible={showShare}
|
||||
coupon={{
|
||||
id: coupon.id || 0,
|
||||
name: coupon.name || '',
|
||||
type: coupon.type || 10,
|
||||
amount: coupon.type === 10 ? coupon.reducePrice || '0' :
|
||||
coupon.type === 20 ? coupon.discount?.toString() || '0' : '0',
|
||||
minAmount: coupon.minPrice,
|
||||
description: coupon.description
|
||||
}}
|
||||
onClose={() => setShowShare(false)}
|
||||
/>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CouponDetail;
|
||||
5
src/user/coupon/index.config.ts
Normal file
5
src/user/coupon/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '我的优惠券',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
})
|
||||
466
src/user/coupon/index.tsx
Normal file
466
src/user/coupon/index.tsx
Normal file
@@ -0,0 +1,466 @@
|
||||
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 {Plus, Filter} from '@nutui/icons-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopUserCoupon} from "@/api/shop/shopUserCoupon/model";
|
||||
import {pageShopUserCoupon, getMyAvailableCoupons, getMyUsedCoupons, getMyExpiredCoupons} from "@/api/shop/shopUserCoupon";
|
||||
import CouponList from "@/components/CouponList";
|
||||
import CouponStats from "@/components/CouponStats";
|
||||
import CouponGuide from "@/components/CouponGuide";
|
||||
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[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [total, setTotal] = useState(0)
|
||||
console.log('total = ', total)
|
||||
const [activeTab, setActiveTab] = useState('0') // 0-可用 1-已使用 2-已过期
|
||||
const [stats, setStats] = useState({
|
||||
available: 0,
|
||||
used: 0,
|
||||
expired: 0
|
||||
})
|
||||
const [showGuide, setShowGuide] = useState(false)
|
||||
const [showFilter, setShowFilter] = useState(false)
|
||||
const [showExpireNotice, setShowExpireNotice] = useState(false)
|
||||
const [expiringSoonCoupons, setExpiringSoonCoupons] = useState<ExpiringSoon[]>([])
|
||||
const [filters, setFilters] = useState({
|
||||
type: [] as number[],
|
||||
minAmount: undefined as number | undefined,
|
||||
sortBy: 'createTime' as 'createTime' | 'amount' | 'expireTime',
|
||||
sortOrder: 'desc' as 'asc' | 'desc'
|
||||
})
|
||||
|
||||
// 获取优惠券状态过滤条件
|
||||
const reload = async (isRefresh = false) => {
|
||||
// 直接调用reloadWithTab,使用当前的activeTab
|
||||
await reloadWithTab(activeTab, isRefresh)
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchValue(value)
|
||||
reload(true)
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
await reload(true)
|
||||
}
|
||||
|
||||
// Tab切换
|
||||
const handleTabChange = (value: string | number) => {
|
||||
const tabValue = String(value)
|
||||
console.log('Tab切换:', {from: activeTab, to: tabValue})
|
||||
setActiveTab(tabValue)
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
|
||||
// 直接调用reload,传入新的tab值
|
||||
reloadWithTab(tabValue)
|
||||
}
|
||||
|
||||
// 根据指定tab加载数据
|
||||
const reloadWithTab = async (tab: string, isRefresh = true) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
let res: any = null
|
||||
|
||||
// 根据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 = {
|
||||
...transformedCoupon,
|
||||
showUseBtn: transformedCoupon.status === 0, // 只有未使用的券显示使用按钮
|
||||
onUse: () => handleUseCoupon(coupon)
|
||||
}
|
||||
|
||||
console.log('最终优惠券数据:', result)
|
||||
return result
|
||||
}
|
||||
|
||||
// 使用优惠券
|
||||
const handleUseCoupon = (_: ShopUserCoupon) => {
|
||||
// 这里可以跳转到商品页面或购物车页面
|
||||
Taro.navigateTo({
|
||||
url: '/shop/category/index?id=4326'
|
||||
})
|
||||
}
|
||||
|
||||
// 优惠券点击事件
|
||||
const handleCouponClick = (_coupon: CouponCardProps, index: number) => {
|
||||
const originalCoupon = list[index]
|
||||
if (originalCoupon) {
|
||||
// 显示优惠券详情
|
||||
showCouponDetail(originalCoupon)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示优惠券详情
|
||||
const showCouponDetail = (coupon: ShopUserCoupon) => {
|
||||
// 跳转到优惠券详情页
|
||||
Taro.navigateTo({
|
||||
url: `/user/coupon/detail?id=${coupon.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 加载优惠券统计数据
|
||||
const loadCouponStats = async () => {
|
||||
try {
|
||||
// 并行获取各状态的优惠券数量
|
||||
const [availableRes, usedRes, expiredRes] = await Promise.all([
|
||||
getMyAvailableCoupons(),
|
||||
getMyUsedCoupons(),
|
||||
getMyExpiredCoupons()
|
||||
])
|
||||
|
||||
setStats({
|
||||
available: availableRes?.length || 0,
|
||||
used: usedRes?.length || 0,
|
||||
expired: expiredRes?.length || 0
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取优惠券统计失败:', error)
|
||||
// 设置默认值
|
||||
setStats({
|
||||
available: 0,
|
||||
used: 0,
|
||||
expired: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片点击事件
|
||||
const handleStatsClick = (type: 'available' | 'used' | 'expired') => {
|
||||
const tabMap = {
|
||||
available: '0',
|
||||
used: '1',
|
||||
expired: '2'
|
||||
}
|
||||
handleTabChange(tabMap[type])
|
||||
}
|
||||
|
||||
// 筛选条件变更
|
||||
const handleFiltersChange = (newFilters: any) => {
|
||||
setFilters(newFilters)
|
||||
reload(true).then()
|
||||
}
|
||||
|
||||
// 检查即将过期的优惠券
|
||||
const checkExpiringSoonCoupons = async () => {
|
||||
try {
|
||||
// 获取即将过期的优惠券(3天内过期)
|
||||
const res = await pageShopUserCoupon({
|
||||
page: page,
|
||||
limit: 50,
|
||||
status: 0, // 未使用
|
||||
isExpire: 0 // 未过期
|
||||
})
|
||||
|
||||
if (res && res.list) {
|
||||
const now = dayjs()
|
||||
const expiringSoon = res.list
|
||||
.map(coupon => {
|
||||
const endTime = dayjs(coupon.endTime)
|
||||
const daysLeft = endTime.diff(now, 'day')
|
||||
|
||||
return {
|
||||
id: coupon.id || 0,
|
||||
name: coupon.name || '',
|
||||
type: coupon.type || 10,
|
||||
amount: coupon.type === 10 ? coupon.reducePrice || '0' :
|
||||
coupon.type === 20 ? coupon.discount?.toString() || '0' : '0',
|
||||
minAmount: coupon.minPrice,
|
||||
endTime: coupon.endTime || '',
|
||||
daysLeft
|
||||
}
|
||||
})
|
||||
.filter(coupon => coupon.daysLeft >= 0 && coupon.daysLeft <= 3)
|
||||
.sort((a, b) => a.daysLeft - b.daysLeft)
|
||||
|
||||
if (expiringSoon.length > 0) {
|
||||
// @ts-ignore
|
||||
setExpiringSoonCoupons(expiringSoon)
|
||||
// 延迟显示提醒,避免与页面加载冲突
|
||||
setTimeout(() => {
|
||||
setShowExpireNotice(true)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查即将过期优惠券失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用即将过期的优惠券
|
||||
const handleUseExpiringSoonCoupon = (coupon: ExpiringSoon) => {
|
||||
console.log(coupon, '使用即将过期优惠券')
|
||||
setShowExpireNotice(false)
|
||||
// 跳转到商品页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
if (!loading && hasMore) {
|
||||
await reload(false) // 不刷新,追加数据
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload(true).then()
|
||||
loadCouponStats().then()
|
||||
// 只在可用优惠券tab时检查即将过期的优惠券
|
||||
if (activeTab === '0') {
|
||||
checkExpiringSoonCoupons().then()
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
{/* 搜索栏和领取入口 */}
|
||||
<View className="bg-white px-4 py-3 hidden">
|
||||
<View className="flex items-center gap-3">
|
||||
<View className="flex-1">
|
||||
<SearchBar
|
||||
placeholder="搜索"
|
||||
value={searchValue}
|
||||
onChange={setSearchValue}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<Plus/>}
|
||||
onClick={() => Taro.navigateTo({url: '/user/coupon/receive'})}
|
||||
>
|
||||
领取
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
icon={<Filter/>}
|
||||
onClick={() => setShowFilter(true)}
|
||||
>
|
||||
筛选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
onClick={() => setShowGuide(true)}
|
||||
>
|
||||
帮助
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 优惠券统计 */}
|
||||
<CouponStats
|
||||
availableCount={stats.available}
|
||||
usedCount={stats.used}
|
||||
expiredCount={stats.expired}
|
||||
onStatsClick={handleStatsClick}
|
||||
/>
|
||||
|
||||
{/* Tab切换 */}
|
||||
<View className="bg-white">
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<TabPane title="可用" value="0"/>
|
||||
<TabPane title="已使用" value="1"/>
|
||||
<TabPane title="已过期" value="2"/>
|
||||
</Tabs>
|
||||
</View>
|
||||
|
||||
{/* 优惠券列表 */}
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<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
|
||||
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>
|
||||
|
||||
{/* 使用指南弹窗 */}
|
||||
<CouponGuide
|
||||
visible={showGuide}
|
||||
onClose={() => setShowGuide(false)}
|
||||
/>
|
||||
|
||||
{/*/!* 筛选弹窗 *!/*/}
|
||||
<CouponFilter
|
||||
visible={showFilter}
|
||||
filters={filters}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
onClose={() => setShowFilter(false)}
|
||||
/>
|
||||
|
||||
{/*/!* 到期提醒弹窗 *!/*/}
|
||||
<CouponExpireNotice
|
||||
visible={showExpireNotice}
|
||||
expiringSoonCoupons={expiringSoonCoupons}
|
||||
onClose={() => setShowExpireNotice(false)}
|
||||
onUseCoupon={handleUseExpiringSoonCoupon}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default CouponManage;
|
||||
5
src/user/coupon/receive.config.ts
Normal file
5
src/user/coupon/receive.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '领取优惠券',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
})
|
||||
247
src/user/coupon/receive.tsx
Normal file
247
src/user/coupon/receive.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
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} from '@nutui/icons-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopCoupon} from "@/api/shop/shopCoupon/model";
|
||||
import {pageShopCoupon} from "@/api/shop/shopCoupon";
|
||||
import CouponList from "@/components/CouponList";
|
||||
import {CouponCardProps} from "@/components/CouponCard";
|
||||
|
||||
const CouponReceive = () => {
|
||||
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 [total, setTotal] = useState(0)
|
||||
|
||||
const reload = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = isRefresh ? 1 : page
|
||||
// 获取可领取的优惠券(启用状态且未过期)
|
||||
const res = await pageShopCoupon({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: searchValue,
|
||||
enabled: 1, // 启用状态
|
||||
isExpire: 0 // 未过期
|
||||
})
|
||||
|
||||
if (res && res.list) {
|
||||
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||
setList(newList)
|
||||
setTotal(res.count || 0)
|
||||
|
||||
setHasMore(res.list.length === 10)
|
||||
|
||||
if (!isRefresh) {
|
||||
setPage(currentPage + 1)
|
||||
} else {
|
||||
setPage(2)
|
||||
}
|
||||
} else {
|
||||
setHasMore(false)
|
||||
setTotal(0)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
// 转换优惠券数据为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 {
|
||||
amount,
|
||||
type,
|
||||
status: 0, // 可领取状态
|
||||
minAmount: parseFloat(coupon.minPrice || '0'),
|
||||
title: coupon.name || '优惠券',
|
||||
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 {
|
||||
// 这里应该调用领取优惠券的API
|
||||
// await receiveCoupon(coupon.id)
|
||||
|
||||
Taro.showToast({
|
||||
title: '领取成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 刷新列表
|
||||
reload(true)
|
||||
} catch (error) {
|
||||
console.error('领取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: '领取失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 优惠券点击事件
|
||||
const handleCouponClick = (_coupon: CouponCardProps, index: number) => {
|
||||
const originalCoupon = list[index]
|
||||
if (originalCoupon) {
|
||||
// 显示优惠券详情
|
||||
showCouponDetail(originalCoupon)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示优惠券详情
|
||||
const showCouponDetail = (coupon: ShopCoupon) => {
|
||||
// 跳转到优惠券详情页
|
||||
Taro.navigateTo({
|
||||
url: `/user/coupon/detail?id=${coupon.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
if (!loading && hasMore) {
|
||||
await reload(false)
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload(true).then()
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
{/* 搜索栏 */}
|
||||
<View className="bg-white px-4 py-3">
|
||||
<SearchBar
|
||||
placeholder="搜索"
|
||||
value={searchValue}
|
||||
onChange={setSearchValue}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 统计信息 */}
|
||||
{total > 0 && (
|
||||
<View className="px-4 py-2 text-sm text-gray-500 bg-gray-50">
|
||||
共找到 {total} 张可领取优惠券
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 优惠券列表 */}
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<View style={{ height: 'calc(100vh - 160px)', overflowY: 'auto' }} id="coupon-scroll">
|
||||
{list.length === 0 && !loading ? (
|
||||
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 250px)'}}>
|
||||
<Empty
|
||||
description="暂无可领取优惠券"
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
className="mt-4"
|
||||
onClick={() => Taro.navigateTo({url: '/pages/index/index'})}
|
||||
>
|
||||
去逛逛
|
||||
</Button>
|
||||
</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="text-gray-400 text-sm">请关注商家活动获取更多优惠券</View>
|
||||
</View>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CouponReceive;
|
||||
Reference in New Issue
Block a user