diff --git a/apps/dmp/core/domain/Dicom/DicomRepository.ts b/apps/dmp/core/domain/Dicom/DicomRepository.ts index 2af6036..85f8e89 100644 --- a/apps/dmp/core/domain/Dicom/DicomRepository.ts +++ b/apps/dmp/core/domain/Dicom/DicomRepository.ts @@ -1,4 +1,4 @@ -import { Apis, ExistInPacsDTO } from "@@/infra/api"; +import { Apis, DownloadArchiveDTO, ExistInPacsDTO } from "@@/infra/api"; export class DicomRepository { async upload2Pacs(dcmFile: File) { @@ -10,4 +10,8 @@ export class DicomRepository { async existInPacs(p: ExistInPacsDTO) { return Apis.existInPacs(p); } + + async downloadDicom(p: DownloadArchiveDTO) { + return Apis.downloadDicom(p); + } } diff --git a/apps/dmp/core/domain/Dicom/DicomService.ts b/apps/dmp/core/domain/Dicom/DicomService.ts index d681b45..8f1dca1 100644 --- a/apps/dmp/core/domain/Dicom/DicomService.ts +++ b/apps/dmp/core/domain/Dicom/DicomService.ts @@ -1,4 +1,4 @@ -import { ExistInPacsDTO } from "@@/infra/api"; +import { DownloadArchiveDTO, ExistInPacsDTO } from "@@/infra/api"; import { DicomRepository } from "./DicomRepository"; export class DicomService { @@ -17,4 +17,8 @@ export class DicomService { async existInPacs(p: ExistInPacsDTO) { return await this.dicomRepository.existInPacs(p); } + + async downloadDicom(p: DownloadArchiveDTO) { + return await this.dicomRepository.downloadDicom(p); + } } diff --git a/apps/dmp/core/infra/api/index.ts b/apps/dmp/core/infra/api/index.ts index 52a4583..970de52 100644 --- a/apps/dmp/core/infra/api/index.ts +++ b/apps/dmp/core/infra/api/index.ts @@ -1,6 +1,8 @@ import { User } from "@@/domain/User/entities/User"; import { Request } from "./Request"; import { Study } from "@/modules/Admin/Dicom/Upload/DicomUploader/util"; +import axios from "axios"; +import { saveAs } from "file-saver"; const PREFIX = "/api/dmp"; const PREFIX_CERT = "/cert"; @@ -31,6 +33,11 @@ export type ArchiveTaskCreateDto = { study: Study[]; }; +export type DownloadArchiveDTO = { + ID: string; + Type: "study" | "series" | "Study" | "Series"; +}; + export const Apis = { /** * 用户登录 @@ -80,4 +87,20 @@ export const Apis = { Request.get(PREFIX + "/annotator/find/archiveTask"), findDicoms: (): ResponseType => Request.get(PREFIX + "/admin/find/dicom/all"), + + /** + * 下载dicom zip + * + */ + downloadDicom: (p: DownloadArchiveDTO) => + axios + .post(PREFIX + `/dicom/download`, p, { responseType: "blob" }) + .then((response) => { + const filename = response.headers["content-name"]; + const blob = new Blob([response.data], { type: "application/zip" }); + saveAs(blob, filename); + }) + .catch((error) => { + console.error("Download error:", error); + }), }; diff --git a/apps/dmp/package.json b/apps/dmp/package.json index 9064ccb..e827abd 100644 --- a/apps/dmp/package.json +++ b/apps/dmp/package.json @@ -22,7 +22,8 @@ "@ant-design/icons": "5.2.5", "dicom-parser": "1.8.21", "path-to-regexp": "6.2.1", - "@tavi/util": "workspace:*" + "@tavi/util": "workspace:*", + "file-saver": "2.0.5" }, "devDependencies": { "@types/react": "18.2.14", @@ -36,6 +37,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", "typescript": "^5.0.2", - "vite": "^4.4.0" + "vite": "^4.4.0", + "@types/file-saver": "2.0.5" } } \ No newline at end of file diff --git a/apps/dmp/src/modules/Admin/Dicom/List/columns.tsx b/apps/dmp/src/modules/Admin/Dicom/List/columns.tsx index 3032a1c..34ff424 100644 --- a/apps/dmp/src/modules/Admin/Dicom/List/columns.tsx +++ b/apps/dmp/src/modules/Admin/Dicom/List/columns.tsx @@ -1,35 +1,74 @@ import { TableColumnsType } from "antd"; -import { DataItemType } from "."; +import { SeriesItemType, StudyItemType } from "."; -export const columnsForStudy: TableColumnsType = [ +export const columnsForStudy: TableColumnsType = [ { title: "PatientID", dataIndex: "PatientID", key: "PatientID" }, { title: "患者姓名", dataIndex: "PatientName", key: "PatientName" }, { title: "性别", dataIndex: "PatientSex", key: "PatientSex" }, { title: "出生日期", dataIndex: "PatientBirthDate", key: "PatientBirthDate" }, { title: "病例日期", dataIndex: "StudyDate", key: "StudyDate" }, - { - title: "相关序列数", - dataIndex: "NumberPatientRelatedInstances", - key: "NumberPatientRelatedInstances", - }, + { title: "机构", dataIndex: "InstitutionName", key: "InstitutionName" }, ]; -export const columnsForSeries: TableColumnsType = [ - { title: "序列号", dataIndex: "SeriesNumber", key: "SeriesNumber" }, - { - title: "成像设备", - dataIndex: "Modality", - key: "Modality", - }, +export const columnsForSeries: TableColumnsType = [ { title: "序列描述", dataIndex: "SeriesDescription", key: "SeriesDescription", }, + { + title: ( +
+ 采集体位 +
+ ), + key: "PatientPosition", + render: (_, record: SeriesItemType) => { + return <>{record.tags.PatientPosition}; + }, + }, + { + title: "检查区域", + key: "BodyPartExamined", + render: (_, record: SeriesItemType) => { + return <>{record.tags.BodyPartExamined}; + }, + }, { - title: "切片数", - dataIndex: "NumberSeriesRelatedInstances", - key: "NumberSeriesRelatedInstances", + title: "切片数量", + dataIndex: "InstanceNumber", + key: "InstanceNumber", }, + { + title: "层厚", + key: "SliceThickness", + render: (_, record: SeriesItemType) => { + return <>{record.tags.SliceThickness}; + }, + }, + { + title: "制造商", + dataIndex: "Manufacturer", + key: "Manufacturer", + }, + { + title: "成像方式", + dataIndex: "Modality", + key: "Modality", + }, + // { + // title: "焦点大小", + // key: "FocalSpots", + // render: (_, record: SeriesItemType) => { + // return <>{record.tags.FocalSpots}; + // }, + // }, + // { + // title: "图像位数", + // key: "BitsStored", + // render: (_, record: SeriesItemType) => { + // return <>{record.tags.BitsStored} bit; + // }, + // }, ]; diff --git a/apps/dmp/src/modules/Admin/Dicom/List/index.tsx b/apps/dmp/src/modules/Admin/Dicom/List/index.tsx index d7ea776..239c02f 100644 --- a/apps/dmp/src/modules/Admin/Dicom/List/index.tsx +++ b/apps/dmp/src/modules/Admin/Dicom/List/index.tsx @@ -1,44 +1,62 @@ import { useDomain } from "@/hook/useDomain"; -import { Button, Space, Table } from "antd"; +import { Button, Space, Spin, Table, Tooltip, message } from "antd"; import { useEffect, useState } from "react"; import { columnsForStudy, columnsForSeries } from "./columns"; -import { EyeOutlined } from "@ant-design/icons"; +import { DesktopOutlined, FileZipOutlined } from "@ant-design/icons"; import { openOHIFViewer } from "../Upload/util"; interface DicomListProps { children?: JSX.Element; } -export type DataItemType = { - StudyDate: string; - StudyTime: string; - AccessionNumber: string; +export type SeriesItemType = { + BodyPartExamined: string; + ContrastBolusAgent: string; + ID: string; + ImageOrientationPatient: string; + Instances: string[]; + Manufacturer: string; Modality: string; - PatientName: string; - PatientID: string; - PatientBirthDate: string; - PatientSex: string; - StudyInstanceUID: string; - SeriesInstanceUID: string; - CharacterSet: string; + OperatorsName: string; + ProtocolName: string; + SeriesDate: string; SeriesDescription: string; + SeriesInstanceUID: string; + SeriesNumber: string; + SeriesTime: string; + StationName: string; + Type: "study"; + tags: any; +}; + +export type StudyItemType = { + AccessionNumber: string; + ID: string; + InstitutionName: string; + PatientBirthDate: string; + PatientID: string; + PatientName: string; + PatientSex: string; + ReferringPhysicianName: string; + StudyDate: string; StudyID: string; - SeriesNumber: string | number; - NumberSeriesRelatedInstances: string | number; - NumberPatientRelatedInstances: string | number; - NumberPatientRelatedSeries: string | number; - subs: DataItemType[]; + StudyInstanceUID: string; + StudyTime: string; + Type: "series"; + subs: SeriesItemType[]; }; export const DicomList = (props: DicomListProps) => { - const [dataSource, setDataSource] = useState([]); + const [dataSource, setDataSource] = useState([]); const [tableLoading, setTableLoading] = useState(false); - const { userDomainService } = useDomain(); + const [loading, setLoading] = useState(false); + const [messageApi, contextHolder] = message.useMessage(); + const { userDomainService, dicomDomainService } = useDomain(); useEffect(() => { setTableLoading(true); userDomainService.findDicoms().then((result) => { const { data } = result; - setDataSource(data as DataItemType[]); + setDataSource(data as StudyItemType[]); setTableLoading(false); }); }, []); @@ -46,7 +64,7 @@ export const DicomList = (props: DicomListProps) => { /** * 序列级别 */ - const expandedRowRenderForSeries = (record: DataItemType) => { + const expandedRowRenderForSeries = (record: StudyItemType) => { return ( { { title: "操作", dataIndex: "operation", - render: (_: any, recordSeries: DataItemType) => ( + render: (_: any, recordSeries: SeriesItemType) => ( - + +
( + + +