|
|
|
@ -66,7 +66,7 @@ |
|
|
|
</div> |
|
|
|
<div |
|
|
|
:class="['tab-item', subTab === 'history' ? 'active' : '']" |
|
|
|
@click="subTab = 'history'" |
|
|
|
@click="switchSubTab('history')" |
|
|
|
> |
|
|
|
{{$t('automation.a12')}} |
|
|
|
</div> |
|
|
|
@ -118,23 +118,39 @@ |
|
|
|
<th>{{$t('automation.a20')}}</th> |
|
|
|
<th>{{$t('automation.a21')}}</th> |
|
|
|
<th>{{$t('automation.a22')}}</th> |
|
|
|
<th>{{$t('automation.a23')}}</th> |
|
|
|
<th>{{$t('automation.a24')}}</th> |
|
|
|
<th class="text-right">{{$t('automation.a25')}}</th> |
|
|
|
<th class="text-right">{{$t('automation.a23')}}</th> |
|
|
|
</tr> |
|
|
|
</thead> |
|
|
|
<tbody> |
|
|
|
<tr v-if="currentHistory.length === 0"> |
|
|
|
<td colspan="10" class="empty-text">{{$t('automation.a26')}}</td> |
|
|
|
<td colspan="8" class="empty-text">{{$t('automation.a26')}}</td> |
|
|
|
</tr> |
|
|
|
<tr v-for="item in currentHistory" :key="item.id"> |
|
|
|
<td>{{ item.trade_time }}</td> |
|
|
|
<td>{{ item.symbol }}</td> |
|
|
|
<td>{{ formatSide(item.side) }}</td> |
|
|
|
<td>{{ item.price }}</td> |
|
|
|
<td>{{ item.quantity }}</td> |
|
|
|
<td>{{ item.amount }}</td> |
|
|
|
<td :class="pnlClass(item.pnl)">{{ item.pnl }}</td> |
|
|
|
<td class="text-right"> |
|
|
|
<span :class="pnlClass(item.pnl)">{{ formatPnlStatus(item.pnl) }}</span> |
|
|
|
</td> |
|
|
|
</tr> |
|
|
|
<!-- 这里可以写 v-for 渲染历史数据 --> |
|
|
|
</tbody> |
|
|
|
</table> |
|
|
|
<!-- 分页占位 --> |
|
|
|
<div class="pagination"> |
|
|
|
<span class="arrow"><</span> |
|
|
|
<span class="page-num">1</span> |
|
|
|
<span class="arrow">></span> |
|
|
|
<div class="pagination" v-if="orderTotal > 0"> |
|
|
|
<span |
|
|
|
class="arrow" |
|
|
|
:class="{ disabled: orderPage <= 1 }" |
|
|
|
@click="changeOrderPage(orderPage - 1)" |
|
|
|
><</span> |
|
|
|
<span class="page-num">{{ orderPage }}</span> |
|
|
|
<span |
|
|
|
class="arrow" |
|
|
|
:class="{ disabled: orderPage >= orderTotalPages }" |
|
|
|
@click="changeOrderPage(orderPage + 1)" |
|
|
|
>></span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -186,6 +202,8 @@ |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import AiStock from '@/api/aiStock'; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: 'EaTrading', |
|
|
|
data() { |
|
|
|
@ -194,8 +212,12 @@ export default { |
|
|
|
subTab: 'list', // 'list' | 'history' |
|
|
|
modalType: '', // '' | 'deposit' | 'withdraw' |
|
|
|
formAmount: '', |
|
|
|
orderPage: 1, |
|
|
|
orderPerPage: 20, |
|
|
|
orderTotal: 0, |
|
|
|
orderList: [], |
|
|
|
|
|
|
|
// 账户数据 mock |
|
|
|
// 股票账户(接口数据) |
|
|
|
accountData: { |
|
|
|
stock: { |
|
|
|
total: '0.00', |
|
|
|
@ -228,10 +250,6 @@ export default { |
|
|
|
{ name: 'GRT/USDC', symbol: 'GRT', price: '0.0205', change: -5.94 }, |
|
|
|
{ name: 'HBAR/USDC', symbol: 'HBAR', price: '0.0815', change: -5.08 }, |
|
|
|
], |
|
|
|
|
|
|
|
// 历史订单 mock (默认空数组体现截图效果) |
|
|
|
stockHistory: [], |
|
|
|
cryptoHistory: [] |
|
|
|
} |
|
|
|
}, |
|
|
|
computed: { |
|
|
|
@ -245,13 +263,114 @@ export default { |
|
|
|
return this.mainTab === 'stock' ? this.stockList : this.cryptoList; |
|
|
|
}, |
|
|
|
currentHistory() { |
|
|
|
return this.mainTab === 'stock' ? this.stockHistory : this.cryptoHistory; |
|
|
|
return this.mainTab === 'stock' ? this.orderList : []; |
|
|
|
}, |
|
|
|
orderTotalPages() { |
|
|
|
return Math.ceil(this.orderTotal / this.orderPerPage) || 1; |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
mainTab(tab) { |
|
|
|
if (tab === 'stock') { |
|
|
|
this.fetchStockAccount(); |
|
|
|
} |
|
|
|
if (this.subTab === 'history') { |
|
|
|
if (tab === 'stock') { |
|
|
|
this.fetchOrders(1); |
|
|
|
} else { |
|
|
|
this.orderList = []; |
|
|
|
this.orderTotal = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
if (this.mainTab === 'stock') { |
|
|
|
this.fetchStockAccount(); |
|
|
|
} |
|
|
|
if (this.subTab === 'history' && this.mainTab === 'stock') { |
|
|
|
this.fetchOrders(1); |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
switchMainTab(tab) { |
|
|
|
this.mainTab = tab; |
|
|
|
// 切换主分类时,可以根据需求决定是否重置子菜单,这里默认保持原样 |
|
|
|
}, |
|
|
|
fetchStockAccount() { |
|
|
|
const today = this.getTodayDate(); |
|
|
|
Promise.all([ |
|
|
|
AiStock.getAccount({ loading: false }), |
|
|
|
AiStock.getOrders({ config_date: today, page: 1, per_page: 100 }, { loading: false }), |
|
|
|
AiStock.getOrders({ page: 1, per_page: 100 }, { loading: false }), |
|
|
|
]).then(([account, todayOrders, allOrders]) => { |
|
|
|
const gift = parseFloat(account.gift_balance) || 0; |
|
|
|
const profit = parseFloat(account.profit_balance) || 0; |
|
|
|
this.accountData.stock = { |
|
|
|
total: this.formatAmount(gift + profit), |
|
|
|
usable: this.formatAmount(profit), |
|
|
|
todayProfit: this.formatAmount(this.sumPnl(todayOrders.list)), |
|
|
|
totalProfit: this.formatAmount(this.sumPnl(allOrders.list)), |
|
|
|
}; |
|
|
|
}).catch(() => { |
|
|
|
this.accountData.stock = { |
|
|
|
total: '0.00', |
|
|
|
usable: '0.00', |
|
|
|
todayProfit: '0.00', |
|
|
|
totalProfit: '0.00', |
|
|
|
}; |
|
|
|
}); |
|
|
|
}, |
|
|
|
getTodayDate() { |
|
|
|
const d = new Date(); |
|
|
|
const y = d.getFullYear(); |
|
|
|
const m = String(d.getMonth() + 1).padStart(2, '0'); |
|
|
|
const day = String(d.getDate()).padStart(2, '0'); |
|
|
|
return `${y}-${m}-${day}`; |
|
|
|
}, |
|
|
|
sumPnl(list) { |
|
|
|
return (list || []).reduce((sum, item) => sum + (parseFloat(item.pnl) || 0), 0); |
|
|
|
}, |
|
|
|
formatAmount(value) { |
|
|
|
const num = parseFloat(value); |
|
|
|
if (Number.isNaN(num)) return '0.00'; |
|
|
|
return num.toFixed(2); |
|
|
|
}, |
|
|
|
switchSubTab(tab) { |
|
|
|
this.subTab = tab; |
|
|
|
if (tab === 'history' && this.mainTab === 'stock') { |
|
|
|
this.fetchOrders(1); |
|
|
|
} |
|
|
|
}, |
|
|
|
fetchOrders(page = this.orderPage) { |
|
|
|
if (this.mainTab !== 'stock') return; |
|
|
|
AiStock.getOrders({ |
|
|
|
page, |
|
|
|
per_page: this.orderPerPage, |
|
|
|
}, { loading: false }).then(data => { |
|
|
|
this.orderList = data.list || []; |
|
|
|
this.orderTotal = data.total || 0; |
|
|
|
this.orderPage = data.page || page; |
|
|
|
}).catch(() => { |
|
|
|
this.orderList = []; |
|
|
|
this.orderTotal = 0; |
|
|
|
}); |
|
|
|
}, |
|
|
|
changeOrderPage(page) { |
|
|
|
if (page < 1 || page > this.orderTotalPages) return; |
|
|
|
this.fetchOrders(page); |
|
|
|
}, |
|
|
|
formatSide(side) { |
|
|
|
return side === 'buy' ? this.$t('common.buy-in') : this.$t('common.sell-out'); |
|
|
|
}, |
|
|
|
pnlClass(pnl) { |
|
|
|
const value = parseFloat(pnl); |
|
|
|
if (Number.isNaN(value) || value === 0) return ''; |
|
|
|
return value > 0 ? 'text-green' : 'text-red'; |
|
|
|
}, |
|
|
|
formatPnlStatus(pnl) { |
|
|
|
const value = parseFloat(pnl); |
|
|
|
if (Number.isNaN(value) || value === 0) return '-'; |
|
|
|
return value > 0 ? this.$t('automation.a33') : this.$t('automation.a34'); |
|
|
|
}, |
|
|
|
showModal(type) { |
|
|
|
this.modalType = type; |
|
|
|
@ -501,6 +620,10 @@ export default { |
|
|
|
cursor: pointer; |
|
|
|
color: #666; |
|
|
|
} |
|
|
|
.pagination .arrow.disabled { |
|
|
|
color: #ccc; |
|
|
|
cursor: not-allowed; |
|
|
|
} |
|
|
|
|
|
|
|
/* ================= 弹窗样式 ================= */ |
|
|
|
.modal-overlay { |
|
|
|
|