Files
log-lottery/src/hooks/useElement/index.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

353 lines
9.0 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 type { IPersonConfig } from '@/types/storeType'
import { rgba } from '@/utils/color'
interface IUseElementStyle {
element: any
person: IPersonConfig
index: number
patternList: number[]
patternColor: string
cardColor: string
cardSize: { width: number, height: number }
scale: number
textSize: number
mod: 'default' | 'lucky' | 'sphere'
type?: 'add' | 'change'
}
export function useElementStyle(props: IUseElementStyle) {
const { element, person, index, patternList, patternColor, cardColor, cardSize, scale, textSize, mod, type } = props
if (patternList.includes(index + 1) && mod === 'default') {
element.style.backgroundColor = rgba(patternColor, Math.random() * 0.2 + 0.8)
}
else if (mod === 'sphere' || mod === 'default') {
element.style.backgroundColor = rgba(cardColor, Math.random() * 0.5 + 0.25)
}
else if (mod === 'lucky') {
element.style.backgroundColor = rgba(cardColor, 0.8)
}
element.style.border = `1px solid ${rgba(cardColor, 0.25)}`
element.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.5)}`
element.style.width = `${cardSize.width * scale}px`
element.style.height = `${cardSize.height * scale}px`
if (mod === 'lucky') {
element.className = 'lucky-element-card'
}
else {
element.className = 'element-card'
}
if (type === 'add') {
element.addEventListener('mouseenter', (ev: MouseEvent) => {
const target = ev.target as HTMLElement
target.style.border = `1px solid ${rgba(cardColor, 0.75)}`
target.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.75)}`
})
element.addEventListener('mouseleave', (ev: MouseEvent) => {
const target = ev.target as HTMLElement
target.style.border = `1px solid ${rgba(cardColor, 0.25)}`
target.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.5)}`
})
}
element.children[0].style.fontSize = `${textSize * scale * 0.5}px`
if (person.uid) {
element.children[0].textContent = person.uid
}
// 非中奖状态隐藏文字
if (mod !== 'lucky') {
element.children[0].style.opacity = '0'
} else {
element.children[0].style.opacity = '1'
}
element.children[1].style.fontSize = `${textSize * scale}px`
element.children[1].style.lineHeight = `${textSize * scale * 3}px`
element.children[1].style.textShadow = `0 0 12px ${rgba(cardColor, 0.95)}`
if (person.name) {
element.children[1].textContent = person.name
}
// 非中奖状态隐藏文字
if (mod !== 'lucky') {
element.children[1].style.opacity = '0'
} else {
element.children[1].style.opacity = '1'
}
element.children[2].style.fontSize = `${textSize * scale * 0.5}px`
// 设置部门和身份的默认值
element.children[2].innerHTML = ''
if (person.department || person.identity) {
element.children[2].innerHTML = `${person.department ? person.department : ''}<br/>${person.identity ? person.identity : ''}`
}
// 非中奖状态隐藏文字
if (mod !== 'lucky') {
element.children[2].style.opacity = '0'
} else {
element.children[2].style.opacity = '1'
}
element.children[3].src = person.avatar
return element
}
interface CardRule {
[key: number]: {
maxLine: number
scale: number
rule: number[]
length: number
}
}
const cardRule: CardRule = {
1: {
maxLine: 5,
scale: 2,
rule: [1],
length: 1,
},
2: {
maxLine: 5,
scale: 2,
rule: [2],
length: 1,
},
3: {
maxLine: 5,
scale: 2,
rule: [3],
length: 1,
},
4: {
maxLine: 5,
scale: 2,
rule: [4],
length: 1,
},
5: {
maxLine: 5,
scale: 2,
rule: [5],
length: 1,
},
6: {
maxLine: 3,
scale: 2,
rule: [3, 3],
length: 2,
},
7: {
maxLine: 4,
scale: 2,
rule: [3, 4],
length: 2,
},
8: {
maxLine: 5,
scale: 2,
rule: [3, 5],
length: 2,
},
9: {
maxLine: 5,
scale: 2,
rule: [4, 5],
length: 2,
},
10: {
maxLine: 5,
scale: 2,
rule: [5, 5],
length: 2,
},
11: {
maxLine: 6,
scale: 1.8,
rule: [5, 6],
length: 2,
},
12: {
maxLine: 6,
scale: 1.8,
rule: [6, 6],
length: 2,
},
13: {
maxLine: 7,
scale: 1.6,
rule: [6, 7],
length: 2,
},
14: {
maxLine: 7,
scale: 1.6,
rule: [7, 7],
length: 2,
},
15: {
maxLine: 8,
scale: 1.5,
rule: [7, 8],
length: 2,
},
16: {
maxLine: 8,
scale: 1.5,
rule: [8, 8],
length: 2,
},
17: {
maxLine: 6,
scale: 1.6,
rule: [5, 6, 6],
length: 3,
},
18: {
maxLine: 6,
scale: 1.6,
rule: [6, 6, 6],
length: 3,
},
19: {
maxLine: 7,
scale: 1.6,
rule: [6, 6, 7],
length: 3,
},
20: {
maxLine: 5,
scale: 1.6,
rule: [6, 7, 7],
length: 3,
},
21: {
maxLine: 7,
scale: 1.6,
rule: [7, 7, 7],
length: 3,
},
22: {
maxLine: 8,
scale: 1.5,
rule: [7, 7, 8],
length: 3,
},
23: {
maxLine: 8,
scale: 1.5,
rule: [7, 8, 8],
length: 3,
},
24: {
maxLine: 8,
scale: 1.5,
rule: [8, 8, 8],
length: 3,
},
25: {
maxLine: 9,
scale: 1.3,
rule: [8, 8, 9],
length: 3,
},
26: {
maxLine: 9,
scale: 1.3,
rule: [8, 9, 9],
length: 3,
},
27: {
maxLine: 9,
scale: 1.3,
rule: [9, 9, 9],
length: 3,
},
28: {
maxLine: 10,
scale: 1.2,
rule: [9, 9, 10],
length: 3,
},
29: {
maxLine: 10,
scale: 1.2,
rule: [9, 10, 10],
length: 3,
},
30: {
maxLine: 10,
scale: 1.2,
rule: [10, 10, 10],
length: 3,
},
}
/**
* @description 设置抽中卡片的位置
*/
function createRuleForCount(count: number) {
// 动态生成规则:行数在 3-5 之间,尽可能均匀分配
const len = Math.min(5, Math.max(3, Math.ceil(count / 10)))
const base = Math.floor(count / len)
let rem = count % len
const rule = Array.from({ length: len }).fill(0).map(() => base + (rem > 0 ? (rem--, 1) : 0))
const scale = Math.max(0.9, 1.2 - (len - 3) * 0.1)
return { maxLine: Math.min(10, Math.ceil(count / len)), scale, rule, length: len }
}
export function useElementPosition(
element: any,
count: number,
totalCount: number,
cardSize: { width: number, height: number },
windowSize: { width: number, height: number },
cardIndex: number,
): {
xTable: number
yTable: number
scale: number
} {
let xTable = 0
let yTable = 0
const centerPosition = {
x: 0,
y: windowSize.height / 2,
}
const ruleObj = cardRule[totalCount] ?? createRuleForCount(totalCount)
const { scale, rule, length } = ruleObj
// 计算缩放后的卡片尺寸
const scaledCardWidth = cardSize.width * scale
const scaledCardHeight = cardSize.height * scale
// 计算当前卡片在第几行从0开始
let currentRow = 0
let cardIndexInRow = cardIndex // 当前卡片在其所在行中的索引
// 根据规则确定卡片在哪一行及行内索引
let cumulativeCount = 0
for (let i = 0; i < rule.length; i++) {
if (cardIndex < cumulativeCount + rule[i]) {
currentRow = i
cardIndexInRow = cardIndex - cumulativeCount
break
}
cumulativeCount += rule[i]
}
// 计算当前行的卡片数量
const cardsInCurrentRow = rule[currentRow]
// 计算每行的垂直中心位置
const verticalSpacing = scaledCardHeight * 1.1 // 垂直间距基于缩放后的高度
// 计算整体高度并调整居中
const totalHeight = (length - 1) * verticalSpacing + scaledCardHeight // 包含卡片本身的高度
const centerYOffset = -totalHeight / 2
// 修改此处逻辑确保当length=2时两行围绕中心点对称分布
centerPosition.y = windowSize.height / 2 - totalHeight / 2
yTable = centerPosition.y + currentRow * verticalSpacing + centerYOffset // 添加卡片高度的一半作为修正
// 计算当前行的水平居中偏移
const horizontalSpacing = scaledCardWidth * 1.2 // 水平间距基于缩放后的宽度
const rowWidth = (cardsInCurrentRow - 1) * horizontalSpacing
const offsetX = -rowWidth / 2 // 行内水平居中
xTable = centerPosition.x + offsetX + cardIndexInRow * horizontalSpacing
return { xTable, yTable, scale }
}