新增:对接详情接口

This commit is contained in:
ivan 2026-02-10 13:18:44 +08:00
parent def6b95b5b
commit f0c1be71cb
3 changed files with 177 additions and 27 deletions

View File

@ -8,38 +8,72 @@ export interface PageResult<T> {
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<{
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
outcomePrices?: string[]
slug?: string
outcomePrices?: string[] | number[]
endDate?: string
volume?: number
[key: string]: unknown
}>
series?: Array<{
}
/** 对应 definitions polymarket.PmSeries 常用字段 */
export interface PmEventSeriesItem {
ID?: number
ticker?: string
title?: string
slug?: string
[key: string]: unknown
}>
tags?: Array<{ label?: 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<PmEventListResponse>('/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: numberEvent
* headers x-token
*
* 200PmEventDetailResponse { code, data: PmEventListItem, msg }
*/
export async function findPmEvent(
id: number,
config?: { headers?: Record<string, string> }
): Promise<PmEventDetailResponse> {
return get<PmEventDetailResponse>('/PmEvent/findPmEvent', { ID: id }, config)
}
/** 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) */
export interface EventCardItem {
id: string

View File

@ -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

View File

@ -7,7 +7,10 @@
<v-card class="chart-card polymarket-chart" elevation="0" rounded="lg">
<!-- 顶部标题当前概率Past / 日期 -->
<div class="chart-header">
<h1 class="chart-title">{{ marketTitle }}</h1>
<h1 class="chart-title">
{{ detailLoading && !eventDetail ? '加载中...' : marketTitle }}
</h1>
<p v-if="detailError" class="chart-error">{{ detailError }}</p>
<div class="chart-controls-row">
<v-btn variant="text" size="small" class="past-btn">Past </v-btn>
<v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn>
@ -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<PmEventListItem | null>(null)
const detailLoading = ref(false)
const detailError = ref<string | null>(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;