fix: 上传完成内存泄漏
This commit is contained in:
parent
e5bf5685cf
commit
2cbab89273
|
@ -8,12 +8,11 @@ export const registerDicomHandler = () => {
|
||||||
const dia = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
const dia = await dialog.showOpenDialog({ properties: ["openDirectory"] });
|
||||||
if (dia.canceled) return null;
|
if (dia.canceled) return null;
|
||||||
const dcmPaths = await filterDicoms(dia.filePaths[0]);
|
const dcmPaths = await filterDicoms(dia.filePaths[0]);
|
||||||
await uploadFilesInBatches({
|
uploadFilesInBatches({
|
||||||
filePaths: dcmPaths,
|
filePaths: dcmPaths,
|
||||||
batchSize: 6,
|
batchSize: 6,
|
||||||
feedback: (d) => event.reply("dicom:upload:detail", d),
|
feedback: (d) => event.reply("dicom:upload:detail", d),
|
||||||
});
|
});
|
||||||
event.reply("dicom:upload:finished"); // 重新刷新病人列表
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("dicom:select", async () => {
|
ipcMain.handle("dicom:select", async () => {
|
||||||
|
|
|
@ -108,11 +108,4 @@ export const uploadFilesInBatches = async ({
|
||||||
totalEndTime - totalStartTime
|
totalEndTime - totalStartTime
|
||||||
} ms`
|
} ms`
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
totalSuccess,
|
|
||||||
totalFailed,
|
|
||||||
totalTime: totalEndTime - totalStartTime,
|
|
||||||
progress: 100,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,7 +75,8 @@
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "3.23.8"
|
"zod": "3.23.8",
|
||||||
|
"mitt": "3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
|
|
@ -28,19 +28,23 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { RocketIcon } from "@radix-ui/react-icons";
|
import { RocketIcon } from "@radix-ui/react-icons";
|
||||||
|
import emitter from "@/lib/events";
|
||||||
|
|
||||||
interface ScanProgress {
|
interface UploadProgress {
|
||||||
percentage: number;
|
progress: number;
|
||||||
|
totalSuccess: number;
|
||||||
|
totalFailed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProgress = {
|
|
||||||
percentage: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MenuBar = () => {
|
export const MenuBar = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [progress, setProgress] = useState<ScanProgress>(defaultProgress);
|
const [uploading, setUploading] = useState<UploadProgress>({
|
||||||
|
progress: 0,
|
||||||
|
totalSuccess: 0,
|
||||||
|
totalFailed: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const [inferOption, setInferOption] =
|
const [inferOption, setInferOption] =
|
||||||
useState<inferDeviceType[]>(inferDevices);
|
useState<inferDeviceType[]>(inferDevices);
|
||||||
const [importDialogVisible, setImportDialogVisible] = useState(false);
|
const [importDialogVisible, setImportDialogVisible] = useState(false);
|
||||||
|
@ -48,9 +52,9 @@ export const MenuBar = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUploadFeedback = (
|
const handleUploadFeedback = (
|
||||||
_event: Electron.IpcRendererEvent,
|
_event: Electron.IpcRendererEvent,
|
||||||
data: { progress: number; totalSuccess: number; totalFailed: number }
|
data: UploadProgress
|
||||||
) => {
|
) => {
|
||||||
setProgress({ percentage: data.progress });
|
setUploading(data);
|
||||||
};
|
};
|
||||||
window.ipcRenderer.on("dicom:upload:detail", handleUploadFeedback);
|
window.ipcRenderer.on("dicom:upload:detail", handleUploadFeedback);
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -59,12 +63,20 @@ export const MenuBar = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const visible = ![0, 100].includes(progress.percentage);
|
const p = uploading?.progress;
|
||||||
setImportDialogVisible(visible);
|
if (p > 0 && p < 100 && !importDialogVisible) {
|
||||||
}, [progress.percentage]);
|
setImportDialogVisible(true);
|
||||||
|
} else if (p === 100 && importDialogVisible) {
|
||||||
|
setImportDialogVisible(false);
|
||||||
|
toast({
|
||||||
|
title: "操作",
|
||||||
|
description: `导入完成,成功: ${uploading.totalSuccess},失败: ${uploading.totalFailed}`,
|
||||||
|
});
|
||||||
|
emitter.emit("datasource:fetch");
|
||||||
|
}
|
||||||
|
}, [uploading, importDialogVisible, navigate, toast]);
|
||||||
|
|
||||||
const handleImportDicom = () => {
|
const handleImportDicom = () => {
|
||||||
navigate("datasource");
|
|
||||||
window.ipcRenderer.send("dicom:upload");
|
window.ipcRenderer.send("dicom:upload");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -192,9 +204,9 @@ export const MenuBar = () => {
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Alert>
|
<Alert>
|
||||||
<RocketIcon className="h-4 w-4" />
|
<RocketIcon className="h-4 w-4" />
|
||||||
<AlertTitle>扫描进度</AlertTitle>
|
<AlertTitle>导入进度</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<Progress value={progress?.percentage} />
|
<Progress value={uploading.progress} />
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
5
apps/desktop/src/lib/events.ts
Normal file
5
apps/desktop/src/lib/events.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import mitt from "mitt";
|
||||||
|
|
||||||
|
const emitter = mitt();
|
||||||
|
|
||||||
|
export default emitter;
|
|
@ -13,6 +13,7 @@ import { PatientInfo, SeriesInfo } from "./type";
|
||||||
import { PatientList } from "./PatientList";
|
import { PatientList } from "./PatientList";
|
||||||
import { StudyList } from "./StudyList";
|
import { StudyList } from "./StudyList";
|
||||||
import { SeriesList } from "./SeriesList";
|
import { SeriesList } from "./SeriesList";
|
||||||
|
import emitter from "@/lib/events";
|
||||||
|
|
||||||
export const Datasource = () => {
|
export const Datasource = () => {
|
||||||
const rawPatientsRef = useRef<PatientInfo[]>([]);
|
const rawPatientsRef = useRef<PatientInfo[]>([]);
|
||||||
|
@ -23,24 +24,20 @@ export const Datasource = () => {
|
||||||
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);
|
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const fetchPatients = () => {
|
useEffect(() => {
|
||||||
|
const fetchData = () => {
|
||||||
window.ipcRenderer
|
window.ipcRenderer
|
||||||
.invoke("dicom:select")
|
.invoke("dicom:select")
|
||||||
.then((patients: PatientInfo[]) => {
|
.then((patients: PatientInfo[]) => {
|
||||||
console.log(patients);
|
console.log("patients", patients);
|
||||||
rawPatientsRef.current = patients;
|
rawPatientsRef.current = patients;
|
||||||
setPatients(patients);
|
setPatients(patients);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
fetchData();
|
||||||
useEffect(() => {
|
emitter.on("datasource:fetch", fetchData);
|
||||||
fetchPatients();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.ipcRenderer.on("dicom:upload:finished", fetchPatients);
|
|
||||||
return () => {
|
return () => {
|
||||||
window.ipcRenderer.off("dicom:upload:finished", fetchPatients);
|
emitter.off("datasource:fetch", fetchData);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,14 @@ import {
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { ResetIcon } from "@radix-ui/react-icons";
|
||||||
import { MoonIcon, PersonStanding } from "lucide-react";
|
import { MoonIcon, PersonStanding } from "lucide-react";
|
||||||
|
|
||||||
export const ToolBarMenu = () => {
|
export interface ToolBarMenuProps {
|
||||||
|
onToolButtonClick?: (key: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ToolBarMenu = (props: ToolBarMenuProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
@ -34,6 +39,22 @@ export const ToolBarMenu = () => {
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => props.onToolButtonClick?.("reset")}
|
||||||
|
>
|
||||||
|
<ResetIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
<p>复原</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IStackViewport,
|
IStackViewport,
|
||||||
|
IVolumeViewport,
|
||||||
PublicViewportInput,
|
PublicViewportInput,
|
||||||
} from "@cornerstonejs/core/dist/types/types";
|
} from "@cornerstonejs/core/dist/types/types";
|
||||||
import {
|
import {
|
||||||
|
@ -253,10 +254,19 @@ export const Viewer = () => {
|
||||||
}
|
}
|
||||||
}, [index]);
|
}, [index]);
|
||||||
|
|
||||||
|
const onToolMenuClick = (key: string) => {
|
||||||
|
const engine = renderingEngineRef.current;
|
||||||
|
if (key === "reset" && engine) {
|
||||||
|
const viewport = engine.getViewport(viewportIds[0]) as IVolumeViewport;
|
||||||
|
viewport.resetCamera(true, true, true, true, false);
|
||||||
|
viewport.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col">
|
<div className="w-full h-full flex flex-col">
|
||||||
<div className="flex-shrink-0 border-b border-secondary">
|
<div className="flex-shrink-0 border-b border-secondary">
|
||||||
<ToolBarMenu />
|
<ToolBarMenu onToolButtonClick={onToolMenuClick} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
|
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
|
||||||
|
|
|
@ -151,6 +151,9 @@ importers:
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.408.0
|
specifier: ^0.408.0
|
||||||
version: 0.408.0(react@18.3.1)
|
version: 0.408.0(react@18.3.1)
|
||||||
|
mitt:
|
||||||
|
specifier: 3.0.1
|
||||||
|
version: 3.0.1
|
||||||
node-machine-id:
|
node-machine-id:
|
||||||
specifier: 1.1.12
|
specifier: 1.1.12
|
||||||
version: 1.1.12
|
version: 1.1.12
|
||||||
|
@ -4034,6 +4037,9 @@ packages:
|
||||||
resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
|
resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
mkdirp-classic@0.5.3:
|
mkdirp-classic@0.5.3:
|
||||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||||
|
|
||||||
|
@ -9845,6 +9851,8 @@ snapshots:
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
rimraf: 5.0.10
|
rimraf: 5.0.10
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
mkdirp-classic@0.5.3: {}
|
mkdirp-classic@0.5.3: {}
|
||||||
|
|
||||||
mkdirp@1.0.4: {}
|
mkdirp@1.0.4: {}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user