From e14bd3bc2365af0a4a0f714ce6c8c9f3f77d79b9 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 11 Feb 2026 12:19:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=A4=9AMarket?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 + .env.example | 3 + src/api/event.ts | 2 +- src/api/market.ts | 31 ++ src/api/mockEventList.ts | 60 +++ src/api/request.ts | 24 + src/components/MarketCard.vue | 4 + src/components/TradeComponent.vue | 237 +++++++-- src/router/index.ts | 6 + src/views/EventMarkets.vue | 836 ++++++++++++++++++++++++++++++ src/views/Home.vue | 12 +- 11 files changed, 1175 insertions(+), 43 deletions(-) create mode 100644 .env create mode 100644 .env.example create mode 100644 src/api/market.ts create mode 100644 src/views/EventMarkets.vue diff --git a/.env b/.env new file mode 100644 index 0000000..4e9b7fb --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +# API 基础地址,不设置时默认 https://api.xtrader.vip +# 连接测试服务器 192.168.3.21:8888 时复制本文件为 .env 或 .env.local 并取消下一行注释: +VITE_API_BASE_URL=http://192.168.3.21:8888 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2141460 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# API 基础地址,不设置时默认 https://api.xtrader.vip +# 连接测试服务器 192.168.3.21:8888 时复制本文件为 .env 或 .env.local 并取消下一行注释: +# VITE_API_BASE_URL=http://192.168.3.21:8888 diff --git a/src/api/event.ts b/src/api/event.ts index f2615dd..0fb4804 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -106,7 +106,7 @@ export interface GetPmEventListParams { export async function getPmEventPublic( params: GetPmEventListParams = {} ): Promise { - const { page = 0, pageSize = 10, keyword, createdAtRange } = params + const { page = 1, pageSize = 10, keyword, createdAtRange } = params const query: Record = { page, pageSize, diff --git a/src/api/market.ts b/src/api/market.ts new file mode 100644 index 0000000..7c27ab2 --- /dev/null +++ b/src/api/market.ts @@ -0,0 +1,31 @@ +import { post } from './request' + +/** 通用响应:与 doc.json response.Response 一致 */ +export interface ApiResponse { + code: number + data?: T + msg: string +} + +/** + * Split 请求体(测试服务器 192.168.3.21:8888 的 /PmMarket/split) + * 用 USDC 兑换该市场的 Yes+No 份额(1 USDC ≈ 1 Yes + 1 No) + */ +export interface PmMarketSplitRequest { + /** 市场 ID */ + marketId: string + /** 要 split 的 USDC 金额 */ + amount: number +} + +/** + * 调用测试服务器 /PmMarket/split 接口 + * - 需鉴权:请求头 x-token + * - 使用 VITE_API_BASE_URL 时可设为 http://192.168.3.21:8888 连接测试服 + */ +export async function pmMarketSplit( + data: PmMarketSplitRequest, + config?: { headers?: Record } +): Promise { + return post('/PmMarket/split', data, config) +} diff --git a/src/api/mockEventList.ts b/src/api/mockEventList.ts index e13810f..31ed6d8 100644 --- a/src/api/mockEventList.ts +++ b/src/api/mockEventList.ts @@ -112,4 +112,64 @@ export const MOCK_EVENT_LIST: PmEventListItem[] = [ series: [{ ID: 4, title: 'Sports', ticker: 'SPORT' }], tags: [{ label: 'World Cup', slug: 'world-cup' }], }, + // 5. 多 market:Elon Musk 推文数量区间(类似 Polymarket 多档位) + { + ID: 9005, + title: 'Elon Musk # tweets February 3 - February 10, 2026?', + slug: 'elon-musk-tweets-feb-2026', + ticker: 'TWEET', + image: '', + icon: '', + volume: 22136050, + endDate: '2026-02-10T23:59:59.000Z', + new: true, + markets: [ + { ID: 90051, question: '260-279', outcomes: ['Yes', 'No'], outcomePrices: [0.01, 0.99], volume: 120000 }, + { ID: 90052, question: '280-299', outcomes: ['Yes', 'No'], outcomePrices: [0.42, 0.58], volume: 939379 }, + { ID: 90053, question: '300-319', outcomes: ['Yes', 'No'], outcomePrices: [0.45, 0.55], volume: 850000 }, + { ID: 90054, question: '320-339', outcomes: ['Yes', 'No'], outcomePrices: [0.16, 0.84], volume: 320000 }, + { ID: 90055, question: '340-359', outcomes: ['Yes', 'No'], outcomePrices: [0.08, 0.92], volume: 180000 }, + ], + series: [{ ID: 5, title: 'Culture', ticker: 'CULTURE' }], + tags: [{ label: 'Tech', slug: 'tech' }, { label: 'Twitter', slug: 'twitter' }], + }, + // 6. 多 market:总统候选人胜选概率(多候选人) + { + ID: 9006, + title: 'Who wins the 2028 U.S. Presidential Election?', + slug: 'us-president-2028', + ticker: 'POL28', + image: '', + icon: '', + volume: 12500000, + endDate: '2028-11-07T23:59:59.000Z', + new: true, + markets: [ + { ID: 90061, question: 'Democrat nominee', outcomes: ['Yes', 'No'], outcomePrices: [0.48, 0.52], volume: 3200000 }, + { ID: 90062, question: 'Republican nominee', outcomes: ['Yes', 'No'], outcomePrices: [0.45, 0.55], volume: 3100000 }, + { ID: 90063, question: 'Third party / Independent', outcomes: ['Yes', 'No'], outcomePrices: [0.07, 0.93], volume: 800000 }, + ], + series: [{ ID: 6, title: 'Politics', ticker: 'POL' }], + tags: [{ label: 'Election', slug: 'election' }], + }, + // 7. 多 market:NBA 分区冠军(4 个选项) + { + ID: 9007, + title: 'NBA Eastern Conference Champion 2025-26?', + slug: 'nba-east-champion-2026', + ticker: 'NBA26', + image: '', + icon: '', + volume: 890000, + endDate: '2026-05-31T23:59:59.000Z', + new: false, + markets: [ + { ID: 90071, question: 'Boston Celtics', outcomes: ['Yes', 'No'], outcomePrices: [0.35, 0.65], volume: 280000 }, + { ID: 90072, question: 'Milwaukee Bucks', outcomes: ['Yes', 'No'], outcomePrices: [0.28, 0.72], volume: 220000 }, + { ID: 90073, question: 'Philadelphia 76ers', outcomes: ['Yes', 'No'], outcomePrices: [0.18, 0.82], volume: 150000 }, + { ID: 90074, question: 'New York Knicks', outcomes: ['Yes', 'No'], outcomePrices: [0.12, 0.88], volume: 120000 }, + ], + series: [{ ID: 7, title: 'Sports', ticker: 'SPORT' }], + tags: [{ label: 'NBA', slug: 'nba' }], + }, ] diff --git a/src/api/request.ts b/src/api/request.ts index 72df01f..fa6116e 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -39,3 +39,27 @@ export async function get( } return res.json() as Promise } + +/** + * 带 x-token 等自定义头的 POST 请求 + */ +export async function post( + path: string, + body?: unknown, + config?: RequestConfig +): Promise { + const url = new URL(path, BASE_URL || window.location.origin) + const headers: Record = { + 'Content-Type': 'application/json', + ...config?.headers, + } + const res = await fetch(url.toString(), { + method: 'POST', + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + }) + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${res.statusText}`) + } + return res.json() as Promise +} diff --git a/src/components/MarketCard.vue b/src/components/MarketCard.vue index 6399592..bfe36ea 100644 --- a/src/components/MarketCard.vue +++ b/src/components/MarketCard.vue @@ -261,6 +261,10 @@ const semiProgressColor = computed(() => { }) const navigateToDetail = () => { + if (props.displayType === 'multi' && (props.outcomes?.length ?? 0) > 1) { + router.push({ path: `/event/${props.id}/markets` }) + return + } router.push({ path: `/trade-detail/${props.id}`, query: { diff --git a/src/components/TradeComponent.vue b/src/components/TradeComponent.vue index 2925ce4..32e3a69 100644 --- a/src/components/TradeComponent.vue +++ b/src/components/TradeComponent.vue @@ -25,7 +25,7 @@ Merge - + Split @@ -44,7 +44,7 @@ text @click="handleOptionChange('yes')" > - Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }} + Yes {{ yesPriceCents }}¢ - No {{ selectedOption === 'no' ? '82¢' : '81¢' }} + No {{ noPriceCents }}¢ @@ -100,7 +100,7 @@ text @click="handleOptionChange('yes')" > - Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }} + Yes {{ yesPriceCents }}¢ - No {{ selectedOption === 'no' ? '82¢' : '81¢' }} + No {{ noPriceCents }}¢ @@ -151,7 +151,7 @@ text @click="handleOptionChange('yes')" > - Yes {{ selectedOption === 'yes' ? '19¢' : '18¢' }} + Yes {{ yesPriceCents }}¢ - No {{ selectedOption === 'no' ? '82¢' : '81¢' }} + No {{ noPriceCents }}¢ @@ -295,15 +295,15 @@ Limit Merge - Split + Split