Эх сурвалжийг харах

Refactor medical views and add blood test dialog

gavin.chen 1 жил өмнө
parent
commit
cf33350a5d

+ 104 - 195
lib/pages/medical/views/biochemistry_test.dart

@@ -1,216 +1,125 @@
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'package:vitalapp/pages/medical/controller.dart';
-
-class BiochemistryData {
-  final String key;
-  final String name;
-  final String unit;
-
-  const BiochemistryData({
-    required this.key,
-    required this.name,
-    required this.unit,
-  });
-}
-
-class BiochemistryResult {
-  final String key;
-  final String result;
-
-  const BiochemistryResult({
-    required this.key,
-    required this.result,
-  });
-}
+import 'package:vitalapp/pages/medical/views/table_input_dialog/index.dart';
 
 class BiochemistryTest extends GetView<MedicalController> {
   const BiochemistryTest({super.key});
 
-  final List<BiochemistryData> mockData = const [
-    BiochemistryData(key: "0", name: "血清谷丙转氨酶", unit: "U/L"),
-    BiochemistryData(key: "1", name: "血清谷草转氨酶", unit: "U/L"),
-    BiochemistryData(key: "2", name: "总胆红素", unit: "umol/L"),
-    BiochemistryData(key: "3", name: "白蛋白", unit: "g/L"),
-    BiochemistryData(key: "4", name: "结合胆红素", unit: "umol/L"),
-  ];
-
   @override
   Widget build(BuildContext context) {
-    final TextEditingController biochemistryController =
-        TextEditingController();
     return Scaffold(
       resizeToAvoidBottomInset: false,
       body: Container(
         height: double.maxFinite,
         color: Colors.white,
-        child: Column(
-          children: [
-            _buildTitle(),
-            for (var item in mockData)
-              _buildInputElement(
-                item.name,
-                biochemistryController,
-                item.unit,
-              ),
-            // _buildInputElement(
-            //   mockData[0].name,
-            //   biochemistryController,
-            //   mockData[0].unit,
-            // ),
-          ],
-        ),
-      ),
-    );
-  }
-
-  // 构建表格标题 [项目名称|检查结果|单位]
-  _buildTitle() {
-    return Row(
-      children: [
-        Expanded(
-          child: Container(
-            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-            decoration: BoxDecoration(
-              border: Border(
-                bottom: BorderSide(
-                  color: Colors.grey[300]!,
-                  width: 1,
-                ),
-              ),
-            ),
-            child: Text(
-              '项目名称',
-              style: TextStyle(
-                fontSize: 14,
-                fontWeight: FontWeight.bold,
-                color: Colors.grey[800],
-              ),
-            ),
-          ),
-        ),
-        Expanded(
-          child: Container(
-            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-            decoration: BoxDecoration(
-              border: Border(
-                bottom: BorderSide(
-                  color: Colors.grey[300]!,
-                  width: 1,
-                ),
+        child: Center(
+          // button open dialog
+          child: Column(
+            children: [
+              ElevatedButton(
+                onPressed: () async {
+                  TableInputResult? result = await Get.dialog<TableInputResult>(
+                    TableInputDialog(
+                      // FIXME 示例代码
+                      tableDataConfig: [
+                        TableElementConfig(
+                          id: "0",
+                          name: "血清谷丙转氨酶",
+                          unit: "U/L",
+                        ),
+                        TableElementConfig(
+                          id: "1",
+                          name: "血清谷草转氨酶",
+                          unit: "U/L",
+                        ),
+                        TableElementConfig(
+                          id: "2",
+                          name: "总胆红素",
+                          unit: "umol/L",
+                        ),
+                        TableElementConfig(
+                          id: "3",
+                          name: "白蛋白",
+                          unit: "g/L",
+                        ),
+                        TableElementConfig(
+                          id: "4",
+                          name: "结合胆红素",
+                          unit: "umol/L",
+                        ),
+                        TableElementConfig(
+                          id: "5",
+                          name: "血清谷丙转氨酶",
+                          unit: "U/L",
+                        ),
+                      ],
+                    ),
+                  );
+                  // 如果不为空,print出来
+                  if (result != null) {
+                    result.data.forEach((key, value) {
+                      print('$key: $value');
+                    });
+                  } else {
+                    print('已取消,无返回值');
+                  }
+                },
+                child: const Text('填写生化数据'),
               ),
-            ),
-            child: Text(
-              '检查结果',
-              style: TextStyle(
-                fontSize: 14,
-                fontWeight: FontWeight.bold,
-                color: Colors.grey[800],
+              ElevatedButton(
+                onPressed: () async {
+                  TableInputResult? result = await Get.dialog<TableInputResult>(
+                    TableInputDialog(
+                      // FIXME 示例代码
+                      tableDataConfig: [
+                        TableElementConfig(
+                            id: "0",
+                            name: "血清谷丙转氨酶",
+                            unit: "U/L",
+                            initValue: "100"),
+                        TableElementConfig(
+                            id: "1",
+                            name: "血清谷草转氨酶",
+                            unit: "U/L",
+                            initValue: "100"),
+                        TableElementConfig(
+                            id: "2",
+                            name: "总胆红素",
+                            unit: "umol/L",
+                            initValue: "100"),
+                        TableElementConfig(
+                            id: "3",
+                            name: "白蛋白",
+                            unit: "g/L",
+                            initValue: "100"),
+                        TableElementConfig(
+                            id: "4",
+                            name: "结合胆红素",
+                            unit: "umol/L",
+                            initValue: "100"),
+                        TableElementConfig(
+                            id: "5",
+                            name: "血清谷丙转氨酶",
+                            unit: "U/L",
+                            initValue: "100"),
+                      ],
+                    ),
+                  );
+                  // 如果不为空,print出来
+                  if (result != null) {
+                    result.data.forEach((key, value) {
+                      print('$key: $value');
+                    });
+                  } else {
+                    print('已取消,无返回值');
+                  }
+                },
+                child: const Text('修改已有生化数据'),
               ),
-            ),
+            ],
           ),
         ),
-        Expanded(
-          child: Container(
-            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-            decoration: BoxDecoration(
-              border: Border(
-                bottom: BorderSide(
-                  color: Colors.grey[300]!,
-                  width: 1,
-                ),
-              ),
-            ),
-            child: Text(
-              '单位',
-              style: TextStyle(
-                fontSize: 14,
-                fontWeight: FontWeight.bold,
-                color: Colors.grey[800],
-              ),
-            ),
-          ),
-        ),
-      ],
-    );
-  }
-
-  // 构建输入框 [项目名称|检查结果(s输入框)|单位]
-  _buildInputElement(
-    String elementName,
-    TextEditingController controller,
-    String unit,
-  ) {
-    return Container(
-      height: 40,
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.stretch,
-        children: [
-          Expanded(
-            child: Container(
-              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-              decoration: BoxDecoration(
-                border: Border(
-                  bottom: BorderSide(
-                    color: Colors.grey[300]!,
-                    width: 1,
-                  ),
-                ),
-              ),
-              child: Text(
-                elementName,
-                style: TextStyle(
-                  fontSize: 14,
-                  color: Colors.grey[600],
-                ),
-              ),
-            ),
-          ),
-          Expanded(
-            child: Container(
-              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-              decoration: BoxDecoration(
-                border: Border(
-                  bottom: BorderSide(
-                    color: Colors.grey[300]!,
-                    width: 1,
-                  ),
-                ),
-              ),
-              child: TextField(
-                controller: controller,
-                decoration: InputDecoration(
-                  border: InputBorder.none,
-                  hintText: '请输入检查结果',
-                  hintStyle: TextStyle(
-                    fontSize: 14,
-                    color: Colors.grey[400],
-                  ),
-                ),
-              ),
-            ),
-          ),
-          Expanded(
-            child: Container(
-              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
-              decoration: BoxDecoration(
-                border: Border(
-                  bottom: BorderSide(
-                    color: Colors.grey[300]!,
-                    width: 1,
-                  ),
-                ),
-              ),
-              child: Text(
-                unit,
-                style: TextStyle(
-                  fontSize: 14,
-                  color: Colors.grey[600],
-                ),
-              ),
-            ),
-          ),
-        ],
       ),
     );
   }

+ 53 - 21
lib/pages/medical/views/blood_test.dart

@@ -1,6 +1,8 @@
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'package:vitalapp/pages/medical/controller.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/controller.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/view.dart';
 
 class BloodTest extends GetView<MedicalController> {
   const BloodTest({super.key});
@@ -12,28 +14,58 @@ class BloodTest extends GetView<MedicalController> {
       body: Container(
         height: double.maxFinite,
         color: Colors.white,
-        child: Row(
-          children: [
-            // Expanded(
-            //   // child: HeartCheckLeft(
-            //   //   onRowTap: (value) async {
-            //   //     Get.dialog(_buildMedical());
-            //   //   },
-            //   ),
-            // ),
-            Container(
-              width: 320,
-              decoration: BoxDecoration(
-                border: Border(
-                  left: BorderSide(
-                    color: Colors.grey[300]!,
-                    width: 1,
-                  ),
+        child: Center(
+          // button open dialog
+          child: ElevatedButton(
+            onPressed: () async {
+              TableInputResult? result = await Get.dialog<TableInputResult>(
+                TableInputDialog(
+                  // FIXME 示例代码
+                  tableDataConfig: [
+                    TableElementConfig(
+                      id: "0",
+                      name: "血清谷丙转氨酶",
+                      unit: "U/L",
+                    ),
+                    TableElementConfig(
+                      id: "1",
+                      name: "血清谷草转氨酶",
+                      unit: "U/L",
+                    ),
+                    TableElementConfig(
+                      id: "2",
+                      name: "总胆红素",
+                      unit: "umol/L",
+                    ),
+                    TableElementConfig(
+                      id: "3",
+                      name: "白蛋白",
+                      unit: "g/L",
+                    ),
+                    TableElementConfig(
+                      id: "4",
+                      name: "结合胆红素",
+                      unit: "umol/L",
+                    ),
+                    TableElementConfig(
+                      id: "5",
+                      name: "血清谷丙转氨酶",
+                      unit: "U/L",
+                    ),
+                  ],
                 ),
-              ),
-              child: Container(),
-            )
-          ],
+              );
+              // 如果不为空,print出来
+              if (result != null) {
+                result.data.forEach((key, value) {
+                  print('$key: $value');
+                });
+              } else {
+                print('已取消,无返回值');
+              }
+            },
+            child: const Text('填写血常规'),
+          ),
         ),
       ),
     );

+ 123 - 0
lib/pages/medical/views/table_input_dialog/controller.dart

@@ -0,0 +1,123 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+class TableInputDialogController extends GetxController {
+  TableInputDialogController({
+    required this.tableDataConfig,
+  });
+
+  final List<TableElementConfig> tableDataConfig;
+
+  final List<TableElementData> tableData = [];
+
+  void onConfirm() {
+    final result = <String, String>{};
+    tableData.forEach((element) {
+      String vlaue = element.textController.text;
+      if (vlaue.isEmpty) {
+        vlaue = '-';
+      }
+      result[element.config.id] = vlaue;
+    });
+    Get.back(result: TableInputResult(data: result));
+  }
+
+  void focusPre() {
+    int index = getCurrFocusIndex();
+    if (index != -1) {
+      if (index > 0) {
+        moveFocus(index - 1);
+      }
+      if (index == 0) {
+        moveFocus(tableData.length - 1);
+      }
+    }
+  }
+
+  void focusNext() {
+    int index = getCurrFocusIndex();
+    if (index != -1) {
+      if (index < tableData.length - 1) {
+        moveFocus(index + 1);
+      }
+      if (index == tableData.length - 1) {
+        moveFocus(0);
+      }
+    }
+  }
+
+  int getCurrFocusIndex() {
+    for (int i = 0; i < tableData.length; i++) {
+      final FocusNode focusNode = tableData[i].focusNode;
+      if (focusNode.hasFocus) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  void moveFocus(int index) {
+    print('moveFocus: $index');
+    if (index >= 0 && index < tableData.length) {
+      tableData[index].focusNode.requestFocus();
+      // 如果该行有值,下一帧执行,选择全部
+      if (tableData[index].textController.text.isNotEmpty) {
+        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+          Future.delayed(Duration(milliseconds: 30), () {
+            tableData[index].textController.selection = TextSelection(
+              baseOffset: 0,
+              extentOffset: tableData[index].textController.text.length,
+            );
+          });
+        });
+      }
+    }
+  }
+
+  @override
+  void onInit() {
+    super.onInit();
+    // 根据 tableDataConfig 初始化 tableData
+    tableDataConfig.forEach((element) {
+      tableData.add(TableElementData(element));
+    });
+  }
+
+  @override
+  void onReady() {
+    super.onReady();
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+  }
+}
+
+/// 表格配置项 (入参)
+class TableElementConfig {
+  final String id;
+  final String name;
+  final String unit;
+  final String? initValue;
+  TableElementConfig(
+      {required this.id,
+      required this.name,
+      required this.unit,
+      this.initValue});
+}
+
+/// 表格输入结果 (出参)
+class TableInputResult {
+  Map<String, String> data;
+  TableInputResult({required this.data});
+}
+
+/// 表格元素数据(UI数据)
+class TableElementData {
+  final TableElementConfig config;
+  final TextEditingController textController;
+  FocusNode focusNode = FocusNode();
+  TableElementData(this.config)
+      : textController = TextEditingController(text: config.initValue);
+}

+ 4 - 0
lib/pages/medical/views/table_input_dialog/index.dart

@@ -0,0 +1,4 @@
+library blood_test_table_dialog;
+
+export './controller.dart';
+export './view.dart';

+ 141 - 0
lib/pages/medical/views/table_input_dialog/view.dart

@@ -0,0 +1,141 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:get/get.dart';
+import 'package:vitalapp/components/button.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/widgets/table_element_row.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/widgets/table_title.dart';
+
+import 'index.dart';
+
+class TableInputDialog extends GetView<TableInputDialogController> {
+  const TableInputDialog({Key? key, required this.tableDataConfig})
+      : super(key: key);
+
+  /// 列表配置项
+  final List<TableElementConfig> tableDataConfig;
+
+  // 主视图
+  Widget _buildView() {
+    const designWidth = 1280.0; // 设计尺寸宽度:1280
+    final width = Get.width;
+    final scale = width / designWidth; // 计算缩放比例
+    return Container(
+      width: Get.width * 0.9 / scale,
+      height: 240 * 3,
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(10),
+      ),
+      clipBehavior: Clip.antiAlias,
+      child: Column(
+        children: [
+          _buildHead(),
+          Expanded(
+            child: _buildBody(),
+          ),
+          _buildBottom(),
+        ],
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return GetBuilder<TableInputDialogController>(
+      init: TableInputDialogController(tableDataConfig: tableDataConfig),
+      builder: (context) {
+        return Dialog(
+          child: _buildView(),
+        );
+      },
+    );
+  }
+
+  /// 构建弹窗顶部,右侧显示关闭按钮
+  Widget _buildHead() {
+    return SizedBox(
+      height: 40,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.end,
+        children: [
+          IconButton(
+            onPressed: () => Get.back(),
+            icon: const Icon(
+              Icons.close,
+              size: 30,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildBody() {
+    final ScrollController scrollController = ScrollController();
+    return RawKeyboardListener(
+      focusNode: FocusNode(),
+      onKey: (RawKeyEvent value) {
+        if (value is RawKeyDownEvent) {
+          if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
+            controller.focusPre();
+          } else if (value.logicalKey == LogicalKeyboardKey.arrowDown ||
+              value.logicalKey == LogicalKeyboardKey.enter) {
+            controller.focusNext();
+          }
+        }
+      },
+      child: Scrollbar(
+        thumbVisibility: true,
+        thickness: 10,
+        radius: const Radius.circular(10),
+        controller: scrollController,
+        child: SingleChildScrollView(
+          controller: scrollController,
+          padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
+          physics: const BouncingScrollPhysics(),
+          child: Column(
+            children: [
+              TableTitle(),
+              for (var item in controller.tableData)
+                TableElementRow(
+                  elementName: item.config.name,
+                  unit: item.config.unit,
+                  textController: item.textController,
+                  focusNode: item.focusNode,
+                ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildBottom() {
+    return Container(
+      height: 50,
+      padding: EdgeInsets.symmetric(vertical: 5),
+      color: Colors.grey[200],
+      child: Row(
+        // mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Expanded(
+            child: Text(
+              "     Enter 或 ⬆ ⬇ 键可切换项目",
+              style: TextStyle(
+                color: Colors.black38,
+                fontSize: 10,
+              ),
+            ),
+          ),
+          VButton(
+            onTap: () {
+              controller.onConfirm();
+            },
+            child: const Text('提交'),
+          ),
+          Expanded(child: Container()),
+        ],
+      ),
+    );
+  }
+}

+ 95 - 0
lib/pages/medical/views/table_input_dialog/widgets/table_element_row.dart

@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/controller.dart';
+
+class TableElementRow extends GetView<TableInputDialogController> {
+  final String elementName;
+  final String unit;
+  final TextEditingController? textController;
+  final FocusNode? focusNode;
+
+  TableElementRow({
+    required this.elementName,
+    required this.unit,
+    required this.textController,
+    required this.focusNode,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 40,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: [
+          Expanded(
+            child: Container(
+              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+              decoration: BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: Colors.grey[300]!,
+                    width: 1,
+                  ),
+                ),
+              ),
+              child: Text(
+                elementName,
+                style: TextStyle(
+                  fontSize: 14,
+                  color: Colors.grey[600],
+                ),
+              ),
+            ),
+          ),
+          Expanded(
+            child: Container(
+              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+              decoration: BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: Colors.grey[300]!,
+                    width: 1,
+                  ),
+                ),
+              ),
+              child: TextField(
+                controller: textController,
+                focusNode: focusNode,
+                textInputAction: TextInputAction.none,
+                decoration: InputDecoration(
+                  border: InputBorder.none,
+                  hintText: '请输入检查结果',
+                  hintStyle: TextStyle(
+                    fontSize: 14,
+                    color: Colors.grey[400],
+                  ),
+                ),
+              ),
+            ),
+          ),
+          Expanded(
+            child: Container(
+              padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+              decoration: BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: Colors.grey[300]!,
+                    width: 1,
+                  ),
+                ),
+              ),
+              child: Text(
+                unit,
+                style: TextStyle(
+                  fontSize: 14,
+                  color: Colors.grey[600],
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 76 - 0
lib/pages/medical/views/table_input_dialog/widgets/table_title.dart

@@ -0,0 +1,76 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:vitalapp/pages/medical/views/table_input_dialog/controller.dart';
+
+class TableTitle extends GetView<TableInputDialogController> {
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        Expanded(
+          child: Container(
+            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+            decoration: BoxDecoration(
+              border: Border(
+                bottom: BorderSide(
+                  color: Colors.grey[300]!,
+                  width: 1,
+                ),
+              ),
+            ),
+            child: Text(
+              '项目名称',
+              style: TextStyle(
+                fontSize: 14,
+                fontWeight: FontWeight.bold,
+                color: Colors.grey[800],
+              ),
+            ),
+          ),
+        ),
+        Expanded(
+          child: Container(
+            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+            decoration: BoxDecoration(
+              border: Border(
+                bottom: BorderSide(
+                  color: Colors.grey[300]!,
+                  width: 1,
+                ),
+              ),
+            ),
+            child: Text(
+              '检查结果',
+              style: TextStyle(
+                fontSize: 14,
+                fontWeight: FontWeight.bold,
+                color: Colors.grey[800],
+              ),
+            ),
+          ),
+        ),
+        Expanded(
+          child: Container(
+            padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+            decoration: BoxDecoration(
+              border: Border(
+                bottom: BorderSide(
+                  color: Colors.grey[300]!,
+                  width: 1,
+                ),
+              ),
+            ),
+            child: Text(
+              '单位',
+              style: TextStyle(
+                fontSize: 14,
+                fontWeight: FontWeight.bold,
+                color: Colors.grey[800],
+              ),
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}