gavin.chen преди 1 година
родител
ревизия
5d09135279

+ 8 - 4
lib/pages/medical_checkup_station/registration/controller/list.dart

@@ -22,6 +22,7 @@ import 'package:vitalapp/pages/medical_checkup_station/registration/widgets/form
 import 'package:vitalapp/pages/medical_checkup_station/registration/widgets/report/report_preview.dart';
 import 'package:vitalapp/pages/medical_checkup_station/usb_print/module/printer_info.dart';
 import 'package:vitalapp/pages/medical_checkup_station/usb_print/page/temp/print_preview.dart';
+import 'package:vitalapp/pages/widgets/overflow_tooltip_wrapper.dart';
 import 'package:vitalapp/store/store.dart';
 
 class RegistrationListController {
@@ -154,6 +155,7 @@ class RegistrationListController {
     var textStyle = TextStyle(
       fontSize: 18,
       overflow: TextOverflow.ellipsis,
+      fontFamily: "NotoSansSC",
     );
     return <TableColumn<ResidentModel>>[
       TableColumn<ResidentModel>(
@@ -161,9 +163,11 @@ class RegistrationListController {
         flex: 4,
         render: (rowData, index) => Container(
           padding: const EdgeInsets.symmetric(vertical: kIsWeb ? 10 : 16),
-          child: Text(
-            rowData.physicalExamNumber ?? '',
-            style: textStyle,
+          child: OverflowTooltipWrapper(
+            child: Text(
+              rowData.physicalExamNumber ?? '',
+              style: textStyle,
+            ),
           ),
         ),
       ),
@@ -190,7 +194,7 @@ class RegistrationListController {
         ),
       TableColumn<ResidentModel>(
         headerText: "身份证号",
-        flex: 5,
+        flex: 6,
         render: (rowData, index) => Center(
           child: Text(
             rowData.idNumber,

+ 49 - 0
lib/pages/widgets/overflow_tooltip_wrapper.dart

@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+import 'package:vitalapp/pages/widgets/text_tooltip.dart';
+
+class OverflowTooltipWrapper extends StatelessWidget {
+  final Text child;
+  final DisplayPosition position;
+  final Offset offset;
+
+  OverflowTooltipWrapper({
+    Key? key,
+    required this.child,
+    this.position = DisplayPosition.top,
+    this.offset = const Offset(0, -3),
+  })  : assert(
+          child.style != null && child.style!.fontFamily != null,
+          'child must have a non-null style with a non-null fontFamily',
+        ),
+        super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    var tp = TextPainter(
+      maxLines: child.maxLines,
+      textAlign: child.textAlign ?? TextAlign.start,
+      textDirection: child.textDirection ?? TextDirection.ltr,
+      text: child.textSpan ??
+          TextSpan(
+            text: child.data,
+            style: child.style,
+          ),
+    );
+
+    return LayoutBuilder(
+      builder: (context, constrains) {
+        tp.layout(maxWidth: constrains.maxWidth);
+        final overflowed = tp.didExceedMaxLines;
+        if (overflowed) {
+          return TextTooltip(
+            position: position,
+            offset: offset,
+            message: child.data ?? '',
+            child: child,
+          );
+        }
+        return child;
+      },
+    );
+  }
+}

+ 253 - 0
lib/pages/widgets/text_tooltip.dart

@@ -0,0 +1,253 @@
+import 'package:flutter/material.dart';
+import 'package:fis_theme/theme.dart';
+
+/// 可选的放置位置
+enum DisplayPosition {
+  top,
+  right,
+  bottom,
+  left,
+  topRight,
+  bottomRight,
+  bottomLeft,
+  topLeft,
+}
+
+/// 支持自定义的纯文本提示框组件
+class TextTooltip extends StatefulWidget {
+  const TextTooltip({
+    super.key,
+    required this.message,
+    required this.child,
+    this.height = 26,
+    this.position = DisplayPosition.top,
+    this.margin = const EdgeInsets.all(0),
+    this.padding = const EdgeInsets.symmetric(horizontal: 10),
+    this.offset = const Offset(0, 0),
+    this.decoration = defaultDecoration,
+    this.textStyle = defaultTextStyle,
+    this.disable = false,
+  }) : super();
+  final String message;
+  final Widget child;
+  final double height;
+  final DisplayPosition position;
+  final EdgeInsetsGeometry margin;
+  final EdgeInsetsGeometry padding;
+  final Offset offset;
+  final Decoration decoration;
+  final TextStyle textStyle;
+  final bool disable;
+
+  static const defaultTextStyle = TextStyle(
+    color: Colors.white,
+    fontSize: 12,
+  );
+
+  static const defaultDecoration = BoxDecoration(
+    color: Colors.black,
+    borderRadius: BorderRadius.all(Radius.circular(4)),
+  );
+
+  @override
+  State<TextTooltip> createState() => TextTooltipState();
+}
+
+class TextTooltipState extends State<TextTooltip> {
+  OverlayEntry? _entry;
+
+  /// 需要靠Bottom定位的情况
+  static const List<DisplayPosition> NEED_BOTTOM = [
+    DisplayPosition.top,
+    DisplayPosition.topRight,
+    DisplayPosition.topLeft,
+  ];
+
+  /// 需要靠Right定位的情况
+  static const List<DisplayPosition> NEED_RIGHT = [
+    DisplayPosition.left,
+    DisplayPosition.topLeft,
+    DisplayPosition.bottomLeft,
+  ];
+
+  /// 将容器扩宽来适应上下显示时的文本居中
+  static const List<DisplayPosition> NEED_ENLARGE_WIDTH = [
+    DisplayPosition.top,
+    DisplayPosition.bottom,
+  ];
+
+  /// 默认情况下的文本大小
+  static const double DEFAULT_TEXT_SIZE = 12;
+
+  double get _maxMessageWidth =>
+      widget.message.length * widget.textStyle.fontSize!;
+  double get _paddingWidth => widget.padding.horizontal;
+  double get _marginWidth => widget.margin.horizontal;
+  double get _enlargeWidth => _maxMessageWidth + _paddingWidth + _marginWidth;
+
+  /// 显示 Tooltip
+  void _showTooltip() {
+    _createNewEntry();
+  }
+
+  /// 隐藏 Tooltip
+  void _hideTooltip() {
+    _entry?.remove();
+    _entry = null;
+  }
+
+  /// 创建新的 OverlayEntry
+  void _createNewEntry() {
+    final OverlayState overlayState = Overlay.of(
+      context,
+      debugRequiredFor: widget,
+    );
+
+    final RenderBox target = context.findRenderObject()! as RenderBox;
+    final RenderBox targetConatiner =
+        overlayState.context.findRenderObject() as RenderBox;
+    final Offset targetOffset = target.localToGlobal(
+      target.size.center(Offset.zero),
+      ancestor: overlayState.context.findRenderObject(),
+    );
+    final Offset overlayOffset = _countOffset(
+        targetConatiner.size, target.size, targetOffset, widget.position);
+    final Offset edgeOffset =
+        _edgeCorrection(targetConatiner.size, target.size, overlayOffset);
+    final Widget overlay = _buildOverlay(edgeOffset, widget.position);
+    _entry = OverlayEntry(builder: (BuildContext context) => overlay);
+    overlayState.insert(_entry!);
+  }
+
+  /// 创建 Overlay 组件
+  Widget _buildOverlay(Offset offset, DisplayPosition position) {
+    return Directionality(
+      textDirection: Directionality.of(context),
+      child: Positioned.directional(
+        top: NEED_BOTTOM.contains(position) ? null : offset.dy,
+        start: NEED_RIGHT.contains(position) ? null : offset.dx,
+        bottom: NEED_BOTTOM.contains(position) ? offset.dy : null,
+        end: NEED_RIGHT.contains(position) ? offset.dx : null,
+        textDirection: Directionality.of(context),
+        child: SizedBox(
+          width: NEED_ENLARGE_WIDTH.contains(position) ? _enlargeWidth : null,
+          child: Center(
+            child: UnconstrainedBox(
+              child: Transform(
+                transform: Matrix4.translationValues(
+                  widget.offset.dx,
+                  widget.offset.dy,
+                  0,
+                ),
+                child: Container(
+                  decoration: widget.decoration,
+                  height: widget.height,
+                  padding: widget.padding,
+                  margin: widget.margin,
+                  child: Center(
+                    child: DefaultTextStyle(
+                      style: TextStyle(
+                        decoration: TextDecoration.none,
+                        fontFamily: "NotoSansSC",
+                      ),
+                      child: Text(widget.message, style: widget.textStyle),
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 计算位置布局
+  Offset _countOffset(Size containerSize, Size targetSize, Offset targetOffset,
+      DisplayPosition position) {
+    final double x = targetOffset.dx;
+    final double y = targetOffset.dy;
+    final double w = targetSize.width;
+    final double h = targetSize.height;
+    final double cw = containerSize.width;
+    final double ch = containerSize.height;
+    switch (position) {
+      case DisplayPosition.left:
+        return Offset(cw - x + w / 2, y - widget.height / 2);
+      case DisplayPosition.top:
+        return Offset(x - _enlargeWidth / 2, ch - y + h / 2);
+      case DisplayPosition.right:
+        return Offset(x + w / 2, y - widget.height / 2);
+      case DisplayPosition.bottom:
+        return Offset(x - _enlargeWidth / 2, y + h / 2);
+      case DisplayPosition.topRight:
+        return Offset(x + w / 2, ch - y + h / 2);
+      case DisplayPosition.bottomRight:
+        return Offset(x + w / 2, y + h / 2);
+      case DisplayPosition.bottomLeft:
+        return Offset(cw - x + w / 2, y + h / 2);
+      case DisplayPosition.topLeft:
+        return Offset(cw - x + w / 2, ch - y + h / 2);
+    }
+  }
+
+  /// 边缘矫正
+  Offset _edgeCorrection(
+      Size containerSize, Size targetSize, Offset targetOffset) {
+    final double x = targetOffset.dx;
+    final double y = targetOffset.dy;
+    final double w = targetSize.width;
+    final double h = targetSize.height;
+    final double cw = containerSize.width;
+    final double ch = containerSize.height;
+    if (x < 0) {
+      return Offset(0, y);
+    }
+    if (x + w > cw) {
+      return Offset(cw - w, y);
+    }
+    if (y < 0) {
+      return Offset(x, 0);
+    }
+    if (y + h > ch) {
+      return Offset(x, ch - h);
+    }
+    return targetOffset;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (widget.disable) {
+      return widget.child;
+    }
+    return MouseRegion(
+      onEnter: (event) {
+        _showTooltip();
+      },
+      onExit: (event) {
+        _hideTooltip();
+      },
+      child: Container(
+        child: widget.child,
+      ),
+    );
+  }
+
+  /// 组件入参更新时,如果 disable 为 true,则隐藏 Tooltip
+  @override
+  void didUpdateWidget(TextTooltip oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.disable) {
+      _hideTooltip();
+    }
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    if (_entry != null) {
+      _entry?.remove();
+      _entry = null;
+    }
+  }
+}