修抽屉两处真故障:详情页里被压成 129px、portal 后令牌全失效变透明

用户报「点开连输入框都看不见」,我前两轮量的是货架那条路 —— 那条本来就是好的。
**走详情页进去才踩得到**,两处叠在一起:

① **抽屉被钉进详情卡里**。`.g-drawer` 是 `position: fixed`,而详情页容器 `.g-detail`
   带着入场动画的 `transform` —— **带 transform 的祖先会成为 fixed 的包含块**,
   于是 `inset: 0` 解析成那张卡的盒子:实测面板矩形 520×**129**,头尾一挤,
   中间的输入框整片没了。改走 `createPortal` 挂到 body,与 DOM 位置解绑。

② **portal 之后面板变透明**。整套令牌(--card / --cline / --ink …)定义在 `.gov-root` 上,
   挂到 body 就出了那个作用域:实测 `background-color: rgba(0, 0, 0, 0)` ——
   顶栏与页面内容从抽屉里透出来,字叠字。修法是把 `gov-root` 类跟着portal 一起挂,
   并给面板背景与边框留了字面兜底值。

顺带:z-index 60 → 120(压过顶栏,先前顶栏那一条压在抽屉标题上)。

实测两条路都验:详情页与货架各开一次,面板均为 (760, 0, 520, 577)、背景 rgb(255,255,255)、
四个输入框全在屏内;详情页测试连接照旧只测不存(判词是厂商原话),Esc 与遮罩关得掉。
五闸绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-30 01:36:44 +08:00
co-authored by Claude Opus 5
parent 51567d3b3b
commit fffb46eae7
2 changed files with 20 additions and 5 deletions
+3 -2
View File
@@ -317,9 +317,10 @@
/* ── 抽屉:一次性的配置岔路(接入模型 / 改绑定)—————————————————— /* ── 抽屉:一次性的配置岔路(接入模型 / 改绑定)——————————————————
右侧 520px 定宽面板 + 遮罩;头固定、身滚动、底动作栏常驻。 右侧 520px 定宽面板 + 遮罩;头固定、身滚动、底动作栏常驻。
配置不该把货架顶下去:做完就该回到原地,所以是抽屉不是页内表单。 */ 配置不该把货架顶下去:做完就该回到原地,所以是抽屉不是页内表单。 */
.g-drawer { position: fixed; inset: 0; z-index: 60; display: flex; justify-content: flex-end; } /* 层级压过顶栏(顶栏 z=40 一档);背景显式给白 —— 不吃 .gov-root 之外的令牌缺失 */
.g-drawer { position: fixed; inset: 0; z-index: 120; display: flex; justify-content: flex-end; }
.g-drawer-veil { flex: 1; border: 0; padding: 0; background: rgba(19, 28, 38, .38); backdrop-filter: blur(1.5px); cursor: default; animation: g-fade .16s ease-out; } .g-drawer-veil { flex: 1; border: 0; padding: 0; background: rgba(19, 28, 38, .38); backdrop-filter: blur(1.5px); cursor: default; animation: g-fade .16s ease-out; }
.g-drawer-p { position: relative; display: flex; flex-direction: column; width: min(520px, 100vw); background: var(--card); border-left: 1px solid var(--cline); box-shadow: -18px 0 44px -24px rgba(19, 28, 38, .5); animation: g-slidein .2s cubic-bezier(.2, .8, .2, 1); } .g-drawer-p { position: relative; display: flex; flex-direction: column; width: min(520px, 100vw); background: var(--card, #fff); border-left: 1px solid var(--cline, #e2e7ee); box-shadow: -18px 0 44px -24px rgba(19, 28, 38, .5); animation: g-slidein .2s cubic-bezier(.2, .8, .2, 1); }
@keyframes g-fade { from { opacity: 0; } } @keyframes g-fade { from { opacity: 0; } }
@keyframes g-slidein { from { transform: translateX(24px); opacity: .6; } } @keyframes g-slidein { from { transform: translateX(24px); opacity: .6; } }
@media (prefers-reduced-motion: reduce) { .g-drawer-veil, .g-drawer-p { animation: none; } } @media (prefers-reduced-motion: reduce) { .g-drawer-veil, .g-drawer-p { animation: none; } }
@@ -6,6 +6,7 @@
* 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。 * 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。
*/ */
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { LlmInstanceView, LlmProvider } from '@contracts'; import type { LlmInstanceView, LlmProvider } from '@contracts';
import { probeLlmDraftRoute, probeLlmInstanceRoute, upsertLlmInstanceRoute } from '@contracts'; import { probeLlmDraftRoute, probeLlmInstanceRoute, upsertLlmInstanceRoute } from '@contracts';
@@ -154,8 +155,20 @@ function LlmDrawer({
onError: (e: unknown) => setProbed({ ok: false, error: (e as { message?: string }).message ?? '测试失败' }), onError: (e: unknown) => setProbed({ ok: false, error: (e as { message?: string }).message ?? '测试失败' }),
}); });
return ( /*
<div className="g-drawer" role="dialog" aria-modal="true" aria-label={isNew ? '接入模型' : `配置 ${draft.name}`}> * **挂到 body 上**(2026-07-30 实测踩到):抽屉是 `position: fixed`,而详情页容器
* `.g-detail` 带着入场动画的 `transform` —— 带 transform 的祖先会成为 fixed 的包含块,
* 于是抽屉被钉进那张卡的盒子里,量出来只有 520×129:头尾一挤,输入框整片没了。
* 走 portal 就与 DOM 位置解绑,谁在祖先上加动画都影响不到它。
*/
return createPortal(
/*
* `gov-root` 这个类必须跟着一起挂:整套令牌(--card / --cline / --ink …)
* 定义在 `.gov-root` 上,而 portal 把节点挂到了 body —— 出了那个作用域,
* `var(--card)` 解析为空,面板背景直接变透明(实测 rgba(0,0,0,0),
* 顶栏与页面内容从抽屉里透出来)。挂上它,portal 与令牌两头都成立。
*/
<div className="gov-root g-drawer" role="dialog" aria-modal="true" aria-label={isNew ? '接入模型' : `配置 ${draft.name}`}>
<button className="g-drawer-veil" aria-label="关闭" onClick={onCancel} /> <button className="g-drawer-veil" aria-label="关闭" onClick={onCancel} />
<div className="g-drawer-p"> <div className="g-drawer-p">
<header> <header>
@@ -274,7 +287,8 @@ function LlmDrawer({
</button> </button>
</footer> </footer>
</div> </div>
</div> </div>,
document.body,
); );
} }