update styles
This commit is contained in:
parent
e5fa1f2024
commit
65b2150acd
@ -5,46 +5,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>后台管理 - Tradus</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--success: #10B981;
|
||||
--success-light: #D1FAE5;
|
||||
--error: #DC2626;
|
||||
--error-light: #FEE2E2;
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Admin Layout */
|
||||
.admin-page {
|
||||
@ -123,7 +93,7 @@
|
||||
padding: 10px 20px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
@ -148,7 +118,7 @@
|
||||
.stat-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@ -178,7 +148,7 @@
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.2s;
|
||||
@ -197,7 +167,7 @@
|
||||
padding: 12px 24px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
@ -206,14 +176,14 @@
|
||||
}
|
||||
|
||||
.search-btn:hover {
|
||||
background: #0052CC;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.users-table {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -281,7 +251,7 @@
|
||||
padding: 8px 16px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
@ -322,9 +292,9 @@
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 48px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.login-box h2 {
|
||||
@ -340,7 +310,7 @@
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
@ -361,7 +331,7 @@
|
||||
padding: 14px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
@ -370,14 +340,14 @@
|
||||
}
|
||||
|
||||
.login-box button:hover {
|
||||
background: #0052CC;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 12px 16px;
|
||||
background: var(--error-light);
|
||||
border: 1px solid var(--error);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--error);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tradus|AI 金融智能体</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
@ -13,35 +16,8 @@
|
||||
<!-- Marked.js for Markdown rendering -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
@ -103,8 +79,8 @@
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background: #FEE2E2;
|
||||
color: #DC2626;
|
||||
background: var(--error-light);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
/* Chat Container */
|
||||
@ -307,7 +283,7 @@
|
||||
}
|
||||
|
||||
.send-btn:hover:not(:disabled) {
|
||||
background: #0052CC;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.send-btn:disabled {
|
||||
@ -372,8 +348,8 @@
|
||||
}
|
||||
|
||||
.modal-close-btn:hover {
|
||||
background: #FEE2E2;
|
||||
color: #DC2626;
|
||||
background: var(--error-light);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
@ -421,7 +397,7 @@
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #0052CC;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
|
||||
@ -5,37 +5,17 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - Tradus AI</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--error: #DC2626;
|
||||
--error-light: #FEE2E2;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -47,9 +27,10 @@
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 48px 40px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
@ -96,7 +77,7 @@
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
color: var(--text-primary);
|
||||
@ -107,7 +88,7 @@
|
||||
outline: none;
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px var(--primary-light);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
@ -127,7 +108,7 @@
|
||||
padding: 12px 20px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
@ -150,7 +131,7 @@
|
||||
padding: 14px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
@ -159,7 +140,7 @@
|
||||
}
|
||||
|
||||
.login-btn:hover:not(:disabled) {
|
||||
background: #0052CC;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
@ -171,7 +152,7 @@
|
||||
padding: 12px 16px;
|
||||
background: var(--error-light);
|
||||
border: 1px solid var(--error);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--error);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
|
||||
@ -5,46 +5,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>实盘交易 - Tradus</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--success: #10B981;
|
||||
--success-light: #D1FAE5;
|
||||
--error: #EF4444;
|
||||
--error-light: #FEE2E2;
|
||||
--warning: #F59E0B;
|
||||
--warning-light: #FEF3C7;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.trading-page {
|
||||
max-width: 1400px;
|
||||
@ -169,7 +139,7 @@
|
||||
padding: 10px 20px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
@ -186,7 +156,7 @@
|
||||
.warning-banner {
|
||||
background: var(--error-light);
|
||||
border: 1px solid var(--error);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
@ -226,7 +196,7 @@
|
||||
.account-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@ -290,7 +260,7 @@
|
||||
.table-container {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -330,7 +300,7 @@
|
||||
.side-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -348,7 +318,7 @@
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@ -5,46 +5,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>交易信号 - Tradus</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--success: #10B981;
|
||||
--success-light: #D1FAE5;
|
||||
--error: #EF4444;
|
||||
--error-light: #FEE2E2;
|
||||
--warning: #F59E0B;
|
||||
--warning-light: #FEF3C7;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.signals-page {
|
||||
max-width: 1400px;
|
||||
@ -86,7 +56,7 @@
|
||||
padding: 10px 20px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
@ -110,7 +80,7 @@
|
||||
.stat-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@ -153,7 +123,7 @@
|
||||
.grade-stat-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@ -213,14 +183,14 @@
|
||||
.signal-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 20px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.signal-card:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.signal-header {
|
||||
@ -258,7 +228,7 @@
|
||||
|
||||
.signal-action {
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -281,7 +251,7 @@
|
||||
.signal-grade {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -389,7 +359,7 @@
|
||||
/* Reason */
|
||||
.signal-reason {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@ -5,419 +5,219 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>模拟交易 - Tradus</title>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Page-Specific Styles -->
|
||||
<style>
|
||||
:root {
|
||||
--primary: #0066FF;
|
||||
--primary-light: #E8F0FF;
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F8FAFB;
|
||||
--bg-tertiary: #F1F5F9;
|
||||
--text-primary: #1E293B;
|
||||
--text-secondary: #64748B;
|
||||
--text-tertiary: #94A3B8;
|
||||
--border: #E2E8F0;
|
||||
--success: #10B981;
|
||||
--success-light: #D1FAE5;
|
||||
--error: #EF4444;
|
||||
--error-light: #FEE2E2;
|
||||
--warning: #F59E0B;
|
||||
--warning-light: #FEF3C7;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.trading-page {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.trading-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.trading-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.paper-badge {
|
||||
display: inline-block;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0052CC;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-primary);
|
||||
color: var(--primary);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--primary-light);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--error-light);
|
||||
color: var(--error);
|
||||
border: 1px solid var(--error);
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: var(--error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Account Info */
|
||||
.account-info {
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.account-card {
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.metric-card, .stat-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-lg);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.account-label {
|
||||
font-size: 12px;
|
||||
.metric-card:hover, .stat-card:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.metric-label, .stat-label {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: var(--space-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.account-value {
|
||||
font-size: 28px;
|
||||
.metric-value, .stat-value {
|
||||
font-size: var(--font-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: var(--space-xs);
|
||||
font-family: 'Inter', monospace;
|
||||
}
|
||||
|
||||
.account-sub {
|
||||
font-size: 12px;
|
||||
.metric-sub {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.account-value.positive {
|
||||
.metric-value.positive, .stat-value.positive {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.account-value.negative {
|
||||
.metric-value.negative, .stat-value.negative {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 24px;
|
||||
.grade-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 14px 24px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: var(--primary);
|
||||
border-bottom-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table-container {
|
||||
.grade-card {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--bg-tertiary);
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
.grade-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 14px 16px;
|
||||
font-size: 13px;
|
||||
.grade-card-title {
|
||||
font-size: var(--font-md);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
.grade-card-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: var(--bg-secondary);
|
||||
.grade-stat-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-sm) 0;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.side-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.side-long {
|
||||
background: var(--success-light);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.side-short {
|
||||
background: var(--error-light);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-open {
|
||||
background: var(--success-light);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: var(--warning-light);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
background: var(--bg-tertiary);
|
||||
.grade-stat-label {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pnl-positive {
|
||||
.grade-stat-value {
|
||||
font-size: var(--font-base);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.grade-stat-value.positive {
|
||||
color: var(--success);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pnl-negative {
|
||||
.grade-stat-value.negative {
|
||||
color: var(--error);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
padding: 6px 12px;
|
||||
background: var(--error-light);
|
||||
border: 1px solid var(--error);
|
||||
border-radius: 4px;
|
||||
color: var(--error);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Loading & Empty */
|
||||
.loading-state, .empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-state svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid var(--bg-tertiary);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.trading-page {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.trading-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.trading-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
.metrics-grid, .stat-grid, .grade-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="trading-page">
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="trading-header">
|
||||
<div class="trading-title">
|
||||
模拟交易
|
||||
<span class="paper-badge">PAPER</span>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">
|
||||
模拟交易
|
||||
<span class="paper-badge">PAPER</span>
|
||||
</div>
|
||||
<div class="page-subtitle">Tradus AI Auto Trading</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-secondary" @click="refreshData">刷新</button>
|
||||
<button class="btn btn-danger" @click="resetAccount" v-if="currentTab === 'positions' && orders.length > 0">
|
||||
<button class="btn btn-primary" @click="sendReport" :disabled="sendingReport">
|
||||
{{ sendingReport ? '发送中...' : '发送报告' }}
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="resetAccount" v-if="currentTab === 'positions' && openPositions.length > 0">
|
||||
重置账户
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Info -->
|
||||
<div class="account-info">
|
||||
<div class="account-card">
|
||||
<div class="account-label">账户余额</div>
|
||||
<div class="account-value">${{ account.current_balance ? account.current_balance.toLocaleString() : '0' }}</div>
|
||||
<div class="account-sub">可用: ${{ account.available ? account.available.toLocaleString() : '0' }}</div>
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">账户余额</div>
|
||||
<div class="metric-value">${{ account.current_balance ? account.current_balance.toLocaleString() : '0' }}</div>
|
||||
<div class="metric-sub">可用: ${{ account.available_margin ? account.available_margin.toLocaleString() : '0' }}</div>
|
||||
</div>
|
||||
<div class="account-card">
|
||||
<div class="account-label">已用保证金</div>
|
||||
<div class="account-value">${{ account.used_margin ? account.used_margin.toLocaleString() : '0' }}</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">已用保证金</div>
|
||||
<div class="metric-value">${{ account.used_margin ? account.used_margin.toLocaleString() : '0' }}</div>
|
||||
</div>
|
||||
<div class="account-card">
|
||||
<div class="account-label">持仓价值</div>
|
||||
<div class="account-value">${{ account.total_position_value ? account.total_position_value.toLocaleString() : '0' }}</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">持仓价值</div>
|
||||
<div class="metric-value">${{ account.total_position_value ? account.total_position_value.toLocaleString() : '0' }}</div>
|
||||
</div>
|
||||
<div class="account-card">
|
||||
<div class="account-label">总盈亏</div>
|
||||
<div class="account-value" :class="account.total_pnl >= 0 ? 'positive' : 'negative'">
|
||||
${{ account.total_pnl ? account.total_pnl.toLocaleString() : '0' }}
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">已实现盈亏</div>
|
||||
<div class="metric-value" :class="account.realized_pnl >= 0 ? 'positive' : 'negative'">
|
||||
${{ account.realized_pnl ? account.realized_pnl.toLocaleString() : '0' }}
|
||||
</div>
|
||||
<div class="account-sub">{{ account.total_trades || 0 }} 笔交易</div>
|
||||
<div class="metric-sub">{{ stats.total_trades || 0 }} 笔交易</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">胜率</div>
|
||||
<div class="stat-value positive">{{ stats.win_rate ? stats.win_rate.toFixed(1) : '0.0' }}%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">盈亏比</div>
|
||||
<div class="stat-value">{{ stats.profit_factor === Infinity ? '∞' : (stats.profit_factor ? stats.profit_factor.toFixed(2) : '0.00') }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">最大回撤</div>
|
||||
<div class="stat-value" :class="stats.max_drawdown <= 0 ? 'negative' : 'positive'">
|
||||
{{ stats.max_drawdown ? stats.max_drawdown.toFixed(2) : '0.00' }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">总杠杆率</div>
|
||||
<div class="stat-value">{{ account.current_total_leverage ? account.current_total_leverage.toFixed(1) : '0.0' }}x / {{ account.max_total_leverage || 10 }}x</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -441,142 +241,192 @@
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentTab === 'positions' && openPositions.length === 0" class="empty-state">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p>暂无持仓</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentTab === 'pending' && pendingOrders.length === 0" class="empty-state">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p>暂无挂单</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentTab === 'history' && orderHistory.length === 0" class="empty-state">
|
||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p>暂无历史订单</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="table-container">
|
||||
<div v-else>
|
||||
<!-- Open Positions Table -->
|
||||
<table v-if="currentTab === 'positions'">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>入场价</th>
|
||||
<th>当前价</th>
|
||||
<th>杠杆</th>
|
||||
<th>保证金</th>
|
||||
<th>未实现盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in openPositions" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="side-badge" :class="order.is_long ? 'side-long' : 'side-short'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>${{ order.current_price.toFixed(2) }}</td>
|
||||
<td>{{ order.leverage }}x</td>
|
||||
<td>${{ order.margin.toFixed(2) }}</td>
|
||||
<td :class="order.unrealized_pnl >= 0 ? 'pnl-positive' : 'pnl-negative'">
|
||||
{{ order.unrealized_pnl >= 0 ? '+' : '' }}${{ order.unrealized_pnl.toFixed(2) }}
|
||||
</td>
|
||||
<td :class="order.pnl_percent >= 0 ? 'pnl-positive' : 'pnl-negative'">
|
||||
{{ order.pnl_percent >= 0 ? '+' : '' }}{{ order.pnl_percent.toFixed(2) }}%
|
||||
</td>
|
||||
<td>
|
||||
<button class="close-btn" @click="closeOrder(order.order_id)">平仓</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="currentTab === 'positions'" class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>入场价</th>
|
||||
<th>当前价</th>
|
||||
<th>杠杆</th>
|
||||
<th>保证金</th>
|
||||
<th>未实现盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in openPositions" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="badge" :class="order.is_long ? 'badge-success' : 'badge-error'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>${{ order.current_price ? order.current_price.toFixed(2) : '-' }}</td>
|
||||
<td>{{ order.leverage }}x</td>
|
||||
<td>${{ order.margin.toFixed(2) }}</td>
|
||||
<td :class="order.unrealized_pnl >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ order.unrealized_pnl >= 0 ? '+' : '' }}${{ order.unrealized_pnl.toFixed(2) }}
|
||||
</td>
|
||||
<td :class="order.pnl_percent >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ order.pnl_percent >= 0 ? '+' : '' }}{{ order.pnl_percent.toFixed(2) }}%
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" @click="closeOrder(order)">平仓</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pending Orders Table -->
|
||||
<table v-else-if="currentTab === 'pending'">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>挂单价</th>
|
||||
<th>杠杆</th>
|
||||
<th>止损</th>
|
||||
<th>止盈</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in pendingOrders" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="side-badge" :class="order.is_long ? 'side-long' : 'side-short'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>{{ order.leverage }}x</td>
|
||||
<td>{{ order.stop_loss ? '$' + order.stop_loss.toFixed(2) : '-' }}</td>
|
||||
<td>{{ order.take_profit ? '$' + order.take_profit.toFixed(2) : '-' }}</td>
|
||||
<td>{{ formatTime(order.created_at) }}</td>
|
||||
<td>
|
||||
<button class="close-btn" @click="cancelOrder(order.order_id)">撤单</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else-if="currentTab === 'pending'" class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>挂单价</th>
|
||||
<th>杠杆</th>
|
||||
<th>止损</th>
|
||||
<th>止盈</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in pendingOrders" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="badge" :class="order.is_long ? 'badge-success' : 'badge-error'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>{{ order.leverage }}x</td>
|
||||
<td>{{ order.stop_loss ? '$' + order.stop_loss.toFixed(2) : '-' }}</td>
|
||||
<td>{{ order.take_profit ? '$' + order.take_profit.toFixed(2) : '-' }}</td>
|
||||
<td>{{ formatTime(order.created_at) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" @click="cancelOrder(order)">撤单</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Order History Table -->
|
||||
<table v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>入场价</th>
|
||||
<th>出场价</th>
|
||||
<th>已实现盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>平仓原因</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in orderHistory" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="side-badge" :class="order.is_long ? 'side-long' : 'side-short'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
<div v-else class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>入场价</th>
|
||||
<th>出场价</th>
|
||||
<th>已实现盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>平仓原因</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in orderHistory" :key="order.order_id">
|
||||
<td><strong>{{ order.symbol }}</strong></td>
|
||||
<td>
|
||||
<span class="badge" :class="order.is_long ? 'badge-success' : 'badge-error'">
|
||||
{{ order.is_long ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>${{ order.exit_price.toFixed(2) }}</td>
|
||||
<td :class="order.realized_pnl >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ order.realized_pnl >= 0 ? '+' : '' }}${{ order.realized_pnl.toFixed(2) }}
|
||||
</td>
|
||||
<td :class="order.pnl_percent >= 0 ? 'text-success' : 'text-error'">
|
||||
{{ order.pnl_percent >= 0 ? '+' : '' }}{{ order.pnl_percent.toFixed(2) }}%
|
||||
</td>
|
||||
<td>{{ getCloseReason(order.close_reason) }}</td>
|
||||
<td>{{ formatTime(order.closed_at) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Detailed Stats (Only show in history tab) -->
|
||||
<div v-if="currentTab === 'history' && orderHistory.length > 0" class="grade-stats">
|
||||
<div class="grade-card">
|
||||
<div class="grade-card-header">
|
||||
<span class="grade-card-title">交易详情</span>
|
||||
</div>
|
||||
<div class="grade-card-stats">
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">总交易数</span>
|
||||
<span class="grade-stat-value">{{ stats.total_trades || 0 }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">盈利交易</span>
|
||||
<span class="grade-stat-value positive">{{ stats.winning_trades || 0 }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">亏损交易</span>
|
||||
<span class="grade-stat-value negative">{{ stats.losing_trades || 0 }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">最佳交易</span>
|
||||
<span class="grade-stat-value positive">{{ stats.best_trade ? stats.best_trade.toFixed(2) : '0.00' }}%</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">最差交易</span>
|
||||
<span class="grade-stat-value negative">{{ stats.worst_trade ? stats.worst_trade.toFixed(2) : '0.00' }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grade-card">
|
||||
<div class="grade-card-header">
|
||||
<span class="grade-card-title">收益分析</span>
|
||||
</div>
|
||||
<div class="grade-card-stats">
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">平均盈利</span>
|
||||
<span class="grade-stat-value positive">${{ stats.average_win ? stats.average_win.toFixed(2) : '0.00' }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">平均亏损</span>
|
||||
<span class="grade-stat-value negative">${{ stats.average_loss ? stats.average_loss.toFixed(2) : '0.00' }}</span>
|
||||
</div>
|
||||
<div class="grade-stat-row">
|
||||
<span class="grade-stat-label">收益率</span>
|
||||
<span class="grade-stat-value" :class="stats.return_percent >= 0 ? 'positive' : 'negative'">
|
||||
{{ stats.return_percent >= 0 ? '+' : '' }}{{ stats.return_percent ? stats.return_percent.toFixed(2) : '0.00' }}%
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity.toFixed(4) }}</td>
|
||||
<td>${{ order.entry_price.toFixed(2) }}</td>
|
||||
<td>${{ order.exit_price.toFixed(2) }}</td>
|
||||
<td :class="order.realized_pnl >= 0 ? 'pnl-positive' : 'pnl-negative'">
|
||||
{{ order.realized_pnl >= 0 ? '+' : '' }}${{ order.realized_pnl.toFixed(2) }}
|
||||
</td>
|
||||
<td :class="order.pnl_percent >= 0 ? 'pnl-positive' : 'pnl-negative'">
|
||||
{{ order.pnl_percent >= 0 ? '+' : '' }}{{ order.pnl_percent.toFixed(2) }}%
|
||||
</td>
|
||||
<td>{{ getCloseReason(order.close_reason) }}</td>
|
||||
<td>{{ formatTime(order.closed_at) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -591,13 +441,36 @@
|
||||
return {
|
||||
currentTab: 'positions',
|
||||
loading: false,
|
||||
account: {
|
||||
current_balance: 0,
|
||||
available: 0,
|
||||
used_margin: 0,
|
||||
total_position_value: 0,
|
||||
sendingReport: false,
|
||||
stats: {
|
||||
total_trades: 0,
|
||||
winning_trades: 0,
|
||||
losing_trades: 0,
|
||||
win_rate: 0,
|
||||
total_pnl: 0,
|
||||
total_trades: 0
|
||||
average_win: 0,
|
||||
average_loss: 0,
|
||||
profit_factor: 0,
|
||||
max_drawdown: 0,
|
||||
best_trade: 0,
|
||||
worst_trade: 0,
|
||||
return_percent: 0
|
||||
},
|
||||
account: {
|
||||
initial_balance: 10000,
|
||||
current_balance: 10000,
|
||||
used_margin: 0,
|
||||
available_margin: 10000,
|
||||
leverage: 10,
|
||||
margin_per_order: 1000,
|
||||
active_orders: 0,
|
||||
max_orders: 10,
|
||||
available_orders: 10,
|
||||
total_position_value: 0,
|
||||
margin_ratio: 0,
|
||||
realized_pnl: 0,
|
||||
current_total_leverage: 0,
|
||||
max_total_leverage: 10
|
||||
},
|
||||
orders: []
|
||||
};
|
||||
@ -623,8 +496,11 @@
|
||||
try {
|
||||
await Promise.all([
|
||||
this.fetchAccountStatus(),
|
||||
this.fetchStatistics(),
|
||||
this.fetchOrders()
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error('刷新数据失败:', e);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@ -641,6 +517,17 @@
|
||||
}
|
||||
},
|
||||
|
||||
async fetchStatistics() {
|
||||
try {
|
||||
const response = await axios.get('/api/trading/statistics');
|
||||
if (response.data.success) {
|
||||
this.stats = response.data.statistics;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchOrders() {
|
||||
try {
|
||||
const response = await axios.get('/api/trading/orders');
|
||||
@ -652,11 +539,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
async closeOrder(orderId) {
|
||||
async closeOrder(order) {
|
||||
if (!confirm('确定要平仓吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await axios.post(`/api/trading/orders/${orderId}/close`);
|
||||
const response = await axios.post(`/api/trading/orders/${order.order_id}/close`);
|
||||
if (response.data.success) {
|
||||
await this.refreshData();
|
||||
alert('平仓成功');
|
||||
@ -669,11 +556,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
async cancelOrder(orderId) {
|
||||
async cancelOrder(order) {
|
||||
if (!confirm('确定要撤单吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await axios.post(`/api/trading/orders/${orderId}/cancel`);
|
||||
const response = await axios.post(`/api/trading/orders/${order.order_id}/cancel`);
|
||||
if (response.data.success) {
|
||||
await this.refreshData();
|
||||
alert('撤单成功');
|
||||
@ -703,6 +590,23 @@
|
||||
}
|
||||
},
|
||||
|
||||
async sendReport() {
|
||||
this.sendingReport = true;
|
||||
try {
|
||||
const response = await axios.post('/api/trading/report?hours=4&send_telegram=true');
|
||||
if (response.data.success) {
|
||||
alert('报告已发送');
|
||||
} else {
|
||||
alert('发送失败: ' + (response.data.message || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送报告失败:', error);
|
||||
alert('发送失败: ' + (error.response?.data?.detail || error.message));
|
||||
} finally {
|
||||
this.sendingReport = false;
|
||||
}
|
||||
},
|
||||
|
||||
formatTime(timeStr) {
|
||||
if (!timeStr) return '-';
|
||||
const date = new Date(timeStr);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user