Преглед изворни кода

feat: 为已曝光图像添加接受/拒绝状态badge显示

- 在 dview 接口中添加 judged_status 字段用于存储判断状态
- 在 ThumbnailList 组件左上角显示判断状态badge(接受显示绿色,拒绝显示红色)
- 在 BodyPositionList 组件左上角显示判断状态badge
- 更新 thumbnailListSlice 数据映射逻辑,从后端数据中提取 judged_status
- 更新 bodyPositionListSlice,在更新体位时保留判断状态信息
- 更新 mapToTask、worklistToExam、appendViewSlice 中的数据映射,确保 judged_status 字段正确传递

改动文件:
- src/domain/dview.ts
- src/pages/patient/components/ThumbnailList.tsx
- src/pages/exam/components/BodyPositionList.tsx
- src/states/patient/worklist/slices/thumbnailListSlice.ts
- src/states/exam/bodyPositionListSlice.ts
- src/domain/patient/mapToTask.ts
- src/domain/patient/worklistToExam.ts
- src/states/exam/appendViewSlice.ts
sw пре 1 дан
родитељ
комит
c2d1382190

+ 1 - 0
src/domain/dview.ts

@@ -11,6 +11,7 @@ export interface dview {
   view_type: string;
   PrimarySopUID: string;
   expose_status: string;
+  judged_status: string;
   image_file_path: string;
   image_file: string;
   thumbnail_file: string;

+ 1 - 0
src/domain/patient/mapToTask.ts

@@ -45,6 +45,7 @@ const mapToTask = (data: RegisterWorkResponseData): Task => {
               view_type: '',
               PrimarySopUID: image.sop_instance_uid,
               expose_status: image.expose_status,
+              judged_status:image.judged_status,
               image_file_path: image.image_file_path,
               image_file: image.image_file_path, // Assuming image_file is the same as image_file
               thumbnail_file: image.thumbnail_file || '', // Assuming thumbnail_file is optional

+ 2 - 0
src/domain/patient/worklistToExam.ts

@@ -42,6 +42,7 @@ export const prepareWorksForExam = async (
                 view_type: '',
                 PrimarySopUID: image.sop_instance_uid,
                 expose_status: image.expose_status,
+                judged_status:image.judged_status,
                 image_file_path: image.image_file_path,
                 image_file: image.image_file_path,
                 thumbnail_file: image.thumbnail_file || '',
@@ -130,6 +131,7 @@ const worklistToProcess = async (selectedIds: string[]) => {
                 view_type: '',
                 PrimarySopUID: image.sop_instance_uid,
                 expose_status: image.expose_status,
+                judged_status:image.judged_status,
                 image_file_path: image.image_file_path,
                 image_file: image.image_file_path,
                 thumbnail_file: image.thumbnail_file || '',

+ 41 - 17
src/pages/exam/components/BodyPositionList.tsx

@@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react';
 import { useSelector, useDispatch } from 'react-redux';
 import { ExtendedBodyPosition } from '../../../states/exam/bodyPositionListSlice';
 import { RootState, AppDispatch } from '../../../states/store';
-import { message } from 'antd';
+import { message, Badge } from 'antd';
+import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
 import AppendViewIcon from '@/components/AppendViewIcon';
 import ImageViewer from './ImageViewer';
 import { getExposedImageUrl, getViewIconUrl } from '../../../API/bodyPosition';
@@ -83,22 +84,45 @@ const BodyPositionList: React.FC<BodyPositionListProps> = ({
     <div className={`${layout} grid grid-rows-[1fr_auto] h-0 flex-grow`}>
       <div className="overflow-y-auto flex flex-col ">
         {bodyPositions.map((bodyPosition, index) => (
-          <ImageViewer
-            key={index}
-            src={
-              bodyPosition.dview.expose_status === 'Exposed'
-                ? getExposedImageUrl(bodyPosition.sop_instance_uid)
-                : getViewIconUrl(bodyPosition.view_icon_name)
-            }
-            className={`image-viewer-item w-[50%] mx-auto hover:border-[var(--color-primary)] hover:border-4
-              ${
-                bodyPosition.sop_instance_uid ===
-                selectedBodyPosition?.sop_instance_uid
-                  ? 'border-4 border-[var(--color-primary)] '
-                  : ''
-              }`}
-            onClick={() => handleImageClick(bodyPosition)}
-          />
+          <div key={index} className="relative w-[50%] mx-auto">
+            <ImageViewer
+              src={
+                bodyPosition.dview.expose_status === 'Exposed'
+                  ? getExposedImageUrl(bodyPosition.sop_instance_uid)
+                  : getViewIconUrl(bodyPosition.view_icon_name)
+              }
+              className={`image-viewer-item hover:border-[var(--color-primary)] hover:border-4
+                ${
+                  bodyPosition.sop_instance_uid ===
+                  selectedBodyPosition?.sop_instance_uid
+                    ? 'border-4 border-[var(--color-primary)] '
+                    : ''
+                }`}
+              onClick={() => handleImageClick(bodyPosition)}
+            />
+            {/* 左上角:判断状态badge */}
+            {bodyPosition.dview.expose_status === 'Exposed' && bodyPosition.dview.judged_status && (
+              <div className="absolute top-1 left-1 z-10">
+                <Badge
+                  count={
+                    bodyPosition.dview.judged_status === 'Accept' ? (
+                      <CheckOutlined style={{ color: '#fff', fontSize: '16px' }} />
+                    ) : bodyPosition.dview.judged_status === 'Rejected' ? (
+                      <CloseOutlined style={{ color: '#fff', fontSize: '16px' }} />
+                    ) : 'Un'
+                  }
+                  style={{
+                    backgroundColor: bodyPosition.dview.judged_status === 'Accept' ? '#52c41a' : '#ff4d4f',
+
+                    borderRadius: '50%',
+                    display: 'flex',
+                    alignItems: 'center',
+                    justifyContent: 'center',
+                  }}
+                />
+              </div>
+            )}
+          </div>
         ))}
       </div>
 

+ 20 - 1
src/pages/patient/components/ThumbnailList.tsx

@@ -1,10 +1,11 @@
 import React, { useEffect } from 'react';
-import { Row, Col, Image, Card, Typography, Tag, Empty } from 'antd';
+import { Row, Col, Image, Card, Typography, Tag, Empty, Badge } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import { useDispatch, useSelector } from 'react-redux';
 import store, { AppDispatch, RootState } from '@/states/store';
 import { dview } from '@/domain/dview';
 import { clearThumbnails, updateThumbnailsFromHistorySelection } from '@/states/patient/worklist/slices/thumbnailListSlice';
+import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
 
 const { Text, Title } = Typography;
 
@@ -111,6 +112,24 @@ const ThumbnailList: React.FC<ThumbnailListProps> = ({ className }) => {
                       ),
                     }}
                   />
+                  {/* 左上角:判断状态badge */}
+                  {thumbnail.expose_status === 'Exposed' && thumbnail.judged_status && (
+                    <div className="absolute top-1 left-1">
+                      <Badge
+                        count={
+                          thumbnail.judged_status === 'Accept' ? (
+                            <CheckOutlined style={{ color: '#fff' }} />
+                          ) : thumbnail.judged_status === 'Rejected' ? (
+                            <CloseOutlined style={{ color: '#fff' }} />
+                          ) : 'Un'
+                        }
+                        style={{
+                          backgroundColor: thumbnail.judged_status === 'Accept' ? '#52c41a' : '#ff4d4f',
+                        }}
+                      />
+                    </div>
+                  )}
+                  {/* 右上角:曝光状态tag */}
                   <div className="absolute top-1 right-1">
                     <Tag
                       color={

+ 1 - 0
src/states/exam/appendViewSlice.ts

@@ -111,6 +111,7 @@ export const appendViewsThunk = createAsyncThunk(
             image_file: image.image_file_path,
             image_file_path: image.image_file_path,
             expose_status: 'Unexposed',
+            judged_status:image.judged_status,
           };
 
           const extendedBodyPosition: ExtendedBodyPosition = {

+ 1 - 0
src/states/exam/bodyPositionListSlice.ts

@@ -177,6 +177,7 @@ const bodyPositionListSlice = createSlice({
           thumbnail_file: msg.thumbnail ?? '',
           image_file: msg.dcm ?? '',
           expose_status: 'Exposed',
+          judged_status: state.bodyPositions[index].dview.judged_status || '',
         } satisfies dview,
       };
       state.bodyPositions[index] = updatedBodyPosition;

+ 1 - 0
src/states/patient/worklist/slices/thumbnailListSlice.ts

@@ -62,6 +62,7 @@ async function getAllDView(selectedIds: string[]) {
               view_type: '',
               PrimarySopUID: image.sop_instance_uid,
               expose_status: image.expose_status,
+              judged_status: image.judged_status,
               image_file_path: image.image_file_path,
               image_file: image.image_file_path,
               thumbnail_file,