更新记录

v0.0.1(2025-11-28) 下载此版本

  1. vue3 ts重构,全屏适配
  2. 增加锤子移动和砸击动画
  3. 允许控制单次和多次砸金蛋

平台兼容性

uni-app(3.6.15)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - -

这是一个基于 Vue 3 + TypeScript 的砸金蛋抽奖组件,重构自 egg-frenzy,在原有的抽奖组件上进行了一些改进。

使用示例

<template>
  <view class="parent-container">
    <egg-frenzy-vue3 :drawList="drawList" @showPrize="showPrizeModal" />

    <!-- 优化后的弹窗 -->
    <view v-if="isPrizeModalVisible" class="prize-modal">
      <view class="modal-overlay" @click="closePrizeModal"></view>

      <view class="prize-dialog">
        <!-- 关闭按钮 -->
        <view class="close-icon" @click="closePrizeModal">
          <text class="close-text">✕</text>
        </view>

        <!-- 装饰元素 -->
        <view class="decoration-stars">
          <text class="star" v-for="i in 6" :key="i">✨</text>
        </view>

        <!-- 头部 -->
        <view class="dialog-header">
          <text class="congrats-text">🎊 恭喜中奖 🎊</text>
        </view>

        <!-- 内容区 -->
        <view class="dialog-content">
          <view class="image-wrapper">
            <image :src="selectedPrize?.prizeImage" class="prize-image" mode="aspectFit" />
            <view class="image-glow"></view>
          </view>

          <text class="prize-label">获得奖励</text>
          <text class="prize-name">{{ selectedPrize?.prizeName }}</text>

          <!-- 操作按钮 -->
          <button @click="closePrizeModal" class="confirm-btn">
            <text class="btn-text">开心收下</text>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref } from "vue";

import EggFrenzyVue3 from "@/components/egg-frenzy-vue3/egg-frenzy-vue3.vue";

const drawList = ref([
  {
    sort: 1,
    prizeName: "一等奖",
    prizeImage: "https://example.com/prize1.png ",
    isOpen: false,
  },
  {
    sort: 2,
    prizeName: "二等奖",
    prizeImage: "https://example.com/prize2.png ",
    isOpen: false,
  },
  {
    sort: 3,
    prizeName: "三等奖",
    prizeImage: "https://example.com/prize3.png ",
    isOpen: false,
  },
  {
    sort: 4,
    prizeName: "谢谢参与",
    prizeImage: "https://example.com/prize4.png ",
    isOpen: false,
  },
  {
    sort: 5,
    prizeName: "特等奖",
    prizeImage: "https://example.com/prize5.png ",
    isOpen: false,
  },
  {
    sort: 6,
    prizeName: "幸运奖",
    prizeImage: "https://example.com/prize6.png ",
    isOpen: false,
  },
]);

const isPrizeModalVisible = ref(false);
const selectedPrize = ref(null);

const showPrizeModal = (prize) => {
  selectedPrize.value = prize;
  isPrizeModalVisible.value = true;
};

const closePrizeModal = () => {
  isPrizeModalVisible.value = false;
  selectedPrize.value = null;
};
</script>

<style scoped>
.parent-container {
  height: 100vh;
  display: flex;
  justify-content: center;
  position: relative;
}

/* 弹窗背景遮罩 */
.prize-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
  animation: fadeIn 0.3s ease;
}

