@@ -8,13 +8,12 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
Repeat,
|
Repeat,
|
||||||
Search,
|
Search,
|
||||||
Settings2,
|
|
||||||
TrendingDown,
|
TrendingDown,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { PageHeader } from '@/components/PageHeader'
|
import { PageHeader } from '@/components/PageHeader'
|
||||||
import { EmptyState } from '@/components/EmptyState'
|
import { EmptyState } from '@/components/EmptyState'
|
||||||
import { AnalysisConfigDialog, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
import { type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||||
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
||||||
import { RpsRotationDialog } from '@/components/RpsRotationDialog'
|
import { RpsRotationDialog } from '@/components/RpsRotationDialog'
|
||||||
import { api, type MarketSnapshotRow } from '@/lib/api'
|
import { api, type MarketSnapshotRow } from '@/lib/api'
|
||||||
@@ -68,9 +67,7 @@ function loadConfig(): AnalysisFieldConfig {
|
|||||||
return storage.conceptAnalysisConfig.get({}) as AnalysisFieldConfig
|
return storage.conceptAnalysisConfig.get({}) as AnalysisFieldConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfig(c: AnalysisFieldConfig) {
|
|
||||||
storage.conceptAnalysisConfig.set(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickBestConfig(
|
function pickBestConfig(
|
||||||
configs: { id: string; label: string; description?: string; fields: { name: string; label: string }[] }[],
|
configs: { id: string; label: string; description?: string; fields: { name: string; label: string }[] }[],
|
||||||
@@ -234,8 +231,7 @@ function statSort(mode: SortMode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ConceptAnalysis() {
|
export function ConceptAnalysis() {
|
||||||
const [fieldConfig, setFieldConfig] = useState<AnalysisFieldConfig>(loadConfig)
|
const [fieldConfig] = useState<AnalysisFieldConfig>(loadConfig)
|
||||||
const [showConfig, setShowConfig] = useState(false)
|
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [selectedKey, setSelectedKey] = useState<string | null>(null)
|
const [selectedKey, setSelectedKey] = useState<string | null>(null)
|
||||||
const [sortMode, setSortMode] = useState<SortMode>('heat')
|
const [sortMode, setSortMode] = useState<SortMode>('heat')
|
||||||
@@ -301,12 +297,6 @@ export function ConceptAnalysis() {
|
|||||||
return set.size
|
return set.size
|
||||||
}, [stats])
|
}, [stats])
|
||||||
|
|
||||||
const handleSaveConfig = (c: AnalysisFieldConfig) => {
|
|
||||||
setFieldConfig(c)
|
|
||||||
saveConfig(c)
|
|
||||||
setSelectedKey(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configsQuery.isLoading) {
|
if (configsQuery.isLoading) {
|
||||||
return <div className="flex h-full items-center justify-center"><RefreshCw className="h-5 w-5 animate-spin text-muted" /></div>
|
return <div className="flex h-full items-center justify-center"><RefreshCw className="h-5 w-5 animate-spin text-muted" /></div>
|
||||||
}
|
}
|
||||||
@@ -316,19 +306,9 @@ export function ConceptAnalysis() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader title="概念分析" />
|
||||||
title="概念分析"
|
|
||||||
right={
|
|
||||||
<button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
|
|
||||||
<Settings2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<EmptyState icon={Layers3} title="暂无概念数据" hint="请通过 local→serve 同步概念数据" />
|
<EmptyState icon={Layers3} title="暂无概念数据" hint="请通过 local→serve 同步概念数据" />
|
||||||
</div>
|
</div>
|
||||||
<AnimatePresence>
|
|
||||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} />}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -348,9 +328,6 @@ export function ConceptAnalysis() {
|
|||||||
>
|
>
|
||||||
<Repeat className="h-3.5 w-3.5" />涨幅RPS轮动分析
|
<Repeat className="h-3.5 w-3.5" />涨幅RPS轮动分析
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
|
|
||||||
<Settings2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -388,9 +365,7 @@ export function ConceptAnalysis() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence>
|
|
||||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} />}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{previewSymbol && (
|
{previewSymbol && (
|
||||||
<StockPreviewDialog
|
<StockPreviewDialog
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { useMemo, useState, type ReactNode } from 'react'
|
import { useMemo, useState, type ReactNode } from 'react'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { AnimatePresence } from 'framer-motion'
|
|
||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
Crown,
|
Crown,
|
||||||
Layers3,
|
Layers3,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Search,
|
Search,
|
||||||
Settings2,
|
|
||||||
TrendingDown,
|
TrendingDown,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { PageHeader } from '@/components/PageHeader'
|
import { PageHeader } from '@/components/PageHeader'
|
||||||
import { EmptyState } from '@/components/EmptyState'
|
import { EmptyState } from '@/components/EmptyState'
|
||||||
import { AnalysisConfigDialog, DimensionHeatmap, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
import { DimensionHeatmap, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||||
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
||||||
import { api, type MarketSnapshotRow } from '@/lib/api'
|
import { api, type MarketSnapshotRow } from '@/lib/api'
|
||||||
import { QK } from '@/lib/queryKeys'
|
import { QK } from '@/lib/queryKeys'
|
||||||
@@ -68,9 +66,7 @@ interface IndustryStat {
|
|||||||
function loadConfig(): AnalysisFieldConfig {
|
function loadConfig(): AnalysisFieldConfig {
|
||||||
return storage.industryAnalysisConfig.get({}) as AnalysisFieldConfig
|
return storage.industryAnalysisConfig.get({}) as AnalysisFieldConfig
|
||||||
}
|
}
|
||||||
function saveConfig(c: AnalysisFieldConfig) {
|
|
||||||
storage.industryAnalysisConfig.set(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 自动选取最佳数据源 =====
|
// ===== 自动选取最佳数据源 =====
|
||||||
|
|
||||||
@@ -267,8 +263,7 @@ function groupByIndustryLevel(groups: DimensionGroup[], level: IndustryLevel): D
|
|||||||
// ===== 主页面 =====
|
// ===== 主页面 =====
|
||||||
|
|
||||||
export function IndustryAnalysis() {
|
export function IndustryAnalysis() {
|
||||||
const [fieldConfig, setFieldConfig] = useState<AnalysisFieldConfig>(loadConfig)
|
const [fieldConfig] = useState<AnalysisFieldConfig>(loadConfig)
|
||||||
const [showConfig, setShowConfig] = useState(false)
|
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [selectedKey, setSelectedKey] = useState<string | null>(null)
|
const [selectedKey, setSelectedKey] = useState<string | null>(null)
|
||||||
const [sortMode, setSortMode] = useState<SortMode>('heat')
|
const [sortMode, setSortMode] = useState<SortMode>('heat')
|
||||||
@@ -349,12 +344,6 @@ export function IndustryAnalysis() {
|
|||||||
return map
|
return map
|
||||||
}, [marketMap])
|
}, [marketMap])
|
||||||
|
|
||||||
const handleSaveConfig = (c: AnalysisFieldConfig) => {
|
|
||||||
setFieldConfig(c)
|
|
||||||
saveConfig(c)
|
|
||||||
setSelectedKey(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configsQuery.isLoading) {
|
if (configsQuery.isLoading) {
|
||||||
return <div className="flex h-full items-center justify-center"><RefreshCw className="h-5 w-5 animate-spin text-muted" /></div>
|
return <div className="flex h-full items-center justify-center"><RefreshCw className="h-5 w-5 animate-spin text-muted" /></div>
|
||||||
}
|
}
|
||||||
@@ -364,19 +353,10 @@ export function IndustryAnalysis() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader
|
<PageHeader title="行业分析" />
|
||||||
title="行业分析"
|
|
||||||
right={
|
|
||||||
<button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
|
|
||||||
<Settings2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<EmptyState icon={Layers3} title="暂无行业数据" hint="请通过 local→serve 同步行业数据" />
|
<EmptyState icon={Layers3} title="暂无行业数据" hint="请通过 local→serve 同步行业数据" />
|
||||||
</div>
|
</div>
|
||||||
<AnimatePresence>
|
|
||||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} showHierarchyLevel />}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -388,13 +368,6 @@ export function IndustryAnalysis() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="行业分析"
|
title="行业分析"
|
||||||
subtitle={`${industryLevelLabel} · ${marketQuery.data?.as_of ?? rowsQuery.data?.date ?? '最新'} · ${stats.length} 个行业 · ${totalSymbols} 只标的`}
|
subtitle={`${industryLevelLabel} · ${marketQuery.data?.as_of ?? rowsQuery.data?.date ?? '最新'} · ${stats.length} 个行业 · ${totalSymbols} 只标的`}
|
||||||
right={
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
|
|
||||||
<Settings2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="min-h-full bg-[radial-gradient(circle_at_12%_0%,rgba(245,158,11,0.12),transparent_28%),radial-gradient(circle_at_85%_8%,rgba(244,63,94,0.08),transparent_28%)] px-6 py-5">
|
<div className="min-h-full bg-[radial-gradient(circle_at_12%_0%,rgba(245,158,11,0.12),transparent_28%),radial-gradient(circle_at_85%_8%,rgba(244,63,94,0.08),transparent_28%)] px-6 py-5">
|
||||||
@@ -441,9 +414,7 @@ export function IndustryAnalysis() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence>
|
|
||||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} showHierarchyLevel />}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{previewSymbol && (
|
{previewSymbol && (
|
||||||
<StockPreviewDialog
|
<StockPreviewDialog
|
||||||
|
|||||||
Reference in New Issue
Block a user