feat(game/ui): 实现周报结算弹窗,包含10秒倒计时、黑话答题和奖励/惩罚逻辑
This commit is contained in:
285
game/ui/WeeklyReportModal.ts
Normal file
285
game/ui/WeeklyReportModal.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user