From 0bb16c94ae062bdb4a8cb22ff63d696d5116fe4f Mon Sep 17 00:00:00 2001 From: mozzie Date: Mon, 18 Sep 2023 14:51:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A0=87=E7=AD=BE=E3=80=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dmp/core/domain/Label/entities/label.ts | 10 + apps/dmp/core/domain/Label/labelRepository.ts | 22 ++ apps/dmp/core/domain/Label/labelService.ts | 26 +++ apps/dmp/core/infra/api/dto.ts | 19 ++ apps/dmp/core/infra/api/index.ts | 28 +++ .../modules/Admin/Label/LabelTree/config.tsx | 28 +++ .../modules/Admin/Label/LabelTree/index.tsx | 192 ++++++++++++++++++ .../Admin/Label/LabelTree/interface.tsx | 47 +++++ apps/dmp/src/modules/Admin/Label/index.tsx | 135 +++++++----- .../dicom/src/filter/orm.exception.filter.ts | 5 +- .../dicom/src/label/entity/category.entity.ts | 5 + .../dicom/src/label/entity/label.entity.ts | 5 + .../dicom/src/label/label.controller.ts | 25 ++- .../services/dicom/src/label/label.service.ts | 28 ++- .../dmp/gateway/src/dicom/dicom.controller.ts | 38 +++- 15 files changed, 546 insertions(+), 67 deletions(-) create mode 100644 apps/dmp/core/domain/Label/entities/label.ts create mode 100644 apps/dmp/src/modules/Admin/Label/LabelTree/config.tsx create mode 100644 apps/dmp/src/modules/Admin/Label/LabelTree/index.tsx create mode 100644 apps/dmp/src/modules/Admin/Label/LabelTree/interface.tsx diff --git a/apps/dmp/core/domain/Label/entities/label.ts b/apps/dmp/core/domain/Label/entities/label.ts new file mode 100644 index 0000000..f0e8367 --- /dev/null +++ b/apps/dmp/core/domain/Label/entities/label.ts @@ -0,0 +1,10 @@ +export type LabelType = { + name: string; +}; + +export class Label { + name: string; + constructor(props: LabelType) { + this.name = props.name; + } +} diff --git a/apps/dmp/core/domain/Label/labelRepository.ts b/apps/dmp/core/domain/Label/labelRepository.ts index 1f67233..078b154 100644 --- a/apps/dmp/core/domain/Label/labelRepository.ts +++ b/apps/dmp/core/domain/Label/labelRepository.ts @@ -1,5 +1,11 @@ import { Apis } from "@@/infra/api"; import { LabelCategory } from "./entities/labelCategory"; +import { + LabelCategoryUpdateDTO, + LabelCreateDTO, + LabelDeleteDTO, + LabelUpdateDTO, +} from "@@/infra/api/dto"; export class LabelRepository { async createNewLabelCategory(labelCategory: LabelCategory) { @@ -9,4 +15,20 @@ export class LabelRepository { async findLabelCategory() { return await Apis.findLabelCategory(); } + + async createLabel(p: LabelCreateDTO) { + return await Apis.createLabel(p); + } + + async updateLabel(p: LabelUpdateDTO) { + return await Apis.updateLabel(p); + } + + async deleteLabel(p: LabelDeleteDTO) { + return await Apis.deleteLabel(p); + } + + async updateCategory(p: LabelCategoryUpdateDTO) { + return await Apis.updateCategory(p); + } } diff --git a/apps/dmp/core/domain/Label/labelService.ts b/apps/dmp/core/domain/Label/labelService.ts index 2f25643..83a0dbd 100644 --- a/apps/dmp/core/domain/Label/labelService.ts +++ b/apps/dmp/core/domain/Label/labelService.ts @@ -1,3 +1,9 @@ +import { + LabelCategoryUpdateDTO, + LabelCreateDTO, + LabelDeleteDTO, + LabelUpdateDTO, +} from "@@/infra/api/dto"; import { LabelCategory } from "./entities/labelCategory"; import { LabelRepository } from "./labelRepository"; @@ -14,4 +20,24 @@ export class LabelService { async findLabelCategory() { return await this.labelRepository.findLabelCategory(); } + + async createNewLabel(p: LabelCreateDTO) { + const { code } = await this.labelRepository.createLabel(p); + return { code }; + } + + async updateLabel(p: LabelUpdateDTO) { + const { code } = await this.labelRepository.updateLabel(p); + return { code }; + } + + async deleteLabel(p: LabelDeleteDTO) { + const { code } = await this.labelRepository.deleteLabel(p); + return { code }; + } + + async updateCategory(p: LabelCategoryUpdateDTO) { + const { code } = await this.labelRepository.updateCategory(p); + return { code }; + } } diff --git a/apps/dmp/core/infra/api/dto.ts b/apps/dmp/core/infra/api/dto.ts index 8ca05b3..0c984b2 100644 --- a/apps/dmp/core/infra/api/dto.ts +++ b/apps/dmp/core/infra/api/dto.ts @@ -34,3 +34,22 @@ export type DownloadArchiveDTO = { export type labelCategoryDTO = { name: string; }; + +export type LabelCreateDTO = { + name: string; + categoryId: number; +}; + +export type LabelUpdateDTO = { + name: string; + id: number; +}; + +export type LabelDeleteDTO = { + id: number; +}; + +export type LabelCategoryUpdateDTO = { + name: string; + id: number; +}; diff --git a/apps/dmp/core/infra/api/index.ts b/apps/dmp/core/infra/api/index.ts index 60d3047..9f6d4f3 100644 --- a/apps/dmp/core/infra/api/index.ts +++ b/apps/dmp/core/infra/api/index.ts @@ -6,10 +6,14 @@ import { ArchiveTaskCreateDto, DownloadArchiveDTO, ExistInPacsDTO, + LabelCreateDTO, + LabelDeleteDTO, + LabelUpdateDTO, ResponsePacsType, ResponseType, labelCategoryDTO, } from "./dto"; +import { Label } from "@@/domain/Label/entities/label"; const PREFIX = "/api/dmp"; const PREFIX_CERT = "/cert"; @@ -93,4 +97,28 @@ export const Apis = { */ findLabelCategory: (): ResponseType => Request.get(PREFIX + "/dicom/label/category/find/all"), + + /** + * 创建新标签 + */ + createLabel: (p: LabelCreateDTO): ResponseType => + Request.post(PREFIX + "/dicom/label/create", p), + + /** + * 修改标签 + */ + updateLabel: (p: LabelUpdateDTO): ResponseType => + Request.post(PREFIX + "/dicom/label/update", p), + + /** + * 删除标签 + */ + deleteLabel: (p: LabelDeleteDTO): ResponseType => + Request.post(PREFIX + "/dicom/label/delete", p), + + /** + * 修改标签分类 + */ + updateCategory: (p: labelCategoryDTO): ResponseType => + Request.post(PREFIX + "/dicom/label/category/update", p), }; diff --git a/apps/dmp/src/modules/Admin/Label/LabelTree/config.tsx b/apps/dmp/src/modules/Admin/Label/LabelTree/config.tsx new file mode 100644 index 0000000..1228867 --- /dev/null +++ b/apps/dmp/src/modules/Admin/Label/LabelTree/config.tsx @@ -0,0 +1,28 @@ +import { ModalType } from "./interface"; + +type ModalConfigType = { + [key in ModalType]: { + title: string; + placeholder: string; + }; +}; + +export const modalConfig: ModalConfigType = { + createLabel: { + title: "添加新标签", + placeholder: "新标签名称", + }, + updateLabel: { + title: "编辑", + placeholder: "新标签名称", + }, + updateCategory: { + title: "编辑分类名称", + placeholder: "新标签分类名称", + }, +}; + +export const defaultModalConfig = { + title: "", + placeholder: "", +}; diff --git a/apps/dmp/src/modules/Admin/Label/LabelTree/index.tsx b/apps/dmp/src/modules/Admin/Label/LabelTree/index.tsx new file mode 100644 index 0000000..6afca21 --- /dev/null +++ b/apps/dmp/src/modules/Admin/Label/LabelTree/index.tsx @@ -0,0 +1,192 @@ +import { + Button, + Col, + Collapse, + CollapseProps, + Form, + Input, + Modal, + Row, + Space, + Tooltip, + theme, +} from "antd"; +import { + CategoryItemType, + LabelTreeProps, + LabelType, + ModalType, +} from "./interface"; +import { useEffect, useState } from "react"; +import { defaultModalConfig, modalConfig } from "./config"; +import { + CaretRightOutlined, + CloseOutlined, + EditOutlined, + TagOutlined, +} from "@ant-design/icons"; + +export const LabelTree = (props: LabelTreeProps) => { + const { token } = theme.useToken(); + const [modalType, setModalType] = useState(null); + const [form] = Form.useForm<{ name: string }>(); + const [activeLabel, setActiveLabel] = useState(null); + const [activeCategory, setActiveCategory] = useState( + null + ); + + useEffect(() => { + switch (modalType) { + case "updateLabel": + form.setFieldsValue({ name: activeLabel?.name }); + break; + case "updateCategory": + form.setFieldsValue({ name: activeCategory?.name }); + break; + default: + break; + } + }, [activeLabel, activeCategory, form, modalType]); + + const { title, placeholder } = !modalType + ? defaultModalConfig + : modalConfig[modalType]; + + const handleConfirm = async () => { + try { + await form.validateFields(); + const { name } = form.getFieldsValue(); + switch (modalType) { + case "createLabel": + activeCategory && + props.onCreateLabel?.({ name, categoryId: activeCategory.id }); + break; + case "updateLabel": + activeLabel && props.onUpdateLabel?.({ name, id: activeLabel.id }); + break; + case "updateCategory": + activeCategory && + props.onUpdateCategory?.({ name, id: activeCategory.id }); + break; + default: + break; + } + form.resetFields(); + setModalType(null); + } catch (error) { + console.log(error); + } + }; + + const getItems = (dataList: CategoryItemType[]): CollapseProps["items"] => + dataList + .map((item) => { + return { + key: item.id, + label: ( + + {item.name} + + + + { + e.stopPropagation(); + setActiveCategory(item); + setModalType("updateCategory"); + }} + /> + + + + + + + + ), + children: ( +
    + {item.labels?.map((label) => ( +
  • + + {label.name} + + + + { + e.stopPropagation(); + setActiveLabel(label); + setModalType("updateLabel"); + }} + /> + + + { + e.stopPropagation(); + props.onDeleteLabel?.(label); + }} + /> + + + + +
  • + ))} + +
+ ), + }; + }) + .map((i) => ({ + ...i, + headerClass: "label-category-header", + className: "label-category-item", + })); + + return ( +
+ ( + + )} + style={{ background: token.colorBgContainer, color: token.colorText }} + items={getItems(props.data)} + /> + setModalType(null)} + onOk={handleConfirm} + > +
+ + + +
+
+
+ ); +}; diff --git a/apps/dmp/src/modules/Admin/Label/LabelTree/interface.tsx b/apps/dmp/src/modules/Admin/Label/LabelTree/interface.tsx new file mode 100644 index 0000000..068abd0 --- /dev/null +++ b/apps/dmp/src/modules/Admin/Label/LabelTree/interface.tsx @@ -0,0 +1,47 @@ +export type ModalType = "createLabel" | "updateLabel" | "updateCategory"; + +export type CategoryItemType = { + id: number; + name: string; + labels?: { id: number; name: string }[]; +}; + +export type LabelCategoryType = { + name: string; + id: number; +}; + +export type LabelType = { + name: string; + id?: number; + categoryId?: number; +}; + +export interface LabelTreeProps { + children?: JSX.Element; + data: CategoryItemType[]; + /** + * 编辑标签分类名称 + * @param {string} name 更新后的标签分类名 + * @param {number} id 标签分类id + */ + onUpdateCategory?: (p: LabelCategoryType) => void; + /** + * 编辑标签分类名称 + * @param {string} name 更新后的标签分类名 + * @param {number} categoryId 标签分类id + */ + onCreateLabel?: (p: LabelType) => void; + + /** + * 编辑标签名称 + * @param {string} name 更新后的标签名 + * @param {number} id 标签id + */ + onUpdateLabel?: (p: LabelType) => void; + /** + * 删除标签 + * @param {number} id 标签id + */ + onDeleteLabel?: (p: LabelType) => void; +} diff --git a/apps/dmp/src/modules/Admin/Label/index.tsx b/apps/dmp/src/modules/Admin/Label/index.tsx index e660795..e272a4a 100644 --- a/apps/dmp/src/modules/Admin/Label/index.tsx +++ b/apps/dmp/src/modules/Admin/Label/index.tsx @@ -1,84 +1,111 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import "./index.less"; -import { - Button, - Collapse, - CollapseProps, - Form, - Input, - Modal, - theme, - Typography, -} from "antd"; -import { CaretRightOutlined, PlusOutlined } from "@ant-design/icons"; +import { Button, Form, Input, Modal, message } from "antd"; +import { PlusOutlined } from "@ant-design/icons"; import { useDomain } from "@/hook/useDomain"; - -const { Text } = Typography; +import { LabelTree } from "./LabelTree"; +import { + CategoryItemType, + LabelCategoryType, + LabelType, +} from "./LabelTree/interface"; interface LabelProps { children?: JSX.Element; } -const content = ( -
    -
  • 切片太少
  • -
  • {"层厚>1.0mm"}
  • -
-); -const getItems = (): CollapseProps["items"] => - [ - { - key: "1", - label: "影像质量", - children: content, - }, - { - key: "2", - label: "其他", - children:

1

, - }, - ].map((i) => ({ - ...i, - headerClass: "label-category-header", - className: "label-category-item", - })); - export const Label = (props: LabelProps) => { - const { token } = theme.useToken(); const [isModalOpen, setIsModalOpen] = useState(false); const [createLabelCategoryForm] = Form.useForm<{ name: string }>(); const { labelDomainService } = useDomain(); + const [labelList, setLabelList] = useState([]); + const [messageApi, contextHolder] = message.useMessage(); - const handleOk = async () => { + const createLabelHandler = async () => { try { await createLabelCategoryForm.validateFields(); const { name } = createLabelCategoryForm.getFieldsValue(); - labelDomainService.createNewLabelCategory({ name }); + labelDomainService.createNewLabelCategory({ name }).then((res) => { + const { code } = res; + if (code === 0) { + renderLabelList(); + createLabelCategoryForm.resetFields(); + setIsModalOpen(false); + } + }); } catch (error) { console.log(error); } }; - useEffect(() => { - labelDomainService.findLabelCategory(); + const renderLabelList = useCallback(async () => { + const { code, data } = await labelDomainService.findLabelCategory(); + if (code === 0) setLabelList(data as CategoryItemType[]); }, [labelDomainService]); + useEffect(() => { + renderLabelList(); + }, [renderLabelList]); + + const onCreateLabelHandler = ({ name, categoryId }: LabelType) => { + labelDomainService + .createNewLabel({ name, categoryId: categoryId! }) + .then((res) => { + const { code } = res; + if (code === 0) { + renderLabelList(); + messageApi.success(`标签 ${name} 添加成功`); + } + }); + }; + + const onUpdateLabelHandler = ({ name, id }: LabelType) => { + labelDomainService.updateLabel({ name, id: id! }).then((res) => { + const { code } = res; + if (code === 0) { + renderLabelList(); + messageApi.success(`标签 ${name} 修改成功`); + } + }); + }; + + const onDeleteLabelHandler = ({ id, name }: LabelType) => { + labelDomainService.deleteLabel({ id: id! }).then((res) => { + const { code } = res; + if (code === 0) { + renderLabelList(); + messageApi.success(`标签 ${name} 修改成功`); + } + }); + }; + + const onUpdateCategoryHandler = ({ name, id }: LabelCategoryType) => { + labelDomainService.updateCategory({ name, id }).then((res) => { + const { code } = res; + if (code === 0) { + renderLabelList(); + messageApi.success(`${name} 分类修改成功`); + } + }); + }; + return (
+ {contextHolder}
标签相关数据
diff --git a/apps/services/dicom/src/filter/orm.exception.filter.ts b/apps/services/dicom/src/filter/orm.exception.filter.ts index 1b23d4d..52dc425 100644 --- a/apps/services/dicom/src/filter/orm.exception.filter.ts +++ b/apps/services/dicom/src/filter/orm.exception.filter.ts @@ -15,8 +15,9 @@ const errorMapping = { @Catch(QueryFailedError) export class TypeOrmExceptionFilter implements ExceptionFilter { catch(exception: QueryFailedError, host: ArgumentsHost) { - console.log('TypeOrmExceptionFilter 过滤器'); - const message = errorMapping?.[exception.driverError] ?? 'sql操作失败。'; + console.log('TypeOrmExceptionFilter 过滤器:', exception.driverError); + const message = + errorMapping?.[exception.driverError.code] ?? 'sql操作失败。'; return { statusCode, message }; } } diff --git a/apps/services/dicom/src/label/entity/category.entity.ts b/apps/services/dicom/src/label/entity/category.entity.ts index 67398c7..32aa879 100644 --- a/apps/services/dicom/src/label/entity/category.entity.ts +++ b/apps/services/dicom/src/label/entity/category.entity.ts @@ -2,9 +2,11 @@ import { Column, CreateDateColumn, Entity, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { Label } from './label.entity'; @Entity() export class LabelCategory { @@ -19,4 +21,7 @@ export class LabelCategory { @UpdateDateColumn({ type: 'timestamp' }) updateAt: Date; + + @OneToMany(() => Label, (label) => label.category) + labels: Label[]; } diff --git a/apps/services/dicom/src/label/entity/label.entity.ts b/apps/services/dicom/src/label/entity/label.entity.ts index 6be9bfb..bdfd2a7 100644 --- a/apps/services/dicom/src/label/entity/label.entity.ts +++ b/apps/services/dicom/src/label/entity/label.entity.ts @@ -3,9 +3,11 @@ import { Column, CreateDateColumn, Entity, + ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { LabelCategory } from './category.entity'; @Entity() export class Label { @@ -27,4 +29,7 @@ export class Label { @UpdateDateColumn({ type: 'timestamp' }) updateAt: Date; + + @ManyToOne(() => LabelCategory, (category) => category.labels) + category: LabelCategory; } diff --git a/apps/services/dicom/src/label/label.controller.ts b/apps/services/dicom/src/label/label.controller.ts index db66cf5..2df8a0c 100644 --- a/apps/services/dicom/src/label/label.controller.ts +++ b/apps/services/dicom/src/label/label.controller.ts @@ -1,5 +1,5 @@ import { Controller } from '@nestjs/common'; -import { EventPattern, MessagePattern } from '@nestjs/microservices'; +import { MessagePattern } from '@nestjs/microservices'; import { LabelService } from './label.service'; @Controller() @@ -12,13 +12,32 @@ export class LabelController { return { statusCode: 200, data: inserted }; } - @EventPattern({ cmd: 'dicom.label.create' }) + @MessagePattern('dicom.label.create') async createLabel(payload) { - return this.labelService.createLabel(payload); + const data = await this.labelService.createLabel(payload); + return { statusCode: 200, data }; + } + + @MessagePattern('dicom.label.update') + async updateLabel(payload) { + const data = await this.labelService.updateLabel(payload); + return { statusCode: 200, data }; } @MessagePattern('dicom.label.category.find.all') async findAllLabelCategory() { return await this.labelService.findAllLabelCategory(); } + + @MessagePattern('dicom.label.delete') + async deleteLabel(payload) { + const { affected } = await this.labelService.deleteLabel(payload); + return { statusCode: affected > 0 ? 200 : -1 }; + } + + @MessagePattern('dicom.label.category.update') + async updateLabelCategory(payload) { + const { affected } = await this.labelService.updateLabelCategory(payload); + return { statusCode: affected > 0 ? 200 : -1 }; + } } diff --git a/apps/services/dicom/src/label/label.service.ts b/apps/services/dicom/src/label/label.service.ts index d45ab2b..70a4695 100644 --- a/apps/services/dicom/src/label/label.service.ts +++ b/apps/services/dicom/src/label/label.service.ts @@ -17,15 +17,31 @@ export class LabelService { return await this.labelCategoryRepository.save({ name }); } - async createLabel(payload) { - console.log(payload); - return await this.labelRepository.save({ - name: '钙化', - description: '钙化的关键词', + async createLabel({ categoryId: id, name }) { + const category = await this.labelCategoryRepository.findOne({ + where: { id }, }); + const label = new Label(); + label.name = name; + label.category = category; + return await this.labelRepository.save(label); + } + + async updateLabel({ id, name }) { + const label = await this.labelRepository.findOne({ where: { id } }); + label.name = name; + return await this.labelRepository.save(label); } async findAllLabelCategory() { - return await this.labelCategoryRepository.find(); + return await this.labelCategoryRepository.find({ relations: ['labels'] }); + } + + async deleteLabel({ id }) { + return await this.labelRepository.delete({ id }); + } + + async updateLabelCategory({ name, id }) { + return await this.labelCategoryRepository.update(id, { name }); } } diff --git a/apps/services/dmp/gateway/src/dicom/dicom.controller.ts b/apps/services/dmp/gateway/src/dicom/dicom.controller.ts index bbde49a..b231b48 100644 --- a/apps/services/dmp/gateway/src/dicom/dicom.controller.ts +++ b/apps/services/dmp/gateway/src/dicom/dicom.controller.ts @@ -32,7 +32,7 @@ export class DicomController { const { statusCode, message, data } = await firstValueFrom( this.client.send('dicom.label.category.create', { name }), ); - return { statusCode, message, data }; + return { code: statusCode === 200 ? 0 : 1, message, data }; } @Get('label/category/find/all') @@ -42,4 +42,40 @@ export class DicomController { ); return { code: 0, data }; } + + @Post('label/create') + async createLabel(@Body() body) { + const { name, categoryId } = body; + const { statusCode, message, data } = await firstValueFrom( + this.client.send('dicom.label.create', { name, categoryId }), + ); + return { code: statusCode === 200 ? 0 : 1, message, data }; + } + + @Post('label/update') + async updateLabel(@Body() body) { + const { name, id } = body; + const { statusCode, message, data } = await firstValueFrom( + this.client.send('dicom.label.update', { name, id }), + ); + return { code: statusCode === 200 ? 0 : 1, message, data }; + } + + @Post('label/delete') + async deleteLabel(@Body() body) { + const { id } = body; + const { statusCode, message, data } = await firstValueFrom( + this.client.send('dicom.label.delete', { id }), + ); + return { code: statusCode === 200 ? 0 : 1, message, data }; + } + + @Post('label/category/update') + async updateLabelCategory(@Body() body) { + const { name, id } = body; + const { statusCode, message, data } = await firstValueFrom( + this.client.send('dicom.label.category.update', { name, id }), + ); + return { code: statusCode === 200 ? 0 : 1, message, data }; + } }