<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FaLayoutContainer 复刻 - 顶部/底部显隐 + 半圆三角按钮</title>
<style>
/* ============================================================
一、设计变量(沿用 Fantastic-admin 风格)
============================================================ */
:root {
--g-app-bg: #ffffff;
--g-main-bg: #f5f7f9;
--g-border-color: #ebeef5;
--g-title-color: #1f2d3d;
--g-text-color: #5a5e66;
--g-icon-color: #8c939d;
--g-primary: #409eff;
--g-radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
height: 100vh;
overflow: hidden;
background: linear-gradient(135deg, #1e3c72, #2a5298, #4b6cb7);
}
/* ============================================================
二、窗口外壳(保持)
============================================================ */
.window-shell {
position: absolute;
width: 90vw;
max-width: 1400px;
height: 85vh;
max-height: 900px;
min-width: 720px;
min-height: 500px;
background: var(--g-app-bg);
border-radius: 10px;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 20px 60px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.25);
display: flex;
flex-direction: column;
overflow: hidden;
transition: opacity .3s, transform .3s, border-radius .3s, width .3s, height .3s, left .3s, top .3s;
}
.window-shell.is-maximized {
left: 0 !important;
top: 0 !important;
width: 100vw !important;
height: 100vh !important;
max-width: none;
max-height: none;
border-radius: 0;
}
.window-shell.is-minimized {
left: 16px !important;
top: auto !important;
bottom: 16px !important;
width: 260px !important;
height: 40px !important;
min-width: 0;
min-height: 0;
border-radius: 8px;
cursor: pointer;
}
.window-shell.is-minimized .window-body,
.window-shell.is-minimized .window-controls {
display: none;
}
.window-shell.is-minimized .window-titlebar {
cursor: pointer;
justify-content: center;
}
.window-shell.is-closed {
opacity: 0;
transform: scale(.92);
pointer-events: none;
}
.window-titlebar {
flex-shrink: 0;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
background: #f3f3f3;
border-bottom: 1px solid #e5e5e5;
padding-left: 12px;
user-select: none;
cursor: default;
}
.window-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
font-weight: 500;
color: #303133;
}
.window-title .app-icon {
width: 16px;
height: 16px;
}
.window-controls {
display: flex;
height: 100%;
}
.win-btn {
width: 46px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
cursor: pointer;
color: #333;
transition: background .15s, color .15s;
}
.win-btn:hover {
background: #e5e5e5;
}
.win-btn.close:hover {
background: #e81123;
color: #fff;
}
.win-btn svg {
width: 10px;
height: 10px;
}
.window-body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
padding: 12px;
background: var(--g-main-bg);
overflow: hidden;
}
/* ============================================================
三、核心布局:官方 FaLayoutContainer 结构(左 | 中(上中下) | 右)
============================================================ */
.layout-container {
display: flex;
flex: 1;
min-height: 0;
border: 1px solid var(--g-border-color);
border-radius: var(--g-radius);
overflow: hidden;
background: var(--g-app-bg);
--left-width: 240px;
--right-width: 240px;
--top-height: 44px;
--bottom-height: 44px;
}
/* ---- 所有面板/区域的基础样式 ---- */
.side-panel,
.main-content,
.inner-panel,
.top-bar,
.bottom-bar {
background: var(--g-app-bg);
border: none;
position: relative;
display: flex;
flex-direction: column;
overflow: visible;
transition: width .3s cubic-bezier(.4, 0, .2, 1), height .3s cubic-bezier(.4, 0, .2, 1),
opacity .3s;
}
/* ---- 左侧面板 ---- */
.left-panel {
width: var(--left-width);
flex-shrink: 0;
border-right: 1px solid var(--g-border-color);
}
/* ---- 右侧面板 ---- */
.right-panel {
width: var(--right-width);
flex-shrink: 0;
border-left: 1px solid var(--g-border-color);
}
/* ---- 中部区域:垂直排列(上 | 中 | 下) ---- */
.middle-area {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 顶部栏 */
.top-bar {
height: var(--top-height);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
border-bottom: 1px solid var(--g-border-color);
position: relative;
}
.top-bar .bar-title {
font-size: 13px;
font-weight: 600;
color: var(--g-title-color);
display: flex;
align-items: center;
gap: 8px;
}
/* 中部主内容区(官方 default slot) */
.main-content {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 底部栏 */
.bottom-bar {
height: var(--bottom-height);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
border-top: 1px solid var(--g-border-color);
position: relative;
}
.bottom-bar .bar-title {
font-size: 13px;
font-weight: 600;
color: var(--g-title-color);
display: flex;
align-items: center;
gap: 8px;
}
/* ---- 中部内容排版 ---- */
.panel-header {
padding: 10px 14px;
font-weight: 600;
font-size: 13px;
border-bottom: 1px solid var(--g-border-color);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
color: var(--g-title-color);
}
.panel-body {
padding: 14px;
overflow-y: auto;
font-size: 13px;
line-height: 1.7;
color: var(--g-text-color);
flex: 1;
}
.panel-body ul {
list-style: none;
padding: 0;
}
.panel-body li {
padding: 6px 10px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 8px;
cursor: default;
}
.panel-body li:hover {
background: var(--g-main-bg);
}
.panel-body li svg {
width: 16px;
height: 16px;
color: var(--g-icon-color);
flex-shrink: 0;
}
/* ============================================================
四、显隐控制(核心修复:顶部/底部栏的独立显隐)
============================================================ */
/* 所有边栏通用的隐藏态:高度/宽度归零,内部内容隐藏,但保留按钮 */
.side-panel.is-hidden .panel-header,
.side-panel.is-hidden .panel-body,
.side-panel.is-hidden .resize-handle {
display: none !important;
}
/* 左侧/右侧面板隐藏:宽度归零 */
.left-panel.is-hidden,
.right-panel.is-hidden {
width: 0 !important;
min-width: 0;
padding: 0;
overflow: visible;
}
/* 顶部/底部栏隐藏:高度归零(官方行为) */
.top-bar.is-hidden,
.bottom-bar.is-hidden {
height: 0 !important;
min-height: 0;
padding: 0;
border: none !important;
overflow: visible;
}
.top-bar.is-hidden .bar-title,
.bottom-bar.is-hidden .bar-title {
display: none !important;
}
/* 折叠后去掉面板自身的边框,避免残留线条 */
.left-panel.is-hidden {
border-right: none !important;
}
.right-panel.is-hidden {
border-left: none !important;
}
/* ============================================================
五、折叠按钮(半圆 + 三角,贴合边缘)
-------------------------------------------------------------
设计思路(贴近官方):
- 按钮是边线的一部分,而非浮起的独立卡片。
- 使用 SVG 绘制“半圆 + 三角”组合图标。
- 直边无边框,白色背景与面板无缝融合。
- 圆弧侧有 1px 边框,颜色与边线一致。
============================================================ */
.toggle-btn {
position: absolute;
z-index: 40;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
outline: none;
cursor: pointer;
background: var(--g-app-bg);
border: 1px solid var(--g-border-color);
transition: width .22s cubic-bezier(.4, 0, .2, 1),
height .22s cubic-bezier(.4, 0, .2, 1),
background .2s, border-color .2s, box-shadow .2s;
}
/* 扩大点击热区(不显示) */
.toggle-btn::after {
content: '';
position: absolute;
top: -4px;
bottom: -4px;
left: -6px;
right: -6px;
}
.toggle-btn svg {
width: 14px;
height: 14px;
flex-shrink: 0;
color: var(--g-icon-color);
opacity: .35;
transition: opacity .15s, color .15s, transform .3s;
}
/* ---- 按钮位置与形状(水平模式) ---- */
/* 左侧面板按钮:贴在右边界,圆弧向右鼓出 */
.left-panel .toggle-btn {
left: 100%;
top: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 44px;
border-radius: 0 22px 22px 0;
border-left: none;
}
.left-panel .toggle-btn:hover,
.left-panel.is-hidden .toggle-btn {
width: 20px;
}
/* 右侧面板按钮:贴在左边界,圆弧向左鼓出 */
.right-panel .toggle-btn {
left: 0;
top: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 44px;
border-radius: 22px 0 0 22px;
border-right: none;
}
.right-panel .toggle-btn:hover,
.right-panel.is-hidden .toggle-btn {
width: 20px;
}
/* 顶部栏按钮:贴在下边界,圆弧向下鼓出 */
.top-bar .toggle-btn {
left: 50%;
top: 100%;
transform: translate(-50%, -50%);
width: 44px;
height: 12px;
border-radius: 0 0 22px 22px;
border-top: none;
}
.top-bar .toggle-btn:hover,
.top-bar.is-hidden .toggle-btn {
height: 20px;
}
/* 底部栏按钮:贴在上边界,圆弧向上鼓出 */
.bottom-bar .toggle-btn {
left: 50%;
top: 0;
transform: translate(-50%, -50%);
width: 44px;
height: 12px;
border-radius: 22px 22px 0 0;
border-bottom: none;
}
.bottom-bar .toggle-btn:hover,
.bottom-bar.is-hidden .toggle-btn {
height: 20px;
}
/* ---- 悬停与折叠态的颜色反馈 ---- */
.toggle-btn:hover,
.side-panel.is-hidden .toggle-btn,
.top-bar.is-hidden .toggle-btn,
.bottom-bar.is-hidden .toggle-btn {
background: var(--g-primary);
border-color: var(--g-primary);
box-shadow: 0 2px 8px rgba(64, 158, 255, .35);
}
.toggle-btn:hover svg,
.side-panel.is-hidden .toggle-btn svg,
.top-bar.is-hidden .toggle-btn svg,
.bottom-bar.is-hidden .toggle-btn svg {
opacity: 1;
color: #fff;
}
/* ---- 折叠后图标翻转 ---- */
.side-panel.is-hidden .toggle-btn svg,
.top-bar.is-hidden .toggle-btn svg,
.bottom-bar.is-hidden .toggle-btn svg {
transform: rotate(180deg);
}
/* ============================================================
六、拖拽手柄
============================================================ */
.resize-handle {
position: absolute;
z-index: 20;
background: transparent;
transition: background .2s;
}
.resize-handle:hover,
.resize-handle.active {
background: var(--g-primary);
}
.left-panel .resize-handle {
right: -2px;
top: 0;
bottom: 0;
width: 5px;
cursor: col-resize;
}
.right-panel .resize-handle {
left: -2px;
top: 0;
bottom: 0;
width: 5px;
cursor: col-resize;
}
/* ============================================================
七、重新打开按钮
============================================================ */
.reopen-btn {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 12px 28px;
background: #fff;
border: 1px solid var(--g-border-color);
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, .3);
font-size: 14px;
color: var(--g-primary);
cursor: pointer;
display: none;
align-items: center;
gap: 8px;
z-index: 9999;
}
.reopen-btn:hover {
background: #ecf5ff;
border-color: var(--g-primary);
}
</style>
</head>
<body>
<div class="window-shell" id="windowShell">
<div class="window-titlebar" id="windowTitlebar">
<div class="window-title">
<svg class="app-icon" viewBox="0 0 16 16">
<rect x="1" y="1" width="6" height="6" rx="1" fill="#409eff" />
<rect x="9" y="1" width="6" height="6" rx="1" fill="#67c23a" />
<rect x="1" y="9" width="6" height="6" rx="1" fill="#e6a23c" />
<rect x="9" y="9" width="6" height="6" rx="1" fill="#f56c6c" />
</svg>
<span>FaLayoutContainer — 顶部/底部显隐 + 半圆三角按钮</span>
</div>
<div class="window-controls">
<button class="win-btn" id="btnMinimize"><svg viewBox="0 0 10 10">
<path d="M0 5h10" stroke="currentColor" />
</svg></button>
<button class="win-btn" id="btnMaximize"><svg viewBox="0 0 10 10">
<rect x=".5" y=".5" width="9" height="9" fill="none" stroke="currentColor" />
</svg></button>
<button class="win-btn close" id="btnClose"><svg viewBox="0 0 10 10">
<path d="M0 0l10 10M10 0L0 10" stroke="currentColor" />
</svg></button>
</div>
</div>
<div class="window-body">
<div class="layout-container" id="layoutContainer">
<!-- ================= 左侧面板(导航) ================= -->
<aside class="side-panel left-panel" id="leftPanel">
<div class="panel-header"><span>导航</span></div>
<div class="panel-body">
<ul>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="7" height="7" rx="1" />
</svg>仪表盘</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>用户管理</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>系统设置</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>日志中心</li>
</ul>
</div>
<div class="resize-handle" data-resize="left"></div>
<button class="toggle-btn" data-toggle="left" aria-label="切换左侧导航">
<svg viewBox="0 0 24 24">
<path d="M15 6l-6 6 6 6V6z" fill="currentColor" />
<path d="M3 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h2V4H3z" fill="currentColor" opacity="0.3" />
</svg>
</button>
</aside>
<!-- ================= 中部区域(上 | 中 | 下) ================= -->
<div class="middle-area">
<!-- 顶部栏 -->
<div class="top-bar" id="topBar">
<span class="bar-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M3 12h18M3 6h18M3 18h18" />
</svg>
顶部栏(topSide)
</span>
<span style="font-size:12px;color:var(--g-icon-color);">高度可调</span>
<button class="toggle-btn" data-toggle="top" aria-label="切换顶部栏">
<svg viewBox="0 0 24 24">
<path d="M6 15l6-6 6 6H6z" fill="currentColor" />
<path d="M4 3a2 2 0 0 0-2 2v2h20V5a2 2 0 0 0-2-2H4z" fill="currentColor"
opacity="0.3" />
</svg>
</button>
</div>
<!-- 中部主内容区(default slot) -->
<main class="main-content">
<div class="panel-header"><span>主内容区(default 插槽)</span></div>
<div class="panel-body">
<p><strong>顶部栏和底部栏现已支持独立显隐。</strong>点击顶部栏边缘的半圆三角按钮,可以将其折叠;再次点击即可展开。</p>
<p>左侧和右侧面板的折叠按钮同样是半圆与三角的组合,贴在边缘线上。</p>
<p>拖动左右面板的边缘可以调整宽度,拖动顶部/底部栏的边缘可以调整高度。</p>
</div>
</main>
<!-- 底部栏 -->
<div class="bottom-bar" id="bottomBar">
<span class="bar-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M3 12h18M3 6h18M3 18h18" />
</svg>
底部栏(bottomSide)
</span>
<span style="font-size:12px;color:var(--g-icon-color);">高度可调</span>
<button class="toggle-btn" data-toggle="bottom" aria-label="切换底部栏">
<svg viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6H6z" fill="currentColor" />
<path d="M4 21a2 2 0 0 1-2-2v-2h20v2a2 2 0 0 1-2 2H4z" fill="currentColor"
opacity="0.3" />
</svg>
</button>
</div>
</div>
<!-- ================= 右侧面板 ================= -->
<aside class="side-panel right-panel" id="rightPanel">
<div class="panel-header"><span>右侧</span></div>
<div class="panel-body">
<p>这里是右侧面板,可以放置属性设置或详情内容。</p>
<p style="margin-top:10px;">拖左边缘调整宽度,边界上会浮现半圆三角把手。</p>
</div>
<div class="resize-handle" data-resize="right"></div>
<button class="toggle-btn" data-toggle="right" aria-label="切换右侧面板">
<svg viewBox="0 0 24 24">
<path d="M9 6l6 6-6 6V6z" fill="currentColor" />
<path d="M21 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-2V4h2z" fill="currentColor" opacity="0.3" />
</svg>
</button>
</aside>
</div>
</div>
</div>
<button class="reopen-btn" id="reopenBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12a9 9 0 1 0 3-6.7L3 8" />
<path d="M3 3v5h5" />
</svg>
重新打开窗口
</button>
<script>
/* ============================================================
窗口逻辑(保持)
============================================================ */
const windowShell = document.getElementById('windowShell');
const windowTitlebar = document.getElementById('windowTitlebar');
const btnMinimize = document.getElementById('btnMinimize');
const btnMaximize = document.getElementById('btnMaximize');
const btnClose = document.getElementById('btnClose');
const reopenBtn = document.getElementById('reopenBtn');
function centerWindow() {
windowShell.style.left = (window.innerWidth - windowShell.offsetWidth) / 2 + 'px';
windowShell.style.top = Math.max(20, (window.innerHeight - windowShell.offsetHeight) / 2) + 'px';
}
window.addEventListener('load', centerWindow);
window.addEventListener('resize', () => {
if (!windowShell.classList.contains('is-maximized') &&
!windowShell.classList.contains('is-minimized') &&
!windowShell.classList.contains('is-closed')) centerWindow();
});
let dragging = false, dsX = 0, dsY = 0, wsL = 0, wsT = 0;
windowTitlebar.addEventListener('mousedown', e => {
if (windowShell.classList.contains('is-maximized') ||
windowShell.classList.contains('is-minimized') ||
e.target.closest('.window-controls')) return;
dragging = true;
dsX = e.clientX; dsY = e.clientY;
wsL = windowShell.offsetLeft; wsT = windowShell.offsetTop;
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
windowShell.style.left = wsL + e.clientX - dsX + 'px';
windowShell.style.top = Math.max(0, wsT + e.clientY - dsY) + 'px';
});
document.addEventListener('mouseup', () => {
if (dragging) { dragging = false; document.body.style.userSelect = ''; }
});
windowTitlebar.addEventListener('dblclick', e => {
if (e.target.closest('.window-controls')) return;
const willMax = !windowShell.classList.contains('is-maximized');
windowShell.classList.toggle('is-maximized', willMax);
btnMaximize.innerHTML = willMax
? `<svg viewBox="0 0 10 10"><rect x=".5" y="2.5" width="7" height="7" fill="none" stroke="currentColor"/><path d="M2.5 2.5V.5h7v7h-2" fill="none" stroke="currentColor"/></svg>`
: `<svg viewBox="0 0 10 10"><rect x=".5" y=".5" width="9" height="9" fill="none" stroke="currentColor"/></svg>`;
if (!willMax) centerWindow();
});
btnMaximize.addEventListener('click', () => windowTitlebar.dispatchEvent(new Event('dblclick')));
btnMinimize.addEventListener('click', () => windowShell.classList.add('is-minimized'));
windowShell.addEventListener('click', e => {
if (windowShell.classList.contains('is-minimized') &&
!e.target.closest('.window-controls')) {
windowShell.classList.remove('is-minimized'); centerWindow();
}
});
btnClose.addEventListener('click', () => {
windowShell.classList.add('is-closed');
reopenBtn.style.display = 'flex';
});
reopenBtn.addEventListener('click', () => {
windowShell.classList.remove('is-closed', 'is-minimized');
reopenBtn.style.display = 'none';
centerWindow();
});
/* ============================================================
布局容器逻辑(核心修复:顶部/底部栏的显隐)
============================================================ */
const container = document.getElementById('layoutContainer');
const panels = {
left: document.getElementById('leftPanel'),
right: document.getElementById('rightPanel'),
top: document.getElementById('topBar'),
bottom: document.getElementById('bottomBar')
};
/* ---- 折叠 / 展开(通用) ---- */
function togglePanel(key) {
const panel = panels[key];
if (!panel) return;
panel.classList.toggle('is-hidden');
syncCtrlBtn(key);
}
/* ---- 同步第二行控制按钮的文字与状态 ---- */
function syncCtrlBtn(key) {
const ctrl = document.querySelector(`[data-ctrl="${key}"]`);
if (!ctrl) return;
const isHidden = panels[key].classList.contains('is-hidden');
ctrl.classList.toggle('is-active', isHidden);
ctrl.innerHTML = ctrl.innerHTML.replace(/(隐藏|展开)/, isHidden ? '展开' : '隐藏');
}
/* ---- 绑定边缘切换按钮 ---- */
document.querySelectorAll('[data-toggle]').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
togglePanel(btn.dataset.toggle);
});
});
/* ---- 绑定第二行控制按钮(现在顶部/底部栏真正生效) ---- */
document.querySelectorAll('[data-ctrl]').forEach(btn => {
btn.addEventListener('click', () => {
const key = btn.dataset.ctrl;
togglePanel(key);
});
});
/* ---- 拖拽调整尺寸(左右宽度 + 上下高度) ---- */
document.querySelectorAll('.resize-handle').forEach(handle => {
handle.addEventListener('mousedown', e => {
e.preventDefault();
const key = handle.dataset.resize;
const panel = panels[key];
if (!panel || panel.classList.contains('is-hidden')) return;
const startX = e.clientX, startY = e.clientY;
const computed = getComputedStyle(container);
const startL = parseFloat(computed.getPropertyValue('--left-width')) || 240;
const startR = parseFloat(computed.getPropertyValue('--right-width')) || 240;
const startT = parseFloat(computed.getPropertyValue('--top-height')) || 44;
const startB = parseFloat(computed.getPropertyValue('--bottom-height')) || 44;
handle.classList.add('active');
const btn = panel.querySelector('.toggle-btn');
if (btn) btn.style.opacity = '0';
function onMove(ev) {
const dx = ev.clientX - startX, dy = ev.clientY - startY;
let v;
if (key === 'left') {
v = Math.max(160, Math.min(startL + dx, 600));
container.style.setProperty('--left-width', v + 'px');
} else if (key === 'right') {
v = Math.max(160, Math.min(startR - dx, 600));
container.style.setProperty('--right-width', v + 'px');
} else if (key === 'top') {
v = Math.max(30, Math.min(startT + dy, 300));
container.style.setProperty('--top-height', v + 'px');
} else if (key === 'bottom') {
v = Math.max(30, Math.min(startB - dy, 300));
container.style.setProperty('--bottom-height', v + 'px');
}
}
function onUp() {
handle.classList.remove('active');
if (btn) btn.style.opacity = '';
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
document.body.style.cursor = (key === 'left' || key === 'right') ? 'col-resize' : 'row-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
});
</script>
</body>
</html>