This commit is contained in:
2026-08-29 16:20:33 +08:00
commit cba31428dc
52 changed files with 6553 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
/**
* 神之一手(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 { SettingsSection } from './views/SettingsSection.jsx';
import { ConnectionProvider } from './views/connection.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(PlaceholderTab, { ...props, title: '交易记录' })) },
{ 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 服务不可用');
}
const withConnection = (render) => (props) =>
createElement(ConnectionProvider, { connection }, 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();
ctx.effect(() => {
return () => {
disposed = true;
disposeAllTabs();
};
}, 'one-divine-lot.client');
}