Parcourir la source

1、添加修改密码入口;2、添加响应数据基础模型。

PC\19500 il y a 3 semaines
Parent
commit
e774a17a7c

+ 6 - 13
README.md

@@ -1,16 +1,9 @@
 # sino_med_cloud
 # sino_med_cloud
 
 
-A new Flutter project.
+#  插件json_serializable
+# 一次性生成
+flutter pub run build_runner build --delete-conflicting-outputs
 
 
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
-
-For help getting started with Flutter development, view the
-[online documentation](https://docs.flutter.dev/), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+# 监听模式
+# 开发时终端打开,监听model文件的变化并自动更新.g.dart文件
+flutter pub run build_runner watch

+ 1 - 1
android/gradle.properties

@@ -1,2 +1,2 @@
-org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
 android.useAndroidX=true
 android.useAndroidX=true

+ 6 - 1
lib/app/router.dart

@@ -1,5 +1,6 @@
 import 'package:go_router/go_router.dart';
 import 'package:go_router/go_router.dart';
 import 'package:sino_med_cloud/features/main_tab_page.dart';
 import 'package:sino_med_cloud/features/main_tab_page.dart';
+import '../features/MinePage/data/change_password_page.dart';
 import '../features/auth/presentation/login_page.dart';
 import '../features/auth/presentation/login_page.dart';
 
 
 class AppRouter {
 class AppRouter {
@@ -12,7 +13,11 @@ class AppRouter {
       GoRoute(
       GoRoute(
         path: '/mainTab',
         path: '/mainTab',
         builder: (_, __) => const MainTabPage(),
         builder: (_, __) => const MainTabPage(),
-      )
+      ),
+      GoRoute(
+        path: '/ChangePassword',
+        builder: (_, __) => const ChangePasswordPage(),
+      ),
     ]
     ]
   );
   );
 }
 }

+ 44 - 0
lib/base/common_response.dart

@@ -0,0 +1,44 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'common_response.g.dart';
+
+/// 返回统一数据结构
+@JsonSerializable(
+  genericArgumentFactories: true,
+  // fieldRename: FieldRename.snake,
+)
+class BaseCommonResponse<T> {
+  /// 调用接口业务相关代码
+  @JsonKey(name: 'code')
+  int code;
+
+  /// 调用接口是否成功
+  @JsonKey(name: 'success')
+  bool success;
+
+  /// 调用接口提示语
+  @JsonKey(name: 'msg')
+  String? msg;
+
+  /// 日志id
+  @JsonKey(name: 'log_id')
+  String? logId;
+
+  /// 调用接口返回数据
+  @JsonKey(name: 'data')
+  T? data;
+
+  BaseCommonResponse(
+      {required this.code,
+      required this.success,
+      this.msg,
+      this.logId,
+      this.data});
+
+  factory BaseCommonResponse.fromJson(
+          Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
+      _$BaseCommonResponseFromJson(json, fromJsonT);
+
+  Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
+      _$BaseCommonResponseToJson(this, toJsonT);
+}

+ 39 - 0
lib/base/common_response.g.dart

@@ -0,0 +1,39 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'common_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BaseCommonResponse<T> _$BaseCommonResponseFromJson<T>(
+  Map<String, dynamic> json,
+  T Function(Object? json) fromJsonT,
+) => BaseCommonResponse<T>(
+  code: (json['code'] as num).toInt(),
+  success: json['success'] as bool,
+  msg: json['msg'] as String?,
+  logId: json['log_id'] as String?,
+  data: _$nullableGenericFromJson(json['data'], fromJsonT),
+);
+
+Map<String, dynamic> _$BaseCommonResponseToJson<T>(
+  BaseCommonResponse<T> instance,
+  Object? Function(T value) toJsonT,
+) => <String, dynamic>{
+  'code': instance.code,
+  'success': instance.success,
+  'msg': instance.msg,
+  'log_id': instance.logId,
+  'data': _$nullableGenericToJson(instance.data, toJsonT),
+};
+
+T? _$nullableGenericFromJson<T>(
+  Object? input,
+  T Function(Object? json) fromJson,
+) => input == null ? null : fromJson(input);
+
+Object? _$nullableGenericToJson<T>(
+  T? input,
+  Object? Function(T value) toJson,
+) => input == null ? null : toJson(input);

+ 46 - 0
lib/base/list_response.dart

