优化:交易组件的价格显示单位与外面一致
This commit is contained in:
parent
8c455ba00a
commit
da9de8c772
@ -203,13 +203,13 @@ export interface EventCardOutcome {
|
||||
title: string
|
||||
/** 第一选项概率(来自 outcomePrices[0]) */
|
||||
chanceValue: number
|
||||
/** 第一选项按钮文案(来自 outcomes[0],如 Yes / Up) */
|
||||
/** Yes 价格 0–1,来自 outcomePrices[0],供交易组件使用 */
|
||||
yesPrice?: number
|
||||
/** No 价格 0–1,来自 outcomePrices[1],供交易组件使用 */
|
||||
noPrice?: number
|
||||
yesLabel?: string
|
||||
/** 第二选项按钮文案(来自 outcomes[1],如 No / Down) */
|
||||
noLabel?: string
|
||||
/** 可选,用于交易时区分 market */
|
||||
marketId?: string
|
||||
/** 用于下单 tokenId,与 outcomes 顺序一致 */
|
||||
clobTokenIds?: string[]
|
||||
}
|
||||
|
||||
@ -240,6 +240,10 @@ export interface EventCardItem {
|
||||
marketId?: string
|
||||
/** 用于下单 tokenId,单 market 时取自 firstMarket.clobTokenIds */
|
||||
clobTokenIds?: string[]
|
||||
/** Yes 价格 0–1,来自 outcomePrices[0],供交易组件使用 */
|
||||
yesPrice?: number
|
||||
/** No 价格 0–1,来自 outcomePrices[1],供交易组件使用 */
|
||||
noPrice?: number
|
||||
}
|
||||
|
||||
/** 内存缓存:列表数据,切换页面时复用,下拉刷新时清空 */
|
||||
@ -322,17 +326,38 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem {
|
||||
|
||||
const category = item.series?.[0]?.title ?? item.tags?.[0]?.label ?? ''
|
||||
|
||||
function parseOutcomePrices(m: PmEventMarketItem): { yesPrice: number; noPrice: number } {
|
||||
const y = m?.outcomePrices?.[0]
|
||||
const n = m?.outcomePrices?.[1]
|
||||
const yesPrice =
|
||||
y != null && Number.isFinite(parseFloat(String(y)))
|
||||
? Math.min(1, Math.max(0, parseFloat(String(y))))
|
||||
: 0.5
|
||||
const noPrice =
|
||||
n != null && Number.isFinite(parseFloat(String(n)))
|
||||
? Math.min(1, Math.max(0, parseFloat(String(n))))
|
||||
: 1 - yesPrice
|
||||
return { yesPrice, noPrice }
|
||||
}
|
||||
|
||||
const outcomes: EventCardOutcome[] | undefined = multi
|
||||
? markets.map((m) => ({
|
||||
? markets.map((m) => {
|
||||
const { yesPrice, noPrice } = parseOutcomePrices(m)
|
||||
return {
|
||||
title: m.question ?? '',
|
||||
chanceValue: marketChance(m),
|
||||
yesPrice,
|
||||
noPrice,
|
||||
yesLabel: m.outcomes?.[0] ?? 'Yes',
|
||||
noLabel: m.outcomes?.[1] ?? 'No',
|
||||
marketId: getMarketId(m),
|
||||
clobTokenIds: m.clobTokenIds,
|
||||
}))
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
|
||||
const firstPrices = firstMarket ? parseOutcomePrices(firstMarket) : { yesPrice: 0.5, noPrice: 0.5 }
|
||||
|
||||
return {
|
||||
id,
|
||||
slug: item.slug ?? undefined,
|
||||
@ -349,5 +374,7 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem {
|
||||
isNew: item.new === true,
|
||||
marketId: getMarketId(firstMarket),
|
||||
clobTokenIds: firstMarket?.clobTokenIds,
|
||||
yesPrice: firstPrices.yesPrice,
|
||||
noPrice: firstPrices.noPrice,
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +181,8 @@ const emit = defineEmits<{
|
||||
clobTokenIds?: string[]
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
yesPrice?: number
|
||||
noPrice?: number
|
||||
},
|
||||
]
|
||||
}>()
|
||||
@ -210,6 +212,10 @@ const props = withDefaults(
|
||||
marketId?: string
|
||||
/** 用于下单 tokenId,单 market 时 */
|
||||
clobTokenIds?: string[]
|
||||
/** Yes 价格 0–1,供交易组件使用 */
|
||||
yesPrice?: number
|
||||
/** No 价格 0–1,供交易组件使用 */
|
||||
noPrice?: number
|
||||
}>(),
|
||||
{
|
||||
marketTitle: 'Mamdan opens city-owned grocery store b...',
|
||||
@ -225,6 +231,8 @@ const props = withDefaults(
|
||||
noLabel: 'No',
|
||||
isNew: false,
|
||||
marketId: undefined,
|
||||
yesPrice: undefined,
|
||||
noPrice: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
@ -306,6 +314,8 @@ function openTradeSingle(side: 'yes' | 'no') {
|
||||
clobTokenIds: props.clobTokenIds,
|
||||
yesLabel: props.yesLabel,
|
||||
noLabel: props.noLabel,
|
||||
yesPrice: props.yesPrice,
|
||||
noPrice: props.noPrice,
|
||||
})
|
||||
}
|
||||
|
||||
@ -318,6 +328,8 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
|
||||
clobTokenIds: outcome.clobTokenIds,
|
||||
yesLabel: outcome.yesLabel,
|
||||
noLabel: outcome.noLabel,
|
||||
yesPrice: outcome.yesPrice,
|
||||
noPrice: outcome.noPrice,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -293,14 +293,15 @@
|
||||
<v-icon>mdi-minus</v-icon>
|
||||
</v-btn>
|
||||
<v-text-field
|
||||
:model-value="limitPrice"
|
||||
:model-value="limitPriceCentsDisplay"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
max="100"
|
||||
step="0.01"
|
||||
class="price-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
suffix="¢"
|
||||
@update:model-value="onLimitPriceInput"
|
||||
@blur="onLimitPriceBlur"
|
||||
@keydown="onLimitPriceKeydown"
|
||||
@ -670,14 +671,15 @@
|
||||
><v-icon>mdi-minus</v-icon></v-btn
|
||||
>
|
||||
<v-text-field
|
||||
:model-value="limitPrice"
|
||||
:model-value="limitPriceCentsDisplay"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
max="100"
|
||||
step="0.01"
|
||||
class="price-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
suffix="¢"
|
||||
@update:model-value="onLimitPriceInput"
|
||||
@blur="onLimitPriceBlur"
|
||||
@keydown="onLimitPriceKeydown"
|
||||
@ -1059,15 +1061,17 @@
|
||||
><v-icon>mdi-minus</v-icon></v-btn
|
||||
>
|
||||
<v-text-field
|
||||
:model-value="limitPrice"
|
||||
:model-value="limitPriceCentsDisplay"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
max="100"
|
||||
step="0.01"
|
||||
class="price-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
suffix="¢"
|
||||
@update:model-value="onLimitPriceInput"
|
||||
@blur="onLimitPriceBlur"
|
||||
@keydown="onLimitPriceKeydown"
|
||||
@paste="onLimitPricePaste"
|
||||
></v-text-field>
|
||||
@ -1446,7 +1450,7 @@ const activeTab = ref('buy')
|
||||
const limitType = ref('Limit')
|
||||
const expirationEnabled = ref(false)
|
||||
const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
||||
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
||||
const limitPrice = ref(0.82) // 内部存储 0–1,显示为美分与按钮一致
|
||||
const shares = ref(20) // 初始份额(正整数)
|
||||
const expirationTime = ref('5m') // 初始过期时间
|
||||
const EXPIRATION_VALUES = ['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d'] as const
|
||||
@ -1485,6 +1489,8 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
// Computed properties
|
||||
/** Limit Price 显示值(美分),与 Yes/No 按钮单位一致 */
|
||||
const limitPriceCentsDisplay = computed(() => Math.round(limitPrice.value * 10000) / 100)
|
||||
const currentPrice = computed(() => {
|
||||
return `${(limitPrice.value * 100).toFixed(0)}¢`
|
||||
})
|
||||
@ -1571,11 +1577,11 @@ const handleOptionChange = (option: 'yes' | 'no') => {
|
||||
emit('optionChange', option)
|
||||
}
|
||||
|
||||
/** 仅接受 135 个允许档位:输入值吸附到最近档位,非法值忽略 */
|
||||
/** 输入为美分(0–100),与 Yes/No 按钮单位一致 */
|
||||
function onLimitPriceInput(v: unknown) {
|
||||
const num = v == null ? NaN : Number(v)
|
||||
if (!Number.isFinite(num) || num < 0 || num > 1) return
|
||||
limitPrice.value = snapToAllowedPrice(num)
|
||||
if (!Number.isFinite(num) || num < 0 || num > 100) return
|
||||
limitPrice.value = snapToAllowedPrice(num / 100)
|
||||
}
|
||||
|
||||
/** 失焦时吸附到允许档位 */
|
||||
@ -1583,7 +1589,7 @@ function onLimitPriceBlur() {
|
||||
limitPrice.value = snapToAllowedPrice(limitPrice.value)
|
||||
}
|
||||
|
||||
/** 只允许数字和小数点输入 */
|
||||
/** 只允许数字和小数点输入(美分 0–100) */
|
||||
function onLimitPriceKeydown(e: KeyboardEvent) {
|
||||
const key = e.key
|
||||
const allowed = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End']
|
||||
@ -1596,11 +1602,11 @@ function onLimitPriceKeydown(e: KeyboardEvent) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
/** 粘贴时只接受有效数字 */
|
||||
/** 粘贴时只接受有效美分数(0–100) */
|
||||
function onLimitPricePaste(e: ClipboardEvent) {
|
||||
const text = e.clipboardData?.getData('text') ?? ''
|
||||
const num = parseFloat(text)
|
||||
if (!Number.isFinite(num) || num < 0 || num > 1) {
|
||||
if (!Number.isFinite(num) || num < 0 || num > 100) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,6 +165,8 @@
|
||||
:is-new="card.isNew"
|
||||
:market-id="card.marketId"
|
||||
:clob-token-ids="card.clobTokenIds"
|
||||
:yes-price="card.yesPrice"
|
||||
:no-price="card.noPrice"
|
||||
@open-trade="onCardOpenTrade"
|
||||
/>
|
||||
<div v-if="eventList.length === 0 && !loadingMore" class="home-list-empty">
|
||||
@ -514,12 +516,23 @@ const tradeDialogMarket = ref<{
|
||||
clobTokenIds?: string[]
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
yesPrice?: number
|
||||
noPrice?: number
|
||||
} | null>(null)
|
||||
const scrollRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function onCardOpenTrade(
|
||||
side: 'yes' | 'no',
|
||||
market?: { id: string; title: string; marketId?: string; yesLabel?: string; noLabel?: string },
|
||||
market?: {
|
||||
id: string
|
||||
title: string
|
||||
marketId?: string
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
yesPrice?: number
|
||||
noPrice?: number
|
||||
clobTokenIds?: string[]
|
||||
},
|
||||
) {
|
||||
tradeDialogSide.value = side
|
||||
tradeDialogMarket.value = market ?? null
|
||||
@ -537,9 +550,14 @@ const homeTradeMarketPayload = computed(() => {
|
||||
const m = tradeDialogMarket.value
|
||||
if (!m) return undefined
|
||||
const marketId = m.marketId ?? m.id
|
||||
const chance = 50
|
||||
const yesPrice = Math.min(1, Math.max(0, chance / 100))
|
||||
const noPrice = 1 - yesPrice
|
||||
const yesPrice =
|
||||
m.yesPrice != null && Number.isFinite(m.yesPrice)
|
||||
? Math.min(1, Math.max(0, m.yesPrice))
|
||||
: 0.5
|
||||
const noPrice =
|
||||
m.noPrice != null && Number.isFinite(m.noPrice)
|
||||
? Math.min(1, Math.max(0, m.noPrice))
|
||||
: 1 - yesPrice
|
||||
const outcomes =
|
||||
m.yesLabel != null || m.noLabel != null
|
||||
? [m.yesLabel ?? 'Yes', m.noLabel ?? 'No']
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user