2026-05-25-00-07-30 优化自动匹配对比视图与采样设置
This commit is contained in:
@@ -2227,6 +2227,7 @@ export function VoxelizationMappingView({
|
||||
toolbar,
|
||||
overlayPlacement,
|
||||
onOverlayStatsChange,
|
||||
showSliceControl = true,
|
||||
}: {
|
||||
project: Project | null;
|
||||
moduleStyles: Record<string, ModuleStyle>;
|
||||
@@ -2241,6 +2242,7 @@ export function VoxelizationMappingView({
|
||||
toolbar?: React.ReactNode;
|
||||
overlayPlacement?: 'bottom' | 'side' | 'none';
|
||||
onOverlayStatsChange?: (stats: OverlayStats, visibleModuleCount: number) => void;
|
||||
showSliceControl?: boolean;
|
||||
}) {
|
||||
const baseCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const overlayCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
@@ -2593,7 +2595,7 @@ export function VoxelizationMappingView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`grid min-h-0 flex-1 ${activeOverlayPlacement === 'side' ? 'grid-cols-[minmax(0,1fr)_188px]' : 'grid-cols-[minmax(0,1fr)_56px]'} bg-black`}>
|
||||
<div className={`grid min-h-0 flex-1 ${showSliceControl ? (activeOverlayPlacement === 'side' ? 'grid-cols-[minmax(0,1fr)_188px]' : 'grid-cols-[minmax(0,1fr)_56px]') : 'grid-cols-1'} bg-black`}>
|
||||
<div
|
||||
className="flex min-h-0 flex-col"
|
||||
>
|
||||
@@ -2632,21 +2634,23 @@ export function VoxelizationMappingView({
|
||||
{activeOverlayPlacement === 'bottom' && renderOverlaySummary('bottom')}
|
||||
</div>
|
||||
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-white/10 bg-[#0f172a] px-2 py-5">
|
||||
<div className="relative min-h-[220px] w-8 flex-1">
|
||||
<div className="absolute inset-y-0 left-1/2 w-1.5 -translate-x-1/2 rounded-full bg-white/10" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={sliderSliceValue}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-dark-vertical-input"
|
||||
aria-label="项目库逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
{activeOverlayPlacement === 'side' && renderOverlaySummary('side')}
|
||||
</aside>
|
||||
{showSliceControl && (
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-white/10 bg-[#0f172a] px-2 py-5">
|
||||
<div className="relative min-h-[220px] w-8 flex-1">
|
||||
<div className="absolute inset-y-0 left-1/2 w-1.5 -translate-x-1/2 rounded-full bg-white/10" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={sliderSliceValue}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-dark-vertical-input"
|
||||
aria-label="项目库逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
{activeOverlayPlacement === 'side' && renderOverlaySummary('side')}
|
||||
</aside>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -2676,7 +2680,7 @@ export function VoxelizationMappingView({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_110px]">
|
||||
<div className={`grid min-h-0 flex-1 ${showSliceControl ? 'grid-cols-[minmax(0,1fr)_110px]' : 'grid-cols-1'}`}>
|
||||
<div className="flex min-h-0 flex-col">
|
||||
<div
|
||||
ref={mappingViewportRef}
|
||||
@@ -2734,47 +2738,49 @@ export function VoxelizationMappingView({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-slate-100 bg-white px-3 py-4">
|
||||
<div className="w-full rounded-2xl border border-slate-100 bg-white px-2 py-3 text-center shadow-sm">
|
||||
<p className="text-[10px] font-bold text-slate-700">DICOM 切片位置</p>
|
||||
<span className="mt-1 block font-mono text-[10px] font-bold text-blue-600">
|
||||
{displaySliceNumber} / {Math.max(totalSlices, 1)}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(1)}
|
||||
disabled={safeSlice >= maxSlice}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 shadow-sm hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-35"
|
||||
title="上一层"
|
||||
>
|
||||
<ChevronUp size={16} />
|
||||
</button>
|
||||
<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-200" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={sliderSliceValue}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-vertical-input"
|
||||
aria-label="逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(-1)}
|
||||
disabled={safeSlice <= 0}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 shadow-sm hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-35"
|
||||
title="下一层"
|
||||
>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
<div className="grid w-full grid-cols-1 gap-1 text-center text-[9px] font-bold text-slate-500">
|
||||
<span>顶层 1</span>
|
||||
<span className="text-blue-600">当前 {displaySliceNumber}</span>
|
||||
<span>底层 {Math.max(totalSlices, 1)}</span>
|
||||
</div>
|
||||
</aside>
|
||||
{showSliceControl && (
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-slate-100 bg-white px-3 py-4">
|
||||
<div className="w-full rounded-2xl border border-slate-100 bg-white px-2 py-3 text-center shadow-sm">
|
||||
<p className="text-[10px] font-bold text-slate-700">DICOM 切片位置</p>
|
||||
<span className="mt-1 block font-mono text-[10px] font-bold text-blue-600">
|
||||
{displaySliceNumber} / {Math.max(totalSlices, 1)}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(1)}
|
||||
disabled={safeSlice >= maxSlice}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 shadow-sm hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-35"
|
||||
title="上一层"
|
||||
>
|
||||
<ChevronUp size={16} />
|
||||
</button>
|
||||
<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-200" />
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxSlice}
|
||||
value={sliderSliceValue}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-vertical-input"
|
||||
aria-label="逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(-1)}
|
||||
disabled={safeSlice <= 0}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 shadow-sm hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-35"
|
||||
title="下一层"
|
||||
>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
<div className="grid w-full grid-cols-1 gap-1 text-center text-[9px] font-bold text-slate-500">
|
||||
<span>顶层 1</span>
|
||||
<span className="text-blue-600">当前 {displaySliceNumber}</span>
|
||||
<span>底层 {Math.max(totalSlices, 1)}</span>
|
||||
</div>
|
||||
</aside>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user