Melon 1 рік тому
батько
коміт
6d7492305a

+ 20 - 2
lib/components/cell.dart

@@ -2,15 +2,21 @@ import 'package:flutter/material.dart';
 
 class VListFormCellGroup extends StatelessWidget {
   final List<Widget> children;
+  final double? leadingIconWidth;
 
   const VListFormCellGroup({
     Key? key,
     required this.children,
+    this.leadingIconWidth,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    final divider = Divider(thickness: 1, color: Colors.grey.shade400);
+    final divider = Divider(
+      thickness: 1,
+      color: Colors.grey.shade400,
+      indent: leadingIconWidth,
+    );
     final kids = <Widget>[];
     for (var i = 0; i < children.length; i++) {
       if (i > 0) {
@@ -42,6 +48,8 @@ class VListFormCell extends StatelessWidget {
   final Widget? contentWidget;
   final VoidCallback? onTap;
   final double? height;
+  final double? leadingIconWidth;
+  final Widget? leadingIcon;
 
   const VListFormCell({
     super.key,
@@ -52,16 +60,26 @@ class VListFormCell extends StatelessWidget {
     this.contentWidget,
     this.onTap,
     this.height,
+    this.leadingIcon,
+    this.leadingIconWidth,
   }) : assert(label != null || labelWidget != null);
 
   @override
   Widget build(BuildContext context) {
+    final h = height ?? 40;
     final children = <Widget>[];
+    if (leadingIcon != null) {
+      final leadingWidget = SizedBox(
+        width: leadingIconWidth ?? h * .6,
+        child: leadingIcon,
+      );
+      children.add(leadingWidget);
+    }
     children.add(_buildLabel());
     children.add(Expanded(child: _buildRightPart()));
 
     return SizedBox(
-      height: height ?? 40,
+      height: h,
       child: InkWell(
         onTap: onTap,
         child: Row(

+ 24 - 0
lib/components/side_nav/controller.dart

@@ -0,0 +1,24 @@
+import 'package:fis_common/event/event_type.dart';
+
+class VSideNavViewController {
+  String? _currentRoute;
+
+  /// 当前路由
+  String? get currentRoute => _currentRoute;
+
+  /// 路由变更事件
+  final routeChangedEvent = FEventHandler<String?>();
+
+  VSideNavViewController() {
+    routeChangedEvent.addListener(_onRouteChanged);
+  }
+
+  /// 释放资源
+  void dispose() {
+    routeChangedEvent.removeListener(_onRouteChanged);
+  }
+
+  void _onRouteChanged(_, String? e) {
+    _currentRoute = e;
+  }
+}

+ 36 - 0
lib/components/side_nav/defines.dart

@@ -0,0 +1,36 @@
+import 'package:flutter/widgets.dart';
+import 'package:vnoteapp/components/route_page/route_setting.dart';
+
+/// 导航菜单项
+///
+/// [onTap] 或 [route] 不可同时为空
+///
+/// [onTap] 优先级高于 [route]
+class VSideNavMenuItem {
+  /// 标题
+  final String title;
+
+  /// 路由
+  final VRouteSetting? route;
+
+  /// 描述
+  final String? description;
+
+  /// 图标
+  final Widget? icon;
+
+  /// 单击回调
+  final VoidCallback? onTap;
+
+  /// 是否从此项开始重排
+  final bool shouldRearrage;
+
+  VSideNavMenuItem({
+    required this.title,
+    this.route,
+    this.description,
+    this.icon,
+    this.onTap,
+    this.shouldRearrage = false,
+  }) : assert(route != null || onTap != null);
+}

+ 64 - 0
lib/components/side_nav/nav_view.dart

@@ -0,0 +1,64 @@
+part of "./side_nav.dart";
+
+class _NavView extends StatefulWidget {
+  /// 导航Id
+  final int navId;
+
+  /// 菜单项集合
+  final List<VSideNavMenuItem> items;
+
+  final VSideNavViewController controller;
+
+  const _NavView({
+    required this.navId,
+    required this.items,
+    required this.controller,
+  });
+
+  @override
+  State<StatefulWidget> createState() => _NavViewState();
+}
+
+class NavigatorObserverS extends NavigatorObserver {}
+
+class _NavViewState extends State<_NavView> {
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: _buildNavigator(context),
+    );
+  }
+
+  Widget _buildNavigator(BuildContext context) {
+    final firstRouteItem =
+        widget.items.firstWhereOrNull((e) => e.route != null);
+
+    final firstRoute = firstRouteItem?.route;
+
+    return Navigator(
+      key: Get.nestedKey(widget.navId),
+      initialRoute: firstRoute?.name,
+      onGenerateRoute: onGenerateRoute,
+    );
+  }
+
+  /// 路由生成器
+  Route? onGenerateRoute(RouteSettings settings) {
+    String? name = settings.name;
+    if (name == null) return null;
+
+    final item = widget.items.firstWhereOrNull((e) => e.route?.name == name);
+    if (item == null) return null;
+
+    final route = item.route!;
+
+    widget.controller.routeChangedEvent.emit(this, route.name);
+
+    return GetPageRoute(
+      page: route.page,
+      binding: route.binding,
+      customTransition: FTransitions.sharedAxisHorizontal,
+      transitionDuration: GlobalStyles.navTransitionDuration,
+    );
+  }
+}

+ 76 - 0
lib/components/side_nav/side_bar.dart

@@ -0,0 +1,76 @@
+part of "./side_nav.dart";
+
+class _SideBar extends StatefulWidget {
+  /// 导航Id
+  final int navId;
+
+  /// 菜单项集合
+  final List<VSideNavMenuItem> items;
+
+  final VSideNavViewController controller;
+
+  const _SideBar({
+    required this.controller,
+    required this.navId,
+    required this.items,
+  });
+
+  @override
+  State<StatefulWidget> createState() => _SideBarState();
+}
+
+class _SideBarState extends State<_SideBar> {
+  @override
+  Widget build(BuildContext context) {
+    return Scrollbar(
+      thumbVisibility: true,
+      child: ListView(
+        children: _buildChildren(context),
+      ),
+    );
+  }
+
+  List<Widget> _buildChildren(BuildContext context) {
+    final children = <Widget>[];
+
+    final hasAnyIcon = widget.items.any((e) => e.icon != null);
+
+    final divider = Divider(
+      thickness: 1,
+      color: Colors.grey.shade400,
+      indent: hasAnyIcon ? 32 : 0,
+    );
+
+    final count = widget.items.length;
+    for (var i = 0; i < count; i++) {
+      final item = widget.items[i];
+
+      if (item.shouldRearrage) {
+        children.add(const SizedBox(height: 12));
+      } else if (i > 0) {
+        children.add(divider);
+      }
+
+      final cell = _buildMenuCell(item);
+      children.add(cell);
+    }
+
+    return children;
+  }
+
+  Widget _buildMenuCell(VSideNavMenuItem item) {
+    // final hasSection =
+    //     item.routeName != null && _currentAnchor == item.routeName;
+
+    return VListFormCell(
+      leadingIcon: item.icon,
+      leadingIconWidth: 32,
+      label: item.title,
+      onTap: () {
+        //
+        var a = 0;
+        print(a is double);
+      },
+    );
+  }
+}

+ 58 - 0
lib/components/side_nav/side_nav.dart

@@ -0,0 +1,58 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:vnoteapp/components/cell.dart';
+import 'package:vnoteapp/consts/styles.dart';
+import 'package:vnoteapp/routes/transition.dart';
+
+import 'controller.dart';
+import 'defines.dart';
+
+part 'nav_view.dart';
+part 'side_bar.dart';
+
+class VSideNavView extends StatelessWidget {
+  /// 导航Id
+  final int navId;
+
+  /// 菜单项集合
+  final List<VSideNavMenuItem> items;
+
+  final VSideNavViewController? controller;
+
+  const VSideNavView({
+    super.key,
+    required this.navId,
+    required this.items,
+    this.controller,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    final c = controller ?? VSideNavViewController();
+    final sideView = _SideBar(
+      controller: c,
+      items: items,
+      navId: navId,
+    );
+    final navView = _NavView(
+      controller: c,
+      items: items,
+      navId: navId,
+    );
+
+    return Container(
+      child: Row(
+        children: [
+          Expanded(
+            flex: 4,
+            child: sideView,
+          ),
+          Expanded(
+            flex: 6,
+            child: navView,
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 3 - 0
lib/consts/styles.dart

@@ -11,4 +11,7 @@ abstract class GlobalStyles {
   /// 边框圆角
   static final BorderRadius borderRadius =
       BorderRadius.circular(borderRadiusNum);
+
+  /// 嵌套路由转场动画时长
+  static const Duration navTransitionDuration = Duration(milliseconds: 1000);
 }

+ 2 - 1
lib/pages/home/controller.dart

@@ -15,6 +15,7 @@ import 'package:vnoteapp/architecture/defines.dart';
 import 'package:vnoteapp/managers/interfaces/account.dart';
 import 'package:vnoteapp/pages/controllers/home_nav_mixin.dart';
 import 'package:vnoteapp/pages/home/models/menu.dart';
+import 'package:vnoteapp/routes/nav_ids.dart';
 
 import 'state.dart';
 
@@ -53,7 +54,7 @@ class HomeController extends FControllerBase with HomeNavMixin {
     array[index].isSelected = true;
     state.menuItems = array;
 
-    Get.offAllNamed(name, id: 1001);
+    Get.offAllNamed(name, id: NavIds.HOME);
   }
 
   @override

+ 2 - 1
lib/pages/home/widgets/navigator.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:vnoteapp/routes/nav_ids.dart';
 import 'package:vnoteapp/routes/routes.dart';
 import 'package:vnoteapp/routes/transition.dart';
 
@@ -10,7 +11,7 @@ class HomeNavigator extends StatelessWidget {
   Widget build(BuildContext context) {
     return Navigator(
       key: Get.nestedKey(
-        1001,
+        NavIds.HOME,
       ),
       initialRoute: "/dashboard",
       onGenerateRoute: onGenerateRoute,

+ 2 - 1
lib/pages/patient/create/controller.dart

@@ -8,6 +8,7 @@ import 'package:vnoteapp/pages/controllers/home_nav_mixin.dart';
 import 'package:vnoteapp/pages/home/controller.dart';
 import 'package:vnoteapp/pages/patient/create/state.dart';
 import 'package:vnoteapp/pages/patient/list/controller.dart';
+import 'package:vnoteapp/routes/nav_ids.dart';
 
 class CreatePatientController extends FControllerBase with HomeNavMixin {
   final _patientManager = Get.find<IPatientManager>();
@@ -90,7 +91,7 @@ class CreatePatientController extends FControllerBase with HomeNavMixin {
       return;
     }
 
-    Get.back(result: code, id: 1001);
+    Get.back(result: code, id: NavIds.HOME);
   }
 
   /// 点击读卡事件

+ 33 - 0
lib/pages/settings/view.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:vnoteapp/pages/widgets/function_button.dart';
 import 'controller.dart';
 
 class SettingsPage extends GetView<SettingsController> {
@@ -34,4 +35,36 @@ class SettingsPage extends GetView<SettingsController> {
       ),
     );
   }
+
+  List<Widget> _buildEntranceList(BuildContext context) {
+    final list = <Widget>[];
+    list.add(FunctionButton(
+      label: "签名设置",
+      icon: _buildIcon(Icons.edit_document, context),
+      // onTap: controller.gotoInfo,
+    ));
+    list.add(FunctionButton(
+      label: "退出登录",
+      icon: _buildIcon(Icons.exit_to_app, context),
+      onTap: controller.logOut,
+    ));
+    return list;
+  }
+
+  Widget _buildImgIcon(String assetName) {
+    return Image.asset(
+      "assets/images/patient/$assetName",
+      width: 100,
+      height: 100,
+      fit: BoxFit.contain,
+    );
+  }
+
+  Widget _buildIcon(IconData iconData, BuildContext context) {
+    return Icon(
+      iconData,
+      size: 100,
+      color: Theme.of(context).primaryColor,
+    );
+  }
 }

+ 13 - 0
lib/routes/nav_ids.dart

@@ -0,0 +1,13 @@
+// ignore_for_file: constant_identifier_names
+
+/// 嵌套导航Id集合
+abstract class NavIds {
+  /// 主页
+  static const HOME = 1001;
+
+  /// 设置
+  static const SETTINGS = 1002;
+
+  /// 签约
+  static const CONTRACT = 1002;
+}