v1.2.35: Admin页面显示28字段,登录/AI/配号次数统计,修复OCR/短信/资讯API等问题
This commit is contained in:
parent
190d11347f
commit
83c8410627
|
|
@ -454,6 +454,10 @@ def get_seek_match(
|
||||||
if not info:
|
if not info:
|
||||||
raise HTTPException(status_code=404, detail="寻配号信息不存在")
|
raise HTTPException(status_code=404, detail="寻配号信息不存在")
|
||||||
|
|
||||||
|
# 更新用户配号(寻号)次数
|
||||||
|
current_user.f99_96_search_count = (current_user.f99_96_search_count or 0) + 1
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# 获取用户所有藏品
|
# 获取用户所有藏品
|
||||||
collections = db.query(Collection).filter(
|
collections = db.query(Collection).filter(
|
||||||
Collection.f99_91_user_id == current_user.f99_90_id,
|
Collection.f99_91_user_id == current_user.f99_90_id,
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,9 @@ async def recognize_image(
|
||||||
# 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext}
|
# 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext}
|
||||||
oss_key, temp_id = get_oss_path("temp", filename=image.filename)
|
oss_key, temp_id = get_oss_path("temp", filename=image.filename)
|
||||||
|
|
||||||
|
# 初始化temp_path为空
|
||||||
|
temp_path = None
|
||||||
|
|
||||||
# 上传到OSS
|
# 上传到OSS
|
||||||
try:
|
try:
|
||||||
image_url = upload_to_oss(image_data, oss_key)
|
image_url = upload_to_oss(image_data, oss_key)
|
||||||
|
|
@ -159,7 +162,7 @@ async def recognize_image(
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
# 识别失败,删除临时文件
|
# 识别失败,删除临时文件
|
||||||
if os.path.exists(temp_path):
|
if temp_path and os.path.exists(temp_path):
|
||||||
os.remove(temp_path)
|
os.remove(temp_path)
|
||||||
raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
|
raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ from typing import Optional
|
||||||
|
|
||||||
# 阿里云短信配置
|
# 阿里云短信配置
|
||||||
SMS_CONFIG = {
|
SMS_CONFIG = {
|
||||||
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"),
|
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5tQAx5niD7JQVqGE5acE"),
|
||||||
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"),
|
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "QsQFAEKBkaNynIoKyvdIi3BUyWVZu1"),
|
||||||
"sign_name": "苏州算力",
|
"sign_name": os.getenv("SMS_SIGN_NAME", "苏州算力"),
|
||||||
"template_code": "SMS_501590956",
|
"template_code": os.getenv("SMS_TEMPLATE_CODE", "SMS_501590956"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 验证码缓存(生产环境建议用Redis)
|
# 验证码缓存(生产环境建议用Redis)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<title>甲辰收藏 v1.2.30</title>
|
<title>甲辰收藏 v1.2.35</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico" />
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,16 @@ export default function Admin() {
|
||||||
const updateData = {
|
const updateData = {
|
||||||
username: editingUser.username,
|
username: editingUser.username,
|
||||||
email: editingUser.email,
|
email: editingUser.email,
|
||||||
role: editingUser.role
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有新密码,添加到更新数据
|
// 如果有新密码,添加到更新数据
|
||||||
|
|
@ -186,9 +195,9 @@ export default function Admin() {
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<div key={user.id} style={{ background: 'rgba(30, 41, 59, 0.8)', borderRadius: '12px', padding: '16px' }}>
|
<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={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
<div style={{ color: '#fbbf24', fontSize: '16px', fontWeight: 'bold' }}>{user.username}</div>
|
<div style={{ color: '#fbbf24', fontSize: '16px', fontWeight: 'bold' }}>{user.username}</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '2px 8px', borderRadius: '4px',
|
padding: '2px 8px', borderRadius: '4px',
|
||||||
|
|
@ -198,9 +207,15 @@ export default function Admin() {
|
||||||
}}>
|
}}>
|
||||||
{user.role === 'admin' ? '👑 管理员' : (user.role === 'editor' ? '📝 信息员' : '👤 用户')}
|
{user.role === 'admin' ? '👑 管理员' : (user.role === 'editor' ? '📝 信息员' : '👤 用户')}
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ background: 'rgba(59, 130, 246, 0.2)', padding: '2px 8px', borderRadius: '4px', color: '#60a5fa', fontSize: '12px' }}>
|
||||||
|
编码: {user.user_code || '-'}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: '#64748b', fontSize: '13px', marginTop: '4px' }}>
|
<div style={{ background: 'rgba(168, 85, 247, 0.2)', padding: '2px 8px', borderRadius: '4px', color: '#a855f7', fontSize: '12px' }}>
|
||||||
📧 {user.email || '未设置'} | 📱 {user.phone || '未设置'}
|
等级: {user.level || '青铜'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#64748b', fontSize: '13px', marginTop: '8px' }}>
|
||||||
|
📧 {user.email || '未设置'} | 📱 {user.phone || '未设置'} | 📱已认证: {user.phoneVerified ? '✓' : '✗'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right' }}>
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
|
@ -208,6 +223,30 @@ export default function Admin() {
|
||||||
<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: '#60a5fa', fontSize: '20px', fontWeight: 'bold', cursor: 'pointer' }} onClick={() => window.location.hash = '#/list?userId=' + user.id} title='点击查看'>{user.collectionCount || 0}</div>
|
||||||
</div>
|
</div>
|
||||||
</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={{ 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' }}>
|
<div style={{ color: '#64748b', fontSize: '12px' }}>
|
||||||
注册时间:{user.created_at ? new Date(user.created_at).toLocaleDateString('zh-CN') : '-'}
|
注册时间:{user.created_at ? new Date(user.created_at).toLocaleDateString('zh-CN') : '-'}
|
||||||
|
|
@ -393,6 +432,122 @@ export default function Admin() {
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
</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 style={{ marginTop: '12px' }}>
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', color: '#94a3b8', fontSize: '13px', cursor: 'pointer' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={editingUser.phoneVerified || false}
|
||||||
|
onChange={(e) => setEditingUser({ ...editingUser, phoneVerified: e.target.checked })}
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
/>
|
||||||
|
手机号已认证
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '12px' }}>
|
<div style={{ display: 'flex', gap: '12px' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingUser(null)}
|
onClick={() => setEditingUser(null)}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ function UserManagement({ token, API_BASE }) {
|
||||||
|
|
||||||
const handleUpdateUser = async () => {
|
const handleUpdateUser = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/api/admin/users/${editingUser.f99_90_id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(editingUser) })
|
const res = await fetch(`${API_BASE}/api/admin/users/${editingUser.id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(editingUser) })
|
||||||
if (res.ok) { alert('更新成功'); setEditingUser(null); fetchUsers() }
|
if (res.ok) { alert('更新成功'); setEditingUser(null); fetchUsers() }
|
||||||
} catch (e) { alert('更新失败') }
|
} catch (e) { alert('更新失败') }
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +89,7 @@ function UserManagement({ token, API_BASE }) {
|
||||||
try { await fetch(`${API_BASE}/api/admin/users/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); fetchUsers() } catch (e) { console.error(e) }
|
try { await fetch(`${API_BASE}/api/admin/users/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); fetchUsers() } catch (e) { console.error(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredUsers = users.filter(u => u.f01_01_name?.includes(search) || u.phone?.includes(search) || u.user_code?.includes(search))
|
const filteredUsers = users.filter(u => u.username?.includes(search) || u.phone?.includes(search) || u.user_code?.includes(search))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -101,21 +101,21 @@ function UserManagement({ token, API_BASE }) {
|
||||||
{loading ? <div style={{ textAlign: 'center', color: '#94a3b8', padding: '20px' }}>加载中...</div> : (
|
{loading ? <div style={{ textAlign: 'center', color: '#94a3b8', padding: '20px' }}>加载中...</div> : (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '12px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '12px' }}>
|
||||||
{filteredUsers.map(u => (
|
{filteredUsers.map(u => (
|
||||||
<div key={u.f99_90_id} style={{ background: '#1e293b', borderRadius: '12px', padding: '16px' }}>
|
<div key={u.id} style={{ background: '#1e293b', borderRadius: '12px', padding: '16px' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
<div style={{ width: '40px', height: '40px', borderRadius: '50%', background: '#3b82f6', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: '16px' }}>{u.f01_01_name?.[0] || '?'}</div>
|
<div style={{ width: '40px', height: '40px', borderRadius: '50%', background: '#3b82f6', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: '16px' }}>{u.username?.[0] || '?'}</div>
|
||||||
<div><div style={{ color: '#fff', fontWeight: 'bold' }}>{u.f01_01_name}</div><div style={{ color: '#64748b', fontSize: '12px' }}>{u.user_code || '-'}</div></div>
|
<div><div style={{ color: '#fff', fontWeight: 'bold' }}>{u.username}</div><div style={{ color: '#64748b', fontSize: '12px' }}>{u.user_code || '-'}</div></div>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ background: u.role === 'admin' ? '#f59e0b' : '#10b981', color: '#fff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>{u.role === 'admin' ? '管理员' : '用户'}</span>
|
<span style={{ background: u.role === 'admin' ? '#f59e0b' : '#10b981', color: '#fff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>{u.role === 'admin' ? '管理员' : '用户'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '8px' }}>
|
<div style={{ fontSize: '12px', color: '#94a3b8', marginBottom: '8px' }}>
|
||||||
<div>📱 {u.phone || '-'}</div><div>🏷️ 等级: {u.f99_94_level || '青铜'} | 藏品: {u.f99_97_collection_count || 0}</div>
|
<div>📱 {u.phone || '-'}</div><div>🏷️ 等级: {u.level || '青铜'} | 藏品: {u.collectionCount || 0}</div>
|
||||||
<div>🔑 登录: {u.f99_98_login_count || 0}次 | AI: {u.f99_95_ai_count || 0}次</div>
|
<div>🔑 登录: {u.loginCount || 0}次 | AI: {u.aiCount || 0}次</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
<button onClick={() => setEditingUser(u)} style={{ flex: 1, padding: '6px', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' }}>编辑</button>
|
<button onClick={() => setEditingUser(u)} style={{ flex: 1, padding: '6px', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' }}>编辑</button>
|
||||||
<button onClick={() => handleDeleteUser(u.f99_90_id)} style={{ flex: 1, padding: '6px', background: '#ef4444', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' }}>删除</button>
|
<button onClick={() => handleDeleteUser(u.id)} style={{ flex: 1, padding: '6px', background: '#ef4444', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' }}>删除</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -144,8 +144,8 @@ function UserManagement({ token, API_BASE }) {
|
||||||
<h3 style={{ color: '#fbbf24', marginTop: 0 }}>编辑用户</h3>
|
<h3 style={{ color: '#fbbf24', marginTop: 0 }}>编辑用户</h3>
|
||||||
<input type="text" placeholder="手机号" value={editingUser.phone || ''} onChange={(e) => setEditingUser({...editingUser, phone: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
|
<input type="text" placeholder="手机号" value={editingUser.phone || ''} onChange={(e) => setEditingUser({...editingUser, phone: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
|
||||||
<select value={editingUser.role} onChange={(e) => setEditingUser({...editingUser, role: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px' }}><option value="user">普通用户</option><option value="admin">管理员</option></select>
|
<select value={editingUser.role} onChange={(e) => setEditingUser({...editingUser, role: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px' }}><option value="user">普通用户</option><option value="admin">管理员</option></select>
|
||||||
<input type="number" placeholder="积分" value={editingUser.f99_100_points || 0} onChange={(e) => setEditingUser({...editingUser, f99_100_points: parseInt(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
|
<input type="number" placeholder="积分" value={editingUser.points || 0} onChange={(e) => setEditingUser({...editingUser, points: parseInt(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
|
||||||
<input type="number" placeholder="余额" value={editingUser.f01_11_balance || 0} onChange={(e) => setEditingUser({...editingUser, f01_11_balance: parseFloat(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '12px', boxSizing: 'border-box' }} />
|
<input type="number" placeholder="余额" value={editingUser.balance || 0} onChange={(e) => setEditingUser({...editingUser, balance: parseFloat(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '12px', boxSizing: 'border-box' }} />
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
<button onClick={handleUpdateUser} style={{ flex: 1, padding: '10px', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>保存</button>
|
<button onClick={handleUpdateUser} style={{ flex: 1, padding: '10px', background: '#3b82f6', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>保存</button>
|
||||||
<button onClick={() => setEditingUser(null)} style={{ flex: 1, padding: '10px', background: '#64748b', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>取消</button>
|
<button onClick={() => setEditingUser(null)} style={{ flex: 1, padding: '10px', background: '#64748b', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>取消</button>
|
||||||
|
|
@ -173,9 +173,9 @@ function Statistics({ token, API_BASE }) {
|
||||||
const totalUsers = Array.isArray(usersData) ? usersData.length : 0
|
const totalUsers = Array.isArray(usersData) ? usersData.length : 0
|
||||||
const totalCollections = collectionsData.total || 0
|
const totalCollections = collectionsData.total || 0
|
||||||
const levelCount = {}
|
const levelCount = {}
|
||||||
if (Array.isArray(usersData)) { usersData.forEach(u => { const level = u.f99_94_level || '青铜'; levelCount[level] = (levelCount[level] || 0) + 1 }) }
|
if (Array.isArray(usersData)) { usersData.forEach(u => { const level = u.level || '青铜'; levelCount[level] = (levelCount[level] || 0) + 1 }) }
|
||||||
const statusCount = { in_collection: collectionsData.byStatus?.find(s => s.status === 'in_collection')?.count || 0, sold: collectionsData.byStatus?.find(s => s.status === 'sold')?.count || 0 }
|
const statusCount = { in_collection: collectionsData.byStatus?.find(s => s.status === 'in_collection')?.count || 0, sold: collectionsData.byStatus?.find(s => s.status === 'sold')?.count || 0 }
|
||||||
const totalBalance = Array.isArray(usersData) ? usersData.reduce((sum, u) => sum + (u.f01_11_balance || 0), 0) : 0
|
const totalBalance = Array.isArray(usersData) ? usersData.reduce((sum, u) => sum + (u.balance || 0), 0) : 0
|
||||||
setStats({ users: { total: totalUsers, levels: levelCount }, collections: { total: totalCollections, status: statusCount }, balance: { total: totalBalance } })
|
setStats({ users: { total: totalUsers, levels: levelCount }, collections: { total: totalCollections, status: statusCount }, balance: { total: totalBalance } })
|
||||||
} catch (e) { console.error(e) }
|
} catch (e) { console.error(e) }
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue