mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
feat: 概念/行业分析页数据缺失时显示「获取数据」按钮
数据缺失时引导用户手动触发拉取, 而非静默无数据。 前端: - analysis-shared.tsx: 新增 PresetFetchState 共享组件 (图标+提示+按钮+错误展示) - api.ts: 新增 extDataPresetFetch(id) 封装 - ConceptAnalysis/IndustryAnalysis: 两个空状态接入 PresetFetchState, 新增 fetchMutation + needsXxxFetch 判断 (内置预设存在但无数据时显示按钮)
This commit is contained in:
@@ -12,9 +12,12 @@ import { useMemo, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
AlertCircle,
|
||||
BarChart3,
|
||||
ChevronDown,
|
||||
Database,
|
||||
DownloadCloud,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Settings2,
|
||||
Tags,
|
||||
@@ -449,3 +452,50 @@ export function ConfigButton({ onClick }: { onClick: () => void }) {
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置预设 (概念/行业) 数据获取空状态。
|
||||
*
|
||||
* 当检测到内置预设存在但无数据时, 展示图标 + 提示 + 「获取数据」按钮,
|
||||
* 让用户手动触发拉取 (POST /api/ext-data/presets/{id}/fetch), 而非自动拉取。
|
||||
*/
|
||||
export function PresetFetchState({
|
||||
title,
|
||||
hint,
|
||||
isLoading,
|
||||
error,
|
||||
onFetch,
|
||||
}: {
|
||||
title: string
|
||||
hint: string
|
||||
isLoading: boolean
|
||||
error: unknown
|
||||
onFetch: () => void
|
||||
}) {
|
||||
const errMsg = error instanceof Error ? error.message : error ? String(error) : ''
|
||||
return (
|
||||
<div className="h-full grid place-items-center px-8 py-16">
|
||||
<div className="text-center max-w-md">
|
||||
<DownloadCloud className="mx-auto h-10 w-10 text-muted" strokeWidth={1.5} />
|
||||
<h2 className="mt-4 text-base font-medium text-foreground">{title}</h2>
|
||||
<p className="mt-2 text-sm text-secondary leading-relaxed">{hint}</p>
|
||||
<button
|
||||
onClick={onFetch}
|
||||
disabled={isLoading}
|
||||
className="mt-5 inline-flex items-center gap-2 rounded-lg bg-accent px-4 py-2 text-sm font-medium text-white transition-colors hover:brightness-110 disabled:opacity-60"
|
||||
>
|
||||
{isLoading ? (
|
||||
<><RefreshCw className="h-4 w-4 animate-spin" /> 获取中...</>
|
||||
) : (
|
||||
<><DownloadCloud className="h-4 w-4" /> 获取数据</>
|
||||
)}
|
||||
</button>
|
||||
{errMsg && (
|
||||
<p className="mt-3 flex items-center justify-center gap-1.5 text-xs text-bear">
|
||||
<AlertCircle className="h-3.5 w-3.5" /> {errMsg}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1242,6 +1242,13 @@ export const api = {
|
||||
{ method: 'POST' },
|
||||
),
|
||||
|
||||
// 内置预设 (概念/行业) 手动获取数据: 走结构转换, 保证 schema 一致
|
||||
extDataPresetFetch: (id: string) =>
|
||||
request<{ status: string; rows: number }>(
|
||||
`/api/ext-data/presets/${id}/fetch`,
|
||||
{ method: 'POST' },
|
||||
),
|
||||
|
||||
extDataDetectFields: (file: File) => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', file)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useMemo, useState, type ReactNode } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { AnimatePresence } from 'framer-motion'
|
||||
import {
|
||||
Activity,
|
||||
Crown,
|
||||
Database,
|
||||
Layers3,
|
||||
RefreshCw,
|
||||
Search,
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { EmptyState } from '@/components/EmptyState'
|
||||
import { AnalysisConfigDialog, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
import { AnalysisConfigDialog, PresetFetchState, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
||||
import { api, type MarketSnapshotRow } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
@@ -256,6 +255,21 @@ export function ConceptAnalysis() {
|
||||
enabled: !!activeConfigId,
|
||||
})
|
||||
|
||||
// 内置概念预设 (ext_gn_ths) 手动获取数据
|
||||
const PRESET_CONCEPT_ID = 'ext_gn_ths'
|
||||
const queryClient = useQueryClient()
|
||||
const fetchMutation = useMutation({
|
||||
mutationFn: () => api.extDataPresetFetch(PRESET_CONCEPT_ID),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: QK.extData })
|
||||
queryClient.invalidateQueries({ queryKey: QK.extDataRows(PRESET_CONCEPT_ID, undefined, PAGE_LIMIT) })
|
||||
},
|
||||
})
|
||||
// 是否处于「内置概念预设存在但无数据」状态 → 显示获取按钮
|
||||
const needsConceptFetch =
|
||||
!!activeConfig && activeConfig.id === PRESET_CONCEPT_ID &&
|
||||
!rowsQuery.isLoading && (rowsQuery.data?.total ?? 0) === 0
|
||||
|
||||
const marketQuery = useQuery({
|
||||
queryKey: QK.marketSnapshot,
|
||||
queryFn: api.marketSnapshot,
|
||||
@@ -310,6 +324,7 @@ export function ConceptAnalysis() {
|
||||
}
|
||||
|
||||
if (!activeConfig) {
|
||||
// 极端情况: 无任何概念配置。仍提供一键获取内置概念数据入口
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full flex-col">
|
||||
@@ -321,7 +336,13 @@ export function ConceptAnalysis() {
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<EmptyState icon={Database} title="暂无概念数据" hint={'请先在"数据"页面创建包含概念/题材字段的扩展数据源'} />
|
||||
<PresetFetchState
|
||||
title="暂无概念数据"
|
||||
hint="从同花顺获取概念分类数据后即可使用概念分析"
|
||||
isLoading={fetchMutation.isPending}
|
||||
error={fetchMutation.error}
|
||||
onFetch={() => fetchMutation.mutate()}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} />}
|
||||
@@ -379,6 +400,14 @@ export function ConceptAnalysis() {
|
||||
</div>
|
||||
) : rowsQuery.isLoading ? (
|
||||
<div className="rounded-2xl border border-border bg-surface px-6 py-16 text-center text-sm text-muted">正在计算概念强度...</div>
|
||||
) : needsConceptFetch ? (
|
||||
<PresetFetchState
|
||||
title="未获取概念数据"
|
||||
hint="内置概念数据源已就绪,点击下方按钮从同花顺获取概念分类数据"
|
||||
isLoading={fetchMutation.isPending}
|
||||
error={fetchMutation.error}
|
||||
onFetch={() => fetchMutation.mutate()}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState icon={Layers3} title="未匹配到概念数据" hint={resolved.hint || '请检查扩展数据是否包含概念/题材相关字段'} />
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useMemo, useState, type ReactNode } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { AnimatePresence } from 'framer-motion'
|
||||
import {
|
||||
Activity,
|
||||
Crown,
|
||||
Database,
|
||||
Layers3,
|
||||
RefreshCw,
|
||||
Search,
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { EmptyState } from '@/components/EmptyState'
|
||||
import { AnalysisConfigDialog, DimensionHeatmap, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
import { AnalysisConfigDialog, DimensionHeatmap, PresetFetchState, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
||||
import { api, type MarketSnapshotRow } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
@@ -291,6 +290,21 @@ export function IndustryAnalysis() {
|
||||
enabled: !!activeConfigId,
|
||||
})
|
||||
|
||||
// 内置行业预设 (ext_hy_ths) 手动获取数据
|
||||
const PRESET_INDUSTRY_ID = 'ext_hy_ths'
|
||||
const queryClient = useQueryClient()
|
||||
const fetchMutation = useMutation({
|
||||
mutationFn: () => api.extDataPresetFetch(PRESET_INDUSTRY_ID),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: QK.extData })
|
||||
queryClient.invalidateQueries({ queryKey: QK.extDataRows(PRESET_INDUSTRY_ID, undefined, PAGE_LIMIT) })
|
||||
},
|
||||
})
|
||||
// 是否处于「内置行业预设存在但无数据」状态 → 显示获取按钮
|
||||
const needsIndustryFetch =
|
||||
!!activeConfig && activeConfig.id === PRESET_INDUSTRY_ID &&
|
||||
!rowsQuery.isLoading && (rowsQuery.data?.total ?? 0) === 0
|
||||
|
||||
const marketQuery = useQuery({
|
||||
queryKey: QK.marketSnapshot,
|
||||
queryFn: api.marketSnapshot,
|
||||
@@ -361,6 +375,7 @@ export function IndustryAnalysis() {
|
||||
}
|
||||
|
||||
if (!activeConfig) {
|
||||
// 极端情况: 无任何行业配置。仍提供一键获取内置行业数据入口
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full flex-col">
|
||||
@@ -372,7 +387,13 @@ export function IndustryAnalysis() {
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<EmptyState icon={Database} title="暂无行业数据" hint={'请先在"数据"页面创建包含行业/板块字段的扩展数据源'} />
|
||||
<PresetFetchState
|
||||
title="暂无行业数据"
|
||||
hint="从同花顺获取行业分类数据后即可使用行业分析"
|
||||
isLoading={fetchMutation.isPending}
|
||||
error={fetchMutation.error}
|
||||
onFetch={() => fetchMutation.mutate()}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} showHierarchyLevel />}
|
||||
@@ -443,6 +464,14 @@ export function IndustryAnalysis() {
|
||||
</div>
|
||||
) : rowsQuery.isLoading ? (
|
||||
<div className="rounded-2xl border border-border bg-surface px-6 py-16 text-center text-sm text-muted">正在计算行业强度...</div>
|
||||
) : needsIndustryFetch ? (
|
||||
<PresetFetchState
|
||||
title="未获取行业数据"
|
||||
hint="内置行业数据源已就绪,点击下方按钮从同花顺获取行业分类数据"
|
||||
isLoading={fetchMutation.isPending}
|
||||
error={fetchMutation.error}
|
||||
onFetch={() => fetchMutation.mutate()}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState icon={Layers3} title="未匹配到行业数据" hint={resolved.hint || '请检查扩展数据是否包含行业/板块相关字段'} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user