4ac8a29805
迭代09 (R-011): Tab 设置统一管理所有会话 tab
- settings.tabs 布尔对象 → 统一有序数组 (kind: builtin|strategy)
- 旧配置 schema union 兼容 + 读取归一化无感迁移
- strategies 收窄为 {id,name},策略行 name join 自动跟随改名
- 策略增删联动 tabs 条目 (add 追加末尾 / remove 联动删除)
- 客户端统一注册读 tabs 数组,内置与策略可任意混排
- 设置页 Tab 设置子 tab:混排表格 + 拖动手柄 + 显隐开关 + 落点立即持久化
- 策略分组瘦身:仅 新增/重命名/删除 + 指引提示
- 回归测试: test-r011-tabs (23) + test-r011-api (12)
迭代10 (R-012): UI 适配 DSH 浅色/深色/跟随系统主题
- 10 个视图文件 141 处硬编码色 → var(--dsw-alias-*, fallback)
- 语义映射: bg-layer/label/border/state/button-contrast 等
- 淡底 color-mix 自适应;遮罩 bg-mask-1;阴影 shadow-lv3
133 lines
5.0 KiB
JavaScript
133 lines
5.0 KiB
JavaScript
/**
|
||
* 神之一手(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 fetchTabs() {
|
||
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 && Array.isArray(data.value)) return data.value;
|
||
} catch (e) { /* ignore */ }
|
||
return null;
|
||
}
|
||
|
||
function strategyTabId(strategyId) {
|
||
return 'odl-strategy-' + strategyId;
|
||
}
|
||
|
||
// R-011:内置 tab 渲染映射(refKey → render);order 由 settings.tabs 统一管理
|
||
const GENERAL_RENDER = {
|
||
allPositions: (withConnection) => withConnection((props) => createElement(AllPositionsTab, props)),
|
||
tradeRecords: (withConnection) => withConnection((props) => createElement(TradeRecordsTab, props)),
|
||
watchlist: (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 = [];
|
||
};
|
||
|
||
// R-011:统一注册 —— 读 settings.tabs 有序数组,按 order 排序、过滤 visible
|
||
// builtin 走内置 render(GENERAL_RENDER),strategy 走 StrategyTab
|
||
const registerAllTabs = async () => {
|
||
const tabs = await fetchTabs();
|
||
if (disposed) return;
|
||
disposeAllTabs();
|
||
if (!tabs) return;
|
||
const sorted = tabs.slice().sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||
let n = 0;
|
||
for (const t of sorted) {
|
||
if (t.visible === false) continue;
|
||
const render = t.kind === 'builtin'
|
||
? GENERAL_RENDER[t.refKey]
|
||
: (withConnection) => withConnection((props) => createElement(StrategyTab, { ...props, strategyId: t.refId, strategyName: t.name }));
|
||
if (!render) continue;
|
||
const dispose = ctx.slots.register({
|
||
name: 'conversation.view',
|
||
id: t.kind === 'builtin' ? t.id : strategyTabId(t.refId),
|
||
order: t.order ?? 0,
|
||
label: () => t.name ?? t.id,
|
||
}, render(withConnection));
|
||
tabDisposers.push(dispose);
|
||
n++;
|
||
}
|
||
ctx.logger?.info?.('[one-divine-lot] 已注册 ' + 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');
|
||
} |