fix(ui): adjust composer status layering

This commit is contained in:
JOJO 2026-05-31 22:14:57 +08:00
parent 0815625cfd
commit 2f0e269788
2 changed files with 108 additions and 81 deletions

View File

@ -1,72 +1,6 @@
<template>
<div class="input-area compact-input-area" ref="inputAreaRoot">
<div class="stadium-input-wrapper" ref="stadiumShellOuter">
<transition name="floating-status-motion">
<div
v-if="floatingStatusVisible"
class="floating-project-status"
@click.stop
>
<span v-if="projectGitSummaryForRender" class="floating-project-status__left">
<span class="floating-project-status__menu-wrap">
<button
type="button"
class="floating-project-status__button floating-project-status__button--project"
:class="{ 'floating-project-status__button--active': projectGitMenuOpen === 'project' }"
@click.stop="toggleProjectGitMenu('project')"
>
{{ projectGitSummaryForRender.projectName }}
</button>
<div
v-if="projectGitMenuOpen === 'project'"
class="floating-project-status__submenu floating-project-status__submenu--project"
@click.stop
>
<button type="button" @click.stop="openProjectInFileManager">在文件管理器中打开</button>
<button type="button" @click.stop="copyProjectPath">复制地址</button>
</div>
</span>
<span class="floating-project-status__menu-wrap">
<button
type="button"
class="floating-project-status__button floating-project-status__button--branch"
:class="{ 'floating-project-status__button--active': projectGitMenuOpen === 'branch' }"
@click.stop="toggleProjectGitMenu('branch')"
>
{{ projectGitSummaryForRender.branch }}
</button>
<div
v-if="projectGitMenuOpen === 'branch'"
class="floating-project-status__submenu floating-project-status__submenu--branch"
@click.stop
>
<button type="button" @click.stop="copyProjectBranch">复制分支名称</button>
</div>
</span>
</span>
<span class="floating-project-status__right">
<span v-if="goalRunning" class="floating-project-status__notice">目标模式运行中</span>
<span v-else-if="goalModeArmed" class="floating-project-status__notice">目标模式就绪</span>
<button
v-if="userQuestionMinimized && pendingUserQuestionCount > 0"
type="button"
class="floating-project-status__notice floating-project-status__notice--button"
@click.stop="$emit('restore-user-question')"
>
等待回答问题
</button>
<button
v-if="projectGitSummaryForRender"
type="button"
class="floating-project-status__stats"
@click.stop="$emit('open-git-changes-panel')"
>
<span class="floating-project-status__add">+{{ projectGitSummaryForRender.additions }}</span>
<span class="floating-project-status__del">-{{ projectGitSummaryForRender.deletions }}</span>
</button>
</span>
</div>
</transition>
<div
class="runtime-queue-list"
:class="{ 'runtime-queue-list--empty': !runtimeQueuedMessagesForRender.length }"
@ -136,6 +70,72 @@
</div>
</div>
</transition>
<transition name="floating-status-motion">
<div
v-if="floatingStatusVisible"
class="floating-project-status"
@click.stop
>
<span v-if="projectGitSummaryForRender" class="floating-project-status__left">
<span class="floating-project-status__menu-wrap">
<button
type="button"
class="floating-project-status__button floating-project-status__button--project"
:class="{ 'floating-project-status__button--active': projectGitMenuOpen === 'project' }"
@click.stop="toggleProjectGitMenu('project')"
>
{{ projectGitSummaryForRender.projectName }}
</button>
<div
v-if="projectGitMenuOpen === 'project'"
class="floating-project-status__submenu floating-project-status__submenu--project"
@click.stop
>
<button type="button" @click.stop="openProjectInFileManager">在文件管理器中打开</button>
<button type="button" @click.stop="copyProjectPath">复制地址</button>
</div>
</span>
<span class="floating-project-status__menu-wrap">
<button
type="button"
class="floating-project-status__button floating-project-status__button--branch"
:class="{ 'floating-project-status__button--active': projectGitMenuOpen === 'branch' }"
@click.stop="toggleProjectGitMenu('branch')"
>
{{ projectGitSummaryForRender.branch }}
</button>
<div
v-if="projectGitMenuOpen === 'branch'"
class="floating-project-status__submenu floating-project-status__submenu--branch"
@click.stop
>
<button type="button" @click.stop="copyProjectBranch">复制分支名称</button>
</div>
</span>
</span>
<span class="floating-project-status__right">
<span v-if="goalRunning" class="floating-project-status__notice">目标模式运行中</span>
<span v-else-if="goalModeArmed" class="floating-project-status__notice">目标模式就绪</span>
<button
v-if="userQuestionMinimized && pendingUserQuestionCount > 0"
type="button"
class="floating-project-status__notice floating-project-status__notice--button"
@click.stop="$emit('restore-user-question')"
>
等待回答问题
</button>
<button
v-if="projectGitSummaryForRender"
type="button"
class="floating-project-status__stats"
@click.stop="$emit('open-git-changes-panel')"
>
<span class="floating-project-status__add">+{{ projectGitSummaryForRender.additions }}</span>
<span class="floating-project-status__del">-{{ projectGitSummaryForRender.deletions }}</span>
</button>
</span>
</div>
</transition>
<div
class="stadium-shell"
ref="compactInputShell"
@ -1459,22 +1459,30 @@ const collectComposerVisualHeight = () => {
if (!root || !shell) {
return 0;
}
const shellRect = shell.getBoundingClientRect();
let top = shellRect.top;
let bottom = shellRect.bottom;
// offset offsetTop/offsetHeight getBoundingClientRect
// / transform translateY(44px)
// 0 260ms
// offset transform
// shell .stadium-input-wrapper
// offsetParent offsetTop
let top = shell.offsetTop;
let bottom = shell.offsetTop + shell.offsetHeight;
const slashMenu = root.querySelector('.skill-slash-menu');
if (slashMenu instanceof HTMLElement) {
const rect = slashMenu.getBoundingClientRect();
return Math.max(0, Math.max(shellRect.bottom, rect.bottom) - Math.min(shellRect.top, rect.top));
const sTop = slashMenu.offsetTop;
const sBottom = slashMenu.offsetTop + slashMenu.offsetHeight;
return Math.max(0, Math.max(bottom, sBottom) - Math.min(top, sTop));
}
//
// .goal-mode-banner--collapsed
const nodes = root.querySelectorAll('.runtime-queue-list:not(.runtime-queue-list--empty), .floating-project-status');
nodes.forEach((node) => {
if (!(node instanceof HTMLElement)) return;
const rect = node.getBoundingClientRect();
top = Math.min(top, rect.top);
bottom = Math.max(bottom, rect.bottom);
// floatingStatusVisible false DOM 退
// 退
if (node.classList.contains('floating-project-status') && !floatingStatusVisible.value) return;
top = Math.min(top, node.offsetTop);
bottom = Math.max(bottom, node.offsetTop + node.offsetHeight);
});
return Math.max(0, bottom - top);
};
@ -1807,9 +1815,9 @@ onBeforeUnmount(() => {
<style scoped>
.floating-project-status {
position: absolute;
left: calc(50% - 7px);
left: calc(5% - 7px);
bottom: calc(100% + 6px);
z-index: 45;
z-index: auto;
width: 90%;
max-width: 90%;
height: 34px;
@ -1822,7 +1830,6 @@ onBeforeUnmount(() => {
border-radius: 14px;
background: var(--theme-surface-muted);
box-shadow: none;
transform: translateX(-50%);
pointer-events: auto;
}
@ -1992,18 +1999,22 @@ onBeforeUnmount(() => {
opacity 180ms ease,
transform 260ms cubic-bezier(0.25, 0.8, 0.25, 1);
transform-origin: bottom center;
will-change: transform, opacity;
/* 状态栏本体永远在最底层:输入栏 shell(z-index:2) > / 菜单(z-index:1) > 状态栏(auto)
不在这里设置 z-index避免创建低层 stacking context 后把二级菜单也压在输入栏下 */
z-index: auto;
}
.floating-status-motion-enter-from,
.floating-status-motion-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(44px);
transform: translateY(44px);
}
.floating-status-motion-enter-to,
.floating-status-motion-leave-from {
opacity: 1;
transform: translateX(-50%) translateY(0);
transform: translateY(0);
}
/* 目标模式横幅:与 + 按钮左对齐,置于输入框上方 */

View File

@ -172,8 +172,21 @@
will-change: transform, clip-path;
}
/* / 菜单始终高于状态栏本体低于输入栏 shell(z-index:2)
输入栏 > / 菜单 > 状态栏本体状态栏不设置正 z-index
只让状态栏二级菜单单独使用高 z-index */
.skill-slash-menu-motion-enter-active,
.skill-slash-menu-motion-leave-active {
z-index: 1;
}
.skill-slash-menu-motion-enter-from,
.skill-slash-menu-motion-leave-to {
/* 底边锚定在输入框上沿bottom:100%-2px配合 clip-path 从底部揭开
translateY(100%) 把元素整体下移一个自身高度后再让 clip 的底部 inset 100%0
净效果是可见底边始终钉在输入框上沿可见顶边向上生长即从输入框上沿升起
注意不能改成 translateY(0)那样 clip 会从元素静止态的顶部在输入框上方一整个菜单高度处
向下揭开看起来像菜单从高空往下展开正是要避免的在状态栏上方出现 */
transform: translateX(-50%) translateY(100%);
clip-path: inset(0 0 100% 0 round 12px 12px 0 0);
}
@ -913,7 +926,10 @@ body[data-theme='dark'] {
}
.composer-container.blank-hero-mode {
transform: translateY(-38vh);
// 新对话居中态当输入栏上方出现 git 状态栏或输入变多行时整体可视高度会向上增长
// 顶到欢迎文字用已计算好的 --composer-growth-height 把输入栏整体下移同样的距离
// 让栈顶回到裸单行输入时的位置从而保证不遮挡欢迎文字
transform: translateY(calc(-38vh + var(--composer-growth-height, 0px)));
}
@media (max-width: 768px) {
@ -925,7 +941,7 @@ body[data-theme='dark'] {
height: 44px;
}
.composer-container.blank-hero-mode {
transform: translateY(-32vh);
transform: translateY(calc(-32vh + var(--composer-growth-height, 0px)));
}
.blank-hero-overlay {
padding-bottom: 120px;