去年为了年会抽奖临时做了这个小工具,今年又被点名用同一套方案。
趁着复用的机会,把之前欠下的一些小坑顺手补了一轮,整理完善之后,就决定顺便发出来给大家也能直接用:
- 在线地址:
https://huyikai.github.io/AnnualRaffle/
下面主要想分享两件事:
- 这套工具大概能做什么;
- 为啥我要在 UI 里留一个"临时加奖项"的入口,以及实战中这个入口是怎么救场的。
这个"年会抽奖"大概长什么样
不用太多形容词,就按模块简单说一下。
抽奖视觉
- 中心是一个 3D 标签云,所有参与抽奖的人会在里面旋转。
- 每个人可以配头像,抽到的时候会同时展示名字和照片。
- 没配头像的会用默认头像兜底。
奖项相关
- 默认有常规奖项:一等奖、二等奖、三等奖、幸运奖之类。
- 每个奖项可以在界面上改中奖人数。
- 可以配置 预设名单 :
- 比如某个特殊奖项,中奖人是提前定好的,只是想在现场走个流程。
- 支持 全局排除名单(领导不参与抽奖这类场景)。
抽奖过程
- 底部选择奖项,点击开始,3D 标签云飞快转起来。
- 有背景音乐、开始音效,可以静音、调音量,设置会保存在浏览器里。
- 抽完之后会展示本轮中奖人,点击可以删掉,防止误操作。
数据持久化
- 抽奖配置、名单、结果、音频设置都存在
localStorage。 - 头像文件放在
public/user/,元数据用IndexedDB管。
就算现场不小心刷新了浏览器,抽过的结果还在,不至于从头来过。
为什么 UI 里专门留了一个"临时加奖项"
这里有个小故事。
去年那场年会,抽到一半,领导突然来了句:
"再加一个特别奖吧,就现在抽。"
如果奖项是写死在代码里,这种需求基本意味着:
- 改配置;
- 重新打包;
- 重新部署;
- 祈祷一切顺利。
现场几百个人盯着大屏幕,这种操作就很不现实了。
所以这次我在设计的时候,刻意做了一个分层:
- 代码里 :有一套相对稳定的默认奖项配置,放在
src/config/lottery.ts,方便版本管理和代码 review。 - UI 里:保留"增加奖项"的交互,可以现场临时加一个奖项、设定人数,用完就算。
实战时这个入口确实救了场:主持人说加奖,我直接在配置弹窗里加了一个新奖项,填好名字和数量,点保存,就可以继续抽了,不需要重启、不需要改代码。
这类现场工具,给业务方留一点"临时操作空间",比纯粹的"配置优雅"要重要很多。
技术堆栈简单带一下
不细讲实现,只把堆栈和大致结构列一下,方便有兴趣的同学顺着代码看:
- 基础框架
- Vue 3 + TypeScript
- Vite
- 状态管理
- Pinia:负责当前奖项、结果列表、配置等全局状态
- UI / 样式
- Element Plus:弹窗、表单、按钮等基础组件
- Tailwind CSS:布局和样式
- 可视化 / 多媒体
- TagCanvas:3D 标签云
- HTML5 Audio:背景音乐和音效
- 数据存储
- localStorage:配置、名单、抽奖结果、音频设置
- IndexedDB:头像元数据
目录拆法比较常规:
composables/:抽奖逻辑、3D 标签云封装、音频控制。config/:奖项配置、用户示例数据。helper/:抽奖算法、IndexedDB 操作。components/:配置弹窗、结果展示、公示组件等。
如果你想改 UI 或接入自家系统,大概只需要看这几个目录就够了。
真要用在年会,大概这么搞就够了
假设你也要搞一场年会抽奖,可以按这个顺序来:
1. 拉代码 & 跑起来
bash
pnpm install
pnpm dev
浏览器打开本地地址,先用示例数据跑一遍流程,熟悉一下界面。
2. 换成你们自己的名单
在代码里:
- 复制
src/config/user.example.ts→src/config/user.ts - 把里面的用户列表改成你们的人:
ts
export const user: UserItem[] = [
{ key: 1001, name: '张三' },
{ key: 1002, name: '李四' },
// ...
];
export const excludedUsers: UserItem[] = [
{ key: 9999, name: '某总' }, // 不参与所有奖项
];
不创建 user.ts 也能跑,只是走示例数据。
3. 配奖项
- 平时 :改
src/config/lottery.ts里的默认奖项即可。 - 现场 :可以直接在"抽奖配置"弹窗里:
- 调整每个奖项的数量;
- 给某个奖项加"预设名单"(逗号分隔的用户 ID);
- 必要时加一个临时奖项。
4. 搞定头像(强烈建议)
视觉氛围基本靠它。
做法:
- 准备头像图片,建议 160×160 左右,体积别太大。
- 放到
public/user/目录。 - 命名规则:
用户 key + 扩展名,比如1001.jpg。
项目里还有一个批量处理头像的脚本,放在 scripts/ 里,用来把原始照片统一裁成头像,细节可以看 scripts/README.md。
批量头像处理脚本:从"真人照片"到统一头像
真实情况是,原始员工照片的尺寸、比例、背景色基本都不一样,直接拿来做头像效果会很怪。
为此我在 scripts/ 目录下写了一套批量头像处理脚本,主要做这些事:
- 批量裁剪 / 缩放成统一尺寸(默认 160×160)。
- 统一背景色、统一格式(JPG/PNG)。
- 提供"快速模式"和"AI 增强模式",按自己的环境和需求选择。
典型用法:
- 把原图放到
scripts/input/。 - 运行:
bash
pnpm run process-images
- 在
scripts/output/拿到处理好的头像,拷到public/user/即可。
详细配置(背景色、尺寸、模式开关等)在 scripts/README.md 里都有写。
部署:用 GitHub Pages 省点事
项目现在是放在 GitHub Pages 上的,地址就是:
https://huyikai.github.io/AnnualRaffle/
如果你也想这么搞,一般步骤是:
- 把代码推到 GitHub。
- 用 GitHub Actions 跑一遍:
- 安装依赖;
pnpm build;- 把
dist/发布到gh-pages分支。
- 在仓库 Settings → Pages 里选对应分支。
配置好之后,后面每次推代码,线上链接会自动更新。
实际使用感受
这套东西在一场不到 200 人的年会上跑了一次,整体体验还可以:
- 3D 标签云加照片,现场观感比纯文字名单要好不少。
- 数据持久化避免了"误刷新导致重来一次"的事故。
- UI 里的"临时加奖项"确实发挥了作用,帮我们兜住了领导临时加奖的情况。
如果你今年也被安排做年会抽奖,可以直接拿这个项目当模板:
先跑一遍 Demo,看下是否符合你们场景,再按自己需求改名单、奖项和 UI 即可。
一点开发过程的小记
这个项目的开发过程里,我是借助了 Cursor 这类辅助工具来提效的:
- 搭项目、改结构、重构一些逻辑时,用它来帮忙生成样板代码和做局部重构。
- 这篇文章本身,我也借助了
Cursor里的 AI 写作辅助,再根据自己的实际场景做了调整。
如果你平时也用 VS Code 类的编辑器,可以尝试一下类似的工作流:
让工具帮你处理重复劳动,把精力放在业务和体验上。
最后:欢迎来 GitHub 点个 Star
项目在线地址我再放一次:
- 在线 Demo:
https://huyikai.github.io/AnnualRaffle/
仓库地址在这里:
- GitHub 仓库:
https://github.com/huyikai/AnnualRaffle
如果你觉得这套年会抽奖还算顺手,或者对你有一点参考价值,欢迎顺手点个 Star。