Browse Source

实现注册页面的协议列表互动,响应用户操作,可选择体位

dengdx 2 months ago
parent
commit
73898047f9

+ 1 - 1
config/dev.ts

@@ -10,7 +10,7 @@ export default {
     devServer: {
     devServer: {
       proxy: {
       proxy: {
         '/dr': {
         '/dr': {
-          target: 'http://192.168.11.4:6001', // 你的后端服务地址
+          target: 'http://101.43.219.60:7700', // 你的后端服务地址
           changeOrigin: true, // 允许跨域
           changeOrigin: true, // 允许跨域
           // pathRewrite: {
           // pathRewrite: {
           //   '^/dr/api': '' // 可选,用于重写路径
           //   '^/dr/api': '' // 可选,用于重写路径

+ 11 - 0
src/pages/patient/components/ProcedureCard.md

@@ -0,0 +1,11 @@
+# 关于
+
+在注册页面,作为协议列表中的列表项
+
+# 实现
+
+使用ant design组件库
+
+使用antd的card组件为基础。
+
+响应点击,触发viewSelection中的reducer:addProtocolViews,实现添加procedure下的所有view到已选择体位列表。

+ 38 - 0
src/pages/patient/components/ProcedureCard.tsx

@@ -0,0 +1,38 @@
+import React from 'react';
+import { Card } from 'antd';
+import { useDispatch } from 'react-redux';
+import { addProtocolViews } from '../../../states/patient/viewSelection';
+import type { Procedure } from '../../../states/patient/viewSelection';
+import type { CardProps } from 'antd';
+
+interface ProcedureCardProps extends CardProps {
+  procedure: Procedure;
+}
+
+const ProcedureCard: React.FC<ProcedureCardProps> = ({ procedure }) => {
+  const dispatch = useDispatch();
+
+  const handleClick = () => {
+    dispatch(addProtocolViews(procedure));
+  };
+
+  return (
+    <Card
+      hoverable
+      onClick={handleClick}
+      style={{
+        width: '100%',
+        height: '100%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        cursor: 'pointer',
+      }}
+      bodyStyle={{ padding: 0, textAlign: 'center' }}
+    >
+      {procedure.ProcedureName}
+    </Card>
+  );
+};
+
+export default ProcedureCard;

+ 56 - 0
src/pages/patient/components/ProcedureViewCard.tsx

@@ -0,0 +1,56 @@
+import React from 'react';
+import { Card } from 'antd';
+import { useDispatch } from 'react-redux';
+import type { Procedure } from '../../../states/patient/viewSelection';
+import { addProtocolViews } from '../../../states/patient/viewSelection';
+
+interface ProcedureViewCardProps {
+  protocol: Procedure;
+  selected?: boolean;
+}
+
+const ProcedureViewCard: React.FC<ProcedureViewCardProps> = ({
+  protocol,
+  selected,
+}) => {
+  const dispatch = useDispatch();
+
+  const handleClick = () => {
+    dispatch(addProtocolViews(protocol));
+  };
+
+  return (
+    <Card
+      hoverable
+      style={{
+        marginBottom: 12,
+        borderColor: selected ? '#1890ff' : undefined,
+        boxShadow: selected ? '0 0 0 2px #1890ff33' : undefined,
+      }}
+      onClick={handleClick}
+      title={protocol.ProcedureName}
+    >
+      <div>
+        <div>
+          <strong>代码:</strong>
+          {protocol.ProcedureCode}
+        </div>
+        <div>
+          <strong>别名:</strong>
+          {protocol.ProcedureOtherName}
+        </div>
+        <div>
+          <strong>描述:</strong>
+          {protocol.ProcedureDescription}
+        </div>
+        <div>
+          <strong>适用类型:</strong>
+          {protocol.PatientType}
+        </div>
+        {/* 可根据需要展示更多字段 */}
+      </div>
+    </Card>
+  );
+};
+
+export default ProcedureViewCard;

+ 40 - 0
src/pages/patient/components/register.available.list.tsx

@@ -0,0 +1,40 @@
+import React, { useState } from 'react';
+import { Segmented, Card } from 'antd';
+import ProtocolSelectionList from './register.protocol.list';
+import RegisterViewList from './register.available.view.list';
+
+const RegisterAvailableList: React.FC = () => {
+  const [selected, setSelected] = useState<'protocol' | 'view'>('protocol');
+
+  return (
+    <Card
+      style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
+      bodyStyle={{
+        flex: 1,
+        display: 'flex',
+        flexDirection: 'column',
+        padding: 0,
+      }}
+    >
+      <div style={{ padding: 16, borderBottom: '1px solid #f0f0f0' }}>
+        <Segmented
+          options={[
+            { label: '协议', value: 'protocol' },
+            { label: '体位', value: 'view' },
+          ]}
+          value={selected}
+          onChange={(val) => setSelected(val as 'protocol' | 'view')}
+        />
+      </div>
+      <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
+        {selected === 'protocol' ? (
+          <ProtocolSelectionList />
+        ) : (
+          <RegisterViewList />
+        )}
+      </div>
+    </Card>
+  );
+};
+
+export default RegisterAvailableList;

+ 47 - 0
src/pages/patient/components/register.available.view.list.tsx

@@ -0,0 +1,47 @@
+import React from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { Card, Row, Col, Empty } from 'antd';
+
+import type { RootState } from '@/states/store'; // 假设RootState已定义
+import { View } from '@/states/patient/viewSelection';
+import { addSelectedView } from '@/states/patient/viewSelection';
+
+const RegisterViewList: React.FC = () => {
+  // 监听selectedViews
+  const availableViews = useSelector(
+    (state: RootState) => state.viewSelection.availableViews
+  );
+  const dispatch = useDispatch();
+
+  const handleCardClick = (view: View) => {
+    dispatch(addSelectedView(view));
+  };
+
+  return (
+    <div>
+      <Row gutter={[16, 16]}>
+        {availableViews.length === 0 ? (
+          <Col span={24}>
+            <Empty description="暂无已选择体位" />
+          </Col>
+        ) : (
+          availableViews.map((view) => (
+            <Col key={view.internal_id} xs={24} sm={12} md={8} lg={6}>
+              {/* 若有自定义ProcedureViewCard组件可替换Card */}
+              <Card
+                title={view.view_name}
+                bordered
+                onClick={() => handleCardClick(view)}
+              >
+                <div>{view.view_description}</div>
+                {/* 可根据需要展示更多字段 */}
+              </Card>
+            </Col>
+          ))
+        )}
+      </Row>
+    </div>
+  );
+};
+
+export default RegisterViewList;

+ 16 - 69
src/pages/patient/components/register.protocol.list.tsx

@@ -1,66 +1,12 @@
 import React from 'react';
 import React from 'react';
-import { Card, Grid } from 'antd';
-import { FormattedMessage } from 'react-intl';
+import { Grid } from 'antd';
+import { useSelector } from 'react-redux';
+// import { FormattedMessage } from 'react-intl';
 
 
-const { useBreakpoint } = Grid;
+import type { RootState } from '@/states/store'; // 假设RootState已定义
+import ProcedureCard from './ProcedureCard'; // 若已实现
 
 
-// 示例协议数据
-const protocols = [
-  {
-    id: 1,
-    name: (
-      <FormattedMessage
-        id="register.protocol.A"
-        defaultMessage="register.protocol.A"
-      />
-    ),
-  },
-  {
-    id: 2,
-    name: (
-      <FormattedMessage
-        id="register.protocol.B"
-        defaultMessage="register.protocol.B"
-      />
-    ),
-  },
-  {
-    id: 3,
-    name: (
-      <FormattedMessage
-        id="register.protocol.C"
-        defaultMessage="register.protocol.C"
-      />
-    ),
-  },
-  {
-    id: 4,
-    name: (
-      <FormattedMessage
-        id="register.protocol.D"
-        defaultMessage="register.protocol.D"
-      />
-    ),
-  },
-  {
-    id: 5,
-    name: (
-      <FormattedMessage
-        id="register.protocol.E"
-        defaultMessage="register.protocol.E"
-      />
-    ),
-  },
-  {
-    id: 6,
-    name: (
-      <FormattedMessage
-        id="register.protocol.F"
-        defaultMessage="register.protocol.F"
-      />
-    ),
-  },
-];
+const { useBreakpoint } = Grid;
 
 
 // 卡片尺寸配置
 // 卡片尺寸配置
 const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
 const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
@@ -77,6 +23,11 @@ const ProtocolSelectionList: React.FC = () => {
   const screens = useBreakpoint();
   const screens = useBreakpoint();
   const cardSize = getCardSize(screens);
   const cardSize = getCardSize(screens);
 
 
+  // 监听redux中的protocols
+  const protocols = useSelector(
+    (state: RootState) => state.viewSelection.protocols
+  );
+
   return (
   return (
     <div
     <div
       style={{
       style={{
@@ -88,22 +39,18 @@ const ProtocolSelectionList: React.FC = () => {
       }}
       }}
     >
     >
       {protocols.map((item) => (
       {protocols.map((item) => (
-        <Card
-          key={item.id}
-          hoverable
+        <ProcedureCard
+          key={item.ProcedureID}
+          procedure={item}
           style={{
           style={{
             width: cardSize.width,
             width: cardSize.width,
             height: cardSize.height,
             height: cardSize.height,
             display: 'flex',
             display: 'flex',
             alignItems: 'center',
             alignItems: 'center',
             justifyContent: 'center',
             justifyContent: 'center',
-            marginBottom: 16,
-            marginRight: 16,
+            cursor: 'pointer',
           }}
           }}
-          bodyStyle={{ padding: 0, textAlign: 'center' }}
-        >
-          {item.name}
-        </Card>
+        />
       ))}
       ))}
     </div>
     </div>
   );
   );

+ 18 - 41
src/pages/patient/components/register.selected.protocol.list.tsx → src/pages/patient/components/register.selected.view.list.tsx

@@ -1,41 +1,13 @@
 import React from 'react';
 import React from 'react';
-import { Card, Grid, Button, Flex } from 'antd';
+import { Card, Grid, Button, Row } from 'antd';
 import { CloseOutlined } from '@ant-design/icons';
 import { CloseOutlined } from '@ant-design/icons';
 import { FormattedMessage } from 'react-intl';
 import { FormattedMessage } from 'react-intl';
+import { useSelector } from 'react-redux';
+import type { RootState } from '@/states/store';
+import type { View } from '@/states/patient/viewSelection';
 
 
 const { useBreakpoint } = Grid;
 const { useBreakpoint } = Grid;
 
 
-// 示例已选协议数据
-const selectedProtocols = [
-  {
-    id: 1,
-    name: (
-      <FormattedMessage
-        id="register.selectedProtocol.A"
-        defaultMessage="register.selectedProtocol.A"
-      />
-    ),
-  },
-  {
-    id: 2,
-    name: (
-      <FormattedMessage
-        id="register.selectedProtocol.B"
-        defaultMessage="register.selectedProtocol.B"
-      />
-    ),
-  },
-  {
-    id: 3,
-    name: (
-      <FormattedMessage
-        id="register.selectedProtocol.C"
-        defaultMessage="register.selectedProtocol.C"
-      />
-    ),
-  },
-];
-
 // 卡片尺寸配置
 // 卡片尺寸配置
 const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
 const getCardSize = (screens: ReturnType<typeof useBreakpoint>) => {
   if (screens.xl || screens.xxl) {
   if (screens.xl || screens.xxl) {
@@ -54,18 +26,23 @@ const SelectedProtocolList: React.FC<SelectedProtocolListProps> = () => {
   const screens = useBreakpoint();
   const screens = useBreakpoint();
   const cardSize = getCardSize(screens);
   const cardSize = getCardSize(screens);
 
 
+  // 从redux获取selectedViews
+  const selectedViews = useSelector(
+    (state: RootState) => state.viewSelection.selectedViews
+  );
+
   // 移除操作(实际项目可用props传递onRemove)
   // 移除操作(实际项目可用props传递onRemove)
-  const handleRemove = (id: number) => {
+  const handleRemove = (id: string) => {
     // 这里仅做演示
     // 这里仅做演示
     // 实际应通过props回调或状态管理移除
     // 实际应通过props回调或状态管理移除
-    alert(`移除协议ID: ${id}`);
+    alert(`移除体位ID: ${id}`);
   };
   };
-  // { display: 'flex', flexWrap: 'wrap', gap: 16 }
+
   return (
   return (
-    <Flex>
-      {selectedProtocols.map((item) => (
+    <Row>
+      {selectedViews.map((item: View) => (
         <Card
         <Card
-          key={item.id}
+          key={item.internal_id}
           hoverable
           hoverable
           style={{
           style={{
             width: cardSize.width,
             width: cardSize.width,
@@ -95,13 +72,13 @@ const SelectedProtocolList: React.FC<SelectedProtocolListProps> = () => {
                 objectFit: 'cover',
                 objectFit: 'cover',
               }}
               }}
             />
             />
-            <span style={{ marginTop: 4 }}>{item.name}</span>
+            <span style={{ marginTop: 4 }}>{item.view_name}</span>
           </div>
           </div>
           <Button
           <Button
             type="text"
             type="text"
             size="small"
             size="small"
             icon={<CloseOutlined />}
             icon={<CloseOutlined />}
-            onClick={() => handleRemove(item.id)}
+            onClick={() => handleRemove(item.internal_id)}
             style={{
             style={{
               position: 'absolute',
               position: 'absolute',
               top: 2,
               top: 2,
@@ -117,7 +94,7 @@ const SelectedProtocolList: React.FC<SelectedProtocolListProps> = () => {
           />
           />
         </Card>
         </Card>
       ))}
       ))}
-    </Flex>
+    </Row>
   );
   );
 };
 };
 
 

+ 9 - 0
src/pages/patient/components/register.view.list.md

@@ -0,0 +1,9 @@
+# 描述
+
+待选择体位列表,展示待选择体位卡片
+
+# 实现
+
+使用ant design组件库
+
+监控viewSelection中定义的selectedViews。根据selectedViews的内容渲染ProcedureViewCard

+ 6 - 5
src/pages/patient/register.tsx

@@ -2,8 +2,9 @@ import React from 'react';
 import { Row, Col, Collapse, Grid, Divider } from 'antd';
 import { Row, Col, Collapse, Grid, Divider } from 'antd';
 import { FormattedMessage } from 'react-intl';
 import { FormattedMessage } from 'react-intl';
 import BasicInfoForm from './components/register.form';
 import BasicInfoForm from './components/register.form';
-import ProtocolList from './components/register.protocol.list';
-import SelectedProtocolList from './components/register.selected.protocol.list';
+// import ProtocolList from './components/register.protocol.list';
+import SelectedProtocolList from './components/register.selected.view.list';
+import RegisterAvailableList from './components/register.available.list';
 
 
 const { useBreakpoint } = Grid;
 const { useBreakpoint } = Grid;
 const { Panel } = Collapse;
 const { Panel } = Collapse;
@@ -36,7 +37,7 @@ const RegisterPage: React.FC = () => {
           <BasicInfoForm style={{ overflow: 'auto' }} />
           <BasicInfoForm style={{ overflow: 'auto' }} />
         </Col>
         </Col>
         <Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} className="h-full">
         <Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} className="h-full">
-          <ProtocolList />
+          <RegisterAvailableList />
         </Col>
         </Col>
         <Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8}>
         <Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8}>
           <SelectedProtocolList />
           <SelectedProtocolList />
@@ -61,7 +62,7 @@ const RegisterPage: React.FC = () => {
         <Col xs={24} sm={24} md={12} lg={12}>
         <Col xs={24} sm={24} md={12} lg={12}>
           <Row gutter={[0, 16]}>
           <Row gutter={[0, 16]}>
             <Col span={24}>
             <Col span={24}>
-              <ProtocolList />
+              <RegisterAvailableList />
             </Col>
             </Col>
             <Divider />
             <Divider />
             <Col span={24}>
             <Col span={24}>
@@ -101,7 +102,7 @@ const RegisterPage: React.FC = () => {
           }
           }
           key="2"
           key="2"
         >
         >
-          <ProtocolList />
+          <RegisterAvailableList />
         </Panel>
         </Panel>
         <Panel
         <Panel
           header={
           header={

+ 160 - 0
src/states/patient/viewSelection/index.ts

@@ -0,0 +1,160 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+// 体位类型
+export interface View {
+  internal_id: string;
+  view_id: string;
+  view_name: string;
+  view_name_local: string;
+  view_other_name: string;
+  view_description: string;
+  view_position: string;
+  application: string;
+  anatomic_region: string;
+  patient_type: string;
+  body_part_id: string;
+  view_icon_name: string;
+  view_big_icon_name: string;
+  view_coach_name: string;
+  modality: string;
+  // config_object: any;
+  tech_template: string;
+  img_proc_template: string;
+  sort: number;
+  is_enabled: boolean;
+  product: string;
+  is_pre_install: boolean;
+}
+
+// 协议类型
+export interface Procedure {
+  ProcedureID: string;
+  ProcedureCode: string;
+  ProcedureName: string;
+  ProcedureOtherName: string;
+  ProcedureDescription: string;
+  PatientType: string;
+  ProcedureGroupID: string;
+  ProcedureType: string;
+  FastSearch: boolean;
+  Enable: boolean;
+  Order: number;
+  UserGroupID: string;
+  ProcedureCategory: string;
+  Modality: string;
+  IsImplanted: boolean;
+  MagFactor: number;
+  // ProcedureViews: View[];
+  // ProcedureViewRelations: any[];
+  ClinicProtocol: boolean;
+  IsFactoryDefault: boolean;
+  MinBMI: number;
+  MaxBMI: number;
+  AutoDecompression: boolean;
+  ConfigObjectValue: string;
+}
+
+interface ViewSelectionState {
+  selectedViews: View[]; // 已选择体位列表
+  availableViews: View[]; // 待选择体位列表
+  protocols: Procedure[]; // 协议列表(只会出现在待选择列表)
+}
+
+//type SelectionType = 'protocol' | 'view';
+
+interface ViewSelectionState {
+  selectedViews: View[]; // 已选择体位列表
+  availableViews: View[]; // 待选择体位列表
+  protocols: Procedure[]; // 协议列表(只会出现在待选择列表)
+  //selectionType: SelectionType; // 可选择值为 'protocol' | 'view'
+}
+
+const initialState: ViewSelectionState = {
+  selectedViews: [],
+  availableViews: [
+    {
+      internal_id: '1',
+      view_id: 'AP',
+      view_name: 'Anteroposterior',
+      view_name_local: '前后位',
+      view_other_name: 'AP View',
+      view_description: '前后体位描述',
+      view_position: 'Standing',
+      application: 'General',
+      anatomic_region: 'Chest',
+      patient_type: 'Adult',
+      body_part_id: 'CHEST',
+      view_icon_name: 'ap_icon.png',
+      view_big_icon_name: 'ap_big_icon.png',
+      view_coach_name: 'ap_coach.png',
+      modality: 'X-Ray',
+      tech_template: 'default',
+      img_proc_template: 'default',
+      sort: 1,
+      is_enabled: true,
+      product: 'Standard',
+      is_pre_install: true,
+    },
+    {
+      internal_id: '2',
+      view_id: 'LAT',
+      view_name: 'Lateral',
+      view_name_local: '侧位',
+      view_other_name: 'LAT View',
+      view_description: '侧体位描述',
+      view_position: 'Standing',
+      application: 'General',
+      anatomic_region: 'Chest',
+      patient_type: 'Adult',
+      body_part_id: 'CHEST',
+      view_icon_name: 'lat_icon.png',
+      view_big_icon_name: 'lat_big_icon.png',
+      view_coach_name: 'lat_coach.png',
+      modality: 'X-Ray',
+      tech_template: 'default',
+      img_proc_template: 'default',
+      sort: 2,
+      is_enabled: true,
+      product: 'Standard',
+      is_pre_install: true,
+    },
+  ],
+  protocols: [],
+  //selectionType: 'protocol',
+};
+
+const viewSelectionSlice = createSlice({
+  name: 'viewSelection',
+  initialState,
+  reducers: {
+    // 添加体位到已选择体位列表
+    addSelectedView(state, action: PayloadAction<View>) {
+      state.selectedViews.push(action.payload);
+    },
+    // 添加协议中的所有体位到已选择体位列表
+    // eslint-disable-next-line
+        addProtocolViews(state, action: PayloadAction<Procedure>) {
+      // 假设协议中包含 ProcedureViews 属性,存储体位数组
+      // todo 这里涉及到基于协议查询其名下的体位,然后再添加
+      // if (Array.isArray(action.payload.ProcedureViews)) {
+      //     state.selectedViews.push(...action.payload.ProcedureViews);
+      // }
+    },
+    // 可根据需要添加其它 reducer,例如设置 availableViews、Procedures 等
+    setAvailableViews(state, action: PayloadAction<View[]>) {
+      state.availableViews = action.payload;
+    },
+    setProtocols(state, action: PayloadAction<Procedure[]>) {
+      state.protocols = action.payload;
+    },
+  },
+});
+
+export const {
+  addSelectedView,
+  addProtocolViews,
+  setAvailableViews,
+  setProtocols,
+} = viewSelectionSlice.actions;
+
+export default viewSelectionSlice.reducer;

+ 2 - 0
src/states/store.ts

@@ -1,9 +1,11 @@
 import { configureStore } from '@reduxjs/toolkit';
 import { configureStore } from '@reduxjs/toolkit';
 import userInfoReducer from './user_info';
 import userInfoReducer from './user_info';
+import viewSelectionReducer from './patient/viewSelection';
 
 
 const store = configureStore({
 const store = configureStore({
   reducer: {
   reducer: {
     userInfo: userInfoReducer,
     userInfo: userInfoReducer,
+    viewSelection: viewSelectionReducer,
   },
   },
 });
 });