新增:对接详情接口

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 total: number
} }
/** 接口 list 单项结构(与 /PmEvent/getPmEventPublic 返回一致) */ /**
* Event doc.json definitions["polymarket.PmEvent"]
* /PmEvent/getPmEventPublic /PmEvent/findPmEvent data
*/
export interface PmEventListItem { export interface PmEventListItem {
ID: number ID: number
CreatedAt?: string createdAt?: string
UpdatedAt?: string updatedAt?: string
ticker?: string
slug?: string slug?: string
ticker?: string
title: string title: string
description?: string description?: string
resolutionSource?: string resolutionSource?: string
startDate?: string startDate?: string
endDate?: string endDate?: string
creationDate?: string
closedTime?: string
startTime?: string
image?: string image?: string
icon?: string icon?: string
active?: boolean active?: boolean
archived?: boolean
closed?: boolean closed?: boolean
featured?: boolean
new?: boolean
restricted?: boolean
enableOrderBook?: boolean
competitive?: number
liquidity?: number
liquidityAmm?: number
liquidityClob?: number
openInterest?: number
volume?: 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 ID?: number
question?: string question?: string
outcomePrices?: string[] slug?: string
outcomePrices?: string[] | number[]
endDate?: string endDate?: string
volume?: number volume?: number
[key: string]: unknown [key: string]: unknown
}> }
series?: Array<{
/** 对应 definitions polymarket.PmSeries 常用字段 */
export interface PmEventSeriesItem {
ID?: number ID?: number
ticker?: string ticker?: string
title?: string title?: string
slug?: string
[key: string]: unknown [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 [key: string]: unknown
} }
@ -76,6 +110,33 @@ export async function getPmEventPublic(
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query) 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 返回结构一致,用于缓存) */ /** 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) */
export interface EventCardItem { export interface EventCardItem {
id: string id: string

View File

@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
export interface UserInfo { export interface UserInfo {
id?: number | string
headerImg?: string headerImg?: string
nickName?: string nickName?: string
userName?: string userName?: string

View File

@ -7,7 +7,10 @@
<v-card class="chart-card polymarket-chart" elevation="0" rounded="lg"> <v-card class="chart-card polymarket-chart" elevation="0" rounded="lg">
<!-- 顶部标题当前概率Past / 日期 --> <!-- 顶部标题当前概率Past / 日期 -->
<div class="chart-header"> <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"> <div class="chart-controls-row">
<v-btn variant="text" size="small" class="past-btn">Past </v-btn> <v-btn variant="text" size="small" class="past-btn">Past </v-btn>
<v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</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 type { ECharts } from 'echarts'
import OrderBook from '../components/OrderBook.vue' import OrderBook from '../components/OrderBook.vue'
import TradeComponent from '../components/TradeComponent.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 } export type ChartIncrement = { point: ChartPoint }
const route = useRoute() const route = useRoute()
const userStore = useUserStore()
// // GET /PmEvent/findPmEvent
const marketTitle = computed( const eventDetail = ref<PmEventListItem | null>(null)
() => (route.query.title as string) || 'U.S. anti-cartel ground operation in Mexico by March 31?' const detailLoading = ref(false)
) const detailError = ref<string | null>(null)
const marketVolume = computed(() => (route.query.marketInfo as string) || '$398,719')
const marketExpiresAt = computed(() => (route.query.expiresAt as string) || 'Mar 31, 2026') 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 resolutionDate = computed(() => {
const s = marketExpiresAt.value const s = marketExpiresAt.value
return s ? s.replace(/,?\s*\d{4}$/, '').trim() || 'Mar 31' : 'Mar 31' 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 let dynamicInterval: number | undefined
const currentChance = computed(() => { 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 d = data.value
const last = d.length > 0 ? d[d.length - 1] : undefined 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' const lineColor = '#2563eb'
@ -441,7 +516,14 @@ const handleResize = () => {
}) })
} }
watch(
() => route.params.id,
() => loadEventDetail(),
{ immediate: false }
)
onMounted(() => { onMounted(() => {
loadEventDetail()
initChart() initChart()
startDynamicUpdate() startDynamicUpdate()
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
@ -623,6 +705,12 @@ onUnmounted(() => {
line-height: 1.3; line-height: 1.3;
} }
.chart-error {
font-size: 0.875rem;
color: #dc2626;
margin: 0 0 8px 0;
}
.chart-controls-row { .chart-controls-row {
display: flex; display: flex;
align-items: center; align-items: center;