@@ -0,0 +1,46 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'list_result.dart';
+
+part 'list_response.g.dart';
+
+/// 统一返回+通用分页 数据结构
+@JsonSerializable(
+  genericArgumentFactories: true,
+  // fieldRename: FieldRename.snake,
+)
+class BaseListResponse<T> {
+  /// 调用接口业务相关代码
+  @JsonKey(name: 'code')
+  int code;
+
+  /// 调用接口是否成功
+  @JsonKey(name: 'success')
+  bool success;
+
+  /// 调用接口提示语
+  @JsonKey(name: 'msg')
+  String? msg;
+
+  /// 日志id
+  @JsonKey(name: 'log_id')
+  String? logId;
+
+  /// 调用接口返回数据
+  @JsonKey(name: 'data')
+  BaseListResult<T> data;
+
+  BaseListResponse(
+      {required this.code,
+      required this.success,
+      this.msg,
+      this.logId,
+      required this.data});
+
+  factory BaseListResponse.fromJson(
+          Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
+      _$BaseListResponseFromJson(json, fromJsonT);
+
+  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
+      _$BaseListResponseToJson(this, toJsonT);
+}

+ 32 - 0
lib/base/list_response.g.dart

@@ -0,0 +1,32 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'list_response.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BaseListResponse<T> _$BaseListResponseFromJson<T>(
+  Map<String, dynamic> json,
+  T Function(Object? json) fromJsonT,
+) => BaseListResponse<T>(
+  code: (json['code'] as num).toInt(),
+  success: json['success'] as bool,
+  msg: json['msg'] as String?,
+  logId: json['log_id'] as String?,
+  data: BaseListResult<T>.fromJson(
+    json['data'] as Map<String, dynamic>,
+    (value) => fromJsonT(value),
+  ),
+);
+
+Map<String, dynamic> _$BaseListResponseToJson<T>(
+  BaseListResponse<T> instance,
+  Object? Function(T value) toJsonT,
+) => <String, dynamic>{
+  'code': instance.code,
+  'success': instance.success,
+  'msg': instance.msg,
+  'log_id': instance.logId,
+  'data': instance.data.toJson((value) => toJsonT(value)),
+};

+ 36 - 0
lib/base/list_result.dart

@@ -0,0 +1,36 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'list_result.g.dart';
+
+/// 通用分页数据结构
+@JsonSerializable(
+  genericArgumentFactories: true,
+  // fieldRename: FieldRename.snake,
+)
+class BaseListResult<T> {
+  /// 总数
+  @JsonKey(name: "count")
+  int count;
+
+  /// 前一页地址
+  @JsonKey(name: "previous")
+  String? previous;
+
+  /// 下一页地址
+  @JsonKey(name: "next")
+  String? next;
+
+  /// 返回数据列表
+  @JsonKey(name: "results")
+  List<T> results;
+
+  BaseListResult(
+      {required this.count, this.previous, this.next, required this.results});
+
+  factory BaseListResult.fromJson(
+          Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
+      _$BaseListResultFromJson(json, fromJsonT);
+
+  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
+      _$BaseListResultToJson(this, toJsonT);
+}

+ 27 - 0
lib/base/list_result.g.dart

@@ -0,0 +1,27 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'list_result.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BaseListResult<T> _$BaseListResultFromJson<T>(
+  Map<String, dynamic> json,
+  T Function(Object? json) fromJsonT,
+) => BaseListResult<T>(
+  count: (json['count'] as num).toInt(),
+  previous: json['previous'] as String?,
+  next: json['next'] as String?,
+  results: (json['results'] as List<dynamic>).map(fromJsonT).toList(),
+);
+
+Map<String, dynamic> _$BaseListResultToJson<T>(
+  BaseListResult<T> instance,
+  Object? Function(T value) toJsonT,
+) => <String, dynamic>{
+  'count': instance.count,
+  'previous': instance.previous,
+  'next': instance.next,
+  'results': instance.results.map(toJsonT).toList(),
+};

+ 19 - 0
lib/core/network/interceptors/auth_interceptor.dart

