diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 1d6865a..9755db9 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -242,6 +242,7 @@ function WorktableSection(props: any) { const [bottomInset, setBottomInset] = useState(0) const bottomInsetRef = useRef(0) const [floatGeo, setFloatGeo] = useState<{ left: number; width: number | null } | null>(null) + const [sidebarRight, setSidebarRight] = useState(null) const [activeSplitId, setActiveSplitId] = useState(() => splitStore.active && splitStore.spec ? splitStore.spec.id : null, ) @@ -325,6 +326,7 @@ function WorktableSection(props: any) { const padRight = parseFloat(cs.paddingRight) || 0 const margin = 20 const width = rect.width - padLeft - padRight - margin * 2 + setSidebarRight(Math.round(rect.right)) if (width > 0) { setFloatGeo({ left: Math.round(rect.left + padLeft + margin), width: Math.round(width) }) } else { @@ -636,6 +638,13 @@ function WorktableSection(props: any) { const dockedStyle = !isFloat && bottomInset > 0 ? { marginBottom: bottomInset } : undefined + // + 弹窗:向右弹出、锚定 sidebar 右边缘与工作台区块顶部(视口内钳制) + const sectionTop = rootRef.current?.getBoundingClientRect().top ?? 100 + const popLeft = sidebarRight != null + ? Math.min(sidebarRight + 8, Math.max(16, window.innerWidth - 344)) + : 16 + const popTop = clamp(sectionTop, MIN_TOP, Math.max(MIN_TOP, window.innerHeight - 540)) + if (!wide) { const projectIcons = registeredIds.map((id) => metas[id]?.icon ?? '📦') const shortcutIcons = projects.shortcuts.map((s) => s.icon) @@ -727,8 +736,9 @@ function WorktableSection(props: any) { )} + {addOpen &&
setAddOpen(false)} />} {addOpen && ( -
+
{t('add.chooseLayout')}
{PRESET_DEFS.map((def) => ( diff --git a/01_content/src/client/styles.ts b/01_content/src/client/styles.ts index 38ed054..b9ecafd 100644 --- a/01_content/src/client/styles.ts +++ b/01_content/src/client/styles.ts @@ -122,4 +122,7 @@ export const css = [ '.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))}', + // + 右侧弹出面板 + '.dsh-wt_popBackdrop{position:fixed;left:0;top:0;right:0;bottom:0;z-index:79;background:transparent}', + '.dsh-wt_pop{max-height:min(540px,calc(100vh - 32px));overflow:auto;box-shadow:var(--dsw-shadow-lv2,0 8px 24px rgba(0,0,0,.4))}', ].join('\n') diff --git a/02_process/PRD.md b/02_process/PRD.md index e8138c2..bf17003 100644 --- a/02_process/PRD.md +++ b/02_process/PRD.md @@ -331,9 +331,10 @@ type SplitPane = { ### 13.5 「+」面板改版 -- 「+」展开后两个入口: - 1. **新建工作区**:拓扑预设选择(可视化缩略图)→ 各窗内容指派 → 命名保存; - 2. **接入项目 / 快捷方式**:现有接入指引 + 本地条目功能保留(§5.6); +- 「+」点击后**向右侧弹出悬浮面板**(fixed 锚定 sidebar 右边缘与工作台区块顶部,320px 宽、 + 视口内钳制;透明遮罩点击关闭),不再使用侧边栏内展开式下拉; + 两个入口:1. **新建工作区**:可视化拓扑缩略图选择 → 只填布局名称 → 进入; + 2. **快捷方式**:本地条目功能保留(§5.6,接入指引已移除); - 保存的布局以「布局条目」形式出现在项目区(布局 = 一种工作台项目),点击打开 tiling 工作区, 再次点击 / ✕ / Esc 关闭。 diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md index aa6c9ab..edcc36a 100644 --- a/02_process/worklogs/2026-08-16_worktable-v2.md +++ b/02_process/worklogs/2026-08-16_worktable-v2.md @@ -301,3 +301,12 @@ ⑤ 引擎 UI 文案经 setSplitT 注入 locale(zh/en 新增 15 键)。 - 验证:构建 + node --check 通过;服务端实时下发(含 presetThumb/PanePicker/setChatSide/swapPanes); 待用户刷新实测。 + +## 补记(+ 面板改为右侧弹出悬浮窗) + +- 用户要求:+ 点击后向右弹出窗口(非展开下拉),窗口比例适配内容;不安排自动重启(其他窗口仍在工作)。 +- 实现:addOpen 时渲染透明全屏遮罩(点击关闭)+ fixed 弹出面板:宽 320px、 + left = sidebar 右边缘 + 8(视口内钳制,sidebar 右边缘由既有 ResizeObserver 测量维护)、 + top = 工作台区块顶部(钳制 56..视口-540);内容不变(布局缩略图 → 名称 → 进入 / + 快捷方式表单);样式 .dsh-wt_pop 限高滚动 + 阴影。 +- 构建 + node --check 通过;服务端实时下发(含 popBackdrop/dsh-wt_pop),无需重启,用户刷新生效。