)}
- {Object.entries(configCategories).map(([category, label]) => (
-
- ))}
+ {/* 配置项(禁用account时不显示) */}
+ {currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'active' ? (
+ Object.entries(configCategories).map(([category, label]) => (
+
+ ))
+ ) : currentAccountMeta && String(currentAccountMeta?.status || 'active') === 'disabled' ? (
+
setShowSnapshot(false)} role="presentation">
diff --git a/frontend/src/components/GlobalConfig.css b/frontend/src/components/GlobalConfig.css
new file mode 100644
index 0000000..4841c8a
--- /dev/null
+++ b/frontend/src/components/GlobalConfig.css
@@ -0,0 +1,167 @@
+.global-config {
+ padding: 24px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.global-config-header {
+ margin-bottom: 32px;
+}
+
+.global-config-header h2 {
+ margin: 0 0 8px 0;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.global-config-header p {
+ margin: 0;
+ color: #666;
+ font-size: 14px;
+}
+
+.global-section {
+ margin-bottom: 40px;
+ background: #fff;
+ border-radius: 8px;
+ padding: 24px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.section-header h3 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.form-card {
+ background: #f5f5f5;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+}
+
+.form-card h4 {
+ margin: 0 0 16px 0;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.form-group {
+ margin-bottom: 16px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 500;
+ font-size: 14px;
+}
+
+.form-group input,
+.form-group select {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.form-actions {
+ display: flex;
+ gap: 12px;
+ margin-top: 20px;
+}
+
+.btn-primary {
+ background: #1976d2;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.btn-primary:hover:not(:disabled) {
+ background: #1565c0;
+}
+
+.btn-primary:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.table-container {
+ overflow-x: auto;
+}
+
+.data-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.data-table th,
+.data-table td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #eee;
+}
+
+.data-table th {
+ background: #f5f5f5;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.data-table td {
+ font-size: 14px;
+}
+
+.data-table select {
+ padding: 4px 8px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 14px;
+}
+
+.status-badge {
+ display: inline-block;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.status-badge.active {
+ background: #e8f5e9;
+ color: #2e7d32;
+}
+
+.status-badge.disabled {
+ background: #ffebee;
+ color: #c62828;
+}
+
+.message {
+ padding: 12px 16px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+}
+
+.message.success {
+ background: #e8f5e9;
+ color: #2e7d32;
+}
+
+.message.error {
+ background: #ffebee;
+ color: #c62828;
+}
diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx
new file mode 100644
index 0000000..fd50c40
--- /dev/null
+++ b/frontend/src/components/GlobalConfig.jsx
@@ -0,0 +1,328 @@
+import React, { useState, useEffect } from 'react'
+import { api } from '../services/api'
+import './GlobalConfig.css'
+
+const GlobalConfig = ({ currentUser }) => {
+ const [users, setUsers] = useState([])
+ const [accounts, setAccounts] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [message, setMessage] = useState('')
+ const [busy, setBusy] = useState(false)
+ const [selectedUserId, setSelectedUserId] = useState(null)
+ const [showUserForm, setShowUserForm] = useState(false)
+ const [newUser, setNewUser] = useState({ username: '', password: '', role: 'user', status: 'active' })
+ const [editingUserId, setEditingUserId] = useState(null)
+
+ useEffect(() => {
+ loadUsers()
+ loadAccounts()
+ }, [])
+
+ const loadUsers = async () => {
+ try {
+ const list = await api.getUsers()
+ setUsers(Array.isArray(list) ? list : [])
+ } catch (error) {
+ setMessage('加载用户列表失败: ' + (error.message || '未知错误'))
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const loadAccounts = async () => {
+ try {
+ const list = await api.getAccounts()
+ setAccounts(Array.isArray(list) ? list : [])
+ } catch (error) {
+ console.error('加载账号列表失败:', error)
+ }
+ }
+
+ const handleCreateUser = async () => {
+ if (!newUser.username || !newUser.password) {
+ setMessage('用户名和密码不能为空')
+ return
+ }
+ setBusy(true)
+ setMessage('')
+ try {
+ await api.createUser(newUser)
+ setMessage('用户创建成功')
+ setShowUserForm(false)
+ setNewUser({ username: '', password: '', role: 'user', status: 'active' })
+ await loadUsers()
+ } catch (error) {
+ setMessage('创建用户失败: ' + (error.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ const handleUpdateUserPassword = async (userId) => {
+ const passwordInput = document.querySelector(`input[data-user-id="${userId}"]`)
+ const password = passwordInput?.value
+ if (!password) {
+ setMessage('密码不能为空')
+ return
+ }
+ setBusy(true)
+ setMessage('')
+ try {
+ await api.updateUserPassword(userId, password)
+ setMessage('密码更新成功')
+ setEditingUserId(null)
+ if (passwordInput) passwordInput.value = ''
+ await loadUsers()
+ } catch (error) {
+ setMessage('更新密码失败: ' + (error.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ const handleUpdateUserRole = async (userId, role) => {
+ setBusy(true)
+ setMessage('')
+ try {
+ await api.updateUserRole(userId, role)
+ setMessage('角色更新成功')
+ await loadUsers()
+ } catch (error) {
+ setMessage('更新角色失败: ' + (error.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ const handleUpdateUserStatus = async (userId, status) => {
+ setBusy(true)
+ setMessage('')
+ try {
+ await api.updateUserStatus(userId, status)
+ setMessage('状态更新成功')
+ await loadUsers()
+ } catch (error) {
+ setMessage('更新状态失败: ' + (error.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ if (loading) {
+ return
加载中...
+ }
+
+ return (
+
+
+
全局配置
+
管理用户、账号和全局策略配置
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+ {/* 用户管理 */}
+
+
+
用户管理
+
+
+
+ {showUserForm && (
+
+
创建新用户
+
+
+ setNewUser({ ...newUser, username: e.target.value })}
+ placeholder="输入用户名"
+ />
+
+
+
+ setNewUser({ ...newUser, password: e.target.value })}
+ placeholder="输入密码"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/* 账号管理 */}
+
+
+
账号管理
+
+
+
+
+
+
+
+ | ID |
+ 名称 |
+ 状态 |
+ 测试网 |
+ API Key |
+
+
+
+ {accounts.map((account) => (
+
+ | {account.id} |
+ {account.name || '未命名'} |
+
+
+ {account.status === 'active' ? '启用' : '禁用'}
+
+ |
+ {account.use_testnet ? '是' : '否'} |
+ {account.has_api_key ? '已配置' : '未配置'} |
+
+ ))}
+
+
+
+
+
+ )
+}
+
+export default GlobalConfig
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index ee2e662..3b5b63a 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -630,4 +630,60 @@ export const api = {
}
return response.json();
},
+
+ // 管理员:创建用户
+ createUser: async (data) => {
+ const response = await fetch(buildUrl('/api/admin/users'), {
+ method: 'POST',
+ headers: withAuthHeaders({ 'Content-Type': 'application/json' }),
+ body: JSON.stringify(data),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '创建用户失败' }));
+ throw new Error(error.detail || '创建用户失败');
+ }
+ return response.json();
+ },
+
+ // 管理员:更新用户密码
+ updateUserPassword: async (userId, password) => {
+ const response = await fetch(buildUrl(`/api/admin/users/${userId}/password`), {
+ method: 'PUT',
+ headers: withAuthHeaders({ 'Content-Type': 'application/json' }),
+ body: JSON.stringify({ password }),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '更新密码失败' }));
+ throw new Error(error.detail || '更新密码失败');
+ }
+ return response.json();
+ },
+
+ // 管理员:更新用户角色
+ updateUserRole: async (userId, role) => {
+ const response = await fetch(buildUrl(`/api/admin/users/${userId}/role`), {
+ method: 'PUT',
+ headers: withAuthHeaders({ 'Content-Type': 'application/json' }),
+ body: JSON.stringify({ role }),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '更新角色失败' }));
+ throw new Error(error.detail || '更新角色失败');
+ }
+ return response.json();
+ },
+
+ // 管理员:更新用户状态
+ updateUserStatus: async (userId, status) => {
+ const response = await fetch(buildUrl(`/api/admin/users/${userId}/status`), {
+ method: 'PUT',
+ headers: withAuthHeaders({ 'Content-Type': 'application/json' }),
+ body: JSON.stringify({ status }),
+ });
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: '更新状态失败' }));
+ throw new Error(error.detail || '更新状态失败');
+ }
+ return response.json();
+ },
};