feat(quickdock): 新增快捷窗口自动展开设置,修复展开按钮与动画

- 个人空间·外观与显示新增「快捷窗口自动展开」开关(默认开);
  关闭后新内容到达不再自动展开,只能通过右侧悬浮按钮手动展开;
  设置切换立即生效
- 展开/收起悬浮按钮无内容时也常显;线条色对齐左侧对话记录侧边栏
  (--claude-text),移除 hover 与展开态变色
- 修复展开动画方向:empty 态保留上下 padding,消除 padding-top
  过渡带来的垂直分量,展开恢复为纯横向从右到左
This commit is contained in:
JOJO 2026-07-28 12:49:24 +08:00
parent ce3979d9c7
commit 0eaa8ba450
7 changed files with 69 additions and 22 deletions

View File

@ -8,7 +8,8 @@
1. **命名**:该功能正式名称为「**快捷窗口**」quick dock / quick window
2. **布局:占位而非悬浮**:快捷窗口**不是** `position: fixed` 悬浮,而是在**对话区域右侧空白区域占位显示**的一列,常驻时会**把对话区域向左挤压**(与 git/终端 right-panels 同为 `chat-container` 的 flex 兄弟节点,位于 `</main>` 之后、right-panels 之前)。有内容时才显示该列。
3. **设置项**个人空间PersonalizationDrawer新增「**隐藏快捷窗口**」开关,**默认关闭**(即默认显示)。字段 `hide_quick_dock: boolean` 存 personalization form。
3. **设置项**个人空间PersonalizationDrawer新增「**隐藏快捷窗口**」开关,**默认关闭**(即默认显示)。字段 `hide_quick_dock: boolean` 存 personalization form。2026-07-28 核查该字段前端未消费,仅后端存在。)
3.1 **设置项**2026-07-28「外观与显示」新增「**快捷窗口自动展开**」开关,**默认是**(有内容时自动展开);切换为否后只能通过右侧悬浮按钮手动展开。字段 `quick_dock_auto_expand: boolean`(默认 true存 personalization form。行为语义QuickDock.vue `watch(hasContent, ..., {immediate:true})` 按模式校正 `userCollapsed = !autoExpand`——手动模式下内容集合变化(出现/清空)后保持收起等手动点击,用户手动展开后只要内容不完全清空就保持展开;`watch(autoExpand)` 让设置切换立即生效(切否收起 / 切是有内容即展开)。
4. 详情面板(子智能体/后台指令)保持 fixed 悬浮从快捷窗口列左侧滑出文件预览侧边栏在快捷窗口列右侧git/终端面板左侧)占位/滑出。
5. 移动端isMobileViewport第一版先隐藏。
@ -103,6 +104,8 @@
4. `overflow` 裁剪窗口阴影 → 滚动容器 `padding + 负 margin` 扩大滚动盒。
5. 菜单 fixed 定位不随栈滚动 → 栈滚动时主动关闭菜单。
6. 两个 ⋯ 菜单 + Esc 多层浮层菜单互斥Esc 按 菜单 → 详情面板 → 预览栏 顺序逐个关(用 `e.preventDefault()` + `e.defaultPrevented` 串联)。
7. **占位列展开/收起过渡中 `padding` 必须保持上下值一致**2026-07-28 踩坑):`.quick-dock--empty` 若 `padding: 0`,展开时 `padding-top 0→44px` 参与过渡会让内容垂直下移 44px叠加 `translateX(24px)→0` 形成「从右上角斜向出现」观感;收起态写 `padding: 44px 0 20px`(仅水平 padding 归零)后动画才是纯横向从右到左。
8. 展开/收起悬浮按钮(`.qd-toggle`App.vue 零宽锚点)**无内容时也常显**2026-07-28 用户要求):`v-if="!isMobileViewport"` 不含 `qdHasContent`;配套 `watch(hasContent)` 任一方向变化都重置 `userCollapsed`(否则「无内容点收起 → 新内容到达」路径 dock 不自动展开)。按钮线条色与左侧对话记录侧边栏导航图标一致用 `var(--claude-text)`**恒定不变**——无 hover 变色展开态也不变色is-active 样式已移除)。
## 6. 正式接入任务清单2026-07-21 已全部完成)

