尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue3 + TypeScript 实战:从零封装一个可复用的九宫格抽奖组件

Vue3 + TypeScript 实战:从零封装一个可复用的九宫格抽奖组件 Vue3 TypeScript 实战构建高复用九宫格抽奖组件去年在开发一个电商促销活动时产品经理突然提出要在首页增加一个九宫格抽奖模块。当时第一反应是去找现成的轮子但试了几个开源组件后发现要么样式定制困难要么API设计不符合我们的业务逻辑。最终决定自己动手封装一个没想到这个组件后来被复用到公司三个不同业务线中。今天就来分享如何从零构建一个生产级可复用的九宫格抽奖组件。1. 组件设计与类型定义1.1 核心数据结构建模在TypeScript中良好的类型定义是组件健壮性的第一道防线。对于抽奖组件我们需要明确几个核心类型interface PrizeItem { id: string | number name: string icon?: string probability?: number // 中奖概率可选 isSpecial?: boolean // 是否是特殊奖项 } interface LotteryConfig { prizes: PrizeItem[] buttonText?: string roundsBeforeStop?: number // 停止前旋转圈数 animationDuration?: number // 动画时长(ms) highlightColor?: string // 高亮颜色 }这种设计将奖品数据与配置参数分离使得组件更容易适应不同业务场景。比如在春节活动中可以使用红包图标而在周年庆时改用奖杯图标。1.2 组件Props的工程化设计使用Vue3的defineProps配合TypeScript我们可以构建出既严格又灵活的props接口const props defineProps{ config: LotteryConfig disabled?: boolean // 事件回调 onStart?: () Promiseboolean // 返回是否允许开始 onEnd?: (prize: PrizeItem) Promisevoid }()特别设计的onStart回调返回Promise这使得我们可以方便地加入前置校验逻辑比如检查用户是否登录验证抽奖资格获取后端生成的随机种子2. 核心逻辑实现2.1 动画控制系统九宫格抽奖的核心在于平滑的动画效果和精确的停止控制。我们使用Composition API封装动画逻辑const useLotteryAnimation (config: LotteryConfig) { const currentActive refnumber(-1) const isRunning ref(false) const transitionSpeed ref(100) // 顺时针索引序列 const indexSequence [0, 1, 2, 5, 8, 7, 6, 3] const start async (targetIndex: number) { isRunning.value true let rounds 0 let currentStep 0 // 加速阶段 while (transitionSpeed.value 10) { transitionSpeed.value - 2 await nextTick() } // 匀速阶段 while (rounds (config.roundsBeforeStop || 3)) { currentActive.value indexSequence[currentStep % indexSequence.length] currentStep if (currentStep % indexSequence.length 0) rounds await sleep(transitionSpeed.value) } // 减速定位 // ...具体实现省略 } return { currentActive, isRunning, start } }2.2 状态管理与错误处理一个健壮的抽奖组件需要处理各种边界情况const drawingState reactive({ isDrawing: false, remainingTimes: 0, lastPrize: null as PrizeItem | null }) const startLottery async () { if (drawingState.isDrawing || props.disabled) return try { drawingState.isDrawing true const canStart await props.onStart?.() if (canStart false) { throw new Error(Not qualified) } const result await fetchPrizeFromBackend() await animation.start(result.index) await props.onEnd?.(result.prize) drawingState.lastPrize result.prize } catch (err) { console.error(抽奖出错:, err) // 显示友好错误提示 } finally { drawingState.isDrawing false } }3. 可视化与交互优化3.1 自适应布局方案九宫格布局看似简单但要适配不同设备需要精心设计.lottery-grid { display: grid; grid-template-columns: repeat(3, 1fr); aspect-ratio: 1/1; max-width: 100vw; .prize-item { position: relative; display: flex; flex-direction: column; justify-content: center; align-items: center; .active { background: v-bind(config.highlightColor || #ffeb3b); transform: scale(0.95); transition: all 0.2s; } } }使用CSS Grid结合aspect-ratio可以确保组件始终保持正方形而v-bind则允许动态样式配置。3.2 交互细节打磨按钮防抖防止快速重复点击动画中断处理页面隐藏时暂停动画移动端适配触摸反馈优化无障碍访问ARIA属性支持const handleClick useDebounce(() { if (!drawingState.isDrawing) { startLottery() } }, 300) onMounted(() { document.addEventListener(visibilitychange, () { if (document.hidden) { // 暂停动画逻辑 } }) })4. 业务集成实践4.1 与后端API的优雅对接在实际项目中抽奖逻辑通常需要与后端深度集成。我们设计了一个API适配层const useLotteryAPI () { const fetchPrize async (): Promise{ index: number prize: PrizeItem } { const res await axios.post(/api/lottery/draw, { activityId: props.activityId }) // 将API响应映射到组件需要的格式 return { index: convertPrizeToIndex(res.data.prize), prize: res.data.prize } } return { fetchPrize } }4.2 多场景配置方案通过预设配置对象可以快速适配不同业务场景const festivalConfig { prizes: [ { id: 1, name: 春节红包, icon: red-packet }, // ...其他奖品 ], buttonText: 立即抽奖, highlightColor: #f44336 } const anniversaryConfig { prizes: [ { id: 1, name: 特等奖, icon: trophy }, // ...其他奖品 ], buttonText: 点击抽奖, roundsBeforeStop: 4 }5. 高级功能扩展5.1 概率权重系统对于需要前端计算中奖概率的场景可以实现权重算法const calculateWinner (prizes: PrizeItem[]) { const totalWeight prizes.reduce((sum, p) sum (p.probability || 1), 0) let random Math.random() * totalWeight for (let i 0; i prizes.length; i) { if (random prizes[i].probability!) { return i } random - prizes[i].probability! } return 0 }5.2 动画效果增强通过CSS自定义属性和Transition组合可以创建更丰富的动画效果.prize-item { --highlight-scale: 0.95; .active { transform: scale(var(--highlight-scale)); box-shadow: 0 0 15px var(--highlight-color); animation: pulse 0.5s infinite alternate; } } keyframes pulse { from { opacity: 0.8; } to { opacity: 1; } }在最近一次使用中我们为这个组件添加了WebGL渲染的粒子特效当用户中大奖时会出现全屏烟花效果。这种渐进式增强的思路既保持了基础功能的可用性又能为高端设备提供更炫酷的体验。
返回列表