修改:卖出支持小数
This commit is contained in:
parent
537f3030e7
commit
604623bd37
@ -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
|
||||
|
||||
@ -97,14 +97,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
variant="outlined"
|
||||
@ -246,14 +247,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
variant="outlined"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
@ -357,14 +359,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
variant="outlined"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
@ -389,7 +392,7 @@
|
||||
<v-btn class="share-btn" @click="setMaxShares">{{ t('trade.max') }}</v-btn>
|
||||
</div>
|
||||
<p v-if="sellSharesExceedsMax" class="shares-exceeds-max-hint">
|
||||
{{ t('trade.sharesExceedsMax', { max: maxAvailableShares }) }}
|
||||
{{ t('trade.sharesExceedsMax', { max: maxAvailableSharesDisplay }) }}
|
||||
</p>
|
||||
<div v-if="activeTab === 'buy'" class="matching-info">
|
||||
<v-icon size="14">mdi-information</v-icon>
|
||||
@ -548,14 +551,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@ -685,14 +689,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@ -786,14 +791,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@ -990,14 +996,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@ -1127,14 +1134,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
variant="outlined"
|
||||
@ -1228,14 +1236,15 @@
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableSharesDisplay }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
:min="sharesInputMin"
|
||||
:step="sharesInputStep"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@ -1712,7 +1721,9 @@ const limitType = ref<'Market' | 'Limit'>(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
|
||||
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 = ''
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user