@@ -12,6 +12,25 @@ class AuthInterceptor extends Interceptor {
   @override
   @override
   void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
   void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
     AppLogger.d('AuthInterceptor.onRequest - 被调用,URL: ${options.uri}');
     AppLogger.d('AuthInterceptor.onRequest - 被调用,URL: ${options.uri}');
+
+    // 定义不需要 Token 的接口列表
+    final noTokenEndpoints = [
+      ApiConstants.login,
+      ApiConstants.sendSmsCode,
+    ];
+
+    // 检查当前请求是否需要 Token
+    final bool needsToken = !noTokenEndpoints.any((path) => options.path.contains(path));
+
+    if (!needsToken) {
+      AppLogger.d('AuthInterceptor - 该请求不需要 Token: ${options.path}');
+      // 设置 Content-Type
+      if (!options.headers.containsKey(ApiConstants.contentType)) {
+        options.headers[ApiConstants.contentType] = ApiConstants.applicationJson;
+      }
+      return handler.next(options);
+    }
+
     // 使用 Future 处理异步操作
     // 使用 Future 处理异步操作
     LocalStorage.getToken().then((token) {
     LocalStorage.getToken().then((token) {
       AppLogger.d('AuthInterceptor - 开始处理请求,URL: ${options.uri}');
       AppLogger.d('AuthInterceptor - 开始处理请求,URL: ${options.uri}');

+ 53 - 0
lib/core/utils/toast_utils.dart

@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+
+/// Toast 工具类
+class ToastUtils {
+  ToastUtils._();
+
+  /// 显示提示
+  static void show(String message) {
+    if (message.isEmpty) return;
+    
+    Fluttertoast.showToast(
+      msg: message,
+      toastLength: Toast.LENGTH_SHORT,
+      gravity: ToastGravity.BOTTOM,
+      timeInSecForIosWeb: 1,
+      backgroundColor: Colors.black87,
+      textColor: Colors.white,
+      fontSize: 16.0,
+    );
+  }
+
+  /// 显示成功提示
+  static void showSuccess(String message) {
+    if (message.isEmpty) return;
+    
+    Fluttertoast.showToast(
+      msg: message,
+      toastLength: Toast.LENGTH_SHORT,
+      gravity: ToastGravity.BOTTOM,
+      timeInSecForIosWeb: 1,
+      backgroundColor: Colors.green,
+      textColor: Colors.white,
+      fontSize: 16.0,
+    );
+  }
+
+  /// 显示错误提示
+  static void showError(String message) {
+    if (message.isEmpty) return;
+    
+    Fluttertoast.showToast(
+      msg: message,
+      toastLength: Toast.LENGTH_SHORT,
+      gravity: ToastGravity.BOTTOM,
+      timeInSecForIosWeb: 1,
+      backgroundColor: Colors.red,
+      textColor: Colors.white,
+      fontSize: 16.0,
+    );
+  }
+}
+

+ 29 - 0
lib/features/MinePage/data/change_password_page.dart

@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import '../../../l10n/app_localizations.dart';
+
+class ChangePasswordPage extends ConsumerStatefulWidget {
+  const ChangePasswordPage({super.key});
+
+  @override
+  ConsumerState<ChangePasswordPage> createState() => _ChangePasswordPageState();
+}
+
+class _ChangePasswordPageState extends ConsumerState<ChangePasswordPage>
+    with SingleTickerProviderStateMixin {
+  @override
+  Widget build(BuildContext context) {
+    final l10n = AppLocalizations.of(context)!;
+    return Scaffold(
+        appBar: AppBar(
+          title: Text(l10n.changePassword),
+          elevation: 0,
+        ),
+      body: ListView.builder(
+          itemCount: 5,
+          itemBuilder: (BuildContext context, int index) {
+            return const SizedBox.shrink();
+          }),
+    );
+  }
+}

+ 9 - 15
lib/features/MinePage/data/quit_model.dart

@@ -1,4 +1,9 @@
+import 'package:json_annotation/json_annotation.dart';
 
 
+part 'quit_model.g.dart';
+
+/// 退出登录响应模型
+@JsonSerializable()
 class QuitModel {
 class QuitModel {
   final int code;
   final int code;
   final bool success;
   final bool success;
@@ -7,24 +12,13 @@ class QuitModel {
   QuitModel({
   QuitModel({
     required this.code,
     required this.code,
     required this.success,
     required this.success,
-    required this.msg
+    required this.msg,
   });
   });
 
 
   /// 从 JSON 创建 QuitModel
   /// 从 JSON 创建 QuitModel
-  factory QuitModel.fromJson(Map<String, dynamic> json) {
-    return QuitModel(
-      code: json['code'] as int,
-      success: json['success'] as bool,
-      msg: json['msg'] as String,
-    );
-  }
+  factory QuitModel.fromJson(Map<String, dynamic> json) =>
+      _$QuitModelFromJson(json);
 
 
   /// 转换为 JSON
   /// 转换为 JSON
-  Map<String, dynamic> toJson() {
-    return {
-      'code': code,
-      'success': success,
-      'msg': msg,
-    };
-  }
+  Map<String, dynamic> toJson() => _$QuitModelToJson(this);
 }
 }

+ 19 - 0
lib/features/MinePage/data/quit_model.g.dart

@@ -0,0 +1,19 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'quit_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+QuitModel _$QuitModelFromJson(Map<String, dynamic> json) => QuitModel(
+  code: (json['code'] as num).toInt(),
+  success: json['success'] as bool,
+  msg: json['msg'] as String,
+);
+
+Map<String, dynamic> _$QuitModelToJson(QuitModel instance) => <String, dynamic>{
+  'code': instance.code,
+  'success': instance.success,
+  'msg': instance.msg,
+};

+ 6 - 0
lib/features/MinePage/domain/mine_service.dart

@@ -27,6 +27,12 @@ class MineService {
         final data = response.data;
         final data = response.data;
         if (data != null && data['code'] == 20000) {
         if (data != null && data['code'] == 20000) {
           AppLogger.d('退出登录成功: $data');
           AppLogger.d('退出登录成功: $data');
+          
+          // 退出登录成功后,清除本地 Token 和用户信息
+          await LocalStorage.removeToken();
+          await LocalStorage.removeUserInfo();
+          await LocalStorage.removeInstitutionInfo();
+          
           // 解析为 QuitModel
           // 解析为 QuitModel
           final authModel = QuitModel.fromJson(data);
           final authModel = QuitModel.fromJson(data);
           return authModel;
           return authModel;

+ 34 - 8
lib/features/MinePage/presentation/mine_page.dart

@@ -18,14 +18,34 @@ class _MinePageState extends ConsumerState<MinePage>
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final l10n = AppLocalizations.of(context)!;
     final l10n = AppLocalizations.of(context)!;
-    return Center(
-      child: // 退出登录按钮
-      ElevatedButton(
-        onPressed: _handleLogout,
-        style: ElevatedButton.styleFrom(
-          padding: EdgeInsets.symmetric(vertical: 16),
-        ),
-        child: Text(l10n.logout),
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(l10n.mine),
+        elevation: 0,
+      ),
+      body: ListView.builder(
+        itemCount: 2,
+        itemBuilder: (BuildContext context, int index) {
+          if (index == 0) {
+            return ListTile(
+              leading: const Icon(Icons.lock_outline),
+              title: Text(l10n.changePassword),
+              trailing: const Icon(Icons.chevron_right),
+              onTap: _handleChangePassword,
+            );
+          } else if (index == 1) {
+            return ListTile(
+              leading: const Icon(Icons.logout, color: Colors.red),
+              title: Text(
+                l10n.logout,
+                style: const TextStyle(color: Colors.red),
+              ),
+              trailing: const Icon(Icons.chevron_right),
+              onTap: _handleLogout,
+            );
+          }
+          return const SizedBox.shrink();
+        },
       ),
       ),
     );
     );
   }
   }
@@ -41,4 +61,10 @@ class _MinePageState extends ConsumerState<MinePage>
     });
     });
   }
   }
 
 
