From f0c1be71cbc82281b08f2db5dd25bd8007fe18a1 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 10 Feb 2026 13:18:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/event.ts | 99 +++++++++++++++++++++++++++++------- src/stores/user.ts | 1 + src/views/TradeDetail.vue | 104 +++++++++++++++++++++++++++++++++++--- 3 files changed, 177 insertions(+), 27 deletions(-) diff --git a/src/api/event.ts b/src/api/event.ts index 2ced17a..2043428 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -8,38 +8,72 @@ export interface PageResult { total: number } -/** 接口 list 单项结构(与 /PmEvent/getPmEventPublic 返回一致) */ +/** + * Event 单项结构(与 doc.json definitions["polymarket.PmEvent"] 对齐) + * 用于 /PmEvent/getPmEventPublic 列表项 与 /PmEvent/findPmEvent 的 data + */ export interface PmEventListItem { ID: number - CreatedAt?: string - UpdatedAt?: string - ticker?: string + createdAt?: string + updatedAt?: string slug?: string + ticker?: string title: string description?: string resolutionSource?: string startDate?: string endDate?: string + creationDate?: string + closedTime?: string + startTime?: string image?: string icon?: string active?: boolean + archived?: boolean closed?: boolean + featured?: boolean + new?: boolean + restricted?: boolean + enableOrderBook?: boolean + competitive?: number + liquidity?: number + liquidityAmm?: number + liquidityClob?: number + openInterest?: number volume?: number - markets?: Array<{ - ID?: number - question?: string - outcomePrices?: string[] - endDate?: string - volume?: number - [key: string]: unknown - }> - series?: Array<{ - ID?: number - ticker?: string - title?: string - [key: string]: unknown - }> - tags?: Array<{ label?: string; slug?: string; [key: string]: unknown }> + commentCount?: number + seriesSlug?: string + markets?: PmEventMarketItem[] + series?: PmEventSeriesItem[] + tags?: PmEventTagItem[] + [key: string]: unknown +} + +/** 对应 definitions polymarket.PmMarket 常用字段;outcomePrices 首项为 Yes 价格 */ +export interface PmEventMarketItem { + ID?: number + question?: string + slug?: string + outcomePrices?: string[] | number[] + endDate?: string + volume?: number + [key: string]: unknown +} + +/** 对应 definitions polymarket.PmSeries 常用字段 */ +export interface PmEventSeriesItem { + ID?: number + ticker?: string + title?: string + slug?: string + [key: string]: unknown +} + +/** 对应 definitions polymarket.PmTag 常用字段 */ +export interface PmEventTagItem { + label?: string + slug?: string + ID?: number [key: string]: unknown } @@ -76,6 +110,33 @@ export async function getPmEventPublic( return get('/PmEvent/getPmEventPublic', query) } +/** + * GET /PmEvent/findPmEvent 响应体(200) + * doc.json: responses["200"].schema = allOf [ response.Response, { data: polymarket.PmEvent, msg } ] + */ +export interface PmEventDetailResponse { + code: number + data: PmEventListItem + msg: string +} + +/** + * 用 id 查询 Event 详情 + * GET /PmEvent/findPmEvent + * + * 请求参数(Query): + * - ID: number,必填,Event 主键 + * 鉴权:需在 headers 中传 x-token + * + * 响应(200):PmEventDetailResponse { code, data: PmEventListItem, msg } + */ +export async function findPmEvent( + id: number, + config?: { headers?: Record } +): Promise { + return get('/PmEvent/findPmEvent', { ID: id }, config) +} + /** 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) */ export interface EventCardItem { id: string diff --git a/src/stores/user.ts b/src/stores/user.ts index 7e3aa3e..5dc6154 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -2,6 +2,7 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' export interface UserInfo { + id?: number | string headerImg?: string nickName?: string userName?: string diff --git a/src/views/TradeDetail.vue b/src/views/TradeDetail.vue index 88c1839..a8bcdfa 100644 --- a/src/views/TradeDetail.vue +++ b/src/views/TradeDetail.vue @@ -7,7 +7,10 @@
-

{{ marketTitle }}

+

+ {{ detailLoading && !eventDetail ? '加载中...' : marketTitle }} +

+

{{ detailError }}

Past ▾ {{ resolutionDate }} @@ -124,6 +127,8 @@ import * as echarts from 'echarts' import type { ECharts } from 'echarts' import OrderBook from '../components/OrderBook.vue' import TradeComponent from '../components/TradeComponent.vue' +import { findPmEvent, type PmEventListItem } from '../api/event' +import { useUserStore } from '../stores/user' /** * 分时图服务端推送数据格式约定 @@ -153,13 +158,71 @@ export type ChartSnapshot = { range?: string; data: ChartPoint[] } export type ChartIncrement = { point: ChartPoint } const route = useRoute() +const userStore = useUserStore() -// 卡片传入:标题、成交量、到期日 -const marketTitle = computed( - () => (route.query.title as string) || 'U.S. anti-cartel ground operation in Mexico by March 31?' -) -const marketVolume = computed(() => (route.query.marketInfo as string) || '$398,719') -const marketExpiresAt = computed(() => (route.query.expiresAt as string) || 'Mar 31, 2026') +// 详情接口 GET /PmEvent/findPmEvent 返回的数据 +const eventDetail = ref(null) +const detailLoading = ref(false) +const detailError = ref(null) + +function formatVolume(volume: number | undefined): string { + if (volume == null || !Number.isFinite(volume)) return '$0 Vol.' + if (volume >= 1000) return `$${(volume / 1000).toFixed(1)}k Vol.` + return `$${Math.round(volume)} Vol.` +} + +function formatExpiresAt(endDate: string | undefined): string { + if (!endDate) return '' + try { + const d = new Date(endDate) + if (Number.isNaN(d.getTime())) return endDate + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) + } catch { + return endDate + } +} + +async function loadEventDetail() { + const idRaw = route.params.id + const id = typeof idRaw === 'string' ? parseInt(idRaw, 10) : Number(idRaw) + if (!Number.isFinite(id) || id < 1) { + detailError.value = '无效的 ID' + eventDetail.value = null + return + } + detailError.value = null + detailLoading.value = true + try { + const res = await findPmEvent(id, { + headers: userStore.token ? { 'x-token': userStore.token } : undefined + }) + if (res.code === 0 || res.code === 200) { + eventDetail.value = res.data ?? null + } else { + detailError.value = res.msg || '加载失败' + eventDetail.value = null + } + } catch (e) { + detailError.value = e instanceof Error ? e.message : '加载失败' + eventDetail.value = null + } finally { + detailLoading.value = false + } +} + +// 标题、成交量、到期日:优先接口详情,其次卡片 query,最后占位 +const marketTitle = computed(() => { + 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(() => { + if (eventDetail.value?.volume != null) return formatVolume(eventDetail.value.volume) + return (route.query.marketInfo as string) || '$398,719' +}) +const marketExpiresAt = computed(() => { + if (eventDetail.value?.endDate) return formatExpiresAt(eventDetail.value.endDate) + return (route.query.expiresAt as string) || 'Mar 31, 2026' +}) const resolutionDate = computed(() => { const s = marketExpiresAt.value return s ? s.replace(/,?\s*\d{4}$/, '').trim() || 'Mar 31' : 'Mar 31' @@ -272,9 +335,21 @@ let chartInstance: ECharts | null = null let dynamicInterval: number | undefined const currentChance = computed(() => { + const ev = eventDetail.value + const market = ev?.markets?.[0] + if (market?.outcomePrices?.[0] != null) { + const p = parseFloat(String(market.outcomePrices[0])) + if (Number.isFinite(p)) return Math.min(100, Math.max(0, Math.round(p * 100))) + } const d = data.value const last = d.length > 0 ? d[d.length - 1] : undefined - return last != null ? last[1] : 20 + if (last != null) return last[1] + const q = route.query.chance + 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' @@ -441,7 +516,14 @@ const handleResize = () => { }) } +watch( + () => route.params.id, + () => loadEventDetail(), + { immediate: false } +) + onMounted(() => { + loadEventDetail() initChart() startDynamicUpdate() window.addEventListener('resize', handleResize) @@ -623,6 +705,12 @@ onUnmounted(() => { line-height: 1.3; } +.chart-error { + font-size: 0.875rem; + color: #dc2626; + margin: 0 0 8px 0; +} + .chart-controls-row { display: flex; align-items: center;