agent-Specialization/static/src/components/chat/quickdock/quickdock.css
JOJO a1e05032a2 fix(quickdock): 进页与切换对话时快捷窗口静态呈现,修复跨工作区输入栏跳动
- 设置与内容状态改为 localStorage 同步缓存(主题缓存同模式):
  自动展开设置、全局内容状态、按对话 ID 内容状态(LRU 上限 50)
- 三态乐观掩码:assumedActive 生效时 hasContent 完全由缓存假定值
  决定(含假定无内容),dock 与对话区同刻切换收起/展开
- 无过渡窗口重写为可重入机制:初始加载与切换对话(flush:sync)
  两个时机开启,按 filesSyncSeq/todoSyncSeq 序号推进精确关窗
- 跨工作区点选对话时保持当前对话视图冻结,不再经过空对话
  中间态,消除输入栏先上后下
2026-07-28 23:24:19 +08:00

1005 lines
20 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
快捷窗口Quick Dock共享样式
配色全部走三主题语义 token动画参数迁移自 demo/todo_float_window
============================================================ */
.quick-dock,
.qd-detail,
.qd-preview,
.qd-menu {
--qd-row-h: 36px;
--qd-width: 270px;
--qd-ease-out: cubic-bezier(0.22, 0.9, 0.28, 1);
--qd-ease-strike: cubic-bezier(0.55, 0.06, 0.35, 1);
}
/* ---------------- 占位列容器 ---------------- */
.quick-dock {
flex: none;
width: 294px; /* 270 + 左右 12 padding */
/* top 44px避开右上角展开/收起悬浮按钮top 8 + 高 28 + 8 间距) */
padding: 44px 12px 20px 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
transition:
width 0.3s ease,
padding 0.3s ease,
opacity 0.25s ease,
transform 0.3s var(--qd-ease-out);
}
/* 四个窗口都无内容时收起到 0 宽,不挤压对话区。
上下 padding 保持与展开态一致44/20若收起态 padding 归 0
展开过渡中 padding-top 0→44px 会让内容垂直下移 44px叠加 translateX
形成「从右上角斜向出现」的观感;保留后动画只剩纯水平分量(从右到左) */
.quick-dock--empty {
width: 0;
padding: 44px 0 20px;
opacity: 0;
transform: translateX(24px);
}
/* 初始加载窗口内禁用容器过渡:进入页面时设置与首批内容异步到达,
首次状态校正必须瞬间完成,否则会看到「先空再展开」/「先展开再收起」的闪烁;
窗口关闭(类移除)后恢复正常交互动画。规则位于 .quick-dock 之后,同优先级覆盖 */
.quick-dock--no-anim {
transition: none;
}
.quick-dock__scroll {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12px;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
/* padding + 负 margin扩大滚动盒以容纳窗口阴影避免被 overflow 裁剪 */
padding: 24px;
margin: -24px;
}
.quick-dock__scroll::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
/* ---------------- 窗口通用 ---------------- */
.qd-window {
flex: none; /* 禁止被列 flex 压缩变矮 */
width: var(--qd-width);
background: var(--surface-raised);
border: 1px solid var(--border-default);
border-radius: 12px;
box-shadow: var(--shadow-card);
overflow: hidden;
}
.qd-window.qd-window-enter {
animation: qd-window-in 0.3s ease-out;
}
@keyframes qd-window-in {
from {
opacity: 0;
transform: translateY(-6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.qd-window__header {
height: 38px;
display: flex;
align-items: center;
gap: 7px;
padding: 0 12px;
border-bottom: 1px solid var(--border-default);
}
.qd-window__icon {
width: 15px;
height: 15px;
color: var(--accent);
flex: none;
}
.qd-window__title {
font-size: 12.5px;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.02em;
}
.qd-window__counter {
margin-left: auto;
font-size: 11.5px;
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
}
/* 列表:固定 5 行,无上下 padding首行紧贴标题分隔线滚动条隐藏 */
.qd-list {
list-style: none;
margin: 0;
padding: 0;
height: calc(var(--qd-row-h) * 5);
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.qd-list::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
/* 交互区域禁止文字选择:点击条目展开详情/预览时,防止 mousedown 起点落在文字上,
DOM 动画期间选择范围异常扩展,把条目与详情文字整片选中。
详情输出与预览代码内容保持可选,不影响复制。 */
.qd-window__header,
.qd-run-item,
.qd-todo-item,
.qd-file-item,
.qd-detail__header,
.qd-preview__header,
.qd-menu {
user-select: none;
}
/* ---------------- 行进入 / 移出动画(待办 / 子智能体 / 后台指令 / 文件共用) ---------------- */
.qd-row-enter {
opacity: 0;
animation: qd-row-in 0.34s var(--qd-ease-out) forwards;
}
@keyframes qd-row-in {
from {
opacity: 0;
transform: translateX(-14px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.qd-row-leave {
animation: qd-row-out 0.26s ease-in forwards;
}
@keyframes qd-row-out {
0% {
opacity: 1;
transform: translateX(0);
height: var(--qd-row-h);
}
55% {
opacity: 0;
transform: translateX(-14px);
height: var(--qd-row-h);
}
100% {
opacity: 0;
transform: translateX(-14px);
height: 0;
}
}
/* ---------------- 待办行 ---------------- */
.qd-todo-item {
height: var(--qd-row-h);
display: flex;
align-items: center;
gap: 9px;
padding: 0 12px;
overflow: hidden; /* 配合移出动画的高度收拢 */
}
.qd-todo-dot {
flex: none;
width: 6px;
height: 6px;
border-radius: 50%;
border: 1.5px solid var(--text-tertiary);
background: transparent;
transition:
background 0.2s ease,
border-color 0.2s ease;
}
.qd-todo-item.is-done .qd-todo-dot {
background: var(--accent);
border-color: var(--accent);
}
.qd-todo-text {
position: relative;
flex: 1;
min-width: 0;
font-size: 13px;
line-height: 1;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.22s ease 0.12s; /* 变灰略滞后于划线 */
}
.qd-todo-text::after {
content: '';
position: absolute;
left: -1px;
right: -1px;
top: 50%;
height: 1.5px;
margin-top: -0.75px;
background: var(--text-tertiary);
border-radius: 1px;
transform: scaleX(0);
transform-origin: left center;
pointer-events: none;
}
/* 完成静态态(整表渲染时已完成行直接呈现,不播划线) */
.qd-todo-item.is-done .qd-todo-text {
color: var(--text-tertiary);
}
.qd-todo-item.is-done .qd-todo-text::after {
transform: scaleX(1);
}
/* 完成动作瞬间:横线从左往右划过 + 状态点弹跳 */
.qd-todo-item.just-done .qd-todo-text::after {
animation: qd-strike-in 0.28s var(--qd-ease-strike) forwards;
}
@keyframes qd-strike-in {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.qd-todo-item.just-done .qd-todo-dot {
animation: qd-dot-pop 0.32s var(--qd-ease-out);
}
@keyframes qd-dot-pop {
0% {
transform: scale(1);
}
45% {
transform: scale(1.6);
}
100% {
transform: scale(1);
}
}
/* ---------------- 子智能体 / 后台指令行 ---------------- */
.qd-run-item {
height: var(--qd-row-h);
display: flex;
align-items: center;
gap: 7px;
padding: 0 8px 0 12px;
overflow: hidden;
cursor: pointer;
transition: background 0.12s ease;
}
.qd-run-item:hover {
background: var(--hover-bg);
}
.qd-run-item.is-active {
background: var(--surface-soft);
}
.qd-run-status {
flex: none;
width: 6px;
height: 6px;
border-radius: 50%;
background: transparent;
}
/* 状态配色对齐旧工作区面板 .sub-agent-status
运行中=info 蓝 / 空闲=次要文字白 / 完成=success 绿 / 失败·超时·终止=danger 红 */
.qd-run-item.is-running .qd-run-status {
background: var(--state-info);
animation: qd-status-pulse 1.6s ease-in-out infinite;
}
.qd-run-item.is-idle .qd-run-status {
background: var(--text-secondary);
}
.qd-run-item.is-done .qd-run-status {
background: var(--state-success);
}
.qd-run-item.is-ended .qd-run-status {
background: var(--state-danger);
}
@keyframes qd-status-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
.qd-run-name {
flex: 1;
min-width: 0;
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.qd-run-item[data-kind='cmd'] .qd-run-name {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;
}
.qd-run-item.is-done .qd-run-name,
.qd-run-item.is-ended .qd-run-name {
color: var(--text-tertiary);
}
/* ⋯ 按钮(行内) */
.qd-row-menu-btn {
flex: none;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
background: none;
border: none;
border-radius: 5px;
color: var(--text-tertiary);
cursor: pointer;
transition:
background 0.12s ease,
color 0.12s ease;
}
.qd-row-menu-btn svg {
width: 14px;
height: 14px;
}
.qd-row-menu-btn:hover {
background: var(--tab-active);
color: var(--text-secondary);
}
/* ---------------- 文件行 ---------------- */
.qd-file-item {
height: var(--qd-row-h);
display: flex;
align-items: center;
gap: 7px;
padding: 0 10px 0 6px;
overflow: hidden;
cursor: pointer;
transition: background 0.12s ease;
}
.qd-file-item:hover {
background: var(--hover-bg);
}
.qd-file-item.is-active {
background: var(--surface-soft);
}
.qd-file-name {
flex: 1;
min-width: 0;
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ---------------- 详情面板fixed 浮在列左侧) ---------------- */
.qd-detail {
position: fixed;
top: 20px;
right: calc(294px + 12px); /* 快捷窗口列宽 + gap */
width: 480px;
max-width: calc(100vw - 360px);
height: 460px;
max-height: calc(100vh - 40px);
background: var(--surface-raised);
border: 1px solid var(--border-default);
border-radius: 12px;
box-shadow: var(--shadow-card);
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 90;
}
.qd-detail.panel-enter {
animation: qd-panel-in 0.24s var(--qd-ease-out);
}
@keyframes qd-panel-in {
from {
opacity: 0;
transform: translateX(12px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.qd-detail.panel-leave {
animation: qd-panel-out 0.18s ease-in forwards;
}
@keyframes qd-panel-out {
to {
opacity: 0;
transform: translateX(8px);
}
}
.qd-detail__header {
flex: none;
height: 42px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
border-bottom: 1px solid var(--border-default);
}
.qd-detail__dot {
flex: none;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--text-tertiary);
}
.qd-detail__dot.is-running {
background: var(--state-info);
animation: qd-status-pulse 1.6s ease-in-out infinite;
}
.qd-detail__dot.is-idle {
background: var(--text-secondary);
}
.qd-detail__dot.is-done {
background: var(--state-success);
}
.qd-detail__dot.is-ended {
background: var(--state-danger);
}
.qd-detail__title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.qd-detail__badge {
flex: none;
font-size: 11px;
line-height: 1;
padding: 4px 8px;
border-radius: 99px;
background: var(--surface-soft);
color: var(--text-tertiary);
}
.qd-detail__badge.is-running {
background: color-mix(in srgb, var(--state-info) 14%, transparent);
color: var(--state-info);
}
.qd-detail__badge.is-idle {
background: var(--surface-soft);
color: var(--text-secondary);
}
.qd-detail__badge.is-done {
background: color-mix(in srgb, var(--state-success) 14%, transparent);
color: var(--state-success);
}
.qd-detail__badge.is-ended {
background: color-mix(in srgb, var(--state-danger) 14%, transparent);
color: var(--state-danger);
}
.qd-detail__close {
flex: none;
margin-left: auto;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
background: none;
border: none;
border-radius: 6px;
color: var(--text-tertiary);
cursor: pointer;
transition:
background 0.12s ease,
color 0.12s ease;
}
.qd-detail__close svg {
width: 13px;
height: 13px;
}
.qd-detail__close:hover {
background: var(--tab-active);
color: var(--text-secondary);
}
.qd-detail__body {
flex: 1;
overflow-y: auto;
padding: 6px 0 10px;
scrollbar-width: none;
-ms-overflow-style: none;
}
.qd-detail__body::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.qd-detail__body.body-fade {
animation: qd-body-fade-in 0.18s ease-out;
}
@keyframes qd-body-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.qd-detail__empty {
padding: 18px 14px;
font-size: 12.5px;
color: var(--text-tertiary);
}
/* ---------------- feed 行(扁平行式,无嵌套卡片) ---------------- */
.qd-feed-row {
display: flex;
align-items: center;
gap: 8px;
padding: 0 14px;
min-height: 28px;
font-size: 12.5px;
}
/* 仅「面板打开期间新出现」的行播进入动画;打开时已有的行由组件静态渲染,
避免每次打开详情全部行重播一遍出现动画 */
.qd-feed-row.is-new {
animation: qd-feed-in 0.25s var(--qd-ease-out);
}
@keyframes qd-feed-in {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 子智能体文本输出:浅底色圆角块,与工具行/终端行的扁平行式区分 */
.qd-feed-row.feed-text {
align-items: flex-start;
margin: 3px 10px;
padding: 6px 8px;
background: var(--surface-soft);
border-radius: 8px;
color: var(--text-secondary);
line-height: 1.55;
white-space: pre-wrap;
word-break: break-word;
}
.qd-feed-row.feed-tool .tool-name {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;
color: var(--text-primary);
flex: none;
}
.qd-feed-row.feed-tool .tool-param {
color: var(--text-tertiary);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.qd-feed-row.feed-tool .tool-result {
flex: none;
margin-left: auto;
font-size: 11.5px;
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
}
.qd-feed-row.feed-tool .tool-result.is-error {
color: var(--state-danger);
}
.qd-tool-spinner {
flex: none;
width: 11px;
height: 11px;
border-radius: 50%;
border: 1.5px solid var(--border-strong);
border-top-color: var(--accent);
animation: qd-tool-spin 0.7s linear infinite;
}
@keyframes qd-tool-spin {
to {
transform: rotate(360deg);
}
}
.qd-tool-done {
flex: none;
width: 11px;
text-align: center;
font-size: 11px;
color: var(--accent);
}
.qd-feed-row.feed-term {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-all;
min-height: 24px;
}
.qd-feed-row.feed-killed {
color: var(--state-danger);
}
/* ---------------- 文件预览侧边栏(占位列) ---------------- */
.qd-preview {
flex: none;
width: 452px; /* 440 + 右 12 padding */
max-width: calc(100vw - 200px);
padding: 20px 12px 20px 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
min-height: 0;
}
.qd-preview__panel {
flex: 1;
min-height: 0;
background: var(--surface-raised);
border: 1px solid var(--border-default);
border-radius: 12px;
box-shadow: var(--shadow-card);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 预览侧边栏滑入滑出(对齐 git/终端面板 right-panels-slide 的参数) */
.qd-preview-slide-enter-active,
.qd-preview-slide-leave-active {
transition:
width 0.24s ease,
padding 0.24s ease,
opacity 0.2s ease,
transform 0.24s var(--qd-ease-out);
overflow: hidden;
}
.qd-preview-slide-enter-from,
.qd-preview-slide-leave-to {
width: 0 !important;
padding-right: 0 !important;
opacity: 0;
transform: translateX(24px);
}
.qd-preview__header {
flex: none;
height: 44px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 14px;
border-bottom: 1px solid var(--border-default);
}
.qd-preview__header > svg {
flex: none;
width: 15px;
height: 15px;
color: var(--accent);
}
.qd-preview__name {
flex: none;
max-width: 45%;
font-size: 13px;
font-weight: 600;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.qd-preview__path {
flex: 1;
min-width: 0;
font-size: 11.5px;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
color: var(--text-tertiary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.qd-preview__close {
flex: none;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
background: none;
border: none;
border-radius: 6px;
color: var(--text-tertiary);
cursor: pointer;
transition:
background 0.12s ease,
color 0.12s ease;
}
.qd-preview__close svg {
width: 13px;
height: 13px;
}
.qd-preview__close:hover {
background: var(--tab-active);
color: var(--text-secondary);
}
.qd-preview__body {
flex: 1;
overflow: auto;
padding: 8px 0 12px;
scrollbar-width: none;
-ms-overflow-style: none;
}
.qd-preview__body::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.qd-preview__loading,
.qd-preview__error {
padding: 18px 14px;
font-size: 12.5px;
color: var(--text-tertiary);
}
.qd-preview__error {
color: var(--state-danger);
}
/* 行高 = 12px × 1.75 = 21px与 FilePreviewPanel.vue 中 LINE_HEIGHT 保持同步 */
.qd-code {
position: relative;
display: flex;
align-items: flex-start;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;
line-height: 1.75;
}
.qd-code-hoverline {
position: absolute;
left: 0;
right: 0;
height: 21px;
background: var(--hover-bg);
pointer-events: none;
}
.qd-code-gutter {
position: relative;
z-index: 1;
flex: none;
width: 44px;
padding-right: 12px;
box-sizing: border-box;
text-align: right;
color: var(--text-tertiary);
user-select: none;
}
.qd-code-gutter span {
display: block;
}
.qd-code-pre {
position: relative;
z-index: 1;
flex: 1;
min-width: 0;
margin: 0;
padding-right: 14px;
font: inherit;
white-space: pre;
color: var(--text-primary);
}
/* ---------------- 全局 ⋯ 菜单fixed 单例) ---------------- */
.qd-menu {
position: fixed;
z-index: 300;
min-width: 116px;
padding: 4px;
background: var(--surface-raised);
border: 1px solid var(--border-default);
border-radius: 8px;
box-shadow: var(--shadow-card);
}
.qd-menu.menu-enter {
animation: qd-menu-in 0.16s ease-out;
}
@keyframes qd-menu-in {
from {
opacity: 0;
transform: translateY(-3px) scale(0.97);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.qd-menu__item {
display: flex;
align-items: center;
width: 100%;
height: 30px;
padding: 0 10px;
background: none;
border: none;
border-radius: 5px;
font-size: 12.5px;
text-align: left;
color: var(--text-primary);
cursor: pointer;
transition: background 0.12s ease;
}
.qd-menu__item:hover:not(:disabled) {
background: var(--surface-soft);
}
.qd-menu__item--danger {
color: var(--state-danger);
}
.qd-menu__item--danger:hover:not(:disabled) {
background: color-mix(in srgb, var(--state-danger) 8%, transparent);
}
.qd-menu__item:disabled {
color: var(--text-tertiary);
cursor: default;
}
/* ---------------- 展开/收起悬浮按钮App.vue 零宽锚点) ---------------- */
/* 零宽锚点flex 兄弟节点,始终贴在 QuickDock 右缘;
dock 收起时 = main 右缘(落在对话标题栏右端),展开时 = dock 右缘(悬浮面板上方) */
.qd-toggle-anchor {
flex: none;
width: 0;
position: relative;
overflow: visible;
z-index: 70;
}
.qd-toggle {
position: absolute;
top: 8px;
right: 16px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
/* 线条色与左侧对话记录侧边栏导航图标一致(--text-primary恒定不变
无 hover 变色展开is-active状态也不变色 */
color: var(--text-primary);
/* 底色与背后面板一致(收起=对话标题栏 / 展开=应用背景,两者同为 --surface-panel */
background: var(--surface-panel);
border: none;
border-radius: 8px;
cursor: pointer;
}
.qd-toggle svg line {
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
}