+
+  void _handleChangePassword() {
+    if (mounted) {
+      context.push('/ChangePassword');
+    }
+  }
 }
 }

+ 0 - 0
lib/features/MinePage/widget/enter_text_widget.dart


+ 22 - 58
lib/features/auth/data/auth_model.dart

@@ -1,4 +1,9 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'auth_model.g.dart';
+
 /// 机构信息模型
 /// 机构信息模型
+@JsonSerializable()
 class InstitutionInfo {
 class InstitutionInfo {
   final int id;
   final int id;
   final String uuid;
   final String uuid;
@@ -11,32 +16,24 @@ class InstitutionInfo {
   });
   });
 
 
   /// 从 JSON 创建 InstitutionInfo
   /// 从 JSON 创建 InstitutionInfo
-  factory InstitutionInfo.fromJson(Map<String, dynamic> json) {
-    return InstitutionInfo(
-      id: json['id'] as int,
-      uuid: json['uuid'] as String,
-      name: json['name'] as String,
-    );
-  }
+  factory InstitutionInfo.fromJson(Map<String, dynamic> json) =>
+      _$InstitutionInfoFromJson(json);
 
 
   /// 转换为 JSON
   /// 转换为 JSON
-  Map<String, dynamic> toJson() {
-    return {
-      'id': id,
-      'uuid': uuid,
-      'name': name,
-    };
-  }
+  Map<String, dynamic> toJson() => _$InstitutionInfoToJson(this);
 }
 }
 
 
 /// 用户信息模型
 /// 用户信息模型
+@JsonSerializable()
 class UserInfo {
 class UserInfo {
   final int id;
   final int id;
   final String uuid;
   final String uuid;
+  @JsonKey(name: 'institution_uuid')
   final String institutionUuid;
   final String institutionUuid;
   final String name;
   final String name;
   final int gender;
   final int gender;
   final String? avatar;
   final String? avatar;
+  @JsonKey(name: 'base_uuid')
   final String baseUuid;
   final String baseUuid;
   final String username;
   final String username;
   final String mobile;
   final String mobile;
@@ -54,41 +51,23 @@ class UserInfo {
   });
   });
 
 
   /// 从 JSON 创建 UserInfo
   /// 从 JSON 创建 UserInfo
-  factory UserInfo.fromJson(Map<String, dynamic> json) {
-    return UserInfo(
-      id: json['id'] as int,
-      uuid: json['uuid'] as String,
-      institutionUuid: json['institution_uuid'] as String,
-      name: json['name'] as String,
-      gender: json['gender'] as int,
-      avatar: json['avatar'] as String?,
-      baseUuid: json['base_uuid'] as String,
-      username: json['username'] as String,
-      mobile: json['mobile'] as String,
-    );
-  }
+  factory UserInfo.fromJson(Map<String, dynamic> json) =>
+      _$UserInfoFromJson(json);
 
 
   /// 转换为 JSON
   /// 转换为 JSON
-  Map<String, dynamic> toJson() {
-    return {
-      'id': id,
-      'uuid': uuid,
-      'institution_uuid': institutionUuid,
-      'name': name,
-      'gender': gender,
-      'avatar': avatar,
-      'base_uuid': baseUuid,
-      'username': username,
-      'mobile': mobile,
-    };
-  }
+  Map<String, dynamic> toJson() => _$UserInfoToJson(this);
 }
 }
 
 
 /// 认证响应模型
 /// 认证响应模型
