New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <!-- 优惠券卡片 --> |
| | | <view class="coupon-card" v-if="couponInfo.storeCoupRef"> |
| | | <u-card :border="false" :head-style="{ padding: '30rpx' }" :body-style="{ padding: '0 30rpx 30rpx' }"> |
| | | <!-- 头部:店铺信息 --> |
| | | <view slot="head" class="card-head"> |
| | | <view class="store-info"> |
| | | <u-icon name="home" size="36" color="#999"></u-icon> |
| | | <text class="store-name">{{ couponInfo.storeName }}</text> |
| | | </view> |
| | | <u-tag v-if="couponInfo.claimStatus === 'NOT_CLAIM'" text="未领取" type="warning" mode="plain" size="mini" /> |
| | | <u-tag v-else-if="couponInfo.claimStatus === 'CLAIM'" text="已领取" type="success" mode="plain" size="mini" /> |
| | | <u-tag v-else text="已过期" type="info" mode="plain" size="mini" /> |
| | | </view> |
| | | |
| | | <!-- 主体:优惠券信息 --> |
| | | <view slot="body" class="card-body"> |
| | | <view class="coupon-title"> |
| | | <text class="title">{{ couponInfo.couponName }}</text> |
| | | <text class="coupon-no">编号:{{ couponInfo.couponNo }}</text> |
| | | </view> |
| | | |
| | | <view class="coupon-desc" v-if="couponInfo.couponDesc"> |
| | | <text class="desc">{{ couponInfo.couponDesc }}</text> |
| | | </view> |
| | | |
| | | <view class="coupon-rule" v-if="couponInfo.couponRule"> |
| | | <text class="rule-title">使用规则:</text> |
| | | <text class="rule-content">{{ couponInfo.couponRule }}</text> |
| | | </view> |
| | | |
| | | <view class="coupon-time" v-if="couponInfo.startTime && couponInfo.endTime"> |
| | | <u-icon name="clock" size="28" color="#999"></u-icon> |
| | | <text class="time-text">有效期:{{ formatDate(couponInfo.startTime) }} 至 {{ formatDate(couponInfo.endTime) }}</text> |
| | | </view> |
| | | |
| | | <view class="coupon-condition" v-if="couponInfo.consumeThreshold > 0"> |
| | | <u-icon name="coupon" size="28" color="#999"></u-icon> |
| | | <text class="condition-text">满{{ formatAmount(couponInfo.consumeThreshold) }}元可用</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 底部:操作按钮 --> |
| | | <view slot="foot" class="card-foot"> |
| | | <u-button |
| | | :type="couponInfo.claimStatus === 'NOT_CLAIM' ? 'primary' : 'default'" |
| | | :disabled="couponInfo.claimStatus !== 'NOT_CLAIM'" |
| | | :loading="loading" |
| | | @click="claimCoupon" |
| | | :ripple="true" |
| | | :hair-line="false" |
| | | > |
| | | {{ couponInfo.claimStatus === 'NOT_CLAIM' ? '立即领取' : couponInfo.claimStatus === 'CLAIM' ? '已领取' : '已过期' }} |
| | | </u-button> |
| | | </view> |
| | | </u-card> |
| | | </view> |
| | | |
| | | <!-- 空状态 --> |
| | | <view class="empty-state" v-else> |
| | | <u-empty text="优惠券信息不存在" mode="coupon" :icon-size="200"></u-empty> |
| | | </view> |
| | | |
| | | <!-- 使用说明 --> |
| | | <view class="instructions"> |
| | | <view class="instructions-title"> |
| | | <u-icon name="info-circle" size="28" color="#999"></u-icon> |
| | | <text class="title-text">使用说明</text> |
| | | </view> |
| | | <view class="instructions-content"> |
| | | <text class="content-text">1. 优惠券仅限在指定店铺使用\n2. 每个用户限领一张\n3. 优惠券不可兑换现金\n4. 请在有效期内使用</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 空白占位 --> |
| | | <view class="placeholder"></view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { getStoreCouponDetail, claimStoreCoupon } from '@/api/store-coupon.js'; |
| | | import { formatPrice } from '@/utils/Foundation.js'; |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | loading: false, |
| | | storeCoupRef: '', // 店铺优惠券关联ID |
| | | couponInfo: { |
| | | id: "", |
| | | storeCoupRef: "", |
| | | storeId: "", |
| | | storeName: "", |
| | | couponId: "", |
| | | couponName: "", |
| | | couponNo: "", |
| | | couponAmount: 0, // 优惠金额 |
| | | couponDesc: "", // 优惠券描述 |
| | | couponRule: "", // 使用规则 |
| | | startTime: "", // 开始时间 |
| | | endTime: "", // 结束时间 |
| | | consumeThreshold: 0, // 消费门槛 |
| | | claimStatus: "NOT_CLAIM", // 领取状态 |
| | | } |
| | | } |
| | | }, |
| | | onShow() { |
| | | this.getCouponDetail(this.storeCoupRef); |
| | | }, |
| | | onLoad(options) { |
| | | // 获取传递的店铺优惠券关联ID |
| | | if (options.storeCoupRef) { |
| | | this.storeCoupRef = options.storeCoupRef; |
| | | this.getCouponDetail(options.storeCoupRef); |
| | | } else if (options.id) { |
| | | // 兼容旧参数 |
| | | this.storeCoupRef = options.id; |
| | | this.getCouponDetail(options.id); |
| | | }else if(options.q){ |
| | | // 双重解码:微信对URL进行了两次编码 |
| | | const decodedUrl = decodeURIComponent(decodeURIComponent(options.q)); |
| | | console.log('原始URL:', decodedUrl); |
| | | |
| | | // 解析URL中的查询参数 |
| | | const params = this.parseUrlParams(decodedUrl); |
| | | this.storeCoupRef = params.id; |
| | | this.getCouponDetail(this.storeCoupRef); |
| | | } |
| | | else { |
| | | this.$u.toast('参数错误'); |
| | | } |
| | | }, |
| | | methods: { |
| | | // 解析URL参数 |
| | | parseUrlParams(url) { |
| | | const params = {}; |
| | | // 处理可能存在的hash(如果有的话) |
| | | const cleanUrl = url.split('#')[0]; |
| | | const queryStr = cleanUrl.split('?')[1] || ''; |
| | | |
| | | queryStr.split('&').forEach(pair => { |
| | | const [key, value] = pair.split('='); |
| | | if (key) { |
| | | // 如果值存在,则解码,否则设为空字符串 |
| | | params[key] = value ? decodeURIComponent(value) : ''; |
| | | } |
| | | }); |
| | | |
| | | return params; |
| | | }, |
| | | // 格式化金额 |
| | | formatAmount(amount) { |
| | | if (!amount) return '0'; |
| | | return formatPrice(parseFloat(amount)); |
| | | }, |
| | | |
| | | // 格式化日期 |
| | | formatDate(dateStr) { |
| | | if (!dateStr) return ''; |
| | | return dateStr.split(' ')[0]; |
| | | }, |
| | | |
| | | // 获取优惠券详情 |
| | | async getCouponDetail(storeCoupRef) { |
| | | uni.showLoading({ |
| | | title: '加载中...' |
| | | }); |
| | | |
| | | try { |
| | | const res = await getStoreCouponDetail(storeCoupRef); |
| | | console.log(JSON.stringify(res)) |
| | | if (res.data.code === 200) { |
| | | this.couponInfo = { |
| | | ...this.couponInfo, |
| | | ...res.data.data, |
| | | couponAmount: res.data.data.couponAmount || 0, |
| | | consumeThreshold: res.data.data.consumeThreshold || 0 |
| | | }; |
| | | } else { |
| | | this.$u.toast(res.data.message || '获取优惠券详情失败'); |
| | | } |
| | | } catch (err) { |
| | | this.$u.toast('获取优惠券详情失败,请稍后重试'); |
| | | console.error('获取优惠券详情失败:', err); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }, |
| | | |
| | | // 领取优惠券 |
| | | async claimCoupon() { |
| | | if (this.couponInfo.claimStatus !== 'NOT_CLAIM') { |
| | | return; |
| | | } |
| | | |
| | | // 确认领取 |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: `确定要领取"${this.couponInfo.couponName}"优惠券吗?`, |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | this.doClaimCoupon(); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 执行领取优惠券操作 |
| | | async doClaimCoupon() { |
| | | this.loading = true; |
| | | try { |
| | | // 调用领取优惠券接口 |
| | | const res = await claimStoreCoupon(this.storeCoupRef); |
| | | if (res.data.code === 200) { |
| | | this.$u.toast('领取成功'); |
| | | this.couponInfo.claimStatus = 'CLAIM'; |
| | | |
| | | // 延迟返回上一页,让用户看到领取成功的提示 |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | this.$u.toast(res.data.message || '领取失败'); |
| | | } |
| | | } catch (err) { |
| | | this.$u.toast('领取失败,请稍后重试'); |
| | | console.error('领取优惠券失败:', err); |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .container { |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .coupon-card { |
| | | margin: 20rpx; |
| | | } |
| | | |
| | | .card-head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .store-info { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .store-name { |
| | | margin-left: 10rpx; |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .card-body { |
| | | padding-top: 20rpx; |
| | | } |
| | | |
| | | .coupon-title { |
| | | margin-bottom: 30rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .title { |
| | | font-size: 36rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .coupon-no { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-left: 20rpx; |
| | | } |
| | | |
| | | .coupon-desc { |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | .desc { |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .coupon-rule { |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | .rule-title { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .rule-content { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .coupon-time, .coupon-condition { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .time-text, .condition-text { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-left: 10rpx; |
| | | } |
| | | |
| | | .card-foot { |
| | | padding: 20rpx 0; |
| | | } |
| | | |
| | | .instructions { |
| | | background-color: #ffffff; |
| | | margin: 20rpx; |
| | | padding: 30rpx; |
| | | border-radius: 16rpx; |
| | | } |
| | | |
| | | .instructions-title { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .title-text { |
| | | margin-left: 10rpx; |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .instructions-content { |
| | | padding-left: 40rpx; |
| | | } |
| | | |
| | | .content-text { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .placeholder { |
| | | height: 40rpx; |
| | | } |
| | | |
| | | .empty-state { |
| | | margin-top: 200rpx; |
| | | } |
| | | </style> |