Spaces:
Sleeping
Sleeping
Upload 62 files
Browse files- pages/UserList.tsx +32 -8
- types.ts +7 -1
pages/UserList.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import React, { useState, useEffect } from 'react';
|
| 3 |
import { User, UserRole, UserStatus, School, ClassInfo } from '../types';
|
| 4 |
import { api } from '../services/api';
|
| 5 |
-
import { Loader2, Check, X, Trash2, Edit, Briefcase, GraduationCap, AlertCircle, Bot } from 'lucide-react';
|
| 6 |
import { ConfirmModal } from '../components/ConfirmModal';
|
| 7 |
import { Toast, ToastState } from '../components/Toast';
|
| 8 |
|
|
@@ -44,14 +44,28 @@ export const UserList: React.FC = () => {
|
|
| 44 |
useEffect(() => { loadData(); }, []);
|
| 45 |
|
| 46 |
const handleApprove = async (user: User) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
setConfirmModal({
|
| 48 |
isOpen: true,
|
| 49 |
title: '批准注册',
|
| 50 |
-
message:
|
| 51 |
onConfirm: async () => {
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
});
|
| 57 |
};
|
|
@@ -249,9 +263,19 @@ export const UserList: React.FC = () => {
|
|
| 249 |
{schools.map(s => <option key={s._id} value={s._id}>{s.name}</option>)}
|
| 250 |
</select>
|
| 251 |
) : (
|
| 252 |
-
<div className={`flex
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
</div>
|
| 256 |
)}
|
| 257 |
</td>
|
|
|
|
| 2 |
import React, { useState, useEffect } from 'react';
|
| 3 |
import { User, UserRole, UserStatus, School, ClassInfo } from '../types';
|
| 4 |
import { api } from '../services/api';
|
| 5 |
+
import { Loader2, Check, X, Trash2, Edit, Briefcase, GraduationCap, AlertCircle, Bot, Building } from 'lucide-react';
|
| 6 |
import { ConfirmModal } from '../components/ConfirmModal';
|
| 7 |
import { Toast, ToastState } from '../components/Toast';
|
| 8 |
|
|
|
|
| 44 |
useEffect(() => { loadData(); }, []);
|
| 45 |
|
| 46 |
const handleApprove = async (user: User) => {
|
| 47 |
+
// If pending school creation, notify admin
|
| 48 |
+
let confirmMsg = `确认批准 ${user.trueName || user.username} 的注册申请?`;
|
| 49 |
+
if (user.pendingSchoolData) {
|
| 50 |
+
confirmMsg = `⚠️ 重要:该用户申请创建新学校 [${user.pendingSchoolData.name}]。\n\n批准后,系统将自动创建该学校并将此账号设为校长。\n确定要批准吗?`;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
setConfirmModal({
|
| 54 |
isOpen: true,
|
| 55 |
title: '批准注册',
|
| 56 |
+
message: confirmMsg,
|
| 57 |
onConfirm: async () => {
|
| 58 |
+
try {
|
| 59 |
+
await api.users.update(user._id || String(user.id), { status: UserStatus.ACTIVE });
|
| 60 |
+
setToast({ show: true, message: '已批准', type: 'success' });
|
| 61 |
+
loadData();
|
| 62 |
+
// If created school, force refresh school list event for header
|
| 63 |
+
if (user.pendingSchoolData) {
|
| 64 |
+
window.dispatchEvent(new Event('school-updated'));
|
| 65 |
+
}
|
| 66 |
+
} catch (e: any) {
|
| 67 |
+
setToast({ show: true, message: '操作失败: ' + (e.message || '未知错误'), type: 'error' });
|
| 68 |
+
}
|
| 69 |
}
|
| 70 |
});
|
| 71 |
};
|
|
|
|
| 263 |
{schools.map(s => <option key={s._id} value={s._id}>{s.name}</option>)}
|
| 264 |
</select>
|
| 265 |
) : (
|
| 266 |
+
<div className={`flex flex-col ${isAdmin && !isSelf ? 'cursor-pointer group' : ''}`} onClick={() => isAdmin && !isSelf && setEditingUser(user)}>
|
| 267 |
+
{user.pendingSchoolData ? (
|
| 268 |
+
<div className="bg-amber-50 p-2 rounded border border-amber-100">
|
| 269 |
+
<span className="text-[10px] bg-amber-200 text-amber-800 px-1.5 py-0.5 rounded w-fit mb-1 font-bold flex items-center gap-1"><Building size={10}/> 申请创建学校</span>
|
| 270 |
+
<div className="text-gray-800 font-bold text-xs">{user.pendingSchoolData.name}</div>
|
| 271 |
+
<div className="text-gray-400 text-[10px] font-mono">CODE: {user.pendingSchoolData.code}</div>
|
| 272 |
+
</div>
|
| 273 |
+
) : (
|
| 274 |
+
<div className="flex items-center space-x-1">
|
| 275 |
+
<span className="text-gray-700">{getSchoolName(user.schoolId)}</span>
|
| 276 |
+
{isAdmin && !isSelf && <Edit size={12} className="text-gray-300 opacity-0 group-hover:opacity-100"/>}
|
| 277 |
+
</div>
|
| 278 |
+
)}
|
| 279 |
</div>
|
| 280 |
)}
|
| 281 |
</td>
|
types.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface User {
|
|
| 35 |
targetClass?: string;
|
| 36 |
status: 'PENDING' | 'REJECTED';
|
| 37 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
studentNo?: string;
|
| 39 |
parentName?: string;
|
| 40 |
parentPhone?: string;
|
|
@@ -290,6 +295,7 @@ export interface ExchangeRule {
|
|
| 290 |
rewardType: 'DRAW_COUNT' | 'ITEM';
|
| 291 |
rewardName: string;
|
| 292 |
rewardValue: number;
|
|
|
|
| 293 |
}
|
| 294 |
|
| 295 |
export interface AchievementConfig {
|
|
@@ -382,4 +388,4 @@ export interface AIChatMessage {
|
|
| 382 |
audio?: string;
|
| 383 |
isAudioMessage?: boolean;
|
| 384 |
timestamp: number;
|
| 385 |
-
}
|
|
|
|
| 35 |
targetClass?: string;
|
| 36 |
status: 'PENDING' | 'REJECTED';
|
| 37 |
};
|
| 38 |
+
// For Principal creating a new school
|
| 39 |
+
pendingSchoolData?: {
|
| 40 |
+
name: string;
|
| 41 |
+
code: string;
|
| 42 |
+
};
|
| 43 |
studentNo?: string;
|
| 44 |
parentName?: string;
|
| 45 |
parentPhone?: string;
|
|
|
|
| 295 |
rewardType: 'DRAW_COUNT' | 'ITEM';
|
| 296 |
rewardName: string;
|
| 297 |
rewardValue: number;
|
| 298 |
+
achievementId?: string;
|
| 299 |
}
|
| 300 |
|
| 301 |
export interface AchievementConfig {
|
|
|
|
| 388 |
audio?: string;
|
| 389 |
isAudioMessage?: boolean;
|
| 390 |
timestamp: number;
|
| 391 |
+
}
|