feat: add order book
This commit is contained in:
@@ -1,45 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
interface OrderBookItem {
|
||||
price: string;
|
||||
amount: string;
|
||||
total: string;
|
||||
}
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
interface OrderBookData {
|
||||
bids: OrderBookItem[]; // 买单
|
||||
asks: OrderBookItem[]; // 卖单
|
||||
}
|
||||
type Item = Treaty.Data<typeof client.api.trading_pairs.orderbook.get>;
|
||||
|
||||
// TODO: 后续从API获取实时数据
|
||||
// const { data } = await client.api.trading.orderbook.get({ query: { symbol: 'BTC_USDT', depth: 20 } })
|
||||
const orderBook = ref<OrderBookData>({
|
||||
asks: [
|
||||
{ price: "43260.50", amount: "0.125", total: "5.407" },
|
||||
{ price: "43258.00", amount: "0.342", total: "14.792" },
|
||||
{ price: "43255.80", amount: "0.856", total: "37.027" },
|
||||
{ price: "43253.20", amount: "1.234", total: "53.350" },
|
||||
{ price: "43251.00", amount: "0.678", total: "29.324" },
|
||||
{ price: "43249.50", amount: "2.145", total: "92.740" },
|
||||
{ price: "43247.00", amount: "0.456", total: "19.720" },
|
||||
{ price: "43245.80", amount: "1.567", total: "67.768" },
|
||||
],
|
||||
bids: [
|
||||
{ price: "43250.50", amount: "0.234", total: "10.120" },
|
||||
{ price: "43248.00", amount: "0.567", total: "24.522" },
|
||||
{ price: "43245.50", amount: "1.234", total: "53.365" },
|
||||
{ price: "43243.20", amount: "0.789", total: "34.118" },
|
||||
{ price: "43240.00", amount: "2.456", total: "106.197" },
|
||||
{ price: "43238.50", amount: "0.345", total: "14.917" },
|
||||
{ price: "43235.00", amount: "1.678", total: "72.548" },
|
||||
{ price: "43232.80", amount: "0.923", total: "39.903" },
|
||||
],
|
||||
const props = defineProps<{
|
||||
symbol: string;
|
||||
}>();
|
||||
|
||||
const { data } = await safeClient(client.api.trading_pairs.orderbook.get({ query: { symbol: props.symbol, depth: 5 } }));
|
||||
|
||||
const latestPrice = computed(() => {
|
||||
if (!data.value || !data.value.asks?.length || !data.value.bids?.length) {
|
||||
return "0.00";
|
||||
}
|
||||
const askPrice = Number(data.value.asks[0].price);
|
||||
const bidPrice = Number(data.value.bids[0].price);
|
||||
const price = ((askPrice + bidPrice) / 2).toFixed(2);
|
||||
return price;
|
||||
});
|
||||
|
||||
// 计算买卖盘深度百分比(用于显示背景条)
|
||||
function getDepthPercent(items: OrderBookItem[], index: number) {
|
||||
const maxTotal = Math.max(...items.map(item => Number.parseFloat(item.total)));
|
||||
const currentTotal = Number.parseFloat(items[index].total);
|
||||
return (currentTotal / maxTotal) * 100;
|
||||
function getTotal(item: Item["asks" | "bids"][number]) {
|
||||
const price = Number(item.price);
|
||||
const quantity = Number(item.quantity);
|
||||
return (price * quantity).toFixed(2);
|
||||
}
|
||||
function getDepthPercent(items?: Item["asks" | "bids"], index?: number) {
|
||||
if (!items || index === undefined)
|
||||
return 0;
|
||||
const totalValues = items.map(item => Number(item.price) * Number(item.quantity));
|
||||
const maxTotal = Math.max(...totalValues);
|
||||
const itemTotal = totalValues[index];
|
||||
return (itemTotal / maxTotal) * 100;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -48,30 +40,30 @@ function getDepthPercent(items: OrderBookItem[], index: number) {
|
||||
<!-- 卖单 (asks) - 从下往上排列 -->
|
||||
<div class="asks-section">
|
||||
<div
|
||||
v-for="(ask, index) in orderBook.asks.slice().reverse()"
|
||||
v-for="(ask, index) in data?.asks"
|
||||
:key="`ask-${index}`"
|
||||
class="order-item ask-item"
|
||||
>
|
||||
<div
|
||||
class="depth-bg ask-depth"
|
||||
:style="{ width: `${getDepthPercent(orderBook.asks, orderBook.asks.length - 1 - index)}%` }"
|
||||
:style="{ width: `${getDepthPercent(data?.asks, index)}%` }"
|
||||
/>
|
||||
<div class="order-content">
|
||||
<span class="price text-danger-500">{{ ask.price }}</span>
|
||||
<span class="amount">{{ ask.amount }}</span>
|
||||
<span class="total">{{ ask.total }}</span>
|
||||
<span class="price text-danger-500">{{ Number(ask.price).toString() }}</span>
|
||||
<span class="amount">{{ Number(ask.quantity).toString() }}</span>
|
||||
<span class="total">{{ getTotal(ask) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间分隔 - 显示最新成交价 -->
|
||||
<div class="spread-section">
|
||||
<div class="flex items-center justify-between px-3 py-2">
|
||||
<div class="flex flex-col px-3 py-2">
|
||||
<div class="text-lg font-semibold text-success-500">
|
||||
43250.50
|
||||
{{ latestPrice }}
|
||||
</div>
|
||||
<div class="text-xs text-text-500">
|
||||
≈ $43,250.50
|
||||
≈ ${{ Number(latestPrice).toLocaleString('en-US') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,18 +71,18 @@ function getDepthPercent(items: OrderBookItem[], index: number) {
|
||||
<!-- 买单 (bids) -->
|
||||
<div class="bids-section">
|
||||
<div
|
||||
v-for="(bid, index) in orderBook.bids"
|
||||
v-for="(bid, index) in data?.bids"
|
||||
:key="`bid-${index}`"
|
||||
class="order-item bid-item"
|
||||
>
|
||||
<div
|
||||
class="depth-bg bid-depth"
|
||||
:style="{ width: `${getDepthPercent(orderBook.bids, index)}%` }"
|
||||
:style="{ width: `${getDepthPercent(data?.bids, index)}%` }"
|
||||
/>
|
||||
<div class="order-content">
|
||||
<span class="price text-success-500">{{ bid.price }}</span>
|
||||
<span class="amount">{{ bid.amount }}</span>
|
||||
<span class="total">{{ bid.total }}</span>
|
||||
<span class="price text-success-500">{{ Number(bid.price).toString() }}</span>
|
||||
<span class="amount">{{ Number(bid.quantity).toString() }}</span>
|
||||
<span class="total">{{ getTotal(bid) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { client, safeClient } from "@/api";
|
||||
import { TradeTypeEnum } from "@/api/enum";
|
||||
import { TradingViewChart } from "@/tradingview/index";
|
||||
import ConfirmOrder from "./components/confirm-order.vue";
|
||||
import OrderBook from "./components/order-book.vue";
|
||||
import OrdersPanel from "./components/orders-panel.vue";
|
||||
import TradePairsModal from "./components/trade-pairs-modal.vue";
|
||||
import TradeSwitch from "./components/trade-switch.vue";
|
||||
@@ -106,8 +107,8 @@ async function handleSubmit() {
|
||||
<ion-content :fullscreen="true">
|
||||
<TradingViewChart ref="tradingViewInst" class="mb-5" height="300px" :symbol="symbol" :options="tradingviewOptions" />
|
||||
|
||||
<div class="grid grid-cols-5 px-4">
|
||||
<div class="col-span-3 space-y-2">
|
||||
<div class="grid grid-cols-6 px-4">
|
||||
<div class="col-span-4 space-y-2">
|
||||
<TradeSwitch v-model:active="mode" @update:active="val => form.side = val" />
|
||||
<TradeWay v-model="form" />
|
||||
<template v-if="form.orderType === 'limit'">
|
||||
@@ -139,7 +140,9 @@ async function handleSubmit() {
|
||||
<ConfirmOrder :form="form" />
|
||||
</ion-modal>
|
||||
</div>
|
||||
<div class="col-span-2" />
|
||||
<div class="col-span-2">
|
||||
<OrderBook :symbol="form.symbol" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 px-4 pb-4">
|
||||
|
||||
Reference in New Issue
Block a user