feat: 上传全部影像

This commit is contained in:
mozzie 2023-09-01 16:36:58 +08:00
parent 184d84726e
commit a84bfb028a
26 changed files with 213 additions and 42 deletions

View File

@ -108,7 +108,7 @@ export class RbacService {
name: r.name,
}));
const user = {
id: Math.max(...this.userList.map((u) => u.id!)),
id: Date.now(),
roles: roleMapping.filter((r) => roleIds?.includes(r.id!)),
isEnabled: true,
username,

View File

@ -49,7 +49,7 @@ export const CreateUserForm = (props: CreateUserFormProps) => {
<Form.Item name="phoneNumber" rules={[{ required: true }]}>
<Input placeholder="手机号" />
</Form.Item>
<Form.Item name="roleIds" style={{ minWidth: 120 }}>
<Form.Item name="roleIds" style={{ minWidth: 220 }}>
<Select
placeholder="角色,可多选"
allowClear

View File

@ -64,7 +64,6 @@ export const UserTableList: React.FC<UserTableProps> = observer(
).toLocaleString();
return <span>{formateDate}</span>;
},
defaultSortOrder: "descend",
},
{
key: "editUser",

View File

@ -24,4 +24,8 @@ export class UserRepository {
async createArchiveTask(p: ArchiveTaskCreateDto) {
return await Apis.createArchiveTask(p);
}
async findArchiveTask() {
return await Apis.findArchiveTask();
}
}

View File

@ -56,4 +56,11 @@ export class UserService {
async createArchiveTask(user: User, study: Study[]) {
return await this.userRepository.createArchiveTask({ user, study });
}
/**
*
*/
async findArchiveTask() {
return await this.userRepository.findArchiveTask();
}
}

View File

@ -27,6 +27,7 @@ export class User {
signIn(user: User) {
this.isLoggedIn = true;
this.username = user.username;
this.roles = user.roles;
}

View File

@ -71,5 +71,11 @@ export const Apis = {
*
*/
createArchiveTask: (p: ArchiveTaskCreateDto): ResponseType =>
Request.post(PREFIX + "/admin/createArchiveTask", p),
Request.post(PREFIX + "/admin/create/archiveTask", p),
/**
*
*/
findArchiveTask: (): ResponseType =>
Request.get(PREFIX + "/annotator/find/archiveTask"),
};

View File

@ -28,7 +28,7 @@ export const useMenu = () => {
const navigate = useNavigate();
const [selectedKeys, setSelectedKeys] = useState([location.pathname]);
useEffect(() => setSelectedKeys([location.pathname]), []);
useEffect(() => setSelectedKeys([location.pathname]), [location.pathname]);
const LeftMenu = (props: RoleMenusType) => {
const { menuItems } = props;

View File

@ -0,0 +1,7 @@
interface ArchiveTaskProps {
children?: JSX.Element;
}
export const ArchiveTask = (props: ArchiveTaskProps) => {
return <div>ArchiveTask</div>
}

View File

@ -1,9 +1,14 @@
import React from "react";
import { Modal, Select } from "antd";
interface optionItem {
value: number;
label: string;
}
interface AssignModalProps {
isOpen: boolean;
options: { value: unknown; label: unknown }[];
options: optionItem[];
value: number | undefined;
onSelectAnnotator: (id: number) => void;
onConfirm: () => void;

View File

@ -173,13 +173,13 @@ export const DicomUpload = (props: DicomUploadProps) => {
<AssignModal
isOpen={isModalOpen}
options={annotators.map((a) => ({
value: a.id,
label: a.username,
value: a.id as number,
label: a.username as string,
}))}
value={selectAnnotator?.id as number}
onSelectAnnotator={(id: number) =>
setSelectAnnotator(annotators.find((a) => a.id === id))
}
onSelectAnnotator={(id: number) => {
setSelectAnnotator(annotators.find((a) => a.id === id));
}}
onConfirm={onAssignConfirm}
onCancel={() => setIsModalOpen(false)}
/>

View File

@ -0,0 +1,14 @@
import { TableColumnsType } from "antd";
export const columns: TableColumnsType<any> = [
{ title: "#", dataIndex: "id", key: "id" },
{ title: "病历号", dataIndex: "PatientID", key: "PatientID" },
{
title: "创建时间",
render: (_, record) => {
const formateDate = new Date(record.createTime ?? "").toLocaleString();
return <span>{formateDate}</span>;
},
key: "createTime",
},
];

View File

@ -0,0 +1,63 @@
import { useEffect, useState } from "react";
import { useDomain } from "@/hook/useDomain";
import { Button, Space, Table, Tooltip } from "antd";
import { columns } from "./columns";
import { EyeOutlined, TagOutlined } from "@ant-design/icons";
import { openOHIFViewer } from "@/modules/Admin/Dicom/Upload/util";
interface ArchiveListProps {
children?: JSX.Element;
}
export const ArchiveList = (props: ArchiveListProps) => {
const [dataSource, setDataSource] = useState<any>([]);
const [tableLoading, setTableLoading] = useState(false);
const { userDomainService } = useDomain();
useEffect(() => {
userDomainService.findArchiveTask().then((res) => {
const { code, data } = res;
if (code === 0) {
setDataSource(data);
}
setTableLoading(false);
});
}, [userDomainService]);
const onViewDicom = (record: any) => {
const { StudyInstanceUID, SeriesInstanceUID } = record;
openOHIFViewer(StudyInstanceUID, SeriesInstanceUID);
};
return (
<div>
<Table
dataSource={dataSource}
loading={tableLoading}
columns={[
...columns,
{
title: "操作",
dataIndex: "operation",
render: (_: any, record: any) => (
<Space>
<Button
type="primary"
size="small"
icon={<EyeOutlined />}
onClick={() => onViewDicom(record)}
>
</Button>
<Button type="primary" size="small" icon={<TagOutlined />}>
</Button>
</Space>
),
},
]}
rowKey="id"
/>
</div>
);
};

View File

@ -1,7 +0,0 @@
interface AnnotatorProps {
children?: JSX.Element;
}
export const AnnotatorList = (props: AnnotatorProps) => {
return <div>Annotator</div>;
};

View File

@ -1,4 +1,4 @@
import { CloudUploadOutlined, DatabaseOutlined } from "@ant-design/icons";
import { DatabaseOutlined, InboxOutlined } from "@ant-design/icons";
import { MenuItem, getItem, useMenu } from "@/components/Layout/Menu";
import { Layout } from "@/components/Layout";
import { Outlet } from "react-router";
@ -9,7 +9,7 @@ interface AnnotatorDashBoardProps {
const annotatorMenuItems: MenuItem[] = [
getItem("影像", "dicom", <DatabaseOutlined />, [
getItem("标注", "/list", <CloudUploadOutlined />),
getItem("归档", "/archive", <InboxOutlined />),
]),
];

View File

@ -1,4 +1,4 @@
import { AnnotatorList } from "@/modules/Annotator/List";
import { ArchiveList } from "@/modules/Annotator/Archive";
import { DicomList } from "@/modules/Admin/Dicom/List";
import { DicomUpload } from "@/modules/Admin/Dicom/Upload";
import { Navigate, RouteObject } from "react-router-dom";
@ -53,11 +53,11 @@ export const roleRoutes: Record<ROLE_NAME, (RouteObject & ExpandRouteProps)[]> =
children: [
{
path: "/",
element: <Navigate to="/list" replace />,
element: <Navigate to="/archive" replace />,
},
{
path: "/list",
element: <AnnotatorList />,
path: "/archive",
element: <ArchiveList />,
},
],
},

View File

@ -30,7 +30,8 @@
"typeorm": "0.3.17",
"@nestjs/typeorm": "10.0.0",
"bcrypt": "5.1.0",
"minimatch": "9.0.3"
"minimatch": "9.0.3",
"nanoid": "3.3.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",

View File

@ -10,4 +10,9 @@ export class AppController {
async createArchiveTask(payload) {
return await this.appService.createArchiveTask(payload);
}
@EventPattern({ cmd: 'archive.task.find' })
async findArchiveTask({ username }) {
return await this.appService.findArchiveTask({ username });
}
}

View File

@ -10,14 +10,14 @@ export class AppService {
private readonly archiveTaskRepository: Repository<ArchiveTask>,
) {}
async createArchiveTask(payload) {
const { annotatorId, study } = payload;
const { username, study } = payload;
try {
for (let i = 0; i < study.length; i++) {
const { StudyInstanceUID, PatientID } = study[i];
for (let j = 0; j < study[i].subs.length; j++) {
const { SeriesInstanceUID } = study[i].subs[j];
await this.archiveTaskRepository.save({
annotatorId,
username,
PatientID,
StudyInstanceUID,
SeriesInstanceUID,
@ -29,4 +29,8 @@ export class AppService {
return { success: false, error };
}
}
async findArchiveTask({ username }) {
return await this.archiveTaskRepository.find({ where: { username } });
}
}

View File

@ -8,13 +8,13 @@ import {
} from 'typeorm';
@Entity()
@Unique(['annotatorId', 'StudyInstanceUID', 'SeriesInstanceUID'])
@Unique(['username', 'StudyInstanceUID', 'SeriesInstanceUID'])
export class ArchiveTask {
@PrimaryGeneratedColumn()
id: number;
@Column()
annotatorId: number;
username: string;
@Column()
PatientID: string;

View File

@ -18,4 +18,4 @@
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}
}

View File

@ -14,12 +14,12 @@ export class AdminController {
return { data, code: 0 };
}
@Post('createArchiveTask')
@Post('create/archiveTask')
async createArchiveTask(@Body() body) {
const { user, study } = body;
const { id: annotatorId } = user;
const { username } = user;
const { success, error } = await firstValueFrom(
this.client.send({ cmd: 'archive.task.create' }, { annotatorId, study }),
this.client.send({ cmd: 'archive.task.create' }, { username, study }),
);
return success ? { code: 0 } : { code: 1, msg: error.code };
}

View File

@ -0,0 +1,25 @@
import { Body, Controller, Get, Inject, Post, Req } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
import { Request } from 'express';
@Controller('annotator')
export class AnnotatorController {
constructor(@Inject('Client') private readonly client: ClientProxy) {}
@Get('find/archiveTask')
async findArchiveTask(@Req() request: Request) {
const { tokenKeyInCookie } = await firstValueFrom(
this.client.send({ cmd: 'cert.token.config' }, []),
);
const tokenCipher = request.cookies[tokenKeyInCookie];
const { payload } = await firstValueFrom(
this.client.send({ cmd: 'cert.token.decode' }, tokenCipher),
);
const { username } = payload;
const data = await firstValueFrom(
this.client.send({ cmd: 'archive.task.find' }, { username }),
);
return { code: 0, data };
}
}

View File

@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { AnnotatorController } from './annotator.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: [AnnotatorController],
providers: [],
})
export class AnnotatorModule {}

View File

@ -8,6 +8,7 @@ import { AuthGuard } from './guard/auth.guard';
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 * as cookieParser from 'cookie-parser';
@Module({
@ -28,6 +29,7 @@ import * as cookieParser from 'cookie-parser';
},
]),
AdminModule,
AnnotatorModule,
],
controllers: [AppController, AuthController],
providers: [

View File

@ -664,6 +664,9 @@ importers:
nacos:
specifier: 2.5.1
version: 2.5.1
nanoid:
specifier: 3.3.4
version: registry.npmmirror.com/nanoid@3.3.4
nats:
specifier: 2.15.1
version: 2.15.1
@ -3726,7 +3729,7 @@ packages:
reflect-metadata: 0.1.13
rxjs: 7.8.1
typeorm: 0.3.17(ts-node@10.9.1)
uuid: 9.0.0
uuid: registry.npmmirror.com/uuid@9.0.0
dev: false
/@nodelib/fs.stat@2.0.5:
@ -8221,12 +8224,6 @@ packages:
lru-cache: registry.npmmirror.com/lru-cache@7.18.3
dev: false
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/nats@2.15.1:
resolution: {integrity: sha512-MMCQXxOLv3dUwh0CRai0RGdMdAHjA3LNOYEAdNkLT8GX4CJFVKlYqkM3K9qVkYhvWwiCUVPHWFSktcrhexSzSw==}
engines: {node: '>= 14.0.0'}
@ -8876,7 +8873,7 @@ packages:
resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
nanoid: registry.npmmirror.com/nanoid@3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
@ -11262,7 +11259,7 @@ packages:
sha.js: 2.4.11
ts-node: 10.9.1(@types/node@18.16.12)(typescript@5.1.3)
tslib: 2.6.0
uuid: 9.0.0
uuid: registry.npmmirror.com/uuid@9.0.0
yargs: 17.7.2
transitivePeerDependencies:
- supports-color
@ -16135,6 +16132,22 @@ packages:
- supports-color
dev: false
registry.npmmirror.com/nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz}
name: nanoid
version: 3.3.4
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
registry.npmmirror.com/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz}
name: nanoid
version: 3.3.6
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
registry.npmmirror.com/nats@2.15.1:
resolution: {integrity: sha512-MMCQXxOLv3dUwh0CRai0RGdMdAHjA3LNOYEAdNkLT8GX4CJFVKlYqkM3K9qVkYhvWwiCUVPHWFSktcrhexSzSw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nats/-/nats-2.15.1.tgz}
name: nats