- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store - 更新 defaultPersonList 为 82 位真实参与者名单 - 调整主页、路由、i18n 及音乐播放以支持抽奖入口 - 附抽奖需求及实现报告文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
322 lines
7.9 KiB
TypeScript
322 lines
7.9 KiB
TypeScript
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,
|
||
},
|
||
],
|
||
},
|
||
})
|