diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 2d68a6d..bab4ac8 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -754,7 +754,7 @@ export function Layout() { 当前数据源无实时行情权限,
@@ -661,7 +661,7 @@ export function Data() { 当前无需 API Key,历史日K将使用免费通道获取。 实时行情、分钟K等能力取决于所选数据源,可在 - + 数据源设置 中配置。 diff --git a/frontend/src/pages/Review.tsx b/frontend/src/pages/Review.tsx index ede6109..2cc6b31 100644 --- a/frontend/src/pages/Review.tsx +++ b/frontend/src/pages/Review.tsx @@ -469,7 +469,7 @@ export function Review() {

手动或定时生成的复盘都会推送完整报告。复用「设置 → 实时监控」的 Webhook 配置。 {((reviewPushChannels.includes('feishu') && !feishuConfigured) || (reviewPushChannels.includes('wecom') && !wecomConfigured)) && ( - setShowSchedule(false)}> + setShowSchedule(false)}> 前往配置 → )} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 67957be..4493733 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -125,7 +125,7 @@ export function Settings() { > {activeTab.key === 'monitoring' ? - : } + : }

diff --git a/frontend/src/pages/settings/AI.tsx b/frontend/src/pages/settings/AI.tsx index 0e7a248..727cc19 100644 --- a/frontend/src/pages/settings/AI.tsx +++ b/frontend/src/pages/settings/AI.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, createContext, useContext } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Save, Loader2, Check, Wifi, WifiOff, Eye, EyeOff, Shield, @@ -8,6 +8,7 @@ import { import { useSettings } from '@/lib/useSharedQueries' import { api, type SettingsState } from '@/lib/api' import { QK } from '@/lib/queryKeys' +import { useCardFlash, cardFlashCls } from '@/lib/useCardFlash' // 统一的输入框样式(与项目其他设置页一致) const INPUT_CLS = @@ -70,7 +71,7 @@ const findPreset = (provider: string, baseUrl: string, codexCommand: string) => return provider === CODEX_PROVIDER ? p.codexCommand === codexCommand : p.url === baseUrl }) ?? PRESETS[0] -export function SettingsAIPanel() { +export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { const qc = useQueryClient() const settings = useSettings() const s = settings.data @@ -306,8 +307,9 @@ export function SettingsAIPanel() { } return ( +
- @@ -519,20 +521,27 @@ export function SettingsAIPanel() {
)} +
) } // ===== 通用卡片(与 Keys 页风格统一) ===== +// 卡片定位锚点: highlight= 时滚动到视口中央并闪烁 (见 useCardFlash) +const HighlightContext = createContext('') + interface CardProps { icon: React.ComponentType<{ className?: string }> title: string right?: React.ReactNode children: React.ReactNode + anchor?: string } -function Card({ icon: Icon, title, right, children }: CardProps) { - return ( +function Card({ icon: Icon, title, right, children, anchor }: CardProps) { + const highlight = useContext(HighlightContext) + const { ref, flash } = useCardFlash(anchor ? highlight : undefined, anchor ?? '') + const inner = (
@@ -544,6 +553,12 @@ function Card({ icon: Icon, title, right, children }: CardProps) { {children}
) + if (!anchor) return inner + return ( +
+ {inner} +
+ ) } // ===== 表单字段(统一 label + 输入框样式) ===== diff --git a/frontend/src/pages/settings/CustomSignals.tsx b/frontend/src/pages/settings/CustomSignals.tsx index d445b22..c19b12d 100644 --- a/frontend/src/pages/settings/CustomSignals.tsx +++ b/frontend/src/pages/settings/CustomSignals.tsx @@ -6,6 +6,7 @@ import { QK } from '@/lib/queryKeys' import { BUILTIN_SIGNAL_DEFINITIONS, type SignalKind } from '@/lib/signals' import { CustomSignalDialog } from '@/components/signals/CustomSignalDialog' import { Skeleton } from '@/components/data/Skeleton' +import { AnchorWrap } from '@/lib/useCardFlash' type SignalSection = 'builtin' | 'custom' @@ -16,7 +17,7 @@ const KIND_CLASS: Record = { both: 'bg-muted/10 text-muted', } -export function SettingsCustomSignalsPanel() { +export function SettingsCustomSignalsPanel({ highlight }: { highlight?: string } = {}) { const qc = useQueryClient() const list = useQuery({ queryKey: QK.customSignals, queryFn: api.customSignalsList }) const options = useQuery({ queryKey: QK.customSignalsOptions, queryFn: api.customSignalsOptions }) @@ -87,6 +88,7 @@ export function SettingsCustomSignalsPanel() { return (
+
@@ -133,6 +135,7 @@ export function SettingsCustomSignalsPanel() {
+
{activeSection === 'builtin' && (
diff --git a/frontend/src/pages/settings/DataSources.tsx b/frontend/src/pages/settings/DataSources.tsx index 06f90f3..1917a9d 100644 --- a/frontend/src/pages/settings/DataSources.tsx +++ b/frontend/src/pages/settings/DataSources.tsx @@ -5,6 +5,7 @@ import { Check, Database, Eye, EyeOff, KeyRound, Plus, RefreshCw, Zap, FileWarni import { api, type DataSourceItem, type PluginDataSourceItem } from '@/lib/api' import { QK } from '@/lib/queryKeys' import { useCapabilities, usePreferences } from '@/lib/useSharedQueries' +import { AnchorWrap } from '@/lib/useCardFlash' import { TIER_RANK, tierRank, tierStyle } from '@/lib/capability-labels' import { toast } from '@/components/Toast' import { DataSourceEditor } from './DataSourceEditor' @@ -367,7 +368,7 @@ function PluginKeyConfig({ plugin }: { plugin: PluginDataSourceItem }) { ) } -export function SettingsDataSourcesPanel() { +export function SettingsDataSourcesPanel({ highlight }: { highlight?: string } = {}) { const qc = useQueryClient() const prefs = usePreferences() const sources = useQuery({ queryKey: QK.dataSources, queryFn: api.dataSources }) @@ -585,6 +586,7 @@ export function SettingsDataSourcesPanel() { return (
{/* ===== 顶部: 当前数据源 + 数据源选择 (一个大卡片) ===== */} +
@@ -779,6 +781,7 @@ export function SettingsDataSourcesPanel() {
+
{/* ===== 下方: 编辑区 ===== */} diff --git a/frontend/src/pages/settings/Monitoring.tsx b/frontend/src/pages/settings/Monitoring.tsx index 7c435cc..779f1ba 100644 --- a/frontend/src/pages/settings/Monitoring.tsx +++ b/frontend/src/pages/settings/Monitoring.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect, useRef } from 'react' +import { useState, useCallback, useEffect, createContext, useContext } from 'react' import { Link } from 'react-router-dom' import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query' import { @@ -19,9 +19,14 @@ import { import { useUpdateQuoteInterval, useToggleRealtimeQuotes } from '@/lib/useSharedMutations' import { api } from '@/lib/api' import { QK } from '@/lib/queryKeys' +import { useCardFlash, cardFlashCls } from '@/lib/useCardFlash' import { toast } from '@/components/Toast' import { DepthConfigContent } from '@/components/data/DepthConfigCard' +// 卡片定位锚点: highlight= 时该卡片滚动到视口中央并闪烁高亮。 +// 其他页面用 /settings?tab=monitoring&highlight= 精确引导用户到某张卡片。 +const HighlightContext = createContext('') + // 页面 → 显示名 const PAGE_LABELS: Record = { 'overview-market': '看板', @@ -288,27 +293,13 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } = return () => window.clearTimeout(t) }, [minuteRefreshIntervalDraft, minuteRefreshInterval, save]) - // highlight=depth-fix 时闪烁高亮连板梯队修正卡片 - const [flash, setFlash] = useState(false) - const flashedRef = useRef(false) - useEffect(() => { - if (highlight === 'depth-fix' && !flashedRef.current) { - flashedRef.current = true - // 延迟一帧确保 DOM 已渲染, 再触发闪烁 - requestAnimationFrame(() => { - setFlash(true) - const t = setTimeout(() => setFlash(false), 2000) - return () => clearTimeout(t) - }) - } - }, [highlight]) - return ( +
{/* ========== 左列 ========== */}
{/* 行情状态 — 开关 + 间隔 */} - + + - {/* 连板梯队降级修正 (移至右列顶部) */} -
+ {/* 连板梯队降级修正 (右列顶部) */} )} -
{/* 盘中分钟增量落盘 (Expert 专有): 交易时段常驻服务, intraday.batch 独立配额 */} - + +

监控规则命中后,可把告警推送到外部。勾选渠道作为新建规则的默认推送, 单条规则仍可在编辑页独立修改。 @@ -841,6 +828,7 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } =

+
) } @@ -899,8 +887,10 @@ interface CardProps { children: React.ReactNode } -function Card({ icon: Icon, title, badge, right, children }: CardProps) { - return ( +function Card({ icon: Icon, title, badge, right, children, anchor }: CardProps & { anchor?: string }) { + const highlight = useContext(HighlightContext) + const { ref, flash } = useCardFlash(anchor ? highlight : undefined, anchor ?? '') + const inner = (
@@ -917,4 +907,10 @@ function Card({ icon: Icon, title, badge, right, children }: CardProps) { {children}
) + if (!anchor) return inner + return ( +
+ {inner} +
+ ) }