+
{spec.title}
+
{/* 顶部通栏行 */}
- {hasTop && topItems.map((it) => (
-
- ))}
+ {hasTop && topItems.map((it, i) => renderPane(it, 'top', i, g.left, topY, topH))}
{/* 主行内容窗 */}
- {mainItems.map((it) => (
-
- ))}
+ {mainItems.map((it, i) => renderPane(it, 'main', i, contentX, bodyTop, mainH))}
{/* 顶部/主行水平分隔线 */}
{hasTop && (
))}
@@ -518,7 +733,7 @@ function SplitWorkspace() {
className="dsh-wt_splitDivider"
role="separator"
title="拖动调整聊天宽度"
- style={{ position: 'fixed', left: g.right - chatW - DIVIDER / 2, top: bodyTop, width: DIVIDER, height: mainH, zIndex: 72 }}
+ style={{ position: 'fixed', left: (chatLeft ? g.left + chatW : g.right - chatW) - DIVIDER / 2, top: bodyTop, width: DIVIDER, height: mainH, zIndex: 72 }}
onPointerDown={makeDividerHandler('chat')}
/>
>
diff --git a/01_content/src/client/styles.ts b/01_content/src/client/styles.ts
index c6edcaf..38ed054 100644
--- a/01_content/src/client/styles.ts
+++ b/01_content/src/client/styles.ts
@@ -83,10 +83,16 @@ export const css = [
'.dsh-wt_splitDividerH{cursor:row-resize}',
'.dsh-wt_splitDivider:hover,.dsh-wt_splitDividerH:hover{background:var(--dsw-alias-state-accent-primary,#4f8ef7)}',
// 新建工作区(拓扑预设)
- '.dsh-wt_presets{display:flex;gap:5px;padding:2px 8px 4px}',
- '.dsh-wt_preset{flex:1;padding:5px 4px;border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4b2);font:inherit;font-size:11px;line-height:15px;cursor:pointer}',
+ '.dsh-wt_presets{display:flex;gap:6px;padding:2px 8px 6px}',
+ '.dsh-wt_preset{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:6px 2px;border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4b2);font:inherit;font-size:10px;line-height:13px;cursor:pointer}',
'.dsh-wt_preset:hover{border-color:var(--dsw-alias-border-l2,#3a4150)}',
'.dsh-wt_preset[data-on=true]{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);color:var(--dsw-alias-state-accent-primary,#4f8ef7)}',
+ '.dsh-wt_thumb{display:flex;flex-direction:column;width:54px;height:36px;border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:3px;overflow:hidden;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.03))}',
+ '.dsh-wt_thumbRow{flex:1;display:flex;min-height:0}',
+ '.dsh-wt_thumbRow:last-child .dsh-wt_thumbCell{border-bottom:none}',
+ '.dsh-wt_thumbCell{flex:1;min-width:0;border-right:1px solid var(--dsw-alias-border-l1,#262b36);border-bottom:1px solid var(--dsw-alias-border-l1,#262b36);display:flex;align-items:center;justify-content:center;font-size:8px;line-height:1}',
+ '.dsh-wt_thumbRow .dsh-wt_thumbCell:last-child{border-right:none}',
+ '.dsh-wt_thumbChat{background:var(--dsw-alias-state-accent-primary,#4f8ef7);color:#fff}',
// 布局条目卡片
'.dsh-wt_layout{display:flex;align-items:center;gap:7px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:8px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02));color:var(--dsw-alias-label-primary,#e6e8eb);font:inherit;font-size:12px;line-height:18px;cursor:pointer;text-align:left}',
'.dsh-wt_layout:hover{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}',
@@ -98,4 +104,22 @@ export const css = [
'.dsh-wt_layoutDesc{font-size:10px;line-height:14px;color:var(--dsw-alias-label-tertiary,#6e7683);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
'.dsh-wt_layoutBadge{flex:none;font-size:9px;line-height:14px;padding:0 5px;border-radius:8px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06));color:var(--dsw-alias-label-tertiary,#6e7683)}',
'.dsh-wt_layoutArrow{flex:none;color:var(--dsw-alias-label-tertiary,#6e7683);font-size:14px;line-height:20px}',
+ // 窗内容:选择器 / 自定义 / 浏览器 / 占位
+ '.dsh-wt_panePicker{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:10px;overflow:auto;align-content:start}',
+ '.dsh-wt_panePick{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:10px 4px;border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:8px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02));color:var(--dsw-alias-label-primary,#e6e8eb);font:inherit;font-size:11px;line-height:15px;cursor:pointer}',
+ '.dsh-wt_panePick:hover{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}',
+ '.dsh-wt_panePick span{font-size:15px;line-height:18px}',
+ '.dsh-wt_paneCustom{flex:1;min-height:0;display:flex;align-items:center;gap:6px;padding:10px}',
+ '.dsh-wt_paneCustom input{flex:1;min-width:0;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.03));border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:6px;color:var(--dsw-alias-label-primary,#e6e8eb);font:inherit;font-size:12px;line-height:18px;padding:5px 8px;outline:none}',
+ '.dsh-wt_paneCustom input:focus{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7)}',
+ '.dsh-wt_paneCustomGo{flex:none;padding:5px 10px;border:none;border-radius:6px;background:var(--dsw-alias-state-accent-primary,#4f8ef7);color:#fff;font:inherit;font-size:12px;cursor:pointer}',
+ '.dsh-wt_browserBar{flex:none;display:flex;align-items:center;gap:5px;padding:4px 6px;border-bottom:1px solid var(--dsw-alias-border-l1,#262b36);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02))}',
+ '.dsh-wt_browserInput{flex:1;min-width:0;background:transparent;border:none;outline:none;color:var(--dsw-alias-label-primary,#e6e8eb);font:inherit;font-size:11px;line-height:16px}',
+ '.dsh-wt_browserGo{flex:none;width:20px;height:20px;padding:0;border:none;border-radius:4px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4b2);font-size:12px;cursor:pointer}',
+ '.dsh-wt_browserGo:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}',
+ '.dsh-wt_paneWip{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--dsw-alias-label-tertiary,#6e7683)}',
+ '.dsh-wt_paneWipIcon{font-size:26px;line-height:30px}',
+ '.dsh-wt_paneWipText{font-size:11px}',
+ '.dsh-wt_splitFlip{flex:none;display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;border-radius:4px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4b2);font-size:13px;cursor:pointer}',
+ '.dsh-wt_splitFlip:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}',
].join('\n')
diff --git a/02_process/PRD.md b/02_process/PRD.md
index 3a71f44..e8138c2 100644
--- a/02_process/PRD.md
+++ b/02_process/PRD.md
@@ -359,9 +359,11 @@ type SplitPane = {
(本版布局模型 = 标题栏 + 顶部通栏行(可选) + 主行内容窗 + 右下聊天窗;聊天窗 marginLeft+marginTop
组合挤法;会话切换重锚定不关闭;chat/top/pane 三级分隔线拖拽;`dsh.worktable.split.v1` 持久化),
owner props 新增 `openSplit(spec)`;
- ✅ 「+」面板「新建工作区」(2026-08-16):拓扑预设选择(左右/三栏横排/上一下二/井字,聊天恒贴右)→
- 布局名称 + 各窗 URL → 保存并打开;布局条目进入项目区(🧱 卡片、选中态、搜索、隐藏/改名/↑↓ 排序/
- 删除、折叠图标框),存 `dsh.worktable.projects.v1.layouts`;
+ ✅ 「+」面板「新建工作区」(2026-08-16):接入指引移除,第一步为**可视化拓扑缩略图选择**
+ (左右两栏/三栏横排/上一下二/井字四栏,聊天窗蓝色标注)→ 只填布局名称 → 进入工作区;
+ 窗内容在工作区内指派:每窗 6 选 1(浏览器/资源管理器/源代码管理/任务管理/终端 + 自定义 URL,
+ 前四项为占位、浏览器可用);标题栏拖拽可换窗位(同行/跨行);工具栏 ⇄ 切换聊天窗左右(左下/右下,
+ marginLeft/marginRight 双挤法);内容与聊天位置变更实时回写 `dsh.worktable.projects.v1.layouts`;
- **互斥规则(2026-08-16 用户反馈定案)**:同一时刻仅一个分栏工作区——
① 反选:同一项目卡片再点 = 关闭;② 替换:不同项目互斥(选 B 关 A);
③ 实现:引擎内开前先关旧;对外广播 `dsh:split-claim` 共享协议并监听让位;
diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md
index 6adb753..aa6c9ab 100644
--- a/02_process/worklogs/2026-08-16_worktable-v2.md
+++ b/02_process/worklogs/2026-08-16_worktable-v2.md
@@ -281,3 +281,23 @@
- 词典新增 12 键(zh/en);样式新增预设按钮与布局卡片。
- 验证:构建 + node --check 通过;服务端实时下发(含 PRESET_DEFS/saveLayout/dsh-wt_layout);
待用户刷新实测。
+
+## 补记(+ 面板改版:可视化布局选择 + 窗内 6 选 1 + 聊天左右切换)
+
+- 用户定案(参照 better-sidebar 交互):
+ ① + 面板移除「接入新项目」说明,第一步 = 可视化布局缩略图选择(画出来的窗格示意,
+ 聊天窗蓝色 💬);② 选完只填一个布局名称即可进入,不再填内容地址;
+ ③ 进入后每个窗内 6 选 1 内容(浏览器/资源管理器/源代码管理/任务管理/终端 + 自定义;
+ better-sidebar 是 5 项,我们多一个自定义);④ 窗位可调整(标题栏拖拽换位),
+ 聊天窗可切左下/右下。
+- 实现(工作台 split.tsx 重写 + index.tsx/locales/styles):
+ ① LayoutSpec 增加 chatSide('left'|'right');聊天居左 = marginRight 挤法(新),
+ 居右 = marginLeft(原有);工具栏 ⇄ 翻转按钮;
+ ② SplitContent 三态扩展:null(未指派)/iframe/builtin(browser|explorer|scm|tasks|terminal);
+ 未指派窗渲染 6 选 1 网格;浏览器内置窗带地址栏;其余内置窗显示「开发中」占位;自定义 = URL 输入;
+ ③ swapPanes 拖拽换位(同行交换 + 跨行互换,宽度跟随);setPaneContent/setChatSide/
+ swapPanes 变更经 splitStore.onSpecMutated 回调回写 projects.v1.layouts(持久化);
+ ④ + 面板:缩略图预设(presetThumb 纯 CSS 窗格图)+ 名称输入 +「进入工作区」;
+ ⑤ 引擎 UI 文案经 setSplitT 注入 locale(zh/en 新增 15 键)。
+- 验证:构建 + node --check 通过;服务端实时下发(含 presetThumb/PanePicker/setChatSide/swapPanes);
+ 待用户刷新实测。