- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store - 更新 defaultPersonList 为 82 位真实参与者名单 - 调整主页、路由、i18n 及音乐播放以支持抽奖入口 - 附抽奖需求及实现报告文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
260 lines
6.9 KiB
Markdown
260 lines
6.9 KiB
Markdown
# 抽奖系统需求文档
|
||
|
||
## 项目背景
|
||
当前系统是"抽人"系统(从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次抽奖
|
||
- [ ] 每个人只能抽一次
|
||
- [ ] 每个奖品数量准确
|
||
- [ ] 动画流畅自然
|
||
- [ ] 数据可以持久化
|
||
- [ ] 可以导出结果
|
||
- [ ] 移动端可用
|
||
- [ ] 代码结构清晰,可维护
|