AnnualRaffle
年会抽奖系统,支持3D标签云可视化抽奖、多奖项配置、用户照片展示等功能,适用于企业年会、活动抽奖等场景。
Install / Use
/learn @huyikai/AnnualRaffleREADME
年会抽奖系统
一个基于 Vue 3 + TypeScript 开发的年会抽奖系统,支持3D标签云可视化抽奖、多奖项配置、用户照片展示等功能,适用于企业年会、活动抽奖等场景。
项目特色
- 🎯 3D标签云抽奖:使用 TagCanvas 实现炫酷的3D旋转标签云效果,增强抽奖仪式感
- 📸 照片展示:支持为每个用户添加照片,照片会显示在标签云和抽奖结果中
- 🎁 多奖项管理:支持配置多个奖项,每个奖项可设置不同的中奖人数
- 🎫 灵活的用户限制:支持为特定用户设置只能参加某些奖项,灵活控制参与范围
- 🚫 灵活的排除控制:支持全局和奖项级两层排除设置,可排除已中奖人员或指定人员,奖项级设置优先于全局
- 🔊 音频支持:支持背景音乐和开始音效,可调节音量和静音控制,增强抽奖氛围
- ⚙️ 统一配置管理:所有业务配置集中在
config目录,易于维护和扩展 - 💾 数据持久化:使用 localStorage 和 IndexedDB 保存配置、结果和照片数据,刷新不丢失
- 🎨 精美UI:基于 Element Plus 和 Tailwind CSS 构建的现代化界面
功能特性
抽奖配置管理
- 可配置多个奖项(一等奖、二等奖、三等奖、幸运奖等)
- 每个奖项可设置中奖人数
- 支持自定义新增奖项
- 预设名单功能:每个奖项都可以选择是否使用预设名单,预设名单与奖项配置集成,设置更便捷
- 排除已中奖人员:全局开关控制是否排除已中奖人员,开启后已中奖人员默认不参与后续抽奖
- 奖项级排除设置:每个奖项可独立设置排除模式(跟随全局/自定义),自定义模式下支持:
- 独立控制是否排除已中奖人员(不受全局开关影响)
- 排除指定人员(如排除领导、高管等,通过人员选择器选取)
- 两者可同时生效,叠加排除
- 音频设置:支持静音/取消静音控制,音量调节(0-100%),设置会自动保存
用户名单管理
- 通过代码配置用户数据(号码 + 姓名格式)
- 支持排除特定用户不参与所有奖项的抽奖(比如排除领导、高管等)
- 用户奖项限制:可以为特定用户设置只能参加某些奖项(如只能参加幸运奖),灵活控制不同用户的参与范围
照片管理
- 支持为每个用户添加照片(通过文件放置)
- 照片格式支持 JPG、PNG
- 照片会显示在3D标签云和抽奖结果中
抽奖功能
- 3D标签云展示参与抽奖的用户
- 开始/停止抽奖控制
- 抽奖结果动画展示
- 灵活排除控制:支持全局排除已中奖人员,也支持按奖项单独设置排除规则(跟随全局或自定义排除已中奖人员/指定人员),无论任何设置,同一人不会在同一奖项中被抽中两次
- 支持预设名单:每个奖项可配置预设中奖名单,启用后直接使用预设名单,无需随机抽取
- 智能补抽:当预设名单人数不足时,自动补充随机抽取剩余名额
- 音频效果:开始抽奖时播放音效,背景音乐循环播放(可在配置中控制)
结果管理
- 查看所有奖项的抽奖结果
- 支持删除已中奖号码(点击结果卡片即可删除)
- 结果数据持久化保存
- 结果展示包含用户照片和姓名
数据重置
- 支持重置全部数据(包括 localStorage 和 IndexedDB)
- 支持分别重置:
- 抽奖配置
- 用户名单
- 照片数据(IndexedDB)
- 抽奖结果
技术栈
- Vue 3 + TypeScript
- Vite
- Pinia(状态管理)
- Element Plus(UI组件库)
- Tailwind CSS(样式框架)
- Vue Router 4(路由管理)
- TagCanvas(3D标签云)
- IndexedDB(照片数据存储)
快速开始
安装依赖
pnpm install
启动开发服务器
pnpm dev
快速上手
启动项目后,你可以直接使用示例数据进行测试,无需任何配置!
快速上手三步:
- 启动项目 → 打开浏览器即可测试(使用示例数据)
- 配置用户数据(可选)→ 详见下方"使用指南"
- 添加用户照片(可选)→ 详见下方"使用指南"
构建生产版本
pnpm build
预览生产构建
pnpm preview
使用指南
1. 配置用户数据(可选)
如果不配置,系统会自动使用示例数据。
步骤:
- 复制
src/config/user.example.ts为src/config/user.ts - 在
user.ts中修改用户列表:
// 定义用户组
const ABC_Users = [
{ key: 1001, name: '张三' },
{ key: 1002, name: '李四' },
// ... 更多用户
];
const XYZ_Users = [
{ key: 2001, name: '王五' },
{ key: 2002, name: '赵六' },
// ... 更多用户
];
const DEF_Users = [
{ key: 3001, name: '钱七' },
{ key: 3002, name: '孙八' },
// ... 更多用户
];
// 合并用户列表,可以为特定用户组设置奖项限制
export const user: UserItem[] = [
...ABC_Users, // ABC_Users 可以参加所有奖项
...XYZ_Users.map(user => ({ ...user, allowedPrizes: ['luckyFirst', 'luckySecond'] })), // XYZ_Users 只能参加幸运奖
...DEF_Users.map(user => ({ ...user, allowedPrizes: ['luckyFirst', 'luckySecond'] })) // DEF_Users 只能参加幸运奖
];
// 或者为单个用户设置限制
export const user: UserItem[] = [
{ key: 1001, name: '张三' }, // 可以参加所有奖项
{ key: 1002, name: '李四', allowedPrizes: ['luckyFirst', 'luckySecond'] }, // 只能参加幸运奖
// ... 添加更多用户
];
// 排除用户(可选,适用于所有奖项)
export const excludedUsers: UserItem[] = [
{ key: 1001, name: '张三' }, // 不参与任何奖项抽奖的用户
];
用户奖项限制说明:
allowedPrizes字段为可选,如果未设置或为空数组,表示该用户可以参加所有奖项- 如果设置了
allowedPrizes,则该用户只能参加列表中指定的奖项 - 可以使用
map方法批量为一组用户设置相同的奖项限制 - 奖项 key 参考:
firstPrize: 一等奖secondPrize: 二等奖thirdPrize: 三等奖luckyFirst: 幸运奖第一轮luckySecond: 幸运奖第二轮- 自定义奖项的 key(在抽奖配置中创建的自定义奖项)
使用场景示例:
- 某些人员只能参加幸运奖,不能参加1、2、3等奖
- 临时工只能参加特定奖项
- 根据不同规则灵活设置用户的参与范围
- 批量为一组用户设置相同的奖项限制(如某个部门的员工只能参加幸运奖)
2. 配置奖项和音频
- 点击页面右上角的"抽奖配置"按钮
- 音频设置(可选):
- 点击"静音"按钮可切换静音/取消静音状态
- 拖动音量滑块调节音量(0-100%)
- 音频设置会自动保存,刷新后保持设置
- 抽奖设置(可选):
- 排除已中奖人员(全局开关):开启后,已中奖人员默认不参与后续所有奖项的抽奖;关闭后,已中奖人员仍可参与其他奖项
- 奖项配置:每个奖项以卡片形式展示,包含以下设置:
- 数量:设置该奖项的中奖人数
- 排除模式(可选):
- 默认"跟随全局",使用全局排除开关的设置
- 选择"自定义"后展开两个子设置(可同时启用,叠加生效):
- 排除已中奖人员:独立控制,不受全局开关影响
- 排除指定人员:点击"排除指定人员"按钮,在弹出的人员选择器中选取要排除的人(如领导、高管等)
- 预设名单(可选):打开开关后,点击"选择预设名单"在弹出的人员选择器中选取预设中奖人员
- 点击"增加奖项"可添加自定义奖项
- 点击"完成"保存配置
排除设置典型场景:
- 公平分配(默认):全局开启排除,所有奖项跟随全局 → 每人最多中一个奖
- 幸运奖不影响大奖:全局开启排除,幸运奖设为"自定义"并关闭排除已中奖 → 中了幸运奖的人仍可抽大奖
- 一等奖排除领导:一等奖设为"自定义",开启排除已中奖 + 排除指定人员选择领导 → 领导和已中奖人员都不参加一等奖
预设名单说明:预设名单是可选的,启用后优先使用预设用户,不足时自动补充随机抽取。
3. 添加用户照片(可选)
照片是可选的,没有照片时会自动显示默认头像。
步骤:
- 准备照片文件(JPG/PNG,建议 20-50KB,160×160px)
- 放入
public/user/目录,命名为{用户key}.jpg(如:1001.jpg) - 刷新页面即可看到照片
提示:可以逐步添加,不需要一次性准备所有照片。
4. 开始抽奖
- 在页面底部选择要抽取的奖项
- 点击"开始抽奖"按钮
- 等待抽奖动画完成
- 点击"停止"按钮或等待自动停止
- 查看抽奖结果
5. 查看和管理结果
- 查看结果:点击页面右上角的"抽奖结果"按钮
- 删除中奖号码:在结果列表中点击要删除的号码卡片
配置说明
奖项配置
编辑 src/config/lottery.ts 文件可以修改默认奖项配置。所有奖项定义在 LOTTERY_ITEMS 数组中,包括奖项的 key、名称和默认数量:
export const LOTTERY_ITEMS: LotteryItem[] = [
{ key: 'firstPrize', name: '一等奖', defaultCount: 1 },
{ key: 'secondPrize', name: '二等奖', defaultCount: 2 },
{ key: 'thirdPrize', name: '三等奖', defaultCount: 3 },
{ key: 'luckyFirst', name: '幸运奖第一轮', defaultCount: 15 },
{ key: 'luckySecond', name: '幸运奖第二轮', defaultCount: 15 },
// ... 更多奖项
];
注意:
- 修改
LOTTERY_ITEMS数组后,所有相关的类型、默认配置和结果类型都会自动更新,无需手动维护 - 每个奖项配置支持以下属性:
count(中奖人数)preset(预设名单,可选):逗号分隔的用户ID字符串,例如"1001,1002,1003"excludeMode(排除模式,可选):'global'(跟随全局,默认)或'custom'(自定义)excludeWinners(排除已中奖,可选):自定义模式下独立控制是否排除已中奖人员,默认trueexcludedPersons(排除指定人员,可选):自定义模式下排除的用户ID,逗号分隔字符串
预设名单配置
在"抽奖配置"界面中,每个奖项下方都有预设名单配置选项。打开开关,输入用户ID(逗号分隔)即可。
用户奖项限制配置
在 src/config/user.ts 中,可以为每个用户设置 allowedPrizes 字段来限制其只能参加指定的奖项:
export interface UserItem {
key: number;
name: string;
allowedPrizes?: string[]; // 可选:允许参加的奖项 key 列表
}
配置规则:
- 如果用户没有设置
allowedPrizes或设置为空数组,则该用户可以参加所有奖项 - 如果设置了
allowedPrizes,则该用户只能参加列表中指定的奖项 - 在抽奖时,系统会自动过滤不符合奖项限制的用户
- 预设名单也会检查用户的奖项限制,只有符合限制的用户才会被选中
示例:
// 方式1:为单个用户设置限制
export const user: UserItem[] = [
{ key: 1001, name: '张三' }, // 可以参加所有奖项
{ key: 1002, name: '李四', allowedPrizes: ['luckyFirst', 'luckySecond'] }, // 只能参加幸运奖
{ key: 1003, name: '王五', allowedPrizes: ['firstPrize', 'secondPrize'] }, // 只能参加一、二等奖
];
// 方式2:批量为一组用户设置相同的限制(推荐)
const TTC_Users = [
{ key: 2001, name: '王五' },
{ key: 2002, name: '赵六' },
// ... 更多用户
];
export const user: UserItem[] = [
...QD_Users, // 可以参加所有奖项
...TTC_Users.map(user => ({ ...user, allowedPrizes: ['luckyFirst', 'luckySecond'] })), // 批量设置为只能参加幸运奖
];
照片目录
用户照片存放在 public/user/ 目录中,文件命名:{用户key}.jpg 或 {用户key}.png。详见 public/user/README.md。
数据存储
- 配置、名单、结果、音频设置:存储在 localStorage 中
- 照片文件:存放在
public/user/目录中,照片元数据存储在 IndexedDB 中
批量头像图片处理脚本(可选)
项目提供了一个位于 scripts/ 目录下的批量头像图片处理脚本,用于将原始员工照片统一为适合抽奖展示的头像格式:
- 用途:批量统一头像的尺寸(默认 160×160)、背景色和格式(默认 JPG),生成后的文件可直接放入
public/user/目录按{用户key}.jpg/{用户key}.png使用。 - 使用方式(简要):
- 将原始图片放入
scripts/input/目录。 - 运行命令:
pnpm run process-images(默认使用 AI 增强模式处理)。 - 在
scripts/output/目录中查看并将生成的头像拷贝到public/user/。
- 将原始图片放入
- 特性概览:支持快速模式与 AI 增强模式、批量处理、统一尺寸/格式/背景色等。
详细配置(环境变量、默认背景色、AI 模式说明等)请参考脚本自己的文档:[scripts/README.md](scripts/README.md)。
开发
代码检查
pnpm lint
代码架构
项目采用 Vue 3 Composition API 和 Composables 模式组织代码:
- Composables:业务逻辑封装在
src/composables/目录useLottery.ts:抽奖业务逻辑(开始、停止、结果显示等)useTagCanvas.ts:TagCanvas 3D标签云管理(初始化、重载、速度控制等)useAudio.ts:音频管理(背景音乐、音效播放、音量控制、静音控制等)
- 状态管理:使用 Pinia 管理全局状态(
src/stores/lottery.ts) - 工具函数:通用工具函数集中在
src/helper/目录 - 常量管理:所有魔法数字和字符串统一在
src/constants/目录管理 - 类型定义:TypeScript 类型定义集中在
src/types/目录
项目结构
AnnualRaffle/
├── public/ # 静态资源
│ └── user/ # 用户照片目录
├── src/
│ ├── assets/ # 静态资源文件
│ │ ├── begin.mp3 # 音频文件
│ │ ├── bg.jpg # 背景图片
│ │ ├── bg.mp3 # 背景音频
│ │ └── style/ # 样式文件
│ ├── lib/ # 第三方库
│ │ ├── tagcanvas.ts # TagCanvas 3D标签云库 (TypeScript)
│ │ └── tagcanvas.types.ts # TagCanvas 类型定义
│ ├── composables/ # 组合式函数(Composables)
│ │ ├── useLottery.ts # 抽奖业务逻辑
│ │ ├── useTagCanvas.ts # TagCanvas 管理逻辑
│ │ └── useAudio.ts # 音频管理逻辑
│ ├── config/ # 业务配置
│ │ ├── lottery.ts # 奖项配置(统一管理)
│ │ ├── user.ts # 用户数据(需自行创建)
│ │ ├── user.example.ts # 用户数据示例
│ │ └── user.template.ts # 用户数据模板
│ ├── constants/ # 常量定义
│ │ └── index.ts # 统一常量管理
│ ├── components/ # 组件
│ │ ├── LotteryConfig.vue # 抽奖配置组件
│ │ ├── Result.vue # 结果展示组件
│ │ ├── Tool.vue # 工具组件
│ │ └── Publicity.vue # 公示组件
│ ├── helper/ # 工具函数
│ │ ├── algorithm.ts # 抽奖算法
│ │ ├── db.ts # IndexedDB 数据库操作
│ │ └── index.ts # 工具函数集合
│ ├── router/ # 路由配置
│ │ └── index.ts # 路由定义
│ ├── stores/ # Pinia 状态管理
│ │ └── lottery.ts # 抽奖状态管理
│ ├── types/ # TypeScript 类型定义
│ │ ├── index.ts # 统一类型定义
│ │ └── tagcanvas.d.ts # TagCanvas 类型声明
│ ├── views/ # 页面视图
│ │ └── Home.vue # 首页组件
│ ├── App.vue # 根组件
│ └── main.ts # 应用入口
└── README.md
注意事项
- 首次使用:
src/config/user.ts文件是可选的,不创建会自动使用示例数据 - 照片文件:
public/user/目录中的照片文件不会被提交到 Git - 数据存储:配置、名单、结果存储在 localStorage 中;照片元数据存储在 IndexedDB 中(数据库名:
AnnualRaffle) - 浏览器兼容性:建议使用现代浏览器(Chrome、Firefox、Edge 等),需要支持 IndexedDB 和 HTML5 Audio API
- 音频功能:
- 系统支持背景音乐(
bg.mp3)和开始音效(begin.mp3) - 音频文件位于
src/assets/目录 - 由于浏览器自动播放策略,音频需要在用户交互后启用(首次点击页面)
- 音频设置(静音状态、音量)会自动保存到 localStorage
- 默认状态为静音,默认音量为 50%
- 系统支持背景音乐(
- 数据备份:重要数据建议定期备份,可通过浏览器开发者工具导出:
- localStorage 数据:Application → Local Storage
- IndexedDB 数据:Application → IndexedDB → AnnualRaffle
- 性能优化:大量用户时建议压缩照片大小以提升加载速度
- 奖项配置:修改奖项只需编辑
src/config/lottery.ts中的LOTTERY_ITEMS数组,所有相关配置会自动更新 - 代码架构:项目采用 Vue 3 Composition API 和 Composables 模式,业务逻辑封装在
composables/目录中
许可证
MIT License
Related Skills
node-connect
354.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
