dvc890 commited on
Commit
bf4b4d1
·
verified ·
1 Parent(s): d11f72c

Upload 24 files

Browse files
Files changed (2) hide show
  1. pages/GameMonster.tsx +7 -3
  2. pages/GameRandom.tsx +51 -40
pages/GameMonster.tsx CHANGED
@@ -2,7 +2,7 @@
2
  import React, { useState, useEffect, useRef } from 'react';
3
  import { api } from '../services/api';
4
  import { StudentReward } from '../types';
5
- import { Play, Pause, Settings, Volume2, Mic, RotateCcw, Award, Keyboard, AlertTriangle } from 'lucide-react';
6
 
7
  export const GameMonster: React.FC = () => {
8
  // Game State
@@ -12,6 +12,7 @@ export const GameMonster: React.FC = () => {
12
  const [monsterPos, setMonsterPos] = useState(100); // 0 (Left/Loss) to 100 (Right/Start)
13
  const [currentVolume, setCurrentVolume] = useState(0);
14
  const [attackCount, setAttackCount] = useState(0);
 
15
 
16
  // Config State
17
  const [duration, setDuration] = useState(300);
@@ -181,7 +182,7 @@ export const GameMonster: React.FC = () => {
181
  };
182
 
183
  return (
184
- <div className="h-full flex flex-col bg-slate-900 relative overflow-hidden select-none" onClick={handleManualInput}>
185
  {/* Background */}
186
  <div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1519074069444-1ba4fff66d16?q=80&w=2544&auto=format&fit=crop')] bg-cover bg-center opacity-30"></div>
187
 
@@ -205,6 +206,9 @@ export const GameMonster: React.FC = () => {
205
  </div>
206
 
207
  <div className="flex gap-2">
 
 
 
208
  <button onClick={() => setIsConfigOpen(true)} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors"><Settings size={20}/></button>
209
  <button onClick={() => setIsPlaying(!isPlaying)} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors">
210
  {isPlaying ? <Pause size={20}/> : <Play size={20}/>}
@@ -303,7 +307,7 @@ export const GameMonster: React.FC = () => {
303
 
304
  {/* Result Modal */}
305
  {gameState !== 'IDLE' && gameState !== 'PLAYING' && (
306
- <div className="absolute inset-0 bg-black/80 z-50 flex items-center justify-center p-4 backdrop-blur-md animate-in zoom-in">
307
  <div className="text-center">
308
  <div className="text-9xl mb-4 animate-bounce">
309
  {gameState === 'VICTORY' ? '🏆' : '💀'}
 
2
  import React, { useState, useEffect, useRef } from 'react';
3
  import { api } from '../services/api';
4
  import { StudentReward } from '../types';
5
+ import { Play, Pause, Settings, Volume2, Mic, RotateCcw, Award, Keyboard, AlertTriangle, Maximize, Minimize } from 'lucide-react';
6
 
7
  export const GameMonster: React.FC = () => {
8
  // Game State
 
12
  const [monsterPos, setMonsterPos] = useState(100); // 0 (Left/Loss) to 100 (Right/Start)
13
  const [currentVolume, setCurrentVolume] = useState(0);
14
  const [attackCount, setAttackCount] = useState(0);
15
+ const [isFullscreen, setIsFullscreen] = useState(false);
16
 
17
  // Config State
18
  const [duration, setDuration] = useState(300);
 
182
  };
183
 
184
  return (
185
+ <div className={`${isFullscreen ? 'fixed inset-0 z-[100] h-screen w-screen' : 'h-full'} flex flex-col bg-slate-900 relative overflow-hidden select-none transition-all duration-300`} onClick={handleManualInput}>
186
  {/* Background */}
187
  <div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1519074069444-1ba4fff66d16?q=80&w=2544&auto=format&fit=crop')] bg-cover bg-center opacity-30"></div>
188
 
 
206
  </div>
207
 
208
  <div className="flex gap-2">
209
+ <button onClick={() => setIsFullscreen(!isFullscreen)} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors" title={isFullscreen?"退出全屏":"全屏显示"}>
210
+ {isFullscreen ? <Minimize size={20}/> : <Maximize size={20}/>}
211
+ </button>
212
  <button onClick={() => setIsConfigOpen(true)} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors"><Settings size={20}/></button>
213
  <button onClick={() => setIsPlaying(!isPlaying)} className="p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors">
214
  {isPlaying ? <Pause size={20}/> : <Play size={20}/>}
 
307
 
308
  {/* Result Modal */}
309
  {gameState !== 'IDLE' && gameState !== 'PLAYING' && (
310
+ <div className="absolute inset-0 bg-black/80 z-[110] flex items-center justify-center p-4 backdrop-blur-md animate-in zoom-in">
311
  <div className="text-center">
312
  <div className="text-9xl mb-4 animate-bounce">
313
  {gameState === 'VICTORY' ? '🏆' : '💀'}
pages/GameRandom.tsx CHANGED
@@ -2,7 +2,7 @@
2
  import React, { useState, useEffect, useRef } from 'react';
3
  import { api } from '../services/api';
4
  import { Student, GameSession, GameTeam, AchievementConfig } from '../types';
5
- import { Loader2, User, Users, Play, Pause, Gift, CheckCircle, XCircle, Award, Volume2, Settings } from 'lucide-react';
6
 
7
  export const GameRandom: React.FC = () => {
8
  const [loading, setLoading] = useState(true);
@@ -17,6 +17,7 @@ export const GameRandom: React.FC = () => {
17
  const [highlightIndex, setHighlightIndex] = useState<number | null>(null);
18
  const [selectedResult, setSelectedResult] = useState<Student | GameTeam | null>(null);
19
  const [showResultModal, setShowResultModal] = useState(false);
 
20
 
21
  // Reward State
22
  const [rewardType, setRewardType] = useState<'DRAW_COUNT' | 'ITEM' | 'ACHIEVEMENT'>('DRAW_COUNT');
@@ -162,49 +163,59 @@ export const GameRandom: React.FC = () => {
162
  const targetList = getTargetList();
163
 
164
  return (
165
- <div className="h-full flex flex-col bg-slate-50 relative overflow-hidden">
166
  {/* Header Config */}
167
  <div className="bg-white p-4 shadow-sm border-b border-gray-200 z-10 flex flex-wrap gap-4 items-center justify-between shrink-0">
168
- <div className="flex gap-2 bg-gray-100 p-1 rounded-lg">
169
- <button onClick={()=>{setMode('STUDENT'); setHighlightIndex(null);}} className={`px-4 py-2 rounded-md text-sm font-bold transition-all ${mode==='STUDENT'?'bg-white shadow text-blue-600':'text-gray-500'}`}>
170
- <User size={16} className="inline mr-1"/> 点名学生
171
- </button>
172
- <button onClick={()=>{setMode('TEAM'); setHighlightIndex(null);}} className={`px-4 py-2 rounded-md text-sm font-bold transition-all ${mode==='TEAM'?'bg-white shadow text-purple-600':'text-gray-500'}`}>
173
- <Users size={16} className="inline mr-1"/> 随机小组
174
- </button>
175
- </div>
 
176
 
177
- {mode === 'STUDENT' && (
178
- <select className="border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white" value={scopeTeamId} onChange={e=>setScopeTeamId(e.target.value)}>
179
- <option value="ALL">全班范围</option>
180
- {teams.map(t => <option key={t.id} value={t.id}>{t.name} (组)</option>)}
181
- </select>
182
- )}
183
 
184
- <div className="flex items-center gap-2 bg-amber-50 px-3 py-2 rounded-lg border border-amber-100">
185
- <Gift size={16} className="text-amber-500"/>
186
- <label className="text-sm font-bold text-gray-700">奖励设置:</label>
187
- <select className="text-sm border-gray-300 rounded bg-white" value={rewardType} onChange={e=>setRewardType(e.target.value as any)}>
188
- <option value="DRAW_COUNT">抽奖券</option>
189
- <option value="ITEM">实物</option>
190
- <option value="ACHIEVEMENT">成就</option>
191
- </select>
192
- {rewardType === 'ACHIEVEMENT' ? (
193
- <select className="text-sm border-gray-300 rounded w-32 bg-white" value={rewardId} onChange={e=>setRewardId(e.target.value)}>
194
- <option value="">选择成就</option>
195
- {achConfig?.achievements.map(a => <option key={a.id} value={a.id}>{a.icon} {a.name}</option>)}
196
  </select>
197
- ) : rewardType === 'ITEM' ? (
198
- <input className="text-sm border-gray-300 rounded w-24 p-1" placeholder="奖品名" value={rewardId} onChange={e=>setRewardId(e.target.value)}/>
199
- ) : null}
200
- <span className="text-xs text-gray-400">x</span>
201
- <input type="number" min={1} className="w-12 text-sm border-gray-300 rounded p-1 text-center" value={rewardCount} onChange={e=>setRewardCount(Number(e.target.value))}/>
 
 
 
 
 
 
202
  </div>
 
 
 
 
 
 
 
 
203
  </div>
204
 
205
  {/* Grid Area */}
206
  <div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
207
- <div className={`grid gap-4 transition-all ${mode==='STUDENT' ? 'grid-cols-4 sm:grid-cols-6 md:grid-cols-8' : 'grid-cols-2 md:grid-cols-3'}`}>
208
  {targetList.map((item, idx) => {
209
  const isHighlighted = idx === highlightIndex;
210
  const isTeam = mode === 'TEAM';
@@ -224,14 +235,14 @@ export const GameRandom: React.FC = () => {
224
  : 'bg-white border-gray-100 shadow-sm opacity-80'
225
  }`}>
226
  {isHighlighted && <div className="absolute inset-0 bg-yellow-400 opacity-20 animate-pulse rounded-xl"></div>}
227
- <div className="text-3xl mb-2 font-bold" style={{color: isTeam ? color : '#64748b'}}>
228
  {mode === 'STUDENT' ? (
229
- <div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 text-lg">
230
  {name[0]}
231
  </div>
232
  ) : avatar}
233
  </div>
234
- <div className="font-bold text-gray-800 text-center truncate w-full px-2">{name}</div>
235
  <div className="text-xs text-gray-400">{subText}</div>
236
  </div>
237
  );
@@ -251,14 +262,14 @@ export const GameRandom: React.FC = () => {
251
 
252
  {/* Result Modal */}
253
  {showResultModal && selectedResult && (
254
- <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm animate-in fade-in">
255
  <div className="bg-white rounded-3xl p-8 w-full max-w-md text-center shadow-2xl relative animate-in zoom-in-95">
256
  <div className="absolute -top-12 left-1/2 -translate-x-1/2 w-24 h-24 bg-yellow-400 rounded-full flex items-center justify-center border-4 border-white shadow-lg">
257
  <span className="text-5xl">✨</span>
258
  </div>
259
 
260
- <h2 className="mt-10 text-gray-500 text-sm font-bold uppercase tracking-widest">Selected {mode === 'STUDENT' ? 'Student' : 'Team'}</h2>
261
- <div className="text-4xl font-black text-gray-800 my-4">
262
  {/* @ts-ignore */}
263
  {selectedResult.name}
264
  </div>
 
2
  import React, { useState, useEffect, useRef } from 'react';
3
  import { api } from '../services/api';
4
  import { Student, GameSession, GameTeam, AchievementConfig } from '../types';
5
+ import { Loader2, User, Users, Play, Pause, Gift, CheckCircle, XCircle, Award, Volume2, Settings, Maximize, Minimize } from 'lucide-react';
6
 
7
  export const GameRandom: React.FC = () => {
8
  const [loading, setLoading] = useState(true);
 
17
  const [highlightIndex, setHighlightIndex] = useState<number | null>(null);
18
  const [selectedResult, setSelectedResult] = useState<Student | GameTeam | null>(null);
19
  const [showResultModal, setShowResultModal] = useState(false);
20
+ const [isFullscreen, setIsFullscreen] = useState(false);
21
 
22
  // Reward State
23
  const [rewardType, setRewardType] = useState<'DRAW_COUNT' | 'ITEM' | 'ACHIEVEMENT'>('DRAW_COUNT');
 
163
  const targetList = getTargetList();
164
 
165
  return (
166
+ <div className={`${isFullscreen ? 'fixed inset-0 z-[100] h-screen w-screen' : 'h-full'} flex flex-col bg-slate-50 relative overflow-hidden transition-all duration-300`}>
167
  {/* Header Config */}
168
  <div className="bg-white p-4 shadow-sm border-b border-gray-200 z-10 flex flex-wrap gap-4 items-center justify-between shrink-0">
169
+ <div className="flex flex-wrap gap-4 items-center">
170
+ <div className="flex gap-2 bg-gray-100 p-1 rounded-lg">
171
+ <button onClick={()=>{setMode('STUDENT'); setHighlightIndex(null);}} className={`px-4 py-2 rounded-md text-sm font-bold transition-all ${mode==='STUDENT'?'bg-white shadow text-blue-600':'text-gray-500'}`}>
172
+ <User size={16} className="inline mr-1"/> 点名学生
173
+ </button>
174
+ <button onClick={()=>{setMode('TEAM'); setHighlightIndex(null);}} className={`px-4 py-2 rounded-md text-sm font-bold transition-all ${mode==='TEAM'?'bg-white shadow text-purple-600':'text-gray-500'}`}>
175
+ <Users size={16} className="inline mr-1"/> 随机小组
176
+ </button>
177
+ </div>
178
 
179
+ {mode === 'STUDENT' && (
180
+ <select className="border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white" value={scopeTeamId} onChange={e=>setScopeTeamId(e.target.value)}>
181
+ <option value="ALL">全班范围</option>
182
+ {teams.map(t => <option key={t.id} value={t.id}>{t.name} (组)</option>)}
183
+ </select>
184
+ )}
185
 
186
+ <div className="flex items-center gap-2 bg-amber-50 px-3 py-2 rounded-lg border border-amber-100">
187
+ <Gift size={16} className="text-amber-500"/>
188
+ <label className="text-sm font-bold text-gray-700">奖励:</label>
189
+ <select className="text-sm border-gray-300 rounded bg-white" value={rewardType} onChange={e=>setRewardType(e.target.value as any)}>
190
+ <option value="DRAW_COUNT">抽奖券</option>
191
+ <option value="ITEM">实物</option>
192
+ <option value="ACHIEVEMENT">成就</option>
 
 
 
 
 
193
  </select>
194
+ {rewardType === 'ACHIEVEMENT' ? (
195
+ <select className="text-sm border-gray-300 rounded w-32 bg-white" value={rewardId} onChange={e=>setRewardId(e.target.value)}>
196
+ <option value="">选择成就</option>
197
+ {achConfig?.achievements.map(a => <option key={a.id} value={a.id}>{a.icon} {a.name}</option>)}
198
+ </select>
199
+ ) : rewardType === 'ITEM' ? (
200
+ <input className="text-sm border-gray-300 rounded w-24 p-1" placeholder="奖品名" value={rewardId} onChange={e=>setRewardId(e.target.value)}/>
201
+ ) : null}
202
+ <span className="text-xs text-gray-400">x</span>
203
+ <input type="number" min={1} className="w-12 text-sm border-gray-300 rounded p-1 text-center" value={rewardCount} onChange={e=>setRewardCount(Number(e.target.value))}/>
204
+ </div>
205
  </div>
206
+
207
+ <button
208
+ onClick={() => setIsFullscreen(!isFullscreen)}
209
+ className="p-2 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-lg transition-colors"
210
+ title={isFullscreen ? "退出全屏" : "全屏显示"}
211
+ >
212
+ {isFullscreen ? <Minimize size={20}/> : <Maximize size={20}/>}
213
+ </button>
214
  </div>
215
 
216
  {/* Grid Area */}
217
  <div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
218
+ <div className={`grid gap-4 transition-all ${mode==='STUDENT' ? 'grid-cols-4 sm:grid-cols-6 md:grid-cols-8 lg:grid-cols-10' : 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4'}`}>
219
  {targetList.map((item, idx) => {
220
  const isHighlighted = idx === highlightIndex;
221
  const isTeam = mode === 'TEAM';
 
235
  : 'bg-white border-gray-100 shadow-sm opacity-80'
236
  }`}>
237
  {isHighlighted && <div className="absolute inset-0 bg-yellow-400 opacity-20 animate-pulse rounded-xl"></div>}
238
+ <div className={`mb-2 font-bold ${isFullscreen ? 'text-5xl' : 'text-3xl'}`} style={{color: isTeam ? color : '#64748b'}}>
239
  {mode === 'STUDENT' ? (
240
+ <div className={`rounded-full bg-blue-100 flex items-center justify-center text-blue-600 ${isFullscreen ? 'w-20 h-20 text-3xl' : 'w-12 h-12 text-lg'}`}>
241
  {name[0]}
242
  </div>
243
  ) : avatar}
244
  </div>
245
+ <div className={`font-bold text-gray-800 text-center truncate w-full px-2 ${isFullscreen ? 'text-xl' : 'text-base'}`}>{name}</div>
246
  <div className="text-xs text-gray-400">{subText}</div>
247
  </div>
248
  );
 
262
 
263
  {/* Result Modal */}
264
  {showResultModal && selectedResult && (
265
+ <div className="fixed inset-0 bg-black/60 z-[110] flex items-center justify-center p-4 backdrop-blur-sm animate-in fade-in">
266
  <div className="bg-white rounded-3xl p-8 w-full max-w-md text-center shadow-2xl relative animate-in zoom-in-95">
267
  <div className="absolute -top-12 left-1/2 -translate-x-1/2 w-24 h-24 bg-yellow-400 rounded-full flex items-center justify-center border-4 border-white shadow-lg">
268
  <span className="text-5xl">✨</span>
269
  </div>
270
 
271
+ <h2 className="mt-10 text-gray-500 text-sm font-bold uppercase tracking-widest">选中对象</h2>
272
+ <div className="text-5xl font-black text-gray-800 my-6">
273
  {/* @ts-ignore */}
274
  {selectedResult.name}
275
  </div>