From 46761bdacd67717921c2daea412fc9e790d93e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 22 Aug 2025 11:46:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(coupon):=20=E6=B7=BB=E5=8A=A0=E4=BC=98?= =?UTF-8?q?=E6=83=A0=E5=88=B8=E9=A2=86=E5=8F=96=E4=B8=AD=E5=BF=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增优惠券领取中心页面,包含热门优惠券轮播、优惠券列表、筛选功能等 - 实现优惠券数据加载、搜索、下拉刷新、加载更多等功能 - 添加优惠券领取逻辑,支持用户领取优惠券 - 优化邀请小程序码生成和分享功能 -调整首页和用户订单组件的样式 --- config/env.ts | 2 +- src/api/invite/index.ts | 14 +- src/api/shop/shopCoupon/index.ts | 14 + src/components/PaymentCountdown.tsx | 15 +- src/components/SimpleQRCodeModal.tsx | 2 +- src/coupon/index.tsx | 510 ++++++++++++++++++++++++ src/dealer/apply/add.tsx | 29 +- src/dealer/index.tsx | 75 ++-- src/dealer/qrcode/index.tsx | 113 +++--- src/pages/index/Header.tsx | 25 +- src/pages/user/components/UserOrder.tsx | 8 +- src/shop/orderConfirm/index.tsx | 46 ++- src/user/order/components/OrderList.tsx | 2 +- src/utils/invite.ts | 2 +- 14 files changed, 695 insertions(+), 162 deletions(-) create mode 100644 src/coupon/index.tsx diff --git a/config/env.ts b/config/env.ts index 44ddb26..c92883b 100644 --- a/config/env.ts +++ b/config/env.ts @@ -2,7 +2,7 @@ export const ENV_CONFIG = { // 开发环境 development: { - API_BASE_URL: 'http://127.0.0.1:9200/api', + API_BASE_URL: 'https://cms-api.websoft.top/api', APP_NAME: '开发环境', DEBUG: 'true', }, diff --git a/src/api/invite/index.ts b/src/api/invite/index.ts index 0e878b8..c8c007a 100644 --- a/src/api/invite/index.ts +++ b/src/api/invite/index.ts @@ -96,10 +96,10 @@ export interface InviteRecordParam { * 生成小程序码 */ export async function generateMiniProgramCode(data: MiniProgramCodeParam) { - const res = await request.post>( - SERVER_API_URL + '/invite/generate-miniprogram-code', - data + const res = await request.get>( + '/wx-login/getOrderQRCodeUnlimited/' + data.scene ); + console.log(res,'res....') if (res.code === 0) { return res.data; } @@ -109,15 +109,15 @@ export async function generateMiniProgramCode(data: MiniProgramCodeParam) { /** * 生成邀请小程序码 */ -export async function generateInviteCode(inviterId: number, source: string = 'qrcode') { - const scene = `inviter=${inviterId}&source=${source}&t=${Date.now()}`; +export async function generateInviteCode(inviterId: number) { + const scene = `uid_${inviterId}`; return generateMiniProgramCode({ page: 'pages/index/index', scene: scene, - width: 430, + width: 180, checkPath: true, - envVersion: 'release' + envVersion: 'trial' }); } diff --git a/src/api/shop/shopCoupon/index.ts b/src/api/shop/shopCoupon/index.ts index 6d2c177..eef0548 100644 --- a/src/api/shop/shopCoupon/index.ts +++ b/src/api/shop/shopCoupon/index.ts @@ -99,3 +99,17 @@ export async function getShopCoupon(id: number) { } return Promise.reject(new Error(res.message)); } + +/** + * 领取优惠券 + */ +export async function receiveCoupon(params: { couponId: number; userId: number }) { + const res = await request.post>( + '/shop/shop-coupon/receive', + params + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/components/PaymentCountdown.tsx b/src/components/PaymentCountdown.tsx index 351905b..c576c9e 100644 --- a/src/components/PaymentCountdown.tsx +++ b/src/components/PaymentCountdown.tsx @@ -31,7 +31,7 @@ const PaymentCountdown: React.FC = ({ createTime, payStatus = false, realTime = false, - timeoutHours = 24, + timeoutHours = 1, showSeconds = false, className = '', onExpired, @@ -47,17 +47,10 @@ const PaymentCountdown: React.FC = ({ // 如果已过期,触发回调并显示过期状态 if (timeLeft.isExpired) { onExpired?.(); - if (mode === 'text') { - return ( - - 支付已过期 - - ); - } return ( - - 支付已过期 - + + 支付已过期 + ); } diff --git a/src/components/SimpleQRCodeModal.tsx b/src/components/SimpleQRCodeModal.tsx index 61d5732..167c8cc 100644 --- a/src/components/SimpleQRCodeModal.tsx +++ b/src/components/SimpleQRCodeModal.tsx @@ -81,7 +81,7 @@ const SimpleQRCodeModal: React.FC = ({ {qrContent ? ( 二维码 { + const [list, setList] = useState([]) + 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([]) // 热门优惠券 + 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 ( + + {/* 搜索栏和功能入口 */} + + + + + + + + + + + {/* 热门优惠券轮播 */} + {hotCoupons.length > 0 && ( + + 🔥 热门推荐 + + {hotCoupons.map((coupon, index) => ( + + handleCouponDetail(coupon)} + > + + + + {coupon.type === 10 ? `¥${coupon.reducePrice}` : + coupon.type === 20 ? `${coupon.discount}折` : '免费'} + + + {coupon.name} + + {coupon.minPrice && ( + + 满¥{coupon.minPrice}可用 + + )} + + + + + + ))} + + + )} + + {/* Tab切换 */} + + + + + + + + + + + + + + {/* 优惠券列表 */} + + + {list.length === 0 && !loading ? ( + + + + ) : ( + + + 加载中... + + } + loadMoreText={ + + {list.length === 0 ? "暂无数据" : "没有更多了"} + + } + > + + + )} + + + + {/* 底部提示 */} + {list.length === 0 && !loading && ( + + + + + 暂无可领取的优惠券 + + + + + )} + + {/* 使用指南弹窗 */} + setShowGuide(false)} + /> + + {/* 筛选弹窗 */} + setShowFilter(false)} + /> + + ); + +}; + +export default CouponReceiveCenter; diff --git a/src/dealer/apply/add.tsx b/src/dealer/apply/add.tsx index 9042c90..9eadfea 100644 --- a/src/dealer/apply/add.tsx +++ b/src/dealer/apply/add.tsx @@ -15,7 +15,7 @@ import { const AddUserAddress = () => { const {user} = useUser() const [loading, setLoading] = useState(true) - const [FormData, setFormData] = useState({}) + const [FormData, setFormData] = useState() const formRef = useRef(null) const [isEditMode, setIsEditMode] = useState(false) const [existingApply, setExistingApply] = useState(null) @@ -47,16 +47,17 @@ const AddUserAddress = () => { setExistingApply(res.list[0]); // 如果有记录,填充表单数据 setFormData(res.list[0]); + setLoading(false) } else { - setFormData({}) setIsEditMode(false); setExistingApply(null); + setLoading(false) } } catch (error) { + setLoading(true) console.error('查询申请记录失败:', error); setIsEditMode(false); setExistingApply(null); - setFormData({}) } } @@ -122,7 +123,11 @@ const AddUserAddress = () => { if (loading) { return 加载中 } + console.log(FormData,'FromData') + if(!FormData){ + return 加载中 + } return ( <>
{ title={'审核状态'} extra={ - {getApplyStatusText(FormData.applyStatus)} + {getApplyStatusText(FormData?.applyStatus)} } /> - {FormData.applyStatus === 20 && ( - + {FormData?.applyStatus === 20 && ( + )} - {FormData.applyStatus === 30 && ( - + {FormData?.applyStatus === 30 && ( + )} )} {/* 底部浮动按钮 */} - {(!isEditMode || FormData.applyStatus === 10 || FormData.applyStatus === 30) && ( + {(!isEditMode || FormData?.applyStatus === 10 || FormData?.applyStatus === 30) && ( } text={isEditMode ? '保存修改' : '提交申请'} - disabled={FormData.applyStatus === 10} + disabled={FormData?.applyStatus === 10} onClick={handleFixedButtonClick} /> )} diff --git a/src/dealer/index.tsx b/src/dealer/index.tsx index 56ef5eb..da6ce5d 100644 --- a/src/dealer/index.tsx +++ b/src/dealer/index.tsx @@ -7,8 +7,7 @@ import { Dongdong, ArrowRight, Purse, - People, - Presentation + People } from '@nutui/icons-react-taro' import {useDealerUser} from '@/hooks/useDealerUser' import { useThemeStyles } from '@/hooks/useTheme' @@ -244,45 +243,45 @@ const DealerIndex: React.FC = () => { {/* 第二行功能 */} - - navigateToPage('/dealer/invite-stats/index')}> - - - - - - + {/**/} + {/* navigateToPage('/dealer/invite-stats/index')}>*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} - {/* 预留其他功能位置 */} - - - - - - + {/* /!* 预留其他功能位置 *!/*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} - - - - - - + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} - - - - - - - + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/**/} diff --git a/src/dealer/qrcode/index.tsx b/src/dealer/qrcode/index.tsx index ee9fd44..31c2e6d 100644 --- a/src/dealer/qrcode/index.tsx +++ b/src/dealer/qrcode/index.tsx @@ -1,19 +1,19 @@ -import React, { useState, useEffect } from 'react' -import { View, Text, Image } from '@tarojs/components' -import { Button, Loading } from '@nutui/nutui-react-taro' -import { Share, Download, Copy, QrCode } from '@nutui/icons-react-taro' +import React, {useState, useEffect} from 'react' +import {View, Text, Image} from '@tarojs/components' +import {Button, Loading} from '@nutui/nutui-react-taro' +import {Share, Download, Copy, QrCode} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' -import { useDealerUser } from '@/hooks/useDealerUser' -import { generateInviteCode, getInviteStats } from '@/api/invite' -import type { InviteStats } from '@/api/invite' -import { businessGradients } from '@/styles/gradients' +import {useDealerUser} from '@/hooks/useDealerUser' +import {generateInviteCode, getInviteStats} from '@/api/invite' +import type {InviteStats} from '@/api/invite' +import {businessGradients} from '@/styles/gradients' const DealerQrcode: React.FC = () => { const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState('') const [loading, setLoading] = useState(false) const [inviteStats, setInviteStats] = useState(null) const [statsLoading, setStatsLoading] = useState(false) - const { dealerUser } = useDealerUser() + const {dealerUser} = useDealerUser() // 生成小程序码 const generateMiniProgramCode = async () => { @@ -23,8 +23,8 @@ const DealerQrcode: React.FC = () => { setLoading(true) // 生成邀请小程序码 - const codeUrl = await generateInviteCode(dealerUser.userId, 'qrcode') - + const codeUrl = await generateInviteCode(dealerUser.userId) + console.log('小程序码生成成功:', codeUrl) if (codeUrl) { setMiniProgramCodeUrl(codeUrl) } @@ -160,7 +160,7 @@ const DealerQrcode: React.FC = () => { if (!dealerUser) { return ( - + 加载中... ) @@ -179,7 +179,7 @@ const DealerQrcode: React.FC = () => { right: '-16px' }}> - + 我的邀请小程序码 分享小程序码邀请好友,获得丰厚佣金奖励 @@ -189,11 +189,16 @@ const DealerQrcode: React.FC = () => { {/* 小程序码展示区 */} + {/**/} {loading ? ( - + 生成中... ) : miniProgramCodeUrl ? ( @@ -219,48 +224,52 @@ const DealerQrcode: React.FC = () => { )} - + 扫码加入我的团队 - - + + 好友扫描小程序码即可直接进入小程序并建立邀请关系 - + {/* 操作按钮 */} - - - - - - + + + + + + + + + + {/* 推广说明 */} @@ -293,7 +302,7 @@ const DealerQrcode: React.FC = () => { 我的邀请数据 {statsLoading ? ( - + 加载中... ) : inviteStats ? ( @@ -353,7 +362,7 @@ const DealerQrcode: React.FC = () => { ) : ( - 暂无邀请数据 + 暂无邀请数据 - - + + ) : ( -
+ - {getWebsiteName()} + {getWebsiteName()} -
+
)}> diff --git a/src/pages/user/components/UserOrder.tsx b/src/pages/user/components/UserOrder.tsx index c5da9e3..cc0f58d 100644 --- a/src/pages/user/components/UserOrder.tsx +++ b/src/pages/user/components/UserOrder.tsx @@ -39,7 +39,7 @@ function UserOrder() { {/* 待付款 */} {orderStats.pending > 0 ? ( - + navTo('/user/order/order?statusFilter=0', true)}/> @@ -56,7 +56,7 @@ function UserOrder() { {/* 待发货 */} {orderStats.paid > 0 ? ( - + navTo('/user/order/order?statusFilter=1', true)}> @@ -73,7 +73,7 @@ function UserOrder() { {/* 待收货 */} {orderStats.shipped > 0 ? ( - + navTo('/user/order/order?statusFilter=3', true)}> @@ -97,7 +97,7 @@ function UserOrder() { {/* 退货/售后 */} {orderStats.refund > 0 ? ( - + navTo('/user/order/order?statusFilter=6', true)}> diff --git a/src/shop/orderConfirm/index.tsx b/src/shop/orderConfirm/index.tsx index 1bac4d6..399e0c4 100644 --- a/src/shop/orderConfirm/index.tsx +++ b/src/shop/orderConfirm/index.tsx @@ -36,6 +36,7 @@ import { filterUsableCoupons, filterUnusableCoupons } from "@/utils/couponUtils"; +import navTo from "@/utils/common"; const OrderConfirm = () => { @@ -271,30 +272,31 @@ const OrderConfirm = () => { // icon: 'success' // }) } catch (error: any) { - console.error('支付失败:', error) + return navTo('/user/order/order?statusFilter=0', true) + // console.error('支付失败:', error) // 只处理PaymentHandler未处理的错误 - if (!error.handled) { - let errorMessage = '支付失败,请重试'; - - // 根据错误类型提供具体提示 - if (error.message?.includes('余额不足')) { - errorMessage = '账户余额不足,请充值后重试'; - } else if (error.message?.includes('优惠券')) { - errorMessage = '优惠券使用失败,请重新选择'; - } else if (error.message?.includes('库存')) { - errorMessage = '商品库存不足,请减少购买数量'; - } else if (error.message?.includes('地址')) { - errorMessage = '收货地址信息有误,请重新选择'; - } else if (error.message) { - errorMessage = error.message; - } - - Taro.showToast({ - title: errorMessage, - icon: 'error' - }) - } + // if (!error.handled) { + // let errorMessage = '支付失败,请重试'; + // + // // 根据错误类型提供具体提示 + // if (error.message?.includes('余额不足')) { + // errorMessage = '账户余额不足,请充值后重试'; + // } else if (error.message?.includes('优惠券')) { + // errorMessage = '优惠券使用失败,请重新选择'; + // } else if (error.message?.includes('库存')) { + // errorMessage = '商品库存不足,请减少购买数量'; + // } else if (error.message?.includes('地址')) { + // errorMessage = '收货地址信息有误,请重新选择'; + // } else if (error.message) { + // errorMessage = error.message; + // } + // Taro.showToast({ + // title: errorMessage, + // icon: 'error' + // }) + // console.log('跳去未付款的订单列表页面') + // } } finally { setPayLoading(false) } diff --git a/src/user/order/components/OrderList.tsx b/src/user/order/components/OrderList.tsx index 2b2dd9a..642dbef 100644 --- a/src/user/order/components/OrderList.tsx +++ b/src/user/order/components/OrderList.tsx @@ -441,7 +441,7 @@ function OrderList(props: OrderListProps) { payStatus={item.payStatus} realTime={false} showSeconds={false} - mode="text" + mode={'badge'} /> ) : ( getOrderStatusText(item) diff --git a/src/utils/invite.ts b/src/utils/invite.ts index df0a00b..d4cf820 100644 --- a/src/utils/invite.ts +++ b/src/utils/invite.ts @@ -1,5 +1,5 @@ import Taro from '@tarojs/taro' -import { processInviteScene, createInviteRelation } from '@/api/invite' +import { createInviteRelation } from '@/api/invite' /** * 邀请参数接口