diff --git a/frontend/src/components/monitor/RuleEditor.tsx b/frontend/src/components/monitor/RuleEditor.tsx index 7e9b411..dbb4216 100644 --- a/frontend/src/components/monitor/RuleEditor.tsx +++ b/frontend/src/components/monitor/RuleEditor.tsx @@ -1,7 +1,7 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { Link } from 'react-router-dom' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { Activity, Building2, ChartNoAxesCombined, Check, Layers3, ListPlus, Plus, RadioTower, Save, Search, Tags, TrendingUp, Waypoints, X } from 'lucide-react' +import { Activity, Building2, ChartNoAxesCombined, Check, ChevronDown, ChevronUp, Eraser, Layers3, ListPlus, Plus, RadioTower, Save, Search, Tags, TrendingUp, Waypoints, X } from 'lucide-react' import { api, genRuleId, type MonitorRule, type MonitorCondition, type SectorKind, type SectorMonitorTarget, type StrategyNotifyEvent } from '@/lib/api' import { DEFAULT_STRATEGY_NOTIFY_EVENTS, LEGACY_STRATEGY_NOTIFY_EVENTS, STRATEGY_NOTIFY_EVENT_OPTIONS } from '@/lib/strategyMonitorEvents' import { QK } from '@/lib/queryKeys' @@ -234,6 +234,30 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) { }) setWatchMenuOpen(false) } + + // ── 标的标签: 名称 + 板标(创/科/北) + 代码, 可逐个删除 ── + const [symbolsExpanded, setSymbolsExpanded] = useState(false) + const symbolsKey = draft.symbols.join(',') + // 名称映射: 本地即时缓存(搜索/自选数据) 优先, 缺失的由批量名称接口补齐 + // (覆盖编辑旧规则等本地无名称的场景)。key 随标的集变化, staleTime 长防抖。 + const localNamesRef = useRef>({}) + const recordLocalNames = (pairs: { symbol: string; name?: string | null }[]) => { + for (const p of pairs) { + if (p.name) localNamesRef.current[p.symbol] = p.name + } + } + if (watchlistQ.data?.symbols) recordLocalNames(watchlistQ.data.symbols) + if (symbolSearch.data?.results) recordLocalNames(symbolSearch.data.results) + const namesQ = useQuery({ + queryKey: ['instrument-names', symbolsKey], + queryFn: () => api.instrumentNames(draft.symbols), + enabled: draft.symbols.length > 0, + staleTime: 5 * 60_000, + }) + const nameBySymbol = useMemo( + () => ({ ...localNamesRef.current, ...(namesQ.data?.names ?? {}) }), + [symbolsKey, namesQ.data], + ) // 自选导入选项: 全部自选 + 各分组 (空分组隐藏) + 未分组 const watchImportOptions = (() => { const entries = watchlistQ.data?.symbols ?? [] @@ -732,15 +756,64 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) { {visibleScopes.map(s => )} {draft.scope === 'symbols' && ( -
- {draft.symbols.map(sym => ( - - {sym} - - - ))} +
+ {/* 收起态: 只显示「已加入 N 只」汇总, 点击展开管理 */} + {draft.symbols.length > 0 && !symbolsExpanded && ( + + )} + {/* 展开态: 名称+板标+代码 tag 流, 可逐个删除/清空/收起 */} + {draft.symbols.length > 0 && symbolsExpanded && ( + <> +
+ 已加入 {draft.symbols.length} + + + + +
+
+ {draft.symbols.map(sym => { + const b = boardTag(sym) + const name = nameBySymbol[sym] + return ( + + {name ?? sym} + {b && {b.label}} + {sym} + + + ) + })} +
+ + )} {/* 从自选/自选分组批量导入标的 */}