feat: 上传全部影像
This commit is contained in:
parent
184d84726e
commit
a84bfb028a
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -64,7 +64,6 @@ export const UserTableList: React.FC<UserTableProps> = observer(
|
|||
).toLocaleString();
|
||||
return <span>{formateDate}</span>;
|
||||
},
|
||||
defaultSortOrder: "descend",
|
||||
},
|
||||
{
|
||||
key: "editUser",
|
||||
|
|
|
@ -24,4 +24,8 @@ export class UserRepository {
|
|||
async createArchiveTask(p: ArchiveTaskCreateDto) {
|
||||
return await Apis.createArchiveTask(p);
|
||||
}
|
||||
|
||||
async findArchiveTask() {
|
||||
return await Apis.findArchiveTask();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export class User {
|
|||
|
||||
signIn(user: User) {
|
||||
this.isLoggedIn = true;
|
||||
this.username = user.username;
|
||||
this.roles = user.roles;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
interface ArchiveTaskProps {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
export const ArchiveTask = (props: ArchiveTaskProps) => {
|
||||
return <div>ArchiveTask</div>
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
14
apps/dmp/src/modules/Annotator/Archive/columns.tsx
Normal file
14
apps/dmp/src/modules/Annotator/Archive/columns.tsx
Normal 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",
|
||||
},
|
||||
];
|
63
apps/dmp/src/modules/Annotator/Archive/index.tsx
Normal file
63
apps/dmp/src/modules/Annotator/Archive/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
interface AnnotatorProps {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
export const AnnotatorList = (props: AnnotatorProps) => {
|
||||
return <div>Annotator</div>;
|
||||
};
|
|
@ -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 />),
|
||||
]),
|
||||
];
|
||||
|
||||
|
|
|
@ -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 />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
22
apps/services/dmp/gateway/src/annotator/annotator.module.ts
Normal file
22
apps/services/dmp/gateway/src/annotator/annotator.module.ts
Normal 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 {}
|
|
@ -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: [
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user