Files
log-lottery/src/store/prizeDrawConfig.ts
kkfluous 25d0c95dc3 feat: 🎁 新增破冰抽奖功能及 82 人名单
- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store
- 更新 defaultPersonList 为 82 位真实参与者名单
- 调整主页、路由、i18n 及音乐播放以支持抽奖入口
- 附抽奖需求及实现报告文档

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:29:52 +08:00

322 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
},
],
},
})