+@JsonSerializable()
 class AuthModel {
 class AuthModel {
+  @JsonKey(name: 'access_token')
   final String accessToken;
   final String accessToken;
+  @JsonKey(name: 'refresh_token')
   final String? refreshToken;
   final String? refreshToken;
+  @JsonKey(name: 'institution_info')
   final List<InstitutionInfo> institutionInfo;
   final List<InstitutionInfo> institutionInfo;
+  @JsonKey(name: 'user_info')
   final UserInfo userInfo;
   final UserInfo userInfo;
 
 
   AuthModel({
   AuthModel({
@@ -99,24 +78,9 @@ class AuthModel {
   });
   });
 
 
   /// 从 JSON 创建 AuthModel
   /// 从 JSON 创建 AuthModel
-  factory AuthModel.fromJson(Map<String, dynamic> json) {
-    return AuthModel(
-      accessToken: json['access_token'] as String,
-      refreshToken: json['refresh_token'] as String?,
-      institutionInfo: (json['institution_info'] as List)
-          .map((item) => InstitutionInfo.fromJson(item as Map<String, dynamic>))
-          .toList(),
-      userInfo: UserInfo.fromJson(json['user_info'] as Map<String, dynamic>),
-    );
-  }
+  factory AuthModel.fromJson(Map<String, dynamic> json) =>
+      _$AuthModelFromJson(json);
 
 
   /// 转换为 JSON
   /// 转换为 JSON
-  Map<String, dynamic> toJson() {
-    return {
-      'access_token': accessToken,
-      'refresh_token': refreshToken,
-      'institution_info': institutionInfo.map((item) => item.toJson()).toList(),
-      'user_info': userInfo.toJson(),
-    };
-  }
+  Map<String, dynamic> toJson() => _$AuthModelToJson(this);
 }
 }

+ 61 - 0
lib/features/auth/data/auth_model.g.dart

@@ -0,0 +1,61 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'auth_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+InstitutionInfo _$InstitutionInfoFromJson(Map<String, dynamic> json) =>
+    InstitutionInfo(
+      id: (json['id'] as num).toInt(),
+      uuid: json['uuid'] as String,
+      name: json['name'] as String,
+    );
+
+Map<String, dynamic> _$InstitutionInfoToJson(InstitutionInfo instance) =>
+    <String, dynamic>{
+      'id': instance.id,
+      'uuid': instance.uuid,
+      'name': instance.name,
+    };
+
+UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => UserInfo(
+  id: (json['id'] as num).toInt(),
+  uuid: json['uuid'] as String,
+  institutionUuid: json['institution_uuid'] as String,
+  name: json['name'] as String,
+  gender: (json['gender'] as num).toInt(),
+  avatar: json['avatar'] as String?,
+  baseUuid: json['base_uuid'] as String,
+  username: json['username'] as String,
+  mobile: json['mobile'] as String,
+);
+
+Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
+  'id': instance.id,
+  'uuid': instance.uuid,
+  'institution_uuid': instance.institutionUuid,
+  'name': instance.name,
+  'gender': instance.gender,
+  'avatar': instance.avatar,
+  'base_uuid': instance.baseUuid,
+  'username': instance.username,
+  'mobile': instance.mobile,
+};
+
+AuthModel _$AuthModelFromJson(Map<String, dynamic> json) => AuthModel(
+  accessToken: json['access_token'] as String,
+  refreshToken: json['refresh_token'] as String?,
+  institutionInfo: (json['institution_info'] as List<dynamic>)
+      .map((e) => InstitutionInfo.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  userInfo: UserInfo.fromJson(json['user_info'] as Map<String, dynamic>),
+);
+
+Map<String, dynamic> _$AuthModelToJson(AuthModel instance) => <String, dynamic>{
+  'access_token': instance.accessToken,
+  'refresh_token': instance.refreshToken,
+  'institution_info': instance.institutionInfo,
+  'user_info': instance.userInfo,
+};

+ 0 - 0
lib/features/auth/presentation/login_provider.dart → lib/features/auth/data/login_provider.dart


+ 28 - 36
lib/features/auth/presentation/login_page.dart

@@ -4,10 +4,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:go_router/go_router.dart';
 import 'package:sino_med_cloud/l10n/app_localizations.dart';
 import 'package:sino_med_cloud/l10n/app_localizations.dart';
 import 'package:sino_med_cloud/core/constants/app_constants.dart';
 import 'package:sino_med_cloud/core/constants/app_constants.dart';
+import 'package:sino_med_cloud/core/utils/toast_utils.dart'; // 添加 ToastUtils 引用
 import '../../../core/utils/logger.dart';
 import '../../../core/utils/logger.dart';
 import '../../../core/utils/crypto_utils.dart';
 import '../../../core/utils/crypto_utils.dart';
 import '../domain/login_service.dart';
 import '../domain/login_service.dart';
-import 'login_provider.dart';
+import '../data/login_provider.dart';
 
 
 class LoginPage extends ConsumerStatefulWidget {
 class LoginPage extends ConsumerStatefulWidget {
   const LoginPage({super.key});
   const LoginPage({super.key});
@@ -110,9 +111,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
     final l10n = AppLocalizations.of(context)!;
     final l10n = AppLocalizations.of(context)!;
     final phone = ref.read(smsLoginPhoneProvider);
     final phone = ref.read(smsLoginPhoneProvider);
     if (phone.isEmpty) {
     if (phone.isEmpty) {
-      ScaffoldMessenger.of(context).showSnackBar(
-        SnackBar(content: Text(l10n.phoneNumberRequiredForSms)),
-      );
+      ToastUtils.show(l10n.phoneNumberRequiredForSms);
       return;
       return;
     }
     }
 
 
@@ -155,9 +154,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
     } catch (e) {
     } catch (e) {
       AppLogger.e('发送验证码错误', e);
       AppLogger.e('发送验证码错误', e);
       if (mounted) {
       if (mounted) {
-        ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text(e.toString())),
-        );
+        ToastUtils.showError(e.toString());
       }
       }
     }
     }
   }
   }
