From e25ff341cd5316d81231139e690de697bd75e701 Mon Sep 17 00:00:00 2001 From: mozzie Date: Wed, 11 Sep 2024 21:59:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/electron/core/pacs.type.ts | 26 +++-- .../src/pages/Datasource/CarouselSeries.tsx | 103 ++++++++++++++++++ apps/desktop/src/pages/Datasource/index.tsx | 60 ++++++++-- apps/desktop/src/pages/Datasource/type.ts | 30 +++-- 4 files changed, 185 insertions(+), 34 deletions(-) create mode 100644 apps/desktop/src/pages/Datasource/CarouselSeries.tsx diff --git a/apps/desktop/electron/core/pacs.type.ts b/apps/desktop/electron/core/pacs.type.ts index 8ef1593..545a693 100644 --- a/apps/desktop/electron/core/pacs.type.ts +++ b/apps/desktop/electron/core/pacs.type.ts @@ -18,10 +18,11 @@ export interface StudyInfo { MainDicomTags: { AccessionNumber: string; StudyDate: string; - StudyDescription: string; + ReferringPhysicianName: string; StudyID: string; StudyInstanceUID: string; StudyTime: string; + InstitutionName: string }; ParentPatient: string; Series: string[]; @@ -34,16 +35,19 @@ export interface SeriesInfo { ID: string; Instances: string[]; MainDicomTags: { - Manufacturer: string; - Modality: string; - NumberOfSlices: string; - ProtocolName: string; - SeriesDate: string; - SeriesDescription: string; - SeriesInstanceUID: string; - SeriesNumber: string; - SeriesTime: string; - StationName: string; + ContrastBolusAgent: string; // 对比剂类型,用于增强成像质量 + ImageOrientationPatient: string; // 患者图像的方向参数 + Manufacturer: string; // 设备制造商 + Modality: string; // 医疗成像的模态类型,如 CT、MRI 等 + OperatorsName: string; // 操作者的名称 + SeriesDate: string; // 成像系列的日期 + SeriesInstanceUID: string; // 成像系列的唯一标识符 + SeriesNumber: number; // 成像系列的编号 + SeriesTime: string; // 成像系列的具体时间 + SeriesDescription: string // 描述 + BodyPartExamined: string // 检查的身体部位 + ProtocolName: string // 成像协议 + StationName: string // 成像操作的设备站点 }; ParentStudy: string; Status: string; diff --git a/apps/desktop/src/pages/Datasource/CarouselSeries.tsx b/apps/desktop/src/pages/Datasource/CarouselSeries.tsx new file mode 100644 index 0000000..9198c33 --- /dev/null +++ b/apps/desktop/src/pages/Datasource/CarouselSeries.tsx @@ -0,0 +1,103 @@ +import { Card } from "@/components/ui/card" +import { + Carousel, + CarouselContent, + CarouselItem, + type CarouselApi, +} from "@/components/ui/carousel" +import { EllipsisIcon, FileDown, Rotate3DIcon, Sparkles, Torus } from "lucide-react"; +import { useEffect, useState } from "react" +import { SeriesInfo } from "./type" +import { Badge } from "@/components/ui/badge" +import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuShortcut, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" + +export type CarouselAction = 'viewMpr' | 'view3D' | 'exportMeaurement' | 'viewReport' + +interface CarouselSeriesProps { + seriesList: SeriesInfo[] + onClickItem?: (series: SeriesInfo, action: CarouselAction) => void +} + +export function CarouselSeries(props: CarouselSeriesProps) { + const [api, setApi] = useState() + const [current, setCurrent] = useState(0) + const [count, setCount] = useState(0) + + useEffect(() => { + if (!api) return + + setCount(api.scrollSnapList().length) + setCurrent(api.selectedScrollSnap() + 1) + + api.on("select", () => { + setCurrent(api.selectedScrollSnap() + 1) + }) + }, [api]) + + + + return ( +
+ + + {props.seriesList.map((series) => ( + + +
+
+
+
+ 序号 {series.MainDicomTags.SeriesNumber} +
+
+
+ + + + + + + props.onClickItem?.(series, 'viewMpr')}> + MPR 阅片 + + + props.onClickItem?.(series, 'view3D')}> + 3D 重建 + + + props.onClickItem?.(series, 'exportMeaurement')}> + 导出测量 + + + props.onClickItem?.(series, 'viewReport')}> + 查看报告 + + + + + +
+
+
+ {series.MainDicomTags.SeriesDescription} +
+
+
+
+ {series.Instances.length} + {series.MainDicomTags.BodyPartExamined && {series.MainDicomTags.BodyPartExamined}} +
+
+
+
+ ))} +
+
+
+ {current} / {count} +
+
+ ) +} diff --git a/apps/desktop/src/pages/Datasource/index.tsx b/apps/desktop/src/pages/Datasource/index.tsx index 9d532be..9c721be 100644 --- a/apps/desktop/src/pages/Datasource/index.tsx +++ b/apps/desktop/src/pages/Datasource/index.tsx @@ -7,21 +7,25 @@ import { } from "@/components/ui/resizable"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Card } from "@/components/ui/card"; -import { PatientInfo } from "./type"; +import { PatientInfo, SeriesInfo } from "./type"; +import { CarouselAction, CarouselSeries } from "./CarouselSeries"; + export const Datasource = () => { - const [patients, setPatients] = useState< - (PatientInfo & { active: boolean })[] - >([]); + const [patients, setPatients] = useState([]); useEffect(() => { window.ipcRenderer .invoke("dicom:select") .then((patients: PatientInfo[]) => { console.log(patients); - setPatients(patients.map((p) => ({ ...p, active: false }))); + setPatients(patients.map((p) => ({ ...p, active: false, children: p.children.map(s => ({ ...s, active: false })) }))); }); }, []); + const onClickSeriesItem = (series: SeriesInfo, action: CarouselAction) => { + console.log(action, series) + } + return ( { p.map((i) => ({ ...i, active: i.ID === patient.ID })) ) } - className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${ - patient.active ? "bg-accent" : "" - }`} + className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${patient.active ? "bg-accent" : "" + }`} >
@@ -75,12 +78,47 @@ export const Datasource = () => { - 123 +
+ + {patients.find(p => p.active) && patients.find(p => p.active)?.children.map(study => + setPatients((p) => + p.map((i) => ({ ...i, children: i.children.map(s => ({ ...s, active: s.ID === study.ID })) })) + ) + } + className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${study.active ? "bg-accent" : ""}`} + > +
+
+
+
+ {study.MainDicomTags.StudyID} +
+
+
+ {study.MainDicomTags.StudyDate} +
+
+
+
+ {study.MainDicomTags.InstitutionName} +
+
+ +
+
)} +
+
- 33 + +
+ 123 +
+
- + ); }; diff --git a/apps/desktop/src/pages/Datasource/type.ts b/apps/desktop/src/pages/Datasource/type.ts index 8ef1593..8ca4f96 100644 --- a/apps/desktop/src/pages/Datasource/type.ts +++ b/apps/desktop/src/pages/Datasource/type.ts @@ -11,6 +11,7 @@ export interface PatientInfo { Studies: string[]; Type: string; children: StudyInfo[]; + active: boolean } export interface StudyInfo { @@ -18,14 +19,16 @@ export interface StudyInfo { MainDicomTags: { AccessionNumber: string; StudyDate: string; - StudyDescription: string; + ReferringPhysicianName: string; StudyID: string; StudyInstanceUID: string; StudyTime: string; + InstitutionName: string }; ParentPatient: string; Series: string[]; Type: string; + active: boolean children: SeriesInfo[]; } @@ -34,17 +37,20 @@ export interface SeriesInfo { ID: string; Instances: string[]; MainDicomTags: { - Manufacturer: string; - Modality: string; - NumberOfSlices: string; - ProtocolName: string; - SeriesDate: string; - SeriesDescription: string; - SeriesInstanceUID: string; - SeriesNumber: string; - SeriesTime: string; - StationName: string; - }; + ContrastBolusAgent: string; // 对比剂类型,用于增强成像质量 + ImageOrientationPatient: string; // 患者图像的方向参数 + Manufacturer: string; // 设备制造商 + Modality: string; // 医疗成像的模态类型,如 CT、MRI 等 + OperatorsName: string; // 操作者的名称 + SeriesDate: string; // 成像系列的日期 + SeriesInstanceUID: string; // 成像系列的唯一标识符 + SeriesNumber: number; // 成像系列的编号 + SeriesTime: string; // 成像系列的具体时间 + SeriesDescription: string // 描述 + BodyPartExamined: string // 检查的身体部位 + ProtocolName: string // 成像协议 + StationName: string // 成像操作的设备站点 + } ParentStudy: string; Status: string; Type: string;