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