@@ -167,12 +164,14 @@ class _LoginPageState extends ConsumerState<LoginPage>
     try {
     try {
       // 当前手机号及密码的格式已经验证过
       // 当前手机号及密码的格式已经验证过
       if (_passwordFormKey.currentState!.validate()) {
       if (_passwordFormKey.currentState!.validate()) {
-        final phoneNumber = ref.watch(passwordLoginPhoneProvider);
-        final password = ref.watch(passwordLoginPasswordProvider);
+        // 在异步方法中应使用 ref.read
+        final phoneNumber = ref.read(passwordLoginPhoneProvider);
+        final password = ref.read(passwordLoginPasswordProvider);
 
 
         // 对密码进行 MD5 加密
         // 对密码进行 MD5 加密
         final encryptedPassword = CryptoUtils.md5(password);
         final encryptedPassword = CryptoUtils.md5(password);
 
 
+        AppLogger.d('开始密码登录请求: $phoneNumber');
         // 调用登录服务
         // 调用登录服务
         await LoginService.passwordLogin(
         await LoginService.passwordLogin(
           mobile: phoneNumber,
           mobile: phoneNumber,
@@ -180,19 +179,16 @@ class _LoginPageState extends ConsumerState<LoginPage>
           loginSystem: _loginSystem,
           loginSystem: _loginSystem,
           loginType: _loginType,
           loginType: _loginType,
         );
         );
-
+        AppLogger.d('密码登录成功,准备跳转主页');
         // 登录成功,跳转到主页
         // 登录成功,跳转到主页
         if (mounted) {
         if (mounted) {
-          context.replace('/mainTab');
+          context.go('/mainTab');
         }
         }
       }
       }
-    } catch (e) {
-      AppLogger.e('密码登录错误', e);
+    } catch (e, stack) {
+      AppLogger.e('密码登录错误', e, stack);
       if (mounted) {
       if (mounted) {
-        final l10n = AppLocalizations.of(context)!;
-        ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text(e.toString())),
-        );
+        ToastUtils.showError(e.toString().replaceAll('Exception: ', ''));
       }
       }
     }
     }
   }
   }
@@ -203,27 +199,25 @@ class _LoginPageState extends ConsumerState<LoginPage>
       final l10n = AppLocalizations.of(context)!;
       final l10n = AppLocalizations.of(context)!;
       
       
       if (_smsFormKey.currentState!.validate()) {
       if (_smsFormKey.currentState!.validate()) {
-        final smsCode = ref.watch(smsLoginCodeProvider);
+        // 在异步方法中应使用 ref.read
+        final smsCode = ref.read(smsLoginCodeProvider);
         final serverSmsCode = ref.read(smsCodeFromServerProvider);
         final serverSmsCode = ref.read(smsCodeFromServerProvider);
         
         
         // 检查验证码是否已过期(为空或倒计时已结束)
         // 检查验证码是否已过期(为空或倒计时已结束)
         if (serverSmsCode.isEmpty) {
         if (serverSmsCode.isEmpty) {
-          ScaffoldMessenger.of(context).showSnackBar(
-            SnackBar(content: Text(l10n.smsCodeHasExpired)),
-          );
+          ToastUtils.show(l10n.smsCodeHasExpired);
           return;
           return;
         }
         }
         
         
         // 验证码输入错误
         // 验证码输入错误
         if (smsCode != serverSmsCode) {
         if (smsCode != serverSmsCode) {
-          ScaffoldMessenger.of(context).showSnackBar(
-            SnackBar(content: Text(l10n.smsCodeError)),
-          );
+          ToastUtils.showError(l10n.smsCodeError);
           return;
           return;
         }
         }
         
         
-        final phoneNumber = ref.watch(smsLoginPhoneProvider);
+        final phoneNumber = ref.read(smsLoginPhoneProvider);
 
 
+        AppLogger.d('开始验证码登录请求: $phoneNumber');
         // 调用登录服务
         // 调用登录服务
         await LoginService.smsLogin(
         await LoginService.smsLogin(
           mobile: phoneNumber,
           mobile: phoneNumber,
@@ -232,6 +226,7 @@ class _LoginPageState extends ConsumerState<LoginPage>
           loginType: _loginSmsType,
           loginType: _loginSmsType,
         );
         );
 
 
+        AppLogger.d('验证码登录成功,清理状态并跳转主页');
         // 登录成功,清理倒计时和定时器
         // 登录成功,清理倒计时和定时器
         _clearCountdownTimer();
         _clearCountdownTimer();
         ref.read(smsCountdownProvider.notifier).state = 0;
         ref.read(smsCountdownProvider.notifier).state = 0;
@@ -240,16 +235,13 @@ class _LoginPageState extends ConsumerState<LoginPage>
 
 
         // 跳转到主页
         // 跳转到主页
         if (mounted) {
         if (mounted) {
-          context.replace('/mainTab');
+          context.go('/mainTab');
         }
         }
       }
       }
