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