2026-05-07-18-42-53 优化可视化工具栏和构件ID联动

This commit is contained in:
2026-05-07 18:55:14 +08:00
parent 796619632b
commit 97edf35bd0
9 changed files with 393 additions and 155 deletions

View File

@@ -10,6 +10,13 @@ type ProjectStatus = 'pending' | 'completed' | 'processing';
type DicomPlane = 'axial' | 'sagittal' | 'coronal';
type DicomDisplayMode = 'default' | 'bone' | 'soft' | 'contrast';
interface ModuleStyleRecord {
visible: boolean;
color: string;
opacity: number;
partId: number;
}
interface UserRecord {
id: number;
name: string;
@@ -33,6 +40,7 @@ interface ProjectRecord {
maskFormats: Array<'nii' | 'nii.gz'>;
exportedMaskCount: number;
isDefault?: boolean;
moduleStyles: Record<string, ModuleStyleRecord>;
}
interface SessionRecord {
@@ -70,6 +78,7 @@ const dicomVolumeCache = new Map<DicomDisplayMode, {
spacingBetweenSlices: number | null;
}>();
const modelPreviewCache = new Map<string, unknown>();
const defaultModuleColors = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#f97316', '#64748b', '#ec4899'];
interface DicomAttributes {
patientName: string;
@@ -146,6 +155,37 @@ function publicSession(state: AppState) {
};
}
function clampNumber(value: number, min: number, max: number) {
return Math.max(min, Math.min(max, value));
}
function normalizeModuleStyle(
style: Partial<ModuleStyleRecord> | undefined,
index: number,
): ModuleStyleRecord {
const opacity = typeof style?.opacity === 'number' && Number.isFinite(style.opacity) ? style.opacity : 0.72;
const partId = typeof style?.partId === 'number' && Number.isFinite(style.partId) ? style.partId : index + 1;
return {
visible: typeof style?.visible === 'boolean' ? style.visible : true,
color: typeof style?.color === 'string' && /^#[0-9a-fA-F]{6}$/.test(style.color)
? style.color
: defaultModuleColors[index % defaultModuleColors.length],
opacity: clampNumber(opacity, 0.1, 1),
partId: clampNumber(Math.round(partId), 1, 255),
};
}
function buildModuleStyles(
stlFiles: string[],
existing?: Record<string, Partial<ModuleStyleRecord>>,
) {
return stlFiles.reduce<Record<string, ModuleStyleRecord>>((acc, fileName, index) => {
acc[fileName] = normalizeModuleStyle(existing?.[fileName], index);
return acc;
}, {});
}
function buildDefaultProject(): ProjectRecord {
const stlFiles = listFiles(modelDir, '.stl');
@@ -163,6 +203,7 @@ function buildDefaultProject(): ProjectRecord {
maskFormats: ['nii', 'nii.gz'],
exportedMaskCount: 0,
isDefault: true,
moduleStyles: buildModuleStyles(stlFiles),
};
}
@@ -180,6 +221,7 @@ function buildEmptyProject(name: string): ProjectRecord {
stlFiles: [],
maskFormats: ['nii', 'nii.gz'],
exportedMaskCount: 0,
moduleStyles: {},
};
}
@@ -197,13 +239,16 @@ function defaultState(): AppState {
function normalizeState(state: AppState): AppState {
const defaultProject = buildDefaultProject();
const savedDefaultProject = state.projects?.find((project) => project.id === defaultProject.id);
const customProjects = Array.isArray(state.projects)
? state.projects
.filter((project) => project.id !== defaultProject.id)
.map((project) => ({
...project,
stlFiles: Array.isArray(project.stlFiles) ? project.stlFiles : [],
exportedMaskCount: project.exportedMaskCount ?? 0,
maskFormats: project.maskFormats ?? ['nii', 'nii.gz'],
moduleStyles: buildModuleStyles(Array.isArray(project.stlFiles) ? project.stlFiles : [], project.moduleStyles),
}))
: [];
@@ -212,8 +257,9 @@ function normalizeState(state: AppState): AppState {
projects: [
{
...defaultProject,
name: state.projects?.find((project) => project.id === defaultProject.id)?.name ?? defaultProject.name,
exportedMaskCount: state.projects?.find((project) => project.id === defaultProject.id)?.exportedMaskCount ?? 0,
name: savedDefaultProject?.name ?? defaultProject.name,
exportedMaskCount: savedDefaultProject?.exportedMaskCount ?? 0,
moduleStyles: buildModuleStyles(defaultProject.stlFiles, savedDefaultProject?.moduleStyles),
},
...customProjects,
],
@@ -1052,6 +1098,28 @@ async function startServer() {
res.json({ ok: true, deletedId: deleted.id });
});
app.patch('/api/projects/:projectId/module-styles', (req, res) => {
const incoming = req.body?.moduleStyles;
if (!incoming || typeof incoming !== 'object' || Array.isArray(incoming)) {
res.status(400).json({ message: '构件样式数据无效' });
return;
}
const state = readState();
const project = findProject(state, req.params.projectId);
if (!project) {
res.status(404).json({ message: '项目不存在' });
return;
}
project.moduleStyles = buildModuleStyles(project.stlFiles, {
...(project.moduleStyles ?? {}),
...(incoming as Record<string, Partial<ModuleStyleRecord>>),
});
writeState(state);
res.json(project);
});
app.get('/api/projects/:projectId/dicom-preview', (req, res) => {
const project = findProject(readState(), req.params.projectId);
if (!project) {