uni-app 优惠券系统

章节介绍

优惠券系统是现代应用中常见的营销工具,通过优惠券的发放、领取和使用,可以有效提升用户转化率和销售额。在 uni-app 中实现优惠券系统需要考虑跨端适配、数据同步、规则管理等多个方面。本教程将详细介绍 uni-app 优惠券系统的核心知识点和实现方法,帮助开发者快速构建功能完善的优惠券体系。

核心知识点

优惠券系统架构设计

优惠券系统通常包含以下核心组件:

  1. 优惠券管理模块:处理优惠券的创建、更新和管理
  2. 规则引擎模块:定义和管理优惠券规则
  3. 发放模块:管理优惠券的发放方式
  4. 领取模块:处理用户领取优惠券
  5. 使用模块:管理优惠券的使用场景
  6. 核销模块:处理优惠券的核销逻辑
  7. 数据分析模块:分析优惠券的使用效果和用户行为

优惠券类型设计

常见的优惠券类型包括:

  1. 满减券:满足一定金额条件后减免
  2. 折扣券:按照一定比例折扣
  3. 代金券:直接抵扣固定金额
  4. 免邮券:免除运费
  5. 赠品券:赠送指定商品
  6. 组合券:多种优惠组合

优惠券规则设计

优惠券规则是优惠券系统的核心,需要考虑以下因素:

  1. 使用条件:优惠券的使用条件,如最低消费金额
  2. 使用范围:优惠券的适用范围,如特定商品、品类或全场
  3. 使用限制:优惠券的使用限制,如每人限领、限用次数
  4. 有效期:优惠券的有效时间
  5. 叠加规则:优惠券是否可以与其他优惠叠加使用
  6. 发放规则:优惠券的发放规则,如发放数量、发放对象

优惠券发放方式

常见的优惠券发放方式包括:

  1. 主动发放:系统主动向用户发放优惠券
  2. 活动领取:用户通过参与活动领取优惠券
  3. 积分兑换:用户使用积分兑换优惠券
  4. 分享领取:用户分享或邀请好友领取优惠券
  5. 购买获得:用户购买特定商品获得优惠券
  6. 会员专享:特定等级会员专享优惠券

优惠券使用场景

常见的优惠券使用场景包括:

  1. 线上购物:在应用内购物时使用优惠券
  2. 线下消费:在线下门店消费时使用优惠券
  3. 服务预约:预约服务时使用优惠券
  4. 续费优惠:会员续费时使用优惠券
  5. 特定活动:在特定活动中使用优惠券

优惠券数据分析

优惠券数据分析需要关注以下指标:

  1. 发放量:系统发放的优惠券数量
  2. 领取率:优惠券的领取比例
  3. 使用率:优惠券的使用比例
  4. 核销率:优惠券的核销比例
  5. 转化率:优惠券带来的转化效果
  6. ROI:优惠券的投资回报率
  7. 用户行为:用户在优惠券使用过程中的行为路径

实用案例分析

案例:实现完整的优惠券系统

功能需求

  1. 优惠券创建:管理员创建不同类型的优惠券
  2. 优惠券发放:系统向用户发放优惠券
  3. 优惠券领取:用户领取优惠券
  4. 优惠券使用:用户在购物时使用优惠券
  5. 优惠券核销:系统核销使用的优惠券
  6. 数据分析:分析优惠券的使用效果

实现步骤

  1. 设计优惠券数据结构:定义优惠券相关的数据模型
  2. 实现优惠券管理 API:开发优惠券相关的接口
  3. 构建优惠券中心页面:展示用户的优惠券
  4. 开发优惠券发放功能:实现各种优惠券发放方式
  5. 实现优惠券领取功能:开发用户领取优惠券的功能
  6. 开发优惠券使用功能:实现优惠券的使用和核销逻辑
  7. 集成数据分析功能:分析优惠券的使用效果

代码示例

优惠券数据结构设计

// 优惠券模板数据模型
const couponTemplateSchema = {
  id: String,           // 模板ID
  name: String,         // 优惠券名称
  type: String,         // 优惠券类型(满减、折扣、代金等)
  value: Number,        // 优惠金额或折扣比例
  minSpend: Number,     // 最低消费金额
  maxDiscount: Number,  // 最大优惠金额
  usageScope: String,   // 使用范围(全场、特定商品、特定品类)
  applicableItems: Array, // 适用商品或品类ID
  usageLimit: Number,   // 每人使用限制
  totalLimit: Number,   // 总发放数量
  issuedCount: Number,  // 已发放数量
  usedCount: Number,    // 已使用数量
  startTime: Date,      // 开始时间
  endTime: Date,        // 结束时间
  status: String,       // 状态(未开始、进行中、已结束)
description: String,  // 优惠券描述
  createdAt: Date,      // 创建时间
  updatedAt: Date       // 更新时间
};

