From 604623bd37a9700e9726efb35f5c13bd31d91787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E4=B8=81?= Date: Sat, 4 Jul 2026 22:09:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=9A=E5=8D=96=E5=87=BA?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B0=8F=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/market.ts | 2 +- src/components/TradeComponent.vue | 144 ++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 38 deletions(-) diff --git a/src/api/market.ts b/src/api/market.ts index 975381b..176122d 100644 --- a/src/api/market.ts +++ b/src/api/market.ts @@ -15,7 +15,7 @@ export interface ClobSubmitOrderRequest { /** 价格(整数,已乘 10000) */ price: number side: number - /** 数量(份额) */ + /** 数量(micro units,1_000_000 = 1 share,支持 6 位小数份额) */ size: number taker: boolean tokenID: string diff --git a/src/components/TradeComponent.vue b/src/components/TradeComponent.vue index fef811e..4343ab1 100644 --- a/src/components/TradeComponent.vue +++ b/src/components/TradeComponent.vue @@ -97,14 +97,15 @@
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.max') }}

- {{ t('trade.sharesExceedsMax', { max: maxAvailableShares }) }} + {{ t('trade.sharesExceedsMax', { max: maxAvailableSharesDisplay }) }}

mdi-information @@ -548,14 +551,15 @@
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
{{ t('trade.shares') }} {{ t('trade.maxShares') }}: {{ maxAvailableShares }}{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}
(prefs.limitType) const expirationEnabled = ref(false) const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no') const limitPrice = ref(0.82) // 内部存储 0–1,显示为美分与按钮一致 -const shares = ref(20) // 初始份额(正整数) +const shares = ref(20) // 初始份额(Buy 为正整数,Sell 支持 6 位小数) +const ORDER_SIZE_SCALE = 1_000_000 +const ORDER_SIZE_MIN = 1 / ORDER_SIZE_SCALE const expirationTime = ref('5m') // 初始过期时间 const EXPIRATION_VALUES = [ '5m', @@ -1836,21 +1847,44 @@ const currentOptionPositionShares = computed(() => { return totalShares }) -/** 最大可卖出份额 */ +/** 最大可卖出份额(保留 6 位小数) */ const maxAvailableShares = computed(() => { - return Math.floor(currentOptionPositionShares.value) + return clampSellShares(currentOptionPositionShares.value) }) -/** 将 shares 限制为正整数(>= 1) */ +/** 展示用最大可卖份额 */ +const maxAvailableSharesDisplay = computed(() => + trimTrailingZeros(maxAvailableShares.value.toFixed(6)), +) + +const sharesInputMin = computed(() => (activeTab.value === 'sell' ? ORDER_SIZE_MIN : 1)) +const sharesInputStep = computed(() => (activeTab.value === 'sell' ? ORDER_SIZE_MIN : 1)) + +/** 将 shares 限制为正整数(>= 1),Buy 模式使用 */ function clampShares(v: number): number { const n = Math.floor(Number.isFinite(v) ? v : 1) return Math.max(1, n) } -// 设置最大份额(基于当前选项的持仓);Sell 模式下份额默认取最大可卖数量 +/** Sell 模式:保留 6 位小数 */ +function clampSellShares(v: number): number { + if (!Number.isFinite(v) || v <= 0) return 0 + const scaled = Math.round(v * ORDER_SIZE_SCALE) / ORDER_SIZE_SCALE + return scaled < ORDER_SIZE_MIN ? 0 : scaled +} + +function sharesToMicro(v: number): number { + return Math.round(v * ORDER_SIZE_SCALE) +} + +// 设置最大份额(基于当前选项的持仓) const setMaxShares = () => { const maxShares = currentOptionPositionShares.value - shares.value = maxShares > 0 ? clampShares(maxShares) : 0 + if (activeTab.value === 'sell') { + shares.value = maxShares > 0 ? clampSellShares(maxShares) : 0 + } else { + shares.value = maxShares > 0 ? clampShares(maxShares) : 0 + } } function applyInitialOption(option: 'yes' | 'no') { @@ -1995,15 +2029,23 @@ const increasePrice = () => { limitPrice.value = ALLOWED_LIMIT_PRICES[nextIdx] ?? limitPrice.value } -/** 仅在值为正整数时更新 shares */ +/** 更新 shares:Buy 仅正整数,Sell 支持 6 位小数 */ function onSharesInput(v: unknown) { const num = v == null ? NaN : Number(v) + if (!Number.isFinite(num)) return + if (activeTab.value === 'sell') { + if (num <= 0) return + const scaled = clampSellShares(num) + if (scaled <= 0) return + shares.value = scaled + return + } const n = Math.floor(num) - if (!Number.isFinite(num) || n < 1 || num !== n) return + if (n < 1 || num !== n) return shares.value = n } -/** 只允许数字输入(Shares 为正整数) */ +/** 键盘输入:Sell 允许小数点 */ function onSharesKeydown(e: KeyboardEvent) { const key = e.key const allowed = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End'] @@ -2012,12 +2054,24 @@ function onSharesKeydown(e: KeyboardEvent) { if (['a', 'c', 'v', 'x'].includes(key.toLowerCase())) return } if (key >= '0' && key <= '9') return + if ( + activeTab.value === 'sell' && + key === '.' && + !String((e.target as HTMLInputElement)?.value ?? '').includes('.') + ) { + return + } e.preventDefault() } -/** 粘贴时只接受正整数 */ +/** 粘贴:Sell 接受小数,Buy 仅正整数 */ function onSharesPaste(e: ClipboardEvent) { const text = e.clipboardData?.getData('text') ?? '' + if (activeTab.value === 'sell') { + const num = parseFloat(text) + if (!Number.isFinite(num) || num <= 0) e.preventDefault() + return + } const num = parseInt(text, 10) if (!Number.isFinite(num) || num < 1) e.preventDefault() } @@ -2031,14 +2085,15 @@ const adjustShares = (amount: number) => { const sellSharesExceedsMax = computed( () => activeTab.value === 'sell' && - maxAvailableShares.value >= 0 && - shares.value > maxAvailableShares.value, + maxAvailableShares.value > 0 && + sharesToMicro(shares.value) > sharesToMicro(maxAvailableShares.value), ) // 份额百分比调整方法(仅在Sell模式下使用) const setSharesPercentage = (percentage: number) => { - const maxShares = currentOptionPositionShares.value || 100 - shares.value = clampShares(Math.round((maxShares * percentage) / 100)) + const maxShares = currentOptionPositionShares.value + if (!Number.isFinite(maxShares) || maxShares <= 0) return + shares.value = clampSellShares((maxShares * percentage) / 100) } // Market mode methods @@ -2160,8 +2215,8 @@ async function submitOrder() { isNoAvailableSharesError.value = true return } - if (shares.value > maxShares) { - orderError.value = t('trade.sharesExceedsMax', { max: maxShares }) + if (sharesToMicro(shares.value) > sharesToMicro(maxShares)) { + orderError.value = t('trade.sharesExceedsMax', { max: maxAvailableSharesDisplay.value }) isNoAvailableSharesError.value = false return } @@ -2179,8 +2234,23 @@ async function submitOrder() { ? parseExpirationTimestamp(expirationTime.value) : 0 - const rawSize = isMarket && activeTab.value === 'buy' ? amount.value : clampShares(shares.value) - const sizeValue = Math.round(rawSize * 1_000_000) + const rawSize = + isMarket && activeTab.value === 'buy' + ? amount.value + : activeTab.value === 'sell' + ? clampSellShares(shares.value) + : clampShares(shares.value) + if (activeTab.value === 'sell' && rawSize <= 0) { + orderError.value = t('trade.orderFailed') + isNoAvailableSharesError.value = false + return + } + const sizeValue = sharesToMicro(rawSize) + if (sizeValue <= 0) { + orderError.value = t('trade.orderFailed') + isNoAvailableSharesError.value = false + return + } orderLoading.value = true orderError.value = ''