11import { useState , useEffect , useCallback } from 'react' ;
22import { useNavigate } from 'react-router-dom' ;
3- import { Plus , Edit , Trash2 , Users , Code , BarChart3 , AlertTriangle , Loader2 , Activity , Percent , Clock , Download , Upload } from 'lucide-react' ;
3+ import { Plus , Edit , Trash2 , Users , Shield , Code , BarChart3 , AlertTriangle , Loader2 , Activity , Percent , Clock , Download , Upload } from 'lucide-react' ;
44import toast from 'react-hot-toast' ;
55import { useAuth } from '../context/AuthContext' ;
66import api from '../services/api' ;
@@ -16,6 +16,8 @@ export default function Admin() {
1616 const [ loading , setLoading ] = useState ( true ) ;
1717 const [ error , setError ] = useState < string | null > ( null ) ;
1818 const [ deletingId , setDeletingId ] = useState < number | null > ( null ) ;
19+ const [ users , setUsers ] = useState < any [ ] > ( [ ] ) ;
20+ const [ usersLoading , setUsersLoading ] = useState ( false ) ;
1921
2022 const fetchData = useCallback ( async ( ) => {
2123 setLoading ( true ) ;
@@ -33,6 +35,10 @@ export default function Admin() {
3335 const adminRes = await api . problems . getAdminStats ( ) ;
3436 setAdminStats ( adminRes ) ;
3537 } catch { /* silently ignore */ }
38+ try {
39+ const userRes = await fetch ( '/api/auth/admin/users' , { headers : { Authorization : `Bearer ${ localStorage . getItem ( 'oj_token' ) } ` } } ) ;
40+ if ( userRes . ok ) setUsers ( ( await userRes . json ( ) ) . users ) ;
41+ } catch { }
3642 }
3743 } catch ( err : any ) {
3844 const msg = err . message || '加载数据失败' ;
@@ -93,6 +99,23 @@ export default function Admin() {
9399 }
94100 } ;
95101
102+ const handleDeleteUser = async ( userId : number , username : string ) => {
103+ if ( ! window . confirm ( `确定要删除用户「${ username } 」吗?此操作不可撤销。` ) ) return ;
104+ try {
105+ const res = await fetch ( `/api/auth/admin/users/${ userId } ` , {
106+ method : 'DELETE' ,
107+ headers : { Authorization : `Bearer ${ localStorage . getItem ( 'oj_token' ) } ` } ,
108+ } ) ;
109+ if ( res . ok ) {
110+ toast . success ( '用户已删除' ) ;
111+ setUsers ( prev => prev . filter ( u => u . id !== userId ) ) ;
112+ } else {
113+ const data = await res . json ( ) ;
114+ toast . error ( data . error || '删除失败' ) ;
115+ }
116+ } catch { toast . error ( '删除失败' ) ; }
117+ } ;
118+
96119 const TYPE_LABELS : Record < string , string > = {
97120 programming : '编程题' ,
98121 choice : '选择题' ,
@@ -213,6 +236,47 @@ export default function Admin() {
213236 </ div >
214237 ) }
215238
239+ { /* User Management */ }
240+ < div className = "card p-6 mb-6" >
241+ < h2 className = "text-lg font-semibold text-white mb-4 flex items-center gap-2" >
242+ < Users className = "w-5 h-5 text-blue-400" /> 用户管理
243+ </ h2 >
244+ < div className = "overflow-x-auto" >
245+ < table className = "w-full text-sm" >
246+ < thead >
247+ < tr className = "border-b border-dark-700" >
248+ < th className = "text-left py-2 px-3 text-dark-400" > ID</ th >
249+ < th className = "text-left py-2 px-3 text-dark-400" > 用户名</ th >
250+ < th className = "text-left py-2 px-3 text-dark-400 hidden md:table-cell" > 邮箱</ th >
251+ < th className = "text-center py-2 px-3 text-dark-400" > 角色</ th >
252+ < th className = "text-center py-2 px-3 text-dark-400 hidden md:table-cell" > 注册时间</ th >
253+ < th className = "text-center py-2 px-3 text-dark-400" > 操作</ th >
254+ </ tr >
255+ </ thead >
256+ < tbody >
257+ { users . map ( u => (
258+ < tr key = { u . id } className = "border-b border-dark-800" >
259+ < td className = "py-2 px-3 text-dark-400" > { u . id } </ td >
260+ < td className = "py-2 px-3 text-white" > { u . username } </ td >
261+ < td className = "py-2 px-3 text-dark-300 hidden md:table-cell" > { u . email } </ td >
262+ < td className = "py-2 px-3 text-center" >
263+ { u . role === 'admin' ? < Shield className = "w-4 h-4 text-primary-400 mx-auto" /> : < span className = "text-dark-400 text-xs" > user</ span > }
264+ </ td >
265+ < td className = "py-2 px-3 text-dark-400 text-center hidden md:table-cell" > { u . created_at ?. slice ( 0 , 10 ) } </ td >
266+ < td className = "py-2 px-3 text-center" >
267+ { u . role !== 'admin' && (
268+ < button onClick = { ( ) => handleDeleteUser ( u . id , u . username ) } className = "btn-danger text-xs px-2 py-1" >
269+ < Trash2 className = "w-3.5 h-3.5" />
270+ </ button >
271+ ) }
272+ </ td >
273+ </ tr >
274+ ) ) }
275+ </ tbody >
276+ </ table >
277+ </ div >
278+ </ div >
279+
216280 { /* Actions */ }
217281 < div className = "flex items-center justify-between mb-4" >
218282 < h2 className = "text-lg font-semibold text-white" > 题目管理</ h2 >
0 commit comments