feat(admin): 实例「管理」菜单改为悬浮图层展开

绝对定位悬浮层(从按钮下方浮出),不再撑高卡片/顶走下方内容;展开时卡片 overflow:visible
+ z-index:5(盖住下方/同列卡片,仍低于弹窗);加点击外部 / 点击菜单项自动关闭。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gloridust
2026-06-14 15:15:05 +08:00
Unverified
parent 3c9c30fcb4
commit 0ccbaa3a35
2 changed files with 40 additions and 11 deletions
+21 -9
View File
@@ -887,7 +887,17 @@ function InstanceAdminCard({
const installed = wx.installed && wx.phase !== 'downloading'; const installed = wx.installed && wx.phase !== 'downloading';
const offline = inst.runtime !== 'running'; const offline = inst.runtime !== 'running';
const working = !!acting || busy; // 生命周期操作中 或 微信下载/更新中 → 锁住卡片 const working = !!acting || busy; // 生命周期操作中 或 微信下载/更新中 → 锁住卡片
const [menuOpen, setMenuOpen] = useState(false); // 「管理」折叠菜单是否展开 const [menuOpen, setMenuOpen] = useState(false); // 「管理」菜单是否展开(悬浮层,不占文档流)
const menuRef = useRef<HTMLDivElement>(null);
// 悬浮下拉:点击菜单外部时关闭
useEffect(() => {
if (!menuOpen) return;
const onDocDown = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setMenuOpen(false);
};
document.addEventListener('mousedown', onDocDown);
return () => document.removeEventListener('mousedown', onDocDown);
}, [menuOpen]);
let badge: { text: string; cls: string }; let badge: { text: string; cls: string };
if (acting) badge = { text: '处理中', cls: 'tag-busy' }; if (acting) badge = { text: '处理中', cls: 'tag-busy' };
@@ -905,7 +915,7 @@ function InstanceAdminCard({
else sub = '微信尚未安装'; else sub = '微信尚未安装';
return ( return (
<div className="inst-card"> <div className={'inst-card' + (menuOpen ? ' open-menu' : '')}>
<div className="inst-head"> <div className="inst-head">
<span className="inst-name">{inst.name}</span> <span className="inst-name">{inst.name}</span>
<span className={'tag ' + badge.cls}>{badge.text}</span> <span className={'tag ' + badge.cls}>{badge.text}</span>
@@ -939,13 +949,14 @@ function InstanceAdminCard({
)} )}
</div> </div>
<button className={'inst-menu-toggle' + (menuOpen ? ' open' : '')} onClick={() => setMenuOpen((v) => !v)}> <div className="inst-menu-wrap" ref={menuRef}>
<span></span> <button className={'inst-menu-toggle' + (menuOpen ? ' open' : '')} onClick={() => setMenuOpen((v) => !v)}>
<span className="inst-menu-caret">{CaretIcon}</span> <span></span>
</button> <span className="inst-menu-caret">{CaretIcon}</span>
</button>
{menuOpen && ( {menuOpen && (
<div className="inst-menu"> <div className="inst-menu" onClick={() => setMenuOpen(false)}>
<div className="inst-menu-group"> <div className="inst-menu-group">
<div className="inst-menu-label"></div> <div className="inst-menu-label"></div>
<div className="inst-menu-items"> <div className="inst-menu-items">
@@ -997,7 +1008,8 @@ function InstanceAdminCard({
</div> </div>
</div> </div>
</div> </div>
)} )}
</div>
</> </>
)} )}
</div> </div>
+19 -2
View File
@@ -307,7 +307,6 @@ button {
/* 实例卡片操作:「管理」分类折叠菜单(默认收起,点开按运维/设置/危险分组展开文字操作) */ /* 实例卡片操作:「管理」分类折叠菜单(默认收起,点开按运维/设置/危险分组展开文字操作) */
.inst-menu-toggle { .inst-menu-toggle {
margin-top: 10px;
width: 100%; width: 100%;
height: 38px; height: 38px;
display: flex; display: flex;
@@ -335,11 +334,24 @@ button {
.inst-menu-toggle.open .inst-menu-caret { .inst-menu-toggle.open .inst-menu-caret {
transform: rotate(180deg); transform: rotate(180deg);
} }
/* 「管理」菜单:绝对定位悬浮层,浮在下方内容之上,不把卡片撑高、不顶走后面的内容 */
.inst-menu-wrap {
position: relative;
margin-top: 10px;
}
.inst-menu { .inst-menu {
margin-top: 8px; position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
z-index: 2;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
padding: 10px;
background: var(--surface);
border-radius: var(--r-small);
box-shadow: 0 6px 22px rgba(var(--shadow) / 0.26), 0 2px 6px rgba(var(--shadow) / 0.18);
animation: inst-menu-in 0.16s ease; animation: inst-menu-in 0.16s ease;
} }
@keyframes inst-menu-in { @keyframes inst-menu-in {
@@ -928,6 +940,11 @@ button {
.inst-card > * { .inst-card > * {
position: relative; position: relative;
} }
/* 菜单展开时:放开裁剪让悬浮层露出,并抬升层级盖住下方/同列卡片(仍低于弹窗 z-index 20 */
.inst-card.open-menu {
overflow: visible;
z-index: 5;
}
.inst-head { .inst-head {
display: flex; display: flex;
align-items: center; align-items: center;