View File

@ -106,6 +106,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"new_chat_button_behavior": "route", # 新建对话按钮行为route-跳转空白新对话页 / blank-立即创建空对话
"default_hide_workspace": False, # 默认隐藏工作区
"hide_quick_dock": False, # 隐藏快捷窗口(对话区右侧的待办/子智能体/后台指令/文件窗口列)
"quick_dock_auto_expand": True, # 快捷窗口自动展开True-有内容时自动展开 / False-只能手动点击按钮展开
"group_sidebar_by_workspace": False, # 侧边栏按工作区/项目分组显示对话
"sidebar_pinned_workspaces": [], # 分组侧边栏中永久置顶的工作区ID列表
"sidebar_workspace_order": [], # 分组侧边栏中非置顶工作区的显示顺序
@ -536,6 +537,12 @@ def sanitize_personalization_payload(
else:
base["hide_quick_dock"] = bool(base.get("hide_quick_dock", False))
# 快捷窗口自动展开
if "quick_dock_auto_expand" in data:
base["quick_dock_auto_expand"] = bool(data.get("quick_dock_auto_expand"))
else:
base["quick_dock_auto_expand"] = bool(base.get("quick_dock_auto_expand", True))
# 侧边栏按工作区/项目分组显示对话
if "group_sidebar_by_workspace" in data:
base["group_sidebar_by_workspace"] = bool(data.get("group_sidebar_by_workspace"))

View File

@ -357,15 +357,15 @@
:host-mode="versioningHostMode"
/>
<!-- 快捷窗口展开/收起按钮零宽锚点始终贴在 QuickDock 右缘
按钮绝对定位悬浮在原处不随面板挤压移动 -->
按钮绝对定位悬浮在原处不随面板挤压移动
无内容时也常显不随 qdHasContent 隐藏避免按钮时隐时现 -->
<div
v-if="!isMobileViewport && qdHasContent"
v-if="!isMobileViewport"
class="qd-toggle-anchor"
>
<button
type="button"
class="qd-toggle"
:class="{ 'is-active': qdExpanded }"
:aria-label="qdExpanded ? '收起快捷窗口' : '展开快捷窗口'"
:aria-pressed="qdExpanded"
@click="qdUserCollapsed = !qdUserCollapsed"

View File

@ -43,6 +43,7 @@ import { useSubAgentStore } from '@/stores/subAgent';
import { useBackgroundCommandStore } from '@/stores/backgroundCommand';
import { useConversationStore } from '@/stores/conversation';
import { useUiStore } from '@/stores/ui';
import { usePersonalizationStore } from '@/stores/personalization';
import TodoWindow from './TodoWindow.vue';
import RunnerWindow from './RunnerWindow.vue';
import RunnerDetailPanel from './RunnerDetailPanel.vue';
@ -61,6 +62,7 @@ const subAgentStore = useSubAgentStore();
const bgStore = useBackgroundCommandStore();
const conversationStore = useConversationStore();
const uiStore = useUiStore();
const personalizationStore = usePersonalizationStore();
// hasContent / userCollapsed storeApp.vue
const { menu, hasContent, userCollapsed } = storeToRefs(quickDock);
@ -73,11 +75,26 @@ watch(userCollapsed, (collapsed) => {
}
});
//
watch(hasContent, (has) => {
if (!has) {
quickDock.userCollapsed = false;
}
/** 快捷窗口自动展开设置(个人空间·外观与显示,默认是;为否则只能手动点击按钮展开) */
const autoExpand = computed(() => personalizationStore.form.quick_dock_auto_expand !== false);
// /
// -
//
// - /
// hasContent dock
watch(
hasContent,
() => {
quickDock.userCollapsed = !autoExpand.value;
},
// immediate
{ immediate: true }
);
//
watch(autoExpand, (enabled) => {
quickDock.userCollapsed = !enabled;
});
const AGENT_TERMINAL = new Set(['completed', 'failed', 'timeout', 'terminated']);

View File

@ -32,10 +32,13 @@
transform 0.3s var(--qd-ease-out);
}
/* 四个窗口都无内容时收起到 0 宽,不挤压对话区 */
/* 四个窗口都无内容时收起到 0 不挤压对话区
上下 padding 保持与展开态一致44/20若收起态 padding 0
展开过渡中 padding-top 044px 会让内容垂直下移 44px叠加 translateX
形成从右上角斜向出现的观感保留后动画只剩纯水平分量从右到左 */
.quick-dock--empty {
width: 0;
padding: 0;
padding: 44px 0 20px;
opacity: 0;
transform: translateX(24px);
}
@ -977,13 +980,14 @@
align-items: center;
justify-content: center;
padding: 0;
color: var(--text-secondary);
/* 线条色与左侧对话记录侧边栏导航图标一致--claude-text恒定不变
hover 变色展开is-active状态也不变色 */
color: var(--claude-text);
/* 底色与背后面板一致(收起=对话标题栏 / 展开=应用背景,两者同为 --surface-panel */
background: var(--surface-panel);
border: none;
border-radius: 8px;
cursor: pointer;
transition: color 140ms ease;
}
.qd-toggle svg line {
@ -991,12 +995,3 @@
stroke-width: 1.8;
stroke-linecap: round;
}
/* hover 只变图标颜色,不变底色 */
.qd-toggle:hover {
color: var(--text-primary);
}
.qd-toggle.is-active {
color: var(--accent);
}

