2026-05-20-22-07-46 导出命名与映射视图摘要优化

This commit is contained in:
2026-05-20 22:19:02 +08:00
parent cc137437bc
commit ec4cb1eae7
7 changed files with 336 additions and 87 deletions

View File

@@ -159,6 +159,36 @@ function now() {
return new Date().toISOString();
}
function timestampForFilename(date = new Date()) {
const parts = new Intl.DateTimeFormat('sv-SE', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}).formatToParts(date);
const value = (type: string) => parts.find((part) => part.type === type)?.value ?? '00';
return `${value('year')}-${value('month')}-${value('day')}-${value('hour')}-${value('minute')}-${value('second')}`;
}
function sanitizeFilenamePart(input: string, fallback: string) {
const cleaned = input
.trim()
.replace(/[\\/:*?"<>|]+/g, '_')
.replace(/\s+/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
return cleaned || fallback;
}
function contentDispositionAttachment(filename: string) {
const asciiFallback = filename.replace(/[^\x20-\x7e]/g, '_').replace(/["\\]/g, '_');
return `attachment; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
}
function ensureDir(dir: string) {
fs.mkdirSync(dir, { recursive: true });
}
@@ -1247,6 +1277,7 @@ function createProjectExportBundle({
compressed,
activePose,
segmentationScope,
exportRoot,
}: {
project: ProjectRecord;
files: string[];
@@ -1254,12 +1285,12 @@ function createProjectExportBundle({
compressed: boolean;
activePose?: ModelPoseValue;
segmentationScope: SegmentationExportScope;
exportRoot: string;
}) {
const entries: Array<{ name: string; data: Buffer; mtime?: number }> = [];
const needsVolume = targets.includes('dicom') || targets.includes('segmentation');
const volume = needsVolume ? readDicomHuVolume(files) : null;
const format = compressed ? 'nii.gz' : 'nii';
const exportRoot = `${project.id}-nifti-export`;
if (targets.includes('dicom') && volume) {
entries.push({
@@ -2488,6 +2519,7 @@ async function startServer() {
try {
const files = getProjectDicomFiles(project);
const exportBase = `${sanitizeFilenamePart(project.name, project.id)}_${timestampForFilename()}`;
const payload = createProjectExportBundle({
project: exportProject,
files,
@@ -2495,14 +2527,15 @@ async function startServer() {
compressed,
activePose,
segmentationScope,
exportRoot: exportBase,
});
const filename = `${project.id}-nifti-export.tar.gz`;
const filename = `${exportBase}.tar.gz`;
fs.writeFileSync(path.join(exportDir, filename), payload);
project.exportedMaskCount += targets.includes('segmentation') ? 1 : 0;
writeState(state);
res.setHeader('Content-Type', 'application/gzip');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.setHeader('Content-Disposition', contentDispositionAttachment(filename));
res.send(payload);
} catch (error) {
res.status(422).json({ message: error instanceof Error ? error.message : '导出包生成失败' });