diff --git a/VERSION b/VERSION index a39cbb3..50e400b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.40 +1.2.41 diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 2218c71..8683b0b 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) DATABASE_URL = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:5432/zodiac" + "postgresql://postgres:postgres@127.0.0.1:5432/zodiac" ) # 增强版数据库引擎配置 diff --git a/config/VERSION b/config/VERSION index a415887..a4f8462 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=v1.2.38 +v1.2.41 diff --git a/frontend/src/pages/AdminV2.jsx b/frontend/src/pages/AdminV2.jsx deleted file mode 100644 index 940ff94..0000000 --- a/frontend/src/pages/AdminV2.jsx +++ /dev/null @@ -1,272 +0,0 @@ -import React, { useState, useEffect } from 'react' - -// 管理后台V2 -export default function AdminV2() { - const [activeTab, setActiveTab] = useState('users') - const token = localStorage.getItem('token') - const API_BASE = localStorage.getItem('API_BASE') || '' - - const userStr = localStorage.getItem('user') - let user = null - try { user = userStr ? JSON.parse(userStr) : null } catch (e) {} - - if (!user || user.role !== 'admin') { - return ( -
-
-
🚫
-
无权访问
-
仅管理员可以访问管理后台
-
-
- ) - } - - return ( -
-
-

⚙️ 管理后台

-
- -
- {[ - { key: 'users', label: '👥 用户管理' }, - { key: 'stats', label: '📊 统计分析' }, - { key: 'publish', label: '📢 信息发布' }, - { key: 'infoManage', label: '📋 信息管理' } - ].map(tab => ( -
setActiveTab(tab.key)} style={{ flex: '1 1 45%', textAlign: 'center', padding: '10px 8px', margin: '2px', borderRadius: '6px', cursor: 'pointer', background: activeTab === tab.key ? '#3b82f6' : 'transparent', color: activeTab === tab.key ? '#fff' : '#94a3b8', fontSize: '13px', fontWeight: activeTab === tab.key ? 'bold' : 'normal' }}> - {tab.label} -
- ))} -
- - {activeTab === 'users' && } - {activeTab === 'stats' && } - {activeTab === 'publish' && } - {activeTab === 'infoManage' && } -
- ) -} - -function UserManagement({ token, API_BASE }) { - const [users, setUsers] = useState([]) - const [loading, setLoading] = useState(true) - const [search, setSearch] = useState('') - const [editingUser, setEditingUser] = useState(null) - const [showAddModal, setShowAddModal] = useState(false) - const [newUser, setNewUser] = useState({ username: '', password: '', phone: '', role: 'user' }) - - useEffect(() => { fetchUsers() }, []) - - const fetchUsers = async () => { - setLoading(true) - try { - const res = await fetch(`${API_BASE}/api/admin/users?page=1&limit=100`, { headers: { 'Authorization': `Bearer ${token}` } }) - const data = await res.json() - setUsers(Array.isArray(data) ? data : []) - } catch (e) { console.error(e) } - setLoading(false) - } - - const handleAddUser = async () => { - if (!newUser.username || !newUser.password) { alert('请填写用户名和密码'); return } - try { - const res = await fetch(`${API_BASE}/api/admin/users`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(newUser) }) - if (res.ok) { alert('添加成功'); setShowAddModal(false); setNewUser({ username: '', password: '', phone: '', role: 'user' }); fetchUsers() } - } catch (e) { alert('添加失败') } - } - - const handleUpdateUser = async () => { - try { - 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() } - } catch (e) { alert('更新失败') } - } - - const handleDeleteUser = async (id) => { - if (!confirm('确定删除该用户?')) return - 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.username?.includes(search) || u.phone?.includes(search) || u.user_code?.includes(search)) - - return ( -
-
- setSearch(e.target.value)} style={{ flex: 1, padding: '10px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff' }} /> - -
- - {loading ?
加载中...
: ( -
- {filteredUsers.map(u => ( -
-
-
-
{u.username?.[0] || '?'}
-
{u.username}
{u.user_code || '-'}
-
- {u.role === 'admin' ? '管理员' : '用户'} -
-
-
📱 {u.phone || '-'}
🏷️ 等级: {u.level || '青铜'} | 藏品: {u.collectionCount || 0}
-
🔑 登录: {u.loginCount || 0}次 | AI: {u.aiCount || 0}次
-
-
- - -
-
- ))} -
- )} - - {showAddModal && ( -
-
-

添加用户

- setNewUser({...newUser, username: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} /> - setNewUser({...newUser, password: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} /> - setNewUser({...newUser, phone: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '12px', boxSizing: 'border-box' }} /> - -
- - -
-
-
- )} - - {editingUser && ( -
-
-

编辑用户

- 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' }} /> - - 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' }} /> - 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' }} /> -
- - -
-
-
- )} -
- ) -} - -function Statistics({ token, API_BASE }) { - const [stats, setStats] = useState(null) - const [loading, setLoading] = useState(true) - - useEffect(() => { fetchStats() }, []) - - const fetchStats = async () => { - setLoading(true) - try { - const collectionsRes = await fetch(`${API_BASE}/api/collections/stats`, { headers: { 'Authorization': `Bearer ${token}` } }) - const collectionsData = await collectionsRes.json() - const usersRes = await fetch(`${API_BASE}/api/admin/users?page=1&limit=100`, { headers: { 'Authorization': `Bearer ${token}` } }) - const usersData = await usersRes.json() - const totalUsers = Array.isArray(usersData) ? usersData.length : 0 - const totalCollections = collectionsData.total || 0 - const levelCount = {} - 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 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 } }) - } catch (e) { console.error(e) } - setLoading(false) - } - - if (loading) return
加载中...
- - return ( -
-
-
{stats?.users?.total || 0}
👥 用户总数
-
{stats?.collections?.total || 0}
📚 藏品总数
-
¥{stats?.balance?.total?.toFixed(2) || '0'}
💰 账户总余额
-
{stats?.collections?.status?.in_collection || 0}
✨ 收藏中
-
-

🏆 会员等级分布

{Object.entries(stats?.users?.levels || {}).map(([level, count]) => (
{level}{count}人
))}
-

📦 藏品状态

{stats?.collections?.status?.in_collection || 0}
收藏中
{stats?.collections?.status?.sold || 0}
已售出
-
- ) -} - -function InfoPublish({ token, API_BASE }) { - const [publishType, setPublishType] = useState('system') - const [formData, setFormData] = useState({ title: '', content: '', expect_version: '', expect_packaging: '', deal_price: '' }) - const [submitting, setSubmitting] = useState(false) - - const handlePublish = async () => { - if (!formData.title) { alert('请输入标题'); return } - setSubmitting(true) - try { - const data = { title: formData.title, content: formData.content, info_type: publishType === 'system' ? 'publish' : 'deal', deal_price: publishType === 'deal' ? parseFloat(formData.deal_price) : null, expect_version: formData.expect_version, expect_packaging: formData.expect_packaging } - const res = await fetch(`${API_BASE}/api/information/`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) - if (res.ok) { alert('发布成功!'); setFormData({ title: '', content: '', expect_version: '', expect_packaging: '', deal_price: '' }) } - } catch (e) { alert('发布失败') } - setSubmitting(false) - } - - return ( -
-
- - -
-
-

{publishType === 'system' ? '📢 发布系统通知' : '📈 发布成交数据'}

-
setFormData({...formData, title: e.target.value})} placeholder={publishType === 'system' ? '请输入通知标题' : '如:2024版纪念钞成交'} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginTop: '4px', boxSizing: 'border-box' }} />
-