View File

@ -757,6 +757,28 @@
class="fancy-path"
></path></svg></span
></label>
<label class="settings-toggle-row"
><span class="settings-row-copy"
><span class="settings-row-title">快捷窗口自动展开</span
><span class="settings-row-desc"
>有内容时自动展开快捷窗口关闭后只能通过右侧按钮手动展开</span
></span
><input
type="checkbox"
:checked="form.quick_dock_auto_expand"
@change="
personalization.updateField({
key: 'quick_dock_auto_expand',
value: $event.target.checked
})
" /><span class="fancy-check" aria-hidden="true"
><svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path></svg></span
></label>
<div class="settings-select-row">
<span class="settings-row-copy"
><span class="settings-row-title">堆叠块显示模式</span

View File

@ -31,6 +31,7 @@ interface PersonalForm {
show_status_avatar: boolean;
show_git_status_bar: boolean;
auto_open_terminal_panel: boolean;
quick_dock_auto_expand: boolean;
stacked_hide_borders: boolean;
minimal_expand_height_limited: boolean;
enhanced_tool_display_categories: string[];
@ -192,6 +193,7 @@ const defaultForm = (): PersonalForm => ({
show_status_avatar: true,
show_git_status_bar: true,
auto_open_terminal_panel: true,
quick_dock_auto_expand: true,
stacked_hide_borders: loadCachedStackedHideBorders(),
minimal_expand_height_limited: loadCachedMinimalExpandHeightLimited(),
enhanced_tool_display_categories: [],
@ -389,6 +391,7 @@ export const usePersonalizationStore = defineStore('personalization', {
show_status_avatar: data.show_status_avatar !== false,
show_git_status_bar: data.show_git_status_bar !== false,
auto_open_terminal_panel: data.auto_open_terminal_panel !== false,
quick_dock_auto_expand: data.quick_dock_auto_expand !== false,
stacked_hide_borders: !!data.stacked_hide_borders,
minimal_expand_height_limited: data.minimal_expand_height_limited !== false,
enhanced_tool_display_categories: Array.isArray(data.enhanced_tool_display_categories)