feat(ui): 数据源页徽章/按钮统一 + 操作后即时刷新实时行情状态

- 第三方徽章统一琥珀色; 插件化说明精简为一句 AI 接入提示并置顶;
  三处「切换为当前数据源」按钮与「使用中」徽章统一样式
- 数据源列表卡片新增「已适配」数据集标识; TickFlow 详情格
  描述去档位词, 改为事实描述
- 修复: Key 保存/清除/重新检测与数据源切换/删除/安装/卸载/
  编辑保存后未失效 quoteStatus, 侧边栏实时行情最长 60s 才
  刷新, 检测成功仍显示「不可用」; 现同步失效 capabilities +
  quoteStatus 即时生效
This commit is contained in:
shy3130
2026-08-23 12:34:45 +08:00
parent da3ada28e1
commit feab972de0
3 changed files with 54 additions and 32 deletions
@@ -192,15 +192,15 @@ export function DataSourceEditor({
{!isNew && !isActive && config.name.trim() && (
<button
onClick={() => onActivate(config.name.toLowerCase().trim())}
className="inline-flex items-center gap-1 px-2.5 py-1 rounded-btn bg-accent/10 text-accent text-xs font-medium hover:bg-accent/20 transition-colors"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/10 text-accent text-xs font-medium hover:bg-accent/20 transition-colors"
>
<Zap className="h-3 w-3" />
<Zap className="h-3.5 w-3.5" />
</button>
)}
{!isNew && onDelete && (
<button
onClick={onDelete}
className="inline-flex items-center gap-1 px-2.5 py-1 rounded-btn text-xs text-muted hover:text-danger hover:bg-danger/10 transition-colors"
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-btn text-xs text-muted hover:text-danger hover:bg-danger/10 transition-colors"
>
<Trash2 className="h-3 w-3" />
</button>
+47 -29
View File
@@ -27,6 +27,9 @@ export function SettingsDataSourcesPanel() {
mutationFn: api.reloadDataSources,
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.dataSources })
// 重载可能改变数据集声明 → 能力与实时模式随之变化
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
toast('配置已重新加载', 'success')
},
})
@@ -36,6 +39,8 @@ export function SettingsDataSourcesPanel() {
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.dataSources })
qc.invalidateQueries({ queryKey: QK.preferences })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
setSelected('tickflow')
setConfirmDelete(null)
toast('数据源已删除', 'success')
@@ -71,6 +76,9 @@ export function SettingsDataSourcesPanel() {
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.preferences })
qc.invalidateQueries({ queryKey: QK.capabilities })
// 切换会改变实时行情 provider → 模式(none/watchlist/full_market)立即刷新
qc.invalidateQueries({ queryKey: QK.quoteStatus })
toast('数据源已切换', 'success')
},
})
@@ -84,6 +92,8 @@ export function SettingsDataSourcesPanel() {
mutationFn: (name: string) => api.installPlugin(name),
onSuccess: (data) => {
qc.invalidateQueries({ queryKey: QK.dataSources })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
if (data.install_ok) {
toast('插件依赖安装成功', 'success')
} else {
@@ -98,6 +108,8 @@ export function SettingsDataSourcesPanel() {
onSuccess: (data) => {
qc.invalidateQueries({ queryKey: QK.dataSources })
qc.invalidateQueries({ queryKey: QK.preferences })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
if (data.uninstall_ok) {
toast(data.uninstall_message || '已卸载', 'success')
} else {
@@ -154,6 +166,19 @@ export function SettingsDataSourcesPanel() {
</button>
</div>
{/* 插件化说明 (置顶黄色提示条): 接入自有行情 → 把文档交给 AI */}
<div className="mb-4 flex items-start gap-2 rounded-lg border border-warning/40 bg-warning/10 px-3 py-2.5">
<Puzzle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-warning" />
<div className="text-[11px] leading-relaxed text-muted">
<span className="text-secondary"></span>
,? AI :
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">docs/custom-data-source.md</span>
( HTTP ) ·
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">docs/plugin-development.md</span>
()
</div>
</div>
{/* 当前数据源状态 */}
<div className="flex items-center gap-2 mb-4 px-3 py-2.5 rounded-lg bg-elevated/30">
<span className="text-[10px] uppercase tracking-widest text-muted"></span>
@@ -199,10 +224,10 @@ export function SettingsDataSourcesPanel() {
{item.display_name}
</span>
{item.name === 'tickflow' && (
<span className="text-[9px] text-muted/50 uppercase tracking-wider shrink-0"></span>
<span className="shrink-0 rounded bg-warning/15 px-1 py-0.5 text-[9px] font-medium leading-none text-warning"></span>
)}
{pluginNames.has(item.name) && (
<span className="text-[9px] text-muted/50 uppercase tracking-wider shrink-0"></span>
<span className="shrink-0 rounded bg-warning/15 px-1 py-0.5 text-[9px] font-medium leading-none text-warning"></span>
)}
{/* 右侧操作区: 插件未安装→安装按钮; 已激活→使用中; 否则→使用/卸载 */}
{pluginUnavailable ? (
@@ -257,7 +282,8 @@ export function SettingsDataSourcesPanel() {
)}
</div>
{item.name !== 'tickflow' && item.datasets.length > 0 && (
<div className="flex flex-wrap gap-1 ml-3.5">
<div className="flex flex-wrap items-center gap-1 ml-3.5">
<span className="text-[9px] font-medium text-accent bg-accent/10 px-1 py-0.5 rounded"></span>
{item.datasets.map(ds => (
<span key={ds} className="text-[9px] text-muted/60 bg-elevated/60 px-1 py-0.5 rounded">
{DATASET_LABEL[ds] || ds}
@@ -266,7 +292,10 @@ export function SettingsDataSourcesPanel() {
</div>
)}
{item.name === 'tickflow' && (
<div className="text-[10px] text-muted/60 ml-3.5">K · · · K</div>
<div className="flex flex-wrap items-center gap-1 ml-3.5">
<span className="text-[9px] font-medium text-accent bg-accent/10 px-1 py-0.5 rounded"></span>
<span className="text-[10px] text-muted/60">K · · · K</span>
</div>
)}
{/* 未安装插件显示安装命令提示 */}
{pluginUnavailable && plugin?.install_hint && (
@@ -312,22 +341,6 @@ export function SettingsDataSourcesPanel() {
<span>退 TickFlow</span>
</div>
{/* 插件化说明 + 第三方数据源配置文档指引 (与引导页同口径) */}
<div className="mt-3 flex items-start gap-2 rounded-lg border border-border/60 bg-elevated/20 px-3 py-2.5">
<Puzzle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-accent/70" />
<div className="text-[11px] leading-relaxed text-muted">
<span className="text-secondary"></span>
,接入自有行情有两条路径: YAML HTTP ,
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">data/data_sources/*.yaml</span>
(也可用「新增数据源」表单配置);或开发插件源,放入
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">backend/app/plugins/</span>
目录。接入方法与字段映射详见
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">docs/custom-data-source.md</span>
<span className="mx-0.5 rounded bg-elevated/70 px-1 py-px font-mono text-[10px] text-secondary">docs/plugin-development.md</span>
</div>
</div>
</section>
{/* ===== 下方: 编辑区 ===== */}
@@ -352,6 +365,10 @@ export function SettingsDataSourcesPanel() {
onCancel={() => setSelected('tickflow')}
onSaved={() => {
qc.invalidateQueries({ queryKey: QK.dataSources })
// 数据集声明变化 → 能力增广与实时模式立即刷新
qc.invalidateQueries({ queryKey: QK.preferences })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
// 强制清除该源的详情缓存, 下次编辑重新拉取最新配置
if (selected !== '__new__') {
qc.removeQueries({ queryKey: ['data-source-detail', selected] })
@@ -430,15 +447,16 @@ function PluginDetail({ plugin, isActive, onSwitch, switching }: {
</div>
<div className="flex items-center gap-3">
{isActive ? (
<span className="inline-flex items-center gap-1.5 text-xs text-accent">
<Check className="h-3.5 w-3.5" /> 使
<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" /> 使
</span>
) : (
<button
onClick={onSwitch}
disabled={switching}
className="px-3 py-1.5 rounded-btn bg-accent/10 text-accent hover:bg-accent/20 text-xs font-medium transition-colors disabled:opacity-50"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/10 text-accent text-xs font-medium hover:bg-accent/20 transition-colors disabled:opacity-50"
>
<Zap className="h-3.5 w-3.5" />
</button>
)}
@@ -458,10 +476,10 @@ function TickFlowDetail({ active, onSwitch, switching }: { active: boolean; onSw
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h2 className="text-base font-semibold text-foreground">TickFlow</h2>
<span className="text-[10px] text-muted/60 uppercase tracking-wider border border-border rounded px-1.5 py-0.5"></span>
<span className="rounded bg-warning/15 px-1.5 py-0.5 text-[10px] font-medium text-warning"></span>
{active && (
<span className="inline-flex items-center gap-1 text-[10px] text-accent bg-accent/10 px-1.5 py-0.5 rounded">
<Check className="h-2.5 w-2.5" /> 使
<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" /> 使
</span>
)}
</div>
@@ -474,9 +492,9 @@ function TickFlowDetail({ active, onSwitch, switching }: { active: boolean; onSw
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2.5 mb-5">
{[
{ label: '日K', desc: '历史 + 实时覆写' },
{ label: '除权因子', desc: 'Starter+ 能力' },
{ label: '除权因子', desc: '复权计算' },
{ label: '实时行情', desc: '全市场快照' },
{ label: '分钟K', desc: 'Pro+ 能力' },
{ label: '分钟K', desc: '分时图 · 回测' },
].map(f => (
<div key={f.label} className="rounded-lg border border-border/50 bg-elevated/20 px-3 py-2.5">
<div className="text-xs font-medium text-foreground">{f.label}</div>
@@ -489,7 +507,7 @@ function TickFlowDetail({ active, onSwitch, switching }: { active: boolean; onSw
<button
onClick={onSwitch}
disabled={switching}
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-btn bg-accent text-white text-sm font-medium hover:bg-accent/90 disabled:opacity-50 transition-colors"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/10 text-accent text-xs font-medium hover:bg-accent/20 transition-colors disabled:opacity-50"
>
<Zap className="h-3.5 w-3.5" />
+4
View File
@@ -40,6 +40,8 @@ export function TickFlowKeyConfig() {
setKeyInput('')
qc.invalidateQueries({ queryKey: QK.settings })
qc.invalidateQueries({ queryKey: QK.capabilities })
// 档位变化会改变实时行情模式(none/watchlist/full_market), 立即刷新侧边栏状态
qc.invalidateQueries({ queryKey: QK.quoteStatus })
if (data.ok) {
setSaved(true)
setTimeout(() => setSaved(false), 2000)
@@ -53,6 +55,7 @@ export function TickFlowKeyConfig() {
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.settings })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
},
})
@@ -61,6 +64,7 @@ export function TickFlowKeyConfig() {
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.settings })
qc.invalidateQueries({ queryKey: QK.capabilities })
qc.invalidateQueries({ queryKey: QK.quoteStatus })
},
})