
我们团队自研的 3D 抽奖大屏系统已经服务过不少年会、发布会场景。前段时间在一次年会彩排中,收到了现场的反馈:主持人正在念一等奖得主名字,准备组织拍照,结果中间的中奖展示弹窗自动收起了——倒计时到了。
虽然左侧历史面板还能看到名单,但小小的列表显然没有中间大弹窗的展示效果。主持人只好指着左边继续念,气氛多少有点打折扣。
这正是自研产品的优势所在——我们可以快速响应真实场景的需求,持续打磨产品体验。
原有的两个痛点
1. 中间展示区自动收起,节奏不可控
系统设置了倒计时自动关闭中间弹窗,初衷是保持页面整洁。但现场节奏变化很大——主持人可能需要念名字、等人上台、组织合影,这个过程时长不固定。
结果虽然还在左侧,但大屏场景下,中间区域的展示效果才是氛围担当。
2. 历史记录缺少轮次维度
左侧历史面板可以看到中奖者和对应奖项,但没有"第几轮"的概念。当主持人问"刚才第二轮三等奖是谁"时,只能手动去数,而且无法将历史结果重新展示到中间区域。
改造目标
- 展示控制权交给操作者:中间弹窗不再自动消失,由人工决定何时关闭
- 历史记录支持轮次分组和回显:按"奖项 + 轮次"组织数据,点击可回显到中间展示区
实现方案
弹窗关闭逻辑调整
原来的流程:
抽奖结束 → 播放庆祝动画 → 倒计时 → 自动关闭弹窗
调整后,弹窗只在以下情况关闭:
- 手动点击关闭按钮(新增)
- 切换到其他奖项
- 开始下一轮抽奖
庆祝动画仍然会自动结束,但弹窗会保持展示状态。
代码层面,将分散的关闭逻辑收口到统一方法:
function closeWinnerDisplay() {
currentWinners.value = []
currentPrizeTitle.value = ''
clearCelebrationTimer()
}
各触发点复用这个方法,便于维护和后续扩展(比如快捷键支持)。
数据结构升级
原有记录结构:
{ name: '张三', prizeId: 'first_prize', prizeName: '一等奖' }
新增轮次字段:
{
name: '张三',
prizeId: 'first_prize',
prizeName: '一等奖',
round_index: 2,
round_key: 'first_prize::2'
}
同时维护 prizeRoundCounter 记录各奖项当前轮次,activeRoundKey 追踪当前展示的轮次用于高亮。
历史面板改造
展示结构从平铺改为分组:
一等奖
├── 第1轮 (2人)
│ ├── 张三
│ └── 李四
└── 第2轮 (2人)
├── 王五
└── 赵六
点击轮次块可将该轮结果回显到中间展示区,同步更新标题。
历史数据兼容
对于没有轮次字段的老数据,在初始化时按奖项的 batch size 进行推断分组:
const batchSize = prize.batch || 2
records.forEach((record, index) => {
record.round_index = Math.floor(index / batchSize) + 1
})
这是前端的兼容方案,如需精确记录,后续可在后端持久化轮次字段。
交互细节补充
- 弹窗右上角新增关闭按钮
- 历史轮次块增加 hover 和 active 状态
- 删除单条记录使用
@click.stop防止触发轮次回显 - 回显时同步更新中间区域的奖项标题
效果
改造完成后:
- 主持人可以按现场节奏控制展示时长
- 任意历史轮次可快速回显,方便复核和讲解
- 历史面板层次更清晰,进度一目了然
工程层面:
- 关闭逻辑集中管理,可维护性提升
- 轮次结构为后续功能(导出、撤销、回放)打好基础
后续规划
作为持续迭代的产品,我们已经在规划下一阶段的优化:
- 轮次持久化:后端存储 round_index,保证跨端一致性
- 快捷键支持:R 回看上轮、C 关闭弹窗、N 下一轮,提升操作效率
- 轮次操作工具条:支持导出、撤销整轮等操作
小结
抽奖页面的开发重心容易放在动画效果上,但真正影响现场体验的往往是流程控制能力。
这次优化本质上是把"系统主导"改为"人工主导",让操作者对展示节奏有完整的控制权。
选择自研而非套用第三方方案,就是为了能够根据实际使用场景不断调优。每一次现场反馈都是产品进化的契机,这也是我们坚持自主研发的核心原因。