feat: 🎁 新增破冰抽奖功能及 82 人名单
- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store - 更新 defaultPersonList 为 82 位真实参与者名单 - 调整主页、路由、i18n 及音乐播放以支持抽奖入口 - 附抽奖需求及实现报告文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
321
src/store/prizeDrawConfig.ts
Normal file
321
src/store/prizeDrawConfig.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getRandomElements } from '@/views/Home/utils/random'
|
||||
import { defaultPersonList } from './data'
|
||||
import type { IPersonConfig } from '@/types/storeType'
|
||||
|
||||
export interface PrizeConfig {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
totalCount: number
|
||||
remainingCount: number
|
||||
color: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface DrawResult {
|
||||
id: string
|
||||
drawIndex: number
|
||||
prizeId: string
|
||||
prizeName: string
|
||||
prizeDescription: string
|
||||
drawTime: string
|
||||
}
|
||||
|
||||
export const usePrizeDrawStore = defineStore('prizeDraw', {
|
||||
state: () => ({
|
||||
// 配置版本号 - 修改此版本号会强制重新初始化
|
||||
configVersion: 2,
|
||||
|
||||
// 奖品配置
|
||||
prizeConfigs: [
|
||||
{
|
||||
id: 'prize-1',
|
||||
name: '快乐通勤奖',
|
||||
description: '可提前1小时下班或晚到1小时',
|
||||
totalCount: 25,
|
||||
remainingCount: 25,
|
||||
color: '#10b981',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
id: 'prize-2',
|
||||
name: '跑马场自由日',
|
||||
description: '可选在家或其他场所办公',
|
||||
totalCount: 18,
|
||||
remainingCount: 18,
|
||||
color: '#3b82f6',
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
id: 'prize-3',
|
||||
name: '前途光明奖',
|
||||
description: '获得boss 1对1畅聊1小时',
|
||||
totalCount: 2,
|
||||
remainingCount: 2,
|
||||
color: '#f59e0b',
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
id: 'prize-4',
|
||||
name: '现金红包',
|
||||
description: '500元',
|
||||
totalCount: 3,
|
||||
remainingCount: 3,
|
||||
color: '#ef4444',
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
id: 'prize-5',
|
||||
name: '现金红包',
|
||||
description: '300元',
|
||||
totalCount: 8,
|
||||
remainingCount: 8,
|
||||
color: '#f97316',
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
id: 'prize-6',
|
||||
name: '现金红包',
|
||||
description: '200元',
|
||||
totalCount: 15,
|
||||
remainingCount: 15,
|
||||
color: '#ec4899',
|
||||
order: 6,
|
||||
},
|
||||
{
|
||||
id: 'prize-7',
|
||||
name: '现金红包',
|
||||
description: '100元',
|
||||
totalCount: 17,
|
||||
remainingCount: 17,
|
||||
color: '#8b5cf6',
|
||||
order: 7,
|
||||
},
|
||||
] as PrizeConfig[],
|
||||
|
||||
// 奖品池(用于抽取)
|
||||
prizePool: [] as string[],
|
||||
|
||||
// 人员池
|
||||
personPool: [] as IPersonConfig[],
|
||||
|
||||
// 抽奖结果
|
||||
drawResults: [] as DrawResult[],
|
||||
|
||||
// 当前状态
|
||||
currentDrawIndex: 0,
|
||||
isInitialized: false,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 进度百分比
|
||||
progress: (state) => {
|
||||
if (state.currentDrawIndex === 0)
|
||||
return 0
|
||||
return Math.round((state.currentDrawIndex / 88) * 100)
|
||||
},
|
||||
|
||||
// 剩余人数
|
||||
remainingPersons: state => state.personPool.length,
|
||||
|
||||
// 剩余奖品数
|
||||
remainingPrizes: state => state.prizePool.length,
|
||||
|
||||
// 是否完成
|
||||
isCompleted: state => state.currentDrawIndex >= 88,
|
||||
|
||||
// 总人数
|
||||
totalPersons: () => 88,
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 初始化
|
||||
init() {
|
||||
// 检查版本号,如果不匹配则强制重新初始化
|
||||
const expectedVersion = 2
|
||||
if (this.configVersion !== expectedVersion) {
|
||||
console.log(`配置版本不匹配 (${this.configVersion} !== ${expectedVersion}),重新初始化...`)
|
||||
this.reset()
|
||||
this.configVersion = expectedVersion
|
||||
}
|
||||
|
||||
if (this.isInitialized && this.personPool.length > 0) {
|
||||
console.log('已初始化,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('初始化抽奖系统...')
|
||||
|
||||
// 从 data.ts 加载88人
|
||||
this.personPool = defaultPersonList.map((p, index) => ({
|
||||
...p,
|
||||
uuid: `uuid-${index}`,
|
||||
}))
|
||||
|
||||
// 构建奖品池(88个奖品)
|
||||
this.prizePool = [
|
||||
...Array(25).fill('prize-1'),
|
||||
...Array(18).fill('prize-2'),
|
||||
...Array(2).fill('prize-3'),
|
||||
...Array(3).fill('prize-4'),
|
||||
...Array(8).fill('prize-5'),
|
||||
...Array(15).fill('prize-6'),
|
||||
...Array(17).fill('prize-7'),
|
||||
]
|
||||
|
||||
// 重置奖品剩余数量
|
||||
this.prizeConfigs.forEach((config) => {
|
||||
config.remainingCount = config.totalCount
|
||||
})
|
||||
|
||||
this.isInitialized = true
|
||||
|
||||
console.log(`初始化完成: ${this.personPool.length}人, ${this.prizePool.length}个奖品`)
|
||||
},
|
||||
|
||||
// 执行一次抽奖
|
||||
async executeDraw(): Promise<DrawResult | null> {
|
||||
if (this.prizePool.length === 0) {
|
||||
console.log('抽奖已完成')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// 从奖品池中随机抽取1个奖品
|
||||
const [prizeId] = getRandomElements(this.prizePool, 1)
|
||||
|
||||
// 找到奖品配置
|
||||
const prizeConfig = this.prizeConfigs.find(p => p.id === prizeId)
|
||||
if (!prizeConfig) {
|
||||
throw new Error(`未找到奖品配置: ${prizeId}`)
|
||||
}
|
||||
|
||||
// 从奖品池中移除
|
||||
const prizeIndex = this.prizePool.indexOf(prizeId)
|
||||
if (prizeIndex > -1) {
|
||||
this.prizePool.splice(prizeIndex, 1)
|
||||
}
|
||||
|
||||
// 更新奖品剩余数量
|
||||
prizeConfig.remainingCount--
|
||||
|
||||
// 记录结果
|
||||
const result: DrawResult = {
|
||||
id: `draw-${Date.now()}-${Math.random()}`,
|
||||
drawIndex: ++this.currentDrawIndex,
|
||||
prizeId,
|
||||
prizeName: prizeConfig.name,
|
||||
prizeDescription: prizeConfig.description,
|
||||
drawTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
this.drawResults.unshift(result)
|
||||
|
||||
console.log(`抽奖结果: ${result.prizeName}`)
|
||||
|
||||
return result
|
||||
}
|
||||
catch (error) {
|
||||
console.error('抽奖失败:', error)
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
// 撤销最后一次抽奖
|
||||
undoLastDraw() {
|
||||
if (this.drawResults.length === 0) {
|
||||
console.log('没有可撤销的抽奖记录')
|
||||
return false
|
||||
}
|
||||
|
||||
const lastResult = this.drawResults[0]
|
||||
|
||||
// 找回奖品
|
||||
this.prizePool.push(lastResult.prizeId)
|
||||
|
||||
// 恢复奖品数量
|
||||
const prizeConfig = this.prizeConfigs.find(p => p.id === lastResult.prizeId)
|
||||
if (prizeConfig) {
|
||||
prizeConfig.remainingCount++
|
||||
}
|
||||
|
||||
// 移除记录
|
||||
this.drawResults.shift()
|
||||
this.currentDrawIndex--
|
||||
|
||||
console.log(`已撤销: ${lastResult.prizeName}`)
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
// 重置所有数据
|
||||
reset() {
|
||||
this.prizePool = []
|
||||
this.personPool = []
|
||||
this.drawResults = []
|
||||
this.currentDrawIndex = 0
|
||||
this.isInitialized = false
|
||||
|
||||
// 重新初始化
|
||||
this.init()
|
||||
|
||||
console.log('已重置抽奖系统')
|
||||
},
|
||||
|
||||
// 导出结果为Excel
|
||||
exportToExcel() {
|
||||
if (this.drawResults.length === 0) {
|
||||
console.log('没有抽奖记录可导出')
|
||||
return
|
||||
}
|
||||
|
||||
// 动态导入xlsx
|
||||
import('xlsx').then((XLSX) => {
|
||||
// 准备数据(按抽奖顺序)
|
||||
const results = this.drawResults.slice().reverse()
|
||||
const data = results.map(result => ({
|
||||
序号: result.drawIndex,
|
||||
奖品名称: result.prizeName,
|
||||
奖品描述: result.prizeDescription,
|
||||
抽奖时间: new Date(result.drawTime).toLocaleString('zh-CN'),
|
||||
}))
|
||||
|
||||
// 创建工作表
|
||||
const ws = XLSX.utils.json_to_sheet(data)
|
||||
|
||||
// 设置列宽
|
||||
ws['!cols'] = [
|
||||
{ wch: 8 }, // 序号
|
||||
{ wch: 12 }, // 人员编号
|
||||
{ wch: 12 }, // 人员姓名
|
||||
{ wch: 20 }, // 奖品名称
|
||||
{ wch: 30 }, // 奖品描述
|
||||
{ wch: 20 }, // 抽奖时间
|
||||
]
|
||||
|
||||
// 创建工作簿
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '抽奖结果')
|
||||
|
||||
// 导出文件
|
||||
const fileName = `抽奖结果-${new Date().toLocaleDateString('zh-CN').replace(/\//g, '-')}.xlsx`
|
||||
XLSX.writeFile(wb, fileName)
|
||||
|
||||
console.log('已导出抽奖结果到Excel')
|
||||
}).catch((error) => {
|
||||
console.error('导出Excel失败:', error)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'prizeDraw',
|
||||
storage: localStorage,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user