jiachenlong/frontend/src.bak/pages/Admin.jsx

419 lines
18 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version'
export default function Admin() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [showAddModal, setShowAddModal] = useState(false)
const [editingUser, setEditingUser] = useState(null)
const [newUser, setNewUser] = useState({ username: '', password: '', email: '', role: 'user' })
const token = localStorage.getItem('token')
// 检查权限
const userStr = localStorage.getItem('user')
let user = null
try {
user = userStr ? JSON.parse(userStr) : null
} catch (e) {}
if (!user || user.role !== 'admin') {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#0f172a', color: '#ef4444', padding: '20px' }}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🚫</div>
<div style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '8px' }}>无权访问</div>
<div style={{ color: '#94a3b8', fontSize: '14px' }}>仅管理员可以访问用户管理界面</div>
</div>
</div>
)
}
useEffect(() => {
fetchUsers()
}, [])
const fetchUsers = async () => {
try {
const res = await fetch('/api/admin/users?page=1&limit=100', {
headers: { 'Authorization': `Bearer ${token}` }
})
if (!res.ok) throw new Error('获取用户列表失败')
const data = await res.json()
setUsers(data)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
const handleDeleteUser = async (userId, username) => {
if (!confirm(`确定要删除用户 "${username}" 吗?`)) return
try {
const res = await fetch(`/api/admin/users/${userId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
})
if (!res.ok) throw new Error('删除失败')
fetchUsers()
} catch (err) {
alert(err.message)
}
}
const handleAddUser = async () => {
if (!newUser.username || !newUser.password) {
alert('用户名和密码为必填项')
return
}
// 如果 email 为空,不传这个字段
const payload = { ...newUser }
if (!payload.email) {
delete payload.email
}
try {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.detail || '添加失败')
}
alert('添加成功!')
setShowAddModal(false)
setNewUser({ username: '', password: '', email: '', role: 'user' })
fetchUsers()
} catch (err) {
alert(err.message)
}
}
const handleEditUser = async () => {
if (!editingUser.username) {
alert('用户名不能为空')
return
}
// 验证密码
if (editingUser.newPassword || editingUser.confirmPassword) {
if (!editingUser.newPassword || !editingUser.confirmPassword) {
alert('请填写完整密码信息')
return
}
if (editingUser.newPassword !== editingUser.confirmPassword) {
alert('两次输入的密码不一致')
return
}
if (editingUser.newPassword.length < 6) {
alert('密码至少 6 个字符')
return
}
}
try {
// 准备更新数据
const updateData = {
username: editingUser.username,
email: editingUser.email,
role: editingUser.role
}
// 如果有新密码,添加到更新数据
if (editingUser.newPassword && editingUser.newPassword.trim()) {
updateData.password = editingUser.newPassword
}
const res = await fetch(`/api/admin/users/${editingUser.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(updateData)
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.detail || '更新失败')
}
alert('更新成功!')
setEditingUser(null)
fetchUsers()
} catch (err) {
alert(err.message)
}
}
if (loading) return <div style={{ padding: '20px', color: '#fff' }}>加载中...</div>
if (error) return <div style={{ padding: '20px', color: '#ef4444' }}> {error}</div>
return (
<div style={{ minHeight: '100vh', overflowY: 'auto', background: '#0f172a', padding: '20px', paddingBottom: '80px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<h1 style={{ color: '#fbbf24', fontSize: '24px', fontWeight: '700' }}>
用户管理
</h1>
<button
onClick={() => setShowAddModal(true)}
style={{
padding: '10px 20px',
background: '#fbbf24',
color: '#1e293b',
border: 'none',
borderRadius: '8px',
fontSize: '14px',
fontWeight: '600',
cursor: 'pointer'
}}
>
添加用户
</button>
</div>
{/* 用户列表 - 卡片式布局 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{users.map((user) => (
<div key={user.id} style={{ background: 'rgba(30, 41, 59, 0.8)', borderRadius: '12px', padding: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ color: '#fbbf24', fontSize: '16px', fontWeight: 'bold' }}>{user.username}</div>
<div style={{
padding: '2px 8px', borderRadius: '4px',
background: user.role === 'admin' ? 'rgba(16, 185, 129, 0.2)' : 'rgba(148, 163, 184, 0.2)',
color: user.role === 'admin' ? '#10b981' : '#94a3b8',
fontSize: '12px'
}}>
{user.role === 'admin' ? '👑 管理员' : '👤 用户'}
</div>
</div>
<div style={{ color: '#64748b', fontSize: '13px', marginTop: '4px' }}>
📧 {user.email || '未设置'} | 📱 {user.phone || '未设置'}
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ color: '#94a3b8', fontSize: '12px' }}>藏品数</div>
<div style={{ color: '#60a5fa', fontSize: '20px', fontWeight: 'bold', cursor: 'pointer' }} onClick={() => window.location.hash = '#/list?userId=' + user.id} title='点击查看'>{user.collection_count || 0}</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '12px', paddingTop: '12px', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
<div style={{ color: '#64748b', fontSize: '12px' }}>
注册时间{user.created_at ? new Date(user.created_at).toLocaleDateString('zh-CN') : '-'}
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={() => setEditingUser({ ...user })}
style={{
padding: '6px 12px',
borderRadius: '6px',
border: 'none',
background: 'rgba(59, 130, 246, 0.2)',
color: '#3b82f6',
cursor: 'pointer',
fontSize: '12px'
}}
>
编辑
</button>
{user.role !== 'admin' && (
<button
onClick={() => handleDeleteUser(user.id, user.username)}
style={{
padding: '6px 12px',
borderRadius: '6px',
border: 'none',
background: 'rgba(239, 68, 68, 0.2)',
color: '#ef4444',
cursor: 'pointer',
fontSize: '12px'
}}
>
🗑 删除
</button>
)}
</div>
</div>
</div>
))}
</div>
{users.length === 0 && (
<div style={{ padding: '40px', textAlign: 'center', color: '#94a3b8' }}>
暂无用户数据
</div>
)}
{/* 添加用户模态框 */}
{showAddModal && (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '24px', width: '90%', maxWidth: '400px' }}>
<h2 style={{ color: '#fff', fontSize: '18px', fontWeight: 'bold', marginBottom: '20px' }}>添加用户</h2>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>用户名 *</label>
<input
type="text"
value={newUser.username}
onChange={(e) => setNewUser({ ...newUser, username: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>密码 *</label>
<input
type="password"
value={newUser.password}
onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>邮箱</label>
<input
type="email"
value={newUser.email}
onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>角色</label>
<select
value={newUser.role}
onChange={(e) => setNewUser({ ...newUser, role: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
>
<option value="user">普通用户</option>
<option value="admin">管理员</option>
</select>
</div>
<div style={{ display: 'flex', gap: '12px' }}>
<button
onClick={() => setShowAddModal(false)}
style={{ flex: 1, padding: '12px', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '8px', cursor: 'pointer' }}
>
取消
</button>
<button
onClick={handleAddUser}
style={{ flex: 1, padding: '12px', background: '#fbbf24', color: '#1e293b', border: 'none', borderRadius: '8px', fontWeight: '600', cursor: 'pointer' }}
>
确定
</button>
</div>
</div>
</div>
)}
{/* 编辑用户模态框 */}
{editingUser && (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}>
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '24px', width: '90%', maxWidth: '500px', maxHeight: '90vh', overflowY: 'auto' }}>
<h2 style={{ color: '#fff', fontSize: '18px', fontWeight: 'bold', marginBottom: '20px' }}>编辑用户</h2>
{/* 第一部分:基本信息 */}
<div style={{ marginBottom: '24px' }}>
<h3 style={{ color: '#fbbf24', fontSize: '15px', fontWeight: 'bold', marginBottom: '16px', paddingBottom: '8px', borderBottom: '1px solid rgba(251, 191, 36, 0.3)' }}>📋 基本信息</h3>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>用户名</label>
<input
type="text"
value={editingUser.username}
onChange={(e) => setEditingUser({ ...editingUser, username: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>邮箱</label>
<input
type="email"
value={editingUser.email || ''}
onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>角色</label>
<select
value={editingUser.role}
onChange={(e) => setEditingUser({ ...editingUser, role: e.target.value })}
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
>
<option value="user">普通用户</option>
<option value="admin">管理员</option>
</select>
</div>
</div>
{/* 第二部分:修改密码 */}
<div style={{ marginBottom: '24px' }}>
<h3 style={{ color: '#fbbf24', fontSize: '15px', fontWeight: 'bold', marginBottom: '16px', paddingBottom: '8px', borderBottom: '1px solid rgba(251, 191, 36, 0.3)' }}>🔐 修改密码可选</h3>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>新密码 <span style={{ color: '#64748b', fontSize: '12px' }}>留空则不修改</span></label>
<input
type="password"
value={editingUser.newPassword || ''}
onChange={(e) => setEditingUser({ ...editingUser, newPassword: e.target.value })}
placeholder="请输入新密码(至少 6 位)"
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', color: '#94a3b8', fontSize: '13px', marginBottom: '6px' }}>确认新密码</label>
<input
type="password"
value={editingUser.confirmPassword || ''}
onChange={(e) => setEditingUser({ ...editingUser, confirmPassword: e.target.value })}
placeholder="请再次输入新密码"
style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
/>
</div>
</div>
<div style={{ display: 'flex', gap: '12px' }}>
<button
onClick={() => setEditingUser(null)}
style={{ flex: 1, padding: '12px', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius: '8px', cursor: 'pointer' }}
>
取消
</button>
<button
onClick={handleEditUser}
style={{ flex: 1, padding: '12px', background: '#fbbf24', color: '#1e293b', border: 'none', borderRadius: '8px', fontWeight: '600', cursor: 'pointer' }}
>
确定
</button>
</div>
</div>
</div>
)}
{/* 版本号 - 移到表格下方,避免被底部导航遮挡 */}
<div style={{ textAlign: 'center', padding: '20px', color: 'rgba(255,255,255,0.3)', fontSize: '12px' }}>
v{APP_VERSION}
</div>
</div>
)
}