-    } catch (e) {
-      AppLogger.e('验证码登录错误', e);
+    } catch (e, stack) {
+      AppLogger.e('验证码登录错误', e, stack);
       if (mounted) {
       if (mounted) {
-        final l10n = AppLocalizations.of(context)!;
-        ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text(e.toString())),
-        );
+        ToastUtils.showError(e.toString().replaceAll('Exception: ', ''));
       }
       }
     }
     }
   }
   }
@@ -417,10 +409,10 @@ class _LoginPageState extends ConsumerState<LoginPage>
             alignment: Alignment.centerRight,
             alignment: Alignment.centerRight,
             child: TextButton(
             child: TextButton(
               onPressed: () {
               onPressed: () {
-                // TODO: 跳转到忘记密码页面
-                ScaffoldMessenger.of(context).showSnackBar(
-                  SnackBar(content: Text(l10n.forgotPasswordNotImplemented)),
-                );
+                // 跳转到忘记密码页面
+                if (mounted) {
+                  context.push('/ChangePassword');
+                }
               },
               },
               child: Text(l10n.forgotPassword),
               child: Text(l10n.forgotPassword),
             ),
             ),

+ 2 - 0
lib/features/main_tab_page.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:sino_med_cloud/core/utils/logger.dart'; // 添加日志引用
 
 
 import 'HomePage/presentation/home_page.dart';
 import 'HomePage/presentation/home_page.dart';
 import 'HospitalPage/presentation/hospital_page.dart';
 import 'HospitalPage/presentation/hospital_page.dart';
