2026-05-20-15-33-38 逆向映射导航外置与遮挡优化
This commit is contained in:
@@ -6,8 +6,8 @@ import {
|
||||
RotateCw,
|
||||
Rotate3d,
|
||||
AlertCircle,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Eye,
|
||||
Layers,
|
||||
Save,
|
||||
@@ -1882,8 +1882,8 @@ export function VoxelizationMappingView({
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-[420px] flex-col overflow-hidden rounded-3xl border border-slate-900 bg-slate-950 shadow-2xl">
|
||||
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
||||
<div className="absolute left-4 top-4 z-10 flex flex-wrap gap-2">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="flex min-w-0 flex-wrap gap-2">
|
||||
<span className="rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[9px] font-bold uppercase tracking-widest text-slate-200">
|
||||
Base DICOM
|
||||
</span>
|
||||
@@ -1891,9 +1891,14 @@ export function VoxelizationMappingView({
|
||||
Overlay Label Map
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute right-4 top-4 z-10 rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[10px] font-mono text-white/70">
|
||||
<div className="shrink-0 rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[10px] font-mono text-white/70">
|
||||
Z {safeSlice + 1}/{Math.max(totalSlices, 1)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_76px]">
|
||||
<div className="flex min-h-0 flex-col">
|
||||
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
||||
{dicomPreview ? (
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
@@ -1907,14 +1912,16 @@ export function VoxelizationMappingView({
|
||||
{dicomStatus}
|
||||
</div>
|
||||
)}
|
||||
<div className="pointer-events-none absolute inset-x-4 bottom-4 z-10 rounded-2xl border border-white/10 bg-black/70 p-3 backdrop-blur-sm">
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="mb-2 flex items-center justify-between gap-3 text-[10px] font-bold text-white/70">
|
||||
<span className="truncate">{overlayStatus}</span>
|
||||
<span className="font-mono text-cyan-100">
|
||||
{overlayStats.activeModules}/{visibleModuleCount} 构件 · {overlayStats.segmentCount} 边 · {overlayStats.filledPixels} px
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-h-20 overflow-auto pr-1">
|
||||
<div className="max-h-24 overflow-auto pr-1">
|
||||
{overlayStats.modules.length ? (
|
||||
<div className="grid grid-cols-2 gap-1.5 xl:grid-cols-3">
|
||||
{overlayStats.modules.map((item) => (
|
||||
@@ -1936,27 +1943,26 @@ export function VoxelizationMappingView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Slice Navigator</p>
|
||||
<span className="font-mono text-[10px] font-bold text-cyan-100">
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-white/10 bg-slate-900/95 px-3 py-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-slate-950/90 px-2 py-3 text-center">
|
||||
<p className="text-[10px] font-bold text-slate-300">DICOM 切片位置</p>
|
||||
<span className="mt-1 block font-mono text-[10px] font-bold text-cyan-100">
|
||||
{safeSlice + 1} / {Math.max(totalSlices, 1)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-[28px_1fr_28px] items-center gap-3">
|
||||
<button
|
||||
onClick={() => stepSlice(-1)}
|
||||
disabled={safeSlice <= 0}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-700 bg-slate-900 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
title="上一层"
|
||||
>
|
||||
<ChevronLeft size={15} />
|
||||
<ChevronUp size={16} />
|
||||
</button>
|
||||
<div className="relative h-8">
|
||||
<div className="absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-slate-800" />
|
||||
<div className="relative min-h-[240px] w-10 flex-1">
|
||||
<div className="absolute inset-y-0 left-1/2 w-2 -translate-x-1/2 rounded-full bg-slate-800" />
|
||||
<div
|
||||
className="absolute top-1/2 h-2 -translate-y-1/2 rounded-full bg-cyan-400"
|
||||
style={{ left: 0, width: `${slicePercent}%` }}
|
||||
className="absolute bottom-0 left-1/2 w-2 -translate-x-1/2 rounded-full bg-cyan-400"
|
||||
style={{ height: `${slicePercent}%` }}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
@@ -1964,19 +1970,24 @@ export function VoxelizationMappingView({
|
||||
max={maxSlice}
|
||||
value={safeSlice}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-input"
|
||||
className="mapping-slice-vertical-input"
|
||||
aria-label="逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(1)}
|
||||
disabled={safeSlice >= maxSlice}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-700 bg-slate-900 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
title="下一层"
|
||||
>
|
||||
<ChevronRight size={15} />
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
<div className="grid w-full grid-cols-1 gap-1 text-center text-[9px] font-bold text-slate-500">
|
||||
<span>顶层 {Math.max(totalSlices, 1)}</span>
|
||||
<span className="text-cyan-200">当前 {safeSlice + 1}</span>
|
||||
<span>底层 1</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -116,3 +116,61 @@
|
||||
.mapping-slice-input:active::-moz-range-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
direction: rtl;
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input::-webkit-slider-runnable-track {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: #22d3ee;
|
||||
border: 3px solid #0f172a;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.16), 0 8px 18px rgba(8, 47, 73, 0.45);
|
||||
cursor: grab;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input::-moz-range-track {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input::-moz-range-thumb {
|
||||
background: #22d3ee;
|
||||
border: 3px solid #0f172a;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.16), 0 8px 18px rgba(8, 47, 73, 0.45);
|
||||
cursor: grab;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input:active::-webkit-slider-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.mapping-slice-vertical-input:active::-moz-range-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
51
工程分析/实现方案-2026-05-20-15-33-38.md
Normal file
51
工程分析/实现方案-2026-05-20-15-33-38.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 实现方案:右侧竖向 Slice Navigator 与影像遮挡治理
|
||||
|
||||
实现方案文档路径:`工程分析/实现方案-2026-05-20-15-33-38.md`
|
||||
|
||||
## 修改目标
|
||||
|
||||
调整逆向分割映射视图布局:将切片导航从影像底部迁移到右侧竖向栏,并把 Overlay Label Map 的构件统计面板从影像内部移到影像下方,避免遮挡 DICOM 影像。
|
||||
|
||||
## 涉及路径
|
||||
|
||||
- `WebSite/src/components/ReverseWorkspace.tsx`
|
||||
- `工程分析/需求分析-2026-05-20-15-33-38.md`
|
||||
- `工程分析/实现方案-2026-05-20-15-33-38.md`
|
||||
- `工程分析/测试方案-2026-05-20-15-33-38.md`
|
||||
- `工程分析/经验记录.md`
|
||||
|
||||
## 技术路线
|
||||
|
||||
1. 定位 `VoxelizationMappingView` 中当前底部 `Slice Navigator` 与影像内 Overlay 状态面板。
|
||||
2. 将组件整体布局改为横向网格:左侧影像与独立信息区,右侧竖向切片导航栏。
|
||||
3. 保留顶部 `Base DICOM`、`Overlay Label Map`、`Z` 状态标签,但缩小为影像角落提示,避免大面积遮挡。
|
||||
4. 将构件统计列表改为影像下方独立面板,显示当前 Overlay 状态、构件数、边数、像素数和当前切片构件表。
|
||||
5. 为竖向 range 增加内联样式或类名,使用 `writingMode: 'vertical-rl'` 与 `direction: 'rtl'` 实现从上到下浏览切片。
|
||||
6. 执行类型检查、构建、部署验证。
|
||||
|
||||
## 执行步骤
|
||||
|
||||
- 阅读当前 `VoxelizationMappingView` JSX 与相关样式。
|
||||
- 修改布局结构和样式。
|
||||
- 检查 `mapping-slice-input` 是否有全局 CSS 影响,必要时增加独立竖向类。
|
||||
- 运行 `npm run lint` 和 `npm run build`。
|
||||
- 重启 `tmux` 服务并验证健康接口和首页。
|
||||
- 更新测试方案与经验记录。
|
||||
- 精确暂存、提交并推送 Gitea。
|
||||
|
||||
## 兼容性与回滚方案
|
||||
|
||||
- 该改动只影响前端布局,不改变数据结构和 API。
|
||||
- 如竖向 `range` 在某浏览器显示异常,可回滚为右侧竖排按钮加数值输入,或补充 WebKit 专用 CSS。
|
||||
- 项目库复用同一组件,因此无需额外复制样式逻辑。
|
||||
|
||||
## 预计文件变更
|
||||
|
||||
- `WebSite/src/components/ReverseWorkspace.tsx`
|
||||
- 本轮工程分析文档与 `工程分析/经验记录.md`
|
||||
|
||||
## 提交与部署策略
|
||||
|
||||
- 暂存本轮相关代码和工程分析文档。
|
||||
- commit message 包含 `2026-05-20-15-33-38`。
|
||||
- 推送 Gitea 后重启 `revoxelseg-dicom` 服务,验证 `http://127.0.0.1:4000/api/health` 与首页。
|
||||
55
工程分析/测试方案-2026-05-20-15-33-38.md
Normal file
55
工程分析/测试方案-2026-05-20-15-33-38.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# 测试方案:Slice Navigator 竖向外置与 Overlay 遮挡检查
|
||||
|
||||
测试方案文档路径:`工程分析/测试方案-2026-05-20-15-33-38.md`
|
||||
|
||||
## 静态检查
|
||||
|
||||
- 确认 `VoxelizationMappingView` 的 `Slice Navigator` 不再位于影像画布底部内部。
|
||||
- 确认切片导航栏位于影像右侧,并使用竖向 `range`。
|
||||
- 确认构件统计面板不再用绝对定位覆盖 DICOM 影像。
|
||||
- 确认 `Base DICOM` 和 `Overlay Label Map` 仅作为小标签显示,不遮挡主体区域。
|
||||
|
||||
## 构建检查
|
||||
|
||||
- 在 `WebSite/` 执行 `npm run lint`。
|
||||
- 在 `WebSite/` 执行 `npm run build`。
|
||||
|
||||
## 关键业务场景验证
|
||||
|
||||
- 逆向工作区中拖动右侧竖向切片条可以切换当前 Z 层。
|
||||
- 上下按钮仍可逐层切换。
|
||||
- DICOM 原始影像和 Overlay Label Map 不被构件列表遮挡。
|
||||
- 项目库中复用的逆向分割映射视图保持可用。
|
||||
|
||||
## 医学影像数据相关边界验证
|
||||
|
||||
- 不修改 DICOM/STL 原始数据。
|
||||
- 不改变体素化、求交、光栅化和导出逻辑。
|
||||
- 仅调整浏览控件与信息面板的位置,保证影像主体可审查。
|
||||
|
||||
## 部署验证
|
||||
|
||||
- 验证 `http://127.0.0.1:4000/api/health`。
|
||||
- 验证 `http://127.0.0.1:4000/` 返回 200。
|
||||
|
||||
## Git/Gitea 备份验证
|
||||
|
||||
- commit message 包含 `2026-05-20-15-33-38`。
|
||||
- 推送 Gitea 成功后记录 commit。
|
||||
- 确认未暂存历史删除状态、软著材料和运行态文件。
|
||||
|
||||
## 风险与回归关注点
|
||||
|
||||
- 竖向 range 的浏览器兼容性。
|
||||
- 小屏幕下右侧导航栏是否挤压影像宽度。
|
||||
- 项目库复用组件后的宽度约束是否仍能正常显示。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- `npm run lint`:通过,TypeScript 无报错。
|
||||
- `npm run build`:通过,Vite 完成生产构建;仅保留当前项目已有的大 chunk 体积提示。
|
||||
- 静态确认:`VoxelizationMappingView` 已移除底部横向 `Slice Navigator`,改为右侧竖向 `mapping-slice-vertical-input`。
|
||||
- 静态确认:Overlay 构件统计面板已从影像绝对定位层移到影像下方独立区域,不再遮挡 DICOM 画布。
|
||||
- 部署验证:已重建 `tmux` 会话 `revoxelseg-dicom`,执行 `npm run serve -- --host 0.0.0.0 --port 4000`。
|
||||
- `curl -fsS http://127.0.0.1:4000/api/health`:通过,返回 `{"ok":true,"service":"revoxelseg-dicom",...}`。
|
||||
- `curl -I -fsS http://127.0.0.1:4000/`:通过,返回 `HTTP/1.1 200 OK`。
|
||||
18
工程分析/经验记录.md
18
工程分析/经验记录.md
@@ -1297,3 +1297,21 @@ C. 解决问题方案
|
||||
D. 后续如何避免问题
|
||||
|
||||
凡是用户要求两个页面“效果一致”,优先抽取或导出已有真实组件,不再维护第二套近似 UI。涉及密码、权限、删除等高风险管理操作时,必须把资料编辑和凭据修改拆成不同入口,并通过显式标签、校验和反馈减少误操作。
|
||||
|
||||
## 2026-05-20-15-33-38 医学影像视图控件不要覆盖审查画布
|
||||
|
||||
A. 具体问题
|
||||
|
||||
用户指出逆向工作区的 `Slice Navigator` 格式与“DICOM 切片范围”不统一,并且 Overlay Label Map 的构件统计区域会遮挡 DICOM 影像,影响对二维切片和叠加分割结果的审查。
|
||||
|
||||
B. 产生问题原因
|
||||
|
||||
此前 `VoxelizationMappingView` 为了紧凑展示,把切片导航放在视图底部,同时将 Overlay 状态和构件列表使用绝对定位压在影像画布底部。该设计在普通预览中节省空间,但在医学影像核验场景中会遮挡 DICOM 主体区域。
|
||||
|
||||
C. 解决问题方案
|
||||
|
||||
将视图结构调整为“影像画布 + 右侧竖向切片导航 + 下方 Overlay 统计面板”:右侧竖向导航使用独立 `mapping-slice-vertical-input`,显示当前 DICOM 切片位置和上下层按钮;Overlay 构件列表移到画布下方,不再覆盖 DICOM 影像。
|
||||
|
||||
D. 后续如何避免问题
|
||||
|
||||
医学影像主画布应优先保持无遮挡,状态、统计、导航控件默认放在画布外侧。若必须悬浮在影像上,只能使用小尺寸状态标识,并在提交前检查是否遮挡解剖结构或分割边界。
|
||||
|
||||
53
工程分析/需求分析-2026-05-20-15-33-38.md
Normal file
53
工程分析/需求分析-2026-05-20-15-33-38.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 需求分析:逆向映射视图切片导航外置与遮挡优化
|
||||
|
||||
开始时间:`2026-05-20-15-33-38`
|
||||
|
||||
## 原始需求摘要
|
||||
|
||||
用户要求修改逆向工作区:
|
||||
|
||||
1. 将逆向工作区中 `Slice Navigator` 的格式与“DICOM 切片范围”的格式统一,不要放在图片里面。
|
||||
2. `Slice Navigator` 调整为竖向滚动条,放在图片右侧。
|
||||
3. `Overlay Label Map` 部分不要遮挡 DICOM 影像。
|
||||
|
||||
## 业务目标
|
||||
|
||||
- 让二维逆向分割映射视图的切片浏览控件不压住医学影像主体。
|
||||
- 保持切片导航与中部工具栏内“DICOM 切片范围”控件在视觉语义上统一。
|
||||
- 避免状态说明、构件统计面板遮挡 DICOM 原始影像和分割叠加区域,提升临床审查可读性。
|
||||
|
||||
## 输入与输出
|
||||
|
||||
输入:
|
||||
|
||||
- `WebSite/src/components/ReverseWorkspace.tsx`
|
||||
- 复用该组件的 `WebSite/src/components/ProjectLibrary.tsx`
|
||||
|
||||
输出:
|
||||
|
||||
- `VoxelizationMappingView` 的切片导航从底部内嵌区域改为右侧竖向导航栏。
|
||||
- 右侧导航栏显示当前层数、竖向 range 控件和上下切片按钮。
|
||||
- 构件统计面板移出影像画布覆盖层,作为影像下方独立信息区展示。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 逆向工作区“逆向分割映射视图”。
|
||||
- 项目库中复用的“逆向分割映射视图”。
|
||||
- 相关 Tailwind 样式和 TypeScript 类型检查。
|
||||
|
||||
## 关键约束
|
||||
|
||||
- 不改动 STL/DICOM 映射算法,只调整控件布局和遮挡关系。
|
||||
- 影像主体区域仍需保持 Base DICOM 与 Overlay Label Map 的标签提示。
|
||||
- 竖向切片条需要可拖动、可点击上下按钮、可通过键盘/辅助技术识别。
|
||||
- 不能把无关工作区历史删除和软著材料纳入提交。
|
||||
|
||||
## 风险点
|
||||
|
||||
- 原生 `range` 竖向显示在不同浏览器上需要兼容写法。
|
||||
- 切片导航移出底部后,需要保证容器高度和图片区域不会被挤压到不可用。
|
||||
- 项目库复用同一组件,布局变化会同步影响项目库,需要保持宽窄视口可用。
|
||||
|
||||
## 默认假设
|
||||
|
||||
- 用户所说“Overlay Label Map 部分”主要指当前切片构件统计和状态面板遮挡 DICOM 影像,而不是取消分割掩码本身;分割掩码仍应叠加显示。
|
||||
Reference in New Issue
Block a user