2026-05-08-03-03-52 修正DICOM切片范围为单范围条
This commit is contained in:
@@ -818,6 +818,8 @@ export default function ReverseWorkspace({ projectId }: { projectId: string }) {
|
||||
const safeSliceEnd = clamp(sliceEnd, 0, maxSlice);
|
||||
const displayStart = Math.min(safeSliceStart, safeSliceEnd);
|
||||
const displayEnd = Math.max(safeSliceStart, safeSliceEnd);
|
||||
const rangeStartPercent = maxSlice > 0 ? (displayStart / maxSlice) * 100 : 0;
|
||||
const rangeEndPercent = maxSlice > 0 ? (displayEnd / maxSlice) * 100 : 0;
|
||||
const selectedDisplay = displayOptions.find((item) => item.id === displayLevel) ?? displayOptions[0];
|
||||
const selectedDicomOpacity = dicomOpacityOptions.find((item) => item.id === dicomOpacityLevel) ?? dicomOpacityOptions[0];
|
||||
const preloadPoints = [0.2, 0.4, 0.6, 0.8, 1].map((ratio) => clamp(Math.max(0, Math.round((project?.dicomCount ?? 1) * ratio) - 1), 0, maxSlice));
|
||||
@@ -924,30 +926,43 @@ export default function ReverseWorkspace({ projectId }: { projectId: string }) {
|
||||
{displayStart + 1} - {displayEnd + 1} / {project?.dicomCount ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<label className="grid grid-cols-[76px_1fr_64px] items-center gap-3 text-[10px] font-bold text-slate-500">
|
||||
起点
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={safeSliceStart}
|
||||
onChange={(event) => setSliceStart(Number(event.target.value))}
|
||||
className="accent-blue-600"
|
||||
/>
|
||||
<span className="text-right font-mono">{safeSliceStart + 1}</span>
|
||||
</label>
|
||||
<label className="mt-2 grid grid-cols-[76px_1fr_64px] items-center gap-3 text-[10px] font-bold text-slate-500">
|
||||
终点
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={safeSliceEnd}
|
||||
onChange={(event) => setSliceEnd(Number(event.target.value))}
|
||||
className="accent-blue-600"
|
||||
/>
|
||||
<span className="text-right font-mono">{safeSliceEnd + 1}</span>
|
||||
</label>
|
||||
<div className="py-1">
|
||||
<div className="relative h-10">
|
||||
<div className="absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-slate-200" />
|
||||
<div
|
||||
className="absolute top-1/2 h-2 -translate-y-1/2 rounded-full bg-blue-600"
|
||||
style={{
|
||||
left: `${rangeStartPercent}%`,
|
||||
right: `${100 - rangeEndPercent}%`,
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
aria-label="DICOM 切片范围起点"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={safeSliceStart}
|
||||
onChange={(event) => setSliceStart(Number(event.target.value))}
|
||||
className="dicom-range-input"
|
||||
style={{ zIndex: safeSliceStart >= safeSliceEnd ? 5 : 4 }}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
aria-label="DICOM 切片范围终点"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={safeSliceEnd}
|
||||
onChange={(event) => setSliceEnd(Number(event.target.value))}
|
||||
className="dicom-range-input"
|
||||
style={{ zIndex: safeSliceStart >= safeSliceEnd ? 4 : 5 }}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 grid grid-cols-3 text-[10px] font-bold text-slate-500">
|
||||
<span>起点 {safeSliceStart + 1}</span>
|
||||
<span className="text-center text-blue-600">范围</span>
|
||||
<span className="text-right">终点 {safeSliceEnd + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3 text-[10px] leading-5 text-slate-400">
|
||||
显示范围支持 M-N,两个端点可双向调整;范围变化只改变可视化切片,不改变模型原始位姿。
|
||||
</p>
|
||||
|
||||
@@ -1 +1,61 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.dicom-range-input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dicom-range-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dicom-range-input::-webkit-slider-runnable-track {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dicom-range-input::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: #2563eb;
|
||||
border: 3px solid #ffffff;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.28);
|
||||
cursor: grab;
|
||||
height: 20px;
|
||||
margin-top: -6px;
|
||||
pointer-events: auto;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.dicom-range-input::-moz-range-track {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dicom-range-input::-moz-range-thumb {
|
||||
background: #2563eb;
|
||||
border: 3px solid #ffffff;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.28);
|
||||
cursor: grab;
|
||||
height: 14px;
|
||||
pointer-events: auto;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.dicom-range-input:active::-webkit-slider-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.dicom-range-input:active::-moz-range-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
53
工程分析/实现方案-2026-05-08-03-03-52.md
Normal file
53
工程分析/实现方案-2026-05-08-03-03-52.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 实现方案:单条 DICOM 范围滑块
|
||||
|
||||
时间戳:2026-05-08-03-03-52
|
||||
|
||||
## 修改目标
|
||||
|
||||
将逆向体素化工作区“DICOM 切片范围”从两个上下排列的原生进度条,改为一条自绘轨道叠加两个可拖动端点的范围条。
|
||||
|
||||
## 涉及路径
|
||||
|
||||
- `WebSite/src/components/ReverseWorkspace.tsx`
|
||||
- `WebSite/src/index.css`
|
||||
- `工程分析/经验记录.md`
|
||||
|
||||
## 技术路线
|
||||
|
||||
1. 在 `ReverseWorkspace.tsx` 中基于 `safeSliceStart`、`safeSliceEnd`、`displayStart`、`displayEnd` 计算范围条起止百分比。
|
||||
2. 替换原有两个 `<label><input type="range"></label>`。
|
||||
3. 使用一个 `relative` 容器绘制:
|
||||
- 灰色底轨;
|
||||
- 蓝色选中范围;
|
||||
- 两个透明轨道的 `range input`,只显示滑块端点。
|
||||
4. 在 `index.css` 中新增 `.dicom-range-input` 样式,隐藏原生轨道,保留 thumb,并让 thumb 可点击拖动。
|
||||
5. 端点重合时提高“起点”滑块层级,便于从默认 `300-300` 状态向左拖出范围。
|
||||
|
||||
## 数据流与交互流程
|
||||
|
||||
- 用户拖动起点端点:调用 `setSliceStart(Number(event.target.value))`。
|
||||
- 用户拖动终点端点:调用 `setSliceEnd(Number(event.target.value))`。
|
||||
- 当前显示范围继续由 `displayStart = min(start, end)` 与 `displayEnd = max(start, end)` 决定。
|
||||
- 后续 `loadFusionVolume(displayStart/displayEnd)` 相关逻辑保持现状。
|
||||
|
||||
## 兼容性与回滚方案
|
||||
|
||||
- 若自定义样式在目标浏览器表现异常,可回滚本次对 `ReverseWorkspace.tsx` 和 `index.css` 的修改,恢复两个原生 range。
|
||||
- 本次不修改后端 API 和数据结构,回滚风险较低。
|
||||
|
||||
## 风险控制
|
||||
|
||||
- 保留无障碍 `aria-label`,明确起点和终点端点。
|
||||
- 通过 `npm run lint` 和 `npm run build` 检查 TypeScript 与构建。
|
||||
- 部署后通过页面源码或构建产物搜索确认不存在“起点/终点两个 label + 两个进度条”的旧结构。
|
||||
|
||||
## 预计文件变更
|
||||
|
||||
- 新增本次需求、实现、测试方案文档。
|
||||
- 修改 `ReverseWorkspace.tsx` 中 DICOM 范围控件 JSX。
|
||||
- 修改 `index.css` 增加范围条浏览器兼容样式。
|
||||
- 追加 `经验记录.md`。
|
||||
|
||||
## 人工审核状态
|
||||
|
||||
用户已在项目工作流历史中确认后续直接执行,本次不等待二次人工审核。
|
||||
45
工程分析/测试方案-2026-05-08-03-03-52.md
Normal file
45
工程分析/测试方案-2026-05-08-03-03-52.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 测试方案:DICOM 单范围条修复
|
||||
|
||||
时间戳:2026-05-08-03-03-52
|
||||
|
||||
## 静态检查
|
||||
|
||||
- 执行 `npm run lint`,验证 TypeScript 类型检查通过。
|
||||
- 执行 `npm run build`,验证 Vite 生产构建通过。
|
||||
|
||||
## 关键业务场景验证
|
||||
|
||||
- 打开逆向体素化工作区,查看“DICOM 切片范围”只显示一条范围轨道。
|
||||
- 默认 `300 - 300 / 300` 状态下,拖动起点端点可向左形成 `M - 300 / 300`。
|
||||
- 拖动终点端点可更新终点,起点和终点允许交叉,显示值仍自动按小到大输出。
|
||||
- 变化范围后,三维融合视角仍加载对应 DICOM 切片范围。
|
||||
|
||||
## 医学影像数据边界验证
|
||||
|
||||
- DICOM 总数为 1 时,范围条不除以 0,端点显示 `1 - 1 / 1`。
|
||||
- DICOM 总数为 300 时,最大端点仍为第 300 张。
|
||||
|
||||
## 回归风险
|
||||
|
||||
- 本次只改 UI 控件,不改 DICOM 数据读取和 STL 叠加逻辑。
|
||||
- 需要确认自定义 range 样式不会影响同页面其它原生滑块。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 页面不再出现上下两条“起点/终点”蓝色进度条。
|
||||
- `ReverseWorkspace.tsx` 中不再保留旧的两个 label range 结构。
|
||||
- 构建和重新部署成功。
|
||||
|
||||
## 无法测试的风险
|
||||
|
||||
- 当前无法在用户浏览器中直接确认缓存是否清除;部署后若仍看到旧界面,需要强制刷新浏览器缓存。
|
||||
|
||||
## 人工审核状态
|
||||
|
||||
用户已在项目工作流历史中确认后续直接执行,本次不等待二次人工审核。
|
||||
|
||||
## 执行结果
|
||||
|
||||
- `npm run lint`:通过。
|
||||
- `npm run build`:通过;仅出现 Vite chunk 大小提示,不影响运行。
|
||||
- `rg` 验证:`ReverseWorkspace.tsx` 中旧的 `grid grid-cols-[76px_1fr_64px]` 双 range 结构已移除,构建产物包含新的 `dicom-range-input` 单范围条结构。
|
||||
18
工程分析/经验记录.md
18
工程分析/经验记录.md
@@ -721,3 +721,21 @@ C. 解决问题方案
|
||||
D. 后续如何避免问题
|
||||
|
||||
所有配准相关可视化都必须区分“显示范围”和“空间基准”:显示范围可以变化,但 DICOM 物理尺寸、模型缩放基准和坐标原点不能跟着变化;任何切割或 Mask 预览都要明确只是辅助显示,不能修改原始 DICOM/STL 位姿。
|
||||
|
||||
## 2026-05-08-03-03-52 项目定位错误导致控件未修复
|
||||
|
||||
A. 具体问题
|
||||
|
||||
用户要求将“DICOM 切片范围”的起点、终点合并为一个范围条,但页面仍显示两个上下排列的进度条。
|
||||
|
||||
B. 产生问题原因
|
||||
|
||||
前一次处理时依据当前工作目录误改了 `Head_CT_Morph`,而用户截图中的真实页面来自 `ReVoxelSeg_DICOM/WebSite/src/components/ReverseWorkspace.tsx`。没有先用截图中的页面文案和 DOM 片段全局定位真实渲染文件,导致改动没有作用到用户看到的程序。
|
||||
|
||||
C. 解决问题方案
|
||||
|
||||
在 `/home/wkmgc/Desktop` 范围内搜索 `DICOM 切片范围` 和 `三维融合场景已就绪`,定位到正确项目 `ReVoxelSeg_DICOM`。将 `ReverseWorkspace.tsx` 中两个独立 `range` 替换为单条自绘轨道叠加两个端点的范围控件,并在 `index.css` 中隐藏原生 range 轨道、保留可拖动 thumb。
|
||||
|
||||
D. 后续如何避免问题
|
||||
|
||||
收到 UI 截图或 DOM 片段时,先用页面可见文案、class 片段和组件文本在所有相关项目目录中定位真实源码,再修改代码。若当前仓库内找不到截图中的文本,必须立即扩大搜索范围并向用户说明实际项目位置,不能默认当前工作目录就是目标项目。
|
||||
|
||||
40
工程分析/需求分析-2026-05-08-03-03-52.md
Normal file
40
工程分析/需求分析-2026-05-08-03-03-52.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 需求分析:修正 DICOM 切片范围控件
|
||||
|
||||
时间戳:2026-05-08-03-03-52
|
||||
|
||||
## 原始需求
|
||||
|
||||
用户反馈页面仍然显示“起点”“终点”两个独立进度条,并指出当前截图中的 DOM:
|
||||
|
||||
- `起点 <input type="range" ...>`
|
||||
- `终点 <input type="range" ...>`
|
||||
|
||||
要求将其改成一个范围控件,而不是上下两条进度条。
|
||||
|
||||
## 业务目标
|
||||
|
||||
- 在逆向体素化工作区的“DICOM 切片范围”中,用一个范围条承载起点和终点两个端点。
|
||||
- 起点、终点仍能独立拖动,并允许调整顺序。
|
||||
- 显示范围仍使用 `M - N / 总数`,不改变 DICOM/STL 数据加载逻辑。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 页面路径:逆向体素化/影像与模型融合视角。
|
||||
- 主要文件:`WebSite/src/components/ReverseWorkspace.tsx`。
|
||||
- 样式文件:`WebSite/src/index.css`。
|
||||
|
||||
## 约束
|
||||
|
||||
- 必须保留现有 `sliceStart`、`sliceEnd` 状态和 `displayStart/displayEnd` 归一化逻辑。
|
||||
- 只修复当前控件形态,不改动模型切割、Mask 生成和数据 API。
|
||||
- 本次是对上次误改错误项目的纠正,必须确认真实渲染文件来自 `ReVoxelSeg_DICOM`。
|
||||
|
||||
## 风险点
|
||||
|
||||
- 双滑块叠在同一个轨道上时,端点相同的情况下可能难以拖动。
|
||||
- 原生 range 轨道默认样式可能在浏览器中显示为两条蓝线,需用 CSS 隐藏原生轨道并自绘单条轨道。
|
||||
- Vite 开发服务需要重新部署到当前项目实际端口 `4000`。
|
||||
|
||||
## 待确认事项
|
||||
|
||||
- 用户已在历史工作流中确认后续直接执行;本次不再停等人工二次确认。
|
||||
Reference in New Issue
Block a user