- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store - 更新 defaultPersonList 为 82 位真实参与者名单 - 调整主页、路由、i18n 及音乐播放以支持抽奖入口 - 附抽奖需求及实现报告文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.9 KiB
6.9 KiB
抽奖系统需求文档
项目背景
当前系统是"抽人"系统(从88人中抽取中奖者)。现在需要新增一个"抽奖"页面,为88个人每人抽取一个奖品。
核心需求
功能描述
- 创建一个新页面,用于为88个人抽奖
- 每次点击抽奖按钮,为一个人抽取一个奖品
- 总共需要点击88次,直到所有人都抽到奖品
- 奖品数量固定,按照以下配置:
奖品配置(共88个)
- 快乐通勤奖 - 可提前1小时下班或晚到1小时(25个)
- 跑马场自由日 - 可选在家或其他场所办公(18个)
- 前途光明奖 - 获得boss 1对1畅聊1小时(2个)
- 现金红包500元(3个)
- 现金红包300元(8个)
- 现金红包200元(15个)
- 现金红包100元(17个)
总计:25 + 18 + 2 + 3 + 8 + 15 + 17 = 88个奖品
技术要求
技术栈
- Vue 3 + TypeScript
- Pinia (状态管理)
- 复用现有的组件和样式系统
数据结构
人员数据
// 使用现有的 88 个人员数据(LN-001 到 LN-088)
// 从 src/store/data.ts 的 defaultPersonList 获取
奖品数据结构
interface Prize {
id: string
name: string
description: string
count: number // 总数量
remaining: number // 剩余数量
color: string // 显示颜色
icon?: string // 图标(可选)
}
interface DrawResult {
personId: string // 人员ID (LN-001)
personName: string // 人员姓名
prizeId: string // 奖品ID
prizeName: string // 奖品名称
drawTime: string // 抽奖时间
drawIndex: number // 第几次抽奖 (1-88)
}
页面布局
主要区域
-
顶部标题区
- 显示"年会抽奖"
- 显示当前进度:已抽 X/88
-
奖品池展示区(左侧或顶部)
- 显示所有奖品类型
- 每个奖品显示:名称、描述、剩余数量/总数量
- 已抽完的奖品置灰
-
抽奖主区域(中央)
- 大按钮:"开始抽奖" / "继续抽奖"
- 抽奖动画效果(可复用现有的卡片翻转/滚动动画)
- 显示当前抽奖结果:
- 人员编号和姓名
- 抽中的奖品
- 庆祝动画
-
已抽奖记录区(右侧或底部)
- 显示已完成的抽奖记录
- 可滚动列表
- 每条记录显示:序号、人员、奖品
核心逻辑
抽奖算法
// 1. 初始化奖品池(88个奖品)
const prizePool = [
...Array(25).fill('快乐通勤奖'),
...Array(18).fill('跑马场自由日'),
...Array(2).fill('前途光明奖'),
...Array(3).fill('现金红包500元'),
...Array(8).fill('现金红包300元'),
...Array(15).fill('现金红包200元'),
...Array(17).fill('现金红包100元'),
]
// 2. 初始化人员池(88个人)
const personPool = [...defaultPersonList]
// 3. 每次抽奖
function draw() {
// 随机选择一个人(从未抽奖的人中)
const randomPersonIndex = Math.floor(Math.random() * personPool.length)
const person = personPool.splice(randomPersonIndex, 1)[0]
// 随机选择一个奖品(从剩余奖品中)
const randomPrizeIndex = Math.floor(Math.random() * prizePool.length)
const prize = prizePool.splice(randomPrizeIndex, 1)[0]
// 记录结果
return { person, prize }
}
状态管理(Pinia Store)
// src/store/prizeDrawConfig.ts
export const usePrizeDrawConfig = defineStore('prizeDraw', {
state: () => ({
prizes: [...], // 奖品配置
persons: [...], // 人员列表
drawResults: [], // 抽奖结果
currentDrawIndex: 0, // 当前抽奖次数
isDrawing: false, // 是否正在抽奖
}),
actions: {
// 初始化
init() {},
// 执行抽奖
async executeDraw() {},
// 重置
reset() {},
// 导出结果
exportResults() {},
}
})
路由配置
// src/router/index.ts
{
path: '/prize-draw',
name: 'PrizeDraw',
component: () => import('@/views/PrizeDraw/index.vue'),
meta: { title: '抽奖系统' }
}
页面入口
- 在主页添加导航按钮,可以切换到抽奖页面
- 或者在顶部菜单添加"抽奖"选项
UI/UX 要求
视觉效果
-
抽奖动画
- 点击按钮后,显示加载/滚动动画(1-2秒)
- 可复用现有的卡片滚动效果
- 最终定格显示结果
-
结果展示
- 大字显示人员编号和奖品名称
- 使用主题色(绿色系)
- 添加庆祝效果(礼花、闪光等)
-
进度指示
- 进度条显示整体进度
- 奖品池实时更新剩余数量
交互流程
- 进入页面 → 显示初始状态(88人待抽奖,88个奖品)
- 点击"开始抽奖" → 播放动画 → 显示结果(第1个人抽到XX奖品)
- 点击"继续抽奖" → 重复步骤2
- 第88次抽奖完成 → 显示"抽奖完成",提供导出功能
数据持久化
- 使用 localStorage 或 IndexedDB 保存抽奖进度
- 刷新页面后可以继续未完成的抽奖
- 提供"重新开始"功能清空数据
额外功能(可选)
导出功能
- 导出抽奖结果为 Excel/CSV
- 包含:序号、人员编号、人员姓名、奖品名称、抽奖时间
统计功能
- 显示每个奖品的分布情况
- 可视化图表展示
撤销功能
- 允许撤销最后一次抽奖
- 将人员和奖品放回池中
实现步骤建议
Phase 1: 基础功能
- 创建 Pinia store (
prizeDrawConfig.ts) - 创建页面组件 (
src/views/PrizeDraw/index.vue) - 实现基础抽奖逻辑
- 添加路由配置
Phase 2: UI 优化
- 设计页面布局
- 添加抽奖动画
- 美化结果展示
- 添加进度指示
Phase 3: 增强功能
- 数据持久化
- 导出功能
- 统计展示
- 撤销功能
文件结构
src/
├── views/
│ └── PrizeDraw/
│ ├── index.vue # 主页面
│ ├── components/
│ │ ├── PrizePool.vue # 奖品池展示
│ │ ├── DrawButton.vue # 抽奖按钮
│ │ ├── DrawResult.vue # 结果展示
│ │ └── DrawHistory.vue # 历史记录
│ └── composables/
│ └── usePrizeDraw.ts # 抽奖逻辑
├── store/
│ └── prizeDrawConfig.ts # 状态管理
└── router/
└── index.ts # 路由配置(更新)
注意事项
- 确保随机算法的公平性
- 处理边界情况(最后一个人、最后一个奖品)
- 考虑性能优化(大量DOM更新)
- 移动端适配
- 复用现有主题配置(颜色、字体等)
验收标准
- 可以完整执行88次抽奖
- 每个人只能抽一次
- 每个奖品数量准确
- 动画流畅自然
- 数据可以持久化
- 可以导出结果
- 移动端可用
- 代码结构清晰,可维护