feat(game/ui): 实现周报结算弹窗,包含10秒倒计时、黑话答题和奖励/惩罚逻辑

This commit is contained in:
Cloud Bot
2026-03-21 08:15:03 +00:00
parent 883b9a48df
commit 4737fd3cb1

View File

@@ -0,0 +1,285 @@
import { GameManager } from '../GameManager'
import { generateWeeklyOptions } from '../data/buzzwords'
export type WeeklyReportCallbacks = {
onBossInspection: () => void
onFullStamina: () => void
}
/**
* 周报结算弹窗
* 每3波结束后触发仿钉钉/飞书全屏通知风格
* 用 DOM 层实现z-index: 9999覆盖 canvas
*/
export class WeeklyReportModal {
private container: HTMLElement | null = null
private timerInterval: ReturnType<typeof setInterval> | null = null
private callbacks: WeeklyReportCallbacks
private onClose?: () => void
constructor(callbacks: WeeklyReportCallbacks) {
this.callbacks = callbacks
}
/** 显示弹窗 */
show(onClose?: () => void): void {
this.onClose = onClose
this.createDOM()
this.startTimer()
}
private createDOM(): void {
// 移除旧弹窗(防止重复)
this.cleanup()
const { options, correctIndex } = generateWeeklyOptions()
// 背景蒙层
const overlay = document.createElement('div')
overlay.id = 'weekly-report-overlay'
overlay.style.cssText = `
position: fixed;
inset: 0;
background: rgba(0,0,0,0.82);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
font-family: 'VT323', monospace;
animation: fadeInOverlay 0.3s ease;
`
// 弹窗卡片
const card = document.createElement('div')
card.style.cssText = `
background: #1a1a2e;
border: 2px solid #7c3aed;
border-radius: 12px;
padding: 32px 40px;
max-width: 520px;
width: 90%;
box-shadow: 0 0 40px rgba(124,58,237,0.5);
position: relative;
animation: slideInCard 0.35s ease;
`
// 顶部进度条容器
const progressWrap = document.createElement('div')
progressWrap.style.cssText = `
position: absolute;
top: 0; left: 0; right: 0;
height: 6px;
background: #374151;
border-radius: 12px 12px 0 0;
overflow: hidden;
`
const progressBar = document.createElement('div')
progressBar.id = 'weekly-progress-bar'
progressBar.style.cssText = `
height: 100%;
background: #ef4444;
width: 100%;
transition: width linear;
`
progressWrap.appendChild(progressBar)
card.appendChild(progressWrap)
// 标题
const title = document.createElement('div')
title.textContent = '📊 周报提交时间!'
title.style.cssText = `
font-size: 28px;
color: #a78bfa;
text-align: center;
margin-bottom: 8px;
letter-spacing: 2px;
`
card.appendChild(title)
// 副标题
const subtitle = document.createElement('div')
subtitle.textContent = '请选择正确的互联网黑话,提交本周工作总结'
subtitle.style.cssText = `
font-size: 16px;
color: #9ca3af;
text-align: center;
margin-bottom: 8px;
`
card.appendChild(subtitle)
// 倒计时文字
const timerText = document.createElement('div')
timerText.id = 'weekly-timer-text'
timerText.textContent = '10'
timerText.style.cssText = `
font-size: 20px;
color: #fcd34d;
text-align: center;
margin-bottom: 24px;
`
card.appendChild(timerText)
// 选项区域
const optionsWrap = document.createElement('div')
optionsWrap.style.cssText = `
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 20px;
`
options.forEach((opt, idx) => {
const btn = document.createElement('button')
btn.textContent = opt
btn.dataset.index = String(idx)
btn.style.cssText = `
flex: 1;
padding: 14px 8px;
background: #0f172a;
border: 2px solid #4b5563;
border-radius: 8px;
color: #e2e8f0;
font-family: 'VT323', monospace;
font-size: 20px;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
`
btn.addEventListener('mouseenter', () => {
if (!btn.disabled) {
btn.style.borderColor = '#7c3aed'
btn.style.background = '#1e1b4b'
}
})
btn.addEventListener('mouseleave', () => {
if (!btn.disabled) {
btn.style.borderColor = '#4b5563'
btn.style.background = '#0f172a'
}
})
btn.addEventListener('click', () => {
this.handleAnswer(idx === correctIndex, options[correctIndex])
})
optionsWrap.appendChild(btn)
})
card.appendChild(optionsWrap)
// 结果区域(初始隐藏)
const resultArea = document.createElement('div')
resultArea.id = 'weekly-result'
resultArea.style.cssText = `
display: none;
text-align: center;
padding: 12px;
border-radius: 8px;
font-size: 20px;
`
card.appendChild(resultArea)
// 注入动画样式
const style = document.createElement('style')
style.textContent = `
@keyframes fadeInOverlay { from { opacity: 0 } to { opacity: 1 } }
@keyframes slideInCard { from { transform: translateY(-30px); opacity: 0 } to { transform: translateY(0); opacity: 1 } }
`
document.head.appendChild(style)
overlay.appendChild(card)
document.body.appendChild(overlay)
this.container = overlay
}
private startTimer(): void {
let remaining = 10
const progressBar = document.getElementById('weekly-progress-bar')
const timerText = document.getElementById('weekly-timer-text')
if (progressBar) {
// 使用 CSS transition 驱动进度条10秒内从100%到0%
requestAnimationFrame(() => {
progressBar.style.transition = 'width 10s linear'
progressBar.style.width = '0%'
})
}
this.timerInterval = setInterval(() => {
remaining--
if (timerText) timerText.textContent = String(remaining)
if (remaining <= 0) {
this.stopTimer()
this.handleAnswer(false, '')
}
}, 1000)
}
private stopTimer(): void {
if (this.timerInterval !== null) {
clearInterval(this.timerInterval)
this.timerInterval = null
}
}
private handleAnswer(isCorrect: boolean, correctWord: string): void {
this.stopTimer()
// 禁用所有按钮
const buttons = this.container?.querySelectorAll('button')
buttons?.forEach(btn => {
(btn as HTMLButtonElement).disabled = true
;(btn as HTMLButtonElement).style.opacity = '0.5'
;(btn as HTMLButtonElement).style.cursor = 'default'
})
const resultArea = document.getElementById('weekly-result')
if (!resultArea) return
if (isCorrect) {
// 随机给 HC 或补满精力
if (Math.random() > 0.5) {
const reward = 50 + Math.floor(Math.random() * 51)
GameManager.getInstance().addHC(reward)
resultArea.textContent = `✓ 正确!"${correctWord}" 就是本周黑话!获得 ${reward} HC`
resultArea.style.background = 'rgba(16,185,129,0.15)'
resultArea.style.border = '1px solid #10b981'
resultArea.style.color = '#6ee7b7'
} else {
this.callbacks.onFullStamina()
resultArea.textContent = `✓ 正确!"${correctWord}" 完美!全场精力已补满!`
resultArea.style.background = 'rgba(16,185,129,0.15)'
resultArea.style.border = '1px solid #10b981'
resultArea.style.color = '#6ee7b7'
}
} else {
this.callbacks.onBossInspection()
const msg = correctWord
? `✗ 答错了!正确答案是"${correctWord}"老板来视察了全场禁锢3秒`
: '✗ 超时了老板来视察了全场禁锢3秒'
resultArea.textContent = msg
resultArea.style.background = 'rgba(239,68,68,0.15)'
resultArea.style.border = '1px solid #ef4444'
resultArea.style.color = '#fca5a5'
}
resultArea.style.display = 'block'
// 2秒后自动关闭
setTimeout(() => {
this.close()
}, 2000)
}
private close(): void {
this.cleanup()
this.onClose?.()
}
private cleanup(): void {
this.stopTimer()
const existing = document.getElementById('weekly-report-overlay')
if (existing) existing.remove()
this.container = null
}
destroy(): void {
this.cleanup()
}
}