feat: 更新 @riwa/api-types 依赖至 0.0.132,优化订单相关组件,修正订单状态枚举
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
"@elysiajs/eden": "^1.4.5",
|
||||
"@ionic/vue": "^8.7.11",
|
||||
"@ionic/vue-router": "^8.7.11",
|
||||
"@riwa/api-types": "http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz",
|
||||
"@riwa/api-types": "http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -69,8 +69,8 @@ importers:
|
||||
specifier: ^8.7.11
|
||||
version: 8.7.11(@stencil/core@4.39.0)(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3))
|
||||
'@riwa/api-types':
|
||||
specifier: http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz
|
||||
version: '@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))'
|
||||
specifier: http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz
|
||||
version: '@riwa/eden@http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))'
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2))
|
||||
@@ -2804,9 +2804,9 @@ packages:
|
||||
'@remirror/core-constants@3.0.0':
|
||||
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
|
||||
|
||||
'@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz':
|
||||
resolution: {tarball: http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz}
|
||||
version: 0.0.130
|
||||
'@riwa/eden@http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz':
|
||||
resolution: {tarball: http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz}
|
||||
version: 0.0.132
|
||||
peerDependencies:
|
||||
'@elysiajs/eden': ^1.4.5
|
||||
|
||||
@@ -12161,7 +12161,7 @@ snapshots:
|
||||
|
||||
'@remirror/core-constants@3.0.0': {}
|
||||
|
||||
'@riwa/eden@http://192.168.1.7:9527/api/riwa-eden-0.0.130.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
'@riwa/eden@http://192.168.1.7:9528/api/riwa-eden-0.0.132.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@elysiajs/eden': 1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ const { data } = await safeClient(client.api.trading_pairs.orderbook.get({ query
|
||||
const maxBidAmount = computed(() => {
|
||||
if (!data.value?.bids)
|
||||
return 0;
|
||||
return Math.max(...data.value.bids.map(item => Number(item[1])));
|
||||
return Math.max(...data.value.bids.map(item => Number(item.price)));
|
||||
});
|
||||
|
||||
const maxAskAmount = computed(() => {
|
||||
if (!data.value?.asks)
|
||||
return 0;
|
||||
return Math.max(...data.value.asks.map(item => Number(item[1])));
|
||||
return Math.max(...data.value.asks.map(item => Number(item.price)));
|
||||
});
|
||||
|
||||
// 计算每个订单的占比百分比
|
||||
@@ -65,17 +65,17 @@ function formatAmount(amount: string) {
|
||||
<!-- 背景色显示占比 -->
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 bg-(--ion-color-success)/10"
|
||||
:style="{ width: `${getBidPercentage(bid[1])}%` }"
|
||||
:style="{ width: `${getBidPercentage(bid.price)}%` }"
|
||||
/>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="relative z-1 text-left text-(--ion-color-success)">
|
||||
{{ formatPrice(bid[0]) }}
|
||||
{{ formatPrice(bid.price) }}
|
||||
</div>
|
||||
|
||||
<!-- 数量 -->
|
||||
<div class="relative z-1 text-right text-(--ion-color-step-600)">
|
||||
{{ formatAmount(bid[1]) }}
|
||||
{{ formatAmount(bid.quantity) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,17 +103,17 @@ function formatAmount(amount: string) {
|
||||
<!-- 背景色显示占比 -->
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 bg-(--ion-color-danger)/10"
|
||||
:style="{ width: `${getAskPercentage(ask[1])}%` }"
|
||||
:style="{ width: `${getAskPercentage(ask.price)}%` }"
|
||||
/>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="relative z-1 text-left text-(--ion-color-danger)">
|
||||
{{ formatPrice(ask[0]) }}
|
||||
{{ formatPrice(ask.price) }}
|
||||
</div>
|
||||
|
||||
<!-- 数量 -->
|
||||
<div class="relative z-1 text-right text-(--ion-color-step-600)">
|
||||
{{ formatAmount(ask[1]) }}
|
||||
{{ formatAmount(ask.quantity) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
98
src/views/trade/components/order-card.vue
Normal file
98
src/views/trade/components/order-card.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script lang='ts' setup>
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import { alertController, toastController } from "@ionic/vue";
|
||||
import { client, safeClient } from "@/api";
|
||||
import { orderStatusMap, tradeWayConfig } from "../config";
|
||||
|
||||
type Item = Treaty.Data<typeof client.api.spot_order.list.get>["data"][number];
|
||||
|
||||
const props = defineProps<{ item: Item; showCancel?: boolean }>();
|
||||
const emit = defineEmits<{ refresh: [] }>();
|
||||
|
||||
const currentTradeWay = computed(() => {
|
||||
return tradeWayConfig.find(item => item.value === props.item.orderType);
|
||||
});
|
||||
|
||||
async function cancelOrder(orderId: string) {
|
||||
const alert = await alertController.create({
|
||||
header: "取消订单",
|
||||
message: "确定要取消该订单吗?",
|
||||
buttons: [
|
||||
{
|
||||
text: "取消",
|
||||
role: "cancel",
|
||||
},
|
||||
{
|
||||
text: "确定",
|
||||
role: "destructive",
|
||||
handler: async () => {
|
||||
await safeClient(client.api.spot_order.cancel.post({ orderId }));
|
||||
emit("refresh");
|
||||
await alert.dismiss();
|
||||
const toast = await toastController
|
||||
.create({
|
||||
message: "订单已取消",
|
||||
duration: 2000,
|
||||
position: "bottom",
|
||||
color: "success",
|
||||
});
|
||||
await toast.present();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="px-2 py-0.5 rounded text-[11px] font-semibold"
|
||||
:class="item.side === 'buy'
|
||||
? 'bg-success/10 text-(--ion-color-success)'
|
||||
: 'bg-danger/10 text-(--ion-color-danger)'"
|
||||
>
|
||||
{{ item.side === 'buy' ? '买入' : '卖出' }}
|
||||
</span>
|
||||
<span class="text-sm font-medium">{{ item.symbol }}</span>
|
||||
<ion-badge :color="orderStatusMap[item.status].color" class="text-[10px] px-1.5 py-0.5">
|
||||
{{ orderStatusMap[item.status].text }}
|
||||
</ion-badge>
|
||||
</div>
|
||||
<button
|
||||
v-if="showCancel && item.status !== 'filled'"
|
||||
class="px-3 py-1 bg-transparent border border-(--ion-color-danger) text-(--ion-color-danger) rounded-md text-xs transition-all active:bg-(--ion-color-danger) active:text-white"
|
||||
@click="cancelOrder(item.id)"
|
||||
>
|
||||
撤单
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 mb-2">
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-(--ion-text-color-step-400)">{{ currentTradeWay?.name }}</span>
|
||||
<span class="text-(--ion-text-color) font-medium">{{ item.orderType === 'limit' ? Number(item.price).toString() : '-' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-(--ion-text-color-step-400)">数量</span>
|
||||
<span class="text-(--ion-text-color) font-medium">{{ Number(item.quantity).toString() }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-(--ion-text-color-step-400)">成交</span>
|
||||
<span class="text-(--ion-text-color) font-medium">{{ Number(item.price).toString() }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-(--ion-text-color-step-400)">总额</span>
|
||||
<span class="text-(--ion-text-color) font-medium">{{ Number(item.totalAmount).toString() }} USDT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-2">
|
||||
<span class="text-[11px] text-(--ion-text-color-step-500)">{{ useDateFormat(item.createdAt, 'YYYY/MM/DD HH:mm:ss') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
73
src/views/trade/components/order-list.vue
Normal file
73
src/views/trade/components/order-list.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang='ts' setup>
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import type { InfiniteScrollCustomEvent } from "@ionic/vue";
|
||||
import type { TreatyQuery } from "@/api/types";
|
||||
import { client, safeClient } from "@/api";
|
||||
import OrderCard from "./order-card.vue";
|
||||
|
||||
type Item = Treaty.Data<typeof client.api.spot_order.list.get>["data"][number];
|
||||
type Body = TreatyQuery<typeof client.api.spot_order.list.get>;
|
||||
|
||||
const props = defineProps<{ symbol: string; status: Body["status"]; showCancel?: boolean }>();
|
||||
|
||||
const data = ref<Item[]>([]);
|
||||
const isFinished = ref(false);
|
||||
const [query] = useResetRef<Body>({
|
||||
symbol: props.symbol,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
async function fetchData() {
|
||||
const { data: responseData } = await safeClient(client.api.spot_order.list.get({
|
||||
query: {
|
||||
...query.value,
|
||||
status: props.status,
|
||||
},
|
||||
}));
|
||||
data.value.push(...(responseData.value?.data || []));
|
||||
isFinished.value = (responseData.value?.data.length || 0) < query.value.limit!;
|
||||
}
|
||||
function resetRwaData() {
|
||||
query.value.offset = 0;
|
||||
data.value = [];
|
||||
isFinished.value = false;
|
||||
}
|
||||
async function handleRefresh() {
|
||||
resetRwaData();
|
||||
await fetchData();
|
||||
}
|
||||
async function handleInfinite(event: InfiniteScrollCustomEvent) {
|
||||
if (isFinished.value) {
|
||||
event.target.complete();
|
||||
event.target.disabled = true;
|
||||
return;
|
||||
}
|
||||
query.value.offset! += query.value.limit!;
|
||||
await fetchData();
|
||||
setTimeout(() => {
|
||||
event.target.complete();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-empty v-if="data.length === 0" title="当前暂无委托" />
|
||||
<template v-else>
|
||||
<div v-for="item in data" :key="item.id" class="bg-faint rounded-xl p-3">
|
||||
<OrderCard :item="item" :show-cancel="props.showCancel" @refresh="handleRefresh" />
|
||||
</div>
|
||||
<ion-infinite-scroll threshold="100px" @ion-infinite="handleInfinite">
|
||||
<ion-infinite-scroll-content
|
||||
loading-spinner="bubbles"
|
||||
loading-text="加载中..."
|
||||
/>
|
||||
</ion-infinite-scroll>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
@@ -1,111 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import { alertController, toastController } from "@ionic/vue";
|
||||
import { client, safeClient } from "@/api";
|
||||
import { orderStatusMap } from "../config";
|
||||
import OrderList from "./order-list.vue";
|
||||
|
||||
type Item = Treaty.Data<typeof client.api.spot_order.list.get>["orders"][number];
|
||||
type TabType = "current" | "history";
|
||||
|
||||
const props = defineProps<{ symbol: string }>();
|
||||
|
||||
const activeTab = ref<TabType>("current");
|
||||
const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ item: Item }>();
|
||||
|
||||
const { data } = await safeClient(client.api.spot_order.list.get({ query: { symbol: props.symbol } }));
|
||||
const currentOrders = computed(() =>
|
||||
data.value?.orders.filter(order => order.status !== "filled" && order.status !== "cancelled") || [],
|
||||
);
|
||||
const historyOrders = computed(() =>
|
||||
data.value?.orders.filter(order => order.status === "filled" || order.status === "cancelled") || [],
|
||||
);
|
||||
|
||||
function getStatusConfig(status: Item["status"]) {
|
||||
return orderStatusMap[status];
|
||||
}
|
||||
async function cancelOrder(orderId: string) {
|
||||
const alert = await alertController.create({
|
||||
header: "取消订单",
|
||||
message: "确定要取消该订单吗?",
|
||||
buttons: [
|
||||
{
|
||||
text: "取消",
|
||||
role: "cancel",
|
||||
},
|
||||
{
|
||||
text: "确定",
|
||||
role: "destructive",
|
||||
handler: async () => {
|
||||
await safeClient(client.api.spot_order.cancel.post({ orderId }));
|
||||
await alert.dismiss();
|
||||
const toast = await toastController
|
||||
.create({
|
||||
message: "订单已取消",
|
||||
duration: 2000,
|
||||
position: "bottom",
|
||||
color: "success",
|
||||
});
|
||||
await toast.present();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
console.log("取消订单", orderId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full bg-(--ion-background-color)">
|
||||
<DefineTemplate v-slot="{ item }">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="px-2 py-0.5 rounded text-[11px] font-semibold"
|
||||
:class="item.side === 'buy'
|
||||
? 'bg-success/10 text-(--ion-color-success)'
|
||||
: 'bg-danger/10 text-(--ion-color-danger)'"
|
||||
>
|
||||
{{ item.side === 'buy' ? '买入' : '卖出' }}
|
||||
</span>
|
||||
<span class="text-sm font-medium">{{ item.symbol }}</span>
|
||||
<ion-badge :color="getStatusConfig(item.status).color" class="text-[10px] px-1.5 py-0.5">
|
||||
{{ getStatusConfig(item.status).text }}
|
||||
</ion-badge>
|
||||
</div>
|
||||
<button
|
||||
v-if="activeTab === 'current' && item.status !== 'filled'"
|
||||
class="px-3 py-1 bg-transparent border border-(--ion-color-danger) text-(--ion-color-danger) rounded-md text-xs transition-all active:bg-(--ion-color-danger) active:text-white"
|
||||
@click="cancelOrder(item.id)"
|
||||
>
|
||||
撤单
|
||||
</button>
|
||||
</div>
|
||||
</DefineTemplate>
|
||||
|
||||
<ui-tabs v-model="activeTab" size="small">
|
||||
<ui-tab-pane
|
||||
title="当前委托"
|
||||
name="current"
|
||||
>
|
||||
<ui-empty v-if="currentOrders.length === 0" title="当前暂无委托" />
|
||||
<template v-else>
|
||||
<div v-for="order in currentOrders" :key="order.id" class="bg-faint rounded-xl p-3">
|
||||
<ReuseTemplate :item="order" />
|
||||
</div>
|
||||
</template>
|
||||
<ui-tab-pane title="当前委托" name="current">
|
||||
<OrderList show-cancel :symbol="props.symbol" :status="['open', 'partial_filled', 'pending', 'rejected']" />
|
||||
</ui-tab-pane>
|
||||
<ui-tab-pane
|
||||
title="历史记录"
|
||||
name="history"
|
||||
>
|
||||
<ui-empty v-if="historyOrders.length === 0" title="暂无历史委托" />
|
||||
<template v-else>
|
||||
<div v-for="order in historyOrders" :key="order.id" class="bg-faint rounded-xl p-3">
|
||||
<ReuseTemplate :item="order" />
|
||||
</div>
|
||||
</template>
|
||||
<ui-tab-pane title="历史记录" name="history">
|
||||
<OrderList :symbol="props.symbol" :status="['filled', 'cancelled']" />
|
||||
</ui-tab-pane>
|
||||
</ui-tabs>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export enum OrderStatusEnum {
|
||||
OPEN = "open",
|
||||
PARTIALLY_FILLED = "partially_filled",
|
||||
FILLED = "filled",
|
||||
CANCELED = "canceled",
|
||||
CANCELED = "cancelled",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ async function handleSubmit() {
|
||||
console.error("订单验证失败:", err);
|
||||
}
|
||||
}
|
||||
function gotoTokenized() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -95,7 +97,7 @@ async function handleSubmit() {
|
||||
<ion-icon :icon="caretDownOutline" />
|
||||
</div>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button>
|
||||
<ion-button @click="gotoTokenized">
|
||||
<MaterialSymbolsCandlestickChartOutline class="text-xl" />
|
||||
</ion-button>
|
||||
<ion-button>
|
||||
|
||||
Reference in New Issue
Block a user