/** * 神之一手(one_divine_lot)客户端插件 * * 注册: * - conversation.view tabs:全部持仓 / 交易记录 / 关注列表(通用,按 tabConfig 显隐)+ 策略 tabs(按策略配置) * - settings.section:神之一手设置菜单(通用设置 + 策略分组) * * 迭代 02: * - O2 策略 tab 动态化:策略配置驱动注册 * - 设置 tab 化扩展:三个通用 tab(全部持仓/交易记录/关注列表)由 tabConfig 控制显隐 * - 刷新页面 = 插件重载 = 按最新配置重新注册(无事件机制) */ import { createElement } from 'react'; import { AllPositionsTab } from './views/AllPositionsTab.jsx'; import { StrategyTab } from './views/StrategyTab.jsx'; import { PlaceholderTab } from './views/PlaceholderTab.jsx'; import { TradeRecordsTab } from './views/TradeRecordsTab.jsx'; import { SettingsSection } from './views/SettingsSection.jsx'; import { QmtConnectionChip } from './views/QmtConnectionChip.jsx'; import { ConnectionProvider } from './views/connection.jsx'; import { MarketDataProvider } from './market/MarketDataProvider.jsx'; export const inject = ['slots', 'connection']; async function fetchStrategies() { try { const res = await fetch('/odl/api/strategies', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); const data = await res.json(); return data && data.ok ? data.value : []; } catch (e) { return []; } } async function fetchTabConfig() { try { const res = await fetch('/odl/api/tabs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); const data = await res.json(); if (data && data.ok) { return { allPositions: data.value.allPositions !== false, tradeRecords: data.value.tradeRecords !== false, watchlist: data.value.watchlist !== false, }; } } catch (e) { /* ignore */ } return { allPositions: true, tradeRecords: true, watchlist: true }; } function strategyTabId(strategyId) { return 'odl-strategy-' + strategyId; } const GENERAL_TABS = [ { key: 'allPositions', id: 'odl-all-positions', label: '全部持仓', order: 10, render: (withConnection) => withConnection((props) => createElement(AllPositionsTab, props)) }, { key: 'tradeRecords', id: 'odl-trade-records', label: '交易记录', order: 11, render: (withConnection) => withConnection((props) => createElement(TradeRecordsTab, props)) }, { key: 'watchlist', id: 'odl-watchlist', label: '关注列表', order: 12, render: (withConnection) => withConnection((props) => createElement(PlaceholderTab, { ...props, title: '关注列表' })) }, ]; export function apply(ctx) { const connection = ctx.get('connection'); if (!connection) { ctx.logger?.warn?.('[one-divine-lot] connection 服务不可用'); } // 神之一手 tab 激活时隐藏会话输入框(composer) // 借鉴 dsh-context 的纯 CSS 方案::has(.odl-root) 命中 → 隐藏 [data-composer-seat] // 我们的 tab 根元素带 odl-root class;chat/其他 tab 无此 class → 输入框正常显示 const ODL_HIDE_COMPOSER_CSS = ` [data-conversation-scroll]:has(.odl-root)>[data-composer-seat]{display:none} `; if (typeof document !== 'undefined' && !document.querySelector('style[data-plugin-css="one-divine-lot/hide-composer"]')) { const tag = document.createElement('style'); tag.dataset.pluginCss = 'one-divine-lot/hide-composer'; tag.textContent = ODL_HIDE_COMPOSER_CSS; document.head.appendChild(tag); } const withConnection = (render) => (props) => createElement(ConnectionProvider, { connection }, createElement(MarketDataProvider, null, render(props))); ctx.slots.register({ name: 'settings.section', id: 'one-divine-lot', order: 50, label: () => '神之一手', }, withConnection((props) => createElement(SettingsSection, props))); let tabDisposers = []; let disposed = false; const disposeAllTabs = () => { for (const d of tabDisposers) { try { d(); } catch (e) { /* ignore */ } } tabDisposers = []; }; const registerGeneralTabs = (tabConfig) => { for (const tab of GENERAL_TABS) { if (tabConfig[tab.key] === false) continue; const dispose = ctx.slots.register({ name: 'conversation.view', id: tab.id, order: tab.order, label: () => tab.label, }, tab.render(withConnection)); tabDisposers.push(dispose); } }; const registerStrategyTabs = (strategies) => { const visible = strategies.filter((s) => s.visible !== false); visible.forEach((s, idx) => { const dispose = ctx.slots.register({ name: 'conversation.view', id: strategyTabId(s.id), order: 13 + idx, label: () => s.name, }, withConnection((props) => createElement(StrategyTab, { ...props, strategyId: s.id, strategyName: s.name }))); tabDisposers.push(dispose); }); return visible.length; }; const registerAllTabs = async () => { const [strategies, tabConfig] = await Promise.all([fetchStrategies(), fetchTabConfig()]); if (disposed) return; disposeAllTabs(); registerGeneralTabs(tabConfig); const n = registerStrategyTabs(strategies); ctx.logger?.info?.('[one-divine-lot] 已注册通用 tabs + ' + n + ' 个策略 tab'); }; registerAllTabs(); // R-004 Q9:会话头部快捷切换 chip(与 DSH 内置 PTC 标签 order=-10 并排,技术约束-009) ctx.slots.register({ name: 'conversation.session.header.actions', id: 'odl-qmt-switch', order: -9, }, withConnection((props) => createElement(QmtConnectionChip, props))); ctx.effect(() => { return () => { disposed = true; disposeAllTabs(); }; }, 'one-divine-lot.client'); }