diff --git a/src/api/event.ts b/src/api/event.ts index c04d2b7..6df1d01 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -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) => ({ - title: m.question ?? '', - chanceValue: marketChance(m), - yesLabel: m.outcomes?.[0] ?? 'Yes', - noLabel: m.outcomes?.[1] ?? 'No', - marketId: getMarketId(m), - clobTokenIds: m.clobTokenIds, - })) + ? 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, } } diff --git a/src/components/MarketCard.vue b/src/components/MarketCard.vue index c070f46..1efa917 100644 --- a/src/components/MarketCard.vue +++ b/src/components/MarketCard.vue @@ -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, }) } diff --git a/src/components/TradeComponent.vue b/src/components/TradeComponent.vue index f4abdbd..9f83c6a 100644 --- a/src/components/TradeComponent.vue +++ b/src/components/TradeComponent.vue @@ -293,14 +293,15 @@ mdi-minus mdi-minus mdi-minus @@ -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() } } diff --git a/src/views/Home.vue b/src/views/Home.vue index a83eff0..8b4fb64 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -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" />
@@ -514,12 +516,23 @@ const tradeDialogMarket = ref<{ clobTokenIds?: string[] yesLabel?: string noLabel?: string + yesPrice?: number + noPrice?: number } | null>(null) const scrollRef = ref(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']