优化:限价单份额显示单位更改

This commit is contained in:
ivan 2026-02-28 23:59:50 +08:00
parent df82732fad
commit fffd7461df
4 changed files with 67 additions and 20 deletions

23
docs/api/order.md Normal file
View File

@ -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**:整数,已乘 10000bps`priceCents = price / 100`
- **originalSize / sizeMatched**:按 6 位小数传1_000_000 = 1 share展示时除以 `ORDER_SIZE_SCALE` 转为实际份额
## 扩展方式
1. 新增订单状态或筛选参数时,更新 `GetOrderListParams``getOrderList`
2. 展示格式变更时,调整 `mapOrderToOpenOrderItem``mapOrderToHistoryItem` 的格式化逻辑

View File

@ -126,6 +126,9 @@ export interface HistoryDisplayItem {
/** Side: Buy=1, Sell=2 */ /** Side: Buy=1, Sell=2 */
const Side = { Buy: 1, Sell: 2 } as const 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 { function formatTimeAgo(createdAt: string | undefined): string {
if (!createdAt) return '' if (!createdAt) return ''
const d = new Date(createdAt) const d = new Date(createdAt)
@ -140,7 +143,7 @@ function formatTimeAgo(createdAt: string | undefined): string {
/** /**
* ClobOrderItem History * ClobOrderItem History
* price 10000sizeMatched * price 10000size 6 1_000_000 = 1 share
*/ */
export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem { export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem {
const id = String(order.ID ?? '') const id = String(order.ID ?? '')
@ -151,11 +154,12 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem
const activity = `${sideLabel} ${outcome}` const activity = `${sideLabel} ${outcome}`
const priceBps = order.price ?? 0 const priceBps = order.price ?? 0
const priceCents = Math.round(priceBps / 100) 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 valueUsd = (priceBps / 10000) * size
const value = `$${valueUsd.toFixed(2)}` const value = `$${valueUsd.toFixed(2)}`
const verb = sideNum === Side.Sell ? 'Sold' : 'Bought' 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 avgPrice = `${priceCents}¢`
const timeAgo = formatTimeAgo(order.createdAt) const timeAgo = formatTimeAgo(order.createdAt)
return { return {
@ -169,7 +173,7 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem
profitLossNegative: false, profitLossNegative: false,
timeAgo, timeAgo,
avgPrice, avgPrice,
shares: String(size), shares: String(Math.floor(size)),
} }
} }
@ -194,7 +198,7 @@ const OrderType = { GTC: 0, GTD: 1 } as const
/** /**
* ClobOrderItem Open Orders * ClobOrderItem Open Orders
* price 10000 * price 10000originalSize/sizeMatched 6 1_000_000 = 1 share
*/ */
export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem { export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem {
const id = String(order.ID ?? '') const id = String(order.ID ?? '')
@ -205,9 +209,11 @@ export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayI
const priceBps = order.price ?? 0 const priceBps = order.price ?? 0
const priceCents = Math.round(priceBps / 100) const priceCents = Math.round(priceBps / 100)
const price = `${priceCents}¢` const price = `${priceCents}¢`
const originalSize = order.originalSize ?? 0 const originalSizeRaw = order.originalSize ?? 0
const sizeMatched = order.sizeMatched ?? 0 const sizeMatchedRaw = order.sizeMatched ?? 0
const filled = `${sizeMatched}/${originalSize}` 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 totalUsd = (priceBps / 10000) * originalSize
const total = `$${totalUsd.toFixed(2)}` const total = `$${totalUsd.toFixed(2)}`
const expiration = const expiration =

View File

@ -248,6 +248,8 @@ const tradeInitialOption = ref<'yes' | 'no' | undefined>(undefined)
const tradeSheetOpen = ref(false) const tradeSheetOpen = ref(false)
/** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */ /** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */
const tradeSheetRenderContent = ref(false) const tradeSheetRenderContent = ref(false)
/** 从三点菜单点击 Merge/Split 时待打开的弹窗,等 TradeComponent 挂载后执行 */
const pendingMergeSplitDialog = ref<'merge' | 'split' | null>(null)
/** 移动端底部栏三点菜单开关 */ /** 移动端底部栏三点菜单开关 */
const mobileMenuOpen = ref(false) const mobileMenuOpen = ref(false)
/** TradeComponent 引用,用于从底部栏触发 Merge/Split */ /** TradeComponent 引用,用于从底部栏触发 Merge/Split */
@ -583,19 +585,15 @@ function openSheetWithOption(side: 'yes' | 'no') {
/** 从底部栏三点菜单打开 Merge 弹窗 */ /** 从底部栏三点菜单打开 Merge 弹窗 */
function openMergeFromBar() { function openMergeFromBar() {
mobileMenuOpen.value = false mobileMenuOpen.value = false
pendingMergeSplitDialog.value = 'merge'
tradeSheetOpen.value = true tradeSheetOpen.value = true
nextTick(() => {
tradeComponentRef.value?.openMergeDialog?.()
})
} }
/** 从底部栏三点菜单打开 Split 弹窗 */ /** 从底部栏三点菜单打开 Split 弹窗 */
function openSplitFromBar() { function openSplitFromBar() {
mobileMenuOpen.value = false mobileMenuOpen.value = false
pendingMergeSplitDialog.value = 'split'
tradeSheetOpen.value = true tradeSheetOpen.value = true
nextTick(() => {
tradeComponentRef.value?.openSplitDialog?.()
})
} }
function onTradeSubmit(payload: { function onTradeSubmit(payload: {
@ -729,8 +727,19 @@ watch(tradeSheetOpen, (open) => {
tradeSheetMountTimer = setTimeout(() => { tradeSheetMountTimer = setTimeout(() => {
tradeSheetRenderContent.value = true tradeSheetRenderContent.value = true
tradeSheetMountTimer = undefined 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) }, 50)
} else { } else {
pendingMergeSplitDialog.value = null
if (tradeSheetMountTimer) { if (tradeSheetMountTimer) {
clearTimeout(tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
tradeSheetMountTimer = undefined tradeSheetMountTimer = undefined

View File

@ -743,6 +743,8 @@ const tradeInitialTabFromBar = ref<'buy' | 'sell' | undefined>(undefined)
const tradeSheetOpen = ref(false) const tradeSheetOpen = ref(false)
/** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */ /** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */
const tradeSheetRenderContent = ref(false) const tradeSheetRenderContent = ref(false)
/** 从三点菜单点击 Merge/Split 时待打开的弹窗,等 TradeComponent 挂载后执行 */
const pendingMergeSplitDialog = ref<'merge' | 'split' | null>(null)
/** 从持仓 Sell 打开的弹窗 */ /** 从持仓 Sell 打开的弹窗 */
const sellDialogOpen = ref(false) const sellDialogOpen = ref(false)
/** 控制 Sell 弹窗内 TradeComponent 的渲染,延迟卸载以避免 emitsOptions 竞态 */ /** 控制 Sell 弹窗内 TradeComponent 的渲染,延迟卸载以避免 emitsOptions 竞态 */
@ -773,18 +775,14 @@ function openSheetWithOption(side: 'yes' | 'no') {
function openMergeFromBar() { function openMergeFromBar() {
mobileMenuOpen.value = false mobileMenuOpen.value = false
pendingMergeSplitDialog.value = 'merge'
tradeSheetOpen.value = true tradeSheetOpen.value = true
nextTick(() => {
mobileTradeComponentRef.value?.openMergeDialog?.()
})
} }
function openSplitFromBar() { function openSplitFromBar() {
mobileMenuOpen.value = false mobileMenuOpen.value = false
pendingMergeSplitDialog.value = 'split'
tradeSheetOpen.value = true tradeSheetOpen.value = true
nextTick(() => {
mobileTradeComponentRef.value?.openSplitDialog?.()
})
} }
const toastStore = useToastStore() const toastStore = useToastStore()
@ -863,8 +861,19 @@ watch(tradeSheetOpen, (open) => {
tradeSheetMountTimer = setTimeout(() => { tradeSheetMountTimer = setTimeout(() => {
tradeSheetRenderContent.value = true tradeSheetRenderContent.value = true
tradeSheetMountTimer = undefined 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) }, 50)
} else { } else {
pendingMergeSplitDialog.value = null
if (tradeSheetMountTimer) { if (tradeSheetMountTimer) {
clearTimeout(tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
tradeSheetMountTimer = undefined tradeSheetMountTimer = undefined