From 2cd0ea987b82d74db97b6d1913f1567dc6b137da Mon Sep 17 00:00:00 2001 From: shy3130 <415333856@qq.com> Date: Sun, 30 Aug 2026 19:05:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(onboarding):=20=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E6=AD=A5=E9=AA=A4=E6=94=B9=E4=B8=BA=E5=8F=AA=E8=AF=BB=E5=B1=95?= =?UTF-8?q?=E7=A4=BA,=20=E4=BB=85=E4=BF=9D=E7=95=99=20API=20Key=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Onboarding.tsx | 220 +++++++----------------------- 1 file changed, 51 insertions(+), 169 deletions(-) diff --git a/frontend/src/pages/Onboarding.tsx b/frontend/src/pages/Onboarding.tsx index a0455cc..8caa0f1 100644 --- a/frontend/src/pages/Onboarding.tsx +++ b/frontend/src/pages/Onboarding.tsx @@ -19,7 +19,6 @@ import { FileText, Landmark, Database, - Plus, Puzzle, KeyRound, Route, @@ -28,7 +27,6 @@ import { api } from '@/lib/api' import { usePreferences, useSettings } from '@/lib/useSharedQueries' import { QK } from '@/lib/queryKeys' import { Logo } from '@/components/Logo' -import { DataSourceEditor } from '@/pages/settings/DataSourceEditor' // ===== 引导页:5 步向导 ===== // 0. 声明 1. 欢迎 2. 数据源与 Key 3. 能力路由检测 4. 完成 → 写标记 → 进面板 @@ -287,71 +285,28 @@ const DATASET_LABELS: Record = { function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => void }) { const qc = useQueryClient() - const prefs = usePreferences() + const settings = useSettings() const sources = useQuery({ queryKey: QK.dataSources, queryFn: api.dataSources, staleTime: 60_000, }) - // null = 跟随后端当前激活源 (首次使用即默认内置源) - const [picked, setPicked] = useState(null) - // 是否展开「添加自有数据源」编辑器 - const [adding, setAdding] = useState(false) - // 正在内联配置 Key 的插件名 (仅 api_key_env 声明型, 如 fuyao) + // 内联 Key 配置目标 (tickflow = 档位 Key; fuyao = 实时/除权增强路由) const [keyFor, setKeyFor] = useState(null) const [keyInput, setKeyInput] = useState('') const [keyError, setKeyError] = useState(null) const [keySaved, setKeySaved] = useState(null) - const builtin = sources.data?.builtin ?? [] const plugins = sources.data?.plugins ?? [] + const builtin = sources.data?.builtin ?? [] const custom = sources.data?.custom ?? [] const items = [ ...builtin.map(s => ({ ...s, kind: 'builtin' as const })), ...plugins.map(s => ({ ...s, kind: 'plugin' as const })), ...custom.map(s => ({ ...s, kind: 'custom' as const })), ] - const byName = new Map(items.map(s => [s.name, s])) - const activeName = prefs.data?.daily_data_provider || 'tickflow' - const selected = picked ?? activeName - - // 切换数据源 —— 与设置页同一套偏好接口: - // 内置源 5 个数据集全量切换; 其他源仅切换其声明支持的数据集, 其余回落内置源 - const switchProvider = useMutation({ - mutationFn: (name: string) => { - if (name === 'tickflow') { - return api.updateDataProviders({ - daily_data_provider: 'tickflow', - adj_factor_provider: 'same_as_daily', - realtime_data_provider: 'tickflow', - minute_data_provider: 'tickflow', - financial_data_provider: 'tickflow', - }) - } - const supported = new Set(byName.get(name)?.datasets ?? []) - const pick = (dataset: string) => (supported.has(dataset) ? name : 'tickflow') - return api.updateDataProviders({ - daily_data_provider: pick('daily'), - adj_factor_provider: 'same_as_daily', - realtime_data_provider: pick('realtime'), - minute_data_provider: pick('minute'), - financial_data_provider: pick('financial'), - }) - }, - onSuccess: () => qc.invalidateQueries({ queryKey: QK.preferences }), - }) - - const choose = (name: string, available: boolean) => { - if (!available || name === selected || switchProvider.isPending) return - setAdding(false) - setPicked(name) - switchProvider.mutate(name) - } - - // 内联 Key 配置 (先探后存): 验证通过才落盘, 插件成功即自动选用该源 - // tickflow 走独立端点: 填 Key 解锁更高档位, 不改变数据源选择 const TICKFLOW_KEY_META = { name: 'tickflow', display_name: 'TickFlow', api_key_env: 'TICKFLOW_API_KEY', homepage: 'https://tickflow.org/auth/register?ref=V3KDKGXPEA', @@ -360,9 +315,10 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => ? (plugins.find(p => p.name === keyFor) ?? (keyFor === 'tickflow' ? TICKFLOW_KEY_META : undefined)) : undefined // 已配置态徽标: tickflow 看 settings.mode (free/api_key 均为已配 Key), fuyao 看插件 api_key_masked - const settings = useSettings() const tfConfigured = settings.data?.mode === 'free' || settings.data?.mode === 'api_key' const fuyaoConfigured = !!plugins.find(p => p.name === 'fuyao')?.api_key_masked + + // 内联 Key 配置 (先探后存): 验证通过才落盘 const saveKey = useMutation({ mutationFn: (name: string) => (name === 'tickflow' ? api.saveTickflowKey(keyInput.trim()) @@ -377,14 +333,12 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => setKeyError(null) if (name === 'tickflow') { setKeySaved(`TickFlow Key 已保存${data.tier_label ? `,当前档位:${data.tier_label}` : ''}`) - } else if (name === 'fuyao') { + } else if (name === 'fuyao' && data.plugin_available) { // fuyao 定位为增强源: 仅实时行情 + 除权因子路由过去, 其余数据集保持 TickFlow routeFuyaoEnhanced.mutate() setKeySaved('fuyao Key 已保存:实时行情与除权因子已切换到 fuyao,其余数据集保持 TickFlow') } else if (data.plugin_available) { - setPicked(name) - switchProvider.mutate(name) - setKeySaved('Key 已保存,已选用该数据源') + setKeySaved('Key 已保存,该数据源已就绪(可在设置中启用)') } setTimeout(() => setKeySaved(null), 6000) } else { @@ -412,12 +366,12 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () =>

配置数据源

- 所有数据源均为第三方服务,按需选择或添加自有接口;随时可在 - 设置 → 数据源 - 中调整。 + 默认使用内置 TickFlow 数据源(无需 Key 即可同步历史日K)。 + 可按下方的说明填写 API Key 增强数据能力;数据源的切换与增删随时在 + 设置 → 数据源 中进行。

- {/* 数据源卡片选择器 */} + {/* 数据源卡片 (只读展示): 不在向导中切换, 保留设置页完整能力 */} {sources.isLoading ? (
@@ -426,57 +380,45 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => ) : (
{items.map(item => { - const isSelected = selected === item.name const plugin = item.kind === 'plugin' ? plugins.find(p => p.name === item.name) : undefined - // 缺 Key 型 (声明了 api_key_env) 不算不可用: 引导页内联填写验证即可启用 + // 缺 Key 型插件 (如 fuyao) 卡片可点击展开 Key 表单; 其余纯展示 const needsKey = item.kind === 'plugin' && !item.available && !!plugin?.api_key_env const unavailable = item.kind === 'plugin' && !item.available && !needsKey - // 切换中的目标卡片: 圆点位置显示转圈, 其余卡片压暗 - const switchingToThis = switchProvider.isPending && switchProvider.variables === item.name + const needsKeyProps = needsKey ? { + type: 'button' as const, + onClick: () => { + setKeyFor(keyFor === item.name ? null : item.name) + setKeyInput('') + setKeyError(null) + }, + title: '需配置 API Key,点击填写', + } : {} return ( - +
) })} - - {/* 添加自有数据源卡片 */} -
)} @@ -544,30 +472,6 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => )} - {/* 插件化提示: 标识数据源体系已插件化, 并给出两条接入路径与文档指引 */} -
- -
- 数据源已插件化 - ,接入自有行情有两条路径:用 YAML 描述自有 HTTP 接口,放入 - data/data_sources/*.yaml - (也可用上方表单配置);或开发插件源,放入 - backend/app/plugins/ - 目录。接入方法与字段映射详见 - docs/custom-data-source.md - 与 - docs/plugin-development.md - 。 -
-
- - {switchProvider.isError && ( -
- - 数据源切换失败:{String((switchProvider.error as any)?.message ?? '')} -
- )} - {/* 内联 Key 配置 (缺 Key 型插件如 fuyao / TickFlow 档位 Key): 先探后存 */} {keyFor && keyPlugin && (
@@ -584,22 +488,10 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () => 留空即免费 None 模式,可直接使用;填写 Key 后按订阅档位解锁实时 / 分钟 / 盘口 / 财务等更多数据集。 仅存本地 (secrets.json),先验证后保存。 + ) : keyFor === 'fuyao' ? ( + '在官网申请 Key;仅存本地 (secrets.json),先验证后保存。保存成功后仅「实时行情」与「除权因子」切换到 fuyao,其余数据集保持 TickFlow。' ) : ( - <> - {keyPlugin.homepage ? ( - <> - 在{' '} - - 官网 - - {' '}申请 Key;仅存本地 (secrets.json),先验证后保存。保存成功后将自动选用该数据源。 - - ) : ( - keyFor === 'fuyao' - ? '在官网申请 Key;仅存本地 (secrets.json),先验证后保存。保存成功后仅「实时行情」与「除权因子」切换到 fuyao,其余数据集保持 TickFlow。' - : '向数据源官方申请 Key;仅存本地 (secrets.json),先验证后保存,成功后自动选用该数据源。' - )} - + '向数据源官方申请 Key;仅存本地 (secrets.json),先验证后保存。' )}

void; onBack: () =>
)} - {/* 添加自有数据源: 复用设置页编辑器 (命名/鉴权/数据集字段映射/测试/启用) */} - - {adding && ( - - setAdding(false)} - onSaved={() => { - qc.invalidateQueries({ queryKey: QK.dataSources }) - setAdding(false) - }} - activeName={activeName} - onActivate={name => switchProvider.mutate(name)} - /> - - )} - + {/* 插件化提示: 自有数据源接入与切换在设置页, 向导保持极简 */} +
+ +
+ 数据源已插件化 + ,接入自有行情或切换数据源请前往 + 设置 → 数据源 + ,方法详见 + docs/custom-data-source.md + 与 + docs/plugin-development.md + 。 +
+
{/* 底部操作 */}