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