feat: 下载dicom
This commit is contained in:
parent
d63eb1a5af
commit
c6834cb4c4
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -1,35 +1,74 @@
|
|||
import { TableColumnsType } from "antd";
|
||||
import { DataItemType } from ".";
|
||||
import { SeriesItemType, StudyItemType } from ".";
|
||||
|
||||
export const columnsForStudy: TableColumnsType<DataItemType> = [
|
||||
export const columnsForStudy: TableColumnsType<StudyItemType> = [
|
||||
{ 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<DataItemType> = [
|
||||
{ title: "序列号", dataIndex: "SeriesNumber", key: "SeriesNumber" },
|
||||
{
|
||||
title: "成像设备",
|
||||
dataIndex: "Modality",
|
||||
key: "Modality",
|
||||
},
|
||||
export const columnsForSeries: TableColumnsType<SeriesItemType> = [
|
||||
{
|
||||
title: "序列描述",
|
||||
dataIndex: "SeriesDescription",
|
||||
key: "SeriesDescription",
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div>
|
||||
<span>采集体位</span>
|
||||
</div>
|
||||
),
|
||||
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</>;
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
|
|
@ -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<DataItemType[]>([]);
|
||||
const [dataSource, setDataSource] = useState<StudyItemType[]>([]);
|
||||
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 (
|
||||
<Table
|
||||
rowKey="SeriesInstanceUID"
|
||||
|
@ -55,16 +73,22 @@ export const DicomList = (props: DicomListProps) => {
|
|||
{
|
||||
title: "操作",
|
||||
dataIndex: "operation",
|
||||
render: (_: any, recordSeries: DataItemType) => (
|
||||
render: (_: any, recordSeries: SeriesItemType) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => onViewDicom(record, recordSeries)}
|
||||
>
|
||||
阅片
|
||||
</Button>
|
||||
<Tooltip title="在线阅片">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DesktopOutlined />}
|
||||
onClick={() => onViewDicom(record, recordSeries)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="下载原始影像 .zip文件">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FileZipOutlined />}
|
||||
onClick={() => onDownloadDicom(recordSeries)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
@ -75,17 +99,61 @@ export const DicomList = (props: DicomListProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const onViewDicom = (record: DataItemType, recordSeries: DataItemType) => {
|
||||
const onViewDicom = (record: StudyItemType, recordSeries: SeriesItemType) => {
|
||||
const { StudyInstanceUID } = record;
|
||||
const { SeriesInstanceUID } = recordSeries;
|
||||
openOHIFViewer(StudyInstanceUID, SeriesInstanceUID);
|
||||
};
|
||||
|
||||
const onDownloadDicom = ({ ID, Type }: SeriesItemType | StudyItemType) => {
|
||||
setLoading(true);
|
||||
dicomDomainService.downloadDicom({ ID, Type }).finally(() => {
|
||||
messageApi.success("数据下载成功");
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{contextHolder}
|
||||
{loading && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
background: "rgb(255 195 195 / 30%)",
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 19940121,
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)}
|
||||
<Table
|
||||
dataSource={dataSource}
|
||||
columns={columnsForStudy}
|
||||
columns={[
|
||||
...columnsForStudy,
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "operation",
|
||||
render: (_: any, record: StudyItemType) => (
|
||||
<Space>
|
||||
<Tooltip title="下载原始影像 .zip文件">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FileZipOutlined />}
|
||||
onClick={() => onDownloadDicom(record)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
loading={tableLoading}
|
||||
expandable={{
|
||||
expandedRowRender: expandedRowRenderForSeries,
|
||||
|
|
|
@ -7,4 +7,4 @@ NACOS_NAMESPACE=56a3b295-f319-4ced-82b5-0df2e98cc541
|
|||
NACOS_DATAID='test'
|
||||
NACOS_GROUP='DEFAULT_GROUP'
|
||||
# dicom配置,可以后期转移到nacos配置中心
|
||||
PACS_URL=http://localhost:8042/dicom-web/
|
||||
PACS_URL=http://localhost:8042
|
|
@ -27,6 +27,47 @@ const metaFormatter = (data) => {
|
|||
};
|
||||
};
|
||||
|
||||
type StudyResourceType = {
|
||||
ID: string;
|
||||
MainDicomTags: {
|
||||
AccessionNumber: string;
|
||||
StudyDate: string;
|
||||
StudyDescription: string;
|
||||
StudyID: string;
|
||||
StudyInstanceUID: string;
|
||||
StudyTime: string;
|
||||
};
|
||||
PatientMainDicomTags: {
|
||||
PatientBirthDate: string;
|
||||
PatientID: string;
|
||||
PatientName: string;
|
||||
PatientSex: string;
|
||||
};
|
||||
Series: string[];
|
||||
Type: 'Study';
|
||||
};
|
||||
|
||||
type SeriesResourceType = {
|
||||
ExpectedNumberOfInstances: number;
|
||||
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;
|
||||
};
|
||||
ParentStudy: string;
|
||||
Status: string;
|
||||
Type: 'Series';
|
||||
};
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(
|
||||
|
@ -38,19 +79,52 @@ export class AppController {
|
|||
async findDicoms() {
|
||||
const pacsUrl = this.configService.get('PACS_URL');
|
||||
try {
|
||||
const { data: studyData } = await axios.get(`${pacsUrl}/studies`);
|
||||
// 全部的study pacs uuid
|
||||
const { data: study_uuids } = await axios.get(`${pacsUrl}/studies`);
|
||||
const result = [];
|
||||
for (const study of studyData) {
|
||||
const studyItem = metaFormatter(study);
|
||||
const { data: seriesData } = await axios.get(
|
||||
`${pacsUrl}/studies/${studyItem.StudyInstanceUID}/series`,
|
||||
);
|
||||
const subs = seriesData.map((s) => metaFormatter(s));
|
||||
result.push({ ...studyItem, subs });
|
||||
// study
|
||||
for (const study_uuid of study_uuids) {
|
||||
const studyUrl = `${pacsUrl}/studies/${study_uuid}`;
|
||||
const { data: studyResource } = await axios.get(studyUrl);
|
||||
const { Series, PatientMainDicomTags, MainDicomTags, ID, Type } =
|
||||
studyResource as StudyResourceType;
|
||||
const subs = [];
|
||||
// series + 第一张 instance 的 tags
|
||||
for (const series_uuid of Series) {
|
||||
const seriesUrl = `${pacsUrl}/series/${series_uuid}`;
|
||||
const { data: seriesResource } = await axios.get(seriesUrl);
|
||||
const { MainDicomTags, Instances, ID, Type } =
|
||||
seriesResource as SeriesResourceType;
|
||||
// 取第一张instance
|
||||
const InstanceNumber = Instances.length;
|
||||
const firstInstanceUrl = `${pacsUrl}/instances/${Instances[0]}/simplified-tags`;
|
||||
const tags =
|
||||
Instances.length > 0
|
||||
? await axios.get(firstInstanceUrl).then((d) => d.data)
|
||||
: {};
|
||||
subs.push({ ...MainDicomTags, Type, InstanceNumber, ID, tags });
|
||||
}
|
||||
result.push({
|
||||
...PatientMainDicomTags,
|
||||
...MainDicomTags,
|
||||
Type,
|
||||
ID,
|
||||
subs,
|
||||
});
|
||||
}
|
||||
return { data: result };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch DICOM data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@EventPattern({ cmd: 'dicom.archive.url' })
|
||||
async archiveUrl({ ID, Type }: { ID: string; Type: string }) {
|
||||
const pacsUrl = this.configService.get('PACS_URL');
|
||||
const mapping = {
|
||||
study: 'studies',
|
||||
series: 'series',
|
||||
};
|
||||
return `${pacsUrl}/${mapping[Type.toLowerCase()]}/${ID}/archive`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"class-validator": "0.14.0",
|
||||
"class-transformer": "0.5.1",
|
||||
"uuid": "9.0.0",
|
||||
"dayjs": "1.11.9"
|
||||
"dayjs": "1.11.9",
|
||||
"axios": "1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ForbiddenExceptionFilter } from './filter/forbid.filter';
|
|||
import { AuthController } from './auth/auth.controller';
|
||||
import { AdminModule } from './admin/admin.module';
|
||||
import { AnnotatorModule } from './annotator/annotator.module';
|
||||
import { DicomModule } from './dicom/dicom.module';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
|
||||
@Module({
|
||||
|
@ -30,6 +31,7 @@ import * as cookieParser from 'cookie-parser';
|
|||
]),
|
||||
AdminModule,
|
||||
AnnotatorModule,
|
||||
DicomModule,
|
||||
],
|
||||
controllers: [AppController, AuthController],
|
||||
providers: [
|
||||
|
|
28
apps/services/dmp/gateway/src/dicom/dicom.controller.ts
Normal file
28
apps/services/dmp/gateway/src/dicom/dicom.controller.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Body, Controller, Get, Inject, Param, Post, Res } from '@nestjs/common';
|
||||
import { ClientProxy } from '@nestjs/microservices';
|
||||
import { Response } from 'express';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import axios from 'axios';
|
||||
|
||||
@Controller('dicom')
|
||||
export class DicomController {
|
||||
constructor(@Inject('Client') private client: ClientProxy) {}
|
||||
|
||||
@Post('download')
|
||||
async downloadDicom(@Body() body, @Res() res: Response) {
|
||||
const { ID, Type } = body;
|
||||
const url = await firstValueFrom(
|
||||
this.client.send({ cmd: 'dicom.archive.url' }, { ID, Type }),
|
||||
);
|
||||
try {
|
||||
const { data: dataStream } = await axios.get(url, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
res.setHeader('Content-Name', `${ID}.zip`);
|
||||
res.setHeader('Content-Type', 'application/zip');
|
||||
dataStream.pipe(res);
|
||||
} catch (error) {
|
||||
return { code: 1, msg: error };
|
||||
}
|
||||
}
|
||||
}
|
21
apps/services/dmp/gateway/src/dicom/dicom.module.ts
Normal file
21
apps/services/dmp/gateway/src/dicom/dicom.module.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { DicomController } from './dicom.controller';
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.register([
|
||||
{
|
||||
name: 'Client',
|
||||
transport: Transport.NATS,
|
||||
options: {
|
||||
servers: ['nats://localhost:4222'],
|
||||
maxReconnectAttempts: 5,
|
||||
reconnectTimeWait: 1000,
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
controllers: [DicomController],
|
||||
})
|
||||
export class DicomModule {}
|
|
@ -258,6 +258,9 @@ importers:
|
|||
dicom-parser:
|
||||
specifier: 1.8.21
|
||||
version: 1.8.21
|
||||
file-saver:
|
||||
specifier: 2.0.5
|
||||
version: registry.npmmirror.com/file-saver@2.0.5
|
||||
mobx:
|
||||
specifier: 6.9.0
|
||||
version: 6.9.0
|
||||
|
@ -283,6 +286,9 @@ importers:
|
|||
specifier: 6.14.2
|
||||
version: 6.14.2(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/file-saver':
|
||||
specifier: 2.0.5
|
||||
version: registry.npmmirror.com/@types/file-saver@2.0.5
|
||||
'@types/react':
|
||||
specifier: 18.2.14
|
||||
version: 18.2.14
|
||||
|
@ -1073,6 +1079,9 @@ importers:
|
|||
'@nestjs/typeorm':
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0(@nestjs/common@10.1.0)(@nestjs/core@10.1.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17)
|
||||
axios:
|
||||
specifier: 1.5.0
|
||||
version: registry.npmmirror.com/axios@1.5.0
|
||||
class-transformer:
|
||||
specifier: 0.5.1
|
||||
version: 0.5.1
|
||||
|
@ -12778,6 +12787,12 @@ packages:
|
|||
'@types/serve-static': registry.npmmirror.com/@types/serve-static@1.15.2
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@types/file-saver@2.0.5:
|
||||
resolution: {integrity: sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.5.tgz}
|
||||
name: '@types/file-saver'
|
||||
version: 2.0.5
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@types/http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.1.tgz}
|
||||
name: '@types/http-errors'
|
||||
|
@ -14869,6 +14884,12 @@ packages:
|
|||
flat-cache: registry.npmmirror.com/flat-cache@3.0.4
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/file-saver@2.0.5:
|
||||
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz}
|
||||
name: file-saver
|
||||
version: 2.0.5
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/fill-range@7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz}
|
||||
name: fill-range
|
||||
|
|
Loading…
Reference in New Issue
Block a user