735 lines
25 KiB
HTML
735 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>模拟交易 - Tradus</title>
|
|
|
|
<!-- 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">
|
|
|
|
<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;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.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 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.account-card {
|
|
background: var(--bg-primary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.account-label {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.account-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.account-sub {
|
|
font-size: 12px;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.account-value.positive {
|
|
color: var(--success);
|
|
}
|
|
|
|
.account-value.negative {
|
|
color: var(--error);
|
|
}
|
|
|
|
/* Tabs */
|
|
.tabs {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--border);
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.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 {
|
|
background: var(--bg-primary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
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);
|
|
border-bottom: 1px solid var(--border);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
td {
|
|
padding: 14px 16px;
|
|
font-size: 13px;
|
|
color: var(--text-primary);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
tr:hover {
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
/* 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);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.pnl-positive {
|
|
color: var(--success);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.pnl-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 {
|
|
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">
|
|
<!-- Header -->
|
|
<div class="trading-header">
|
|
<div class="trading-title">
|
|
模拟交易
|
|
<span class="paper-badge">PAPER</span>
|
|
</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>
|
|
</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>
|
|
<div class="account-card">
|
|
<div class="account-label">已用保证金</div>
|
|
<div class="account-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>
|
|
<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>
|
|
<div class="account-sub">{{ account.total_trades || 0 }} 笔交易</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button class="tab" :class="{ active: currentTab === 'positions' }" @click="switchTab('positions')">
|
|
当前持仓 ({{ openPositions.length }})
|
|
</button>
|
|
<button class="tab" :class="{ active: currentTab === 'pending' }" @click="switchTab('pending')">
|
|
挂单中 ({{ pendingOrders.length }})
|
|
</button>
|
|
<button class="tab" :class="{ active: currentTab === 'history' }" @click="switchTab('history')">
|
|
历史订单 ({{ orderHistory.length }})
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div v-if="loading" class="loading-state">
|
|
<div class="spinner"></div>
|
|
<p style="margin-top: 16px; font-size: 14px;">加载中...</p>
|
|
</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">
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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 ? '做多' : '做空' }}
|
|
</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>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
<script>
|
|
const { createApp } = Vue;
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
currentTab: 'positions',
|
|
loading: false,
|
|
account: {
|
|
current_balance: 0,
|
|
available: 0,
|
|
used_margin: 0,
|
|
total_position_value: 0,
|
|
total_pnl: 0,
|
|
total_trades: 0
|
|
},
|
|
orders: []
|
|
};
|
|
},
|
|
computed: {
|
|
openPositions() {
|
|
return this.orders.filter(o => o.status === 'open');
|
|
},
|
|
pendingOrders() {
|
|
return this.orders.filter(o => o.status === 'pending');
|
|
},
|
|
orderHistory() {
|
|
return this.orders.filter(o => o.status === 'closed');
|
|
}
|
|
},
|
|
methods: {
|
|
async switchTab(tab) {
|
|
this.currentTab = tab;
|
|
},
|
|
|
|
async refreshData() {
|
|
this.loading = true;
|
|
try {
|
|
await Promise.all([
|
|
this.fetchAccountStatus(),
|
|
this.fetchOrders()
|
|
]);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async fetchAccountStatus() {
|
|
try {
|
|
const response = await axios.get('/api/trading/account');
|
|
if (response.data.success) {
|
|
this.account = response.data.account;
|
|
}
|
|
} catch (error) {
|
|
console.error('获取账户状态失败:', error);
|
|
}
|
|
},
|
|
|
|
async fetchOrders() {
|
|
try {
|
|
const response = await axios.get('/api/trading/orders');
|
|
if (response.data.success) {
|
|
this.orders = response.data.orders;
|
|
}
|
|
} catch (error) {
|
|
console.error('获取订单失败:', error);
|
|
}
|
|
},
|
|
|
|
async closeOrder(orderId) {
|
|
if (!confirm('确定要平仓吗?')) return;
|
|
|
|
try {
|
|
const response = await axios.post(`/api/trading/orders/${orderId}/close`);
|
|
if (response.data.success) {
|
|
await this.refreshData();
|
|
alert('平仓成功');
|
|
} else {
|
|
alert('平仓失败: ' + (response.data.message || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('平仓失败:', error);
|
|
alert('平仓失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
async cancelOrder(orderId) {
|
|
if (!confirm('确定要撤单吗?')) return;
|
|
|
|
try {
|
|
const response = await axios.post(`/api/trading/orders/${orderId}/cancel`);
|
|
if (response.data.success) {
|
|
await this.refreshData();
|
|
alert('撤单成功');
|
|
} else {
|
|
alert('撤单失败: ' + (response.data.message || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('撤单失败:', error);
|
|
alert('撤单失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
async resetAccount() {
|
|
if (!confirm('确定要重置账户吗?这将清除所有持仓和订单!')) return;
|
|
|
|
try {
|
|
const response = await axios.post('/api/trading/account/reset');
|
|
if (response.data.success) {
|
|
await this.refreshData();
|
|
alert('账户重置成功');
|
|
} else {
|
|
alert('重置失败: ' + (response.data.message || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('重置账户失败:', error);
|
|
alert('重置失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
formatTime(timeStr) {
|
|
if (!timeStr) return '-';
|
|
const date = new Date(timeStr);
|
|
return date.toLocaleString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
},
|
|
|
|
getCloseReason(reason) {
|
|
const map = {
|
|
'manual': '手动',
|
|
'stop_loss': '止损',
|
|
'take_profit': '止盈',
|
|
'timeout': '超时',
|
|
'signal': '信号'
|
|
};
|
|
return map[reason] || reason || '-';
|
|
}
|
|
},
|
|
mounted() {
|
|
this.refreshData();
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|