mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
feat(ui): 数据源能力卡片显示 TickFlow 订阅档位要求与全档位标识
每张能力卡右上角显示该能力所需最低档位(全档位/Starter+/Pro+/Expert+, 配色与左侧菜单档位体系一致, 当前档位不足时琥珀描边提示), 替代原 "当前提供方"徽标(信息已由下方高亮标签承载)。TickFlow 详情头部与 列表卡片在"第三方"后增加 Expert 三色渐变的"已适配全档位"标识。
This commit is contained in:
@@ -4,7 +4,8 @@ import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Check, Database, Eye, EyeOff, KeyRound, Plus, RefreshCw, Zap, FileWarning, Puzzle, AlertCircle, CheckCircle2, Loader2, Save, Trash2 } from 'lucide-react'
|
||||
import { api, type DataSourceItem, type PluginDataSourceItem } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
import { usePreferences } from '@/lib/useSharedQueries'
|
||||
import { useCapabilities, usePreferences } from '@/lib/useSharedQueries'
|
||||
import { TIER_RANK, tierRank, tierStyle } from '@/lib/capability-labels'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { DataSourceEditor } from './DataSourceEditor'
|
||||
import { TickFlowKeyConfig } from './Keys'
|
||||
@@ -37,6 +38,50 @@ const DATASET_ROUTE: Record<string, {
|
||||
realtime: { field: 'realtime_data_provider', def: 'tickflow' },
|
||||
}
|
||||
|
||||
/** 各能力在 TickFlow 需要的最低订阅档位 (对照 tiers.yaml: 日K 全档位可用,
|
||||
* 全市场实时/除权因子需 Starter+, 分钟K需 Pro+, 财务需 Expert+) */
|
||||
const TICKFLOW_TIER_REQ: Record<string, string> = {
|
||||
daily: 'none',
|
||||
adj_factor: 'starter',
|
||||
realtime: 'starter',
|
||||
minute: 'pro',
|
||||
financial: 'expert',
|
||||
}
|
||||
|
||||
/** TickFlow 档位要求徽标: 按所需档位配色(与左侧菜单/Key 页一致), 当前档位不足时琥珀描边提示 */
|
||||
function TierReqChip({ tier, currentLabel }: { tier: string; currentLabel?: string }) {
|
||||
const text = tier === 'none' ? '全档位' : `${tier}+`
|
||||
const req = TIER_RANK[tier] ?? -1
|
||||
const unmet = currentLabel != null && tierRank(currentLabel) < req
|
||||
const t = tierStyle(tier)
|
||||
return (
|
||||
<span
|
||||
title={unmet
|
||||
? `TickFlow 该能力需 ${text} — 当前档位 ${currentLabel}`
|
||||
: `TickFlow 该能力需 ${text}`}
|
||||
className={`inline-flex h-[15px] shrink-0 items-center gap-1 rounded px-1.5 text-[9px] font-bold font-mono leading-none ${unmet ? 'ring-1 ring-warning/60' : ''}`}
|
||||
style={t.tagBg}
|
||||
>
|
||||
<span className="h-1 w-1 rounded-full shrink-0" style={t.dotStyle} />
|
||||
<span className="capitalize" style={t.labelTextStyle}>{text}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/** TickFlow「已适配全档位」标识: Expert 三色渐变(全档位体系里最醒目的身份色) */
|
||||
function AllTiersBadge({ size = 'text-[10px]' }: { size?: string }) {
|
||||
const t = tierStyle('expert')
|
||||
return (
|
||||
<span
|
||||
className="inline-flex shrink-0 items-center rounded px-1.5 py-0.5 font-medium leading-none"
|
||||
style={t.tagBg}
|
||||
title="无 Key 到 Expert 各订阅档位均有可用能力 — 日K全档位可用, 除权/分钟/财务等高级能力按订阅档位解锁"
|
||||
>
|
||||
<span className={size} style={t.labelTextStyle}>✦ 已适配全档位</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/** 卡片内静态数据集标签: 只展示该源适配了哪些数据集 (路由选择在下方能力卡片) */
|
||||
function DatasetChipRow({ datasets }: { datasets: string[] }) {
|
||||
if (datasets.length === 0) return null
|
||||
@@ -55,7 +100,7 @@ function DatasetChipRow({ datasets }: { datasets: string[] }) {
|
||||
/** 按能力路由网格: 每个数据源详情下展示一张能力卡,卡片上以可点标签列出
|
||||
* 所有具备该能力的数据源 — 点谁,该数据集就立刻由谁提供,可跨源自由组合。
|
||||
* TickFlow 详情含全部能力; 其他源只列自己参与的能力。 */
|
||||
function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesOf, providerOf, displayOf, pending, onSelect, anyCustom, onReset }: {
|
||||
function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesOf, providerOf, pending, onSelect, anyCustom, onReset }: {
|
||||
sourceName: string
|
||||
sourceDisplay: string
|
||||
datasets: string[]
|
||||
@@ -63,7 +108,6 @@ function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesO
|
||||
candidatesOf: (dataset: string) => { name: string; display: string }[]
|
||||
/** 该数据集当前的原始路由偏好值 (adj_factor 可能是 same_as_daily) */
|
||||
providerOf: (dataset: string) => string
|
||||
displayOf: (name?: string) => string
|
||||
pending?: boolean
|
||||
onSelect: (dataset: string, provider: string) => void
|
||||
anyCustom?: boolean
|
||||
@@ -71,6 +115,7 @@ function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesO
|
||||
}) {
|
||||
const isDefault = sourceName === 'tickflow'
|
||||
const dsList = isDefault ? [...datasets, 'financial'] : datasets
|
||||
const caps = useCapabilities()
|
||||
if (dsList.length === 0) return null
|
||||
|
||||
return (
|
||||
@@ -103,10 +148,13 @@ function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesO
|
||||
<div key={ds} className="rounded-lg border border-border/50 bg-elevated/20 px-3 py-2.5" title="该数据集暂不支持切换数据源">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium text-foreground">{label}</div>
|
||||
<div className="text-xs font-medium text-foreground truncate">{label}</div>
|
||||
{desc && <div className="text-[10px] text-muted mt-0.5">{desc}</div>}
|
||||
</div>
|
||||
<span className="shrink-0 px-1.5 py-0.5 rounded text-[10px] text-muted/50 bg-elevated/60">固定</span>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{isDefault && <TierReqChip tier={TICKFLOW_TIER_REQ[ds]} currentLabel={caps.data?.label} />}
|
||||
<span className="shrink-0 px-1.5 py-0.5 rounded text-[10px] text-muted/50 bg-elevated/60">固定</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -120,15 +168,8 @@ function SourceCapabilityGrid({ sourceName, sourceDisplay, datasets, candidatesO
|
||||
<div className="text-xs font-medium text-foreground">{label}</div>
|
||||
{desc && <div className="text-[10px] text-muted mt-0.5">{desc}</div>}
|
||||
</div>
|
||||
{/* 右上角: 当前提供方 */}
|
||||
<span
|
||||
title={`「${label}」当前由 ${displayOf(raw)} 提供`}
|
||||
className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] ${
|
||||
raw === route.def ? 'text-muted/50 bg-elevated/60' : 'bg-accent/15 text-accent font-medium'
|
||||
}`}
|
||||
>
|
||||
<span className="truncate max-w-[80px] inline-block align-middle">{displayOf(raw)}</span>
|
||||
</span>
|
||||
{/* 右上角: TickFlow 所需档位 (当前提供方由下方高亮标签指示) */}
|
||||
{isDefault && <TierReqChip tier={TICKFLOW_TIER_REQ[ds]} currentLabel={caps.data?.label} />}
|
||||
</div>
|
||||
{/* 提供方标签: 点谁该数据集就由谁提供,当前项高亮 */}
|
||||
<div className="flex flex-wrap gap-1 mt-2 pt-2 border-t border-border/50">
|
||||
@@ -626,7 +667,10 @@ export function SettingsDataSourcesPanel() {
|
||||
</span>
|
||||
)}
|
||||
{item.name === 'tickflow' && (
|
||||
<span className="shrink-0 rounded bg-warning/15 px-1 py-0.5 text-[9px] font-medium leading-none text-warning">第三方</span>
|
||||
<>
|
||||
<span className="shrink-0 rounded bg-warning/15 px-1 py-0.5 text-[9px] font-medium leading-none text-warning">第三方</span>
|
||||
<AllTiersBadge size="text-[9px]" />
|
||||
</>
|
||||
)}
|
||||
{pluginNames.has(item.name) && (
|
||||
<span className="shrink-0 rounded bg-warning/15 px-1 py-0.5 text-[9px] font-medium leading-none text-warning">第三方</span>
|
||||
@@ -785,7 +829,6 @@ export function SettingsDataSourcesPanel() {
|
||||
datasets={selectedCustom?.datasets || []}
|
||||
candidatesOf={routeCtx.candidatesOf}
|
||||
providerOf={routeCtx.providerOf}
|
||||
displayOf={routeCtx.displayOf}
|
||||
pending={routeCtx.pending}
|
||||
onSelect={routeCtx.onSelect}
|
||||
/>
|
||||
@@ -887,7 +930,6 @@ function PluginDetail({ plugin, isActive, onSwitch, switching, route }: {
|
||||
datasets={plugin.datasets}
|
||||
candidatesOf={route.candidatesOf}
|
||||
providerOf={route.providerOf}
|
||||
displayOf={route.displayOf}
|
||||
pending={route.pending}
|
||||
onSelect={route.onSelect}
|
||||
/>
|
||||
@@ -935,6 +977,7 @@ function TickFlowDetail({ active, onSwitch, switching, route }: {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<h2 className="text-base font-semibold text-foreground">TickFlow</h2>
|
||||
<span className="rounded bg-warning/15 px-1.5 py-0.5 text-[10px] font-medium text-warning">第三方</span>
|
||||
<AllTiersBadge />
|
||||
{active && (
|
||||
<span className="inline-flex items-center gap-1 text-[10px] text-accent bg-accent/10 px-2 py-1 rounded">
|
||||
<Check className="h-2.5 w-2.5" /> 服务中
|
||||
@@ -954,7 +997,6 @@ function TickFlowDetail({ active, onSwitch, switching, route }: {
|
||||
datasets={['daily', 'adj_factor', 'realtime', 'minute']}
|
||||
candidatesOf={route.candidatesOf}
|
||||
providerOf={route.providerOf}
|
||||
displayOf={route.displayOf}
|
||||
pending={route.pending}
|
||||
onSelect={route.onSelect}
|
||||
anyCustom={route.anyCustom}
|
||||
|
||||
Reference in New Issue
Block a user