绿满眶商城微信小程序-uniapp
peng
2025-09-27 0b23a4f1cae5dd34b407614baa88dee3af162f29
扫码门店领取优惠卷
1个文件已修改
2个文件已添加
410 ■■■■■ 已修改文件
api/store-coupon.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/storeClaim/store-claim.vue 362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/store-coupon.js
New file
@@ -0,0 +1,27 @@
import { http, Method } from "@/utils/request.js";
/**
 * 根据店铺优惠券关联ID获取优惠券详情
 * @param {string|number} storeCoupRef - 店铺优惠券关联ID
 * @returns {Promise} 返回优惠券详情
 */
export function getStoreCouponDetail(storeCoupRef) {
  return http.request({
    url: `/lmk/store/coupon/${storeCoupRef}`,
    method: Method.GET,
    needToken: true,
  });
}
/**
 * 领取店铺优惠券
 * @param {string|number} storeCoupRef - 店铺优惠券关联ID
 * @returns {Promise} 返回领取结果
 */
export function claimStoreCoupon(storeCoupRef) {
  return http.request({
    url: `/lmk/store/coupon/${storeCoupRef}`,
    method: Method.POST,
    needToken: true,
  });
}
pages.json
@@ -198,6 +198,7 @@
    //   "path": "pages/tabbar/user/my",
    //   "style": {
    //     "navigationBarTextStyle": "white",
    //     "navigationBarTextStyle": "white",
    //     "enablePullDownRefresh": true,
    //     "navigationStyle": "custom",
    //     "componentPlaceholder": {
@@ -2247,7 +2248,27 @@
          }
        }
      ]
    },
    {
      "root": "pages/storeClaim",
      "pages": [{
          "path" : "store-claim",
          "style" :
          {
              "navigationBarTitleText" : "优惠卷领取",
            "componentPlaceholder":{
                "u-card": "view",
                "u-navbar": "view",
                "u-tag": "view",
                "u-icon": "view",
                "u-button": "view",
                "u-empty": "view"
    }
          }
      }]
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
pages/storeClaim/store-claim.vue
New file
@@ -0,0 +1,362 @@
<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", // 领取状态
                }
            }
        },
        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>