feat(Tab设置+UI主题): 迭代09 Tab设置统一管理 + 迭代10 UI适配DSH主题

迭代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
This commit is contained in:
2026-09-02 14:00:56 +08:00
parent 35f2ad1a23
commit 4ac8a29805
15 changed files with 629 additions and 385 deletions
+31 -61
View File
@@ -23,21 +23,8 @@ 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() {
async function fetchTabs() {
try {
const res = await fetch('/odl/api/tabs', {
method: 'POST',
@@ -45,29 +32,21 @@ async function fetchTabConfig() {
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,
};
}
if (data && data.ok && Array.isArray(data.value)) return data.value;
} catch (e) { /* ignore */ }
return { allPositions: true, tradeRecords: true, watchlist: true };
return null;
}
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: '关注列表' })) },
];
// 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');
@@ -109,40 +88,31 @@ export function apply(ctx) {
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;
};
// R-011:统一注册 —— 读 settings.tabs 有序数组,按 order 排序、过滤 visible
// builtin 走内置 renderGENERAL_RENDER),strategy 走 StrategyTab
const registerAllTabs = async () => {
const [strategies, tabConfig] = await Promise.all([fetchStrategies(), fetchTabConfig()]);
const tabs = await fetchTabs();
if (disposed) return;
disposeAllTabs();
registerGeneralTabs(tabConfig);
const n = registerStrategyTabs(strategies);
ctx.logger?.info?.('[one-divine-lot] 已注册通用 tabs + ' + n + ' 个策略 tab');
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();