feat: 更新 @riwa/api-types 依赖至 0.0.132,优化订单相关组件,修正订单状态枚举

This commit is contained in:
2026-01-13 00:09:30 +07:00
parent 2297e9bdcf
commit f516408d75
8 changed files with 195 additions and 112 deletions

View File

@@ -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
View File

@@ -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))

View File

@@ -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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -59,7 +59,7 @@ export enum OrderStatusEnum {
OPEN = "open",
PARTIALLY_FILLED = "partially_filled",
FILLED = "filled",
CANCELED = "canceled",
CANCELED = "cancelled",
REJECTED = "rejected",
}

View File

@@ -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>