Compare commits
2 Commits
7bc5831edd
...
c976333b72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c976333b72 | ||
|
|
fc480dc4a8 |
File diff suppressed because it is too large
Load Diff
@ -64,9 +64,49 @@ export interface PmEventMarketItem {
|
|||||||
clobTokenIds?: string[]
|
clobTokenIds?: string[]
|
||||||
endDate?: string
|
endDate?: string
|
||||||
volume?: number
|
volume?: number
|
||||||
|
/** 为 true 时表示市场已关闭/已结算,不再交易 */
|
||||||
|
closed?: boolean
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 市场是否已关闭(不再接受下单) */
|
||||||
|
export function isPmMarketClosed(m: PmEventMarketItem | null | undefined): boolean {
|
||||||
|
return m?.closed === true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已结算市场的结果角标:取 outcomePrices 中概率最高项对应 outcomes 文案(兼容 0–1 或 1–100)。
|
||||||
|
*/
|
||||||
|
export function getPmMarketSettledWinner(m: PmEventMarketItem): {
|
||||||
|
label: string
|
||||||
|
winnerIndex: number
|
||||||
|
} | null {
|
||||||
|
if (!isPmMarketClosed(m)) return null
|
||||||
|
const prices = m.outcomePrices
|
||||||
|
const outcomes = m.outcomes
|
||||||
|
if (!prices?.length) {
|
||||||
|
return { label: outcomes?.[0] ?? '—', winnerIndex: 0 }
|
||||||
|
}
|
||||||
|
let bestIdx = 0
|
||||||
|
let bestProb = -1
|
||||||
|
for (let i = 0; i < prices.length; i++) {
|
||||||
|
const p = parseFloat(String(prices[i]))
|
||||||
|
if (!Number.isFinite(p)) continue
|
||||||
|
const prob = p > 1 ? Math.min(1, p / 100) : p
|
||||||
|
if (prob > bestProb) {
|
||||||
|
bestProb = prob
|
||||||
|
bestIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestProb < 0) {
|
||||||
|
return { label: outcomes?.[0] ?? '—', winnerIndex: 0 }
|
||||||
|
}
|
||||||
|
const label =
|
||||||
|
outcomes?.[bestIdx] ??
|
||||||
|
(bestIdx === 0 ? 'Yes' : bestIdx === 1 ? 'No' : `#${bestIdx + 1}`)
|
||||||
|
return { label, winnerIndex: bestIdx }
|
||||||
|
}
|
||||||
|
|
||||||
/** 从市场项取 marketId,兼容 ID / id */
|
/** 从市场项取 marketId,兼容 ID / id */
|
||||||
export function getMarketId(m: PmEventMarketItem | null | undefined): string | undefined {
|
export function getMarketId(m: PmEventMarketItem | null | undefined): string | undefined {
|
||||||
if (!m) return undefined
|
if (!m) return undefined
|
||||||
@ -237,36 +277,24 @@ export function readTradeRouteEventSlug(obj: unknown): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TradeDetailRouteInput {
|
export interface TradeDetailRouteInput {
|
||||||
/** 事件数字 ID(与首页 MarketCard :id 一致),优先用于路径参数 */
|
/** 事件数字 ID;无 slug 时用作路径段 */
|
||||||
eventId?: string | number | null
|
eventId?: string | number | null
|
||||||
/** 事件 slug;无 eventId 时用作路径;路径为数字 ID 时可作为 query.slug 传给 findPmEvent */
|
/** 事件 slug,优先作为路径参数(详情页仅用其请求 findPmEvent) */
|
||||||
eventSlug?: string | null
|
eventSlug?: string | null
|
||||||
/** 市场行 ID,对应详情页 query.marketId */
|
|
||||||
marketId?: string | null
|
|
||||||
/** 详情页标题回显 query.title */
|
|
||||||
title?: string | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造与 MarketCard、EventMarkets → trade-detail 一致的 router.push 参数。
|
* 跳转详情:`/trade-detail/:id` 仅传路径参数,无 query;优先 slug,否则数字 ID。
|
||||||
* 路径:`/trade-detail/:id`,id 为事件数字 ID 或事件 slug。
|
|
||||||
*/
|
*/
|
||||||
export function buildTradeDetailPushOptions(
|
export function buildTradeDetailPushOptions(
|
||||||
input: TradeDetailRouteInput,
|
input: TradeDetailRouteInput,
|
||||||
): { name: 'trade-detail'; params: { id: string }; query: Record<string, string> } | null {
|
): { name: 'trade-detail'; params: { id: string } } | null {
|
||||||
|
const slug = input.eventSlug?.trim() || ''
|
||||||
const eid =
|
const eid =
|
||||||
input.eventId != null && String(input.eventId).trim() !== '' ? String(input.eventId).trim() : ''
|
input.eventId != null && String(input.eventId).trim() !== '' ? String(input.eventId).trim() : ''
|
||||||
const slugOnly = input.eventSlug?.trim() || ''
|
const pathId = slug || eid
|
||||||
if (!eid && !slugOnly) return null
|
if (!pathId) return null
|
||||||
const pathId = eid || slugOnly
|
return { name: 'trade-detail', params: { id: pathId } }
|
||||||
const numId = parseInt(eid, 10)
|
|
||||||
const isNumericEventPath =
|
|
||||||
eid !== '' && Number.isFinite(numId) && String(numId) === eid && numId >= 1
|
|
||||||
const query: Record<string, string> = {}
|
|
||||||
if (input.title?.trim()) query.title = input.title.trim()
|
|
||||||
if (input.marketId?.trim()) query.marketId = input.marketId.trim()
|
|
||||||
if (isNumericEventPath && slugOnly) query.slug = slugOnly
|
|
||||||
return { name: 'trade-detail', params: { id: pathId }, query }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 多选项卡片中单个选项(用于左右滑动切换) */
|
/** 多选项卡片中单个选项(用于左右滑动切换) */
|
||||||
|
|||||||
@ -68,10 +68,12 @@ export function usdcToAmount(displayAmount: number): number {
|
|||||||
return Math.round(displayAmount * USDC_SCALE)
|
return Math.round(displayAmount * USDC_SCALE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提现状态:审核中、提现成功、审核不通过、提现失败 */
|
/** 提现状态:审核中、提现成功(含 processed)、审核不通过、提现失败 */
|
||||||
export const WITHDRAW_STATUS = {
|
export const WITHDRAW_STATUS = {
|
||||||
PENDING: 'pending',
|
PENDING: 'pending',
|
||||||
SUCCESS: 'success',
|
SUCCESS: 'success',
|
||||||
|
/** 与 success 同义,后端已处理完成 */
|
||||||
|
PROCESSED: 'processed',
|
||||||
REJECTED: 'rejected',
|
REJECTED: 'rejected',
|
||||||
FAILED: 'failed',
|
FAILED: 'failed',
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@ -232,26 +232,13 @@ const semiProgressColor = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const navigateToDetail = () => {
|
const navigateToDetail = () => {
|
||||||
|
const segment = (props.slug?.trim() || props.id || '').trim()
|
||||||
|
if (!segment) return
|
||||||
if (props.displayType === 'multi' && (props.outcomes?.length ?? 0) > 1) {
|
if (props.displayType === 'multi' && (props.outcomes?.length ?? 0) > 1) {
|
||||||
router.push({
|
router.push({ name: 'event-markets', params: { id: segment } })
|
||||||
path: `/event/${props.id}/markets`,
|
|
||||||
query: { ...(props.slug && { slug: props.slug }) },
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
router.push({
|
router.push({ name: 'trade-detail', params: { id: segment } })
|
||||||
path: `/trade-detail/${props.id}`,
|
|
||||||
query: {
|
|
||||||
title: props.marketTitle,
|
|
||||||
imageUrl: props.imageUrl || undefined,
|
|
||||||
category: props.category || undefined,
|
|
||||||
marketInfo: props.marketInfo || undefined,
|
|
||||||
expiresAt: props.expiresAt || undefined,
|
|
||||||
chance: String(props.chanceValue),
|
|
||||||
...(props.marketId && { marketId: props.marketId }),
|
|
||||||
...(props.slug && { slug: props.slug }),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTradeSingle(side: 'yes' | 'no') {
|
function openTradeSingle(side: 'yes' | 'no') {
|
||||||
|
|||||||
@ -77,6 +77,10 @@
|
|||||||
"pleaseSelectMarket": "Please select a market (with clobTokenIds)",
|
"pleaseSelectMarket": "Please select a market (with clobTokenIds)",
|
||||||
"userError": "User info error",
|
"userError": "User info error",
|
||||||
"orderFailed": "Order failed",
|
"orderFailed": "Order failed",
|
||||||
|
"marketClosedTitle": "Market is closed",
|
||||||
|
"marketClosedDesc": "This market has settled. Trading and new orders are not available.",
|
||||||
|
"marketClosedOutcome": "Outcome: {outcome}",
|
||||||
|
"marketClosedBanner": "This market is closed and has settled.",
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"5m": "5m",
|
"5m": "5m",
|
||||||
"15m": "15m",
|
"15m": "15m",
|
||||||
@ -106,7 +110,8 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"priceZero": "0¢",
|
"priceZero": "0¢",
|
||||||
"moreActions": "More actions"
|
"moreActions": "More actions",
|
||||||
|
"settledSubMarketsTitle": "Sub-markets (settled)"
|
||||||
},
|
},
|
||||||
"searchPage": {
|
"searchPage": {
|
||||||
"title": "Search",
|
"title": "Search",
|
||||||
|
|||||||
@ -77,6 +77,10 @@
|
|||||||
"pleaseSelectMarket": "市場を選択してください(clobTokenIds が必要)",
|
"pleaseSelectMarket": "市場を選択してください(clobTokenIds が必要)",
|
||||||
"userError": "ユーザー情報エラー",
|
"userError": "ユーザー情報エラー",
|
||||||
"orderFailed": "注文に失敗しました",
|
"orderFailed": "注文に失敗しました",
|
||||||
|
"marketClosedTitle": "マーケットは閉鎖されています",
|
||||||
|
"marketClosedDesc": "このマーケットは決済済みです。取引や新規注文はできません。",
|
||||||
|
"marketClosedOutcome": "結果:{outcome}",
|
||||||
|
"marketClosedBanner": "このマーケットは閉鎖され決済されています。",
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"5m": "5分",
|
"5m": "5分",
|
||||||
"15m": "15分",
|
"15m": "15分",
|
||||||
@ -106,7 +110,8 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"priceZero": "0¢",
|
"priceZero": "0¢",
|
||||||
"moreActions": "その他の操作"
|
"moreActions": "その他の操作",
|
||||||
|
"settledSubMarketsTitle": "サブマーケット(決済済み)"
|
||||||
},
|
},
|
||||||
"searchPage": {
|
"searchPage": {
|
||||||
"title": "検索",
|
"title": "検索",
|
||||||
|
|||||||
@ -77,6 +77,10 @@
|
|||||||
"pleaseSelectMarket": "시장을 선택하세요 (clobTokenIds 필요)",
|
"pleaseSelectMarket": "시장을 선택하세요 (clobTokenIds 필요)",
|
||||||
"userError": "사용자 정보 오류",
|
"userError": "사용자 정보 오류",
|
||||||
"orderFailed": "주문 실패",
|
"orderFailed": "주문 실패",
|
||||||
|
"marketClosedTitle": "마켓이 종료되었습니다",
|
||||||
|
"marketClosedDesc": "이 마켓은 정산되었으며 거래와 신규 주문을 받지 않습니다.",
|
||||||
|
"marketClosedOutcome": "결과: {outcome}",
|
||||||
|
"marketClosedBanner": "이 마켓은 종료되어 정산되었습니다.",
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"5m": "5분",
|
"5m": "5분",
|
||||||
"15m": "15분",
|
"15m": "15분",
|
||||||
@ -106,7 +110,8 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"priceZero": "0¢",
|
"priceZero": "0¢",
|
||||||
"moreActions": "더보기"
|
"moreActions": "더보기",
|
||||||
|
"settledSubMarketsTitle": "하위 마켓(정산 완료)"
|
||||||
},
|
},
|
||||||
"searchPage": {
|
"searchPage": {
|
||||||
"title": "검색",
|
"title": "검색",
|
||||||
|
|||||||
@ -77,6 +77,10 @@
|
|||||||
"pleaseSelectMarket": "请先选择市场(需包含 clobTokenIds)",
|
"pleaseSelectMarket": "请先选择市场(需包含 clobTokenIds)",
|
||||||
"userError": "用户信息异常",
|
"userError": "用户信息异常",
|
||||||
"orderFailed": "下单失败",
|
"orderFailed": "下单失败",
|
||||||
|
"marketClosedTitle": "市场已关闭",
|
||||||
|
"marketClosedDesc": "本市场已结算,不再接受交易与下单。",
|
||||||
|
"marketClosedOutcome": "结果:{outcome}",
|
||||||
|
"marketClosedBanner": "该市场已关闭并完成结算。",
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"5m": "5分钟",
|
"5m": "5分钟",
|
||||||
"15m": "15分钟",
|
"15m": "15分钟",
|
||||||
@ -106,7 +110,8 @@
|
|||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
"priceZero": "0¢",
|
"priceZero": "0¢",
|
||||||
"moreActions": "更多操作"
|
"moreActions": "更多操作",
|
||||||
|
"settledSubMarketsTitle": "子市场(已结算)"
|
||||||
},
|
},
|
||||||
"searchPage": {
|
"searchPage": {
|
||||||
"title": "搜索",
|
"title": "搜索",
|
||||||
|
|||||||
@ -77,6 +77,10 @@
|
|||||||
"pleaseSelectMarket": "請先選擇市場(需包含 clobTokenIds)",
|
"pleaseSelectMarket": "請先選擇市場(需包含 clobTokenIds)",
|
||||||
"userError": "用戶資訊異常",
|
"userError": "用戶資訊異常",
|
||||||
"orderFailed": "下單失敗",
|
"orderFailed": "下單失敗",
|
||||||
|
"marketClosedTitle": "市場已關閉",
|
||||||
|
"marketClosedDesc": "本市場已結算,不再接受交易與下單。",
|
||||||
|
"marketClosedOutcome": "結果:{outcome}",
|
||||||
|
"marketClosedBanner": "該市場已關閉並完成結算。",
|
||||||
"expiration": {
|
"expiration": {
|
||||||
"5m": "5分鐘",
|
"5m": "5分鐘",
|
||||||
"15m": "15分鐘",
|
"15m": "15分鐘",
|
||||||
@ -106,7 +110,8 @@
|
|||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
"priceZero": "0¢",
|
"priceZero": "0¢",
|
||||||
"moreActions": "更多操作"
|
"moreActions": "更多操作",
|
||||||
|
"settledSubMarketsTitle": "子市場(已結算)"
|
||||||
},
|
},
|
||||||
"searchPage": {
|
"searchPage": {
|
||||||
"title": "搜尋",
|
"title": "搜尋",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { HOME_MAIN_SCROLL_STORAGE_KEY } from './scrollKeys'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
import Trade from '../views/Trade.vue'
|
import Trade from '../views/Trade.vue'
|
||||||
import Login from '../views/Login.vue'
|
import Login from '../views/Login.vue'
|
||||||
@ -67,6 +68,24 @@ const router = createRouter({
|
|||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
const el = document.querySelector('[data-main-scroll]')
|
const el = document.querySelector('[data-main-scroll]')
|
||||||
if (el) {
|
if (el) {
|
||||||
|
const shouldRestoreHomeScroll =
|
||||||
|
to.name === 'home' &&
|
||||||
|
from?.name != null &&
|
||||||
|
(from.name === 'trade-detail' || from.name === 'event-markets')
|
||||||
|
|
||||||
|
if (shouldRestoreHomeScroll) {
|
||||||
|
const raw = sessionStorage.getItem(HOME_MAIN_SCROLL_STORAGE_KEY)
|
||||||
|
const top = raw != null ? parseInt(raw, 10) : NaN
|
||||||
|
if (Number.isFinite(top) && top >= 0) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
el.scrollTo({ top, left: 0 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (savedPosition && from?.name) {
|
if (savedPosition && from?.name) {
|
||||||
el.scrollTo({ top: savedPosition.top, left: savedPosition.left ?? 0 })
|
el.scrollTo({ top: savedPosition.top, left: savedPosition.left ?? 0 })
|
||||||
return
|
return
|
||||||
|
|||||||
2
src/router/scrollKeys.ts
Normal file
2
src/router/scrollKeys.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/** 离开首页时写入 [data-main-scroll].scrollTop,从详情/多市场返回首页时恢复 */
|
||||||
|
export const HOME_MAIN_SCROLL_STORAGE_KEY = 'pcv-home-main-scroll-top'
|
||||||
@ -94,12 +94,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- 市场列表 -->
|
<!-- 可交易市场列表 -->
|
||||||
<v-card class="markets-list-card" elevation="0" rounded="lg">
|
<v-card
|
||||||
|
v-if="activeMarkets.length > 0"
|
||||||
|
class="markets-list-card"
|
||||||
|
elevation="0"
|
||||||
|
rounded="lg"
|
||||||
|
>
|
||||||
<div class="markets-list">
|
<div class="markets-list">
|
||||||
<div
|
<div
|
||||||
v-for="(market, index) in markets"
|
v-for="(market, index) in activeMarkets"
|
||||||
:key="market.ID ?? index"
|
:key="market.ID ?? market.id ?? index"
|
||||||
class="market-row"
|
class="market-row"
|
||||||
:class="{ selected: selectedMarketIndex === index }"
|
:class="{ selected: selectedMarketIndex === index }"
|
||||||
@click="goToTradeDetail(market)"
|
@click="goToTradeDetail(market)"
|
||||||
@ -134,11 +139,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
|
<!-- 已结算子市场(与 design vb1xr 一致:标题 + 行内结果角标) -->
|
||||||
|
<v-card
|
||||||
|
v-if="closedMarkets.length > 0"
|
||||||
|
class="markets-settled-card"
|
||||||
|
elevation="0"
|
||||||
|
rounded="lg"
|
||||||
|
>
|
||||||
|
<div class="markets-settled-head">
|
||||||
|
{{ t('eventMarkets.settledSubMarketsTitle') }}
|
||||||
|
</div>
|
||||||
|
<div class="markets-settled-list">
|
||||||
|
<div
|
||||||
|
v-for="(market, index) in closedMarkets"
|
||||||
|
:key="market.ID ?? market.id ?? `c-${index}`"
|
||||||
|
class="markets-settled-row"
|
||||||
|
@click="goToTradeDetail(market)"
|
||||||
|
>
|
||||||
|
<div class="markets-settled-row-text">
|
||||||
|
<span class="markets-settled-question">{{
|
||||||
|
market.question || t('eventMarkets.marketPlaceholder')
|
||||||
|
}}</span>
|
||||||
|
<span class="markets-settled-vol">{{ formatVolumeLine(market.volume) }}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="settledWinnerPill(market)"
|
||||||
|
class="markets-settled-pill"
|
||||||
|
:class="settledPillClass(market)"
|
||||||
|
>
|
||||||
|
{{ settledWinnerPill(market)!.label }}
|
||||||
|
</span>
|
||||||
|
<v-icon class="markets-settled-chevron" size="18">mdi-chevron-right</v-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 右侧:购买组件(桌面端显示;移动端用底部栏+弹窗) -->
|
<!-- 右侧:购买组件(桌面端显示;移动端用底部栏+弹窗) -->
|
||||||
<v-col v-if="!isMobile" cols="12" class="trade-col">
|
<v-col v-if="!isMobile" cols="12" class="trade-col">
|
||||||
<div v-if="markets.length > 0" class="trade-sidebar">
|
<div v-if="activeMarkets.length > 0" class="trade-sidebar">
|
||||||
<TradeComponent
|
<TradeComponent
|
||||||
:market="tradeMarketPayload"
|
:market="tradeMarketPayload"
|
||||||
:initial-option="tradeInitialOption"
|
:initial-option="tradeInitialOption"
|
||||||
@ -149,7 +189,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- 移动端:单 market 时显示固定底部 Yes/No 栏 + 三点菜单(Merge/Split) -->
|
<!-- 移动端:单 market 时显示固定底部 Yes/No 栏 + 三点菜单(Merge/Split) -->
|
||||||
<template v-if="isMobile && markets.length === 1">
|
<template v-if="isMobile && activeMarkets.length === 1">
|
||||||
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
||||||
<div class="mobile-trade-bar">
|
<div class="mobile-trade-bar">
|
||||||
<v-btn
|
<v-btn
|
||||||
@ -200,7 +240,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 移动端交易弹窗:单 market 与多 market 均需(多 market 时通过列表 Buy Yes/No 打开) -->
|
<!-- 移动端交易弹窗:单 market 与多 market 均需(多 market 时通过列表 Buy Yes/No 打开) -->
|
||||||
<template v-if="isMobile && markets.length > 0">
|
<template v-if="isMobile && activeMarkets.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"
|
v-if="tradeSheetRenderContent"
|
||||||
@ -237,6 +277,8 @@ import {
|
|||||||
findPmEvent,
|
findPmEvent,
|
||||||
getMarketId,
|
getMarketId,
|
||||||
getClobTokenId,
|
getClobTokenId,
|
||||||
|
getPmMarketSettledWinner,
|
||||||
|
isPmMarketClosed,
|
||||||
type FindPmEventParams,
|
type FindPmEventParams,
|
||||||
type PmEventListItem,
|
type PmEventListItem,
|
||||||
type PmEventMarketItem,
|
type PmEventMarketItem,
|
||||||
@ -282,9 +324,13 @@ const markets = computed(() => {
|
|||||||
const list = eventDetail.value?.markets ?? []
|
const list = eventDetail.value?.markets ?? []
|
||||||
return list.length > 0 ? list : []
|
return list.length > 0 ? list : []
|
||||||
})
|
})
|
||||||
const selectedMarket = computed(() => markets.value[selectedMarketIndex.value] ?? null)
|
/** 仍可交易的市场(closed !== true) */
|
||||||
/** 移动端底部栏显示的市场(选中项或首个),仅在 markets.length > 0 时使用 */
|
const activeMarkets = computed(() => markets.value.filter((m) => !isPmMarketClosed(m)))
|
||||||
const barMarket = computed(() => selectedMarket.value ?? markets.value[0])
|
/** 已结算/已关闭的子市场 */
|
||||||
|
const closedMarkets = computed(() => markets.value.filter((m) => isPmMarketClosed(m)))
|
||||||
|
const selectedMarket = computed(() => activeMarkets.value[selectedMarketIndex.value] ?? null)
|
||||||
|
/** 移动端底部栏显示的市场(选中项或首个),仅在有可交易市场时使用 */
|
||||||
|
const barMarket = computed(() => selectedMarket.value ?? activeMarkets.value[0])
|
||||||
/** 传给购买组件的市场数据(当前选中的市场) */
|
/** 传给购买组件的市场数据(当前选中的市场) */
|
||||||
const tradeMarketPayload = computed(() => {
|
const tradeMarketPayload = computed(() => {
|
||||||
const m = selectedMarket.value
|
const m = selectedMarket.value
|
||||||
@ -316,6 +362,26 @@ function formatVolume(volume: number | undefined): string {
|
|||||||
return `$${Math.round(volume)} Vol.`
|
return `$${Math.round(volume)} Vol.`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 已结算列表 secondary 行:Vol. $12.4M / k(与设计稿一致) */
|
||||||
|
function formatVolumeLine(volume: number | undefined): string {
|
||||||
|
if (volume == null || !Number.isFinite(volume)) return 'Vol. —'
|
||||||
|
if (volume >= 1_000_000) return `Vol. $${(volume / 1_000_000).toFixed(1)}M`
|
||||||
|
if (volume >= 1000) return `Vol. $${(volume / 1000).toFixed(1)}k`
|
||||||
|
return `Vol. $${Math.round(volume)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function settledWinnerPill(market: PmEventMarketItem) {
|
||||||
|
return getPmMarketSettledWinner(market)
|
||||||
|
}
|
||||||
|
|
||||||
|
function settledPillClass(market: PmEventMarketItem): string {
|
||||||
|
const w = getPmMarketSettledWinner(market)
|
||||||
|
if (!w) return 'markets-settled-pill--neutral'
|
||||||
|
if (w.winnerIndex === 0) return 'markets-settled-pill--yes'
|
||||||
|
if (w.winnerIndex === 1) return 'markets-settled-pill--no'
|
||||||
|
return 'markets-settled-pill--neutral'
|
||||||
|
}
|
||||||
|
|
||||||
function formatExpiresAt(endDate: string | undefined): string {
|
function formatExpiresAt(endDate: string | undefined): string {
|
||||||
if (!endDate) return ''
|
if (!endDate) return ''
|
||||||
try {
|
try {
|
||||||
@ -581,20 +647,13 @@ function noPrice(market: PmEventMarketItem): string {
|
|||||||
return `${Math.round(p * 100)}¢`
|
return `${Math.round(p * 100)}¢`
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToTradeDetail(market: PmEventMarketItem, side?: 'yes' | 'no') {
|
function goToTradeDetail(_market: PmEventMarketItem, _side?: 'yes' | 'no') {
|
||||||
const eventId = route.params.id
|
void _market
|
||||||
const marketId = market.ID != null ? String(market.ID) : undefined
|
void _side
|
||||||
router.push({
|
const slug = eventDetail.value?.slug?.trim()
|
||||||
path: `/trade-detail/${eventId}`,
|
const eventKey = slug || String(route.params.id ?? '').trim()
|
||||||
query: {
|
if (!eventKey) return
|
||||||
title: market.question ?? eventDetail.value?.title,
|
router.push({ name: 'trade-detail', params: { id: eventKey } })
|
||||||
marketId,
|
|
||||||
marketInfo: formatVolume(market.volume),
|
|
||||||
chance: String(marketChance(market)),
|
|
||||||
...(side && { side }),
|
|
||||||
...(eventDetail.value?.slug && { slug: eventDetail.value.slug }),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadEventDetail() {
|
async function loadEventDetail() {
|
||||||
@ -607,10 +666,9 @@ async function loadEventDetail() {
|
|||||||
}
|
}
|
||||||
const numId = parseInt(idStr, 10)
|
const numId = parseInt(idStr, 10)
|
||||||
const isNumericId = Number.isFinite(numId) && String(numId) === idStr && numId >= 1
|
const isNumericId = Number.isFinite(numId) && String(numId) === idStr && numId >= 1
|
||||||
const slugFromQuery = (route.query.slug as string)?.trim()
|
|
||||||
const params: FindPmEventParams = {
|
const params: FindPmEventParams = {
|
||||||
id: isNumericId ? numId : undefined,
|
id: isNumericId ? numId : undefined,
|
||||||
slug: isNumericId ? slugFromQuery || undefined : idStr,
|
slug: isNumericId ? undefined : idStr,
|
||||||
}
|
}
|
||||||
|
|
||||||
detailError.value = null
|
detailError.value = null
|
||||||
@ -646,6 +704,16 @@ async function loadEventDetail() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
activeMarkets,
|
||||||
|
(list) => {
|
||||||
|
if (selectedMarketIndex.value >= list.length) {
|
||||||
|
selectedMarketIndex.value = Math.max(0, list.length - 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadEventDetail()
|
loadEventDetail()
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
@ -1082,6 +1150,96 @@ watch(
|
|||||||
color: #cc0000 !important;
|
color: #cc0000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 已结算子市场列表(Pencil vb1xr) */
|
||||||
|
.markets-settled-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid #e7e7e7;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: none;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-head {
|
||||||
|
padding: 14px 16px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-row:hover {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-row-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-question {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #111827;
|
||||||
|
line-height: 1.35;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-vol {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-pill {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-pill--yes {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-pill--no {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-pill--neutral {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markets-settled-chevron {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
/* 移动端底部交易栏 */
|
/* 移动端底部交易栏 */
|
||||||
.mobile-trade-bar-spacer {
|
.mobile-trade-bar-spacer {
|
||||||
height: 72px;
|
height: 72px;
|
||||||
|
|||||||
@ -227,6 +227,8 @@ import {
|
|||||||
computed,
|
computed,
|
||||||
watch,
|
watch,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
|
import { onBeforeRouteLeave } from 'vue-router'
|
||||||
|
import { HOME_MAIN_SCROLL_STORAGE_KEY } from '../router/scrollKeys'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import MarketCard from '../components/MarketCard.vue'
|
import MarketCard from '../components/MarketCard.vue'
|
||||||
import TradeComponent from '../components/TradeComponent.vue'
|
import TradeComponent from '../components/TradeComponent.vue'
|
||||||
@ -644,6 +646,13 @@ function checkScrollLoad() {
|
|||||||
if (scrollHeight - scrollTop - clientHeight < SCROLL_LOAD_THRESHOLD) loadMore()
|
if (scrollHeight - scrollTop - clientHeight < SCROLL_LOAD_THRESHOLD) loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeRouteLeave(() => {
|
||||||
|
const el = getMainScrollEl()
|
||||||
|
if (el) {
|
||||||
|
sessionStorage.setItem(HOME_MAIN_SCROLL_STORAGE_KEY, String((el as HTMLElement).scrollTop))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.initialSearchExpanded) expandSearch()
|
if (props.initialSearchExpanded) expandSearch()
|
||||||
loadCategory()
|
loadCategory()
|
||||||
|
|||||||
@ -111,9 +111,8 @@ function formatTradingFee(rate: number): string {
|
|||||||
/** 与接口 needDeposit / accumulated 同一套口径:大额按 USDC 6 位小数,否则按美元数值 */
|
/** 与接口 needDeposit / accumulated 同一套口径:大额按 USDC 6 位小数,否则按美元数值 */
|
||||||
function depositRawToUsd(raw: number): number {
|
function depositRawToUsd(raw: number): number {
|
||||||
if (!Number.isFinite(raw) || raw <= 0) return 0
|
if (!Number.isFinite(raw) || raw <= 0) return 0
|
||||||
return raw >= 1_000_000_000 ? raw / 1_000_000 : raw
|
return raw / 1_000_000
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseUserAccumulatedUsd(user: Record<string, unknown>): number {
|
function parseUserAccumulatedUsd(user: Record<string, unknown>): number {
|
||||||
const v = user.accumulated ?? user.Accumulated
|
const v = user.accumulated ?? user.Accumulated
|
||||||
if (typeof v === 'number' && Number.isFinite(v)) return depositRawToUsd(v)
|
if (typeof v === 'number' && Number.isFinite(v)) return depositRawToUsd(v)
|
||||||
|
|||||||
@ -302,27 +302,15 @@ function openResult(item: SearchResultItem) {
|
|||||||
const card = mapEventItemToCard(item.raw)
|
const card = mapEventItemToCard(item.raw)
|
||||||
if (!card.id) return
|
if (!card.id) return
|
||||||
|
|
||||||
|
const segment = (card.slug?.trim() || card.id || '').trim()
|
||||||
|
if (!segment) return
|
||||||
|
|
||||||
if (card.displayType === 'multi' && (card.outcomes?.length ?? 0) > 1) {
|
if (card.displayType === 'multi' && (card.outcomes?.length ?? 0) > 1) {
|
||||||
router.push({
|
router.push({ name: 'event-markets', params: { id: segment } })
|
||||||
path: `/event/${card.id}/markets`,
|
|
||||||
query: { ...(card.slug && { slug: card.slug }) },
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
router.push({ name: 'trade-detail', params: { id: segment } })
|
||||||
path: `/trade-detail/${card.id}`,
|
|
||||||
query: {
|
|
||||||
title: card.marketTitle,
|
|
||||||
imageUrl: card.imageUrl || undefined,
|
|
||||||
category: card.category || undefined,
|
|
||||||
marketInfo: card.marketInfo || undefined,
|
|
||||||
expiresAt: card.expiresAt || undefined,
|
|
||||||
chance: String(card.chanceValue),
|
|
||||||
...(card.marketId && { marketId: card.marketId }),
|
|
||||||
...(card.slug && { slug: card.slug }),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else> {{ currentChance }}% {{ t('common.chance') }} </template>
|
<template v-else> {{ currentChance }}% {{ t('common.chance') }} </template>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="currentMarketClosed" class="chart-closed-banner">
|
||||||
|
{{ t('trade.marketClosedBanner') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
@ -92,6 +95,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
|
<!-- 移动端:无右侧交易栏时展示已关闭说明 -->
|
||||||
|
<v-card
|
||||||
|
v-if="isMobile && currentMarketClosed"
|
||||||
|
class="market-closed-mobile-card"
|
||||||
|
elevation="0"
|
||||||
|
rounded="lg"
|
||||||
|
>
|
||||||
|
<div class="market-closed-pane market-closed-pane--mobile">
|
||||||
|
<v-icon size="40" color="grey-darken-1">mdi-lock-outline</v-icon>
|
||||||
|
<h2 class="market-closed-pane-title">{{ t('trade.marketClosedTitle') }}</h2>
|
||||||
|
<p class="market-closed-pane-desc">{{ t('trade.marketClosedDesc') }}</p>
|
||||||
|
<p v-if="settledOutcomeLabel" class="market-closed-pane-outcome">
|
||||||
|
{{ t('trade.marketClosedOutcome', { outcome: settledOutcomeLabel }) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
<!-- 持仓 / 限价(订单簿上方) -->
|
<!-- 持仓 / 限价(订单簿上方) -->
|
||||||
<v-card class="positions-orders-card" elevation="0" rounded="lg">
|
<v-card class="positions-orders-card" elevation="0" rounded="lg">
|
||||||
<v-tabs
|
<v-tabs
|
||||||
@ -150,6 +170,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
class="position-sell-btn"
|
class="position-sell-btn"
|
||||||
:disabled="
|
:disabled="
|
||||||
|
currentMarketClosed ||
|
||||||
!(pos.availableSharesNum != null && pos.availableSharesNum > 0)
|
!(pos.availableSharesNum != null && pos.availableSharesNum > 0)
|
||||||
"
|
"
|
||||||
@click="openSellFromPosition(pos)"
|
@click="openSellFromPosition(pos)"
|
||||||
@ -199,7 +220,7 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- Order Book Section -->
|
<!-- Order Book Section -->
|
||||||
<v-card class="order-book-card" elevation="0" rounded="lg">
|
<v-card v-if="!currentMarketClosed" class="order-book-card" elevation="0" rounded="lg">
|
||||||
<OrderBook
|
<OrderBook
|
||||||
:asks-yes="orderBookAsksYes"
|
:asks-yes="orderBookAsksYes"
|
||||||
:bids-yes="orderBookBidsYes"
|
:bids-yes="orderBookBidsYes"
|
||||||
@ -257,11 +278,18 @@
|
|||||||
|
|
||||||
<!-- 右侧:交易组件(固定宽度),传入当前市场以便 Split 调用拆单接口;移动端隐藏,改用底部栏+弹窗 -->
|
<!-- 右侧:交易组件(固定宽度),传入当前市场以便 Split 调用拆单接口;移动端隐藏,改用底部栏+弹窗 -->
|
||||||
<v-col v-if="!isMobile" cols="12" class="trade-col">
|
<v-col v-if="!isMobile" cols="12" class="trade-col">
|
||||||
<div class="trade-sidebar">
|
<div v-if="currentMarketClosed" class="trade-sidebar market-closed-pane">
|
||||||
|
<v-icon size="44" color="grey-darken-1">mdi-lock-outline</v-icon>
|
||||||
|
<h2 class="market-closed-pane-title">{{ t('trade.marketClosedTitle') }}</h2>
|
||||||
|
<p class="market-closed-pane-desc">{{ t('trade.marketClosedDesc') }}</p>
|
||||||
|
<p v-if="settledOutcomeLabel" class="market-closed-pane-outcome">
|
||||||
|
{{ t('trade.marketClosedOutcome', { outcome: settledOutcomeLabel }) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tradeMarketPayload" class="trade-sidebar">
|
||||||
<TradeComponent
|
<TradeComponent
|
||||||
ref="tradeComponentRef"
|
ref="tradeComponentRef"
|
||||||
:market="tradeMarketPayload"
|
:market="tradeMarketPayload"
|
||||||
:initial-option="tradeInitialOption"
|
|
||||||
:positions="tradePositionsForComponent"
|
:positions="tradePositionsForComponent"
|
||||||
@merge-success="onMergeSuccess"
|
@merge-success="onMergeSuccess"
|
||||||
@split-success="onSplitSuccess"
|
@split-success="onSplitSuccess"
|
||||||
@ -270,7 +298,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<!-- 移动端:固定底部 Yes/No 栏 + 三点菜单(Merge/Split) -->
|
<!-- 移动端:固定底部 Yes/No 栏 + 三点菜单(Merge/Split) -->
|
||||||
<template v-if="isMobile && tradeMarketPayload">
|
<template v-if="isMobile && tradeMarketPayload && !currentMarketClosed">
|
||||||
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
||||||
<div class="mobile-trade-bar">
|
<div class="mobile-trade-bar">
|
||||||
<v-btn
|
<v-btn
|
||||||
@ -381,6 +409,8 @@ import {
|
|||||||
findPmEvent,
|
findPmEvent,
|
||||||
getMarketId,
|
getMarketId,
|
||||||
getClobTokenId,
|
getClobTokenId,
|
||||||
|
getPmMarketSettledWinner,
|
||||||
|
isPmMarketClosed,
|
||||||
type FindPmEventParams,
|
type FindPmEventParams,
|
||||||
type PmEventListItem,
|
type PmEventListItem,
|
||||||
} from '../api/event'
|
} from '../api/event'
|
||||||
@ -486,10 +516,9 @@ async function loadEventDetail() {
|
|||||||
}
|
}
|
||||||
const numId = parseInt(idStr, 10)
|
const numId = parseInt(idStr, 10)
|
||||||
const isNumericId = Number.isFinite(numId) && String(numId) === idStr && numId >= 1
|
const isNumericId = Number.isFinite(numId) && String(numId) === idStr && numId >= 1
|
||||||
const slugFromQuery = (route.query.slug as string)?.trim()
|
|
||||||
const params: FindPmEventParams = {
|
const params: FindPmEventParams = {
|
||||||
id: isNumericId ? numId : undefined,
|
id: isNumericId ? numId : undefined,
|
||||||
slug: isNumericId ? slugFromQuery || undefined : idStr,
|
slug: isNumericId ? undefined : idStr,
|
||||||
}
|
}
|
||||||
|
|
||||||
detailError.value = null
|
detailError.value = null
|
||||||
@ -535,18 +564,15 @@ function onRefresh({ done }: { done: () => void }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标题、成交量、到期日:优先接口详情,其次卡片 query,最后占位
|
// 标题、成交量、到期日:仅使用接口详情(路径只带 slug/id,无 query)
|
||||||
const marketTitle = computed(() => {
|
const marketTitle = computed(() => eventDetail.value?.title ?? '')
|
||||||
if (eventDetail.value?.title) return eventDetail.value.title
|
|
||||||
return (route.query.title as string) || 'U.S. anti-cartel ground operation in Mexico by March 31?'
|
|
||||||
})
|
|
||||||
const marketVolume = computed(() => {
|
const marketVolume = computed(() => {
|
||||||
if (eventDetail.value?.volume != null) return formatVolume(eventDetail.value.volume)
|
if (eventDetail.value?.volume != null) return formatVolume(eventDetail.value.volume)
|
||||||
return (route.query.marketInfo as string) || '$398,719'
|
return formatVolume(undefined)
|
||||||
})
|
})
|
||||||
const marketExpiresAt = computed(() => {
|
const marketExpiresAt = computed(() => {
|
||||||
if (eventDetail.value?.endDate) return formatExpiresAt(eventDetail.value.endDate)
|
if (eventDetail.value?.endDate) return formatExpiresAt(eventDetail.value.endDate)
|
||||||
return (route.query.expiresAt as string) || 'Mar 31, 2026'
|
return ''
|
||||||
})
|
})
|
||||||
const resolutionDate = computed(() => {
|
const resolutionDate = computed(() => {
|
||||||
const s = marketExpiresAt.value
|
const s = marketExpiresAt.value
|
||||||
@ -593,17 +619,19 @@ function setChartMode(mode: 'yesno' | 'crypto') {
|
|||||||
updateChartData()
|
updateChartData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前市场(用于交易组件与 Split 拆单):query.marketId 匹配或取第一个 */
|
/** 当前市场(用于交易组件与 Split 拆单):取接口返回的第一个市场 */
|
||||||
const currentMarket = computed(() => {
|
const currentMarket = computed(() => {
|
||||||
const list = eventDetail.value?.markets ?? []
|
const list = eventDetail.value?.markets ?? []
|
||||||
if (list.length === 0) return null
|
return list[0] ?? null
|
||||||
const qId = route.query.marketId
|
})
|
||||||
if (qId != null && String(qId).trim() !== '') {
|
|
||||||
const qStr = String(qId).trim()
|
const currentMarketClosed = computed(() => isPmMarketClosed(currentMarket.value))
|
||||||
const found = list.find((m) => getMarketId(m) === qStr)
|
|
||||||
if (found) return found
|
const settledOutcomeLabel = computed(() => {
|
||||||
}
|
const m = currentMarket.value
|
||||||
return list[0]
|
if (!m) return ''
|
||||||
|
const w = getPmMarketSettledWinner(m)
|
||||||
|
return w?.label ?? ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- CLOB WebSocket 订单簿与成交 ---
|
// --- CLOB WebSocket 订单簿与成交 ---
|
||||||
@ -861,23 +889,6 @@ const tradeMarketPayload = computed(() => {
|
|||||||
bestBidNoCents,
|
bestBidNoCents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const qId = route.query.marketId
|
|
||||||
if (qId != null && String(qId).trim() !== '') {
|
|
||||||
return {
|
|
||||||
marketId: String(qId).trim(),
|
|
||||||
yesPrice,
|
|
||||||
noPrice,
|
|
||||||
title: (route.query.title as string) || undefined,
|
|
||||||
bestBidYesCents,
|
|
||||||
bestBidNoCents,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const tradeInitialOption = computed(() => {
|
|
||||||
const side = route.query.side
|
|
||||||
if (side === 'yes' || side === 'no') return side
|
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1216,12 +1227,7 @@ const currentChance = computed(() => {
|
|||||||
const d = data.value
|
const d = data.value
|
||||||
const last = d.length > 0 ? d[d.length - 1] : undefined
|
const last = d.length > 0 ? d[d.length - 1] : undefined
|
||||||
if (last != null) return last[1]
|
if (last != null) return last[1]
|
||||||
const q = route.query.chance
|
return 0
|
||||||
if (q != null) {
|
|
||||||
const n = Number(q)
|
|
||||||
if (Number.isFinite(n)) return Math.min(100, Math.max(0, Math.round(n)))
|
|
||||||
}
|
|
||||||
return 20
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const lineColor = '#2563eb'
|
const lineColor = '#2563eb'
|
||||||
@ -1966,6 +1972,59 @@ onUnmounted(() => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-closed-banner {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #92400e;
|
||||||
|
background: #fffbeb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-pane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 28px 20px;
|
||||||
|
border: 1px solid #e7e7e7;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-pane--mobile {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-pane-title {
|
||||||
|
margin: 12px 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-pane-desc {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-pane-outcome {
|
||||||
|
margin: 14px 0 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-closed-mobile-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #e7e7e7;
|
||||||
|
box-shadow: none !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.trade-detail-row {
|
.trade-detail-row {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|||||||
@ -767,17 +767,10 @@ function canOpenTradeDetail(opts: { tradeEventId?: string; tradeEventSlug?: stri
|
|||||||
return !!(opts.tradeEventId?.trim() || opts.tradeEventSlug?.trim())
|
return !!(opts.tradeEventId?.trim() || opts.tradeEventSlug?.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTradeDetailFromWallet(input: {
|
function openTradeDetailFromWallet(input: { tradeEventId?: string; tradeEventSlug?: string }) {
|
||||||
tradeEventId?: string
|
|
||||||
tradeEventSlug?: string
|
|
||||||
marketId?: string
|
|
||||||
title?: string
|
|
||||||
}) {
|
|
||||||
const loc = buildTradeDetailPushOptions({
|
const loc = buildTradeDetailPushOptions({
|
||||||
eventId: input.tradeEventId,
|
eventId: input.tradeEventId,
|
||||||
eventSlug: input.tradeEventSlug,
|
eventSlug: input.tradeEventSlug,
|
||||||
marketId: input.marketId,
|
|
||||||
title: input.title,
|
|
||||||
})
|
})
|
||||||
if (loc) router.push(loc)
|
if (loc) router.push(loc)
|
||||||
}
|
}
|
||||||
@ -787,8 +780,6 @@ function onPositionRowClick(pos: Position) {
|
|||||||
openTradeDetailFromWallet({
|
openTradeDetailFromWallet({
|
||||||
tradeEventId: pos.tradeEventId,
|
tradeEventId: pos.tradeEventId,
|
||||||
tradeEventSlug: pos.tradeEventSlug,
|
tradeEventSlug: pos.tradeEventSlug,
|
||||||
marketId: pos.marketID,
|
|
||||||
title: pos.market,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,8 +788,6 @@ function onOpenOrderRowClick(ord: OpenOrder) {
|
|||||||
openTradeDetailFromWallet({
|
openTradeDetailFromWallet({
|
||||||
tradeEventId: ord.tradeEventId,
|
tradeEventId: ord.tradeEventId,
|
||||||
tradeEventSlug: ord.tradeEventSlug,
|
tradeEventSlug: ord.tradeEventSlug,
|
||||||
marketId: ord.detailMarketId,
|
|
||||||
title: ord.market,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,8 +802,6 @@ function onHistoryRowClick(h: HistoryItem) {
|
|||||||
openTradeDetailFromWallet({
|
openTradeDetailFromWallet({
|
||||||
tradeEventId: h.tradeEventId,
|
tradeEventId: h.tradeEventId,
|
||||||
tradeEventSlug: h.tradeEventSlug,
|
tradeEventSlug: h.tradeEventSlug,
|
||||||
marketId: h.detailMarketId,
|
|
||||||
title: h.market,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1174,7 +1161,12 @@ function getWithdrawStatusLabel(status: string | undefined): string {
|
|||||||
const s = (status ?? '').toLowerCase()
|
const s = (status ?? '').toLowerCase()
|
||||||
if (s === WITHDRAW_STATUS.PENDING || s === '0' || s === 'pending')
|
if (s === WITHDRAW_STATUS.PENDING || s === '0' || s === 'pending')
|
||||||
return t('wallet.withdrawStatusPending')
|
return t('wallet.withdrawStatusPending')
|
||||||
if (s === WITHDRAW_STATUS.SUCCESS || s === '1' || s === 'success')
|
if (
|
||||||
|
s === WITHDRAW_STATUS.SUCCESS ||
|
||||||
|
s === WITHDRAW_STATUS.PROCESSED ||
|
||||||
|
s === '1' ||
|
||||||
|
s === 'success'
|
||||||
|
)
|
||||||
return t('wallet.withdrawStatusSuccess')
|
return t('wallet.withdrawStatusSuccess')
|
||||||
if (s === WITHDRAW_STATUS.REJECTED || s === '2' || s === 'rejected')
|
if (s === WITHDRAW_STATUS.REJECTED || s === '2' || s === 'rejected')
|
||||||
return t('wallet.withdrawStatusRejected')
|
return t('wallet.withdrawStatusRejected')
|
||||||
@ -1186,7 +1178,13 @@ function getWithdrawStatusLabel(status: string | undefined): string {
|
|||||||
function getWithdrawStatusClass(status: string | undefined): string {
|
function getWithdrawStatusClass(status: string | undefined): string {
|
||||||
const s = (status ?? '').toLowerCase()
|
const s = (status ?? '').toLowerCase()
|
||||||
if (s === WITHDRAW_STATUS.PENDING || s === '0' || s === 'pending') return 'status-pending'
|
if (s === WITHDRAW_STATUS.PENDING || s === '0' || s === 'pending') return 'status-pending'
|
||||||
if (s === WITHDRAW_STATUS.SUCCESS || s === '1' || s === 'success') return 'status-success'
|
if (
|
||||||
|
s === WITHDRAW_STATUS.SUCCESS ||
|
||||||
|
s === WITHDRAW_STATUS.PROCESSED ||
|
||||||
|
s === '1' ||
|
||||||
|
s === 'success'
|
||||||
|
)
|
||||||
|
return 'status-success'
|
||||||
if (s === WITHDRAW_STATUS.REJECTED || s === '2' || s === 'rejected') return 'status-rejected'
|
if (s === WITHDRAW_STATUS.REJECTED || s === '2' || s === 'rejected') return 'status-rejected'
|
||||||
if (s === WITHDRAW_STATUS.FAILED || s === '3' || s === 'failed') return 'status-failed'
|
if (s === WITHDRAW_STATUS.FAILED || s === '3' || s === 'failed') return 'status-failed'
|
||||||
return ''
|
return ''
|
||||||
@ -2731,12 +2729,17 @@ async function submitAuthorize() {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.withdrawal-status-pill.status-pending,
|
.withdrawal-status-pill.status-pending,
|
||||||
.withdrawal-status-pill.status-success,
|
|
||||||
.withdrawal-status-pill.status-failed {
|
.withdrawal-status-pill.status-failed {
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
color: #374151;
|
color: #374151;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.withdrawal-status-pill.status-success {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
.withdrawal-status-pill.status-rejected {
|
.withdrawal-status-pill.status-rejected {
|
||||||
background: #fef2f2;
|
background: #fef2f2;
|
||||||
color: #b91c1c;
|
color: #b91c1c;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user