2026-04-16 14:27:41 +08:00
|
|
|
|
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,
|
|
|
|
|
|
phone: editingUser.phone,
|
|
|
|
|
|
phoneVerified: editingUser.phoneVerified,
|
|
|
|
|
|
role: editingUser.role,
|
|
|
|
|
|
user_code: editingUser.user_code,
|
|
|
|
|
|
level: editingUser.level,
|
|
|
|
|
|
loginCount: editingUser.loginCount,
|
|
|
|
|
|
aiCount: editingUser.aiCount,
|
|
|
|
|
|
searchCount: editingUser.searchCount,
|
|
|
|
|
|
points: editingUser.points,
|
|
|
|
|
|
balance: editingUser.balance,
|
|
|
|
|
|
totalAmount: editingUser.totalAmount,
|
|
|
|
|
|
phoneVerified: editingUser.phoneVerified
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有新密码,添加到更新数据
|
|
|
|
|
|
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: 'flex-start' }}>
|
|
|
|
|
|
<div style={{ flex: 1 }}>
|
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
|
|
|
|
|
<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)' : (user.role === 'editor' ? 'rgba(245, 158, 11, 0.2)' : 'rgba(148, 163, 184, 0.2)'),
|
|
|
|
|
|
color: user.role === 'admin' ? '#10b981' : (user.role === 'editor' ? '#f59e0b' : '#94a3b8'),
|
|
|
|
|
|
fontSize: '12px'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{user.role === 'admin' ? '👑 管理员' : (user.role === 'editor' ? '📝 信息员' : '👤 用户')}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(59, 130, 246, 0.2)', padding: '2px 8px', borderRadius: '4px', color: '#60a5fa', fontSize: '12px' }}>
|
|
|
|
|
|
编码: {user.user_code || '-'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(168, 85, 247, 0.2)', padding: '2px 8px', borderRadius: '4px', color: '#a855f7', fontSize: '12px' }}>
|
|
|
|
|
|
等级: {user.level || '青铜'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#64748b', fontSize: '13px', marginTop: '8px' }}>
|
|
|
|
|
|
📧 {user.email || '未设置'} | 📱 {user.phone || '未设置'} | 📱已认证: {user.phoneVerified ? '✓' : '✗'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ textAlign: 'right' }}>
|
2026-04-17 00:40:29 +08:00
|
|
|
|
<div style={{ color: '#94a3b8', fontSize: '12px' }}>藏品数 | 行情数</div>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', alignItems: 'center' }}>
|
|
|
|
|
|
<div style={{ color: '#60a5fa', fontSize: '20px', fontWeight: 'bold', cursor: 'pointer' }} onClick={() => window.location.hash = '#/list?userId=' + user.id} title='点击查看藏品'>{user.collectionCount || 0}</div>
|
|
|
|
|
|
<div style={{ color: '#22c55e', fontSize: '20px', fontWeight: 'bold', cursor: 'pointer' }} onClick={() => window.location.hash = '#/news?userId=' + user.id} title='点击查看行情'>{user.dealCount || 0}</div>
|
|
|
|
|
|
</div>
|
2026-04-16 14:27:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* 更多字段 */}
|
|
|
|
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginTop: '12px', paddingTop: '12px', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
|
|
|
|
|
|
<div style={{ background: 'rgba(16, 185, 129, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#10b981' }}>
|
|
|
|
|
|
🔑 登录: {user.loginCount || 0}次
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(59, 130, 246, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#3b82f6' }}>
|
|
|
|
|
|
🤖 AI识别: {user.aiCount || 0}次
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(20, 184, 166, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#14b8a6' }}>
|
|
|
|
|
|
🎯 配号: {user.searchCount || 0}次
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(245, 158, 11, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#f59e0b' }}>
|
|
|
|
|
|
🔍 寻号: {user.searchCount || 0}次
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(168, 85, 247, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#a855f7' }}>
|
|
|
|
|
|
💎 积分: {user.points || 0}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(236, 72, 153, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#ec4899' }}>
|
|
|
|
|
|
💰 余额: {user.balance || 0}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ background: 'rgba(34, 197, 94, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#22c55e' }}>
|
|
|
|
|
|
📊 累计: {user.totalAmount || 0}
|
|
|
|
|
|
</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, email: user.email || '', phone: user.phone || '', phoneVerified: user.phoneVerified !== false, newPassword: '', confirmPassword: '' })}
|
|
|
|
|
|
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>
|
|
|
|
|
|
<option value="editor">信息员</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>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={editingUser.phone || ''}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, phone: 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: 'flex', alignItems: 'center', color: '#94a3b8', fontSize: '13px', marginBottom: '6px', cursor: 'pointer' }}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={editingUser.phoneVerified === true}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, phoneVerified: e.target.checked })}
|
|
|
|
|
|
style={{ marginRight: '8px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
手机号已验证
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
<option value="editor">信息员</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={{ marginBottom: '24px' }}>
|
|
|
|
|
|
<h3 style={{ color: '#a855f7', fontSize: '15px', fontWeight: 'bold', marginBottom: '16px', paddingBottom: '8px', borderBottom: '1px solid rgba(168, 85, 247, 0.3)' }}>📊 扩展信息</h3>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>用户编码</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={editingUser.user_code || ''}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, user_code: e.target.value })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>会员等级</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={editingUser.level || '青铜'}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, level: e.target.value })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="青铜">青铜</option>
|
|
|
|
|
|
<option value="白银">白银</option>
|
|
|
|
|
|
<option value="黄金">黄金</option>
|
|
|
|
|
|
<option value="铂金">铂金</option>
|
|
|
|
|
|
<option value="钻石号">钻石号</option>
|
|
|
|
|
|
<option value="永恒号">永恒号</option>
|
|
|
|
|
|
<option value="带7号">带7号</option>
|
|
|
|
|
|
<option value="带4号">带4号</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>登录次数</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={editingUser.loginCount || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, loginCount: parseInt(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>AI识别次数</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={editingUser.aiCount || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, aiCount: parseInt(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>配号次数</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={editingUser.searchCount || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, searchCount: parseInt(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>寻号次数</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={editingUser.searchCount || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, searchCount: parseInt(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>积分</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={editingUser.points || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, points: parseInt(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>余额</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
value={editingUser.balance || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, balance: parseFloat(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
|
|
|
<label style={{ display: 'block', color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>累计金额</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
value={editingUser.totalAmount || 0}
|
|
|
|
|
|
onChange={(e) => setEditingUser({ ...editingUser, totalAmount: parseFloat(e.target.value) || 0 })}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|