diff --git a/index.html b/index.html index 9e5fc8f..df3f429 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite App + TestMarket
diff --git a/src/components/OrderBook.vue b/src/components/OrderBook.vue index 7bad62e..240a528 100644 --- a/src/components/OrderBook.vue +++ b/src/components/OrderBook.vue @@ -43,9 +43,9 @@ :trackStyle="{ backgroundColor: 'transparent' }" /> -
{{ ask.price }}¢
-
{{ ask.shares.toFixed(2) }}
-
{{ ask.cumulativeTotal.toFixed(2) }}
+
{{ formatOrderBookPrice(ask) }}¢
+
{{ trimTrailingZeros(ask.shares.toFixed(2)) }}
+
{{ trimTrailingZeros(ask.cumulativeTotal.toFixed(4)) }}
{{ t('trade.orderBookAsks') }}
@@ -63,9 +63,9 @@ :trackStyle="{ backgroundColor: 'transparent' }" /> -
{{ bid.price }}¢
-
{{ bid.shares.toFixed(2) }}
-
{{ bid.cumulativeTotal.toFixed(2) }}
+
{{ formatOrderBookPrice(bid) }}¢
+
{{ trimTrailingZeros(bid.shares.toFixed(2)) }}
+
{{ trimTrailingZeros(bid.cumulativeTotal.toFixed(4)) }}
@@ -93,8 +93,12 @@ import { USE_MOCK_ORDER_BOOK } from '../config/mock' const { t } = useI18n() export interface OrderBookRow { + /** 价格(美分),用于展示 */ price: number + /** 份额数量 */ shares: number + /** 原始精度单价(美元/份),用于计算总价,避免舍入误差 */ + priceRaw?: number } const props = withDefaults( @@ -223,6 +227,21 @@ watch( { immediate: true }, ) +/** 去掉小数点后多余的 0(如 1.00 → 1,0.10 → 0.1) */ +function trimTrailingZeros(s: string): string { + return s.replace(/\.?0+$/, '') +} + +/** 订单簿价格展示:小于 1 美分时最多显示两位小数,使用原始精度 priceRaw;小数点后全为 0 则不显示 */ +function formatOrderBookPrice(row: OrderBookRow): string { + const cents = + row.priceRaw != null && Number.isFinite(row.priceRaw) ? row.priceRaw * 100 : row.price + if (!Number.isFinite(cents)) return '0' + if (cents < 1) return trimTrailingZeros(cents.toFixed(2)) + if (cents < 100) return trimTrailingZeros(cents.toFixed(1)) + return String(Math.round(cents)) +} + // Calculate cumulative total for asks const asksWithCumulativeTotal = computed(() => { let cumulativeTotal = 0 @@ -231,8 +250,10 @@ const asksWithCumulativeTotal = computed(() => { return sortedAsks .map((ask) => { - // Calculate current ask's value - const askValue = (ask.price * ask.shares) / 100 // Convert cents to dollars + // 总价用原始精度单价(美元)计算,无则回退为 price/100 + const priceDollars = + ask.priceRaw != null && Number.isFinite(ask.priceRaw) ? ask.priceRaw : ask.price / 100 + const askValue = priceDollars * ask.shares cumulativeTotal += askValue return { ...ask, @@ -249,9 +270,10 @@ const bidsWithCumulativeTotal = computed(() => { const sortedBids = [...bids.value].sort((a, b) => b.price - a.price) return sortedBids.map((bid) => { - // Calculate current bid's value - console.log('bid.price', bid.price) - const bidValue = (bid.price * bid.shares) / 100.0 // Convert cents to dollars + // 总价用原始精度单价(美元)计算,无则回退为 price/100 + const priceDollars = + bid.priceRaw != null && Number.isFinite(bid.priceRaw) ? bid.priceRaw : bid.price / 100 + const bidValue = priceDollars * bid.shares cumulativeTotal += bidValue return { ...bid, diff --git a/src/components/TradeComponent.vue b/src/components/TradeComponent.vue index 21dadc3..736b465 100644 --- a/src/components/TradeComponent.vue +++ b/src/components/TradeComponent.vue @@ -1680,27 +1680,35 @@ const emit = defineEmits<{ // Computed properties /** Limit Price 显示值(美分),与 Yes/No 按钮单位一致 */ +/** 去掉小数点后多余的 0(如 1.00 → 1,0.10 → 0.1) */ +function trimTrailingZeros(s: string): string { + return s.replace(/\.?0+$/, '') +} + const limitPriceCentsDisplay = computed(() => Math.round(limitPrice.value * 10000) / 100) const currentPrice = computed(() => { - return `${(limitPrice.value * 100).toFixed(0)}¢` + return `${trimTrailingZeros((limitPrice.value * 100).toFixed(0))}¢` }) const totalPrice = computed(() => { + let raw: number if (activeTab.value === 'sell' && limitType.value === 'Market') { const bestBidCents = selectedOption.value === 'yes' ? (props.market?.bestBidYesCents ?? 0) : (props.market?.bestBidNoCents ?? 0) - const price = bestBidCents / 100 - return (price * shares.value).toFixed(2) + raw = (bestBidCents / 100) * shares.value + } else { + raw = limitPrice.value * shares.value } - return (limitPrice.value * shares.value).toFixed(2) + if (!Number.isFinite(raw)) return '0' + return trimTrailingZeros(raw.toFixed(4)) }) /** Sell 模式下的平均单价(¢) */ const avgPriceCents = computed(() => { const p = limitPrice.value - return (p * 100).toFixed(1) + return trimTrailingZeros((p * 100).toFixed(1)) }) /** To win = 份数 × 1U = shares × 1 USDC */ @@ -1711,9 +1719,9 @@ const toWinValue = computed(() => { ? (props.market?.yesPrice ?? 0.5) : (props.market?.noPrice ?? 0.5) const sharesFromAmount = price > 0 ? amount.value / price : 0 - return sharesFromAmount.toFixed(2) + return trimTrailingZeros(sharesFromAmount.toFixed(2)) } - return (shares.value * 1).toFixed(2) + return trimTrailingZeros((shares.value * 1).toFixed(2)) }) const limitTypeDisplay = computed(() => diff --git a/src/views/TradeDetail.vue b/src/views/TradeDetail.vue index 4bb36be..c53d31e 100644 --- a/src/views/TradeDetail.vue +++ b/src/views/TradeDetail.vue @@ -510,7 +510,7 @@ const currentMarket = computed(() => { // --- CLOB WebSocket 订单簿与成交 --- // 按 token 索引区分:0 = Yes,1 = No -type OrderBookRows = { price: number; shares: number }[] +type OrderBookRows = { price: number; shares: number; priceRaw?: number }[] const clobSdkRef = ref(null) const orderBookByToken = ref>({ 0: { asks: [], bids: [] }, @@ -560,14 +560,21 @@ const clobSpreadNo = computed(() => clobSpreadByToken.value[1]) /** 订单簿份额接口按 6 位小数传(1_000_000 = 1 share),需除以该系数转为展示值 */ const ORDER_BOOK_SIZE_SCALE = 1_000_000 +/** 接口 key p 为价格(与 price 美分同源,除以 100 为美分);priceRaw 为美元单价保留原始精度供订单簿算总价 */ function priceSizeToRows(record: Record | undefined): OrderBookRows { if (!record) return [] return Object.entries(record) .filter(([, rawShares]) => rawShares > 0) - .map(([p, rawShares]) => ({ - price: Math.round(parseFloat(p) / 100), - shares: rawShares / ORDER_BOOK_SIZE_SCALE, - })) + .map(([p, rawShares]) => { + const numP = parseFloat(p) + const priceCents = Math.round(numP / 100) + const priceRawDollars = Number.isFinite(numP) ? numP / 10000 : priceCents / 100 + return { + price: priceCents, + shares: rawShares / ORDER_BOOK_SIZE_SCALE, + priceRaw: priceRawDollars, + } + }) } function getTokenIndex(msg: PriceSizePolyMsg): number { @@ -599,17 +606,24 @@ function applyPriceSizeDelta(msg: PriceSizePolyMsg) { delta: Record | undefined, asc: boolean, ) => { - const map = new Map(current.map((r) => [r.price, r.shares])) + const map = new Map() + const keyOf = (price: number, priceRaw: number) => priceRaw.toFixed(8) + current.forEach((r) => { + const pr = r.priceRaw ?? r.price / 100 + map.set(keyOf(r.price, pr), { price: r.price, shares: r.shares, priceRaw: pr }) + }) if (delta) { Object.entries(delta).forEach(([p, rawShares]) => { - const price = Math.round(parseFloat(p) / 100) + const numP = parseFloat(p) + const price = Math.round(numP / 100) + const priceRaw = Number.isFinite(numP) ? numP / 10000 : price / 100 const shares = rawShares / ORDER_BOOK_SIZE_SCALE - if (shares <= 0) map.delete(price) - else map.set(price, shares) + const key = keyOf(price, priceRaw) + if (shares <= 0) map.delete(key) + else map.set(key, { price, shares, priceRaw }) }) } - return Array.from(map.entries()) - .map(([price, shares]) => ({ price, shares })) + return Array.from(map.values()) .sort((a, b) => (asc ? a.price - b.price : b.price - a.price)) } const prev = orderBookByToken.value[idx] ?? { asks: [], bids: [] }