// 用户优惠券数据模型
const userCouponSchema = {
  id: String,           // 优惠券ID
  userId: String,       // 用户ID
  templateId: String,   // 优惠券模板ID
  couponCode: String,   // 优惠券码
  value: Number,        // 优惠金额或折扣比例
  minSpend: Number,     // 最低消费金额
  maxDiscount: Number,  // 最大优惠金额
  usageScope: String,   // 使用范围
  applicableItems: Array, // 适用商品或品类ID
  status: String,       // 状态(未使用、已使用、已过期)
  startTime: Date,      // 开始时间
  endTime: Date,        // 结束时间
  obtainTime: Date,     // 领取时间
  usedTime: Date,       // 使用时间
  orderId: String,      // 使用的订单ID
  createdAt: Date,      // 创建时间
  updatedAt: Date       // 更新时间
};

// 优惠券发放记录数据模型
const couponIssueRecordSchema = {
  id: String,           // 记录ID
  templateId: String,   // 优惠券模板ID
  userId: String,       // 用户ID
  issueType: String,    // 发放类型(主动发放、活动领取、积分兑换等)
  issueTime: Date,      // 发放时间
  status: String,       // 状态(成功、失败)
  reason: String,       // 发放原因或失败原因
  createdAt: Date,      // 创建时间
  updatedAt: Date       // 更新时间
};

// 优惠券使用记录数据模型
const couponUseRecordSchema = {
  id: String,           // 记录ID
  couponId: String,     // 优惠券ID
  userId: String,       // 用户ID
  orderId: String,      // 订单ID
  useTime: Date,        // 使用时间
  originalAmount: Number, // 原始金额
  discountAmount: Number, // 优惠金额
  actualAmount: Number, // 实际支付金额
  status: String,       // 状态(成功、失败)
  reason: String,       // 使用原因或失败原因
  createdAt: Date,      // 创建时间
  updatedAt: Date       // 更新时间
};

优惠券管理API

// api/coupon.js
import request from './request';

export const couponApi = {
  // 获取用户优惠券列表
  getUserCoupons(params) {
    return request({
      url: '/api/coupon/user/list',
      method: 'GET',
      params
    });
  },
  
  // 领取优惠券
  receiveCoupon(templateId) {
    return request({
      url: '/api/coupon/receive',
      method: 'POST',
      data: { templateId }
    });
  },
  
  // 兑换优惠券
  exchangeCoupon(exchangeCode) {
    return request({
      url: '/api/coupon/exchange',
      method: 'POST',
      data: { exchangeCode }
    });
  },
  
  // 获取可用优惠券
  getAvailableCoupons(orderInfo) {
    return request({
      url: '/api/coupon/available',
      method: 'POST',
      data: { orderInfo }
    });
  },
  
  // 使用优惠券
  useCoupon(couponId, orderId) {
    return request({
      url: '/api/coupon/use',
      method: 'POST',
      data: { couponId, orderId }
    });
  },
  
  // 取消使用优惠券
  cancelUseCoupon(couponId, orderId) {
    return request({
      url: '/api/coupon/cancel',
      method: 'POST',
      data: { couponId, orderId }
    });
  },
  
  // 获取优惠券模板列表
  getCouponTemplates(params) {
    return request({
      url: '/api/coupon/template/list',
      method: 'GET',
      params
    });
  }
};

优惠券中心组件

