From fffd7461dfadd9f7e60adfcbe887758af9c46fd1 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 28 Feb 2026 23:59:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E9=99=90=E4=BB=B7?= =?UTF-8?q?=E5=8D=95=E4=BB=BD=E9=A2=9D=E6=98=BE=E7=A4=BA=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/order.md | 23 +++++++++++++++++++++++ src/api/order.ts | 22 ++++++++++++++-------- src/views/EventMarkets.vue | 21 +++++++++++++++------ src/views/TradeDetail.vue | 21 +++++++++++++++------ 4 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 docs/api/order.md diff --git a/docs/api/order.md b/docs/api/order.md new file mode 100644 index 0000000..5eea3f6 --- /dev/null +++ b/docs/api/order.md @@ -0,0 +1,23 @@ +# order.ts + +**路径**:`src/api/order.ts` + +## 功能用途 + +订单相关 API:获取订单列表、取消订单,以及将 `ClobOrderItem` 映射为展示项(History、Open Orders)。 + +## 使用方式 + +```typescript +import { getOrderList, mapOrderToOpenOrderItem, mapOrderToHistoryItem, cancelOrder } from '@/api/order' +``` + +## 数据单位约定 + +- **price**:整数,已乘 10000(bps),`priceCents = price / 100` +- **originalSize / sizeMatched**:按 6 位小数传(1_000_000 = 1 share),展示时除以 `ORDER_SIZE_SCALE` 转为实际份额 + +## 扩展方式 + +1. 新增订单状态或筛选参数时,更新 `GetOrderListParams` 与 `getOrderList` +2. 展示格式变更时,调整 `mapOrderToOpenOrderItem`、`mapOrderToHistoryItem` 的格式化逻辑 diff --git a/src/api/order.ts b/src/api/order.ts index d2ac017..0b6f453 100644 --- a/src/api/order.ts +++ b/src/api/order.ts @@ -126,6 +126,9 @@ export interface HistoryDisplayItem { /** Side: Buy=1, Sell=2 */ const Side = { Buy: 1, Sell: 2 } as const +/** 订单份额接口按 6 位小数传(1_000_000 = 1 share),需除以该系数转为展示值 */ +const ORDER_SIZE_SCALE = 1_000_000 + function formatTimeAgo(createdAt: string | undefined): string { if (!createdAt) return '' const d = new Date(createdAt) @@ -140,7 +143,7 @@ function formatTimeAgo(createdAt: string | undefined): string { /** * 将 ClobOrderItem 映射为钱包 History 展示项 - * price 为整数(已乘 10000),sizeMatched 为已成交份额 + * price 为整数(已乘 10000),size 按 6 位小数传(1_000_000 = 1 share) */ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem { const id = String(order.ID ?? '') @@ -151,11 +154,12 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem const activity = `${sideLabel} ${outcome}` const priceBps = order.price ?? 0 const priceCents = Math.round(priceBps / 100) - const size = order.sizeMatched ?? order.originalSize ?? 0 + const sizeRaw = order.sizeMatched ?? order.originalSize ?? 0 + const size = sizeRaw / ORDER_SIZE_SCALE const valueUsd = (priceBps / 10000) * size const value = `$${valueUsd.toFixed(2)}` const verb = sideNum === Side.Sell ? 'Sold' : 'Bought' - const activityDetail = `${verb} ${size} ${outcome} at ${priceCents}¢` + const activityDetail = `${verb} ${Math.floor(size)} ${outcome} at ${priceCents}¢` const avgPrice = `${priceCents}¢` const timeAgo = formatTimeAgo(order.createdAt) return { @@ -169,7 +173,7 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem profitLossNegative: false, timeAgo, avgPrice, - shares: String(size), + shares: String(Math.floor(size)), } } @@ -194,7 +198,7 @@ const OrderType = { GTC: 0, GTD: 1 } as const /** * 将 ClobOrderItem 映射为钱包 Open Orders 展示项(未成交订单) - * price 为整数(已乘 10000) + * price 为整数(已乘 10000),originalSize/sizeMatched 按 6 位小数传(1_000_000 = 1 share) */ export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem { const id = String(order.ID ?? '') @@ -205,9 +209,11 @@ export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayI const priceBps = order.price ?? 0 const priceCents = Math.round(priceBps / 100) const price = `${priceCents}¢` - const originalSize = order.originalSize ?? 0 - const sizeMatched = order.sizeMatched ?? 0 - const filled = `${sizeMatched}/${originalSize}` + const originalSizeRaw = order.originalSize ?? 0 + const sizeMatchedRaw = order.sizeMatched ?? 0 + const originalSize = originalSizeRaw / ORDER_SIZE_SCALE + const sizeMatched = sizeMatchedRaw / ORDER_SIZE_SCALE + const filled = `${Math.floor(sizeMatched)}/${Math.floor(originalSize)}` const totalUsd = (priceBps / 10000) * originalSize const total = `$${totalUsd.toFixed(2)}` const expiration = diff --git a/src/views/EventMarkets.vue b/src/views/EventMarkets.vue index 0d8a847..bbb85fd 100644 --- a/src/views/EventMarkets.vue +++ b/src/views/EventMarkets.vue @@ -248,6 +248,8 @@ const tradeInitialOption = ref<'yes' | 'no' | undefined>(undefined) const tradeSheetOpen = ref(false) /** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */ const tradeSheetRenderContent = ref(false) +/** 从三点菜单点击 Merge/Split 时待打开的弹窗,等 TradeComponent 挂载后执行 */ +const pendingMergeSplitDialog = ref<'merge' | 'split' | null>(null) /** 移动端底部栏三点菜单开关 */ const mobileMenuOpen = ref(false) /** TradeComponent 引用,用于从底部栏触发 Merge/Split */ @@ -583,19 +585,15 @@ function openSheetWithOption(side: 'yes' | 'no') { /** 从底部栏三点菜单打开 Merge 弹窗 */ function openMergeFromBar() { mobileMenuOpen.value = false + pendingMergeSplitDialog.value = 'merge' tradeSheetOpen.value = true - nextTick(() => { - tradeComponentRef.value?.openMergeDialog?.() - }) } /** 从底部栏三点菜单打开 Split 弹窗 */ function openSplitFromBar() { mobileMenuOpen.value = false + pendingMergeSplitDialog.value = 'split' tradeSheetOpen.value = true - nextTick(() => { - tradeComponentRef.value?.openSplitDialog?.() - }) } function onTradeSubmit(payload: { @@ -729,8 +727,19 @@ watch(tradeSheetOpen, (open) => { tradeSheetMountTimer = setTimeout(() => { tradeSheetRenderContent.value = true tradeSheetMountTimer = undefined + nextTick(() => { + const pending = pendingMergeSplitDialog.value + if (pending === 'merge') { + pendingMergeSplitDialog.value = null + tradeComponentRef.value?.openMergeDialog?.() + } else if (pending === 'split') { + pendingMergeSplitDialog.value = null + tradeComponentRef.value?.openSplitDialog?.() + } + }) }, 50) } else { + pendingMergeSplitDialog.value = null if (tradeSheetMountTimer) { clearTimeout(tradeSheetMountTimer) tradeSheetMountTimer = undefined diff --git a/src/views/TradeDetail.vue b/src/views/TradeDetail.vue index 9e14cc6..34e2692 100644 --- a/src/views/TradeDetail.vue +++ b/src/views/TradeDetail.vue @@ -743,6 +743,8 @@ const tradeInitialTabFromBar = ref<'buy' | 'sell' | undefined>(undefined) const tradeSheetOpen = ref(false) /** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */ const tradeSheetRenderContent = ref(false) +/** 从三点菜单点击 Merge/Split 时待打开的弹窗,等 TradeComponent 挂载后执行 */ +const pendingMergeSplitDialog = ref<'merge' | 'split' | null>(null) /** 从持仓 Sell 打开的弹窗 */ const sellDialogOpen = ref(false) /** 控制 Sell 弹窗内 TradeComponent 的渲染,延迟卸载以避免 emitsOptions 竞态 */ @@ -773,18 +775,14 @@ function openSheetWithOption(side: 'yes' | 'no') { function openMergeFromBar() { mobileMenuOpen.value = false + pendingMergeSplitDialog.value = 'merge' tradeSheetOpen.value = true - nextTick(() => { - mobileTradeComponentRef.value?.openMergeDialog?.() - }) } function openSplitFromBar() { mobileMenuOpen.value = false + pendingMergeSplitDialog.value = 'split' tradeSheetOpen.value = true - nextTick(() => { - mobileTradeComponentRef.value?.openSplitDialog?.() - }) } const toastStore = useToastStore() @@ -863,8 +861,19 @@ watch(tradeSheetOpen, (open) => { tradeSheetMountTimer = setTimeout(() => { tradeSheetRenderContent.value = true tradeSheetMountTimer = undefined + nextTick(() => { + const pending = pendingMergeSplitDialog.value + if (pending === 'merge') { + pendingMergeSplitDialog.value = null + mobileTradeComponentRef.value?.openMergeDialog?.() + } else if (pending === 'split') { + pendingMergeSplitDialog.value = null + mobileTradeComponentRef.value?.openSplitDialog?.() + } + }) }, 50) } else { + pendingMergeSplitDialog.value = null if (tradeSheetMountTimer) { clearTimeout(tradeSheetMountTimer) tradeSheetMountTimer = undefined