123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import React, { useEffect, useState, useRef } from 'react';
- import { useSelector, useDispatch } from 'react-redux';
- import { ExtendedBodyPosition } from '../../../states/exam/bodyPositionListSlice';
- import { RootState, AppDispatch } from '../../../states/store';
- 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';
- import {
- manualSelectBodyPositionWithFlowSwitch,
- autoSelectFirstBodyPosition,
- } from '@/domain/exam/bodyPositionSelection';
- import AppendViewModal from './AppendViewModal';
- interface BodyPositionListProps {
- layout: 'horizontal' | 'vertical';
- showAddButton?: boolean;
- }
- const BodyPositionList: React.FC<BodyPositionListProps> = ({
- layout,
- showAddButton = true,
- }) => {
- const dispatch = useDispatch<AppDispatch>();
- const currentKey = useSelector(
- (state: RootState) => state.BusinessFlow.currentKey
- );
- // 🔧 修改:处理单击事件
- const handleImageClick = async (
- bodyPosition: ExtendedBodyPosition
- ): Promise<void> => {
- console.log(`[BodyPositionList] Single-click on: ${bodyPosition.view_name}`);
-
- // 单击时,禁止自动流程切换
- await manualSelectBodyPositionWithFlowSwitch(
- bodyPosition,
- dispatch,
- currentKey,
- false // allowFlowSwitch = false(禁止流程切换)
- );
- };
- // 🆕 新增:处理双击事件
- const handleImageDoubleClick = async (
- bodyPosition: ExtendedBodyPosition
- ): Promise<void> => {
- console.log(`[BodyPositionList] Double-click on: ${bodyPosition.view_name}`);
-
- // 双击时,允许自动流程切换
- await manualSelectBodyPositionWithFlowSwitch(
- bodyPosition,
- dispatch,
- currentKey,
- true // allowFlowSwitch = true(允许流程切换)
- );
- };
- const bodyPositions = useSelector(
- (state: RootState) => state.bodyPositionList.bodyPositions
- );
- const selectedBodyPosition = useSelector(
- (state: RootState) => state.bodyPositionList.selectedBodyPosition
- );
- // 🔧 修复:使用 ref 跟踪是否已经执行过初始化
- // 避免在 judgeImage 等操作导致 bodyPositions 引用改变时重复执行
- const hasInitializedRef = useRef(false);
- useEffect(() => {
- // 只在第一次体位列表有值时执行自动选择
- if (bodyPositions.length > 0 && !hasInitializedRef.current) {
- hasInitializedRef.current = true;
- console.log(
- '[BodyPositionList] Auto-selecting first body position on component mount'
- );
- autoSelectFirstBodyPosition(bodyPositions, dispatch, currentKey).catch(
- (error) => {
- message.error(
- `Failed to auto-select the first body position ${error}`
- );
- }
- );
- }
- }, [bodyPositions, dispatch, currentKey]);
- const [isAppendModalOpen, setIsAppendModalOpen] = useState(false);
- const addBodyPositionClick = (): void => {
- console.log('[BodyPositionList] Add button clicked');
- console.log(
- '[BodyPositionList] selectedBodyPosition:',
- selectedBodyPosition
- );
- if (!selectedBodyPosition) {
- message.warning('请先选择一个体位');
- return;
- }
- console.log('[BodyPositionList] Opening append modal');
- setIsAppendModalOpen(true);
- };
- const handleModalClose = (): void => {
- setIsAppendModalOpen(false);
- };
- return (
- // 父级是flex,这里是grid,grid的高度需要设置为0,并且flex-grow,这样才能不撑开grid
- <div className={`${layout} grid grid-rows-[1fr_auto] h-0 flex-grow`}>
- <div className="overflow-y-auto flex flex-col ">
- {bodyPositions.map((bodyPosition, index) => (
- <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)}
- onDoubleClick={() => handleImageDoubleClick(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 === 'Reject' ? (
- <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>
- {showAddButton && (
- <div
- className="mx-auto cursor-pointer"
- style={{ width: '50%' }}
- onClick={addBodyPositionClick}
- >
- <div
- style={{
- stroke: 'var(--color-primary)',
- strokeWidth: 0.5,
- color: 'var(--color-text)',
- }}
- >
- <AppendViewIcon className="w-full h-full hover:opacity-100 " />
- </div>
- </div>
- )}
- <AppendViewModal open={isAppendModalOpen} onCancel={handleModalClose} />
- </div>
- );
- };
- export default BodyPositionList;
|