新增:对接详情接口
This commit is contained in:
parent
def6b95b5b
commit
f0c1be71cb
@ -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<{
|
||||
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<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: number,必填,Event 主键
|
||||
* 鉴权:需在 headers 中传 x-token
|
||||
*
|
||||
* 响应(200):PmEventDetailResponse { 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user