diff --git a/src/App.vue b/src/App.vue index 08b305b..204e94c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -56,7 +56,11 @@ const currentRoute = computed(() => { - + + + + + diff --git a/src/api/event.ts b/src/api/event.ts index 2043428..f2615dd 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -49,11 +49,18 @@ export interface PmEventListItem { [key: string]: unknown } -/** 对应 definitions polymarket.PmMarket 常用字段;outcomePrices 首项为 Yes 价格 */ +/** + * 对应 definitions polymarket.PmMarket 常用字段 + * - outcomes: 选项展示文案,如 ["Yes", "No"] 或 ["Up", "Down"],与 outcomePrices 一一对应 + * - outcomePrices: 各选项价格(首项为第一选项概率,如 Yes/Up) + */ export interface PmEventMarketItem { ID?: number question?: string slug?: string + /** 选项展示文案,与 outcomePrices 顺序一致 */ + outcomes?: string[] + /** 各选项价格,outcomes[0] 对应 outcomePrices[0] */ outcomePrices?: string[] | number[] endDate?: string volume?: number @@ -137,7 +144,23 @@ export async function findPmEvent( return get('/PmEvent/findPmEvent', { ID: id }, config) } -/** 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) */ +/** 多选项卡片中单个选项(用于左右滑动切换) */ +export interface EventCardOutcome { + title: string + /** 第一选项概率(来自 outcomePrices[0]) */ + chanceValue: number + /** 第一选项按钮文案(来自 outcomes[0],如 Yes / Up) */ + yesLabel?: string + /** 第二选项按钮文案(来自 outcomes[1],如 No / Down) */ + noLabel?: string + /** 可选,用于交易时区分 market */ + marketId?: string +} + +/** + * 首页卡片项(与 mapEventItemToCard 返回结构一致,用于缓存) + * displayType:single = 单一 Yes/No,multi = 多个选项左右滑动 + */ export interface EventCardItem { id: string marketTitle: string @@ -146,6 +169,15 @@ export interface EventCardItem { imageUrl: string category: string expiresAt: string + /** 展示类型:单一二元 或 多选项滑动 */ + displayType: 'single' | 'multi' + /** 多选项时每个选项的标题与概率,按顺序滑动展示 */ + outcomes?: EventCardOutcome[] + /** 单一类型时可选按钮文案,如 "Up"/"Down" */ + yesLabel?: string + noLabel?: string + /** 是否显示 NEW 角标 */ + isNew?: boolean } /** 内存缓存:列表数据,切换页面时复用,下拉刷新时清空 */ @@ -173,23 +205,31 @@ export function clearEventListCache(): void { eventListCache = null } +function marketChance(market: PmEventMarketItem): number { + const raw = market?.outcomePrices?.[0] + if (raw == null) return 17 + const yesPrice = parseFloat(String(raw)) + if (!Number.isFinite(yesPrice)) return 17 + return Math.min(100, Math.max(0, Math.round(yesPrice * 100))) +} + /** * 将 list 单项映射为首页 MarketCard 所需字段 - * 字段对应:ID→id, title→marketTitle, image→imageUrl, volume→marketInfo, - * outcomePrices[0]→chanceValue, series[0].title→category, endDate→expiresAt + * - 单一 market 或无 market:displayType single,当前逻辑 + * - 多个 markets:displayType multi,outcomes 为每项标题+概率,卡片内左右滑动切换 */ export function mapEventItemToCard(item: PmEventListItem): EventCardItem { const id = String(item.ID ?? '') const marketTitle = item.title ?? '' const imageUrl = item.image ?? item.icon ?? '' + const markets = item.markets ?? [] + const multi = markets.length > 1 + let chanceValue = 17 - const market = item.markets?.[0] - if (market?.outcomePrices?.[0] != null) { - const yesPrice = parseFloat(String(market.outcomePrices[0])) - if (Number.isFinite(yesPrice)) { - chanceValue = Math.min(100, Math.max(0, Math.round(yesPrice * 100))) - } + const firstMarket = markets[0] + if (firstMarket?.outcomePrices?.[0] != null) { + chanceValue = marketChance(firstMarket) } let marketInfo = '$0 Vol.' @@ -216,6 +256,16 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem { const category = item.series?.[0]?.title ?? item.tags?.[0]?.label ?? '' + const outcomes: EventCardOutcome[] | undefined = multi + ? markets.map((m) => ({ + title: m.question ?? '', + chanceValue: marketChance(m), + yesLabel: m.outcomes?.[0] ?? 'Yes', + noLabel: m.outcomes?.[1] ?? 'No', + marketId: m.ID != null ? String(m.ID) : undefined, + })) + : undefined + return { id, marketTitle, @@ -224,5 +274,10 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem { imageUrl, category, expiresAt, + displayType: multi ? 'multi' : 'single', + outcomes, + yesLabel: firstMarket?.outcomes?.[0] ?? 'Yes', + noLabel: firstMarket?.outcomes?.[1] ?? 'No', + isNew: item.new === true, } } diff --git a/src/api/mockEventList.ts b/src/api/mockEventList.ts new file mode 100644 index 0000000..e13810f --- /dev/null +++ b/src/api/mockEventList.ts @@ -0,0 +1,115 @@ +import type { PmEventListItem } from './event' + +/** + * 模拟事件列表,用于本地测试: + * - 动态按钮文案(Up/Down、Yes/No) + * - 多 markets 轮播 + */ +export const MOCK_EVENT_LIST: PmEventListItem[] = [ + // 1. 单一 market,按钮文案为 Up/Down(测试动态 outcomes) + { + ID: 9001, + title: 'S&P 500 (SPX) Opens Up or Down on February 10?', + slug: 'spx-up-down-feb10', + ticker: 'SPX', + image: '', + icon: '', + volume: 11000, + endDate: '2026-02-10T21:00:00.000Z', + new: true, + markets: [ + { + ID: 90011, + question: 'S&P 500 (SPX) Opens Up or Down on February 10?', + outcomes: ['Up', 'Down'], + outcomePrices: [0.48, 0.52], + }, + ], + series: [{ ID: 1, title: 'Economy', ticker: 'ECON' }], + tags: [{ label: 'Markets', slug: 'markets' }], + }, + // 2. 多个 markets,测试左右滑动轮播(NFL Champion 多支队伍) + { + ID: 9002, + title: 'NFL Champion 2027', + slug: 'nfl-champion-2027', + ticker: 'NFL27', + image: '', + icon: '', + volume: 196000, + endDate: '2027-02-15T00:00:00.000Z', + new: true, + markets: [ + { + ID: 90021, + question: 'Seattle Seahawks', + outcomes: ['Yes', 'No'], + outcomePrices: [0.12, 0.88], + }, + { + ID: 90022, + question: 'Buffalo Bills', + outcomes: ['Yes', 'No'], + outcomePrices: [0.08, 0.92], + }, + { + ID: 90023, + question: 'Kansas City Chiefs', + outcomes: ['Yes', 'No'], + outcomePrices: [0.18, 0.82], + }, + ], + series: [{ ID: 2, title: 'Sports', ticker: 'SPORT' }], + tags: [{ label: 'NFL', slug: 'nfl' }], + }, + // 3. 单一 market,Yes/No,带 + NEW + { + ID: 9003, + title: 'Will Trump pardon Ghislaine Maxwell by end of 2026?', + slug: 'trump-pardon-maxwell-2026', + ticker: 'POL', + image: '', + icon: '', + volume: 361000, + endDate: '2026-12-31T23:59:59.000Z', + new: false, + markets: [ + { + ID: 90031, + question: 'Will Trump pardon Ghislaine Maxwell by end of 2026?', + outcomes: ['Yes', 'No'], + outcomePrices: [0.09, 0.91], + }, + ], + series: [{ ID: 3, title: 'Politics', ticker: 'POL' }], + tags: [{ label: 'Politics', slug: 'politics' }], + }, + // 4. 两个 markets,不同 outcomes 文案(混合 Yes/No) + { + ID: 9004, + title: 'Which team wins the 2026 World Cup?', + slug: 'world-cup-2026-winner', + ticker: 'FIFA', + image: '', + icon: '', + volume: 52000, + endDate: '2026-07-19T00:00:00.000Z', + new: true, + markets: [ + { + ID: 90041, + question: 'Brazil', + outcomes: ['Yes', 'No'], + outcomePrices: [0.22, 0.78], + }, + { + ID: 90042, + question: 'Germany', + outcomes: ['Yes', 'No'], + outcomePrices: [0.15, 0.85], + }, + ], + series: [{ ID: 4, title: 'Sports', ticker: 'SPORT' }], + tags: [{ label: 'World Cup', slug: 'world-cup' }], + }, +] diff --git a/src/components/MarketCard.vue b/src/components/MarketCard.vue index 4ed81cd..f97b071 100644 --- a/src/components/MarketCard.vue +++ b/src/components/MarketCard.vue @@ -1,9 +1,8 @@ diff --git a/src/router/index.ts b/src/router/index.ts index 7516959..70d4fa9 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -34,6 +34,11 @@ const router = createRouter({ component: Wallet } ], + scrollBehavior(to, from, savedPosition) { + if (savedPosition) return savedPosition + if (to.hash) return { el: to.hash } + return { top: 0 } + }, }) export default router diff --git a/src/views/Home.vue b/src/views/Home.vue index 5ea86cc..39dfa2f 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,6 +1,6 @@