<template>
  <view class="coupon-center">
    <!-- 优惠券分类标签 -->
    <view class="coupon-tabs">
      <view 
        v-for="tab in tabs" 
        :key="tab.key" 
        :class="['tab-item', { 'active': activeTab === tab.key }]"
        @click="switchTab(tab.key)"
      >
        <text class="tab-text">{{ tab.name }}</text>
      </view>
    </view>
    
    <!-- 可领取优惠券 -->
    <view v-if="activeTab === 'available'" class="available-coupons">
      <view class="section-header">
        <text class="section-title">可领取优惠券</text>
      </view>
      <view class="coupon-list">
        <view v-for="template in availableTemplates" :key="template.id" class="coupon-template">
          <view class="template-content">
            <view class="template-value">
              <text v-if="template.type === 'discount'" class="value-text">
                {{ template.value }}折
              </text>
              <text v-else class="value-text">
                ¥{{ template.value }}
              </text>
              <text v-if="template.minSpend" class="min-spend">
                满{{ template.minSpend }}可用
              </text>
            </view>
            <view class="template-info">
              <text class="template-name">{{ template.name }}</text>
              <text class="template-desc">{{ template.description }}</text>
              <text class="template-time">
                {{ formatTime(template.startTime) }}至{{ formatTime(template.endTime) }}
              </text>
              <text class="template-count">
                剩余{{ template.totalLimit - template.issuedCount }}张
              </text>
            </view>
            <button @click="receiveCoupon(template.id)" class="receive-btn">
              立即领取
            </button>
          </view>
        </view>
      </view>
    </view>
    
    <!-- 已领取优惠券 -->
    <view v-else class="my-coupons">
      <view class="section-header">
        <text class="section-title">我的优惠券</text>
      </view>
      <view class="coupon-list">
        <view v-for="coupon in userCoupons" :key="coupon.id" class="coupon-item" :class="coupon.status">
          <view class="coupon-content">
            <view class="coupon-value">
              <text v-if="coupon.type === 'discount'" class="value-text">
                {{ coupon.value }}折
              </text>
              <text v-else class="value-text">
                ¥{{ coupon.value }}
              </text>
              <text v-if="coupon.minSpend" class="min-spend">
                满{{ coupon.minSpend }}可用
              </text>
            </view>
            <view class="coupon-info">
              <text class="coupon-name">{{ coupon.name }}</text>
              <text class="coupon-desc">{{ coupon.description }}</text>
              <text class="coupon-time">
                {{ formatTime(coupon.startTime) }}至{{ formatTime(coupon.endTime) }}
              </text>
              <text class="coupon-scope">{{ getScopeText(coupon.usageScope) }}</text>
            </view>
            <view class="coupon-status">
              <text v-if="coupon.status === 'unused'" class="status-text">
                未使用
              </text>
              <text v-else-if="coupon.status === 'used'" class="status-text">
                已使用
              </text>
              <text v-else-if="coupon.status === 'expired'" class="status-text">
                已过期
              </text>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      activeTab: 'available',
      tabs: [
        { key: 'available', name: '可领取' },
        { key: 'unused', name: '未使用' },
        { key: 'used', name: '已使用' },
        { key: 'expired', name: '已过期' }
      ],
      availableTemplates: [],
      userCoupons: []
    };
  },
  mounted() {
    this.loadData();
  },
  methods: {
    async loadData() {
      try {
        // 获取可领取优惠券模板
        const templatesResponse = await this.$api.coupon.getCouponTemplates({ status: 'ongoing' });
        this.availableTemplates = templatesResponse.data;
        
        // 获取用户优惠券
        const couponsResponse = await this.$api.coupon.getUserCoupons({ status: this.activeTab });
        this.userCoupons = couponsResponse.data;
      } catch (error) {
        console.error('获取优惠券数据失败', error);
      }
    },
    
    switchTab(tabKey) {
      this.activeTab = tabKey;
      this.loadData();
    },
    
    async receiveCoupon(templateId) {
      try {
        await this.$api.coupon.receiveCoupon(templateId);
        uni.showToast({ title: '领取成功', icon: 'success' });
        // 刷新数据
        this.loadData();
      } catch (error) {
        uni.showToast({ title: error.message || '领取失败', icon: 'none' });
      }
    },
    
    formatTime(time) {
      const date = new Date(time);
      return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
    },
    
    getScopeText(scope) {
      const scopeMap = {
        all: '全场通用',
        category: '指定品类',
        product: '指定商品'
      };
      return scopeMap[scope] || '未知范围';
    }
  }
};
</script>

<style scoped>
.coupon-center {
  background-color: #f5f5f5;
  min-height: 100vh;
}

.coupon-tabs {
  display: flex;
  background-color: #fff;
  padding: 10px 0;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: sticky;
  top: 0;
  z-index: 10;
}

.tab-item {
  flex: 1;
  text-align: center;
  padding: 10px 0;
}

.tab-item.active {
  border-bottom: 2px solid #1890ff;
}

