Files
PolyHermes/frontend/src/constants/whaleMonitor.ts
T
WrBugandCursor 1d47cf0451 feat(whale-monitor): 保存策略时落库缓存市场并回显标题
- 创建/更新策略时复用 MarketService.getMarkets 写入 markets 表缓存
- 策略 DTO 增加 markets 字段,编辑页直接用其显示市场标题

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 05:19:27 +08:00

230 lines
6.2 KiB
TypeScript

import type { WhaleMonitorFormDraft, WhaleMonitorMarketItem } from '../types'
export const WHALE_MONITOR_FORM_DRAFT_KEY = 'whale_monitor_form_draft'
export function saveWhaleMonitorFormDraft(draft: WhaleMonitorFormDraft): void {
sessionStorage.setItem(WHALE_MONITOR_FORM_DRAFT_KEY, JSON.stringify(draft))
}
export function loadWhaleMonitorFormDraft(): WhaleMonitorFormDraft | null {
const raw = sessionStorage.getItem(WHALE_MONITOR_FORM_DRAFT_KEY)
if (!raw) return null
try {
return JSON.parse(raw) as WhaleMonitorFormDraft
} catch {
return null
}
}
export function clearWhaleMonitorFormDraft(): void {
sessionStorage.removeItem(WHALE_MONITOR_FORM_DRAFT_KEY)
}
export function conditionIdsToMarkets(
conditionIds: string[],
known: WhaleMonitorMarketItem[] = []
): WhaleMonitorMarketItem[] {
return conditionIds.map(id => {
const found = known.find(m => m.conditionId === id)
return found ?? { conditionId: id, title: id }
})
}
/** 策略列表/详情中的 markets 字段转为表单展示项 */
export function strategyMarketsToItems(
markets: Array<{
conditionId: string
title: string
slug?: string
category?: string
image?: string
icon?: string
}>
): WhaleMonitorMarketItem[] {
return markets.map(m => ({
conditionId: m.conditionId,
title: m.title,
slug: m.slug,
category: m.category,
image: m.image,
icon: m.icon
}))
}
/** 解析 Gamma 返回的 outcomes JSON 字符串 */
export function parseMarketOutcomes(outcomes?: string): string[] {
if (!outcomes?.trim()) return []
try {
const parsed = JSON.parse(outcomes) as unknown
if (Array.isArray(parsed)) {
return parsed.filter((item): item is string => typeof item === 'string' && item.length > 0)
}
} catch {
return outcomes.split(',').map(s => s.trim()).filter(Boolean)
}
return []
}
export interface WhaleMonitorMarketGroup {
key: string
title: string
markets: WhaleMonitorMarketItem[]
imageUrl?: string
}
/** 市场行展示图:image > icon > eventImage */
export function pickMarketImageUrl(market?: WhaleMonitorMarketItem | null): string | undefined {
if (!market) return undefined
return market.image?.trim() || market.icon?.trim() || market.eventImage?.trim() || undefined
}
/** 赛事分组头图:优先 eventImage,否则取组内首个市场图 */
export function pickGroupImageUrl(markets: WhaleMonitorMarketItem[]): string | undefined {
if (markets.length === 0) return undefined
const eventImg = markets[0].eventImage?.trim()
if (eventImg) return eventImg
return pickMarketImageUrl(markets[0])
}
export interface WhaleMonitorMarketSection {
key: 'game' | 'season'
title: string
groups: WhaleMonitorMarketGroup[]
}
export interface WhaleMonitorMarketSectionLabels {
game: string
season: string
ungrouped: string
}
/** 先按单场/长期分块,再在块内按赛事或分类分组 */
export function groupMarketsBySection(
markets: WhaleMonitorMarketItem[],
labels: WhaleMonitorMarketSectionLabels
): WhaleMonitorMarketSection[] {
if (markets.length === 0) return []
const hasTyped = markets.some(m => m.marketType === 'game' || m.marketType === 'season')
if (!hasTyped) {
const groups = groupMarketsForDisplay(markets, labels.ungrouped)
if (groups.length === 0) return []
return [{ key: 'season', title: labels.ungrouped, groups }]
}
const sections: WhaleMonitorMarketSection[] = []
const games = markets.filter(m => m.marketType === 'game')
const seasons = markets.filter(m => m.marketType === 'season')
if (games.length > 0) {
sections.push({
key: 'game',
title: labels.game,
groups: groupMarketsForDisplay(games, labels.ungrouped)
})
}
if (seasons.length > 0) {
sections.push({
key: 'season',
title: labels.season,
groups: groupMarketsForDisplay(seasons, labels.ungrouped)
})
}
return sections
}
/** 按赛事/分类分组展示市场列表 */
export function groupMarketsForDisplay(
markets: WhaleMonitorMarketItem[],
ungroupedLabel: string
): WhaleMonitorMarketGroup[] {
if (markets.length === 0) return []
const eventMap = new Map<string, WhaleMonitorMarketItem[]>()
const miscMarkets: WhaleMonitorMarketItem[] = []
for (const market of markets) {
if (market.marketType === 'season') {
miscMarkets.push(market)
continue
}
const eventTitle = market.eventTitle?.trim()
if (eventTitle) {
const list = eventMap.get(eventTitle) ?? []
list.push(market)
eventMap.set(eventTitle, list)
} else {
miscMarkets.push(market)
}
}
if (eventMap.size > 0) {
const groups: WhaleMonitorMarketGroup[] = Array.from(eventMap.entries())
.map(([title, items]) => ({
key: `event:${title}`,
title,
markets: items,
imageUrl: pickGroupImageUrl(items)
}))
.sort((a, b) => a.title.localeCompare(b.title))
if (miscMarkets.length > 0) {
groups.push({
key: 'misc',
title: ungroupedLabel,
markets: miscMarkets,
imageUrl: pickMarketImageUrl(miscMarkets[0])
})
}
return groups
}
const categoryMap = new Map<string, WhaleMonitorMarketItem[]>()
for (const market of markets) {
const category = market.category?.trim() || ungroupedLabel
const list = categoryMap.get(category) ?? []
list.push(market)
categoryMap.set(category, list)
}
if (categoryMap.size <= 1) {
return [{ key: 'all', title: ungroupedLabel, markets }]
}
return Array.from(categoryMap.entries())
.map(([title, items]) => ({
key: `cat:${title}`,
title,
markets: items,
imageUrl: pickMarketImageUrl(items[0])
}))
.sort((a, b) => a.title.localeCompare(b.title))
}
export function toWhaleMonitorMarketItem(m: {
conditionId: string
title: string
slug?: string
category?: string
volume?: string
outcomes?: string
eventTitle?: string
marketType?: 'game' | 'season'
image?: string
icon?: string
eventImage?: string
}): WhaleMonitorMarketItem {
return {
conditionId: m.conditionId,
title: m.title,
slug: m.slug,
category: m.category,
volume: m.volume,
outcomes: m.outcomes,
eventTitle: m.eventTitle,
marketType: m.marketType,
image: m.image,
icon: m.icon,
eventImage: m.eventImage
}
}