feat(whale-monitor): 优化市场选择页交互与分类缓存
- 体育联赛通过 series_id 展示单场比赛,长期市场分块可折叠 - 移除全部分类,支持搜索与分类切换缓存、首屏自动加载 - 单组列表扁平展示,列表行不展示图片(联赛下拉保留 logo) - 补充大单监听功能说明与 Code Review 清单文档 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { groupMarketsBySection, type WhaleMonitorMarketGroup } from '../constants/whaleMonitor'
|
||||
import type { WhaleMonitorMarketItem } from '../types'
|
||||
import WhaleMonitorMarketListItem from './WhaleMonitorMarketListItem'
|
||||
import WhaleMonitorMarketThumbnail from './WhaleMonitorMarketThumbnail'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
|
||||
@@ -16,31 +15,44 @@ interface WhaleMonitorMarketGroupedListProps {
|
||||
onToggleGroup: (markets: WhaleMonitorMarketItem[], checked: boolean) => void
|
||||
}
|
||||
|
||||
const renderFlatMarketList = (
|
||||
markets: WhaleMonitorMarketItem[],
|
||||
selectedMap: Map<string, WhaleMonitorMarketItem>,
|
||||
isMobile: boolean,
|
||||
onToggleMarket: (market: WhaleMonitorMarketItem, checked: boolean) => void,
|
||||
compact = false
|
||||
) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: isMobile ? 4 : 8 }}>
|
||||
{markets.map(market => (
|
||||
<WhaleMonitorMarketListItem
|
||||
key={market.conditionId}
|
||||
market={market}
|
||||
checked={selectedMap.has(market.conditionId)}
|
||||
isMobile={isMobile}
|
||||
hideEventTitle
|
||||
compact={compact}
|
||||
onToggle={checked => onToggleMarket(market, checked)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderMarketGroups = (
|
||||
groups: WhaleMonitorMarketGroup[],
|
||||
selectedMap: Map<string, WhaleMonitorMarketItem>,
|
||||
isMobile: boolean,
|
||||
onToggleMarket: (market: WhaleMonitorMarketItem, checked: boolean) => void,
|
||||
onToggleGroup: (markets: WhaleMonitorMarketItem[], checked: boolean) => void,
|
||||
t: (key: string, options?: Record<string, number>) => string
|
||||
t: (key: string, options?: Record<string, number>) => string,
|
||||
defaultActiveKeys?: string[]
|
||||
) => {
|
||||
const showAsGroups = groups.length > 1 || (groups.length === 1 && groups[0].key.startsWith('event:'))
|
||||
|
||||
if (!showAsGroups) {
|
||||
const flatMarkets = groups.flatMap(g => g.markets)
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: isMobile ? 4 : 8 }}>
|
||||
{flatMarkets.map(market => (
|
||||
<WhaleMonitorMarketListItem
|
||||
key={market.conditionId}
|
||||
market={market}
|
||||
checked={selectedMap.has(market.conditionId)}
|
||||
isMobile={isMobile}
|
||||
hideEventTitle
|
||||
onToggle={checked => onToggleMarket(market, checked)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
if (groups.length <= 1) {
|
||||
return renderFlatMarketList(
|
||||
groups.flatMap(g => g.markets),
|
||||
selectedMap,
|
||||
isMobile,
|
||||
onToggleMarket,
|
||||
groups.length === 1 && groups[0].key.startsWith('event:')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,16 +74,13 @@ const renderMarketGroups = (
|
||||
paddingRight: 8
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flex: 1, minWidth: 0 }}>
|
||||
<WhaleMonitorMarketThumbnail src={group.imageUrl} size={32} alt={group.title} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text strong style={{ wordBreak: 'break-word' }}>
|
||||
{group.title}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12, marginLeft: 0, display: 'block' }}>
|
||||
{t('whaleMonitorStrategy.marketSelect.marketsInGroup', { count: group.markets.length })}
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text strong style={{ wordBreak: 'break-word' }}>
|
||||
{group.title}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12, display: 'block' }}>
|
||||
{t('whaleMonitorStrategy.marketSelect.marketsInGroup', { count: group.markets.length })}
|
||||
</Text>
|
||||
</div>
|
||||
<div onClick={e => e.stopPropagation()} onKeyDown={e => e.stopPropagation()}>
|
||||
<Checkbox
|
||||
@@ -105,13 +114,44 @@ const renderMarketGroups = (
|
||||
return (
|
||||
<Collapse
|
||||
bordered={false}
|
||||
defaultActiveKey={groups.map(g => g.key)}
|
||||
defaultActiveKey={defaultActiveKeys ?? groups.map(g => g.key)}
|
||||
items={collapseItems}
|
||||
style={{ background: 'transparent' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSectionHeader = (sectionTitle: string, marketCount: number) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
{sectionTitle}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 13, fontWeight: 400 }}>
|
||||
({marketCount})
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderSectionBody = (
|
||||
section: { key: 'game' | 'season'; groups: WhaleMonitorMarketGroup[] },
|
||||
selectedMap: Map<string, WhaleMonitorMarketItem>,
|
||||
isMobile: boolean,
|
||||
onToggleMarket: (market: WhaleMonitorMarketItem, checked: boolean) => void,
|
||||
onToggleGroup: (markets: WhaleMonitorMarketItem[], checked: boolean) => void,
|
||||
t: (key: string, options?: Record<string, number>) => string
|
||||
) => {
|
||||
const isSeason = section.key === 'season'
|
||||
return renderMarketGroups(
|
||||
section.groups,
|
||||
selectedMap,
|
||||
isMobile,
|
||||
onToggleMarket,
|
||||
onToggleGroup,
|
||||
t,
|
||||
isSeason ? [] : section.groups.map(g => g.key)
|
||||
)
|
||||
}
|
||||
|
||||
const WhaleMonitorMarketGroupedList: React.FC<WhaleMonitorMarketGroupedListProps> = ({
|
||||
markets,
|
||||
selectedMap,
|
||||
@@ -131,37 +171,95 @@ const WhaleMonitorMarketGroupedList: React.FC<WhaleMonitorMarketGroupedListProps
|
||||
[markets, t]
|
||||
)
|
||||
|
||||
const totalGroups = sections.reduce((sum, s) => sum + s.groups.length, 0)
|
||||
const allMarkets = sections.flatMap(s => s.groups.flatMap(g => g.markets))
|
||||
|
||||
if (totalGroups <= 1) {
|
||||
return renderFlatMarketList(allMarkets, selectedMap, isMobile, onToggleMarket)
|
||||
}
|
||||
|
||||
if (sections.length === 1) {
|
||||
const section = sections[0]
|
||||
const showSectionTitle =
|
||||
markets.some(m => m.marketType === 'game' || m.marketType === 'season') ||
|
||||
section.groups.some(g => g.key.startsWith('event:'))
|
||||
const marketCount = section.groups.reduce((sum, g) => sum + g.markets.length, 0)
|
||||
|
||||
if (section.key === 'season' && section.groups.length > 1) {
|
||||
return (
|
||||
<Collapse
|
||||
bordered={false}
|
||||
defaultActiveKey={[]}
|
||||
style={{ background: 'transparent' }}
|
||||
items={[
|
||||
{
|
||||
key: 'season-section',
|
||||
label: renderSectionHeader(section.title, marketCount),
|
||||
children: renderSectionBody(
|
||||
section,
|
||||
selectedMap,
|
||||
isMobile,
|
||||
onToggleMarket,
|
||||
onToggleGroup,
|
||||
t
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSectionTitle && (
|
||||
<Title level={5} style={{ marginTop: 0, marginBottom: 12 }}>
|
||||
{section.title}
|
||||
</Title>
|
||||
)}
|
||||
{renderMarketGroups(section.groups, selectedMap, isMobile, onToggleMarket, onToggleGroup, t)}
|
||||
<Title level={5} style={{ marginTop: 0, marginBottom: 12 }}>
|
||||
{section.title}
|
||||
<Text type="secondary" style={{ fontSize: 13, fontWeight: 400, marginLeft: 8 }}>
|
||||
({marketCount})
|
||||
</Text>
|
||||
</Title>
|
||||
{renderSectionBody(section, selectedMap, isMobile, onToggleMarket, onToggleGroup, t)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sections.map((section, index) => (
|
||||
<div key={section.key}>
|
||||
{index > 0 && <Divider style={{ margin: '16px 0' }} />}
|
||||
<Title level={5} style={{ marginTop: index === 0 ? 0 : undefined, marginBottom: 12 }}>
|
||||
{section.title}
|
||||
<Text type="secondary" style={{ fontSize: 13, fontWeight: 400, marginLeft: 8 }}>
|
||||
({section.groups.reduce((sum, g) => sum + g.markets.length, 0)})
|
||||
</Text>
|
||||
</Title>
|
||||
{renderMarketGroups(section.groups, selectedMap, isMobile, onToggleMarket, onToggleGroup, t)}
|
||||
</div>
|
||||
))}
|
||||
{sections.map((section, index) => {
|
||||
const marketCount = section.groups.reduce((sum, g) => sum + g.markets.length, 0)
|
||||
return (
|
||||
<div key={section.key}>
|
||||
{index > 0 && <Divider style={{ margin: '16px 0' }} />}
|
||||
{section.key === 'season' && section.groups.length > 1 ? (
|
||||
<Collapse
|
||||
bordered={false}
|
||||
defaultActiveKey={[]}
|
||||
style={{ background: 'transparent' }}
|
||||
items={[
|
||||
{
|
||||
key: 'season-section',
|
||||
label: renderSectionHeader(section.title, marketCount),
|
||||
children: renderSectionBody(
|
||||
section,
|
||||
selectedMap,
|
||||
isMobile,
|
||||
onToggleMarket,
|
||||
onToggleGroup,
|
||||
t
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Title level={5} style={{ marginTop: index === 0 ? 0 : undefined, marginBottom: 12 }}>
|
||||
{section.title}
|
||||
<Text type="secondary" style={{ fontSize: 13, fontWeight: 400, marginLeft: 8 }}>
|
||||
({marketCount})
|
||||
</Text>
|
||||
</Title>
|
||||
{renderSectionBody(section, selectedMap, isMobile, onToggleMarket, onToggleGroup, t)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Checkbox, Tag, Typography } from 'antd'
|
||||
import { LinkOutlined } from '@ant-design/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { parseMarketOutcomes, pickMarketImageUrl } from '../constants/whaleMonitor'
|
||||
import { parseMarketOutcomes } from '../constants/whaleMonitor'
|
||||
import type { WhaleMonitorMarketItem } from '../types'
|
||||
import { formatUSDC } from '../utils'
|
||||
import WhaleMonitorMarketThumbnail from './WhaleMonitorMarketThumbnail'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@@ -33,9 +32,6 @@ const WhaleMonitorMarketListItem: React.FC<WhaleMonitorMarketListItemProps> = ({
|
||||
const volumeDisplay =
|
||||
market.volume && parseFloat(market.volume) > 0 ? formatUSDC(market.volume) : null
|
||||
const polymarketUrl = market.slug ? `https://polymarket.com/event/${market.slug}` : null
|
||||
const imageUrl = pickMarketImageUrl(market)
|
||||
const thumbSize = compact ? 36 : isMobile ? 40 : 44
|
||||
|
||||
const handleOpenLink = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (polymarketUrl) {
|
||||
@@ -72,7 +68,6 @@ const WhaleMonitorMarketListItem: React.FC<WhaleMonitorMarketListItemProps> = ({
|
||||
onChange={e => onToggle(e.target.checked)}
|
||||
style={{ marginTop: 2 }}
|
||||
/>
|
||||
<WhaleMonitorMarketThumbnail src={imageUrl} size={thumbSize} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
{market.eventTitle && !hideEventTitle && (
|
||||
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 2 }}>
|
||||
|
||||
@@ -1907,7 +1907,7 @@
|
||||
"subtitle": "Filter by category or search by name. Multiple selection supported.",
|
||||
"back": "Back",
|
||||
"searchPlaceholder": "Search market name (min. 2 characters)",
|
||||
"searchHint": "Enter keywords or pick a category above",
|
||||
"searchHint": "Pick a category above, or enter at least 2 characters to search",
|
||||
"sportsGamesHint": "Sports leagues show individual game markets; search by team name",
|
||||
"sportsGamesEmpty": "No active game markets for this league. Try searching a team name or check back later.",
|
||||
"empty": "No markets found",
|
||||
|
||||
@@ -1907,7 +1907,7 @@
|
||||
"subtitle": "按分类筛选或搜索市场名称,可多选",
|
||||
"back": "返回",
|
||||
"searchPlaceholder": "搜索市场名称(至少 2 个字符)",
|
||||
"searchHint": "请输入关键词搜索,或选择上方分类筛选",
|
||||
"searchHint": "请选择上方分类,或输入至少 2 个字符搜索",
|
||||
"sportsGamesHint": "体育联赛将展示单场比赛盘口;也可输入队名搜索",
|
||||
"sportsGamesEmpty": "该联赛当前暂无进行中的单场比赛盘口,可尝试搜索队名或稍后再试",
|
||||
"empty": "未找到匹配的市场",
|
||||
|
||||
@@ -1907,7 +1907,7 @@
|
||||
"subtitle": "按分類篩選或搜索市場名稱,可多選",
|
||||
"back": "返回",
|
||||
"searchPlaceholder": "搜索市場名稱(至少 2 個字符)",
|
||||
"searchHint": "請輸入關鍵詞搜索,或選擇上方分類篩選",
|
||||
"searchHint": "請選擇上方分類,或輸入至少 2 個字符搜索",
|
||||
"sportsGamesHint": "體育聯賽將展示單場比賽盤口;也可輸入隊名搜索",
|
||||
"sportsGamesEmpty": "該聯賽當前暫無進行中的單場比賽盤口,可嘗試搜索隊名或稍後再試",
|
||||
"empty": "未找到匹配的市場",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
@@ -23,13 +23,27 @@ import type { WhaleMonitorMarketItem, WhaleMonitorMarketSelectLocationState } fr
|
||||
const { Title, Text } = Typography
|
||||
|
||||
const TAG_OPTIONS = [
|
||||
{ key: 'all', value: '' },
|
||||
{ key: 'sports', value: '1' },
|
||||
{ key: 'politics', value: '2' },
|
||||
{ key: 'crypto', value: '21' },
|
||||
{ key: 'popCulture', value: '100639' }
|
||||
] as const
|
||||
|
||||
const MIN_SEARCH_LENGTH = 2
|
||||
|
||||
interface TagViewCache {
|
||||
sportSubSeriesId?: string
|
||||
keyword: string
|
||||
debouncedKeyword: string
|
||||
markets: WhaleMonitorMarketItem[]
|
||||
}
|
||||
|
||||
const buildListCacheKey = (
|
||||
tag: string,
|
||||
seriesId: string | undefined,
|
||||
searchKeyword: string
|
||||
): string => `${tag}::${seriesId ?? ''}::${searchKeyword}`
|
||||
|
||||
const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
@@ -41,7 +55,7 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [debouncedKeyword, setDebouncedKeyword] = useState('')
|
||||
const [tagId, setTagId] = useState<string>('')
|
||||
const [tagId, setTagId] = useState<string | undefined>(undefined)
|
||||
const [sportSubSeriesId, setSportSubSeriesId] = useState<string | undefined>(undefined)
|
||||
const [sportSubCategories, setSportSubCategories] = useState<
|
||||
Array<{ id: number; slug: string; label: string; tagId: string; seriesId: string; image?: string }>
|
||||
@@ -54,6 +68,12 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
return map
|
||||
})
|
||||
|
||||
const tagViewCacheRef = useRef<Map<string, TagViewCache>>(new Map())
|
||||
const listCacheRef = useRef<Map<string, WhaleMonitorMarketItem[]>>(new Map())
|
||||
const skipNextFetchRef = useRef(false)
|
||||
const initialTagSetRef = useRef(false)
|
||||
const initialSportLeagueSetRef = useRef(false)
|
||||
|
||||
const tagSegmentOptions = useMemo(
|
||||
() =>
|
||||
TAG_OPTIONS.map(opt => ({
|
||||
@@ -68,17 +88,7 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
return () => clearTimeout(timer)
|
||||
}, [keyword])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagId === '1') {
|
||||
fetchSportSubCategories()
|
||||
setSportSubSeriesId(undefined)
|
||||
} else {
|
||||
setSportSubCategories([])
|
||||
setSportSubSeriesId(undefined)
|
||||
}
|
||||
}, [tagId])
|
||||
|
||||
const fetchSportSubCategories = async () => {
|
||||
const fetchSportSubCategories = useCallback(async () => {
|
||||
try {
|
||||
const res = await apiService.markets.sportsCategories()
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
@@ -91,35 +101,137 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
} catch {
|
||||
setSportSubCategories([])
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (initialTagSetRef.current) return
|
||||
initialTagSetRef.current = true
|
||||
setTagId(TAG_OPTIONS[0].value)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagId === '1') {
|
||||
if (sportSubCategories.length === 0) {
|
||||
fetchSportSubCategories()
|
||||
}
|
||||
} else {
|
||||
setSportSubCategories([])
|
||||
}
|
||||
}, [tagId, sportSubCategories.length, fetchSportSubCategories])
|
||||
|
||||
useEffect(() => {
|
||||
if (tagId !== '1' || sportSubCategories.length === 0) return
|
||||
if (sportSubSeriesId) return
|
||||
const tagCached = tagViewCacheRef.current.get('1')
|
||||
if (tagCached?.sportSubSeriesId) {
|
||||
setSportSubSeriesId(tagCached.sportSubSeriesId)
|
||||
return
|
||||
}
|
||||
if (!initialSportLeagueSetRef.current) {
|
||||
initialSportLeagueSetRef.current = true
|
||||
setSportSubSeriesId(sportSubCategories[0].seriesId)
|
||||
}
|
||||
}, [tagId, sportSubCategories, sportSubSeriesId])
|
||||
|
||||
const saveCurrentTagView = useCallback(() => {
|
||||
if (!tagId) return
|
||||
tagViewCacheRef.current.set(tagId, {
|
||||
sportSubSeriesId,
|
||||
keyword,
|
||||
debouncedKeyword,
|
||||
markets: marketList
|
||||
})
|
||||
listCacheRef.current.set(
|
||||
buildListCacheKey(tagId, sportSubSeriesId, debouncedKeyword),
|
||||
marketList
|
||||
)
|
||||
}, [tagId, sportSubSeriesId, keyword, debouncedKeyword, marketList])
|
||||
|
||||
const handleTagChange = (newTagId: string) => {
|
||||
if (newTagId === tagId) return
|
||||
saveCurrentTagView()
|
||||
|
||||
const cached = tagViewCacheRef.current.get(newTagId)
|
||||
skipNextFetchRef.current = !!cached
|
||||
|
||||
setTagId(newTagId)
|
||||
|
||||
if (cached) {
|
||||
setSportSubSeriesId(cached.sportSubSeriesId)
|
||||
setKeyword(cached.keyword)
|
||||
setDebouncedKeyword(cached.debouncedKeyword)
|
||||
setMarketList(cached.markets)
|
||||
if (newTagId === '1' && sportSubCategories.length === 0) {
|
||||
fetchSportSubCategories()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setKeyword('')
|
||||
setDebouncedKeyword('')
|
||||
setMarketList([])
|
||||
if (newTagId === '1') {
|
||||
setSportSubSeriesId(undefined)
|
||||
if (sportSubCategories.length === 0) {
|
||||
fetchSportSubCategories()
|
||||
}
|
||||
} else {
|
||||
setSportSubSeriesId(undefined)
|
||||
setSportSubCategories([])
|
||||
}
|
||||
}
|
||||
|
||||
const flushSearch = () => {
|
||||
setDebouncedKeyword(keyword.trim())
|
||||
}
|
||||
|
||||
const fetchMarkets = useCallback(async () => {
|
||||
if (!tagId) return
|
||||
|
||||
const selectedSport = sportSubCategories.find(s => s.seriesId === sportSubSeriesId)
|
||||
const seriesId = sportSubSeriesId
|
||||
const sportSlug = selectedSport?.slug
|
||||
const effectiveTagId = tagId && tagId !== '1' ? tagId : undefined
|
||||
const searchKeyword = debouncedKeyword
|
||||
const hasKeyword = searchKeyword.length >= MIN_SEARCH_LENGTH
|
||||
const categoryTagId = tagId !== '1' ? tagId : undefined
|
||||
const sportsTagId = tagId === '1' && !seriesId && hasKeyword ? '1' : undefined
|
||||
const effectiveTagId = categoryTagId || sportsTagId
|
||||
const listCacheKey = buildListCacheKey(tagId, sportSubSeriesId, searchKeyword)
|
||||
|
||||
if (tagId === '1' && !sportSubSeriesId && searchKeyword.length < 2) {
|
||||
if (tagId === '1' && !sportSubSeriesId && !hasKeyword) {
|
||||
setMarketList([])
|
||||
return
|
||||
}
|
||||
if (!seriesId && !effectiveTagId && searchKeyword.length < 2) {
|
||||
if (!seriesId && !effectiveTagId && !hasKeyword) {
|
||||
setMarketList([])
|
||||
return
|
||||
}
|
||||
|
||||
const listCached = listCacheRef.current.get(listCacheKey)
|
||||
if (listCached) {
|
||||
setMarketList(listCached)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await apiService.markets.search({
|
||||
keyword: searchKeyword.length >= 2 ? searchKeyword : '',
|
||||
keyword: hasKeyword ? searchKeyword : '',
|
||||
seriesId: seriesId || undefined,
|
||||
sportSlug: seriesId ? sportSlug : undefined,
|
||||
tagId: seriesId ? undefined : effectiveTagId,
|
||||
limit: 200
|
||||
})
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
setMarketList(res.data.data.map(m => toWhaleMonitorMarketItem(m)))
|
||||
const items = res.data.data.map(m => toWhaleMonitorMarketItem(m))
|
||||
listCacheRef.current.set(listCacheKey, items)
|
||||
setMarketList(items)
|
||||
tagViewCacheRef.current.set(tagId, {
|
||||
sportSubSeriesId,
|
||||
keyword,
|
||||
debouncedKeyword: searchKeyword,
|
||||
markets: items
|
||||
})
|
||||
} else {
|
||||
setMarketList([])
|
||||
}
|
||||
@@ -128,12 +240,21 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [debouncedKeyword, tagId, sportSubSeriesId, sportSubCategories])
|
||||
}, [debouncedKeyword, tagId, sportSubSeriesId, sportSubCategories, keyword])
|
||||
|
||||
useEffect(() => {
|
||||
if (skipNextFetchRef.current) {
|
||||
skipNextFetchRef.current = false
|
||||
return
|
||||
}
|
||||
fetchMarkets()
|
||||
}, [fetchMarkets])
|
||||
|
||||
const handleSportLeagueChange = (seriesId: string | undefined) => {
|
||||
saveCurrentTagView()
|
||||
setSportSubSeriesId(seriesId)
|
||||
}
|
||||
|
||||
const selectedList = useMemo(() => Array.from(selectedMap.values()), [selectedMap])
|
||||
|
||||
const toggleMarket = (market: WhaleMonitorMarketItem, checked: boolean) => {
|
||||
@@ -172,10 +293,14 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
navigate('/whale-monitor-strategy')
|
||||
}
|
||||
|
||||
const showSelectSportHint = tagId === '1' && !sportSubSeriesId && debouncedKeyword.length < 2
|
||||
const showSelectSportHint = tagId === '1' && !sportSubSeriesId && debouncedKeyword.length < MIN_SEARCH_LENGTH
|
||||
const showSportsGamesEmpty =
|
||||
tagId === '1' && !!sportSubSeriesId && marketList.length === 0 && !loading && debouncedKeyword.length < 2
|
||||
const showSearchHint = !tagId && debouncedKeyword.length < 2
|
||||
tagId === '1' &&
|
||||
!!sportSubSeriesId &&
|
||||
marketList.length === 0 &&
|
||||
!loading &&
|
||||
debouncedKeyword.length < MIN_SEARCH_LENGTH
|
||||
const showSearchHint = !tagId && debouncedKeyword.length < MIN_SEARCH_LENGTH
|
||||
|
||||
return (
|
||||
<div style={{ padding: isMobile ? 12 : 24, paddingBottom: isMobile ? 88 : 24 }}>
|
||||
@@ -202,6 +327,7 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
placeholder={t('whaleMonitorStrategy.marketSelect.searchPlaceholder')}
|
||||
value={keyword}
|
||||
onChange={e => setKeyword(e.target.value)}
|
||||
onPressEnter={flushSearch}
|
||||
size={isMobile ? 'large' : 'middle'}
|
||||
style={{ marginBottom: 12 }}
|
||||
/>
|
||||
@@ -209,7 +335,7 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
block={isMobile}
|
||||
options={tagSegmentOptions}
|
||||
value={tagId}
|
||||
onChange={val => setTagId(val as string)}
|
||||
onChange={val => handleTagChange(val)}
|
||||
style={{ marginBottom: tagId === '1' ? 12 : 0 }}
|
||||
/>
|
||||
{tagId === '1' && sportSubCategories.length > 0 && (
|
||||
@@ -220,7 +346,7 @@ const WhaleMonitorMarketSelect: React.FC = () => {
|
||||
placeholder={t('whaleMonitorStrategy.form.sportLeague')}
|
||||
style={{ width: '100%', marginTop: 12 }}
|
||||
value={sportSubSeriesId}
|
||||
onChange={setSportSubSeriesId}
|
||||
onChange={handleSportLeagueChange}
|
||||
optionFilterProp="label"
|
||||
size={isMobile ? 'large' : 'middle'}
|
||||
options={sportSubCategories.map(s => ({
|
||||
|
||||
Reference in New Issue
Block a user