docs(vtk): 三栏结构重构设计 + 高保真原型(方案C视图内嵌侧栏/全屏/右键菜单/改名VTK视图)

This commit is contained in:
gaozheng 2026-06-16 14:39:33 +08:00
parent c058c851ee
commit 540fb1cde5
2 changed files with 625 additions and 0 deletions

View File

@ -0,0 +1,495 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>三栏结构重构 · 高保真原型对比</title>
<style>
/* ===== 真实 app 深色令牌src/app/Theme.cpp dark 列) ===== */
:root{
--bg-app:#0E1116; --bg-panel:#161A20; --bg-panel-subtle:#161B22; --bg-header:#12161C;
--bg-hover:#1B2129; --bg-selected:#16243F;
--border:#262C35; --border-strong:#333B45;
--text:#E6E9EF; --text-2:#A4ADBB; --text-3:#7A8494; --text-dis:#5A626F;
--accent:#5E8DF5; --accent-h:#93B4FA;
--canvas-bg:#0B1320; --canvas-soft:#111B2D; --canvas-grid:#1E2A3D;
--canvas-text:#E6ECF5; --canvas-dim:#8A97AC;
--danger:#FF6166; --warn:#F5A623; --ok:#46C07A;
--r:6px;
}
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;}
body{
font-family:"Microsoft YaHei UI","Segoe UI",system-ui,sans-serif;
background:#05080d;color:var(--text);font-size:13px;
display:flex;flex-direction:column;height:100vh;overflow:hidden;
}
/* ===== 顶部方案切换条(原型控制,非 app 一部分) ===== */
.meta{
display:flex;align-items:center;gap:14px;padding:10px 16px;
background:#11151c;border-bottom:1px solid var(--border);flex:0 0 auto;
}
.meta .lbl{color:var(--text-3);font-size:12px;}
.meta .opts{display:flex;gap:8px;}
.meta button{
font:inherit;font-size:12.5px;color:var(--text-2);background:var(--bg-panel);
border:1px solid var(--border);border-radius:20px;padding:6px 16px;cursor:pointer;
transition:.12s;
}
.meta button:hover{border-color:var(--accent);color:var(--text);}
.meta button.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600;}
.meta .tag{margin-left:auto;font-size:11.5px;color:var(--text-3);}
.meta .tag b{color:var(--ok);}
/* ===== app 外壳 ===== */
.app{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;background:var(--bg-app);}
/* TopBar */
.topbar{
height:46px;flex:0 0 auto;display:flex;align-items:center;gap:14px;padding:0 14px;
background:var(--bg-header);border-bottom:1px solid var(--border);
}
.topbar .logo{width:24px;height:24px;border-radius:6px;background:linear-gradient(135deg,var(--accent),#3B73EC);}
.topbar .ws{font-weight:600;color:var(--text);}
.topbar .ws small{color:var(--text-3);font-weight:400;margin-left:6px;}
.topbar .spacer{flex:1;}
.topbar .ico{width:30px;height:30px;border-radius:6px;display:grid;place-items:center;color:var(--text-2);}
.topbar .ico:hover{background:var(--bg-hover);}
.topbar .avatar{width:30px;height:30px;border-radius:50%;background:var(--accent);color:#fff;display:grid;place-items:center;font-size:12px;font-weight:700;}
/* dock 网格 */
.dockgrid{flex:1 1 auto;display:grid;gap:6px;padding:6px;min-height:0;}
/* 默认A/B左 / 中 / 右 三列 */
.dockgrid.cols{grid-template-columns:288px 1fr 248px;grid-template-rows:1fr;}
.col{display:flex;flex-direction:column;gap:6px;min-height:0;min-width:0;}
/* dock 面板 */
.dock{
background:var(--bg-panel);border:1px solid var(--border);border-radius:var(--r);
display:flex;flex-direction:column;min-height:0;overflow:hidden;
}
.dock-hd{
height:30px;flex:0 0 auto;display:flex;align-items:center;gap:6px;padding:0 10px;
background:var(--bg-header);border-bottom:1px solid var(--border);
font-size:12.5px;font-weight:600;color:var(--text-2);
}
.dock-hd .badge{margin-left:auto;font-size:10.5px;font-weight:600;color:var(--text-3);
background:var(--bg-hover);border-radius:9px;padding:1px 7px;}
.dock-bd{flex:1 1 auto;overflow:auto;padding:8px 10px;min-height:0;}
.dock.flex1{flex:1 1 auto;}
.dock.flexN{flex:0 0 auto;}
/* 对象树 / 列表项 */
.tree{list-style:none;}
.tree li{padding:4px 2px;color:var(--text);white-space:nowrap;border-radius:4px;}
.tree li:hover{background:var(--bg-hover);}
.tree li.sel{background:var(--bg-selected);}
.tree .ind1{padding-left:18px;}
.tree .ind2{padding-left:34px;}
.tree .ind3{padding-left:50px;}
.ck{display:inline-block;width:13px;height:13px;border:1px solid var(--border-strong);border-radius:3px;
vertical-align:-2px;margin-right:7px;position:relative;background:var(--canvas-bg);}
.ck.on{background:var(--accent);border-color:var(--accent);}
.ck.on::after{content:"";position:absolute;left:4px;top:1px;width:3px;height:7px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}
.tw{color:var(--text-3);margin-right:4px;font-size:10px;}
.muted{color:var(--text-3);}
.pill{font-size:10px;color:var(--accent-h);border:1px solid var(--canvas-grid);border-radius:8px;padding:0 6px;margin-left:6px;}
/* 工具条 */
.toolbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:8px 10px;
border-bottom:1px solid var(--border);background:var(--bg-panel-subtle);}
.toolbar.canvas{background:var(--canvas-soft);border-color:var(--canvas-grid);}
select.mini,.btnm{
font:inherit;font-size:11.5px;color:var(--text);background:var(--canvas-bg);
border:1px solid var(--canvas-grid);border-radius:4px;padding:3px 7px;cursor:pointer;
}
.btnm:hover{background:var(--bg-hover);border-color:var(--accent);}
.grp{display:flex;gap:3px;flex-wrap:wrap;}
.grp .btnm{padding:3px 8px;}
/* 工具条分组栏位 */
.toolbar.col{flex-direction:column;align-items:stretch;gap:0;}
.tgrp{display:flex;flex-wrap:wrap;align-items:center;gap:5px;padding:6px 0;border-bottom:1px dashed var(--canvas-grid);}
.tgrp:last-child{border-bottom:none;}
.tgrp .glbl{width:100%;font-size:11px;font-weight:600;color:var(--canvas-dim);margin-bottom:2px;}
.flbl{font-size:11px;color:var(--canvas-dim);display:inline-flex;align-items:center;gap:2px;}
.zin{font:inherit;font-size:11px;width:52px;color:var(--text);background:var(--canvas-bg);
border:1px solid var(--canvas-grid);border-radius:4px;padding:2px 5px;}
/* 表单行:标签固定宽 + 控件填满,不再流式折行 */
.frow{display:flex;align-items:center;gap:8px;width:100%;}
.frow .flbl{width:58px;flex:0 0 auto;}
.frow select.mini{width:132px;flex:0 0 auto;}
.frow .track{flex:1;}
.sval{font-size:11px;color:var(--canvas-text);width:36px;text-align:right;flex:0 0 auto;}
/* 滑块:独立类,不再依赖 .slider 父级(之前那样导致 track 0 高度看不见) */
.track{height:5px;background:var(--canvas-grid);border-radius:3px;position:relative;min-width:80px;}
.track .knob{position:absolute;top:-4px;width:13px;height:13px;border-radius:50%;
background:var(--accent);box-shadow:0 0 0 3px rgba(94,141,245,.22);cursor:pointer;}
.track .fill{position:absolute;left:0;top:0;bottom:0;background:var(--accent);border-radius:3px;opacity:.55;}
/* VTK 视图区 */
.vtk{
flex:1 1 auto;position:relative;min-height:0;border-radius:var(--r);overflow:hidden;
background:radial-gradient(120% 120% at 50% 18%,#16243f 0%,var(--canvas-bg) 60%);
border:1px solid var(--border);
}
.vtk .hd{position:absolute;top:0;left:0;right:0;height:30px;display:flex;align-items:center;padding:0 10px;
background:rgba(18,22,28,.72);border-bottom:1px solid var(--canvas-grid);font-size:12px;color:var(--text-2);z-index:4;backdrop-filter:blur(2px);}
/* 全屏按钮VTK视图 + 数据详情 标题栏右侧) */
.fsbtn{margin-left:auto;width:22px;height:20px;display:grid;place-items:center;cursor:pointer;
border-radius:4px;color:var(--text-3);font-size:13px;}
.fsbtn:hover{background:var(--bg-hover);color:var(--accent);}
.fs-on{position:fixed !important;inset:0;z-index:90;border-radius:0;width:auto !important;height:auto !important;}
/* 假三维:两片帘面 + 体素盒 + 切片 */
.scene{position:absolute;inset:30px 0 0 0;perspective:900px;display:grid;place-items:center;}
.stage{transform-style:preserve-3d;transform:rotateX(60deg) rotateZ(-28deg);}
.curtain{position:absolute;width:230px;height:120px;left:-115px;top:-60px;
background:linear-gradient(90deg,#2a4a8f,#3aa0c0 35%,#7ec96f 60%,#e8c84a 78%,#d9603a);
opacity:.92;border:1px solid rgba(255,255,255,.18);box-shadow:0 0 24px rgba(94,141,245,.25);}
.curtain.b{transform:rotateZ(90deg) translateZ(0);opacity:.78;}
.axes{position:absolute;left:-130px;top:70px;width:0;height:0;}
.axes i{position:absolute;height:2px;transform-origin:left center;}
.ax-x{width:150px;background:#e5605f;}
.ax-y{width:120px;background:#46c07a;transform:rotate(-90deg);}
.grid-floor{position:absolute;width:300px;height:300px;left:-150px;top:-150px;
background-image:linear-gradient(var(--canvas-grid) 1px,transparent 1px),linear-gradient(90deg,var(--canvas-grid) 1px,transparent 1px);
background-size:30px 30px;opacity:.35;transform:translateZ(-2px);}
.slice3d{position:absolute;width:160px;height:90px;left:-80px;top:-45px;
background:repeating-linear-gradient(45deg,rgba(94,141,245,.35) 0 8px,rgba(94,141,245,.12) 8px 16px);
border:2px solid var(--accent);transform:rotateY(0deg) rotateZ(28deg) translateZ(40px);box-shadow:0 0 18px rgba(94,141,245,.4);}
.legend{position:absolute;right:12px;bottom:12px;width:14px;height:96px;border-radius:3px;
background:linear-gradient(#d9603a,#e8c84a,#7ec96f,#3aa0c0,#2a4a8f);border:1px solid var(--canvas-grid);z-index:3;}
.legend::after{content:"Ω·m";position:absolute;left:-24px;top:40px;font-size:10px;color:var(--canvas-dim);}
/* tabs方案 A */
.tabbar{display:flex;gap:2px;border-bottom:1px solid var(--border);background:var(--bg-header);flex:0 0 auto;}
.tabbar .tab{padding:7px 14px;font-size:12.5px;color:var(--text-3);cursor:pointer;border-bottom:2px solid transparent;}
.tabbar .tab:hover{color:var(--text);}
.tabbar .tab.on{color:var(--accent);border-bottom-color:var(--accent);font-weight:600;}
.tabpane{display:none;flex-direction:column;min-height:0;flex:1 1 auto;}
.tabpane.on{display:flex;}
/* 折叠分段(方案 B */
.section .sec-hd{display:flex;align-items:center;gap:6px;padding:7px 10px;cursor:pointer;
background:var(--bg-header);border-top:1px solid var(--border);font-weight:600;color:var(--text-2);font-size:12.5px;}
.section:first-child .sec-hd{border-top:none;}
.section .sec-hd .tw{font-size:11px;}
.section .sec-bd{padding:6px 10px 10px;}
/* 视图内嵌侧栏(方案 C 修正版):抽屉式,画布在右、不遮挡 */
.vtk.with-drawer .scene{left:var(--drawer-w,300px);transition:left .18s;}
.view-drawer{position:absolute;top:30px;left:0;bottom:0;width:300px;z-index:6;
background:rgba(17,27,45,.94);border-right:1px solid var(--canvas-grid);
display:flex;flex-direction:column;backdrop-filter:blur(3px);transition:width .18s;overflow:hidden;}
.view-drawer .tabbar{background:rgba(18,22,28,.55);}
.drawer-toggle{position:absolute;top:38px;z-index:7;left:300px;width:18px;height:46px;
background:rgba(17,27,45,.94);border:1px solid var(--canvas-grid);border-left:none;
border-radius:0 6px 6px 0;display:grid;place-items:center;color:var(--canvas-dim);cursor:pointer;
font-size:11px;transition:left .18s;}
.drawer-toggle:hover{color:var(--accent);}
.vtk.drawer-collapsed .view-drawer{width:0;border-right:none;}
.vtk.drawer-collapsed .scene{left:0;}
.vtk.drawer-collapsed .drawer-toggle{left:0;}
/* 右键菜单 */
.ctx{position:absolute;z-index:30;min-width:150px;background:var(--bg-panel);border:1px solid var(--border-strong);
border-radius:6px;box-shadow:0 10px 30px rgba(0,0,0,.6);padding:4px;display:none;}
.ctx.show{display:block;}
.ctx .it{padding:6px 12px;border-radius:4px;font-size:12.5px;color:var(--text);cursor:pointer;white-space:nowrap;}
.ctx .it:hover{background:var(--accent);color:#fff;}
.ctx .sep{height:1px;background:var(--border);margin:4px 6px;}
.ctx .it.sub::after{content:"▸";float:right;color:var(--text-3);margin-left:18px;}
.hint{font-size:11px;color:var(--text-3);padding:6px 10px;border-top:1px dashed var(--border);}
.note{font-size:11.5px;color:var(--warn);background:rgba(245,166,35,.08);border:1px solid rgba(245,166,35,.25);
border-radius:5px;padding:6px 9px;margin:8px 10px;}
kbd{background:var(--bg-hover);border:1px solid var(--border-strong);border-radius:3px;padding:0 5px;font-size:11px;}
</style>
</head>
<body>
<div class="meta">
<span class="lbl">三栏结构重构 · 布局方案</span>
<div class="opts">
<button data-opt="C" class="on">方案 C · 视图内嵌侧栏(修正·推荐)</button>
<button data-opt="A">方案 A · 左侧独立 dock</button>
<button data-opt="B">方案 B · 竖向分段 dock</button>
</div>
<span class="tag">配色取自 <b>Theme.cpp</b> 深色令牌 · 右键「三维分析」树里的三维体试试创建切片</span>
</div>
<!-- ============ APP 外壳 ============ -->
<div class="app">
<div class="topbar">
<div class="logo"></div>
<div class="ws">地大演示项目 <small>· 工作区</small></div>
<div class="spacer" style="flex:1"></div>
<div class="ico"></div><div class="ico"></div>
<div class="avatar">GZ</div>
</div>
<div class="dockgrid cols" id="grid">
<!-- 左列:内容随方案变 -->
<div class="col" id="leftcol"></div>
<!-- 中列VTK + 详情 -->
<div class="col" style="min-width:0">
<div class="dock flex1" style="padding:0;border:none;background:transparent">
<div class="vtk" id="vtk">
<div class="hd">VTK视图 · 地大演示项目<span class="fsbtn" data-fs title="全屏 / 还原"></span></div>
<div class="scene"><div class="stage">
<div class="grid-floor"></div>
<div class="curtain"></div>
<div class="curtain b"></div>
<div class="slice3d"></div>
<div class="axes"><i class="ax-x"></i><i class="ax-y"></i></div>
</div></div>
<div class="legend"></div>
</div>
</div>
<div class="dock flexN" style="height:120px">
<div class="dock-hd">数据详情<span class="fsbtn" data-fs title="全屏 / 还原"></span></div>
<div class="dock-bd muted" style="font-size:12px">选中数据集查看详情(源数据 / 切片 / 异常 / 插值模型 / 色阶 / 测量)</div>
</div>
</div>
<!-- 右列:异常/属性 -->
<div class="col">
<div class="dock flex1">
<div class="dock-hd">异常 / 对象属性</div>
<div class="dock-bd">
<ul class="tree">
<li><span class="tw"></span>异常体 A <span class="pill">随GS</span></li>
<li class="ind1"><span class="tw"></span>分组-1</li>
<li class="ind2 muted">异常 #1</li>
<li class="ind2 muted">异常 #2</li>
</ul>
</div>
</div>
<div class="dock flexN" style="height:150px">
<div class="dock-hd">数据集属性</div>
<div class="dock-bd muted" style="font-size:12px">名称 / 类型 / 维度 / 创建时间…</div>
</div>
</div>
</div>
</div>
<!-- 三维体数据集 右键菜单原文行21-27切片/色阶/显隐/详情,无删除) -->
<div class="ctx" id="ctx">
<div class="it sub" data-act="slice">切片</div>
<div class="it">色阶</div>
<div class="it">显示 / 隐藏</div>
<div class="it">数据详情</div>
</div>
<div class="ctx" id="ctxSub">
<div class="it">上下</div>
<div class="it">前后</div>
<div class="it">左右</div>
<div class="it">任意</div>
</div>
<!-- 切片数据集 右键菜单原文行29-35保存/保存为/导出/删除/色阶/显隐/详情) -->
<div class="ctx" id="ctxSlice">
<div class="it">保存</div>
<div class="it">保存为</div>
<div class="it">导出</div>
<div class="it" style="color:var(--danger)">删除</div>
<div class="sep"></div>
<div class="it">色阶</div>
<div class="it">显示 / 隐藏</div>
<div class="it">数据详情</div>
</div>
<script>
// ===== 三栏内容片段 =====
function dataset3DList(){return `
<ul class="tree">
<li><span class="ck on"></span>反演剖面-L1 <span class="pill">帘面</span></li>
<li><span class="ck on"></span>反演剖面-L2 <span class="pill">帘面</span></li>
<li><span class="ck"></span>体素模型-V1 <span class="pill">dd_voxel</span></li>
<li><span class="ck"></span>地形 DEM+影像</li>
</ul>`;}
function dataset2DList(){return `
<ul class="tree">
<li><span class="ck on"></span>测线-T1 <span class="pill">俯视</span></li>
<li><span class="ck"></span>轨迹-Tr1 <span class="pill">trajectory</span></li>
</ul>`;}
function analysisTree(){return `
<ul class="tree" id="anaTree">
<li><span class="ck on"></span><span class="tw"></span>GS-地大演示</li>
<li class="ind1" data-vol="1"><span class="ck on"></span><span class="tw"></span>三维体模型-V1 <span class="pill">右键</span></li>
<li class="ind2" data-slice="1"><span class="ck on"></span><span class="tw"></span>切片·上下-01 <span class="pill">右键</span></li>
<li class="ind2" data-slice="1"><span class="ck"></span><span class="tw"></span>切片·任意-02</li>
<li class="ind1" data-vol="1"><span class="ck"></span><span class="tw"></span>三维体模型-V2</li>
</ul>
<div class="hint">右键<b>三维体</b>→切片▸(上下/前后/左右/任意)·色阶·显隐·详情;右键<b>切片</b>→保存/保存为/导出/删除·色阶·显隐·详情。</div>`;}
function toolbar3D(canvas){return `
<div class="toolbar col ${canvas?'canvas':''}">
<div class="tgrp"><span class="glbl">坐标轴设置</span>
<div class="frow"><span class="flbl">显示方式</span><select class="mini"><option>标准</option><option>三维立体</option><option>不显示</option></select></div>
<div class="frow"><span class="flbl">O点位置</span><span class="btnm">设置…</span></div>
<div class="frow"><span class="flbl">刻度</span><select class="mini"><option>无刻度</option><option selected></option><option>英尺</option><option>经纬度</option></select></div>
<div class="frow"><span class="flbl">字体</span><span class="btnm">设置…</span></div>
</div>
<div class="tgrp"><span class="glbl">水平/垂直比例</span>
<div class="frow"><span class="track"><span class="fill" style="width:24%"></span><span class="knob" style="left:24%"></span></span><span class="sval">2.0×</span></div>
</div>
<div class="tgrp"><span class="glbl">快捷视图</span>
<span class="grp"><span class="btnm"></span><span class="btnm"></span><span class="btnm"></span><span class="btnm"></span><span class="btnm"></span><span class="btnm"></span></span>
</div>
<div class="tgrp"><span class="glbl">缩放 (Zoom)</span>
<span class="grp"><span class="btnm">放大</span><span class="btnm">缩小</span><span class="btnm">适配</span></span>
</div>
</div>`;}
function toolbar2D(canvas){return `
<div class="toolbar col ${canvas?'canvas':''}">
<div class="tgrp"><span class="glbl">地图</span>
<div class="frow"><span class="flbl">底图源</span><select class="mini"><option>天地图</option><option>Google Map</option><option>隐藏</option></select></div>
</div>
<div class="tgrp"><span class="glbl">2D视图</span>
<div class="frow"><span class="flbl">位置</span><select class="mini" onchange="document.getElementById('zwrap').style.display=this.value==='自定义'?'inline-flex':'none'">
<option>关闭</option><option selected>Z=0</option><option>顶部</option><option>底部</option><option>自定义</option></select></div>
<div class="frow" id="zwrap" style="display:none"><span class="flbl">Z 值</span><input class="zin" type="number" value="0"><span class="flbl">m</span></div>
</div>
</div>`;}
// 对象树 dockA/B/C 都有,作为勾选源)
function objectDock(){return `
<div class="dock flexN" style="height:170px">
<div class="dock-hd">对象 <span class="badge">勾选源</span></div>
<div class="dock-bd">
<ul class="tree">
<li class="sel"><span class="ck on"></span><span class="tw"></span>GS-地大演示</li>
<li class="ind1"><span class="ck on"></span>TM-反演成果</li>
<li class="ind1"><span class="ck"></span>TM-轨迹</li>
<li><span class="ck"></span><span class="tw"></span>GS-威立雅</li>
</ul>
</div>
</div>`;}
// ===== 三种方案的左列 =====
function buildA(){ // Tab 切换
return objectDock() + `
<div class="dock flex1">
<div class="tabbar" id="tabbarA">
<div class="tab on" data-t="0">三维数据集</div>
<div class="tab" data-t="1">二维数据集</div>
<div class="tab" data-t="2">三维分析</div>
</div>
<div class="tabpane on">${toolbar3D(false)}<div class="dock-bd">${dataset3DList()}</div></div>
<div class="tabpane">${toolbar2D()}<div class="dock-bd">${dataset2DList()}</div></div>
<div class="tabpane"><div class="dock-bd">${analysisTree()}</div></div>
</div>`;
}
function buildB(){ // 竖向分段
return objectDock() + `
<div class="dock flex1">
<div class="dock-hd">数据集 / 分析 <span class="badge">全可见</span></div>
<div class="dock-bd" style="padding:0">
<div class="section">
<div class="sec-hd"><span class="tw"></span>三维数据集</div>
<div class="sec-bd">${toolbar3D(false)}${dataset3DList()}</div>
</div>
<div class="section">
<div class="sec-hd"><span class="tw"></span>二维数据集</div>
<div class="sec-bd">${toolbar2D()}${dataset2DList()}</div>
</div>
<div class="section">
<div class="sec-hd"><span class="tw"></span>三维分析</div>
<div class="sec-bd">${analysisTree()}</div>
</div>
</div>
</div>`;
}
function buildC(){ // 视图内嵌侧栏(修正):左列保留对象列表(三栏的筛选来源)+ 详情
return objectDock() + `
<div class="dock flex1">
<div class="dock-hd">数据集(详情查看)</div>
<div class="dock-bd">
<ul class="tree">
<li class="muted">反演剖面-L1</li><li class="muted">体素模型-V1</li><li class="muted">测线-T1</li>
</ul>
<div class="note" style="color:var(--accent-h);background:rgba(94,141,245,.08);border-color:rgba(94,141,245,.3)">方案 C修正三个「子列表栏」内嵌在 VTK 视图左侧(抽屉式侧栏),三 tab 切换。画布在其右侧、不被遮挡;点侧栏右缘 <b></b> 可折叠让画布全宽。这正是需求「VTK视图上提供三个子列表栏」的形态。左侧「对象」列表是三栏的筛选来源需求筛勾选对象中的 ds</div>
</div>
</div>`;
}
function viewDrawerHTML(){return `
<div class="view-drawer">
<div class="tabbar">
<div class="tab on" data-t="0">三维数据集</div>
<div class="tab" data-t="1">二维数据集</div>
<div class="tab" data-t="2">三维分析</div>
</div>
<div class="tabpane on">${toolbar3D(true)}<div class="dock-bd">${dataset3DList()}</div></div>
<div class="tabpane">${toolbar2D(true)}<div class="dock-bd">${dataset2DList()}</div></div>
<div class="tabpane"><div class="dock-bd">${analysisTree()}</div></div>
</div>
<div class="drawer-toggle" id="drawerToggle"></div>`;}
// ===== 渲染 =====
const grid=document.getElementById('grid');
const vtk=document.getElementById('vtk');
function render(opt){
document.querySelectorAll('.meta button').forEach(b=>b.classList.toggle('on',b.dataset.opt===opt));
const left=document.getElementById('leftcol');
// 清掉方案 C 的内嵌侧栏
vtk.classList.remove('with-drawer','drawer-collapsed');
vtk.querySelectorAll('.view-drawer,.drawer-toggle').forEach(e=>e.remove());
if(opt==='A'){ left.innerHTML=buildA(); wireTabs(); }
if(opt==='B'){ left.innerHTML=buildB(); wireSections(); }
if(opt==='C'){
left.innerHTML=buildC();
vtk.classList.add('with-drawer');
vtk.insertAdjacentHTML('beforeend', viewDrawerHTML());
wireTabs(vtk.querySelector('.view-drawer'));
const tg=document.getElementById('drawerToggle');
tg.onclick=()=>{const c=vtk.classList.toggle('drawer-collapsed');tg.textContent=c?'▶':'◀';};
}
wireCtx();
}
function wireTabs(scope){
(scope||document).querySelectorAll('.tabbar .tab').forEach(t=>{
t.onclick=()=>{
const bar=t.parentElement, panes=bar.parentElement.querySelectorAll(':scope > .tabpane');
bar.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));
t.classList.add('on');
panes.forEach((p,i)=>p.classList.toggle('on',i===+t.dataset.t));
};
});
}
function wireSections(){
document.querySelectorAll('.section .sec-hd').forEach(h=>{
h.onclick=()=>{const bd=h.nextElementSibling, tw=h.querySelector('.tw');
const open=bd.style.display!=='none'; bd.style.display=open?'none':''; tw.textContent=open?'▸':'▾';};
});
}
// 右键菜单:三维体(ctx+ctxSub子菜单) / 切片(ctxSlice)
const ctx=document.getElementById('ctx'), ctxSub=document.getElementById('ctxSub'),
ctxSlice=document.getElementById('ctxSlice');
function popAt(menu,e){e.preventDefault();hideCtx();
menu.style.left=e.pageX+'px';menu.style.top=e.pageY+'px';menu.classList.add('show');}
function wireCtx(){
document.querySelectorAll('[data-vol]').forEach(li=>li.oncontextmenu=(e)=>popAt(ctx,e));
document.querySelectorAll('[data-slice]').forEach(li=>li.oncontextmenu=(e)=>popAt(ctxSlice,e));
ctx.querySelector('[data-act=slice]').onmouseenter=()=>{
const r=ctx.getBoundingClientRect();
ctxSub.style.left=r.right+'px';ctxSub.style.top=r.top+'px';ctxSub.classList.add('show');
};
}
function hideCtx(){[ctx,ctxSub,ctxSlice].forEach(m=>m.classList.remove('show'));}
document.addEventListener('click',hideCtx);
document.addEventListener('scroll',hideCtx,true);
// 全屏切换VTK视图 / 数据详情
document.querySelectorAll('[data-fs]').forEach(b=>b.onclick=(e)=>{
e.stopPropagation();
const panel=b.closest('.vtk')||b.closest('.dock');
const on=panel.classList.toggle('fs-on');
b.textContent=on?'🗗':'⛶';
});
document.querySelectorAll('.meta button').forEach(b=>b.onclick=()=>render(b.dataset.opt));
render('C');
</script>
</body>
</html>

View File

@ -0,0 +1,130 @@
# VTK 三维视图「三栏结构重构」设计
- 日期2026-06-16
- 分支:`feat/vtk-3d-view`
- 状态:**设计稿(经高保真原型逐项对齐用户反馈后定稿)**
- 上位文档:`2026-06-15-vtk-3d-supplementary-design.md`(总设计 v2。本文是其 **A1 三栏结构** 的实现增量设计,**就 UI 形态与若干控件细节,取代总 spec §7.1 中"左侧新 dock / tab 二选一"的开放表述**。
- 高保真原型:`docs/superpowers/mockups/2026-06-16-three-column-layout.html`(深色令牌取自 `src/app/Theme.cpp`;默认即定稿方案)。
- 需求来源:`Geopro3.0 需求表.xlsx`「补充需求」页签(已逐行精读,行号见下文)。
---
## 0. 目标与范围(用户已确认)
把当前"旧二维/三维切换 + 三个浮层"的过渡态 UI重组为需求 A1 的**三个子列表栏**,并接通到已有的渲染/交互能力。
### 本轮 IN
1. **三栏内嵌侧栏**VTK 视图内左侧抽屉 + 三 tab + 可折叠);删除旧「二维地图/三维视图」互斥切换;中央 dock 改名 **「VTK视图」**。
2. **收编三浮层**:左上 `layerPanel`(图层勾选)、右上 `axisBar`P2 工具条)→ 并入「三维数据集」栏;左下 `sliceBar`**删除**(切片改走三维分析右键菜单)。
3. **三维数据集栏**:工具条 4 栏位接 P2 已实现控制器;数据集列表按 `dimensionOf` 过滤 3D ds + 勾选 → 接现有 `VtkSceneController` 渲染。
4. **二维数据集栏**:列表按 2D 过滤;「地图 / 2D视图」控件做出来底图瓦片渲染留 P5本轮控件 UI + 2D 视图 Z 平面接通)。
5. **三维分析栏**:对象→三维体模型→切片 树;两个右键菜单 UI 完整;右键「切片」(上下/前后/左右/任意) **接已有 `SliceTool`**(替代 sliceBar
6. **VTK视图 + 数据详情** 标题栏右侧加**全屏按钮**。
### 本轮 OUT菜单项可见但暂 stub/禁用,留 P4/P5
- 切片 CRUD保存 / 保存为 / 导出 / 删除P4`I3dSceneRepository` 接口已留位)。
- 色阶编辑F26 参考 Geopro 1.0,无参考,留 P4
- 底图瓦片渲染P5、异常体管理 / 三维体详情 / 任务管理P4
---
## 1. 核心架构决策(关键澄清)
**全工作台只有一个共享的中央 VTK 视图**,三栏是叠在其上的「子列表栏」,各自把数据喂进同一视图。证据(需求交叉验证):
- 行 2「**VTK视图上**提供三个子列表栏」;行 11「显示在 VTK 中」;行 16「显示在 VTK 的 2D 视图」;行 19「显示在 vtk 的三维视图」——三处同指一个 VTK。
- 需求行 36「VTK视图」挂在「三维分析」下**只是把视图交互(选中拖动旋转 / 双击正视)归类描述**,不代表视图归三维分析独有。
**推论(本轮落地):**
- 旧「二维地图/三维视图」互斥分段按钮(`main.cpp:308-316` 一带)**删除**。
- 2D 不再是独立视图模式,而是 VTK 视图里的一个 **2D 图层/平面**(底图 + 2D 数据,摆在某 Z 平面),由「二维数据集」栏控制。
- 中央 dock 名 `二维地图/三维视图`**`VTK视图`**(同时承载 2D/3D且为需求原文叫法
---
## 2. 三栏物理形态(方案 C·视图内嵌侧栏
- 三栏 = **VTK 视图内左侧的抽屉式侧栏**,三个 tab`三维数据集 / 二维数据集 / 三维分析`,一次显一栏。
- 画布在侧栏右侧、**不被遮挡**;侧栏右缘有折叠开关(◀/▶),折叠后画布全宽。
- 侧栏与画布同属 VTK 视图容器(侧栏是视图子控件,符合"VTK视图上提供")。
- 左侧保留现有 `ObjectTreePanel`「对象」dock作为**筛选来源**:三栏列表只在"被勾选对象"范围内、按维度过滤显示 ds需求行 10/15。两级关系勾对象 → 三栏按维度显示其 ds。
- 现有左下「数据集」dock详情查看保留与三栏列表并存总 spec §7.1 已定的"两条线")。
> 取舍记录:曾考虑"左侧独立 dock + tab"(方案 A与"竖向分段"(方案 B均被否——需求「VTK视图**上**」明确栏在视图内;浮窗式(最初的 C遮挡画面亦否改为抽屉式。
---
## 3. 各栏内容(逐行对齐需求,含控件形态)
### 3.1 三维数据集栏(行 311
工具条 = **4 个分组栏位** + 数据集列表:
1. **坐标轴设置**(行 46——表单式每项一行、左对齐
- 显示方式:下拉(标准 / 三维立体 / 不显示)
- O点位置按钮弹框设原点
- 刻度:下拉(无刻度 / 米 / 英尺 / 经纬度)
- 字体:按钮(设刻度文字字体)
2. **水平/垂直比例**(行 7——**单个拖动滑块 + 数值**(如 2.0×,纵向放大系数;现有 `kVerticalExaggeration`)。**非两个独立控件**。
3. **快捷视图**(行 8——6 钮:前 / 后 / 左 / 右 / 上 / 下。
4. **缩放 Zoom**(行 9——3 钮:放大(In) / 缩小(Out) / 适配(Fit)。
5. **数据集列表**(行 1011——`dimensionOf==Dim3D` 过滤勾选对象的 ds勾选→渲染。
### 3.2 二维数据集栏(行 1216= 3 栏位
1. **地图**(行 13——下拉天地图 / Google Map / 隐藏)。**底图瓦片渲染留 P5**。
2. **2D视图**(行 14——下拉关闭 / Z=0 / 顶部 / 底部 / **自定义**);选「自定义」显数值输入框。
- **「自定义 Z」= 世界绝对高程(米),向上为正**与「Z=0/顶部/底部」同坐标系(`GeoLocalFrame` 世界 Z。决策依据同一下拉里「Z=0」即绝对值「自定义」只是输入任意绝对 Z与业界Petrel/Leapfrog/ParaView水平面高度用项目 CRS 绝对高程一致。
3. **数据集列表**(行 1516——`dimensionOf==Dim2D` 过滤,勾选→渲染到 VTK 的 2D 视图。
### 3.3 三维分析栏(行 1735
- **数据集列表 = 树**(行 18对象 → 三维体模型数据集 → 切片。可勾选三维体/切片 → 渲染(行 19
- **右键菜单(按节点类型分派):**
**三维体数据集**(行 2027**直接项,无"创建切片"父级,无删除**
| 菜单项 | 说明 | 本轮 |
|---|---|---|
| 切片 ▸ 上下 / 前后 / 左右 / 任意 | 一级「切片」父菜单 + 二级方向(上下/前后/左右=固定角度;任意=初始 45°可调| **接已有 SliceTool** |
| 色阶 | 参考 Geopro 1.0 | OUTstub/禁用)|
| 显示 / 隐藏 | actor 可见性 | IN |
| 数据详情 | 详情栏显示 | IN接现有详情|
> 注:需求字面是"上下切片/前后切片…"直接项;本轮按用户决策归入「切片」一级父菜单、二级去「切片」二字显「上下/前后/左右/任意」。
**切片数据集**(行 2835**有删除**
| 菜单项 | 本轮 |
|---|---|
| 保存 / 保存为 / 导出 / 删除 | OUTP4 CRUDstub/禁用)|
| 色阶 | OUT |
| 显示 / 隐藏 | IN |
| 数据详情 | IN |
- **VTK视图交互**(行 3638、**切片分析**(行 3951含视图内切片右键创建异常/保存/导出图片/导出dat/正视图/视图翻转/关闭)——属交互层,多已在 P3 实现或留 P4本轮不在树结构范围内。
---
## 4. 全屏功能(新需求)
- 在 **「VTK视图」** 与 **「数据详情」** 两个 dock 标题栏右侧各加一个**全屏切换按钮**:点击→该视图充满工作区;再点→还原。
- 理由:两视图含图形、内容多,常需全屏操作。
- 实现方向ADS`CDockWidget` 标题栏插入自定义 `QToolButton`,切换时把该 dock 最大化覆盖 dock 管理区(隐藏同级 / 浮动后最大化,择一,落地时定)。
---
## 5. 代码触点(落地指引,细节进 plan
- `src/app/main.cpp::buildWorkbench`
- 删 `layerPanel` / `axisBar` / `sliceBar` 三浮层及其锚定器(`RightTopAnchor`/`BottomLeftAnchor`)与 `showLayerPanel` 显隐逻辑。
- 删旧「二维地图/三维视图」分段切换;`vtkDock` 改名「VTK视图」。
- VTK 视图容器内新建**抽屉侧栏**QTabWidget 或自绘,三 tab+ 折叠开关;三栏工具条/列表迁入。
- **dock 布局持久化版本号须 bump**(见 `main.cpp:1429` 附近注释:改 dock 名/结构要升版本,否则旧布局反序列化错位)。
- 三维数据集工具条接 P2 已实现的 `VtkSceneController` 坐标轴/比例/快捷视图/zoom 槽(原 axisBar 的接线迁移,不重写控制器)。
- 数据集列表:用 `I3dSceneRepository::dimensionOf` 过滤;勾选信号接 `VtkSceneController`(复用现 `checkedTmsChanged` 一路的编排)。
- 三维分析树右键「切片」→ 调已有 `InteractionManager`/`SliceTool` 建切片(替代 sliceBar 原按钮路径)。
- 全屏按钮ADS 标题栏自定义按钮 + 最大化/还原。
---
## 6. 待定 / 风险
- 全屏在 ADS 的最大化实现方式(隐藏同级 vs 浮动最大化)落地时定。
- 二维「地图」底图本轮仅控件,渲染 P5需确保控件状态能持久化到 P5 不返工。
- 三栏侧栏与 ADS dock 的交互(侧栏是 VTK 容器子控件,不是 dock——确保折叠/全屏时布局正确。
- dock 名变更后旧用户布局失效bump 版本号后回落默认排布,可接受)。