123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- import {
- Row,
- Col,
- Select,
- InputNumber,
- Button,
- Switch,
- Divider,
- Tooltip,
- message,
- Flex,
- } from 'antd';
- import ErrorMessage from '@/components/ErrorMessage';
- import { patientSizes, PatientSize } from '../../states/patientSize';
- import { WorkstationTypeLabels } from '../../states/workstation';
- import { FormattedMessage } from 'react-intl';
- import { useSelector, useDispatch, useStore } from 'react-redux';
- import { deleteBodyPosition } from '../../API/patient/viewActions';
- import { copyPositionThunk } from '../../states/exam/examWorksCacheSlice';
- import {
- removeBodyPositionBySopInstanceUid,
- setByIndex,
- } from '../../states/exam/bodyPositionListSlice';
- import { RootState } from '../../states/store';
- import {
- setAprConfig,
- setBodysize,
- setWorkstation,
- setThickness,
- setIsAECEnabled,
- } from '../../states/exam/aprSlice';
- import BodyPositionList from './components/BodyPositionList';
- import BodyPositionDetail from './components/BodyPositionDetail';
- import { AppDispatch } from '@/states/store';
- import { useRef } from 'react';
- import Icon from '@/components/Icon';
- import ParaSettingCoordinator from '@/domain/exam/paraSettingCoordinator';
- import { resetDevices } from '@/states/device/deviceSlice';
- import { openCamera, closeCamera } from '@/states/exam/cameraSlice';
- import CameraModal from '@/components/CameraModal';
- const ContentAreaLarge = () => {
- const dispatch = useDispatch<AppDispatch>();
- const isResetting = useSelector(
- (state: RootState) => state.device.status === 'loading'
- );
- const store = useStore<RootState>();
- const aprConfig = useSelector((state: RootState) => state.apr.aprConfig);
- const bodysize = useSelector((state: RootState) => state.apr.bodysize);
- const workstation = useSelector((state: RootState) => state.apr.workstation);
- const thickness = useSelector((state: RootState) => state.apr.thickness);
- const isAECEnabled = useSelector(
- (state: RootState) => state.apr.isAECEnabled
- );
- const currentExposureMode = useSelector(
- (state: RootState) => state.apr.currentExposureMode
- );
- const productName = useSelector(
- (state: RootState) => state.product.productName
- );
- const handleBodysizeChange = (key: string) => {
- const value = patientSizes[key as PatientSize]; // 获取对应的显示文本
- console.log('体型 key:', key); // 例如: 'small'
- console.log('体型 value:', value); // 例如: 'Small'
- dispatch(setBodysize(value));
- };
- const handleWorkstationChange = (value: string) => {
- dispatch(setWorkstation(value));
- };
- const handleThicknessChange = (value: number | null) => {
- if (value !== null) {
- dispatch(setThickness(value));
- }
- };
- const handleAECChange = (checked: boolean) => {
- dispatch(setIsAECEnabled(checked));
- };
- const handleExposureModeChange = (value: string) => {
- ParaSettingCoordinator.setExposureMode(value);
- };
- const handleResetParameters = async () => {
- try {
- await dispatch(resetDevices());
- } catch (error) {
- console.error('Error resetting devices:', error);
- }
- };
- /**
- * 处理打开摄像头按钮点击事件
- */
- const handleOpenCamera = () => {
- // 获取当前选中体位的 study_id
- const currentStudyId = selectedBodyPosition?.study_id;
- if (!currentStudyId) {
- message.warning('请先选择一个体位');
- return;
- }
- console.log('[handleOpenCamera] 打开摄像头,Study ID:', currentStudyId);
-
- // dispatch openCamera action,打开摄像头
- dispatch(openCamera(currentStudyId));
- };
- // 1. 正常在顶层用 useSelector 订阅
- const selectedBodyPosition = useSelector(
- (state: RootState) => state.bodyPositionList.selectedBodyPosition
- );
- // 2. 用 ref 保存最新值(每次渲染都会更新)
- const positionRef = useRef(selectedBodyPosition);
- positionRef.current = selectedBodyPosition;
- // 3. 订阅 camera 状态
- const cameraIsOpen = useSelector((state: RootState) => state.camera.isOpen);
- const cameraStudyId = useSelector(
- (state: RootState) => state.camera.currentStudyId
- );
- return (
- <Row className="w-full p-1" style={{ height: '100%', display: 'flex' }}>
- <Col span={20} style={{ display: 'flex', flexDirection: 'column' }}>
- <Row gutter={16} style={{ flex: 1, minHeight: 0, display: 'flex' }}>
- <Col span={4} className="flex flex-col">
- <BodyPositionList layout="vertical"></BodyPositionList>
- </Col>
- <Col span={20}>
- <BodyPositionDetail />
- </Col>
- </Row>
- </Col>
- <Col
- span={4}
- style={{ height: '100%', overflowY: 'auto', flexShrink: 0 }}
- >
- <Row gutter={16} align="middle">
- <Col span={productName === 'DROS' ? 9 : 12}>
- <Select
- placeholder="选择体型"
- style={{ width: '100%', marginBottom: 8 }}
- value={bodysize}
- onChange={handleBodysizeChange}
- >
- {Object.entries(patientSizes).map(
- ([key, value]: [string, string]) => (
- <Select.Option key={key} value={key}>
- <FormattedMessage id={value} />
- </Select.Option>
- )
- )}
- </Select>
- </Col>
- <Col span={productName === 'DROS' ? 9 : 12}>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- 厚度 (cm)
- </label>
- <InputNumber
- placeholder="厚度"
- style={{ width: '100%', marginBottom: 8 }}
- value={thickness || undefined}
- min={1}
- max={50}
- step={1}
- onChange={handleThicknessChange}
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseThickness();
- } else {
- ParaSettingCoordinator.decreaseThickness();
- }
- }}
- />
- </Col>
- {productName === 'DROS' && (
- <Col span={15}>
- <Select
- placeholder="选择工作位"
- style={{ width: '100%', marginBottom: 8 }}
- value={workstation}
- onChange={handleWorkstationChange}
- >
- {Object.entries(WorkstationTypeLabels).map(
- ([key, value]: [string, string]) => (
- <Select.Option key={key} value={value}>
- <FormattedMessage
- id={`workstation.${key.toLowerCase()}`}
- />
- </Select.Option>
- )
- )}
- </Select>
- </Col>
- )}
- </Row>
- <div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- mA
- </label>
- <Tooltip
- title={
- currentExposureMode === 'mAs'
- ? '当前为 mAs 模式,mA 和 ms 不可调整'
- : ''
- }
- >
- <InputNumber
- disabled={currentExposureMode === 'mAs'}
- placeholder={currentExposureMode === 'mAs' ? '--' : 'mA'}
- style={{ width: '100%', marginBottom: 8 }}
- value={
- currentExposureMode === 'mAs'
- ? null
- : (aprConfig.mA ?? undefined)
- }
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseMA();
- } else {
- ParaSettingCoordinator.decreaseMA();
- }
- return false;
- }}
- />
- </Tooltip>
- </div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- ms
- </label>
- <Tooltip
- title={
- currentExposureMode === 'mAs'
- ? '当前为 mAs 模式,mA 和 ms 不可调整'
- : ''
- }
- >
- <InputNumber
- disabled={currentExposureMode === 'mAs'}
- placeholder={currentExposureMode === 'mAs' ? '--' : 'ms'}
- style={{ width: '100%', marginBottom: 8 }}
- value={
- currentExposureMode === 'mAs'
- ? null
- : (aprConfig.ms ?? undefined)
- }
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseMS();
- } else {
- ParaSettingCoordinator.decreaseMS();
- }
- }}
- />
- </Tooltip>
- </div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- mAs
- </label>
- <Tooltip
- title={
- currentExposureMode === 'time'
- ? '当前为 time 模式,mAs 不可调整'
- : ''
- }
- >
- <InputNumber
- disabled={currentExposureMode === 'time'}
- placeholder={currentExposureMode === 'time' ? '--' : 'mAs'}
- style={{ width: '100%', marginBottom: 8 }}
- value={
- currentExposureMode === 'time'
- ? null
- : (aprConfig.mAs ?? undefined)
- }
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseMAS();
- } else {
- ParaSettingCoordinator.decreaseMAS();
- }
- }}
- />
- </Tooltip>
- </div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- KV
- </label>
- <InputNumber
- placeholder="KV"
- style={{ width: '100%', marginBottom: 8 }}
- value={aprConfig.kV ?? undefined}
- // onChange={(value) =>
- // dispatch(setAprConfig({ ...aprConfig, kV: value ?? 0 }))
- // }
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseKV();
- } else {
- ParaSettingCoordinator.decreaseKV();
- }
- }}
- />
- </div>
- {/* <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- density
- </label>
- <InputNumber
- placeholder="density"
- style={{ width: '100%', marginBottom: 8 }}
- value={aprConfig.AECDensity ?? undefined}
- onChange={(value) =>
- dispatch(setAprConfig({ ...aprConfig, AECDensity: value ?? 0 }))
- }
- onStep={(value, info) => {
- if (info.type === 'up') {
- ParaSettingCoordinator.increaseDensity();
- } else {
- ParaSettingCoordinator.decreaseDensity();
- }
- }}
- />
- </div> */}
- </div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- 曝光模式
- </label>
- <Select
- placeholder="选择曝光模式"
- value={currentExposureMode}
- onChange={handleExposureModeChange}
- style={{ width: '100%', marginBottom: 8 }}
- >
- <Select.Option value="mAs">mAs</Select.Option>
- <Select.Option value="time">time</Select.Option>
- </Select>
- </div>
- <div>
- <label
- style={{ display: 'block', marginBottom: 4, fontSize: '12px' }}
- >
- AEC
- </label>
- <Switch
- checkedChildren="开启AEC"
- unCheckedChildren="关闭AEC"
- checked={isAECEnabled}
- onChange={handleAECChange}
- style={{ marginBottom: 8 }}
- />
- </div>
- <Flex align="center" justify="start" gap="middle" wrap>
- <Button
- data-testid="reset-generator-btn"
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_ResetGenerator"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- title="重置参数"
- onClick={handleResetParameters}
- disabled={isResetting}
- />
- </Flex>
- <Divider />
- <Flex wrap gap="small">
- <Tooltip title="删除选择的体位">
- <Button
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_DeleteView"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- onClick={async () => {
- const state = store.getState().bodyPositionList;
- const selectedBodyPosition = state.selectedBodyPosition;
- const bodyPositions = state.bodyPositions;
- // 检查体位数量,至少保留一个
- if (bodyPositions.length <= 1) {
- message.warning('至少需要保留一个体位,无法删除');
- return;
- }
- console.log(
- `选中的体位:${JSON.stringify(selectedBodyPosition)}`
- );
- if (
- selectedBodyPosition &&
- selectedBodyPosition.sop_instance_uid
- ) {
- try {
- await deleteBodyPosition(
- selectedBodyPosition.sop_instance_uid
- );
- dispatch(
- removeBodyPositionBySopInstanceUid(
- selectedBodyPosition.sop_instance_uid
- )
- );
- dispatch(setByIndex(0));
- } catch (error) {
- console.error('Error deleting body position:', error);
- message.error('Failed to delete body position');
- }
- }
- }}
- />
- </Tooltip>
- <Tooltip title="复制选择的体位">
- <Button
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_Copy"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- onClick={() => {
- const instanceUid =
- store.getState().bodyPositionList.selectedBodyPosition
- ?.study_instance_uid ?? '';
- console.log('Copying position for instance UID:', instanceUid);
- console.log(
- `${store.getState().bodyPositionList.selectedBodyPosition}`
- );
- dispatch(copyPositionThunk({ instanceUid }));
- }}
- />
- </Tooltip>
- <Tooltip title="保存参数">
- <Button
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_Save"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- />
- </Tooltip>
- <Tooltip title="打开/关闭摄像头">
- <Button
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_OpenCamera"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- onClick={handleOpenCamera}
- />
- </Tooltip>
- <Tooltip title="拒绝">
- <Button
- style={{ width: '1.5rem', height: '1.5rem' }}
- icon={
- <Icon
- module="module-exam"
- name="btn_RejectImage"
- userId="base"
- theme="default"
- size="2x"
- state="normal"
- />
- }
- />
- </Tooltip>
- </Flex>
- <ErrorMessage />
- </Col>
- {/* 摄像头 Modal */}
- {cameraIsOpen && cameraStudyId && (
- <CameraModal
- visible={cameraIsOpen}
- studyId={cameraStudyId}
- onClose={() => dispatch(closeCamera())}
- />
- )}
- </Row>
- );
- };
- export default ContentAreaLarge;
|