Skip to content

Coupon クーポン - Vant 4

🎫 Coupon クーポンセレクター

🎁 紹介

誰がクーポンを嫌いでしょうか?Coupon クーポンコンポーネントは親切な買い物アシスタントのように、アプリケーションに完全なクーポンエコシステムをもたらします!🛍️

クーポンの華やかな表示から便利な交換機能、そして賢い選択メカニズムまで、このコンポーネントはクーポンの執事のように、ユーザーが割引を楽しみながら、満足感あふれる体験を提供します。クーポンを使うたびに、小さなサプライズボックスを開いているような感じです!✨

📦 インポート

以下の方法でコンポーネントをグローバルに登録します。詳細な登録方法についてはコンポーネントの登録を参照してください。

js
import { createApp } from'vue'; 
import { CouponCell, CouponList } from'vant'; 
const app = createApp(); 
app.use(CouponCell); 
app.use(CouponList);

🎯 コードデモ

🔧 基本的な使い方 - クーポン旅を始めよう

驚きに満ちた宝箱を開けるように!シンプルな設定で、ユーザーに機能が完全で体験が流れるようなクーポンシステムを提示できます。選択から交換まで、すべてのステップが買い物の楽しさに満ちています。

html
js
import { ref } from'vue'; exportdefault { setup() { const coupon = { available: 1, condition: '条件なし\n最大12元割引', reason: '', value: 150, name: 'クーポン名', startAt: 1489104000, endAt: 1514592000, valueDesc: '1.5', unitDesc: '元', }; const coupons = ref([coupon]); const showList = ref(false); const chosenCoupon = ref(-1); constonChange = (index) => { showList.value = false; chosenCoupon.value = index; }; constonExchange = (code) => { coupons.value.push(coupon); }; return { coupons, showList, onChange, onExchange, chosenCoupon, disabledCoupons: [coupon], }; }, };

📖 API

CouponCell プロパティ

パラメータ説明タイプデフォルト値
titleセルのタイトルstringクーポン
chosen-coupon現在選択されているクーポンのインデックス*numbernumber[]*
coupons利用可能なクーポンリストCoupon[][]
editableクーポンの切り替えが可能かどうかbooleantrue
border内側の枠線を表示するかどうかbooleantrue
currency通貨記号string¥

CouponList プロパティ

