优化:卖出订单份额默认取最大有效份额
This commit is contained in:
parent
ff6c7a1877
commit
df82732fad
@ -37,6 +37,7 @@ interface TradePositionItem {
|
||||
- 事件处理:`onAmountInput`、`onAmountKeydown`、`onAmountPaste`
|
||||
- 余额不足时 Buy 显示 Deposit 按钮
|
||||
- 25%/50%/Max 快捷份额
|
||||
- **Sell 模式份额默认取最大可卖**:切换至 Sell 或 maxAvailableShares 变化时自动调用 `setMaxShares()`,将 shares 设为最大可卖数量(无可卖时为 0)
|
||||
- **Sell 模式 UI 优化**:
|
||||
- Shares 标签与 Max shares 提示同行显示(`max-shares-inline`)
|
||||
- 输入框独占一行(`shares-input-wrapper`)
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
- 移动端:底部栏 + `v-bottom-sheet` 嵌入 `TradeComponent`
|
||||
- Merge/Split:通过 `TradeComponent` 或底部菜单触发,成功后监听 `mergeSuccess`/`splitSuccess` 事件刷新持仓
|
||||
- **401 权限错误**:加载详情失败时,通过 `useAuthError().formatAuthError` 统一提示「请先登录」或「权限不足」
|
||||
- **Sell 弹窗 emitsOptions 竞态**:`sellDialogRenderContent` 延迟 350ms 卸载 TradeComponent,等 v-dialog transition 完成,避免 Vue patch 时组件实例为 null 的 `emitsOptions` 错误
|
||||
- **底部栏 slot 竞态**:`tradeSheetRenderContent` 延迟 50ms 挂载、350ms 卸载 TradeComponent,避免 v-bottom-sheet transition 期间 VTextField 触发「Slot default invoked outside of the render function」警告
|
||||
|
||||
## 使用方式
|
||||
|
||||
|
||||
@ -1748,6 +1748,18 @@ const maxAvailableShares = computed(() => {
|
||||
return Math.floor(currentOptionPositionShares.value)
|
||||
})
|
||||
|
||||
/** 将 shares 限制为正整数(>= 1) */
|
||||
function clampShares(v: number): number {
|
||||
const n = Math.floor(Number.isFinite(v) ? v : 1)
|
||||
return Math.max(1, n)
|
||||
}
|
||||
|
||||
// 设置最大份额(基于当前选项的持仓);Sell 模式下份额默认取最大可卖数量
|
||||
const setMaxShares = () => {
|
||||
const maxShares = currentOptionPositionShares.value
|
||||
shares.value = maxShares > 0 ? clampShares(maxShares) : 0
|
||||
}
|
||||
|
||||
function applyInitialOption(option: 'yes' | 'no') {
|
||||
selectedOption.value = option
|
||||
syncLimitPriceFromMarket()
|
||||
@ -1769,6 +1781,7 @@ onMounted(() => {
|
||||
if (props.initialOption) applyInitialOption(props.initialOption)
|
||||
else if (props.market) syncLimitPriceFromMarket()
|
||||
if (props.initialTab) activeTab.value = props.initialTab
|
||||
if (activeTab.value === 'sell') setMaxShares()
|
||||
})
|
||||
watch(
|
||||
() => props.initialOption,
|
||||
@ -1810,14 +1823,18 @@ watch(
|
||||
watch(
|
||||
() => [activeTab.value, maxAvailableShares.value] as const,
|
||||
([tab, max]) => {
|
||||
if (tab === 'sell' && (!Number.isFinite(max) || max <= 0)) {
|
||||
orderError.value = t('activity.noAvailableSharesToSell')
|
||||
isNoAvailableSharesError.value = true
|
||||
} else if (tab !== 'sell' || (Number.isFinite(max) && max > 0)) {
|
||||
if (isNoAvailableSharesError.value) {
|
||||
if (tab === 'sell') {
|
||||
setMaxShares()
|
||||
if (!Number.isFinite(max) || max <= 0) {
|
||||
orderError.value = t('activity.noAvailableSharesToSell')
|
||||
isNoAvailableSharesError.value = true
|
||||
} else if (isNoAvailableSharesError.value) {
|
||||
orderError.value = ''
|
||||
isNoAvailableSharesError.value = false
|
||||
}
|
||||
} else if (isNoAvailableSharesError.value) {
|
||||
orderError.value = ''
|
||||
isNoAvailableSharesError.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
@ -1885,12 +1902,6 @@ const increasePrice = () => {
|
||||
limitPrice.value = ALLOWED_LIMIT_PRICES[nextIdx] ?? limitPrice.value
|
||||
}
|
||||
|
||||
/** 将 shares 限制为正整数(>= 1) */
|
||||
function clampShares(v: number): number {
|
||||
const n = Math.floor(Number.isFinite(v) ? v : 1)
|
||||
return Math.max(1, n)
|
||||
}
|
||||
|
||||
/** 仅在值为正整数时更新 shares */
|
||||
function onSharesInput(v: unknown) {
|
||||
const num = v == null ? NaN : Number(v)
|
||||
@ -1979,14 +1990,6 @@ const setMaxAmount = () => {
|
||||
amount.value = balance.value
|
||||
}
|
||||
|
||||
// 设置最大份额(基于当前选项的持仓)
|
||||
const setMaxShares = () => {
|
||||
const maxShares = currentOptionPositionShares.value
|
||||
if (maxShares > 0) {
|
||||
shares.value = clampShares(maxShares)
|
||||
}
|
||||
}
|
||||
|
||||
/** Buy 模式:余额是否足够(>= 所需金额且不为 0)。cost:balance>0 时用 totalPrice,否则用 amount */
|
||||
const canAffordBuy = computed(() => {
|
||||
const bal = balance.value
|
||||
|
||||
@ -192,6 +192,7 @@
|
||||
<template v-if="isMobile && markets.length > 0">
|
||||
<v-bottom-sheet v-model="tradeSheetOpen" content-class="event-markets-trade-sheet">
|
||||
<TradeComponent
|
||||
v-if="tradeSheetRenderContent"
|
||||
ref="tradeComponentRef"
|
||||
:key="`trade-${selectedMarketIndex}-${tradeInitialOption}`"
|
||||
:market="tradeMarketPayload"
|
||||
@ -245,6 +246,8 @@ const selectedMarketIndex = ref(0)
|
||||
const tradeInitialOption = ref<'yes' | 'no' | undefined>(undefined)
|
||||
/** 移动端交易弹窗开关 */
|
||||
const tradeSheetOpen = ref(false)
|
||||
/** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */
|
||||
const tradeSheetRenderContent = ref(false)
|
||||
/** 移动端底部栏三点菜单开关 */
|
||||
const mobileMenuOpen = ref(false)
|
||||
/** TradeComponent 引用,用于从底部栏触发 Merge/Split */
|
||||
@ -714,11 +717,38 @@ onMounted(() => {
|
||||
loadEventDetail()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
let tradeSheetUnmountTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let tradeSheetMountTimer: ReturnType<typeof setTimeout> | undefined
|
||||
watch(tradeSheetOpen, (open) => {
|
||||
if (open) {
|
||||
if (tradeSheetUnmountTimer) {
|
||||
clearTimeout(tradeSheetUnmountTimer)
|
||||
tradeSheetUnmountTimer = undefined
|
||||
}
|
||||
if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
|
||||
tradeSheetMountTimer = setTimeout(() => {
|
||||
tradeSheetRenderContent.value = true
|
||||
tradeSheetMountTimer = undefined
|
||||
}, 50)
|
||||
} else {
|
||||
if (tradeSheetMountTimer) {
|
||||
clearTimeout(tradeSheetMountTimer)
|
||||
tradeSheetMountTimer = undefined
|
||||
}
|
||||
tradeSheetUnmountTimer = setTimeout(() => {
|
||||
tradeSheetRenderContent.value = false
|
||||
tradeSheetUnmountTimer = undefined
|
||||
}, 350)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
onUnmounted(() => {
|
||||
stopDynamicUpdate()
|
||||
window.removeEventListener('resize', handleResize)
|
||||
chartInstance?.dispose()
|
||||
chartInstance = null
|
||||
if (tradeSheetUnmountTimer) clearTimeout(tradeSheetUnmountTimer)
|
||||
if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
|
||||
})
|
||||
|
||||
watch(
|
||||
|
||||
@ -290,6 +290,7 @@
|
||||
</div>
|
||||
<v-bottom-sheet v-model="tradeSheetOpen" content-class="trade-detail-trade-sheet">
|
||||
<TradeComponent
|
||||
v-if="tradeSheetRenderContent"
|
||||
ref="mobileTradeComponentRef"
|
||||
:market="tradeMarketPayload"
|
||||
:initial-option="tradeInitialOptionFromBar"
|
||||
@ -311,7 +312,7 @@
|
||||
transition="dialog-transition"
|
||||
>
|
||||
<TradeComponent
|
||||
v-if="sellDialogOpen"
|
||||
v-if="sellDialogRenderContent"
|
||||
:market="tradeMarketPayload"
|
||||
:initial-option="sellInitialOption"
|
||||
:initial-tab="'sell'"
|
||||
@ -740,8 +741,12 @@ const tradeInitialOptionFromBar = ref<'yes' | 'no' | undefined>(undefined)
|
||||
const tradeInitialTabFromBar = ref<'buy' | 'sell' | undefined>(undefined)
|
||||
/** 移动端交易弹窗开关 */
|
||||
const tradeSheetOpen = ref(false)
|
||||
/** 控制底部栏内 TradeComponent 的渲染,延迟挂载以避免 slot 竞态警告 */
|
||||
const tradeSheetRenderContent = ref(false)
|
||||
/** 从持仓 Sell 打开的弹窗 */
|
||||
const sellDialogOpen = ref(false)
|
||||
/** 控制 Sell 弹窗内 TradeComponent 的渲染,延迟卸载以避免 emitsOptions 竞态 */
|
||||
const sellDialogRenderContent = ref(false)
|
||||
/** 从持仓 Sell 时预选的 Yes/No */
|
||||
const sellInitialOption = ref<'yes' | 'no'>('yes')
|
||||
/** 移动端三点菜单开关 */
|
||||
@ -823,6 +828,54 @@ function onSellOrderSuccess() {
|
||||
onOrderSuccess()
|
||||
}
|
||||
|
||||
// 延迟卸载 Sell 弹窗内容,等 dialog transition 完成,避免 emitsOptions 竞态
|
||||
let sellDialogUnmountTimer: ReturnType<typeof setTimeout> | undefined
|
||||
watch(sellDialogOpen, (open) => {
|
||||
if (open) {
|
||||
if (sellDialogUnmountTimer) {
|
||||
clearTimeout(sellDialogUnmountTimer)
|
||||
sellDialogUnmountTimer = undefined
|
||||
}
|
||||
sellDialogRenderContent.value = true
|
||||
} else {
|
||||
sellDialogUnmountTimer = setTimeout(() => {
|
||||
sellDialogRenderContent.value = false
|
||||
sellDialogUnmountTimer = undefined
|
||||
}, 350)
|
||||
}
|
||||
}, { immediate: true })
|
||||
onUnmounted(() => {
|
||||
if (sellDialogUnmountTimer) clearTimeout(sellDialogUnmountTimer)
|
||||
if (tradeSheetUnmountTimer) clearTimeout(tradeSheetUnmountTimer)
|
||||
if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
|
||||
})
|
||||
|
||||
// 底部栏 TradeComponent 延迟挂载/卸载,避免 transition 期间 slot 竞态
|
||||
let tradeSheetUnmountTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let tradeSheetMountTimer: ReturnType<typeof setTimeout> | undefined
|
||||
watch(tradeSheetOpen, (open) => {
|
||||
if (open) {
|
||||
if (tradeSheetUnmountTimer) {
|
||||
clearTimeout(tradeSheetUnmountTimer)
|
||||
tradeSheetUnmountTimer = undefined
|
||||
}
|
||||
if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
|
||||
tradeSheetMountTimer = setTimeout(() => {
|
||||
tradeSheetRenderContent.value = true
|
||||
tradeSheetMountTimer = undefined
|
||||
}, 50)
|
||||
} else {
|
||||
if (tradeSheetMountTimer) {
|
||||
clearTimeout(tradeSheetMountTimer)
|
||||
tradeSheetMountTimer = undefined
|
||||
}
|
||||
tradeSheetUnmountTimer = setTimeout(() => {
|
||||
tradeSheetRenderContent.value = false
|
||||
tradeSheetUnmountTimer = undefined
|
||||
}, 350)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 当前市场的 marketID,用于筛选持仓和订单
|
||||
const currentMarketId = computed(() => getMarketId(currentMarket.value))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user