@@ -19,6 +20,7 @@ class MainTabPage extends ConsumerWidget {
 
 
   @override
   @override
   Widget build(BuildContext context, WidgetRef ref) {
   Widget build(BuildContext context, WidgetRef ref) {
+    AppLogger.d('MainTabPage - 正在构建'); // 添加构建日志
     // 使用 ref.watch 监听当前 tab 索引的变化
     // 使用 ref.watch 监听当前 tab 索引的变化
     final currentIndex = ref.watch(currentTabIndexProvider);
     final currentIndex = ref.watch(currentTabIndexProvider);
 
 

+ 23 - 10
lib/l10n/app_localizations.dart

@@ -62,7 +62,7 @@ import 'app_localizations_zh.dart';
 /// property.
 /// property.
 abstract class AppLocalizations {
 abstract class AppLocalizations {
   AppLocalizations(String locale)
   AppLocalizations(String locale)
-      : localeName = intl.Intl.canonicalizedLocale(locale.toString());
+    : localeName = intl.Intl.canonicalizedLocale(locale.toString());
 
 
   final String localeName;
   final String localeName;
 
 
@@ -85,11 +85,11 @@ abstract class AppLocalizations {
   /// of delegates is preferred or required.
   /// of delegates is preferred or required.
   static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
   static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
       <LocalizationsDelegate<dynamic>>[
       <LocalizationsDelegate<dynamic>>[
-    delegate,
-    GlobalMaterialLocalizations.delegate,
-    GlobalCupertinoLocalizations.delegate,
-    GlobalWidgetsLocalizations.delegate,
-  ];
+        delegate,
+        GlobalMaterialLocalizations.delegate,
+        GlobalCupertinoLocalizations.delegate,
+        GlobalWidgetsLocalizations.delegate,
+      ];
 
 
   /// A list of this localizations delegate's supported locales.
   /// A list of this localizations delegate's supported locales.
   static const List<Locale> supportedLocales = <Locale>[Locale('zh')];
   static const List<Locale> supportedLocales = <Locale>[Locale('zh')];
@@ -190,6 +190,18 @@ abstract class AppLocalizations {
   /// **'退出登录'**
   /// **'退出登录'**
   String get logout;
   String get logout;
 
 
+  /// 我的页面标题
+  ///
+  /// In zh, this message translates to:
+  /// **'我的'**
+  String get mine;
+
+  /// 修改密码按钮
+  ///
+  /// In zh, this message translates to:
+  /// **'修改密码'**
+  String get changePassword;
+
   /// 登录功能提示
   /// 登录功能提示
   ///
   ///
   /// In zh, this message translates to:
   /// In zh, this message translates to:
@@ -294,8 +306,9 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
   }
   }
 
 
   throw FlutterError(
   throw FlutterError(
-      'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
-      'an issue with the localizations generation tool. Please file an issue '
-      'on GitHub with a reproducible sample app and the gen-l10n configuration '
-      'that was used.');
+    'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
+    'an issue with the localizations generation tool. Please file an issue '
+    'on GitHub with a reproducible sample app and the gen-l10n configuration '
+    'that was used.',
+  );
 }
 }

+ 6 - 0
lib/l10n/app_localizations_zh.dart

@@ -57,6 +57,12 @@ class AppLocalizationsZh extends AppLocalizations {
   String get logout => '退出登录';
   String get logout => '退出登录';
 
 
   @override
   @override
+  String get mine => '我的';
+
+  @override
+  String get changePassword => '修改密码';
+
+  @override
   String get loginNotImplemented => '登录功能待实现';
   String get loginNotImplemented => '登录功能待实现';
 
 
   @override
   @override

+ 8 - 0
lib/l10n/app_zh.arb

@@ -64,6 +64,14 @@
   "@logout": {
   "@logout": {
     "description": "退出登录按钮"
     "description": "退出登录按钮"
   },
   },
+  "mine": "我的",
+  "@mine": {
+    "description": "我的页面标题"
+  },
+  "changePassword": "修改密码",
+  "@changePassword": {
+    "description": "修改密码按钮"
+  },
   "loginNotImplemented": "登录功能待实现",
   "loginNotImplemented": "登录功能待实现",
   "@loginNotImplemented": {
   "@loginNotImplemented": {
     "description": "登录功能提示"
     "description": "登录功能提示"

+ 144 - 0
pubspec.lock

@@ -41,6 +41,54 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "2.1.2"
     version: "2.1.2"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.3"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  build_daemon:
+    dependency: transitive
+    description:
+      name: build_daemon
+      sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.1"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.10.4"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.1.1"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.12.1"
   characters:
   characters:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -49,6 +97,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "1.4.0"
     version: "1.4.0"
+  checked_yaml:
+    dependency: transitive
+    description:
+      name: checked_yaml
+      sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.4"
   cli_config:
   cli_config:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -65,6 +121,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "1.1.2"
     version: "1.1.2"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.11.0"
   collection:
   collection:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -121,6 +185,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "1.0.8"
     version: "1.0.8"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.3"
   dbus:
   dbus:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -185,6 +257,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "7.0.1"
     version: "7.0.1"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
   flutter:
   flutter:
     dependency: "direct main"
     dependency: "direct main"
     description: flutter
     description: flutter
@@ -237,6 +317,14 @@ packages:
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     version: "0.0.0"
+  fluttertoast:
+    dependency: "direct main"
+    description:
+      name: fluttertoast
+      sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2"
+      url: "https://pub.dev"
+    source: hosted
+    version: "9.0.0"
   frontend_server_client:
   frontend_server_client:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -261,6 +349,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "17.0.1"
     version: "17.0.1"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
   http:
   http:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -309,6 +405,22 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "0.7.2"
     version: "0.7.2"
+  json_annotation:
+    dependency: "direct main"
+    description:
+      name: json_annotation
+      sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.9.0"
+  json_serializable:
+    dependency: "direct main"
+    description:
+      name: json_serializable
+      sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.11.2"
   leak_tracker:
   leak_tracker:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -589,6 +701,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "2.2.0"
     version: "2.2.0"
+  pubspec_parse:
+    dependency: transitive
+    description:
+      name: pubspec_parse
+      sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.5.0"
   responsive_framework:
   responsive_framework:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
@@ -698,6 +818,22 @@ packages:
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     version: "0.0.0"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.1"
+  source_helper:
+    dependency: transitive
+    description:
+      name: source_helper
+      sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.8"
   source_map_stack_trace:
   source_map_stack_trace:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -786,6 +922,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "2.1.4"
     version: "2.1.4"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
   string_scanner:
   string_scanner:
     dependency: transitive
     dependency: transitive
     description:
     description:

+ 5 - 1
pubspec.yaml

@@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 version: 1.0.0+1
 version: 1.0.0+1
 
 
 environment:
 environment:
-  sdk: '>=3.0.0'
+  sdk: '>=3.8.0'
 
 
 # Dependencies specify other packages that your package needs in order to work.
 # Dependencies specify other packages that your package needs in order to work.
 # To automatically upgrade your package dependencies to the latest versions
 # To automatically upgrade your package dependencies to the latest versions
@@ -54,6 +54,9 @@ dependencies:
   intl: ^0.20.2
   intl: ^0.20.2
   logger: ^2.6.2
   logger: ^2.6.2
   crypto: ^3.0.5
   crypto: ^3.0.5
+  fluttertoast: ^9.0.0
+  json_annotation: ^4.9.0
+  json_serializable: ^6.11.2
 
 
 dev_dependencies:
 dev_dependencies:
   flutter_test:
   flutter_test:
@@ -65,6 +68,7 @@ dev_dependencies:
   # package. See that file for information about deactivating specific lint
   # package. See that file for information about deactivating specific lint
   # rules and activating additional ones.
   # rules and activating additional ones.
   flutter_lints: ^6.0.0
   flutter_lints: ^6.0.0
+  build_runner: ^2.4.13
 
 
 # For information on the generic Dart part of this file, see the
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 # following page: https://dart.dev/tools/pub/pubspec