パラメータ説明タイプデフォルト値
v-model:code現在入力されている交換コードstring-
chosen-coupon現在選択されているクーポンのインデックス、複数選択をサポート(タイプは []*numbernumber[]*
coupons利用可能なクーポンリストCouponInfo[][]
disabled-coupons利用できないクーポンリストCouponInfo[][]
enabled-title利用可能なクーポンリストのタイトルstring利用可能なクーポン
disabled-title利用できないクーポンリストのタイトルstring利用できないクーポン
exchange-button-text交換ボタンのテキストstring交換
exchange-button-loading交換ボタンの読み込みアニメーションを表示するかどうかbooleanfalse
exchange-button-disabled交換ボタンを無効にするかどうかbooleanfalse
exchange-min-length交換コードの最小長さnumber1
displayed-coupon-index特定のクーポン位置にスクロールnumber-
show-close-buttonリストの下部ボタンを表示するかどうかbooleantrue
close-button-textリストの下部ボタンのテキストstringクーポンを使用しない
input-placeholder入力ボックスのプレースホルダーテキストstringクーポンコードを入力してください
show-exchange-bar交換バーを表示するかどうかbooleantrue
currency通貨記号string¥
empty-imageリストが空の時のプレースホルダー画像string-
show-count利用可能/利用不可の数を表示するかどうかbooleantrue

CouponList イベント

イベント名説明コールバックパラメータ
changeクーポン切り替えコールバックindex, 選択されたクーポンのインデックス
exchangeクーポン交換コールバックcode, 交換コード

CouponList Slots

名称说明
list-footer优惠券列表底部
disabled-list-footer不可用优惠券列表底部
list-button自定义底部按钮

CouponInfo データ構造

キー名説明タイプ
idクーポンidstring
nameクーポン名string
condition条件string
startAtカードの有効開始時刻 (タイムスタンプ, 単位は秒)number
endAtカードの失効日 (タイムスタンプ, 単位は秒)number
description説明情報、クーポンが利用可能な時に表示string
reason利用不可の理由、クーポンが利用不可の時に表示string
value割引券の割引金額、単位は分number
valueDesc割引券の割引金額のテキストstring
unitDesc単位のテキストstring

タイプ定義

コンポーネントは以下のタイプ定義をエクスポートします:

ts
importtype { CouponCellProps, CouponListProps, CouponInfo } from'vant';

テーマカスタマイズ

スタイル変数

コンポーネントは以下の CSS 変数を提供しており、カスタムスタイルに使用できます。使用方法については ConfigProvider コンポーネント を参照してください。

名称デフォルト値説明
--van-coupon-margin0 var(--van-padding-sm) var(--van-padding-sm)-
--van-coupon-content-height84px-
--van-coupon-content-padding14px 0-
--van-coupon-content-text-colorvar(--van-text-color)-
--van-coupon-backgroundvar(--van-background-2)-
--van-coupon-active-backgroundvar(--van-active-color)-
--van-coupon-radiusvar(--van-radius-lg)-
--van-coupon-shadow0 0 4px rgba(0, 0, 0, 0.1)-
--van-coupon-head-width96px-
--van-coupon-amount-colorvar(--van-danger-color)-
--van-coupon-amount-font-size30px-
--van-coupon-currency-font-size40%-
--van-coupon-name-font-sizevar(--van-font-size-md)-
--van-coupon-disabled-text-colorvar(--van-text-color-2)-
--van-coupon-description-paddingvar(--van-padding-xs) var(--van-padding-md)-
--van-coupon-description-border-colorvar(--van-border-color)-
--van-coupon-checkbox-colorvar(--van-danger-color)-
--van-coupon-list-backgroundvar(--van-background)-
--van-coupon-list-field-padding5px 0 5px var(--van-padding-md)-
--van-coupon-list-exchange-button-height32px-
--van-coupon-list-close-button-height40px-
--van-coupon-list-empty-tip-colorvar(--van-text-color-2)-
--van-coupon-list-empty-tip-font-sizevar(--van-font-size-md)-
--van-coupon-list-empty-tip-line-heightvar(--van-line-height-md)-
--van-coupon-cell-selected-text-colorvar(--van-text-color)-

🌟 最佳实践

优惠券数据管理

javascript
// 优惠券状态管理
const useCouponStore = () => {
  const coupons = ref([]);
  const selectedCoupon = ref(-1);
  
  // 获取可用优惠券
  const getAvailableCoupons = async (orderAmount) => {
    const response = await fetch(`/api/coupons?amount=${orderAmount}`);
    const data = await response.json();
    
    return data.filter(coupon => {
      // 检查使用条件
      if (coupon.minAmount && orderAmount < coupon.minAmount) {
        return false;
      }
      
      // 检查有效期
      const now = Date.now() / 1000;
      return coupon.startAt <= now && coupon.endAt >= now;
    });
  };
  
  // 计算优惠金额
  const calculateDiscount = (coupon, orderAmount) => {
    if (coupon.type === 'percentage') {
      return Math.min(orderAmount * coupon.value / 100, coupon.maxDiscount || Infinity);
    }
    return Math.min(coupon.value / 100, orderAmount);
  };
  
  return {
    coupons,
    selectedCoupon,
    getAvailableCoupons,
    calculateDiscount
  };
};

用户体验优化

javascript
// 智能推荐最优优惠券
const recommendBestCoupon = (coupons, orderAmount) => {
  return coupons
    .map(coupon => ({
      ...coupon,
      discount: calculateDiscount(coupon, orderAmount)
    }))
    .sort((a, b) => b.discount - a.discount)[0];
};

// 优惠券到期提醒
const checkExpiringCoupons = (coupons) => {
  const threeDaysLater = Date.now() / 1000 + 3 * 24 * 60 * 60;
  
  return coupons.filter(coupon => 
    coupon.endAt <= threeDaysLater && coupon.endAt > Date.now() / 1000
  );
};

// 动画效果增强
const animateCouponSelection = (index) => {
  const couponElement = document.querySelector(`[data-coupon-index="${index}"]`);
  if (couponElement) {
    couponElement.classList.add('coupon-selected-animation');
    setTimeout(() => {
      couponElement.classList.remove('coupon-selected-animation');
    }, 300);
  }
};

💡 使用技巧

多种优惠券类型支持

javascript
// クーポンタイプ列挙
const CouponType = {
  FIXED: 'fixed',        // 固定金額
  PERCENTAGE: 'percentage', // パーセンテージ割引
  SHIPPING: 'shipping',     // 送料無料
  GIFT: 'gift'             // プレゼント
};

// クーポンファクトリ関数
const createCoupon = (type, config) => {
  const baseCoupon = {
    id: generateId(),
    name: config.name,
    startAt: config.startAt,
    endAt: config.endAt,
    condition: config.condition || '条件なし',
    type
  };
  
  switch (type) {
    case CouponType.FIXED:
      return {
        ...baseCoupon,
        value: config.amount * 100, // 转换为分
        valueDesc: config.amount.toString(),
        unitDesc: '元'
      };
      
    case CouponType.PERCENTAGE:
      return {
        ...baseCoupon,
        value: config.percentage,
        valueDesc: config.percentage.toString(),
        unitDesc: '折',
        maxDiscount: config.maxDiscount * 100
      };
      
    case CouponType.SHIPPING:
      return {
        ...baseCoupon,
        value: 0,
        valueDesc: '送料無料',
        unitDesc: ''
      };
      
    default:
      return baseCoupon;
  }
};

优惠券兑换码生成

javascript
// 交換コード生成器
const generateCouponCode = (length = 8) => {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  let result = '';
  
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  
  return result;
};

// 交換コード検証
const validateCouponCode = (code) => {
  const rules = [
    { test: /^[A-Z0-9]+$/, message: '大文字英字と数字のみを含む' },
    { test: code => code.length >= 6, message: '長さは最低6文字' },
    { test: code => code.length <= 12, message: '長さは12文字を超えない' }
  ];
  
  for (const rule of rules) {
    const isValid = typeof rule.test === 'function' 
      ? rule.test(code) 
      : rule.test.test(code);
      
    if (!isValid) {
      return { valid: false, message: rule.message };
    }
  }
  
  return { valid: true };
};

🔧 常见问题解决

优惠券叠加使用

javascript
// Q: 如何实现多张优惠券叠加使用?
// A: クーポンの組み合わせロジックを実装する

const applyCoupons = (coupons, orderAmount) => {
  // 優先順位でソート(固定金額 > パーセンテージ > 送料無料)
  const sortedCoupons = coupons.sort((a, b) => {
    const priority = { fixed: 3, percentage: 2, shipping: 1 };
    return priority[b.type] - priority[a.type];
  });
  
  let totalDiscount = 0;
  let currentAmount = orderAmount;
  
  for (const coupon of sortedCoupons) {
    const discount = calculateDiscount(coupon, currentAmount);
    totalDiscount += discount;
    currentAmount -= discount;
    
    // 過度の割引を防止
    if (currentAmount <= 0) {
      currentAmount = 0.01; // 最小金額を保持
      break;
    }
  }
  
  return {
    totalDiscount,
    finalAmount: Math.max(0.01, orderAmount - totalDiscount)
  };
};

クーポン状態の同期

javascript
// Q: クーポンの複数ページ間での状態同期をどのように処理しますか?
// A: グローバル状態管理を使用する

// Pinia store
export const useCouponStore = defineStore('coupon', {
  state: () => ({
    availableCoupons: [],
    selectedCoupons: [],
    lastSyncTime: 0
  }),
  
  actions: {
    async syncCoupons() {
      const now = Date.now();
      // 頻繁な同期を避ける
      if (now - this.lastSyncTime < 30000) return;
      
      try {
        const response = await api.getCoupons();
        this.availableCoupons = response.data;
        this.lastSyncTime = now;
      } catch (error) {
        console.error('クーポンの同期に失敗しました:', error);
      }
    },
    
    selectCoupon(coupon) {
      const index = this.selectedCoupons.findIndex(c => c.id === coupon.id);
      if (index > -1) {
        this.selectedCoupons.splice(index, 1);
      } else {
        this.selectedCoupons.push(coupon);
      }
    }
  }
});

クーポンの有効期限処理

javascript
// 期限切れクーポンの自動クリーンアップ
const cleanExpiredCoupons = () => {
  const now = Date.now() / 1000;
  
  coupons.value = coupons.value.filter(coupon => {
    if (coupon.endAt < now) {
      // 期限切れ通知を送信
      showNotify({
        type: 'warning',
        message: `クーポン「${coupon.name}」の有効期限が切れました`
      });
      return false;
    }
    return true;
  });
};

// 定期的なチェック
setInterval(cleanExpiredCoupons, 60000); // 1分ごとにチェック

🎨 设计灵感

创意优惠券样式

css
/* 撕边效果优惠券 */
.coupon-torn {
  position: relative;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 8px;
}

.coupon-torn::before {
  content: '';
  position: absolute;
  top: 50%;
  right: -5px;
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;
  transform: translateY(-50%);
  box-shadow: 
    0 -15px 0 white,
    0 15px 0 white,
    0 -30px 0 white,
    0 30px 0 white;
}

/* 金色VIP优惠券 */
.coupon-vip {
  background: linear-gradient(45deg, #ffd700, #ffed4e);
  border: 2px solid #ffa000;
  box-shadow: 0 4px 20px rgba(255, 215, 0, 0.3);
  position: relative;
  overflow: hidden;
}

.coupon-vip::after {
  content: '';
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background: linear-gradient(
    45deg,
    transparent,
    rgba(255, 255, 255, 0.3),
    transparent
  );
  animation: shine 3s infinite;
}

@keyframes shine {
  0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
  100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
}

/* 节日主题优惠券 */
.coupon-festival {
  background: 
    radial-gradient(circle at 20% 80%, #ff6b6b 0%, transparent 50%),
    radial-gradient(circle at 80% 20%, #4ecdc4 0%, transparent 50%),
    radial-gradient(circle at 40% 40%, #45b7d1 0%, transparent 50%);
  animation: festival-glow 2s ease-in-out infinite alternate;
}

@keyframes festival-glow {
  from { box-shadow: 0 0 20px rgba(255, 107, 107, 0.5); }
  to { box-shadow: 0 0 30px rgba(78, 205, 196, 0.5); }
}

交互动画效果

css
/* 优惠券选中动画 */
.coupon-selected-animation {
  animation: coupon-select 0.3s ease-out;
}

@keyframes coupon-select {
  0% { transform: scale(1); }
  50% { transform: scale(1.05); }
  100% { transform: scale(1); }
}

/* 优惠券翻转效果 */
.coupon-flip {
  transition: transform 0.6s;
  transform-style: preserve-3d;
}

.coupon-flip:hover {
  transform: rotateY(180deg);
}

.coupon-flip .front,
.coupon-flip .back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
}

.coupon-flip .back {
  transform: rotateY(180deg);
}

🚀 高级功能扩展

智能优惠券推荐

vue
<template>
  <div class="smart-coupon-recommender">
    <div class="recommendation-header">
      <h3>🎯 为您推荐</h3>
      <span class="save-amount">预计节省 ¥{{ recommendedSavings }}</span>
    </div>
    
    <div class="recommended-coupons">
      <div 
        v-for="coupon in recommendedCoupons" 
        :key="coupon.id"
        class="recommended-coupon"
        @click="selectRecommendedCoupon(coupon)"
      >
        <div class="coupon-badge">推荐</div>
        <van-coupon-cell
          :coupons="[coupon]"
          :chosen-coupon="0"
        />
        <div class="recommendation-reason">
          {{ coupon.recommendReason }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
const recommendCoupons = (coupons, orderInfo) => {
  return coupons
    .map(coupon => ({
      ...coupon,
      savings: calculateSavings(coupon, orderInfo),
      recommendReason: getRecommendReason(coupon, orderInfo)
    }))
    .filter(coupon => coupon.savings > 0)
    .sort((a, b) => b.savings - a.savings)
    .slice(0, 3);
};

const getRecommendReason = (coupon, orderInfo) => {
  if (coupon.type === 'shipping' && orderInfo.needsShipping) {
    return '送料無料、配送コストを節約します';
  }
  
  if (coupon.savings > orderInfo.amount * 0.1) {
    return '超値割引、10%以上節約';
  }
  
  return '現在の注文に適しており、すぐに利用可能';
};
</script>

📚 拡張読み物

技术文档

电商设计

関連コンポーネント

Vant に基づく企業向けモバイルソリューション