dvc890 commited on
Commit
945b7cf
·
verified ·
1 Parent(s): 9ca4753

Update pages/GameLucky.tsx

Browse files
Files changed (1) hide show
  1. pages/GameLucky.tsx +75 -67
pages/GameLucky.tsx CHANGED
@@ -2,25 +2,25 @@
2
  import React, { useState, useEffect } from 'react';
3
  import { api } from '../services/api';
4
  import { LuckyDrawConfig, Student, LuckyPrize } from '../types';
5
- import { Gift, Settings, Loader2, Save, Trash2, X, UserCircle } from 'lucide-react';
6
 
7
  const FlipCard = ({ index, prize, onFlip, isRevealed, activeIndex }: { index: number, prize: string, onFlip: (idx: number) => void, isRevealed: boolean, activeIndex: number | null }) => {
8
  const showBack = isRevealed && activeIndex === index;
9
 
10
  return (
11
- <div className="relative w-full h-full cursor-pointer perspective-1000 group" onClick={() => !isRevealed && onFlip(index)}>
12
  <div className={`relative w-full h-full text-center transition-transform duration-700 transform-style-3d shadow-md rounded-xl ${showBack ? 'rotate-y-180' : 'hover:scale-[1.02] active:scale-95'}`}>
13
  {/* Front */}
14
  <div className="absolute w-full h-full backface-hidden bg-gradient-to-br from-red-500 to-red-600 rounded-xl flex flex-col items-center justify-center border-4 border-yellow-400 shadow-inner">
15
- <div className="w-12 h-12 md:w-16 md:h-16 bg-yellow-100 rounded-full flex items-center justify-center mb-2 shadow-lg border-2 border-yellow-300">
16
- <span className="text-2xl md:text-3xl">🧧</span>
17
  </div>
18
- <span className="text-yellow-100 font-black text-xl md:text-2xl tracking-widest drop-shadow-md">開</span>
19
  </div>
20
  {/* Back */}
21
  <div className="absolute w-full h-full backface-hidden bg-white rounded-xl flex flex-col items-center justify-center border-4 border-red-200 rotate-y-180 shadow-inner p-2">
22
- <span className="text-4xl mb-2 animate-bounce">🎁</span>
23
- <span className="text-red-600 font-bold text-sm md:text-lg break-words leading-tight text-center px-1">{prize}</span>
24
  </div>
25
  </div>
26
  </div>
@@ -48,10 +48,9 @@ export const GameLucky: React.FC = () => {
48
 
49
  useEffect(() => {
50
  loadData();
51
- }, [proxyStudentId]); // Reload info if proxy changes
52
 
53
  const loadData = async () => {
54
- // Only set loading on initial mount, not on proxy switch
55
  if(!luckyConfig) setLoading(true);
56
  try {
57
  const config = await api.games.getLuckyConfig();
@@ -60,14 +59,12 @@ export const GameLucky: React.FC = () => {
60
  const allStus = await api.students.getAll();
61
 
62
  if (isTeacher) {
63
- // Load class students for proxy list
64
  if (currentUser.homeroomClass) {
65
  setStudents(allStus.filter((s: Student) => s.className === currentUser.homeroomClass));
66
  } else {
67
  setStudents(allStus);
68
  }
69
 
70
- // If proxy selected, load their attempts
71
  if (proxyStudentId) {
72
  const proxy = allStus.find((s: Student) => (s._id || String(s.id)) === proxyStudentId);
73
  setStudentInfo(proxy || null);
@@ -84,11 +81,9 @@ export const GameLucky: React.FC = () => {
84
 
85
  const handleDraw = async (index: number) => {
86
  if (isFlipping) return;
87
-
88
- // Determine who is drawing
89
  const targetId = isTeacher ? proxyStudentId : (studentInfo?._id || String(studentInfo?.id));
90
 
91
- if (!targetId) return alert(isTeacher ? '请先选择要代抽的学生' : '学生信息未加载');
92
  if (!studentInfo || (studentInfo.drawAttempts || 0) <= 0) return alert('抽奖次数不足!');
93
 
94
  setIsFlipping(true);
@@ -122,58 +117,15 @@ export const GameLucky: React.FC = () => {
122
  if (!luckyConfig) return <div className="h-full flex items-center justify-center text-gray-400">配置加载失败</div>;
123
 
124
  return (
125
- <div className="flex-1 flex flex-col h-full overflow-hidden bg-gradient-to-b from-red-50 to-white relative">
126
- {/* Teacher Controls (Overlay or Top Bar) */}
127
- {isTeacher && (
128
- <div className="shrink-0 p-4 bg-white border-b border-gray-100 flex items-center justify-between shadow-sm z-20">
129
- <div className="flex items-center gap-2">
130
- <UserCircle className="text-blue-600" size={20}/>
131
- <span className="text-sm font-bold text-gray-600">代抽模式:</span>
132
- <select
133
- className="border border-gray-300 rounded-lg py-1.5 px-3 text-sm focus:ring-2 focus:ring-blue-500 outline-none bg-gray-50"
134
- value={proxyStudentId}
135
- onChange={e => setProxyStudentId(e.target.value)}
136
- >
137
- <option value="">-- 选择学生 --</option>
138
- {students.map(s => <option key={s._id} value={s._id || String(s.id)}>{s.name} (剩余: {s.drawAttempts||0})</option>)}
139
- </select>
140
- </div>
141
- <button onClick={() => setIsSettingsOpen(true)} className="flex items-center text-xs font-bold text-gray-600 bg-gray-100 px-3 py-2 rounded-lg hover:bg-gray-200 transition-colors">
142
- <Settings size={16} className="mr-1"/> 奖池配置
143
- </button>
144
- </div>
145
- )}
146
-
147
- <div className="flex-1 flex flex-col items-center justify-center p-4 md:p-6 min-h-0 relative">
148
- {/* Header / Counter */}
149
- <div className="shrink-0 mb-6 relative w-full max-w-lg">
150
- <div className="bg-gradient-to-r from-yellow-100 to-amber-100 border border-yellow-200 rounded-2xl p-4 shadow-sm flex items-center justify-between">
151
- <div className="flex items-center gap-3">
152
- <div className="bg-white p-2 rounded-full shadow-inner"><Gift className="text-red-500" size={24}/></div>
153
- <div>
154
- <h3 className="font-bold text-yellow-900 text-lg leading-tight">
155
- {isTeacher && proxyStudentId ? `正在为 ${studentInfo?.name} 抽奖` : (isTeacher ? '请先选择学生' : '我的幸运抽奖')}
156
- </h3>
157
- <p className="text-xs text-yellow-700 opacity-80">每日限 {luckyConfig.dailyLimit} 次</p>
158
- </div>
159
- </div>
160
- <div className="text-right">
161
- <span className="block text-xs text-yellow-800 font-bold uppercase tracking-wider">剩余次数</span>
162
- <span className={`block text-3xl font-black ${studentInfo?.drawAttempts ? 'text-red-600' : 'text-gray-400'}`}>
163
- {studentInfo?.drawAttempts || 0}
164
- </span>
165
- </div>
166
- </div>
167
- </div>
168
-
169
- {/* Responsive Grid */}
170
- <div className={`w-full max-w-4xl flex-1 grid gap-3 md:gap-6 min-h-0 pb-4 ${
171
- (luckyConfig.cardCount || 9) <= 4 ? 'grid-cols-2' :
172
- (luckyConfig.cardCount || 9) <= 6 ? 'grid-cols-2 md:grid-cols-3' :
173
- (luckyConfig.cardCount || 9) <= 9 ? 'grid-cols-3' :
174
- 'grid-cols-3 md:grid-cols-4'
175
- }`} style={{gridAutoRows: '1fr'}}>
176
- {/* auto-rows-fr is key for filling height evenly */}
177
  {Array.from({ length: luckyConfig.cardCount || 9 }).map((_, i) => (
178
  <FlipCard
179
  key={i}
@@ -187,6 +139,62 @@ export const GameLucky: React.FC = () => {
187
  </div>
188
  </div>
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  {/* SETTINGS MODAL */}
191
  {isSettingsOpen && (
192
  <div className="fixed inset-0 bg-black/60 z-[100] flex items-center justify-center p-4 backdrop-blur-sm">
@@ -204,7 +212,7 @@ export const GameLucky: React.FC = () => {
204
  </div>
205
  <div className="bg-white p-4 rounded-xl border shadow-sm">
206
  <label className="text-xs font-bold text-gray-500 uppercase block mb-2">红包显示数量</label>
207
- <input type="number" min={4} max={12} className="w-full border rounded-lg px-3 py-2 font-bold text-lg text-center focus:ring-2 focus:ring-blue-500 outline-none" value={luckyConfig.cardCount || 9} onChange={e => setLuckyConfig({...luckyConfig, cardCount: Number(e.target.value)})}/>
208
  </div>
209
  <div className="bg-white p-4 rounded-xl border shadow-sm">
210
  <label className="text-xs font-bold text-gray-500 uppercase block mb-2">安慰奖文案</label>
 
2
  import React, { useState, useEffect } from 'react';
3
  import { api } from '../services/api';
4
  import { LuckyDrawConfig, Student, LuckyPrize } from '../types';
5
+ import { Gift, Settings, Loader2, Save, Trash2, X, UserCircle, RefreshCcw } from 'lucide-react';
6
 
7
  const FlipCard = ({ index, prize, onFlip, isRevealed, activeIndex }: { index: number, prize: string, onFlip: (idx: number) => void, isRevealed: boolean, activeIndex: number | null }) => {
8
  const showBack = isRevealed && activeIndex === index;
9
 
10
  return (
11
+ <div className="relative w-full aspect-[3/4] cursor-pointer perspective-1000 group" onClick={() => !isRevealed && onFlip(index)}>
12
  <div className={`relative w-full h-full text-center transition-transform duration-700 transform-style-3d shadow-md rounded-xl ${showBack ? 'rotate-y-180' : 'hover:scale-[1.02] active:scale-95'}`}>
13
  {/* Front */}
14
  <div className="absolute w-full h-full backface-hidden bg-gradient-to-br from-red-500 to-red-600 rounded-xl flex flex-col items-center justify-center border-4 border-yellow-400 shadow-inner">
15
+ <div className="w-10 h-10 md:w-14 md:h-14 bg-yellow-100 rounded-full flex items-center justify-center mb-2 shadow-lg border-2 border-yellow-300">
16
+ <span className="text-xl md:text-2xl">🧧</span>
17
  </div>
18
+ <span className="text-yellow-100 font-black text-lg md:text-xl tracking-widest drop-shadow-md">開</span>
19
  </div>
20
  {/* Back */}
21
  <div className="absolute w-full h-full backface-hidden bg-white rounded-xl flex flex-col items-center justify-center border-4 border-red-200 rotate-y-180 shadow-inner p-2">
22
+ <span className="text-3xl mb-2 animate-bounce">🎁</span>
23
+ <span className="text-red-600 font-bold text-xs md:text-sm break-words leading-tight text-center px-1">{prize}</span>
24
  </div>
25
  </div>
26
  </div>
 
48
 
49
  useEffect(() => {
50
  loadData();
51
+ }, [proxyStudentId]);
52
 
53
  const loadData = async () => {
 
54
  if(!luckyConfig) setLoading(true);
55
  try {
56
  const config = await api.games.getLuckyConfig();
 
59
  const allStus = await api.students.getAll();
60
 
61
  if (isTeacher) {
 
62
  if (currentUser.homeroomClass) {
63
  setStudents(allStus.filter((s: Student) => s.className === currentUser.homeroomClass));
64
  } else {
65
  setStudents(allStus);
66
  }
67
 
 
68
  if (proxyStudentId) {
69
  const proxy = allStus.find((s: Student) => (s._id || String(s.id)) === proxyStudentId);
70
  setStudentInfo(proxy || null);
 
81
 
82
  const handleDraw = async (index: number) => {
83
  if (isFlipping) return;
 
 
84
  const targetId = isTeacher ? proxyStudentId : (studentInfo?._id || String(studentInfo?.id));
85
 
86
+ if (!targetId) return alert(isTeacher ? '请先在右侧选择要代抽的学生' : '学生信息未加载');
87
  if (!studentInfo || (studentInfo.drawAttempts || 0) <= 0) return alert('抽奖次数不足!');
88
 
89
  setIsFlipping(true);
 
117
  if (!luckyConfig) return <div className="h-full flex items-center justify-center text-gray-400">配置加载失败</div>;
118
 
119
  return (
120
+ <div className="flex-1 flex flex-col md:flex-row h-full overflow-hidden bg-gradient-to-br from-red-50 to-orange-50 relative">
121
+
122
+ {/* LEFT: Game Area (Scrollable) */}
123
+ <div className="flex-1 overflow-y-auto p-4 md:p-8 custom-scrollbar">
124
+ <div className={`grid gap-4 md:gap-6 w-full max-w-5xl mx-auto ${
125
+ (luckyConfig.cardCount || 9) <= 4 ? 'grid-cols-2 md:grid-cols-3' :
126
+ (luckyConfig.cardCount || 9) <= 6 ? 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4' :
127
+ 'grid-cols-3 md:grid-cols-4 lg:grid-cols-5'
128
+ }`}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  {Array.from({ length: luckyConfig.cardCount || 9 }).map((_, i) => (
130
  <FlipCard
131
  key={i}
 
139
  </div>
140
  </div>
141
 
142
+ {/* RIGHT: Sidebar Controls */}
143
+ <div className="w-full md:w-80 bg-white border-l border-gray-200 shadow-xl flex flex-col shrink-0 z-20">
144
+ <div className="p-6 border-b border-gray-100 bg-red-600 text-white relative overflow-hidden">
145
+ <div className="relative z-10">
146
+ <h3 className="text-xl font-bold flex items-center"><Gift className="mr-2"/> 幸运抽奖</h3>
147
+ <p className="text-red-100 text-xs mt-1">好运连连,惊喜不断</p>
148
+ </div>
149
+ <div className="absolute -right-4 -bottom-4 text-red-700 opacity-30"><Gift size={80}/></div>
150
+ </div>
151
+
152
+ <div className="p-6 flex-1 overflow-y-auto space-y-6">
153
+ {/* Stats Card */}
154
+ <div className="bg-gradient-to-r from-amber-50 to-orange-50 p-5 rounded-xl border border-orange-100 text-center">
155
+ <p className="text-xs font-bold text-amber-700 uppercase tracking-wider mb-2">当前抽奖人</p>
156
+ <div className="text-lg font-bold text-gray-800 truncate mb-4">
157
+ {isTeacher && proxyStudentId ? (studentInfo?.name || '加载中...') : (isTeacher ? '未选择' : '我')}
158
+ </div>
159
+
160
+ <div className="border-t border-orange-200 pt-4">
161
+ <p className="text-xs font-bold text-amber-700 uppercase tracking-wider mb-1">剩余次数</p>
162
+ <p className={`text-4xl font-black ${studentInfo?.drawAttempts ? 'text-red-600' : 'text-gray-400'}`}>
163
+ {studentInfo?.drawAttempts || 0}
164
+ </p>
165
+ </div>
166
+ </div>
167
+
168
+ {/* Teacher Controls */}
169
+ {isTeacher && (
170
+ <div className="space-y-4">
171
+ <div>
172
+ <label className="text-xs font-bold text-gray-500 uppercase block mb-1">选择代抽学生</label>
173
+ <div className="relative">
174
+ <UserCircle className="absolute left-3 top-2.5 text-gray-400" size={18}/>
175
+ <select
176
+ className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-500 bg-white"
177
+ value={proxyStudentId}
178
+ onChange={e => setProxyStudentId(e.target.value)}
179
+ >
180
+ <option value="">-- 请选择 --</option>
181
+ {students.map(s => <option key={s._id} value={s._id || String(s.id)}>{s.name} ({s.studentNo})</option>)}
182
+ </select>
183
+ </div>
184
+ </div>
185
+
186
+ <button onClick={() => setIsSettingsOpen(true)} className="w-full flex items-center justify-center gap-2 py-3 bg-gray-100 text-gray-700 rounded-xl font-bold hover:bg-gray-200 transition-colors">
187
+ <Settings size={18}/> 奖池配置
188
+ </button>
189
+ </div>
190
+ )}
191
+
192
+ <button onClick={loadData} className="w-full flex items-center justify-center gap-2 py-2 text-sm text-gray-400 hover:text-blue-600 transition-colors">
193
+ <RefreshCcw size={14}/> 刷新数据
194
+ </button>
195
+ </div>
196
+ </div>
197
+
198
  {/* SETTINGS MODAL */}
199
  {isSettingsOpen && (
200
  <div className="fixed inset-0 bg-black/60 z-[100] flex items-center justify-center p-4 backdrop-blur-sm">
 
212
  </div>
213
  <div className="bg-white p-4 rounded-xl border shadow-sm">
214
  <label className="text-xs font-bold text-gray-500 uppercase block mb-2">红包显示数量</label>
215
+ <input type="number" min={4} max={20} className="w-full border rounded-lg px-3 py-2 font-bold text-lg text-center focus:ring-2 focus:ring-blue-500 outline-none" value={luckyConfig.cardCount || 9} onChange={e => setLuckyConfig({...luckyConfig, cardCount: Number(e.target.value)})}/>
216
  </div>
217
  <div className="bg-white p-4 rounded-xl border shadow-sm">
218
  <label className="text-xs font-bold text-gray-500 uppercase block mb-2">安慰奖文案</label>