.tab-text {
  font-size: 14px;
  color: #333;
}

.tab-item.active .tab-text {
  color: #1890ff;
  font-weight: bold;
}

.section-header {
  padding: 15px;
  background-color: #f5f5f5;
}

.section-title {
  font-size: 16px;
  font-weight: bold;
  color: #333;
}

.coupon-list {
  padding: 10px;
}

.coupon-template {
  background-color: #fff;
  border-radius: 8px;
  margin-bottom: 15px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.template-content {
  display: flex;
  padding: 15px;
  align-items: center;
}

.template-value {
  flex: 0 0 100px;
  text-align: center;
  border-right: 1px dashed #e0e0e0;
  padding-right: 15px;
}

.value-text {
  font-size: 24px;
  font-weight: bold;
  color: #ff4d4f;
  display: block;
  margin-bottom: 5px;
}

.min-spend {
  font-size: 12px;
  color: #999;
  display: block;
}

.template-info {
  flex: 1;
  padding: 0 15px;
}

.template-name {
  font-size: 14px;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 5px;
}

.template-desc {
  font-size: 12px;
  color: #666;
  display: block;
  margin-bottom: 5px;
}

.template-time {
  font-size: 12px;
  color: #999;
  display: block;
  margin-bottom: 5px;
}

.template-count {
  font-size: 12px;
  color: #999;
  display: block;
}

.receive-btn {
  background-color: #1890ff;
  color: #fff;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 14px;
}

.coupon-item {
  background-color: #fff;
  border-radius: 8px;
  margin-bottom: 15px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  position: relative;
}

.coupon-item.used,
.coupon-item.expired {
  opacity: 0.6;
}

.coupon-item.used::after,
.coupon-item.expired::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(45deg, transparent 49%, #999 49%, #999 51%, transparent 51%);
  background-size: 10px 10px;
  pointer-events: none;
  border-radius: 8px;
}

.coupon-content {
  display: flex;
  padding: 15px;
  align-items: center;
}

.coupon-value {
  flex: 0 0 100px;
  text-align: center;
  border-right: 1px dashed #e0e0e0;
  padding-right: 15px;
}

.coupon-info {
  flex: 1;
  padding: 0 15px;
}

.coupon-name {
  font-size: 14px;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 5px;
}

.coupon-desc {
  font-size: 12px;
  color: #666;
  display: block;
  margin-bottom: 5px;
}

.coupon-time {
  font-size: 12px;
  color: #999;
  display: block;
  margin-bottom: 5px;
}

.coupon-scope {
  font-size: 12px;
  color: #999;
  display: block;
}

.coupon-status {
  flex: 0 0 80px;
  text-align: center;
}

.status-text {
  font-size: 14px;
  color: #999;
  display: block;
}

.coupon-item.unused .status-text {
  color: #1890ff;
}

.coupon-item.used .status-text {
  color: #999;
}

.coupon-item.expired .status-text {
  color: #999;
}
</style>

优惠券规则引擎

// 优惠券规则引擎
class CouponRuleEngine {
  // 初始化规则
  constructor() {
    this.rules = {
      // 使用条件规则
      usageConditions: {
        minSpend: this.checkMinSpend,
        usageScope: this.checkUsageScope,
        validity: this.checkValidity,
        usageLimit: this.checkUsageLimit
      },
      // 发放规则
      issueConditions: {
        totalLimit: this.checkTotalLimit,
        userLimit: this.checkUserLimit,
        eligibility: this.checkEligibility
      }
    };
  }
  
  // 检查优惠券使用条件
  checkUsageConditions(coupon, orderInfo) {
    const conditions = [];
    
    // 检查最低消费
    if (coupon.minSpend) {
      conditions.push(this.rules.usageConditions.minSpend(coupon, orderInfo));
    }
    
    // 检查使用范围
    conditions.push(this.rules.usageConditions.usageScope(coupon, orderInfo));
    
    // 检查有效期
    conditions.push(this.rules.usageConditions.validity(coupon));
    
    // 检查使用限制
    conditions.push(this.rules.usageConditions.usageLimit(coupon, orderInfo));
    
    const result = conditions.every(condition => condition.allowed);
    const reason = conditions.find(condition => !condition.allowed)?.reason;
    
    return { allowed: result, reason };
  }
  
  // 检查最低消费
  checkMinSpend(coupon, orderInfo) {
    const orderAmount = orderInfo.totalAmount || 0;
    if (orderAmount < coupon.minSpend) {
      return { allowed: false, reason: `订单金额未达到最低消费${coupon.minSpend}元` };
    }
    return { allowed: true };
  }
  
  // 检查使用范围
  checkUsageScope(coupon, orderInfo) {
    if (coupon.usageScope === 'all') {
      return { allowed: true };
    }
    
    if (coupon.usageScope === 'category' && coupon.applicableItems) {
      const orderCategories = orderInfo.items.map(item => item.categoryId);
      const hasApplicableItem = coupon.applicableItems.some(categoryId => 
        orderCategories.includes(categoryId)
      );
      if (!hasApplicableItem) {
        return { allowed: false, reason: '订单商品不在优惠券使用范围内' };
      }
    }
    
    if (coupon.usageScope === 'product' && coupon.applicableItems) {
      const orderProductIds = orderInfo.items.map(item => item.productId);
      const hasApplicableItem = coupon.applicableItems.some(productId => 
        orderProductIds.includes(productId)
      );
      if (!hasApplicableItem) {
        return { allowed: false, reason: '订单商品不在优惠券使用范围内' };
      }
    }
    
    return { allowed: true };
  }
  
  // 检查有效期
  checkValidity(coupon) {
    const now = new Date();
    if (now < new Date(coupon.startTime)) {
      return { allowed: false, reason: '优惠券尚未开始使用' };
    }
    if (now > new Date(coupon.endTime)) {
      return { allowed: false, reason: '优惠券已过期' };
    }
    return { allowed: true };
  }
  
  // 检查使用限制
  checkUsageLimit(coupon, orderInfo) {
    // 实际项目中从数据库获取用户使用次数
    const usageCount = 0;
    if (coupon.usageLimit && usageCount >= coupon.usageLimit) {
      return { allowed: false, reason: '优惠券使用次数已达上限' };
    }
    return { allowed: true };
  }
  
  // 检查优惠券发放条件
  checkIssueConditions(template, user) {
    const conditions = [];
    
    // 检查总发放数量
    conditions.push(this.rules.issueConditions.totalLimit(template));
    
    // 检查用户领取限制
    conditions.push(this.rules.issueConditions.userLimit(template, user));
    
    // 检查用户资格
    conditions.push(this.rules.issueConditions.eligibility(template, user));
    
    const result = conditions.every(condition => condition.allowed);
    const reason = conditions.find(condition => !condition.allowed)?.reason;
    
    return { allowed: result, reason };
  }
  
  // 检查总发放数量
  checkTotalLimit(template) {
    if (template.totalLimit && template.issuedCount >= template.totalLimit) {
      return { allowed: false, reason: '优惠券已发放完毕' };
    }
    return { allowed: true };
  }
  
  // 检查用户领取限制
  checkUserLimit(template, user) {
    // 实际项目中从数据库获取用户领取次数
    const userIssueCount = 0;
    if (template.userLimit && userIssueCount >= template.userLimit) {
      return { allowed: false, reason: '您已达到领取上限' };
    }
    return { allowed: true };
  }
  
  // 检查用户资格
  checkEligibility(template, user) {
    // 检查用户等级
    if (template.minUserLevel && user.level < template.minUserLevel) {
      return { allowed: false, reason: '等级不足,无法领取' };
    }
    
    // 检查用户标签
    if (template.targetTags && template.targetTags.length > 0) {
      const userTags = user.tags || [];
      const hasTag = template.targetTags.some(tag => userTags.includes(tag));
      if (!hasTag) {
        return { allowed: false, reason: '不符合领取条件' };
      }
    }
    
    return { allowed: true };
  }
  
  // 计算优惠金额
  calculateDiscount(coupon, orderInfo) {
    const orderAmount = orderInfo.totalAmount || 0;
    let discountAmount = 0;
    
    switch (coupon.type) {
      case '满减':
        discountAmount = coupon.value;
        break;
      case '折扣':
        discountAmount = orderAmount * (1 - coupon.value / 10);
        break;
      case '代金':
        discountAmount = coupon.value;
        break;
      default:
        discountAmount = 0;
    }
    
    // 检查最大优惠金额
    if (coupon.maxDiscount && discountAmount > coupon.maxDiscount) {
      discountAmount = coupon.maxDiscount;
    }
    
    // 优惠金额不能超过订单金额
    discountAmount = Math.min(discountAmount, orderAmount);
    
    return discountAmount;
  }
  
  // 检查优惠券是否可以叠加
  checkStackable(coupon1, coupon2) {
    // 这里可以实现具体的叠加规则
    // 例如:代金券可以与折扣券叠加,但不能与其他代金券叠加
    if (coupon1.type === coupon2.type && coupon1.type === '代金') {
      return false;
    }
    return true;
  }
}

export default new CouponRuleEngine();

优惠券管理

// 优惠券管理
class CouponManager {
  // 初始化
  constructor(ruleEngine) {
    this.ruleEngine = ruleEngine;
  }
  
  // 创建优惠券模板
  async createCouponTemplate(templateData) {
    try {
      // 验证模板数据
      this.validateTemplateData(templateData);
      
      // 保存模板数据
      const template = await this.saveCouponTemplate(templateData);
      
      return { success: true, template };
    } catch (error) {
      console.error('创建优惠券模板失败', error);
      throw error;
    }
  }
  
  // 验证模板数据
  validateTemplateData(templateData) {
    if (!templateData.name) {
      throw new Error('优惠券名称不能为空');
    }
    
    if (!templateData.type) {
      throw new Error('优惠券类型不能为空');
    }
    
    if (templateData.value === undefined || templateData.value <= 0) {
      throw new Error('优惠券价值必须大于0');
    }
    
    if (!templateData.startTime || !templateData.endTime) {
      throw new Error('优惠券有效期不能为空');
    }
    
    if (new Date(templateData.endTime) < new Date(templateData.startTime)) {
      throw new Error('优惠券结束时间不能早于开始时间');
    }
    
    if (templateData.totalLimit && templateData.totalLimit <= 0) {
      throw new Error('优惠券发放数量必须大于0');
    }
  }
  
  // 保存优惠券模板
  async saveCouponTemplate(templateData) {
    // 实际项目中保存到数据库
    console.log('保存优惠券模板', templateData);
    return { id: '1', ...templateData };
  }
  
  // 发放优惠券
  async issueCoupon(templateId, userId, issueType = '主动发放') {
    try {
      // 获取优惠券模板
      const template = await this.getCouponTemplate(templateId);
      if (!template) {
        throw new Error('优惠券模板不存在');
      }
      
      // 检查发放条件
      const user = await this.getUserInfo(userId);
      const issueCheck = this.ruleEngine.checkIssueConditions(template, user);
      if (!issueCheck.allowed) {
        throw new Error(issueCheck.reason);
      }
      
      // 创建用户优惠券
      const coupon = await this.createUserCoupon(template, userId);
      
      // 更新模板发放数量
      await this.updateTemplateIssuedCount(templateId);
      
      // 记录发放记录
      await this.recordCouponIssue(templateId, userId, issueType);
      
      return { success: true, coupon };
    } catch (error) {
      console.error('发放优惠券失败', error);
      throw error;
    }
  }
  
  // 获取优惠券模板
  async getCouponTemplate(templateId) {
    // 实际项目中从数据库获取
    return {
      id: templateId,
      name: '测试优惠券',
      type: '满减',
      value: 10,
      minSpend: 100,
      totalLimit: 100,
      issuedCount: 0,
      startTime: new Date(),
      endTime: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
    };
  }
  
  // 获取用户信息
  async getUserInfo(userId) {
    // 实际项目中从数据库获取
    return {
      id: userId,
      level: 1,
      tags: []
    };
  }
  
  // 创建用户优惠券
  async createUserCoupon(template, userId) {
    // 生成优惠券码
    const couponCode = this.generateCouponCode();
    
    const couponData = {
      userId,
      templateId: template.id,
      couponCode,
      value: template.value,
      minSpend: template.minSpend,
      maxDiscount: template.maxDiscount,
      usageScope: template.usageScope,
      applicableItems: template.applicableItems,
      status: 'unused',
      startTime: template.startTime,
      endTime: template.endTime,
      obtainTime: new Date(),
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    // 实际项目中保存到数据库
    console.log('创建用户优惠券', couponData);
    return { id: '1', ...couponData };
  }
  
  // 更新模板发放数量
  async updateTemplateIssuedCount(templateId) {
    // 实际项目中更新数据库
    console.log('更新模板发放数量', templateId);
  }
  
  // 记录发放记录
  async recordCouponIssue(templateId, userId, issueType) {
    // 实际项目中保存到数据库
    console.log('记录发放记录', templateId, userId, issueType);
  }
  
  // 生成优惠券码
  generateCouponCode() {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let code = '';
    for (let i = 0; i < 12; i++) {
      code += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return code;
  }
  
  // 用户领取优惠券
  async receiveCoupon(templateId, userId) {
    return this.issueCoupon(templateId, userId, '用户领取');
  }
  
  // 使用优惠券
  async useCoupon(couponId, orderId, orderInfo) {
    try {
      // 获取优惠券
      const coupon = await this.getUserCoupon(couponId, orderId);
      if (!coupon) {
        throw new Error('优惠券不存在');
      }
      
      if (coupon.status !== 'unused') {
        throw new Error('优惠券已使用或过期');
      }
      
      // 检查使用条件
      const usageCheck = this.ruleEngine.checkUsageConditions(coupon, orderInfo);
      if (!usageCheck.allowed) {
        throw new Error(usageCheck.reason);
      }
      
      // 计算优惠金额
      const discountAmount = this.ruleEngine.calculateDiscount(coupon, orderInfo);
      
      // 更新优惠券状态
      await this.updateCouponStatus(couponId, 'used', { orderId, usedTime: new Date() });
      
      // 记录使用记录
      await this.recordCouponUse(couponId, orderId, orderInfo, discountAmount);
      
      // 更新模板使用数量
      await this.updateTemplateUsedCount(coupon.templateId);
      
      return { success: true, discountAmount };
    } catch (error) {
      console.error('使用优惠券失败', error);
      throw error;
    }
  }
  
  // 获取用户优惠券
  async getUserCoupon(couponId, userId) {
    // 实际项目中从数据库获取
    return {
      id: couponId,
      userId,
      templateId: '1',
      status: 'unused',
      minSpend: 100,
      value: 10,
      type: '满减',
      startTime: new Date(),
      endTime: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
    };
  }
  
  // 更新优惠券状态
  async updateCouponStatus(couponId, status, updateData = {}) {
    // 实际项目中更新数据库
    console.log('更新优惠券状态', couponId, status, updateData);
  }
  
  // 记录使用记录
  async recordCouponUse(couponId, orderId, orderInfo, discountAmount) {
    // 实际项目中保存到数据库
    console.log('记录使用记录', couponId, orderId, orderInfo, discountAmount);
  }
  
  // 更新模板使用数量
  async updateTemplateUsedCount(templateId) {
    // 实际项目中更新数据库
    console.log('更新模板使用数量', templateId);
  }
  
  // 取消使用优惠券
  async cancelUseCoupon(couponId, orderId) {
    try {
      // 获取优惠券
      const coupon = await this.getUserCoupon(couponId, orderId);
      if (!coupon) {
        throw new Error('优惠券不存在');
      }
      
      if (coupon.status !== 'used' || coupon.orderId !== orderId) {
        throw new Error('优惠券未使用或不属于该订单');
      }
      
      // 更新优惠券状态
      await this.updateCouponStatus(couponId, 'unused', { orderId: null, usedTime: null });
      
      // 更新模板使用数量
      await this.decrementTemplateUsedCount(coupon.templateId);
      
      return { success: true };
    } catch (error) {
      console.error('取消使用优惠券失败', error);
      throw error;
    }
  }
  
  // 减少模板使用数量
  async decrementTemplateUsedCount(templateId) {
    // 实际项目中更新数据库
    console.log('减少模板使用数量', templateId);
  }
  
  // 处理优惠券过期
  async processCouponExpiration() {
    try {
      // 获取过期的优惠券
      const expiredCoupons = await this.getExpiredCoupons();
      
      for (const coupon of expiredCoupons) {
        // 更新优惠券状态
        await this.updateCouponStatus(coupon.id, 'expired');
      }
    } catch (error) {
      console.error('处理优惠券过期失败', error);
    }
  }
  
  // 获取过期的优惠券
  async getExpiredCoupons() {
    // 实际项目中从数据库获取
    return [];
  }
  
  // 获取用户可用优惠券
  async getUserAvailableCoupons(userId, orderInfo) {
    try {
      // 获取用户未使用的优惠券
      const unusedCoupons = await this.getUserUnusedCoupons(userId);
      
      // 过滤可用的优惠券
      const availableCoupons = [];
      for (const coupon of unusedCoupons) {
        const usageCheck = this.ruleEngine.checkUsageConditions(coupon, orderInfo);
        if (usageCheck.allowed) {
          const discountAmount = this.ruleEngine.calculateDiscount(coupon, orderInfo);
          availableCoupons.push({ ...coupon, discountAmount });
        }
      }
      
      // 按照优惠金额排序
      availableCoupons.sort((a, b) => b.discountAmount - a.discountAmount);
      
      return availableCoupons;
    } catch (error) {
      console.error('获取用户可用优惠券失败', error);
      return [];
    }
  }
  
  // 获取用户未使用的优惠券
  async getUserUnusedCoupons(userId) {
    // 实际项目中从数据库获取
    return [];
  }
}

export default new CouponManager();

实现技巧与注意事项

跨端适配

  1. 数据同步:使用 uni-app 的本地存储和云存储结合,确保不同平台的数据同步
  2. API 适配:对于平台特定的 API,使用条件编译进行适配
  3. UI 适配:根据不同平台的屏幕尺寸和交互习惯,调整优惠券中心的 UI 设计
  4. 性能优化:考虑不同平台的性能差异,优化优惠券数据的加载和渲染

数据安全

  1. 敏感数据加密:对优惠券的敏感信息进行加密存储和传输
  2. 防作弊机制:实现防作弊机制,防止用户刷优惠券
  3. 数据验证:在服务端验证所有优惠券操作,防止客户端篡改
  4. 日志记录:记录详细的优惠券操作日志,便于排查问题

性能优化

  1. 数据缓存:缓存优惠券数据,减少重复请求
  2. 批量处理:对优惠券过期等操作使用批量处理,减少数据库压力
  3. 异步处理:对非关键优惠券操作使用异步处理,提升响应速度
  4. 索引优化:为优惠券相关的数据库表添加适当的索引,提升查询性能

可扩展性

  1. 模块化设计:将优惠券系统拆分为多个模块,便于维护和扩展
  2. 插件化架构:采用插件化架构,支持不同类型的优惠券插件
  3. 配置化管理:使用配置文件管理优惠券规则,便于调整
  4. API 标准化:设计标准化的 API 接口,便于与其他系统集成

常见问题与解决方案

问题:优惠券领取失败

解决方案

  1. 检查优惠券发放数量是否已达上限
  2. 检查用户领取次数是否已达上限
  3. 检查用户是否符合领取条件
  4. 检查网络连接是否正常
  5. 记录详细的错误日志,便于排查问题

问题:优惠券使用失败

解决方案

  1. 检查优惠券是否在有效期内
  2. 检查订单金额是否达到最低消费要求
  3. 检查订单商品是否在优惠券使用范围内
  4. 检查优惠券是否已被使用或过期
  5. 检查优惠券使用次数是否已达上限

问题:优惠券系统性能问题

解决方案

  1. 优化数据库查询,使用适当的索引
  2. 实现优惠券数据的缓存机制
  3. 对优惠券操作进行批量处理
  4. 考虑使用分布式架构,提升系统处理能力

问题:用户恶意刷优惠券

解决方案

  1. 实现防作弊机制,如 IP 限制、设备限制
  2. 对高频优惠券领取行为进行监控和限制
  3. 建立优惠券异常检测系统,识别和处理异常领取行为
  4. 制定明确的优惠券规则和惩罚机制,对作弊用户进行处理

总结

本教程详细介绍了 uni-app 优惠券系统的核心知识点和实现方法,包括优惠券系统架构设计、优惠券类型设计、优惠券规则设计、优惠券发放方式、优惠券使用场景等内容。通过实用案例和代码示例,展示了如何在 uni-app 中实现完整的优惠券功能。

优惠券系统是提升用户转化率和销售额的重要工具,合理设计和实现优惠券系统可以有效促进应用的增长。在实际开发中,需要根据应用的具体需求和用户群体,设计适合的优惠券类型和规则,同时注重系统的性能、安全性和可扩展性。

通过本教程的学习,开发者应该能够:

  1. 理解优惠券系统的基本架构和核心组件
  2. 掌握优惠券规则的设计和实现方法
  3. 实现各种优惠券发放和使用场景
  4. 解决优惠券系统开发中遇到的常见问题
  5. 优化优惠券系统的性能和安全性

希望本教程对开发者在 uni-app 中实现优惠券系统有所帮助,祝大家开发顺利!

« 上一篇 uni-app 积分系统 下一篇 » uni-app 评价系统