feat(quickdock): 新增快捷窗口自动展开设置,修复展开按钮与动画
- 个人空间·外观与显示新增「快捷窗口自动展开」开关(默认开); 关闭后新内容到达不再自动展开,只能通过右侧悬浮按钮手动展开; 设置切换立即生效 - 展开/收起悬浮按钮无内容时也常显;线条色对齐左侧对话记录侧边栏 (--claude-text),移除 hover 与展开态变色 - 修复展开动画方向:empty 态保留上下 padding,消除 padding-top 过渡带来的垂直分量,展开恢复为纯横向从右到左
This commit is contained in:
parent
ce3979d9c7
commit
0eaa8ba450
@ -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 已全部完成)
|
||||
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 提升到 store:App.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']);
|
||||
|
||||
@ -32,10 +32,13 @@
|
||||
transform 0.3s var(--qd-ease-out);
|
||||
}
|
||||
|
||||
/* 四个窗口都无内容时收起到 0 宽,不挤压对话区 */
|
||||
/* 四个窗口都无内容时收起到 0 宽,不挤压对话区。
|
||||
上下 padding 保持与展开态一致(44/20):若收起态 padding 归 0,
|
||||
展开过渡中 padding-top 0→44px 会让内容垂直下移 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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user