.prize-dialog {
  position: relative;
  width: 85%;
  max-width: 420px;
  background: linear-gradient(135deg, #ff4757 0%, #ff3838 100%);
  border-radius: 24px;
  overflow: hidden;
  box-shadow: 0 20px 40px rgba(255, 71, 87, 0.3);
  animation: bounceIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.close-icon {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 25px;
  height: 25px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
  cursor: pointer;
  transition: all 0.3s ease;
}

.close-text {
  font-size: 14px;
  color: #ff4757;
  font-weight: bold;
  line-height: 1;
}

.close-icon:active {
  transform: scale(0.95);
  background: #fff;
}

.decoration-stars {
  position: absolute;
  top: -20px;
  left: 0;
  right: 0;
  height: 60px;
  display: flex;
  justify-content: space-around;
  pointer-events: none;
}

.star {
  font-size: 24px;
  color: #fff;
  opacity: 0.8;
  animation: twinkle 2s infinite;
}

.star:nth-child(2n) {
  animation-delay: 0.5s;
}

.star:nth-child(3n) {
  animation-delay: 1s;
}

.dialog-header {
  padding: 30px 20px 20px;
  text-align: center;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, transparent 100%);
}

.congrats-text {
  font-size: 28px;
  font-weight: bold;
  color: #fff;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  letter-spacing: 2px;
}

.dialog-content {
  padding: 0 30px 30px;
  text-align: center;
}

.image-wrapper {
  position: relative;
  width: 160px;
  height: 160px;
  margin: 0 auto 20px;
}

.prize-image {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  border: 6px solid #fff;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
  position: relative;
  z-index: 2;
  background: #fff;
}

.image-glow {
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: radial-gradient(circle, rgba(255, 215, 0, 0.4) 0%, transparent 70%);
  border-radius: 50%;
  animation: pulse 2s infinite;
}

.prize-label {
  display: block;
  font-size: 16px;
  color: rgba(255, 255, 255, 0.9);
  margin-bottom: 8px;
  letter-spacing: 1px;
}

.prize-name {
  display: block;
  font-size: 32px;
  font-weight: bold;
  color: #fff;
  margin-bottom: 30px;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.confirm-btn {
  width: 100%;
  padding: 0 16px;
  background: linear-gradient(135deg, #ffd700 0%, #ffc400 100%);
  border: none;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(255, 215, 0, 0.3);
  transition: all 0.3s ease;
}

.confirm-btn:active {
  transform: scale(0.98);
  box-shadow: 0 2px 6px rgba(255, 215, 0, 0.3);
}

.btn-text {
  font-size: 18px;
  font-weight: bold;
  color: #d63031;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes bounceIn {
  0% {
    transform: scale(0.3);
    opacity: 0;
  }
  50% {
    transform: scale(1.05);
  }
  70% {
    transform: scale(0.95);
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes twinkle {
  0%,
  100% {
    opacity: 0.8;
    transform: scale(1);
  }
  50% {
    opacity: 1;
    transform: scale(1.2);
  }
}

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 0.6;
  }
  50% {
    transform: scale(1.1);
    opacity: 0.8;
  }
  100% {
    transform: scale(1);
    opacity: 0.6;
  }
}
</style>

Props 属性说明

属性名 类型 默认值 必填 说明
drawList Array<PrizeItem> - 奖品数据列表,最多 6 个
singleMode boolean true 是否单抽模式(true 只砸一次,false 可砸多次)

数据格式

interface PrizeItem {
  sort: number;          // 金蛋排序号(1-6)
  prizeName: string;     // 奖品名称
  prizeImage: string;    // 奖品图片 URL
  isOpen?: boolean;      // 是否已打开
}

示例数据:

const drawList = [
  {
    sort: 1,
    prizeName: "一等奖",
    prizeImage: "https://example.com/prize1.png",
    isOpen: false
  },
  // ...最多 6 个
]

Events 事件

事件名 回调参数 触发时机 说明
showPrize (item: PrizeItem, index: number) 金蛋打开后 返回打开的奖品数据和索引,用于展示奖品弹窗

附加说明

  • 图片预加载:组件会自动预加载金蛋、锤子图片,确保网络畅通
  • 单抽模式限制:singleMode=true 时,砸开一个金蛋后其他金蛋会变暗且不可点击

常见问题

Q1: 锤子不循环高亮? A: 检查 drawList 中 isOpen 是否全为 true,或 remainingCount 为 0

Q2: 如何控制抽奖次数? A: 当前逻辑内置 remainingCount=6,可在源码中调整

Q3: 单抽模式下如何重置? A: 修改 drawList 中所有项的 isOpen 为 false,并重新渲染组件

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。