├── test └── widget_test.dart ├── lib ├── app │ ├── modules │ │ ├── components │ │ │ ├── image_upload │ │ │ │ └── keep │ │ │ ├── bar_chart │ │ │ │ └── bar_chart.dart │ │ │ ├── line_chart │ │ │ │ └── line_chart.dart │ │ │ ├── big_tree │ │ │ │ ├── big_node.dart │ │ │ │ └── big_tree.dart │ │ │ ├── dashed_line │ │ │ │ └── dashed_line.dart │ │ │ ├── big_card │ │ │ │ └── big_card.dart │ │ │ ├── big_data_grid │ │ │ │ ├── big_grid_def.dart │ │ │ │ ├── big_data_field.dart │ │ │ │ ├── big_data_column.dart │ │ │ │ └── big_data_grid.dart │ │ │ ├── dialog │ │ │ │ ├── big_alert.dart │ │ │ │ └── big_dialog.dart │ │ │ └── combobox │ │ │ │ └── dropdown2.dart │ │ ├── home │ │ │ ├── base_page.dart │ │ │ ├── home_binding.dart │ │ │ ├── dashboard │ │ │ │ ├── dashboard_binding.dart │ │ │ │ ├── dashboard_controller.dart │ │ │ │ ├── widget │ │ │ │ │ ├── header.dart │ │ │ │ │ └── miniinfo_card.dart │ │ │ │ └── dashboard_view.dart │ │ │ ├── role_manage │ │ │ │ ├── role_manage_binding.dart │ │ │ │ ├── role_manage_controller.dart │ │ │ │ └── role_manage_view.dart │ │ │ ├── user_manage │ │ │ │ ├── user_manage_binding.dart │ │ │ │ ├── user_manage_controller.dart │ │ │ │ └── user_manage_view.dart │ │ │ ├── group_manage │ │ │ │ ├── group_manage_binding.dart │ │ │ │ ├── group_manage_controller.dart │ │ │ │ └── group_manage_view.dart │ │ │ ├── module_manage │ │ │ │ ├── module_manage_binding.dart │ │ │ │ ├── module_manage_controller.dart │ │ │ │ └── module_manage_view.dart │ │ │ ├── channel_manage │ │ │ │ ├── channel_manage_binding.dart │ │ │ │ ├── channel_manage_controller.dart │ │ │ │ └── channel_manage_view.dart │ │ │ ├── sysconfig_manage │ │ │ │ ├── sys_config_manage_binding.dart │ │ │ │ ├── sys_config_manage_controller.dart │ │ │ │ └── sys_config_manage_view.dart │ │ │ ├── permission_manage │ │ │ │ ├── permission_manage_binding.dart │ │ │ │ ├── permission_manage_controller.dart │ │ │ │ └── permission_manage_view.dart │ │ │ ├── home_controller.dart │ │ │ └── home_view.dart │ │ ├── login │ │ │ ├── login_binding.dart │ │ │ ├── login_controller.dart │ │ │ └── login_view.dart │ │ └── loading │ │ │ ├── loading_binding.dart │ │ │ ├── loading_controller.dart │ │ │ └── loading_view.dart │ ├── models │ │ ├── big_notification.dart │ │ ├── base_model.dart │ │ ├── chart_line.dart │ │ ├── pager.dart │ │ ├── module.dart │ │ └── query_param.dart │ ├── consts │ │ ├── global.dart │ │ └── consts.dart │ ├── utils │ │ ├── json_util.dart │ │ ├── datetime_util.dart │ │ ├── datatype_util.dart │ │ ├── http_util.dart │ │ ├── url_util.dart │ │ └── local_storage.dart │ ├── theme │ │ └── app_theme.dart │ ├── api │ │ ├── user_api.dart │ │ └── base_api.dart │ └── routes │ │ ├── app_routes.dart │ │ └── app_pages.dart ├── window_size_service.dart └── main.dart ├── imgs ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── 8.png ├── .gitattributes ├── assets ├── images │ ├── 404.png │ ├── logo.png │ ├── avatar.png │ ├── loading_bg.jpg │ ├── login_bg.jpg │ ├── vector-1.png │ ├── vector-2.png │ ├── vector-3.png │ └── profile_pic.png ├── fonts │ └── Poppins-Regular.ttf └── icons │ ├── media.svg │ ├── google_drive.svg │ ├── folder.svg │ ├── media_file.svg │ ├── search.svg │ ├── menu_profile.svg │ ├── menu_task.svg │ ├── menu_doc.svg │ ├── menu_tran.svg │ ├── documents.svg │ ├── unknown.svg │ ├── one_drive.svg │ ├── Figma_file.svg │ ├── excel_file.svg │ ├── doc_file.svg │ ├── sound_file.svg │ ├── menu_notification.svg │ ├── drop_box.svg │ ├── menu_dashboard.svg │ ├── menu_store.svg │ ├── xd_file.svg │ ├── menu_setting.svg │ └── pdf_file.svg ├── go_author_client.code-workspace ├── doc └── getx使用说明.txt ├── README.md ├── analysis_options.yaml ├── .vscode └── launch.json ├── .gitignore ├── .metadata └── pubspec.yaml /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/app/modules/components/image_upload/keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/app/modules/components/bar_chart/bar_chart.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/app/modules/components/line_chart/line_chart.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/1.png -------------------------------------------------------------------------------- /imgs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/2.png -------------------------------------------------------------------------------- /imgs/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/3.png -------------------------------------------------------------------------------- /imgs/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/4.png -------------------------------------------------------------------------------- /imgs/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/5.png -------------------------------------------------------------------------------- /imgs/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/6.png -------------------------------------------------------------------------------- /imgs/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/7.png -------------------------------------------------------------------------------- /imgs/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/imgs/8.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/404.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/avatar.png -------------------------------------------------------------------------------- /assets/images/loading_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/loading_bg.jpg -------------------------------------------------------------------------------- /assets/images/login_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/login_bg.jpg -------------------------------------------------------------------------------- /assets/images/vector-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/vector-1.png -------------------------------------------------------------------------------- /assets/images/vector-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/vector-2.png -------------------------------------------------------------------------------- /assets/images/vector-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/vector-3.png -------------------------------------------------------------------------------- /assets/images/profile_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/images/profile_pic.png -------------------------------------------------------------------------------- /assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigCatGit/go_author_client/HEAD/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /go_author_client.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/models/big_notification.dart: -------------------------------------------------------------------------------- 1 | class BigNotification { 2 | dynamic msg; 3 | String id; 4 | BigNotification({required this.id, this.msg}); 5 | } 6 | -------------------------------------------------------------------------------- /doc/getx使用说明.txt: -------------------------------------------------------------------------------- 1 | > 安装getx cli 2 | flutter pub global activate get_cli 3 | 4 | > 创建页面 5 | get create page:home 6 | 7 | > 添加依赖库 8 | flutter pub add excel 9 | -------------------------------------------------------------------------------- /lib/app/modules/home/base_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BasePage { 4 | String path; 5 | Widget page; 6 | 7 | BasePage({required this.path, required this.page}); 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/modules/home/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_controller.dart'; 4 | 5 | class HomeBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/login/login_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'login_controller.dart'; 4 | 5 | class LoginBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => LoginController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/loading/loading_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'loading_controller.dart'; 4 | 5 | class LoadingBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.put( 9 | LoadingController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/models/base_model.dart: -------------------------------------------------------------------------------- 1 | class BaseModel { 2 | BaseModel(); 3 | 4 | factory BaseModel.fromJson(Map json) { 5 | return BaseModel(); 6 | } 7 | 8 | Map toJson() { 9 | final Map data = {}; 10 | return data; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/dashboard/dashboard_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'dashboard_controller.dart'; 4 | 5 | class DashboardBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => DashboardController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/consts/global.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:go_author_client/app/models/module.dart'; 3 | 4 | class Global { 5 | static final List modules = []; 6 | // 权限url列表 7 | static final Set permissions = {}; 8 | // 初始化事件 9 | static final EventBus eventBus = EventBus(); 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/modules/home/role_manage/role_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'role_manage_controller.dart'; 4 | 5 | class RoleManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => RoleManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/user_manage/user_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'user_manage_controller.dart'; 4 | 5 | class UserManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => UserManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/group_manage/group_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'group_manage_controller.dart'; 4 | 5 | class GroupManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => GroupManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/module_manage/module_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'module_manage_controller.dart'; 4 | 5 | class ModuleManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ModuleManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/channel_manage/channel_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'channel_manage_controller.dart'; 4 | 5 | class ChannelManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ChannelManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go_author_client 2 | 3 | 目前只适配了pc端 4 | 5 | ## 运行项目 6 | 7 | 删除项目下的build、macos、windows目录, 根本运营平台重新启用mac或者windows, 再运行即可。这一步不会的请稳步flutter官方文档学习项目的构建和运行 8 | 9 | ## Mac平台DIO无法访问网络 10 | 11 | 在*package*/macos/Runner下DebugProfile.entilements与Release.entilements添加网络访问权限: 12 | 13 | ``` 14 | com.apple.security.network.client 15 | 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /lib/app/modules/home/sysconfig_manage/sys_config_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'sys_config_manage_controller.dart'; 4 | 5 | class SysConfigManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => SysConfigManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/permission_manage/permission_manage_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'permission_manage_controller.dart'; 4 | 5 | class PermissionManageBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => PermissionManageController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/utils/json_util.dart: -------------------------------------------------------------------------------- 1 | // import 'dart:convert'; 2 | import 'package:validators/validators.dart'; 3 | 4 | class JsonUtil { 5 | bool isJson(String jsonStr) { 6 | try { 7 | // 使用原生包 8 | // json.decode(jsonStr) as Map; 9 | // return true; 10 | return isJSON(jsonStr); 11 | } catch (e) { 12 | return false; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/modules/home/module_manage/module_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class ModuleManageController extends GetxController { 4 | @override 5 | void onInit() { 6 | super.onInit(); 7 | } 8 | 9 | @override 10 | void onReady() { 11 | super.onReady(); 12 | } 13 | 14 | @override 15 | void onClose() { 16 | super.onClose(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | linter: 3 | rules: 4 | prefer_const_constructors: false 5 | prefer_const_literals_to_create_immutables: false 6 | constant_identifier_names: false 7 | unnecessary_overrides: false 8 | unnecessary_this: false 9 | non_constant_identifier_names: false 10 | 11 | analyzer: 12 | errors: 13 | must_be_immutable: ignore 14 | -------------------------------------------------------------------------------- /assets/icons/media.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/app/modules/home/role_manage/role_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class RoleManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/user_manage/user_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class UserManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/group_manage/group_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class GroupManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/channel_manage/channel_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class ChannelManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/sysconfig_manage/sys_config_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class SysConfigManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/permission_manage/permission_manage_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class PermissionManageController extends GetxController { 4 | final count = 0.obs; 5 | @override 6 | void onInit() { 7 | super.onInit(); 8 | } 9 | 10 | @override 11 | void onReady() { 12 | super.onReady(); 13 | } 14 | 15 | @override 16 | void onClose() { 17 | super.onClose(); 18 | } 19 | 20 | void increment() => count.value++; 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/consts/consts.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class Consts { 4 | static const String title = "后台管理"; 5 | 6 | static const primaryColor = Color(0xFF2697FF); 7 | static const secondaryColor = Color(0xFF2A2D3E); 8 | static const bgColor = Color(0xFF212332); 9 | 10 | static const defaultPadding = 16.0; 11 | 12 | static const server_host = "http://127.0.0.1:83"; 13 | static const api_login = "/login"; 14 | static const api_logout = "/logout"; 15 | 16 | // 测试模式模拟延迟/毫秒 17 | static const debug_delay = 200; 18 | } 19 | -------------------------------------------------------------------------------- /lib/app/models/chart_line.dart: -------------------------------------------------------------------------------- 1 | import 'package:fl_chart/fl_chart.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ChartLine { 5 | String? title; 6 | IconData? icon; 7 | List spots; 8 | List colors; 9 | Map leftTitles; 10 | Map bottomTitles; 11 | Color? belowBarColor; 12 | 13 | ChartLine({ 14 | required this.spots, 15 | required this.colors, 16 | this.leftTitles = const {}, 17 | this.bottomTitles = const {}, 18 | this.title, 19 | this.icon, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /assets/icons/google_drive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/app/utils/datetime_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart' as intl; 2 | 3 | class DateTimeUtil { 4 | static DateTime from(String gmt) { 5 | return DateTime.parse(gmt); 6 | } 7 | 8 | static String format(DateTime dateTime, {String format = 'yyyy-MM-dd HH:mm:ss'}) { 9 | intl.DateFormat formatter = intl.DateFormat(format); 10 | return formatter.format(dateTime); 11 | } 12 | 13 | static DateTime parse(String dateTimeString, {String format = 'yyyy-MM-dd HH:mm:ss'}) { 14 | intl.DateFormat parser = intl.DateFormat(format); 15 | return parser.parse(dateTimeString); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/modules/loading/loading_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:go_author_client/window_size_service.dart'; 3 | 4 | class LoadingController extends GetxController { 5 | final RxBool isLoading = true.obs; // 用于表示是否正在加载数据 6 | 7 | final count = 0.obs; 8 | @override 9 | void onInit() { 10 | super.onInit(); 11 | WindowSizeService.instance.setWindowSize(); 12 | } 13 | 14 | @override 15 | void onReady() { 16 | super.onReady(); 17 | } 18 | 19 | @override 20 | void onClose() { 21 | super.onClose(); 22 | } 23 | 24 | void increment() => count.value++; 25 | } 26 | -------------------------------------------------------------------------------- /assets/icons/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/media_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/menu_profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/menu_task.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/menu_doc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/menu_tran.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter调试", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "lib/main.dart", 12 | "args": [], 13 | "flutterMode": "debug", 14 | }, 15 | { 16 | "name": "Flutter性能", 17 | "request": "launch", 18 | "type": "dart", 19 | "flutterMode": "profile" 20 | }, 21 | { 22 | "name": "Flutter运行", 23 | "request": "launch", 24 | "type": "dart", 25 | "flutterMode": "release" 26 | }, 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/theme/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppTheme { 4 | AppTheme._(); 5 | static const Color nearlyWhite = Color(0xFFFAFAFA); 6 | static const Color white = Color(0xFFFFFFFF); 7 | static const Color background = Color(0xFFF2F3F8); 8 | static const Color nearlyDarkBlue = Color(0xFF2633C5); 9 | 10 | static const Color nearlyBlue = Color(0xFF00B6F0); 11 | static const Color nearlyBlack = Color(0xFF213333); 12 | static const Color grey = Color(0xFF3A5160); 13 | static const Color dark_grey = Color(0xFF313A44); 14 | 15 | static const Color darkText = Color(0xFF253840); 16 | static const Color darkerText = Color(0xFF17262A); 17 | static const Color lightText = Color(0xFF4A6572); 18 | static const Color deactivatedText = Color(0xFF767676); 19 | static const Color dismissibleBackground = Color(0xFF364A54); 20 | static const Color spacer = Color(0xFFF2F2F2); 21 | } 22 | -------------------------------------------------------------------------------- /assets/icons/documents.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/app/utils/datatype_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class DataTypeUtil { 4 | static dynamic convertDataType(dynamic value, String toType) { 5 | switch (toType) { 6 | case 'int': 7 | return int.tryParse(value.toString()) ?? 0; 8 | case 'double': 9 | return double.tryParse(value.toString()) ?? 0.0; 10 | case 'bool': 11 | return value.toString().toLowerCase() == 'true'; 12 | case 'String': 13 | case 'string': 14 | return value.toString(); 15 | case 'datetime': 16 | case 'date': 17 | try { 18 | return DateTime.parse(value).toString(); 19 | } catch (e) { 20 | return null; 21 | } 22 | case 'List': 23 | case 'list': 24 | case 'Map': 25 | case 'map': 26 | return json.decode(value.toString()); 27 | // Add more cases for additional types as needed 28 | default: 29 | return value; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/app/api/user_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:go_author_client/app/api/base_api.dart'; 2 | 3 | class UserApi extends BaseApi { 4 | /// 登录 5 | /// @param username 6 | /// @param password 7 | Future?> login(String username, String password) async { 8 | var params = {"username": username, "password": password}; 9 | // token在父类会自动上传 10 | return await super.post("/login", params, null); 11 | } 12 | 13 | /// 登出 14 | Future?> logout() async { 15 | // token在父类会自动上传 16 | return await super.post("/logout", null, null); 17 | } 18 | 19 | /// 获取用户信息 20 | Future?> getProfile() async { 21 | // token在父类会自动上传 22 | return await super.get("/admin/system/member/getProfile", null, null); 23 | } 24 | 25 | /// 获取用户权限模块树 26 | Future?> getModuleTree() async { 27 | // token在父类会自动上传 28 | return await super.get("/admin/system/member/getModuleTree", null, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | /macos/ 35 | /windows/ 36 | /linux/ 37 | /ios/ 38 | /android/ 39 | /web/ 40 | 41 | # Symbolication related 42 | app.*.symbols 43 | 44 | # Obfuscation related 45 | app.*.map.json 46 | .flutter-plugins* 47 | 48 | # Android Studio will place build artifacts here 49 | /android/app/debug 50 | /android/app/profile 51 | /android/app/release 52 | -------------------------------------------------------------------------------- /lib/app/utils/http_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class HttpUtil { 4 | /// get请求 5 | /// @param url 6 | /// @param params 7 | /// @param headers 8 | static Future get( 9 | String url, 10 | Map? params, 11 | Map? headers, 12 | ) async { 13 | params ??= {}; 14 | headers ??= {}; 15 | 16 | ///发起get请求 17 | var dio = Dio(); 18 | Response response = await dio.get(url, queryParameters: params, options: Options(headers: headers)); 19 | return response; 20 | } 21 | 22 | static Future post( 23 | String url, 24 | Map? params, 25 | Map? headers, 26 | ) async { 27 | params ??= {}; 28 | headers ??= {}; 29 | 30 | ///发起get请求 31 | var dio = Dio(); 32 | Response response = await dio.post( 33 | url, 34 | data: params, 35 | options: Options(headers: headers), 36 | ); 37 | return response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/icons/unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/app/modules/home/dashboard/dashboard_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/consts/global.dart'; 5 | import 'package:go_author_client/app/models/big_notification.dart'; 6 | 7 | class DashboardController extends GetxController { 8 | final RxInt count = RxInt(0); 9 | late Timer timer; 10 | final RxString nickname = RxString(""); 11 | 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | Global.eventBus.on().listen((event) { 16 | if (event.id == "profile") { 17 | this.nickname.value = event.msg["nickname"] ?? ""; 18 | } 19 | }); 20 | } 21 | 22 | @override 23 | void onReady() { 24 | super.onReady(); 25 | startTimer(); 26 | } 27 | 28 | @override 29 | void onClose() { 30 | super.onClose(); 31 | } 32 | 33 | void increment() => count.value++; 34 | void startTimer() { 35 | timer = Timer.periodic(Duration(seconds: 1), (timer) { 36 | count.value++; 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/icons/one_drive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "abb292a07e20d696c4568099f918f6c5f330e6b0" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 17 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 18 | - platform: macos 19 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 20 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /assets/icons/Figma_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/icons/excel_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/app/models/pager.dart: -------------------------------------------------------------------------------- 1 | import 'base_model.dart'; 2 | 3 | class Pager extends BaseModel { 4 | List> data; 5 | int page_num; 6 | int page_size; 7 | int page_count; 8 | int total; 9 | 10 | Pager({ 11 | required this.data, 12 | required this.page_num, 13 | required this.page_size, 14 | required this.page_count, 15 | required this.total, 16 | }); 17 | 18 | @override 19 | factory Pager.fromJson(Map json) { 20 | return Pager( 21 | data: json['data'] == null ? [] : List>.from(json['data'] as List), 22 | page_num: json['page_num'] as int, 23 | page_size: json['page_size'] as int, 24 | page_count: json['page_count'] as int, 25 | total: json['total'] as int, 26 | ); 27 | } 28 | 29 | @override 30 | Map toJson() { 31 | final Map data = { 32 | 'data': this.data, 33 | 'page_num': page_num, 34 | 'page_size': page_size, 35 | 'page_count': page_count, 36 | 'total': total, 37 | }; 38 | return data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /assets/icons/doc_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | static const LOADING = _Paths.LOADING; 7 | static const LOGIN = _Paths.LOGIN; 8 | static const HOME = _Paths.HOME; 9 | static const PERMISSION_MANAGE = _Paths.PERMISSION_MANAGE; 10 | static const ROLE_MANAGE = _Paths.ROLE_MANAGE; 11 | static const USER_MANAGE = _Paths.USER_MANAGE; 12 | static const GROUP_MANAGE = _Paths.GROUP_MANAGE; 13 | static const SYS_CONFIG_MANAGE = _Paths.SYS_CONFIG_MANAGE; 14 | static const CHANNEL_MANAGE = _Paths.CHANNEL_MANAGE; 15 | } 16 | 17 | abstract class _Paths { 18 | _Paths._(); 19 | static const LOGIN = '/login'; 20 | static const HOME = '/home'; 21 | static const LOADING = '/loading'; 22 | static const PERMISSION_MANAGE = '/permission-manage'; 23 | static const ROLE_MANAGE = '/role-manage'; 24 | static const USER_MANAGE = '/user-manage'; 25 | static const GROUP_MANAGE = '/group-manage'; 26 | static const SYS_CONFIG_MANAGE = '/sys-config-manage'; 27 | static const CHANNEL_MANAGE = '/channel-manage'; 28 | } 29 | -------------------------------------------------------------------------------- /assets/icons/sound_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/menu_notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/drop_box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: go_author_client 2 | version: 1.0.0+1 3 | publish_to: none 4 | description: admin client 5 | environment: 6 | sdk: ">=2.19.4" 7 | 8 | dependencies: 9 | cupertino_icons: ^1.0.5 10 | get: 4.6.5 11 | google_fonts: ^4.0.4 12 | flutter: 13 | sdk: flutter 14 | flutter_localizations: 15 | sdk: flutter 16 | 17 | provider: ^6.0.5 18 | fl_chart: ^0.65.0 19 | flutter_svg: ^2.0.5 20 | dio: ^5.4.1 21 | validators: ^3.0.0 22 | json_annotation: ^4.8.0 23 | 24 | window_size: 25 | git: 26 | url: https://github.com/google/flutter-desktop-embedding 27 | path: plugins/window_size 28 | ref: 6c66ad23ee79749f30a8eece542cf54eaf157ed8 29 | shared_preferences: ^2.2.1 30 | bot_toast: ^4.1.3 31 | common_utils: ^2.1.0 32 | syncfusion_flutter_datagrid: ^23.1.44 33 | syncfusion_localizations: ^23.1.44 34 | intl: ^0.18.1 35 | file_picker: ^6.1.1 36 | dropdown_button2: ^2.3.9 37 | excel: ^4.0.2 38 | event_bus: ^2.0.0 39 | 40 | dev_dependencies: 41 | flutter_lints: 2.0.1 42 | flutter_test: 43 | sdk: flutter 44 | build_runner: ^2.3.0 45 | json_serializable: ^6.1.0 46 | 47 | flutter: 48 | uses-material-design: true 49 | 50 | fonts: 51 | - family: Poppins 52 | fonts: 53 | - asset: assets/fonts/Poppins-Regular.ttf 54 | 55 | assets: 56 | - assets/images/ 57 | - assets/fonts/ 58 | - assets/icons/ 59 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_tree/big_node.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | const BIGTREE_STATUS_CHECKED_NONE = 0; 5 | const BIGTREE_STATUS_CHECKED_HALF = 1; 6 | const BIGTREE_STATUS_CHECKED_FULL = 2; 7 | 8 | class BigNode { 9 | int id; 10 | String title; 11 | int parentId; 12 | Icon? icon; 13 | RxInt? checked = 0.obs; 14 | RxBool? expanded = false.obs; 15 | bool alone; 16 | List childs; 17 | 18 | BigNode({ 19 | required this.id, 20 | required this.title, 21 | this.parentId = 0, 22 | this.expanded, 23 | this.checked, 24 | this.alone = true, 25 | this.childs = const [], 26 | }) { 27 | checked = checked ?? BIGTREE_STATUS_CHECKED_NONE.obs; 28 | expanded = expanded ?? false.obs; 29 | } 30 | 31 | factory BigNode.fromJson(Map json) { 32 | return BigNode( 33 | id: json['id'], 34 | title: json['title'], 35 | parentId: json['parentId'], 36 | checked: json['checked'].obs, 37 | expanded: json['expanded'].obs, 38 | childs: json['childs'] != null ? List.from(json['childs'].map((child) => BigNode.fromJson(child))) : [], 39 | ); 40 | } 41 | 42 | Map toJson() { 43 | final Map data = { 44 | 'id': this.id, 45 | 'title': this.title, 46 | 'parentId': this.parentId, 47 | 'checked': this.checked!.value, 48 | 'expanded': this.expanded!.value, 49 | 'childs': this.childs.map((child) => child.toJson()).toList(), 50 | }; 51 | return data; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/icons/menu_dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/menu_store.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/app/utils/url_util.dart: -------------------------------------------------------------------------------- 1 | class UrlUtil { 2 | static String getHostAndSchema(String url) { 3 | if (!url.contains("?")) { 4 | return url; 5 | } 6 | return url.split("?")[0]; 7 | } 8 | 9 | static String getQueryString(String url) { 10 | if (!url.contains("?")) { 11 | return ''; 12 | } 13 | return url.split("?")[1]; 14 | } 15 | 16 | static Map urlToMap(String url) { 17 | if (!url.contains("?")) { 18 | return {}; 19 | } 20 | var query = url.split("?")[1]; 21 | return Uri.splitQueryString(query); 22 | } 23 | 24 | static String mapToQuery(Map params) { 25 | // 这种方法只支持值为String的情况 26 | // var uri = Uri(queryParameters: params); 27 | // return uri.query; 28 | // 手动实现 29 | var queryString = ""; 30 | var split = ""; 31 | for (var key in params.keys) { 32 | queryString += "$split$key=${params[key]}"; 33 | split = "&"; 34 | } 35 | return queryString; 36 | } 37 | 38 | static String mapToUrl(String host, Map params) { 39 | return "$host?${mapToQuery(params)}"; 40 | } 41 | 42 | static addUrlParams(String url, Map params) { 43 | var map = urlToMap(url); 44 | if (map.isEmpty) map = {}; 45 | map.addAll(params); 46 | return mapToUrl(getHostAndSchema(url), map); 47 | } 48 | } 49 | 50 | // void main() { 51 | // var map = UrlUtil.urlToMap("https://www.baidu.com?name=1&age=2"); 52 | // print(UrlUtil.mapToUrlQuery("https://www.baidu.com", map).toString()); 53 | // print(UrlUtil.addUrlParams("https://www.baidu.com?name1=1&age1=2", map).toString()); 54 | // } 55 | -------------------------------------------------------------------------------- /lib/app/modules/components/dashed_line/dashed_line.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DashedLine extends StatelessWidget { 4 | final Axis axis; // 水平方向 & 垂直方向 5 | final double dashedWidth; // 虚线宽度 6 | final double dashedHeight; // 虚线高度 7 | final int count; // 虚线总个数 8 | final Color dashedColor; // 虚线颜色 9 | double dashedTotalLengthWith; // 虚线水平垂直总长度 10 | EdgeInsetsGeometry padding; 11 | 12 | DashedLine({ 13 | super.key, 14 | required this.axis, 15 | this.dashedWidth = 1, 16 | this.dashedHeight = 1, 17 | this.count = 10, 18 | this.dashedColor = Colors.grey, 19 | this.dashedTotalLengthWith = 200, 20 | this.padding = EdgeInsets.zero, 21 | }); 22 | 23 | Widget showDashedLineWidgets() { 24 | return Padding( 25 | padding: padding, 26 | child: Flex( 27 | direction: axis, 28 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 | children: List.generate(count, (index) { 30 | return SizedBox( 31 | width: dashedWidth, 32 | height: dashedHeight, 33 | child: DecoratedBox( 34 | decoration: BoxDecoration(color: dashedColor), 35 | ), 36 | ); 37 | }), 38 | ), 39 | ); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { 45 | return axis == Axis.horizontal 46 | ? SizedBox(width: dashedTotalLengthWith, child: showDashedLineWidgets()) 47 | : SizedBox( 48 | height: dashedTotalLengthWith, 49 | child: showDashedLineWidgets(), 50 | ); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_card/big_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BigCard extends StatelessWidget { 4 | String title; 5 | TextStyle? titleStyle; 6 | Widget child; 7 | Color? backgroundColor; 8 | Widget? leftIcon; 9 | Widget? rightIcon; 10 | double? width; 11 | double? height; 12 | double? padding; 13 | BigCard({ 14 | super.key, 15 | required this.title, 16 | required this.child, 17 | this.leftIcon, 18 | this.rightIcon, 19 | this.titleStyle, 20 | this.backgroundColor, 21 | this.width, 22 | this.height, 23 | this.padding = 16, 24 | }); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return SizedBox( 29 | width: width, 30 | height: height, 31 | child: Container( 32 | decoration: BoxDecoration( 33 | color: backgroundColor ?? Color.fromARGB(0xFF, 0x2A, 0x2A, 0x2A), 34 | borderRadius: const BorderRadius.all(Radius.circular(10)), 35 | ), 36 | padding: EdgeInsets.all(padding!), 37 | child: Column( 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: [ 40 | Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: [ 43 | Row( 44 | children: [ 45 | if (this.leftIcon != null) this.leftIcon!, 46 | Text( 47 | title, 48 | style: this.titleStyle ?? TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.amber), 49 | ), 50 | ], 51 | ), 52 | if (this.rightIcon != null) this.rightIcon!, 53 | ], 54 | ), 55 | SizedBox(height: 16), 56 | this.child, 57 | ], 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_data_grid/big_grid_def.dart: -------------------------------------------------------------------------------- 1 | import 'package:syncfusion_flutter_datagrid/datagrid.dart'; 2 | 3 | typedef OnCellSubmited = Future Function(DataGridRow dataGridRow, GridColumn column, int id, int dataRowIndex, int dataCellIndex, dynamic oldValue, dynamic newValue); 4 | typedef OnFormSubmited = Future Function(bool isEdit, Map params); 5 | typedef OnSearchSubmit = bool Function(Map params); 6 | typedef OnSearchCompleted = bool Function(List>? params); 7 | typedef OnLoadCompleted = bool Function(Map data); 8 | typedef OnDataLoadCompleted = void Function(List>? data); 9 | typedef OnDropdownWillAppear = void Function(int toolbarAction, List> options); 10 | 11 | typedef OnRefresh = Future Function(); 12 | typedef OnDeleteCompleted = Future Function(List ids); 13 | // 上传文件,返回服务器url 14 | typedef OnUploadFile = Future Function(Map row, String path); 15 | 16 | const String BIGDATAGRID_DATATYPE_STRING = "string"; 17 | const String BIGDATAGRID_DATATYPE_INT = "int"; 18 | const String BIGDATAGRID_DATATYPE_DOUBLE = "double"; 19 | const String BIGDATAGRID_DATATYPE_BOOL = "bool"; 20 | const String BIGDATAGRID_DATATYPE_DATETIME = "datetime"; 21 | const String BIGDATAGRID_DATATYPE_DATE = "date"; 22 | const String BIGDATAGRID_DATATYPE_TIME = "time"; 23 | const String BIGDATAGRID_DATATYPE_LIST = "list"; 24 | 25 | const String BIGDATAGRID_CTRLTYPE_INPUT = "input"; 26 | const String BIGDATAGRID_CTRLTYPE_CHECKBOX = "checkbox"; // 未实现 27 | const String BIGDATAGRID_CTRLTYPE_DROPDOWN = "dropdown"; 28 | const String BIGDATAGRID_CTRLTYPE_IMAGE = "image"; 29 | const String BIGDATAGRID_CTRLTYPE_FILE = "file"; 30 | const String BIGDATAGRID_CTRLTYPE_VIDEO = "video"; // 未实现 31 | const String BIGDATAGRID_CTRLTYPE_BUTTON = "button"; 32 | const String BIGDATAGRID_CTRLTYPE_CTRL = "control"; 33 | -------------------------------------------------------------------------------- /lib/app/models/module.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | 3 | import 'base_model.dart'; 4 | 5 | class Module extends BaseModel { 6 | late int id; 7 | String? create_time; 8 | String? icon; 9 | String? update_time; 10 | dynamic delete_time; 11 | String? note; 12 | String path = ""; 13 | String? method = ""; 14 | late String name; 15 | int? parent_id = 0; 16 | late bool hidden; 17 | List? childs; 18 | 19 | Module({ 20 | required this.id, 21 | required this.name, 22 | this.parent_id, 23 | this.icon, 24 | required this.hidden, 25 | required this.path, 26 | this.method, 27 | this.create_time, 28 | this.update_time, 29 | this.delete_time, 30 | this.note, 31 | this.childs, 32 | }); 33 | 34 | @override 35 | factory Module.fromJson(Map json) { 36 | return Module( 37 | id: json["id"], 38 | name: json["name"], 39 | path: json["path"], 40 | icon: json["icon"], 41 | method: json["method"], 42 | hidden: json["hidden"], 43 | parent_id: json["parent_id"], 44 | create_time: json["create_time"], 45 | update_time: json["update_time"], 46 | note: json["note"], 47 | childs: json["childs"] == null ? null : (json["childs"] as List).map((e) => Module.fromJson(e)).toList(), 48 | ); 49 | } 50 | 51 | @override 52 | Map toJson() { 53 | final Map data = {}; 54 | data["id"] = id; 55 | data["icon"] = icon; 56 | data["method"] = method; 57 | data["create_time"] = create_time; 58 | data["update_time"] = update_time; 59 | data["delete_time"] = delete_time; 60 | data["note"] = note; 61 | data["path"] = path; 62 | data["name"] = name; 63 | data["parent_id"] = parent_id; 64 | data["hidden"] = hidden; 65 | if (childs != null) { 66 | data["childs"] = childs?.map((e) => e.toJson()).toList(); 67 | } 68 | return data; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/icons/xd_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/menu_setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_data_grid/big_data_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:go_author_client/app/models/query_param.dart'; 2 | 3 | import 'big_grid_def.dart'; 4 | 5 | // 自定义列配置 6 | class BigDataField { 7 | String label; 8 | String name; 9 | String? helperText; // 输入框描述 10 | String? errorText; // 错误提示 11 | String? Function(String?)? validator; 12 | 13 | String dataType; 14 | String ctrlType; 15 | double minimumWidth; 16 | 17 | // 下拉列表属性 18 | List> options; 19 | bool multipleSelect; 20 | bool selectable; 21 | List> selectedOptions; 22 | // 从服务器列表的url 23 | String? optionsUrl; 24 | String? optionsColName; 25 | String? optionsColValue; 26 | // 查询逻辑操作符 27 | String? operator; 28 | SubQueryParam? subQueryParam; 29 | 30 | OnSearchSubmit? onSearchSubmit; 31 | OnSearchCompleted? onSearchCompeleted; 32 | OnDataLoadCompleted? onDataLoadCompleted; 33 | OnDropdownWillAppear? onDropdownWillAppear; 34 | 35 | BigDataField({ 36 | required this.label, 37 | required this.name, 38 | this.minimumWidth = double.nan, 39 | this.ctrlType = BIGDATAGRID_CTRLTYPE_INPUT, 40 | this.dataType = BIGDATAGRID_DATATYPE_STRING, 41 | this.helperText, 42 | this.errorText, 43 | this.validator, 44 | this.options = const [], 45 | this.multipleSelect = false, 46 | this.selectable = false, 47 | this.selectedOptions = const [], 48 | this.optionsUrl, 49 | this.optionsColName, 50 | this.optionsColValue, 51 | this.operator = "eq", 52 | this.subQueryParam, 53 | this.onSearchSubmit, 54 | this.onSearchCompeleted, 55 | this.onDataLoadCompleted, 56 | this.onDropdownWillAppear, 57 | }) { 58 | // 如果外部没有指定选项列表,我们这里以一个默认值 59 | if (this.options.isEmpty || this.ctrlType == BIGDATAGRID_CTRLTYPE_DROPDOWN) { 60 | if (this.dataType == BIGDATAGRID_DATATYPE_BOOL) { 61 | this.options = [ 62 | {"name": "是", "value": true}, 63 | {"name": "否", "value": false}, 64 | ]; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/app/modules/login/login_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:go_author_client/app/api/user_api.dart'; 4 | import 'package:go_author_client/app/routes/app_pages.dart'; 5 | import 'package:go_author_client/app/utils/local_storage.dart'; 6 | import 'package:go_author_client/window_size_service.dart'; 7 | 8 | class LoginController extends GetxController { 9 | final RxBool rememberMe = RxBool(true); 10 | final RxBool agreement = RxBool(true); 11 | final RxBool isObscure = RxBool(true); 12 | RxString username = RxString(""); 13 | RxString password = RxString(""); 14 | 15 | @override 16 | void onInit() { 17 | super.onInit(); 18 | WindowSizeService.instance.setWindowSize(); 19 | } 20 | 21 | @override 22 | void onReady() { 23 | super.onReady(); 24 | } 25 | 26 | @override 27 | void onClose() { 28 | super.onClose(); 29 | } 30 | 31 | Future onLogin() async { 32 | try { 33 | // 只要勾选了记住我,不管有没有填登录信息都要保存,以便包含清空的场景 34 | if (rememberMe.value) { 35 | LocalStorage.set("username", username.value); 36 | LocalStorage.set("password", password.value); 37 | LocalStorage.set("remember-me", rememberMe.value); 38 | } 39 | Get.log("账号: ${username.value}, 密码: ${password.value}"); 40 | if (username.isEmpty || password.isEmpty) { 41 | BotToast.showText(text: "请输入用户名和密码"); 42 | return; 43 | } 44 | Map? resp = await UserApi().login(username.value, password.value); 45 | if (resp == null) { 46 | BotToast.showText(text: "登录失败"); 47 | return; 48 | } 49 | if (resp["code"] != 200 && resp["message"] != null) { 50 | BotToast.showText(text: resp["message"]); 51 | return; 52 | } 53 | LocalStorage.set("x-token", resp["data"]["token"]); 54 | BotToast.showText(text: "登录成功"); 55 | Get.offAllNamed(Routes.HOME); 56 | } catch (e) { 57 | BotToast.showText(text: "登录失败, 请求失败"); 58 | return; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/app/modules/components/dialog/big_alert.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BigAlert { 4 | static Future show({ 5 | required BuildContext context, 6 | String? title, 7 | String? content, 8 | String? buttonTitle, 9 | }) async { 10 | // 弹出对话框 11 | await showDialog( 12 | context: context, 13 | builder: (BuildContext context) { 14 | return AlertDialog( 15 | title: Text(title ?? ""), 16 | content: Text(content ?? ""), 17 | actions: [ 18 | TextButton( 19 | onPressed: () { 20 | Navigator.of(context).pop(true); 21 | }, 22 | child: Text(buttonTitle ?? "确定"), 23 | ), 24 | ], 25 | ); 26 | }, 27 | ); 28 | } 29 | 30 | static Future showConfirm({ 31 | required BuildContext context, 32 | String? title, 33 | String? content, 34 | String? yesButtonTitle, 35 | String? noButtonTitle, 36 | Function()? onYesCallback, 37 | Function()? onNoCallback, 38 | }) async { 39 | // 弹出对话框 40 | bool result = await showDialog( 41 | context: context, 42 | builder: (BuildContext context) { 43 | return AlertDialog( 44 | title: Text(title ?? ""), 45 | content: Text(content ?? ""), 46 | actions: [ 47 | TextButton( 48 | onPressed: () { 49 | if (onNoCallback != null) { 50 | onNoCallback(); 51 | } 52 | Navigator.of(context).pop(false); 53 | }, 54 | child: Text(noButtonTitle ?? "取消"), 55 | ), 56 | TextButton( 57 | onPressed: () { 58 | if (onYesCallback != null) { 59 | onYesCallback(); 60 | } 61 | Navigator.of(context).pop(true); 62 | }, 63 | child: Text(yesButtonTitle ?? "确定"), 64 | ), 65 | ], 66 | ); 67 | }, 68 | ); 69 | return result; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/app/models/query_param.dart: -------------------------------------------------------------------------------- 1 | // 查询参数 2 | import 'base_model.dart'; 3 | 4 | class SubQueryParam extends BaseModel { 5 | String table; 6 | String where; 7 | String sub_column; 8 | String sub_where; 9 | List? sub_args; 10 | List? sub_querys; 11 | 12 | SubQueryParam({ 13 | required this.table, 14 | required this.where, 15 | required this.sub_column, 16 | required this.sub_where, 17 | this.sub_args, 18 | this.sub_querys, 19 | }); 20 | 21 | // 反序列化 22 | factory SubQueryParam.fromJson(Map json) { 23 | return SubQueryParam( 24 | table: json['table'] as String, 25 | where: json['where'] as String, 26 | sub_column: json['sub_column'] as String, 27 | sub_where: json['sub_where'] as String, 28 | sub_args: json['sub_args'] as List?, 29 | sub_querys: (json['sub_querys'] as List?)?.map((e) => SubQueryParam.fromJson(e)).toList(), 30 | ); 31 | } 32 | 33 | // 序列化 34 | @override 35 | Map toJson() { 36 | return { 37 | 'table': table, 38 | 'where': where, 39 | 'sub_column': sub_column, 40 | 'sub_where': sub_where, 41 | 'sub_args': sub_args, 42 | 'sub_querys': sub_querys?.map((e) => e.toJson()).toList(), 43 | }; 44 | } 45 | } 46 | 47 | class QueryParam extends BaseModel { 48 | String? where; 49 | List? args; 50 | List? sub_querys; 51 | 52 | QueryParam({ 53 | this.where, 54 | this.args, 55 | this.sub_querys, 56 | }); 57 | 58 | // 反序列化 59 | factory QueryParam.fromJson(Map json) { 60 | return QueryParam( 61 | where: json['where'] as String?, 62 | args: json['args'] as List?, 63 | sub_querys: (json['sub_querys'] as List?)?.map((e) => SubQueryParam.fromJson(e)).toList(), 64 | ); 65 | } 66 | 67 | // 序列化 68 | @override 69 | Map toJson() { 70 | return { 71 | 'where': where, 72 | 'args': args, 73 | 'sub_querys': sub_querys?.map((e) => e.toJson()).toList(), 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /assets/icons/pdf_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/app/modules/home/dashboard/widget/header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class Header extends StatelessWidget { 6 | RxString nickname; 7 | Header({Key? key, required this.nickname}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Obx(() { 12 | return Row( 13 | // 子元素两端对齐 14 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 15 | children: [ 16 | Column( 17 | mainAxisAlignment: MainAxisAlignment.start, 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | Text( 21 | "您好! [${this.nickname.value}] 👋", 22 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), 23 | ), 24 | SizedBox( 25 | height: 8, 26 | ), 27 | Text( 28 | "欢迎阅览仪表盘", 29 | style: Theme.of(context).textTheme.titleSmall, 30 | ), 31 | ], 32 | ), 33 | SizedBox( 34 | width: 320, 35 | child: SearchField(), 36 | ), 37 | ], 38 | ); 39 | }); 40 | } 41 | } 42 | 43 | class SearchField extends StatelessWidget { 44 | const SearchField({ 45 | Key? key, 46 | }) : super(key: key); 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return TextField( 51 | decoration: InputDecoration( 52 | hintText: "搜索", 53 | // 背景颜色 54 | // fillColor: Colors.amber, 55 | filled: true, 56 | border: OutlineInputBorder( 57 | borderSide: BorderSide.none, 58 | borderRadius: const BorderRadius.all(Radius.circular(10)), 59 | ), 60 | suffixIcon: InkWell( 61 | onTap: () {}, 62 | child: Container( 63 | padding: EdgeInsets.all(16), 64 | margin: EdgeInsets.symmetric(horizontal: 8), 65 | decoration: BoxDecoration( 66 | color: Colors.blue, 67 | borderRadius: const BorderRadius.all(Radius.circular(10)), 68 | ), 69 | child: SvgPicture.asset("assets/icons/search.svg"), 70 | ), 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/app/modules/home/group_manage/group_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/models/query_param.dart'; 5 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 6 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 7 | 8 | import 'group_manage_controller.dart'; 9 | 10 | class GroupManageView extends GetView { 11 | final scaffoldKey = GlobalKey(); 12 | 13 | GroupManageView({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return BigDataControl( 18 | key: scaffoldKey, 19 | title: "分组管理", 20 | searchUrl: "/admin/system/group/listPage", 21 | insertUrl: "/admin/system/group/insert", 22 | updateUrl: "/admin/system/group/update", 23 | deleteUrl: "/admin/system/group/delete", 24 | cleanUrl: "/admin/system/group/clean", 25 | queryParam: QueryParam(), 26 | columns: [ 27 | BigDataColumn( 28 | dataType: 'int', 29 | name: "id", 30 | label: "ID", 31 | allowEditing: false, 32 | allowSearch: true, 33 | ), 34 | BigDataColumn( 35 | dataType: 'string', 36 | name: "name", 37 | allowEditing: true, 38 | allowResizeWidth: true, 39 | minimumWidth: 240, 40 | label: "分组名", 41 | required: true, 42 | ), 43 | BigDataColumn( 44 | dataType: 'string', 45 | name: "note", 46 | minimumWidth: 120, 47 | label: "备注", 48 | ), 49 | BigDataColumn( 50 | dataType: 'datetime', 51 | name: "create_time", 52 | minimumWidth: 240, 53 | label: "创建时间", 54 | allowSearch: true, 55 | ), 56 | BigDataColumn( 57 | dataType: 'datetime', 58 | name: "update_time", 59 | minimumWidth: 240, 60 | label: "更新时间", 61 | ), 62 | BigDataColumn( 63 | dataType: 'datetime', 64 | name: "delete_time", 65 | minimumWidth: 240, 66 | label: "删除时间", 67 | allowEditing: false, 68 | visible: false, 69 | ), 70 | ], 71 | searchFields: [], 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/app/modules/loading/loading_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/routes/app_pages.dart'; 5 | import 'package:go_author_client/app/utils/local_storage.dart'; 6 | 7 | import 'loading_controller.dart'; 8 | 9 | class LoadingView extends GetView { 10 | const LoadingView({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | // 初始化异步数据 15 | initAsyncData().then((value) { 16 | // 初始化完成过后则关闭进度条,跳转到登录页面 17 | Get.offAllNamed(Routes.LOGIN); 18 | controller.isLoading.value = false; 19 | }); 20 | return Obx(() { 21 | return Scaffold( 22 | body: Stack( 23 | children: [ 24 | // 背景图片 25 | // Positioned.fill( 26 | // child: Image.asset( 27 | // "assets/images/loading_bg.jpg", 28 | // fit: BoxFit.cover, // 适应方式,可根据需要调整 29 | // ), 30 | // ), 31 | Column( 32 | children: [ 33 | SizedBox(height: 50), 34 | Text( 35 | "请稍候", 36 | style: TextStyle(fontSize: 36), 37 | ), 38 | Expanded( 39 | child: Column( 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | controller.isLoading.value 44 | ? Center( 45 | child: CircularProgressIndicator( 46 | semanticsLabel: "正在初始化...", 47 | ), 48 | ) 49 | : Center( 50 | child: Text('初始化已完成...'), 51 | ), 52 | SizedBox( 53 | height: 24, 54 | ), 55 | Text("加载中..."), 56 | ], 57 | ), 58 | ), 59 | ], 60 | ), 61 | ], 62 | ), 63 | ); 64 | }); 65 | } 66 | 67 | Future initAsyncData() async { 68 | // 执行异步初始化操作 69 | // await Future.delayed(Duration(seconds: 2)); // 模拟异步加载 70 | await LocalStorage.initStore(); 71 | if (LocalStorage.get("remember-me") == null) { 72 | LocalStorage.set("remember-me", true); 73 | } 74 | if (LocalStorage.get("agreement") == null) { 75 | LocalStorage.set("agreement", true); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/window_size_service.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_element 2 | 3 | import 'dart:io'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:window_size/window_size.dart' as window_size; 6 | import 'package:window_size/window_size.dart'; 7 | 8 | class WindowSizeService { 9 | // static const double width = 1170 / 3.0; 10 | // static const double height = 2532 / 3.0; 11 | static const double width = 1800 / 3.0; 12 | static const double height = 1800 / 3.0; 13 | 14 | // 单例模式的私有构造函数,防止外部直接实例化 15 | WindowSizeService._(); 16 | // 单例实例 17 | static WindowSizeService? _instance; 18 | // 获取单例实例的方法 19 | static WindowSizeService get instance { 20 | // 如果实例不存在,创建一个新的实例 21 | _instance ??= WindowSizeService._(); 22 | return _instance!; 23 | } 24 | 25 | Future getPlatformWindow() async { 26 | return await window_size.getWindowInfo(); 27 | } 28 | 29 | // 设置窗口最大化 30 | void setWindowMaximize() async { 31 | var platformWindow = await getPlatformWindow(); 32 | final Rect frame = Rect.fromCenter( 33 | center: Offset( 34 | platformWindow.frame.center.dx, 35 | platformWindow.frame.center.dy, 36 | ), 37 | width: platformWindow.screen!.visibleFrame.width, 38 | height: platformWindow.screen!.visibleFrame.height, 39 | ); 40 | window_size.setWindowFrame(frame); 41 | } 42 | 43 | // 控制大小的核心代码 44 | void setWindowSize() async { 45 | var platformWindow = await getPlatformWindow(); 46 | final Rect frame = Rect.fromCenter( 47 | center: Offset( 48 | platformWindow.frame.center.dx, 49 | platformWindow.frame.center.dy, 50 | ), 51 | width: width, 52 | height: height, 53 | ); 54 | window_size.setWindowFrame(frame); 55 | if (Platform.isMacOS || Platform.isWindows) { 56 | // 如果最大尺寸和最大尺寸相同,则是禁止调整窗口大小 57 | // window_size.setWindowMinSize(Size(width, height)); 58 | // window_size.setWindowMaxSize(Size(width, height)); 59 | } 60 | } 61 | 62 | Future initialize() async { 63 | PlatformWindow platformWindow = await getPlatformWindow(); 64 | if (platformWindow.screen != null) { 65 | // 判断初始时不等于指定的尺寸,则修改尺寸 66 | // if (platformWindow.screen?.visibleFrame.width != 800 || platformWindow.screen?.visibleFrame.height != 500) { 67 | // setWindowSize(platformWindow); 68 | // } 69 | // 强制设置窗口大小 70 | // setWindowSize(platformWindow); 71 | // 设置窗口最大化 72 | // setWindowMaximize(platformWindow); 73 | } 74 | 75 | // 修改标题, 这个设置可以禁止修改窗口大小 76 | // setWindowTitle(Consts.title); 77 | } 78 | 79 | void setWindowTitle(String title) { 80 | window_size.setWindowTitle(title); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/app/modules/home/channel_manage/channel_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/models/query_param.dart'; 5 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 6 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 7 | 8 | import 'channel_manage_controller.dart'; 9 | 10 | class ChannelManageView extends GetView { 11 | final scaffoldKey = GlobalKey(); 12 | 13 | ChannelManageView({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return BigDataControl( 18 | key: scaffoldKey, 19 | title: "渠道管理", 20 | searchUrl: "/admin/business/channel/listPage", 21 | insertUrl: "/admin/business/channel/insert", 22 | updateUrl: "/admin/business/channel/update", 23 | deleteUrl: "/admin/business/channel/delete", 24 | cleanUrl: "/admin/business/channel/clean", 25 | queryParam: QueryParam(), 26 | columns: [ 27 | BigDataColumn( 28 | dataType: 'int', 29 | name: "id", 30 | label: "ID", 31 | allowEditing: true, 32 | allowSearch: true, 33 | ), 34 | BigDataColumn( 35 | dataType: 'string', 36 | name: "name", 37 | allowEditing: false, 38 | allowResizeWidth: true, 39 | minimumWidth: 240, 40 | label: "渠道名", 41 | required: true, 42 | ), 43 | BigDataColumn( 44 | dataType: 'int', 45 | name: "parent_id", 46 | allowEditing: false, 47 | allowResizeWidth: true, 48 | minimumWidth: 120, 49 | label: "父渠道", 50 | required: true, 51 | ), 52 | BigDataColumn( 53 | dataType: 'string', 54 | name: "note", 55 | minimumWidth: 120, 56 | label: "备注", 57 | ), 58 | BigDataColumn( 59 | dataType: 'datetime', 60 | name: "create_time", 61 | minimumWidth: 240, 62 | label: "创建时间", 63 | allowSearch: true, 64 | ), 65 | BigDataColumn( 66 | dataType: 'datetime', 67 | name: "update_time", 68 | minimumWidth: 240, 69 | label: "更新时间", 70 | ), 71 | BigDataColumn( 72 | dataType: 'datetime', 73 | name: "delete_time", 74 | minimumWidth: 240, 75 | label: "删除时间", 76 | allowEditing: false, 77 | visible: false, 78 | ), 79 | ], 80 | searchFields: [], 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/app/modules/home/sysconfig_manage/sys_config_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:go_author_client/app/models/query_param.dart'; 4 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 5 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 6 | 7 | import 'sys_config_manage_controller.dart'; 8 | 9 | class SysConfigManageView extends GetView { 10 | final scaffoldKey = GlobalKey(); 11 | 12 | SysConfigManageView({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return BigDataControl( 17 | key: scaffoldKey, 18 | title: "系统配置", 19 | searchUrl: "/admin/system/sysConfig/listPage", 20 | insertUrl: "/admin/system/sysConfig/insert", 21 | updateUrl: "/admin/system/sysConfig/update", 22 | deleteUrl: "/admin/system/sysConfig/delete", 23 | cleanUrl: "/admin/system/sysConfig/clean", 24 | queryParam: QueryParam(), 25 | columns: [ 26 | BigDataColumn( 27 | dataType: 'int', 28 | name: "id", 29 | label: "ID", 30 | allowEditing: false, 31 | allowSearch: true, 32 | ), 33 | BigDataColumn( 34 | dataType: 'string', 35 | name: "name", 36 | allowEditing: false, 37 | allowResizeWidth: true, 38 | minimumWidth: 240, 39 | label: "名称", 40 | required: true, 41 | ), 42 | BigDataColumn( 43 | dataType: 'string', 44 | name: "version_number", 45 | allowEditing: false, 46 | allowResizeWidth: true, 47 | minimumWidth: 240, 48 | label: "版本号", 49 | required: true, 50 | ), 51 | BigDataColumn( 52 | dataType: 'string', 53 | name: "note", 54 | minimumWidth: 120, 55 | label: "备注", 56 | ), 57 | BigDataColumn( 58 | dataType: 'datetime', 59 | name: "create_time", 60 | minimumWidth: 240, 61 | label: "创建时间", 62 | allowSearch: true, 63 | ), 64 | BigDataColumn( 65 | dataType: 'datetime', 66 | name: "update_time", 67 | minimumWidth: 240, 68 | label: "更新时间", 69 | ), 70 | BigDataColumn( 71 | dataType: 'datetime', 72 | name: "delete_time", 73 | minimumWidth: 240, 74 | label: "删除时间", 75 | allowEditing: false, 76 | visible: false, 77 | ), 78 | ], 79 | searchFields: [], 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bot_toast/bot_toast.dart'; 4 | import 'package:common_utils/common_utils.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_localizations/flutter_localizations.dart'; 7 | import 'package:get/get.dart'; 8 | import 'package:go_author_client/app/consts/consts.dart'; 9 | import 'package:go_author_client/app/routes/app_pages.dart'; 10 | import 'package:go_author_client/window_size_service.dart'; 11 | import 'package:syncfusion_localizations/syncfusion_localizations.dart'; 12 | import 'package:intl/date_symbol_data_local.dart'; 13 | 14 | void main() { 15 | WidgetsFlutterBinding.ensureInitialized(); 16 | 17 | // 自定义窗口大小 18 | if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { 19 | WindowSizeService.instance.initialize(); 20 | } 21 | initializeDateFormatting(); 22 | 23 | GetMaterialApp app = GetMaterialApp( 24 | title: Consts.title, 25 | theme: ThemeData.light().copyWith( 26 | textTheme: TextTheme( 27 | bodySmall: TextStyle( 28 | fontFamily: 'Poppins', 29 | ), 30 | bodyMedium: TextStyle( 31 | fontFamily: 'Poppins', 32 | ), 33 | bodyLarge: TextStyle( 34 | fontFamily: 'Poppins', 35 | ), 36 | // 设置其他文本样式 37 | ), 38 | ), 39 | darkTheme: ThemeData.dark().copyWith( 40 | textTheme: TextTheme( 41 | bodySmall: TextStyle( 42 | fontFamily: 'Poppins', 43 | ), 44 | bodyMedium: TextStyle( 45 | fontFamily: 'Poppins', 46 | ), 47 | bodyLarge: TextStyle( 48 | fontFamily: 'Poppins', 49 | ), 50 | // 设置其他文本样式 51 | ), 52 | ), 53 | // theme: ThemeData.dark().copyWith( 54 | // scaffoldBackgroundColor: bgColor, 55 | // textTheme: GoogleFonts.poppinsTextTheme().apply(bodyColor: Colors.white), 56 | // canvasColor: secondaryColor, 57 | // ), 58 | debugShowCheckedModeBanner: false, 59 | localizationsDelegates: [ 60 | GlobalMaterialLocalizations.delegate, 61 | GlobalWidgetsLocalizations.delegate, 62 | GlobalCupertinoLocalizations.delegate, 63 | SfGlobalLocalizations.delegate, 64 | ], 65 | supportedLocales: [ 66 | const Locale('en', 'US'), // 美式英语 67 | const Locale('zh', 'CN'), // 中文简体 68 | ], 69 | locale: const Locale('zh', 'CN'), // 中文简体 70 | // 首页 71 | initialRoute: AppPages.INITIAL, 72 | getPages: AppPages.routes, 73 | // 实现pc端的鼠标拖动页面 74 | scrollBehavior: MaterialScrollBehavior().copyWith( 75 | scrollbars: true, 76 | ), 77 | builder: BotToastInit(), // 初始化BotToast 78 | navigatorObservers: [BotToastNavigatorObserver()], // 注册BotToast观察者 79 | ); 80 | //初始化设置 LogUtil 81 | LogUtil.init(tag: "[Log] ", isDebug: true); 82 | runApp(app); 83 | } 84 | -------------------------------------------------------------------------------- /lib/app/utils/local_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import 'dart:convert'; 3 | 4 | /// 封装SharedPreferences为单例模式 5 | class LocalStorage { 6 | /// SharedPreferences对象 7 | static late SharedPreferences _storage; 8 | 9 | //到这里还没有完 有时候会遇到使用时提示 SharedPreferences 未初始化,所以还需要提供一个static 的方法 10 | static Future initStore() async { 11 | //静态方法不能访问非静态变量所以需要创建变量再通过方法赋值回去 12 | _storage = await SharedPreferences.getInstance(); 13 | } 14 | 15 | /// 设置存储 16 | static set(String key, dynamic value) { 17 | String type; 18 | // 监测value的类型 如果是Map和List,则转换成JSON,以字符串进行存储 19 | if (value is Map || value is List) { 20 | type = 'String'; 21 | value = JsonEncoder().convert(value); 22 | } 23 | // 否则 获取value的类型的字符串形式 24 | else { 25 | type = value.runtimeType.toString(); 26 | } 27 | // 根据value不同的类型 用不同的方法进行存储 28 | switch (type) { 29 | case 'String': 30 | _storage.setString(key, value); 31 | break; 32 | case 'int': 33 | _storage.setInt(key, value); 34 | break; 35 | case 'double': 36 | _storage.setDouble(key, value); 37 | break; 38 | case 'bool': 39 | _storage.setBool(key, value); 40 | break; 41 | case 'List': 42 | _storage.setStringList(key, value); 43 | break; 44 | } 45 | } 46 | 47 | static saveMap(String key, Map map) { 48 | _storage.setString(key, jsonEncode(map)); 49 | } 50 | 51 | static remove(String key) { 52 | _storage.remove(key); 53 | } 54 | 55 | static Map getMap(String key) { 56 | var jsonstr = _storage.getString(key); 57 | if (jsonstr == null || jsonstr == "") { 58 | return {}; 59 | } 60 | return jsonDecode(jsonstr); 61 | } 62 | 63 | /// 获取存储 注意:返回的是一个Future对象 要么用await接收 要么在.then中接收 64 | static dynamic get(String key) { 65 | // 获取key对应的value 66 | dynamic value = _storage.get(key); 67 | // 判断value是不是一个json的字符串 是 则解码 68 | if (_isJson(value)) { 69 | return JsonDecoder().convert(value); 70 | } else { 71 | // 不是 则直接返回 72 | return value; 73 | } 74 | } 75 | 76 | /// 是否包含某个key 77 | static bool hasKey(String key) { 78 | return _storage.containsKey(key); 79 | } 80 | 81 | /// 删除key指向的存储 如果key存在则删除并返回true,否则返回false 82 | bool removeStorage(String key) { 83 | if (hasKey(key)) { 84 | _storage.remove(key); 85 | return true; 86 | } else { 87 | return false; 88 | } 89 | // return _storage.remove(key); 90 | } 91 | 92 | /// 清空存储 并总是返回true 93 | static bool clear() { 94 | _storage.clear(); 95 | return true; 96 | } 97 | 98 | /// 获取所有的key 类型为Set 99 | static Set getKeys() { 100 | return _storage.getKeys(); 101 | } 102 | 103 | // 判断是否是JSON字符串 104 | static _isJson(dynamic value) { 105 | try { 106 | // 如果value是一个json的字符串 则不会报错 返回true 107 | JsonDecoder().convert(value); 108 | return true; 109 | } catch (e) { 110 | // 如果value不是json的字符串 则报错 进入catch 返回false 111 | return false; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/app/modules/home/module_manage/module_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:go_author_client/app/models/query_param.dart'; 4 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 5 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 6 | 7 | import 'module_manage_controller.dart'; 8 | 9 | class ModuleManageView extends GetView { 10 | final scaffoldKey = GlobalKey(); 11 | 12 | ModuleManageView({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return BigDataControl( 17 | key: scaffoldKey, 18 | title: "模块管理", 19 | searchUrl: "/admin/system/module/listPage", 20 | insertUrl: "/admin/system/module/insert", 21 | updateUrl: "/admin/system/module/update", 22 | deleteUrl: "/admin/system/module/delete", 23 | cleanUrl: "/admin/system/module/clean", 24 | queryParam: QueryParam(), 25 | columns: [ 26 | BigDataColumn( 27 | dataType: 'int', 28 | name: "id", 29 | label: "ID", 30 | allowEditing: false, 31 | ), 32 | BigDataColumn( 33 | dataType: 'string', 34 | name: "name", 35 | allowResizeWidth: true, 36 | minimumWidth: 240, 37 | label: "模块名", 38 | required: true, 39 | ), 40 | BigDataColumn( 41 | dataType: 'string', 42 | name: "path", 43 | allowResizeWidth: true, 44 | minimumWidth: 240, 45 | label: "路径", 46 | required: true, 47 | ), 48 | BigDataColumn( 49 | dataType: 'image', 50 | name: "icon", 51 | minimumWidth: 120, 52 | label: "图标", 53 | ), 54 | BigDataColumn( 55 | dataType: 'string', 56 | name: "method", 57 | minimumWidth: 120, 58 | label: "方法", 59 | required: true, 60 | ), 61 | BigDataColumn( 62 | dataType: 'bool', 63 | ctrlType: "dropdown", 64 | name: "hidden", 65 | minimumWidth: 120, 66 | label: "隐藏", 67 | required: true, 68 | ), 69 | BigDataColumn( 70 | dataType: 'int', 71 | name: "parent_id", 72 | minimumWidth: 120, 73 | label: "父ID", 74 | required: true, 75 | ), 76 | BigDataColumn( 77 | dataType: 'string', 78 | name: "note", 79 | minimumWidth: 120, 80 | label: "备注", 81 | ), 82 | BigDataColumn( 83 | dataType: 'datetime', 84 | name: "create_time", 85 | minimumWidth: 240, 86 | label: "创建时间", 87 | ), 88 | BigDataColumn( 89 | dataType: 'datetime', 90 | name: "update_time", 91 | minimumWidth: 240, 92 | label: "更新时间", 93 | ), 94 | BigDataColumn( 95 | dataType: 'datetime', 96 | name: "delete_time", 97 | minimumWidth: 240, 98 | label: "删除时间", 99 | allowEditing: false, 100 | visible: false, 101 | ), 102 | ], 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/app/modules/home/home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/api/user_api.dart'; 5 | import 'package:go_author_client/app/consts/consts.dart'; 6 | import 'package:go_author_client/app/consts/global.dart'; 7 | import 'package:go_author_client/app/models/module.dart'; 8 | import 'package:go_author_client/app/routes/app_pages.dart'; 9 | import 'package:go_author_client/app/utils/local_storage.dart'; 10 | import 'package:go_author_client/window_size_service.dart'; 11 | 12 | // 注意这里我们需要多次添加tab, 所以不能使用GetSingleTickerProviderStateMixin 13 | // 这种Mixin不能多次实例化tabController 14 | // 使用Drawer作为左侧功能菜单,但是每次点击菜单都会自动关闭菜单 15 | class HomeController extends GetxController with GetTickerProviderStateMixin { 16 | int currentPage = 0; 17 | // 全局控件对象,可以取context 18 | final GlobalKey scaffoldKey = GlobalKey(); 19 | List modules = []; 20 | List tiles = []; 21 | Rx selectedModule = Module(id: 0, name: "", path: "", hidden: false).obs; 22 | RxDouble leftMenuWidth = RxDouble(200); 23 | PageController pageController = PageController(initialPage: 0); 24 | 25 | @override 26 | void onInit() async { 27 | super.onInit(); 28 | WindowSizeService.instance.setWindowMaximize(); 29 | } 30 | 31 | @override 32 | void onReady() { 33 | super.onReady(); 34 | } 35 | 36 | @override 37 | void onClose() { 38 | super.onClose(); 39 | } 40 | 41 | // 从服务器获取模块列表 42 | Future>? fetchData() async { 43 | try { 44 | Map? resp = await UserApi().getModuleTree(); 45 | if (resp == null || resp["code"] != 200) { 46 | BotToast.showText(text: "获取模块列表失败: ${resp?["message"]}"); 47 | return []; 48 | } 49 | // 使用map方法将List转换为List 50 | List> jsonList = resp["data"].cast>(); 51 | modules = jsonList.map((jsonMap) => Module.fromJson(jsonMap)).toList(); 52 | if (modules.isNotEmpty) { 53 | selectedModule.value = modules[0]; 54 | } 55 | Global.modules.clear(); 56 | Global.modules.addAll(modules); 57 | Global.permissions.clear(); 58 | this.modulesToList(modules); 59 | // 模拟延迟 60 | await Future.delayed(Duration(milliseconds: Consts.debug_delay), () {}); 61 | return modules; 62 | } catch (e) { 63 | BotToast.showText(text: "获取模块列表失败"); 64 | return []; 65 | } 66 | } 67 | 68 | // 从服务器获取用户信息 69 | Future>? fetchProfile() async { 70 | try { 71 | Map? resp = await UserApi().getProfile(); 72 | if (resp == null || resp["code"] != 200) { 73 | BotToast.showText(text: "获取模块列表失败: ${resp?["message"]}"); 74 | return {}; 75 | } 76 | LocalStorage.saveMap("profile", resp["data"]); 77 | return resp["data"]; 78 | } catch (e) { 79 | BotToast.showText(text: "获取用户信息失败"); 80 | return {}; 81 | } 82 | } 83 | 84 | // 根据module动态查找对应path的下标 85 | int getMenuIndexByPath(String path) { 86 | for (int i = 0; i < AppPages.pages.length; i++) { 87 | var page = AppPages.pages[i]; 88 | if (page.path == path) { 89 | return i; 90 | } 91 | } 92 | return -1; 93 | } 94 | 95 | void modulesToList(List modules) { 96 | for (var m in modules) { 97 | Global.permissions.add(m.path); 98 | if (m.childs != null) { 99 | modulesToList(m.childs!); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_data_grid/big_data_column.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_field.dart'; 3 | import 'package:syncfusion_flutter_datagrid/datagrid.dart'; 4 | 5 | import 'big_grid_def.dart'; 6 | 7 | typedef OnBuildCell = DataGridCell Function(BigDataColumn col, Map map); 8 | 9 | class BigButton extends StatelessWidget { 10 | String? text; 11 | Widget? icon; 12 | ButtonStyle? style; 13 | 14 | Function(BigDataColumn col, int rowIndex, int id, Map row) onPressed; 15 | BigButton({ 16 | super.key, 17 | this.icon, 18 | this.text, 19 | this.style, 20 | required this.onPressed, 21 | }); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container(); 26 | } 27 | } 28 | 29 | // 自定义列配置 30 | class BigDataColumn extends BigDataField { 31 | bool required = false; 32 | OnBuildCell? onBuildCell; // 33 | 34 | bool allowResizeWidth; 35 | bool allowSearch; 36 | ColumnWidthMode columnWidthMode; 37 | bool visible; 38 | bool allowSorting; 39 | ColumnHeaderIconPosition sortIconPosition; 40 | ColumnHeaderIconPosition filterIconPosition; 41 | EdgeInsets autoFitPadding; 42 | double maximumWidth; 43 | // 取消此属性,使用minimumWidth代替 44 | // double width; 45 | bool allowEditing; 46 | bool allowFiltering; 47 | FilterPopupMenuOptions? filterPopupMenuOptions; 48 | EdgeInsetsGeometry filterIconPadding; 49 | OnUploadFile? onUploadFile; 50 | List? allowedExtensions; 51 | List widgets; 52 | 53 | BigDataColumn({ 54 | String dataType = BIGDATAGRID_DATATYPE_STRING, 55 | String ctrlType = BIGDATAGRID_CTRLTYPE_INPUT, 56 | required String label, 57 | required String name, 58 | String? helperText, 59 | String? errorText, 60 | this.allowResizeWidth = false, 61 | this.allowSearch = false, 62 | this.required = false, 63 | String? Function(String?)? validator, 64 | this.onBuildCell, 65 | super.operator, 66 | // 系统属性 67 | this.columnWidthMode = ColumnWidthMode.none, 68 | this.visible = true, 69 | this.allowSorting = true, 70 | this.sortIconPosition = ColumnHeaderIconPosition.end, 71 | this.filterIconPosition = ColumnHeaderIconPosition.end, 72 | this.autoFitPadding = const EdgeInsets.all(16.0), 73 | double minimumWidth = double.nan, 74 | this.maximumWidth = double.nan, 75 | this.allowEditing = true, 76 | this.allowFiltering = true, 77 | this.filterPopupMenuOptions, 78 | this.filterIconPadding = const EdgeInsets.symmetric(horizontal: 8.0), 79 | List> options = const [], 80 | bool multipleSelect = false, 81 | bool selectable = false, 82 | List> selectedOptions = const [], 83 | String? optionsUrl, 84 | String? optionsColName, 85 | String? optionsColValue, 86 | OnSearchSubmit? onSearchSubmit, 87 | OnSearchCompleted? onSearchCompeleted, 88 | OnDataLoadCompleted? onDataLoadCompleted, 89 | OnDropdownWillAppear? onDropdownWillAppear, 90 | this.onUploadFile, 91 | this.allowedExtensions, 92 | this.widgets = const [], 93 | }) : super( 94 | label: label, 95 | name: name, 96 | dataType: dataType, 97 | ctrlType: ctrlType, 98 | validator: validator, 99 | helperText: helperText, 100 | errorText: errorText, 101 | minimumWidth: minimumWidth, 102 | options: options, 103 | multipleSelect: multipleSelect, 104 | selectable: selectable, 105 | selectedOptions: selectedOptions, 106 | optionsUrl: optionsUrl, 107 | optionsColName: optionsColName, 108 | optionsColValue: optionsColValue, 109 | onSearchSubmit: onSearchSubmit, 110 | onSearchCompeleted: onSearchCompeleted, 111 | onDataLoadCompleted: onDataLoadCompleted, 112 | onDropdownWillAppear: onDropdownWillAppear, 113 | ) { 114 | // 如果外部没有指定选项列表,我们这里以一个默认值 115 | if (this.options.isEmpty || this.ctrlType == BIGDATAGRID_CTRLTYPE_DROPDOWN) { 116 | if (this.dataType == BIGDATAGRID_DATATYPE_BOOL) { 117 | this.options = [ 118 | {"name": "是", "value": true}, 119 | {"name": "否", "value": false}, 120 | ]; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/app/api/base_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart' as dio; 4 | // ignore: depend_on_referenced_packages 5 | import 'package:path/path.dart' as path; 6 | import 'package:bot_toast/bot_toast.dart'; 7 | import 'package:dio/dio.dart'; 8 | import 'package:get/get.dart'; 9 | import 'package:go_author_client/app/consts/consts.dart'; 10 | import 'package:go_author_client/app/models/query_param.dart'; 11 | import 'package:go_author_client/app/routes/app_pages.dart'; 12 | import 'package:go_author_client/app/utils/http_util.dart'; 13 | import 'package:go_author_client/app/utils/local_storage.dart'; 14 | 15 | class BaseApi { 16 | String getToken() { 17 | if (LocalStorage.get("x-token") != null) { 18 | return LocalStorage.get("x-token").toString(); 19 | } 20 | return ""; 21 | } 22 | 23 | /// get请求, 添加公共请求头和统一跳转处理 24 | /// @param url 25 | /// @param params 26 | /// @param headers 27 | Future?> get(String url, Map? params, Map? headers) async { 28 | params ??= {}; 29 | headers ??= {}; 30 | if (headers["x-token"] == null) { 31 | // 获取x-token 32 | headers["x-token"] = getToken(); 33 | } 34 | var resp = await HttpUtil.get(Consts.server_host + url, params, headers); 35 | if (resp.statusCode == 401) { 36 | BotToast.showText(text: "授权已过期"); 37 | // 如果已经是登录页面,则不再跳转 38 | if (!url.contains("/login")) { 39 | Get.offAllNamed(Routes.LOGIN); 40 | } 41 | // 主动抛出异常 42 | throw Exception("授权已过期"); 43 | // return null; 44 | } 45 | if (resp.statusCode != 200) { 46 | return null; 47 | } 48 | return resp.data; 49 | } 50 | 51 | /// post请求, 添加公共请求头和统一跳转处理 52 | /// @param url 53 | /// @param params 54 | /// @param headers 55 | Future?> post( 56 | String url, 57 | Map? params, 58 | Map? headers, 59 | ) async { 60 | params ??= {}; 61 | headers ??= {}; 62 | if (headers["x-token"] == null) { 63 | // 获取x-token 64 | headers["x-token"] = getToken(); 65 | } 66 | 67 | var resp = await HttpUtil.post(Consts.server_host + url, params, headers); 68 | if (resp.statusCode == 401 || (resp.data != null && resp.data["code"] == 401)) { 69 | LocalStorage.remove("x-token"); 70 | BotToast.showText(text: "授权已过期"); 71 | // 如果已经是登录页面,则不再跳转 72 | if (!url.contains("/login")) { 73 | Get.offAllNamed(Routes.LOGIN); 74 | } 75 | // 主动抛出异常 76 | throw Exception("授权已过期"); 77 | // return null; 78 | } 79 | if (resp.statusCode != 200) { 80 | return null; 81 | } 82 | return resp.data; 83 | } 84 | 85 | Future?> upload( 86 | String url, 87 | String filepath, 88 | String fileParamName, 89 | // 注意参数中不可再有fileParamName值, 否则会被覆盖 90 | Map? params, 91 | Map? headers, 92 | ) async { 93 | params ??= {}; 94 | headers ??= {}; 95 | if (headers["x-token"] == null) { 96 | // 获取x-token 97 | headers["x-token"] = getToken(); 98 | } 99 | 100 | dio.Dio dioInst = dio.Dio(); 101 | var formData = dio.FormData.fromMap({ 102 | fileParamName: await dio.MultipartFile.fromFile( 103 | filepath, 104 | filename: path.basename(filepath), 105 | ), 106 | ...params, 107 | }); 108 | var resp = await dioInst.post( 109 | Consts.server_host + url, 110 | data: formData, 111 | options: Options(headers: headers), 112 | ); 113 | if (resp.statusCode != 200) { 114 | return null; 115 | } 116 | return resp.data; 117 | } 118 | 119 | /// 获取分页列表 120 | Future?> getPageList(String path, int pageNum, int pageSize, String sortColumn, bool ascending, QueryParam queryParam) async { 121 | // token在父类会自动上传 122 | return await get( 123 | path, 124 | { 125 | "pageNum": pageNum, 126 | "pageSize": pageSize, 127 | "sortColumn": sortColumn, 128 | "ascending": ascending, 129 | "queryParam": json.encode(queryParam.toJson()), 130 | }, 131 | null); 132 | } 133 | 134 | /// 获取所有列表 135 | Future?> getAllList(String path, QueryParam? queryParam) async { 136 | // token在父类会自动上传 137 | return await get( 138 | path, 139 | { 140 | "queryParam": queryParam ?? json.encode(queryParam!.toJson()), 141 | }, 142 | null); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/app/modules/home/permission_manage/permission_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:go_author_client/app/models/query_param.dart'; 4 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 5 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 6 | 7 | import 'permission_manage_controller.dart'; 8 | 9 | class PermissionManageView extends GetView { 10 | final scaffoldKey = GlobalKey(); 11 | 12 | PermissionManageView({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return BigDataControl( 17 | key: scaffoldKey, 18 | title: "权限管理", 19 | searchUrl: "/admin/system/permission/listPage", 20 | insertUrl: "/admin/system/permission/insert", 21 | updateUrl: "/admin/system/permission/update", 22 | deleteUrl: "/admin/system/permission/delete", 23 | cleanUrl: "/admin/system/permission/clean", 24 | queryParam: QueryParam(), 25 | columns: [ 26 | BigDataColumn( 27 | dataType: 'int', 28 | name: "id", 29 | label: "ID", 30 | allowEditing: false, 31 | allowSearch: true, 32 | ), 33 | BigDataColumn( 34 | dataType: 'int', 35 | ctrlType: 'dropdown', 36 | name: "module_id", 37 | allowResizeWidth: true, 38 | minimumWidth: 240, 39 | label: "模块ID", 40 | required: true, 41 | optionsUrl: "/admin/system/module/listAll", 42 | optionsColName: "name", 43 | optionsColValue: "id", 44 | allowSearch: true, 45 | selectable: true, 46 | multipleSelect: false, 47 | ), 48 | // BigDataColumn( 49 | // dataType: 'string', 50 | // name: "module.name", 51 | // allowEditing: false, 52 | // allowResizeWidth: true, 53 | // minimumWidth: 240, 54 | // label: "模块名", 55 | // required: true, 56 | // ), 57 | BigDataColumn( 58 | dataType: 'int', 59 | ctrlType: 'dropdown', 60 | name: "role_id", 61 | allowResizeWidth: true, 62 | minimumWidth: 240, 63 | label: "角色ID", 64 | required: true, 65 | optionsUrl: "/admin/system/role/listAll", 66 | optionsColName: "name", 67 | optionsColValue: "id", 68 | allowSearch: true, 69 | onDropdownWillAppear: (toolbarAction, options) { 70 | if (options.isNotEmpty && options.first["name"] == "超级管理员") { 71 | options.removeAt(0); 72 | } 73 | }, 74 | selectable: true, 75 | multipleSelect: false, 76 | ), 77 | // BigDataColumn( 78 | // dataType: 'string', 79 | // name: "role.name", 80 | // allowEditing: false, 81 | // allowResizeWidth: true, 82 | // minimumWidth: 240, 83 | // label: "角色名", 84 | // required: true, 85 | // ), 86 | BigDataColumn( 87 | dataType: 'bool', 88 | ctrlType: 'dropdown', 89 | name: "visible", 90 | minimumWidth: 120, 91 | label: "可见性", 92 | required: true, 93 | options: [ 94 | {"name": "可见", "value": true}, 95 | {"name": "不可见", "value": false}, 96 | ], 97 | allowSearch: true, 98 | ), 99 | BigDataColumn( 100 | dataType: 'string', 101 | name: "note", 102 | minimumWidth: 120, 103 | label: "备注", 104 | ), 105 | BigDataColumn( 106 | dataType: 'datetime', 107 | name: "create_time", 108 | minimumWidth: 240, 109 | label: "创建时间", 110 | allowSearch: true, 111 | ), 112 | BigDataColumn( 113 | dataType: 'datetime', 114 | name: "update_time", 115 | minimumWidth: 240, 116 | label: "更新时间", 117 | ), 118 | BigDataColumn( 119 | dataType: 'datetime', 120 | name: "delete_time", 121 | minimumWidth: 240, 122 | label: "删除时间", 123 | allowEditing: false, 124 | visible: false, 125 | ), 126 | ], 127 | searchFields: [ 128 | // 嵌套子查询 129 | // BigDataField( 130 | // label: "模块名", 131 | // name: "module_name", // 这里要取一个在当前页面唯一的列名 132 | // subQueryParam: SubQueryParam( 133 | // table: 'module', 134 | // where: 'module_id = (?)', 135 | // sub_column: 'module.id', 136 | // sub_where: 'module.name = ?', 137 | // ), 138 | // ), 139 | ], 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/app/modules/components/dialog/big_dialog.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: deprecated_member_use 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | // 我们必须将对话窗口创建为有状态的,在关闭时销毁, 7 | // 打开时重新加载数据, 这样以便于放在全局页面中打开时自动更新数据 8 | 9 | class BigDialog extends StatefulWidget { 10 | final String title; 11 | final double width; 12 | final double height; 13 | final Function(Widget widget)? onClose; 14 | final Function(Widget widget)? onShow; 15 | final Widget child; 16 | final bool buttonsVisible; 17 | final List otherButtons; 18 | final Function(BigDialog dialog)? onCancel; 19 | final Function(BigDialog dialog)? onConfirm; 20 | 21 | const BigDialog({ 22 | Key? key, 23 | required this.title, 24 | this.width = 600, 25 | this.height = 720, 26 | this.onClose, 27 | this.onShow, 28 | this.buttonsVisible = false, 29 | this.otherButtons = const [], 30 | required this.child, 31 | this.onCancel, 32 | this.onConfirm, 33 | }) : super(key: key); 34 | 35 | @override 36 | State createState() => _BigDialogState(); 37 | } 38 | 39 | class _BigDialogState extends State { 40 | @override 41 | void initState() { 42 | super.initState(); 43 | RawKeyboard.instance.addListener(_handleKey); 44 | } 45 | 46 | void _handleKey(RawKeyEvent event) { 47 | if (event.logicalKey == LogicalKeyboardKey.escape) { 48 | if (widget.onClose != null) { 49 | widget.onClose!(widget); 50 | } 51 | } 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | if (widget.onShow != null) { 57 | widget.onShow!(widget); 58 | } 59 | return Center( 60 | child: Stack( 61 | children: [ 62 | Container( 63 | width: widget.width, 64 | height: widget.height, 65 | padding: EdgeInsets.all(16.0), 66 | decoration: BoxDecoration( 67 | color: const Color.fromARGB(0xFF, 0x24, 0x24, 0x24), 68 | borderRadius: BorderRadius.circular(8.0), 69 | border: Border.all( 70 | color: Colors.grey, 71 | width: 1.6, 72 | ), 73 | boxShadow: [ 74 | BoxShadow( 75 | color: Colors.grey, 76 | offset: Offset(0.0, 3.0), 77 | blurRadius: 5.0, 78 | ), 79 | ], 80 | ), 81 | child: Column( 82 | mainAxisSize: MainAxisSize.min, 83 | children: [ 84 | // 标题控件 85 | Text( 86 | widget.title, 87 | style: TextStyle( 88 | fontSize: 20.0, 89 | fontWeight: FontWeight.bold, 90 | ), 91 | ), 92 | SizedBox(height: 16.0), 93 | 94 | Expanded( 95 | child: widget.child, 96 | ), 97 | 98 | if (widget.buttonsVisible) SizedBox(height: 16.0), 99 | 100 | if (widget.buttonsVisible) 101 | Row( 102 | mainAxisAlignment: MainAxisAlignment.end, 103 | children: [ 104 | for (var btn in widget.otherButtons) 105 | Padding( 106 | padding: EdgeInsets.fromLTRB(0, 0, 8, 0), 107 | child: btn, 108 | ), 109 | SizedBox( 110 | child: ElevatedButton.icon( 111 | onPressed: () { 112 | if (widget.onCancel != null) { 113 | widget.onCancel!(widget); 114 | } 115 | }, 116 | icon: Icon(Icons.close), 117 | label: Text('取消'), 118 | ), 119 | ), 120 | SizedBox(width: 8.0), 121 | SizedBox( 122 | child: ElevatedButton.icon( 123 | onPressed: () { 124 | if (widget.onConfirm != null) { 125 | widget.onConfirm!(widget); 126 | } 127 | }, 128 | icon: Icon(Icons.done), 129 | label: Text('确定'), 130 | ), 131 | ), 132 | ], 133 | ), 134 | ], 135 | ), 136 | ), 137 | // 关闭按钮 138 | Positioned( 139 | top: 0, 140 | right: 0, 141 | child: IconButton( 142 | icon: Icon(Icons.close), 143 | iconSize: 32, 144 | onPressed: () { 145 | if (widget.onClose != null) { 146 | widget.onClose!(widget); 147 | } 148 | }, 149 | ), 150 | ), 151 | ], 152 | ), 153 | ); 154 | } 155 | 156 | @override 157 | void dispose() { 158 | super.dispose(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:go_author_client/app/modules/home/channel_manage/channel_manage_controller.dart'; 3 | import 'package:go_author_client/app/modules/home/group_manage/group_manage_controller.dart'; 4 | import 'package:go_author_client/app/modules/home/sysconfig_manage/sys_config_manage_controller.dart'; 5 | 6 | import '../modules/home/channel_manage/channel_manage_binding.dart'; 7 | import '../modules/home/channel_manage/channel_manage_view.dart'; 8 | import '../modules/home/group_manage/group_manage_binding.dart'; 9 | import '../modules/home/group_manage/group_manage_view.dart'; 10 | import '../modules/home/sysconfig_manage/sys_config_manage_binding.dart'; 11 | import '../modules/home/sysconfig_manage/sys_config_manage_view.dart'; 12 | import '../modules/home/base_page.dart'; 13 | import '../modules/home/dashboard/dashboard_controller.dart'; 14 | import '../modules/home/dashboard/dashboard_view.dart'; 15 | import '../modules/home/home_binding.dart'; 16 | import '../modules/home/home_view.dart'; 17 | import '../modules/home/module_manage/module_manage_controller.dart'; 18 | import '../modules/home/module_manage/module_manage_view.dart'; 19 | import '../modules/home/permission_manage/permission_manage_binding.dart'; 20 | import '../modules/home/permission_manage/permission_manage_controller.dart'; 21 | import '../modules/home/permission_manage/permission_manage_view.dart'; 22 | import '../modules/home/role_manage/role_manage_binding.dart'; 23 | import '../modules/home/role_manage/role_manage_controller.dart'; 24 | import '../modules/home/role_manage/role_manage_view.dart'; 25 | import '../modules/home/user_manage/user_manage_binding.dart'; 26 | import '../modules/home/user_manage/user_manage_controller.dart'; 27 | import '../modules/home/user_manage/user_manage_view.dart'; 28 | import '../modules/loading/loading_binding.dart'; 29 | import '../modules/loading/loading_view.dart'; 30 | import '../modules/login/login_binding.dart'; 31 | import '../modules/login/login_view.dart'; 32 | 33 | part 'app_routes.dart'; 34 | 35 | class AppPages { 36 | AppPages._(); 37 | // 首页 38 | static const String INITIAL = Routes.LOADING; 39 | 40 | static final routes = [ 41 | // 非管理页面放在最前面 42 | GetPage( 43 | name: _Paths.LOADING, 44 | page: () => LoadingView(), 45 | binding: LoadingBinding(), 46 | ), 47 | GetPage( 48 | name: _Paths.LOGIN, 49 | page: () => LoginView(), 50 | binding: LoginBinding(), 51 | ), 52 | GetPage( 53 | name: _Paths.HOME, 54 | page: () => HomeView(), 55 | binding: HomeBinding(), 56 | ), 57 | // 管理页面不需要添加到路由列表 58 | GetPage( 59 | name: _Paths.PERMISSION_MANAGE, 60 | page: () => PermissionManageView(), 61 | binding: PermissionManageBinding(), 62 | ), 63 | GetPage( 64 | name: _Paths.ROLE_MANAGE, 65 | page: () => RoleManageView(), 66 | binding: RoleManageBinding(), 67 | ), 68 | GetPage( 69 | name: _Paths.USER_MANAGE, 70 | page: () => UserManageView(), 71 | binding: UserManageBinding(), 72 | ), 73 | GetPage( 74 | name: _Paths.GROUP_MANAGE, 75 | page: () => GroupManageView(), 76 | binding: GroupManageBinding(), 77 | ), 78 | GetPage( 79 | name: _Paths.SYS_CONFIG_MANAGE, 80 | page: () => SysConfigManageView(), 81 | binding: SysConfigManageBinding(), 82 | ), 83 | GetPage( 84 | name: _Paths.CHANNEL_MANAGE, 85 | page: () => ChannelManageView(), 86 | binding: ChannelManageBinding(), 87 | ), 88 | ]; 89 | 90 | // 将需要显示到PageView的管理页面手动添加到下面的列表 91 | // path必须与后台的对应 92 | static List pages = [ 93 | BasePage( 94 | path: "/admin/dashboard", 95 | page: GetBuilder( 96 | init: DashboardController(), 97 | builder: (controller) => DashboardView(), 98 | ), 99 | ), 100 | BasePage( 101 | path: "/admin/system/module", 102 | page: GetBuilder( 103 | init: ModuleManageController(), 104 | builder: (controller) => ModuleManageView(), 105 | ), 106 | ), 107 | BasePage( 108 | path: "/admin/system/permission", 109 | page: GetBuilder( 110 | init: PermissionManageController(), 111 | builder: (controller) => PermissionManageView(), 112 | ), 113 | ), 114 | BasePage( 115 | path: "/admin/system/role", 116 | page: GetBuilder( 117 | init: RoleManageController(), 118 | builder: (controller) => RoleManageView(), 119 | ), 120 | ), 121 | BasePage( 122 | path: "/admin/system/user", 123 | page: GetBuilder( 124 | init: UserManageController(), 125 | builder: (controller) => UserManageView(), 126 | ), 127 | ), 128 | BasePage( 129 | path: "/admin/system/group", 130 | page: GetBuilder( 131 | init: GroupManageController(), 132 | builder: (controller) => GroupManageView(), 133 | ), 134 | ), 135 | BasePage( 136 | path: "/admin/system/sysConfig", 137 | page: GetBuilder( 138 | init: SysConfigManageController(), 139 | builder: (controller) => SysConfigManageView(), 140 | ), 141 | ), 142 | BasePage( 143 | path: "/admin/business/channel", 144 | page: GetBuilder( 145 | init: ChannelManageController(), 146 | builder: (controller) => ChannelManageView(), 147 | ), 148 | ), 149 | ]; 150 | } 151 | -------------------------------------------------------------------------------- /lib/app/modules/home/dashboard/widget/miniinfo_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fl_chart/fl_chart.dart'; 3 | import 'package:go_author_client/app/models/chart_line.dart'; 4 | 5 | class MiniInfoCard extends StatelessWidget { 6 | double crossAxisSpacing; 7 | double mainAxisSpacing; 8 | List data; 9 | final double childAspectRatio; 10 | 11 | MiniInfoCard({ 12 | Key? key, 13 | required this.data, 14 | this.childAspectRatio = 1, 15 | this.crossAxisSpacing = 24, 16 | this.mainAxisSpacing = 24, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Row( 22 | children: _getWidgets(), 23 | ); 24 | } 25 | 26 | List _getWidgets() { 27 | List list = []; 28 | for (int i = 0; i < data.length; i++) { 29 | var line = data[i]; 30 | list.add(Expanded( 31 | child: Stack( 32 | children: [ 33 | Container( 34 | decoration: BoxDecoration( 35 | color: Color.fromARGB(0xFF, 0x2A, 0x2A, 0x2A), 36 | borderRadius: const BorderRadius.all(Radius.circular(10)), 37 | ), 38 | padding: EdgeInsets.fromLTRB(16, 40, 32, 0), 39 | margin: EdgeInsets.fromLTRB(0, 0, i < data.length - 1 ? 8 : 0, 0), // 设置间隔 40 | child: LineChart( 41 | LineChartData( 42 | // // 控制图形弯曲度 43 | // minX: line.minX, 44 | // maxX: line.maxX, 45 | // minY: line.minY, 46 | // maxY: line.maxY, 47 | lineBarsData: [ 48 | LineChartBarData( 49 | spots: line.spots, 50 | aboveBarData: BarAreaData(show: false), 51 | isCurved: true, 52 | barWidth: 5, 53 | isStrokeCapRound: true, 54 | dotData: FlDotData(show: true), 55 | belowBarData: BarAreaData( 56 | show: true, 57 | gradient: LinearGradient( 58 | colors: line.colors, 59 | ), 60 | ), 61 | ), 62 | ], 63 | gridData: FlGridData( 64 | show: false, 65 | drawVerticalLine: false, 66 | horizontalInterval: 1, 67 | verticalInterval: 1, 68 | getDrawingHorizontalLine: (value) { 69 | return FlLine( 70 | color: Colors.amber, 71 | strokeWidth: 1, 72 | ); 73 | }, 74 | getDrawingVerticalLine: (value) { 75 | return FlLine( 76 | color: Colors.blue, 77 | strokeWidth: 1, 78 | ); 79 | }, 80 | ), 81 | lineTouchData: LineTouchData(enabled: false), 82 | titlesData: FlTitlesData( 83 | show: true, 84 | rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), 85 | topTitles: AxisTitles( 86 | sideTitles: SideTitles(showTitles: false), 87 | // 图表上方标题, 会档住图表 88 | // axisNameWidget: Row( 89 | // children: [ 90 | // Icon(Icons.search), 91 | // SizedBox(width: 8), // 加入间隔 92 | // Text('Search'), 93 | // ], 94 | // ), 95 | ), 96 | bottomTitles: AxisTitles( 97 | sideTitles: SideTitles( 98 | showTitles: true, 99 | reservedSize: 30, 100 | interval: 1, 101 | getTitlesWidget: (value, meta) { 102 | if (line.bottomTitles.isNotEmpty && line.bottomTitles.containsKey(value.toInt())) { 103 | return Text(line.bottomTitles[value.toInt()]!, style: TextStyle(color: Colors.grey[250])); 104 | } else { 105 | return Container(); 106 | } 107 | }, 108 | ), 109 | ), 110 | leftTitles: AxisTitles( 111 | sideTitles: SideTitles( 112 | showTitles: true, 113 | interval: 1, 114 | getTitlesWidget: (value, meta) { 115 | if (line.leftTitles.isNotEmpty && line.leftTitles.containsKey(value.toInt())) { 116 | return Text(line.leftTitles[value.toInt()]!, style: TextStyle(color: Colors.grey[250])); 117 | } else { 118 | return Container(); 119 | } 120 | }, 121 | reservedSize: 42, 122 | ), 123 | ), 124 | ), 125 | borderData: FlBorderData( 126 | show: true, 127 | border: Border.all(color: const Color(0xff37434d)), 128 | ), 129 | ), 130 | ), 131 | ), 132 | // 自定义标题 133 | if (line.title != null) 134 | Positioned( 135 | left: 14, 136 | top: 8, 137 | child: Row( 138 | children: [ 139 | if (line.icon != null) Icon(line.icon!), 140 | if (line.icon != null) SizedBox(width: 8), // 加入间隔 141 | Text( 142 | line.title!, 143 | style: TextStyle( 144 | fontWeight: FontWeight.bold, 145 | color: Colors.amber, 146 | ), 147 | ), 148 | ], 149 | ), 150 | ), 151 | ], 152 | ), 153 | )); 154 | } 155 | return list; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/app/modules/home/dashboard/dashboard_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:fl_chart/fl_chart.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/models/chart_line.dart'; 5 | import 'package:go_author_client/app/models/query_param.dart'; 6 | import 'package:go_author_client/app/modules/components/big_card/big_card.dart'; 7 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 8 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_grid.dart'; 9 | import 'package:go_author_client/app/modules/home/dashboard/widget/miniinfo_card.dart'; 10 | 11 | import 'dashboard_controller.dart'; 12 | import 'widget/header.dart'; 13 | 14 | class DashboardView extends GetView { 15 | final scaffoldKey = GlobalKey(); 16 | 17 | DashboardView({super.key}); 18 | 19 | var chartsData = [ 20 | ChartLine( 21 | title: "新增", 22 | colors: [Color(0xff23b6e6), Color(0xff02d39a)], 23 | leftTitles: {1: "10K", 2: "20K", 3: "30K", 4: "40K", 5: "50k", 6: "60K", 7: "70K", 8: "80K", 9: "90K"}, 24 | bottomTitles: {1: "10M", 2: "20M", 3: "30M", 4: "40M", 5: "50M", 6: "60M", 7: "70M", 8: "80M", 9: "90M"}, 25 | spots: [FlSpot(1, 2), FlSpot(2, 1.0), FlSpot(3, 1.8), FlSpot(4, 1.5), FlSpot(5, 1.0), FlSpot(6, 2.2), FlSpot(7, 1.8), FlSpot(8, 1.5)], 26 | ), 27 | ChartLine( 28 | title: "活跃", 29 | colors: [Color(0xfff12711), Color(0xfff5af19)], 30 | leftTitles: {1: "10K", 2: "20K", 3: "30K", 4: "40K", 5: "50k", 6: "60K", 7: "70K", 8: "80K", 9: "90K"}, 31 | bottomTitles: {1: "10M", 2: "20M", 3: "30M", 4: "40M", 5: "50M", 6: "60M", 7: "70M", 8: "80M", 9: "90M"}, 32 | spots: [FlSpot(1, 1.3), FlSpot(2, 1.0), FlSpot(3, 4), FlSpot(4, 1.5), FlSpot(5, 1.0), FlSpot(6, 3), FlSpot(7, 1.8), FlSpot(8, 1.5)], 33 | ), 34 | ChartLine( 35 | title: "Onboarding", 36 | colors: [Color(0xff2980B9), Color(0xff6DD5FA)], 37 | leftTitles: {1: "10K", 2: "20K", 3: "30K", 4: "40K", 5: "50k", 6: "60K", 7: "70K", 8: "80K", 9: "90K"}, 38 | bottomTitles: {1: "10M", 2: "20M", 3: "30M", 4: "40M", 5: "50M", 6: "60M", 7: "70M", 8: "80M", 9: "90M"}, 39 | spots: [FlSpot(1, 1.3), FlSpot(2, 5), FlSpot(3, 1.8), FlSpot(4, 6), FlSpot(5, 1.0), FlSpot(6, 2.2), FlSpot(7, 1.8), FlSpot(8, 1)], 40 | ), 41 | ChartLine( 42 | title: "Open Position", 43 | colors: [Color(0xff93291E), Color(0xffED213A)], 44 | leftTitles: {1: "10K", 2: "20K", 3: "30K", 4: "40K", 5: "50k", 6: "60K", 7: "70K", 8: "80K", 9: "90K"}, 45 | bottomTitles: {1: "10M", 2: "20M", 3: "30M", 4: "40M", 5: "50M", 6: "60M", 7: "70M", 8: "80M", 9: "90M"}, 46 | spots: [FlSpot(1, 3), FlSpot(2, 4), FlSpot(3, 1.8), FlSpot(4, 1.5), FlSpot(5, 1.0), FlSpot(6, 2.2), FlSpot(7, 1.8), FlSpot(8, 1.5)], 47 | ), 48 | ]; 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Scaffold( 53 | body: SingleChildScrollView( 54 | padding: EdgeInsets.all(16), 55 | child: Column( 56 | children: [ 57 | Header(nickname: controller.nickname), 58 | SizedBox(height: 16), 59 | SizedBox( 60 | height: 240, 61 | child: MiniInfoCard( 62 | data: chartsData, 63 | ), 64 | ), 65 | SizedBox(height: 16), 66 | Row( 67 | children: [ 68 | BigCard( 69 | title: "分组列表", 70 | width: 900, 71 | height: 500, 72 | child: Expanded( 73 | child: getDataGrid(), 74 | ), 75 | ), 76 | SizedBox(width: 16), 77 | Expanded( 78 | child: BigCard( 79 | title: "分组列表", 80 | height: 500, 81 | child: Expanded( 82 | child: getDataGrid(pagerVisable: false), 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | SizedBox(height: 16), 89 | BigCard( 90 | title: "分组列表", 91 | height: 500, 92 | child: Expanded( 93 | child: getDataGrid(), 94 | ), 95 | ), 96 | ], 97 | ), 98 | ), 99 | ); 100 | } 101 | 102 | Widget getDataGrid({double? width, double? height, bool? pagerVisable = true}) { 103 | return BigDataGrid( 104 | // key: scaffoldKey, 105 | // title: "分组管理", 106 | width: width, 107 | height: height, 108 | searchUrl: "/admin/system/group/listPage", 109 | // insertUrl: "/admin/system/group/insert", 110 | updateUrl: "/admin/system/group/update", 111 | deleteUrl: "/admin/system/group/delete", 112 | // cleanUrl: "/admin/system/group/clean", 113 | queryParam: QueryParam(), 114 | pagerVisable: pagerVisable!, 115 | columns: [ 116 | BigDataColumn( 117 | dataType: 'int', 118 | name: "id", 119 | label: "ID", 120 | allowEditing: false, 121 | allowSearch: true, 122 | ), 123 | BigDataColumn( 124 | dataType: 'string', 125 | name: "name", 126 | allowEditing: true, 127 | allowResizeWidth: true, 128 | minimumWidth: 240, 129 | label: "分组名", 130 | required: true, 131 | ), 132 | BigDataColumn( 133 | dataType: 'string', 134 | name: "note", 135 | minimumWidth: 120, 136 | label: "备注", 137 | ), 138 | BigDataColumn( 139 | dataType: 'datetime', 140 | name: "create_time", 141 | minimumWidth: 240, 142 | label: "创建时间", 143 | allowSearch: true, 144 | ), 145 | BigDataColumn( 146 | dataType: 'datetime', 147 | name: "update_time", 148 | minimumWidth: 240, 149 | label: "更新时间", 150 | ), 151 | BigDataColumn( 152 | dataType: 'datetime', 153 | name: "delete_time", 154 | minimumWidth: 240, 155 | label: "删除时间", 156 | allowEditing: false, 157 | visible: false, 158 | ), 159 | ], 160 | // searchFields: [], 161 | ); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/app/modules/home/user_manage/user_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:go_author_client/app/api/base_api.dart'; 6 | import 'package:go_author_client/app/models/query_param.dart'; 7 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 8 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 9 | import 'package:go_author_client/app/modules/components/big_data_grid/big_grid_def.dart'; 10 | 11 | import 'user_manage_controller.dart'; 12 | 13 | class UserManageView extends GetView { 14 | final scaffoldKey = GlobalKey(); 15 | 16 | UserManageView({Key? key}) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return BigDataControl( 21 | key: scaffoldKey, 22 | title: "用户管理", 23 | searchUrl: "/admin/system/user/listPage", 24 | insertUrl: "/admin/system/user/insert", 25 | updateUrl: "/admin/system/user/update", 26 | deleteUrl: "/admin/system/user/delete", 27 | cleanUrl: "/admin/system/user/clean", 28 | queryParam: QueryParam(), 29 | columns: [ 30 | BigDataColumn( 31 | dataType: 'int', 32 | name: "id", 33 | label: "ID", 34 | minimumWidth: 100, 35 | allowEditing: false, 36 | allowSearch: true, 37 | ), 38 | BigDataColumn( 39 | dataType: 'string', 40 | name: "nickname", 41 | allowEditing: true, 42 | allowResizeWidth: true, 43 | minimumWidth: 200, 44 | label: "昵称 ", 45 | required: true, 46 | ), 47 | BigDataColumn( 48 | dataType: 'string', 49 | ctrlType: BIGDATAGRID_CTRLTYPE_IMAGE, 50 | name: "avatar", 51 | allowEditing: true, 52 | allowResizeWidth: true, 53 | minimumWidth: 200, 54 | label: "头像", 55 | allowedExtensions: ["jpg", "jpeg", "png"], 56 | onUploadFile: (row, path) async { 57 | // 如果是新增,则不上传 58 | if (row["id"] == null) { 59 | return null; 60 | } 61 | // TODO 上传到阿里云oss 62 | 63 | // 这里先上传到本地 64 | var resp = await BaseApi().upload("/admin/system/user/uploadAvatar", path, "avatarFile", {"id": row["id"]}, {}); 65 | if (resp != null && resp["code"] == 200) { 66 | BotToast.showText(text: "上传成功"); 67 | return resp["data"]; 68 | } else { 69 | BotToast.showText(text: "上传失败"); 70 | return null; 71 | } 72 | }, 73 | ), 74 | BigDataColumn( 75 | dataType: 'string', 76 | name: "username", 77 | allowEditing: true, 78 | allowResizeWidth: true, 79 | minimumWidth: 200, 80 | label: "用户名 ", 81 | required: true, 82 | ), 83 | BigDataColumn( 84 | dataType: 'string', 85 | name: "password", 86 | allowEditing: true, 87 | allowResizeWidth: true, 88 | minimumWidth: 150, 89 | label: "密码", 90 | required: true, 91 | ), 92 | BigDataColumn( 93 | dataType: 'int', 94 | name: "age", 95 | allowEditing: true, 96 | allowResizeWidth: true, 97 | minimumWidth: 80, 98 | label: "年龄", 99 | ), 100 | BigDataColumn( 101 | dataType: 'string', 102 | name: "token", 103 | allowEditing: false, 104 | allowResizeWidth: true, 105 | minimumWidth: 150, 106 | label: "授权码", 107 | ), 108 | BigDataColumn( 109 | dataType: 'int', 110 | ctrlType: 'dropdown', 111 | name: "role_id", 112 | allowResizeWidth: true, 113 | minimumWidth: 120, 114 | label: "角色ID", 115 | required: true, 116 | optionsUrl: "/admin/system/role/listAll", 117 | optionsColName: "name", 118 | optionsColValue: "id", 119 | allowSearch: true, 120 | onDropdownWillAppear: (toolbarAction, options) {}, 121 | selectable: true, 122 | multipleSelect: false, 123 | ), 124 | BigDataColumn( 125 | dataType: 'int', 126 | ctrlType: 'dropdown', 127 | name: "group_id", 128 | allowResizeWidth: true, 129 | minimumWidth: 120, 130 | label: "分组ID", 131 | optionsUrl: "/admin/system/group/listAll", 132 | optionsColName: "name", 133 | optionsColValue: "id", 134 | allowSearch: true, 135 | onDropdownWillAppear: (toolbarAction, options) { 136 | if (options.isEmpty || options.first["value"] != 0) { 137 | options.insert(0, {"name": "未分配", "value": 0}); 138 | } 139 | }, 140 | selectable: true, 141 | multipleSelect: false, 142 | ), 143 | BigDataColumn( 144 | dataType: 'int', 145 | ctrlType: 'dropdown', 146 | name: "channel_id", 147 | allowResizeWidth: true, 148 | minimumWidth: 120, 149 | label: "渠道ID", 150 | optionsUrl: "/admin/business/channel/listAll", 151 | optionsColName: "name", 152 | optionsColValue: "id", 153 | allowSearch: true, 154 | onDropdownWillAppear: (toolbarAction, options) { 155 | if (options.isEmpty || options.first["value"] != 0) { 156 | options.insert(0, {"name": "未分配", "value": 0}); 157 | } 158 | }, 159 | selectable: true, 160 | multipleSelect: false, 161 | ), 162 | BigDataColumn( 163 | dataType: 'string', 164 | name: "desc", 165 | allowEditing: true, 166 | allowResizeWidth: true, 167 | minimumWidth: 120, 168 | label: "描述", 169 | ), 170 | BigDataColumn( 171 | dataType: 'string', 172 | name: "phone", 173 | allowEditing: true, 174 | allowResizeWidth: true, 175 | minimumWidth: 200, 176 | label: "手机号", 177 | ), 178 | BigDataColumn( 179 | dataType: 'string', 180 | name: "email", 181 | allowEditing: true, 182 | allowResizeWidth: true, 183 | minimumWidth: 200, 184 | label: "邮箱", 185 | ), 186 | BigDataColumn( 187 | dataType: 'bool', 188 | ctrlType: 'dropdown', 189 | name: "disabled", 190 | minimumWidth: 80, 191 | label: "封禁", 192 | required: true, 193 | allowSearch: true, 194 | ), 195 | BigDataColumn( 196 | dataType: 'string', 197 | name: "note", 198 | minimumWidth: 120, 199 | label: "备注", 200 | ), 201 | BigDataColumn( 202 | dataType: 'datetime', 203 | name: "create_time", 204 | minimumWidth: 240, 205 | label: "创建时间", 206 | allowSearch: true, 207 | ), 208 | BigDataColumn( 209 | dataType: 'datetime', 210 | name: "update_time", 211 | minimumWidth: 240, 212 | label: "更新时间", 213 | ), 214 | BigDataColumn( 215 | dataType: 'datetime', 216 | name: "delete_time", 217 | minimumWidth: 240, 218 | label: "删除时间", 219 | allowEditing: false, 220 | visible: false, 221 | ), 222 | ], 223 | searchFields: [ 224 | // 嵌套子查询 225 | // BigDataField( 226 | // label: "模块名", 227 | // name: "module_name", // 这里要取一个在当前页面唯一的列名 228 | // subQueryParam: SubQueryParam( 229 | // table: 'module', 230 | // where: 'module_id = (?)', 231 | // sub_column: 'module.id', 232 | // sub_where: 'module.name = ?', 233 | // ), 234 | // ), 235 | ], 236 | ); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/app/modules/components/combobox/dropdown2.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:dropdown_button2/dropdown_button2.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | // options里的key和value都必须唯一 6 | class Dropdown2 extends StatelessWidget { 7 | List selectedKeys = []; 8 | List selectedValues = []; 9 | List keys = []; 10 | List> items = []; 11 | final Map _data = {}; 12 | 13 | Widget? hint; 14 | bool isExpanded; 15 | bool isDense; 16 | bool autofocus; 17 | bool selectable; 18 | bool multiSelectable; 19 | double? dropdownMaxHeight; 20 | // 默认文本左对齐 21 | bool textAlignmentStart; 22 | AlignmentGeometry alignment; 23 | TextStyle? style; 24 | ButtonStyleData? buttonStyleData; 25 | IconStyleData iconStyleData; 26 | // 该样式会取消文本左对齐 27 | DropdownStyleData? dropdownStyleData; 28 | MenuItemStyleData menuItemStyleData; 29 | Widget? underline; 30 | Function(List selectedKeys, List selectedValues)? onChanged; 31 | OnMenuStateChangeFn? onMenuStateChange; 32 | FormFieldValidator? validator; 33 | 34 | Dropdown2({ 35 | super.key, 36 | required this.selectedValues, 37 | required this.items, 38 | this.hint, 39 | this.isExpanded = false, 40 | this.isDense = false, 41 | this.autofocus = false, 42 | this.alignment = AlignmentDirectional.centerStart, 43 | this.style, 44 | this.underline, 45 | this.onChanged, 46 | this.onMenuStateChange, 47 | this.validator, 48 | this.selectable = false, 49 | this.textAlignmentStart = false, 50 | this.multiSelectable = false, 51 | this.buttonStyleData, 52 | this.iconStyleData = const IconStyleData(), 53 | this.dropdownStyleData, 54 | this.dropdownMaxHeight, 55 | this.menuItemStyleData = const MenuItemStyleData(), 56 | }) { 57 | // 将列表转成map 58 | for (var item in this.items) { 59 | this._data[item["name"]] = item["value"]; 60 | } 61 | 62 | // 选中后的值是value, 需要转换为key 63 | for (var value in selectedValues) { 64 | var key = this.valueToKey(value); 65 | if (key != null) { 66 | this.selectedKeys.add(key); 67 | } 68 | } 69 | 70 | // 将key值转换为数组 71 | for (var item in this.items) { 72 | this.keys.add(item["name"]); 73 | } 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { 79 | DropdownButtonBuilder? selectBuilder; 80 | if (!this.selectable || !this.multiSelectable) { 81 | selectBuilder = null; 82 | } else { 83 | // 多选时初始化选择构建器 84 | selectBuilder = (context) { 85 | return [ 86 | Text( 87 | selectedKeys.join(', '), 88 | style: TextStyle(overflow: TextOverflow.ellipsis), 89 | maxLines: 1, 90 | ), 91 | ]; 92 | }; 93 | } 94 | String? defaultValue; 95 | if (this.selectable && this.multiSelectable) { 96 | // 这里特别注意,只有有默认选中项的时候,才将默认值设置为第一项,否则无法正常显示 97 | if (this.selectedValues.isNotEmpty) { 98 | defaultValue = keys.first; 99 | } else { 100 | // 而是新的时候,没有默认选中项,我们要提示hint, 所以必须设置为null 101 | defaultValue = null; 102 | } 103 | } else { 104 | if (this.selectedKeys.isNotEmpty) { 105 | defaultValue = selectedKeys.first; 106 | } 107 | } 108 | dropdownStyleData ??= DropdownStyleData( 109 | maxHeight: dropdownMaxHeight ?? 480, 110 | // 设置与父控件宽度相同, 这个属性设置过后就可以让文本左对齐 111 | width: textAlignmentStart ? constraints.maxWidth : null, 112 | ); 113 | return DropdownButtonHideUnderline( 114 | child: DropdownButtonFormField2( 115 | isDense: isDense, 116 | autofocus: autofocus, 117 | hint: hint, 118 | isExpanded: isExpanded, 119 | // 这里的初始值非常关闭,与当前选中的值没有关系,直接设置为数组第一个即可 120 | value: defaultValue, 121 | alignment: alignment, 122 | style: style, 123 | validator: validator, 124 | onMenuStateChange: onMenuStateChange, 125 | // 弹出设置下拉列表样式 126 | // dropdownStyleData: DropdownStyleData( 127 | // width: 400, // 指定宽度可以让内容左对齐 128 | // ), 129 | buttonStyleData: buttonStyleData, 130 | iconStyleData: iconStyleData, 131 | dropdownStyleData: dropdownStyleData!, 132 | menuItemStyleData: menuItemStyleData, 133 | // TODO 搜索功能看官方文档 134 | items: this.keys.map((name) { 135 | if (!this.selectable || !this.multiSelectable) { 136 | return DropdownMenuItem( 137 | value: name, 138 | child: Text(name), 139 | ); 140 | } 141 | return DropdownMenuItem( 142 | value: name, 143 | //disable default onTap to avoid closing menu when selecting an item 144 | enabled: false, 145 | child: StatefulBuilder( 146 | builder: (context, menuSetState) { 147 | final isSelected = selectedKeys.contains(name); 148 | return InkWell( 149 | onTap: () { 150 | if (isSelected) { 151 | if (!this.selectable || !this.multiSelectable) { 152 | this.selectedKeys.clear(); 153 | } 154 | selectedKeys.remove(name); 155 | selectedValues.remove(_data[name]); 156 | } else { 157 | selectedKeys.add(name); 158 | selectedValues.add(_data[name]); 159 | } 160 | //This rebuilds the StatefulWidget to update the button's text 161 | // setState(() {}); 162 | //This rebuilds the dropdownMenu Widget to update the check mark 163 | menuSetState(() {}); 164 | if (selectedKeys.length != selectedValues.length) { 165 | BotToast.showText(text: "下拉列表缓存数据同步错误"); 166 | return; 167 | } 168 | if (onChanged != null) { 169 | onChanged!(selectedKeys, selectedValues); 170 | } 171 | }, 172 | child: Container( 173 | height: double.infinity, 174 | padding: EdgeInsets.symmetric(horizontal: 0.0), 175 | child: Row( 176 | children: [ 177 | if (isSelected) Icon(Icons.check_box_outlined) else Icon(Icons.check_box_outline_blank), 178 | const SizedBox(width: 8), 179 | Expanded( 180 | child: Text(name), 181 | ), 182 | ], 183 | ), 184 | ), 185 | ); 186 | }, 187 | ), 188 | ); 189 | }).toList(), 190 | onChanged: _onChange, 191 | // 选择完成后显示的控件, 这里可以实现动态删除的效果 192 | selectedItemBuilder: selectBuilder, 193 | ), 194 | ); 195 | }); 196 | } 197 | 198 | _onChange(name) { 199 | if (!this.selectable || !this.multiSelectable) { 200 | this.selectedKeys.clear(); 201 | this.selectedValues.clear(); 202 | } 203 | if (name != null) { 204 | this.selectedKeys.add(name); 205 | this.selectedValues.add(_data[name]); 206 | } 207 | if (onChanged != null) { 208 | onChanged!(selectedKeys, selectedValues); 209 | } 210 | } 211 | 212 | String? valueToKey(dynamic value) { 213 | if (value == null) { 214 | return null; 215 | } 216 | for (var entry in this._data.entries) { 217 | if (entry.value == value) { 218 | return entry.key; 219 | } 220 | } 221 | return null; 222 | } 223 | 224 | dynamic keyToValue(String key) { 225 | return this._data[key]; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /lib/app/modules/home/role_manage/role_manage_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:go_author_client/app/api/base_api.dart'; 6 | import 'package:go_author_client/app/models/query_param.dart'; 7 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_column.dart'; 8 | import 'package:go_author_client/app/modules/components/big_data_grid/big_data_control.dart'; 9 | import 'package:go_author_client/app/modules/components/big_tree/big_node.dart'; 10 | import 'package:go_author_client/app/modules/components/big_tree/big_tree.dart'; 11 | import 'package:go_author_client/app/modules/components/dialog/big_dialog.dart'; 12 | 13 | import 'role_manage_controller.dart'; 14 | 15 | class RoleManageView extends GetView { 16 | final scaffoldKey = GlobalKey(); 17 | final treeKey = GlobalKey(); 18 | final RxList allModules = [].obs; 19 | final RxList initCheckedNodes = [].obs; 20 | List hasModules = []; 21 | late int roleId = 0; 22 | final int roolNodeId = 0xFFFFFFFFFFFFFFF; 23 | RxBool treeCheckAll = false.obs; 24 | 25 | RoleManageView({Key? key}) : super(key: key); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return BigDataControl( 30 | key: scaffoldKey, 31 | title: "角色管理", 32 | searchUrl: "/admin/system/role/listPage", 33 | insertUrl: "/admin/system/role/insert", 34 | updateUrl: "/admin/system/role/update", 35 | deleteUrl: "/admin/system/role/delete", 36 | cleanUrl: "/admin/system/role/clean", 37 | queryParam: QueryParam(), 38 | columns: [ 39 | BigDataColumn( 40 | dataType: 'int', 41 | name: "id", 42 | label: "ID", 43 | minimumWidth: 100, 44 | allowEditing: false, 45 | allowSearch: false, 46 | ), 47 | BigDataColumn( 48 | dataType: 'string', 49 | name: "name", 50 | allowSearch: true, 51 | operator: "like", 52 | allowEditing: true, 53 | allowResizeWidth: true, 54 | minimumWidth: 240, 55 | label: "角色名", 56 | required: true, 57 | ), 58 | BigDataColumn( 59 | dataType: 'string', 60 | name: "note", 61 | minimumWidth: 120, 62 | label: "备注", 63 | ), 64 | BigDataColumn( 65 | dataType: 'datetime', 66 | name: "create_time", 67 | minimumWidth: 240, 68 | label: "创建时间", 69 | allowSearch: true, 70 | ), 71 | BigDataColumn( 72 | dataType: 'datetime', 73 | name: "update_time", 74 | minimumWidth: 240, 75 | label: "更新时间", 76 | ), 77 | BigDataColumn( 78 | label: "操作", 79 | name: "control", 80 | ctrlType: "control", 81 | minimumWidth: 480, 82 | allowEditing: false, 83 | allowSearch: false, 84 | allowSorting: false, 85 | widgets: [ 86 | BigButton( 87 | text: "设置权限", 88 | onPressed: (col, rowIndex, id, row) async { 89 | this.roleId = id; 90 | this.treeCheckAll.value = this.roleId == 1; 91 | this.closePermissionDialog(); 92 | await initTreeData(id); 93 | this.openPermissionDialog(); 94 | }, 95 | ), 96 | ], 97 | dataType: '', 98 | ), 99 | ], 100 | searchFields: [ 101 | // 嵌套子查询 102 | // BigDataField( 103 | // label: "模块名", 104 | // name: "module_name", // 这里要取一个在当前页面唯一的列名 105 | // subQueryParam: SubQueryParam( 106 | // table: 'module', 107 | // where: 'module_id = (?)', 108 | // sub_column: 'module.id', 109 | // sub_where: 'module.name = ?', 110 | // ), 111 | // ), 112 | ], 113 | coverWidget: BigDialog( 114 | title: "角色权限选择", 115 | buttonsVisible: true, 116 | child: Obx( 117 | () => Container( 118 | padding: EdgeInsets.all(10), 119 | child: BigTree( 120 | key: treeKey, 121 | titlePadding: EdgeInsets.symmetric(horizontal: 8), 122 | checkable: true, 123 | checkedAll: treeCheckAll.value, 124 | verticalLineVisable: true, 125 | horizontalLineVisable: true, 126 | nodes: allModules, 127 | initCheckedNodes: initCheckedNodes, 128 | onChange: (status, node, halfCheckedNodes, fullCheckedNodes) {}, 129 | ), 130 | ), 131 | ), 132 | onClose: (widget) { 133 | (scaffoldKey.currentWidget as BigDataControl).datagrid.closeCover(); 134 | }, 135 | onCancel: (dialog) { 136 | this.closePermissionDialog(); 137 | }, 138 | onConfirm: (dialog) async { 139 | if (this.roleId == 1) { 140 | BotToast.showText(text: "超级管理员不可编辑权限"); 141 | return; 142 | } 143 | var modules = [...getTreeCtrl().fullCheckedNodes, ...getTreeCtrl().halfCheckedNodes]; 144 | modules.remove(roolNodeId); 145 | var resp = await BaseApi().post("/admin/system/permission/updateRolePermission", {"roleId": this.roleId, "modules": modules}, {}); 146 | if (resp == null || resp["code"] != 200) { 147 | BotToast.showText(text: "请求失败"); 148 | } else { 149 | BotToast.showText(text: "操作成功"); 150 | this.closePermissionDialog(); 151 | } 152 | }, 153 | ), 154 | ); 155 | } 156 | 157 | void openPermissionDialog() { 158 | (scaffoldKey.currentWidget as BigDataControl).datagrid.openCover(); 159 | } 160 | 161 | void closePermissionDialog() { 162 | (scaffoldKey.currentWidget as BigDataControl).datagrid.closeCover(); 163 | } 164 | 165 | BigTree getTreeCtrl() { 166 | return treeKey.currentWidget as BigTree; 167 | } 168 | 169 | Future initTreeData(int id) async { 170 | this.allModules.value = [ 171 | // 添加一个虚拟的根节点 172 | BigNode( 173 | id: roolNodeId, 174 | title: "全部", 175 | parentId: 0, 176 | expanded: true.obs, 177 | childs: (await this.initAllModules()).map((node) { 178 | node.parentId = roolNodeId; 179 | return node; 180 | }).toList(), 181 | ) 182 | ]; 183 | this.hasModules = await this.initHasModules(id); 184 | // 将已有的模块的最低层单节点设置为树的选中节点 185 | this.initCheckedNodes.clear(); 186 | this.initTreeCheckeds(this.hasModules); 187 | } 188 | 189 | Future> initAllModules() async { 190 | List list = []; 191 | // 拉取后台树型数据,必须要是树型数据 192 | var resp = await BaseApi().get("/admin/system/module/getAllModules", {"istree": true}, {}); 193 | if (resp == null || resp["code"] != 200) { 194 | return list; 195 | } 196 | return moduleTreeToBigTree(resp["data"]); 197 | } 198 | 199 | // 将服务器返回的树型数据复制为树型节点数据 200 | List moduleTreeToBigTree(List list) { 201 | List tree = []; 202 | for (var item in list) { 203 | tree.add(BigNode( 204 | id: item["id"], 205 | title: item["name"], 206 | parentId: item["parent_id"], 207 | childs: item["childs"] != null ? moduleTreeToBigTree(item["childs"]) : [], 208 | )); 209 | } 210 | return tree; 211 | } 212 | 213 | Future> initHasModules(int id) async { 214 | List list = []; 215 | // 拉取后台线性数据 216 | var resp = await BaseApi().get("/admin/system/permission/getModulesByRoleId", { 217 | "id": id, 218 | "istree": false, 219 | }, {}); 220 | if (resp == null || resp["code"] != 200) { 221 | return list; 222 | } 223 | for (var item in resp["data"]) { 224 | list.add(BigNode(id: item["id"], title: item["name"], alone: item["alone"])); 225 | } 226 | return list; 227 | } 228 | 229 | // 将选中节点的所有单节点缓存到数组 230 | void initTreeCheckeds(List nodes) { 231 | for (var node in nodes) { 232 | // if (node.childs.isEmpty) { 233 | if (node.alone) { 234 | initCheckedNodes.add(node.id); 235 | } else { 236 | initTreeCheckeds(node.childs); 237 | } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /lib/app/modules/home/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/consts/global.dart'; 5 | import 'package:go_author_client/app/models/big_notification.dart'; 6 | import 'package:go_author_client/app/models/module.dart'; 7 | import 'package:go_author_client/app/modules/components/dialog/big_alert.dart'; 8 | import 'package:go_author_client/app/routes/app_pages.dart'; 9 | import 'package:go_author_client/app/utils/local_storage.dart'; 10 | 11 | import 'home_controller.dart'; 12 | 13 | // 登录过后的主框架页面 14 | 15 | class HomeView extends GetView { 16 | late BuildContext context; 17 | 18 | HomeView({Key? key}) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | this.context = context; 23 | return Scaffold( 24 | // 必须显示标题栏,才能显示抽屉菜单 25 | // appBar: AppBar( 26 | // actions: [ 27 | // IconButton( 28 | // icon: Icon(Icons.refresh), 29 | // onPressed: () {}, 30 | // ) 31 | // ], 32 | // ), 33 | drawer: Drawer( 34 | child: ListView( 35 | children: [ 36 | ListTile( 37 | title: Text("测试"), 38 | ), 39 | ], 40 | ), 41 | // FutureBuilder>( 42 | // future: controller.fetchData(), 43 | // ), 44 | ), 45 | body: Row( 46 | children: [ 47 | // 左侧菜单 48 | Obx( 49 | () => SizedBox( 50 | width: controller.leftMenuWidth.value, 51 | child: Container( 52 | decoration: BoxDecoration( 53 | color: Color.fromARGB(255, 36, 36, 36), // 设置背景颜色 54 | // borderRadius: BorderRadius.circular(10), // 设置圆角 55 | ), 56 | child: buildMenu(), 57 | ), 58 | ), 59 | ), 60 | 61 | // 垂直分割线 62 | // VerticalDivider( 63 | // // color: Colors.grey, 64 | // thickness: 1, 65 | // endIndent: 0, 66 | // width: 20, 67 | // ), 68 | 69 | // 可滚动内容 70 | Expanded( 71 | child: Container( 72 | // decoration: BoxDecoration( 73 | // color: Color.fromARGB(255, 36, 36, 36), 74 | // // borderRadius: BorderRadius.circular(15.0), 75 | // ), 76 | padding: EdgeInsets.all(0.0), 77 | child: PageView.builder( 78 | controller: controller.pageController, 79 | physics: NeverScrollableScrollPhysics(), 80 | itemBuilder: (context, index) { 81 | return AppPages.pages[index].page; 82 | }, 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ); 89 | } 90 | 91 | FutureBuilder> buildProfileView() { 92 | return FutureBuilder( 93 | future: controller.fetchProfile(), 94 | builder: ((context, snapshot) { 95 | if (snapshot.connectionState == ConnectionState.waiting) { 96 | return CircularProgressIndicator(); // 加载中的UI 97 | } else if (snapshot.hasError) { 98 | return Text('Error: ${snapshot.error}'); // 错误时的UI 99 | } else { 100 | Map profile = snapshot.data!; 101 | if (profile.isEmpty) { 102 | profile["nickname"] = "请求失败"; 103 | profile["email"] = "请求失败"; 104 | } 105 | Global.eventBus.fire(BigNotification(id: "profile", msg: profile)); 106 | // 抽屉头部 107 | return UserAccountsDrawerHeader( 108 | // margin: EdgeInsets.only(right: 16.0), // 设置右侧空白 109 | accountName: Text(profile["nickname"]), 110 | accountEmail: Text(profile["email"]), 111 | // 头像图标 112 | currentAccountPicture: CircleAvatar( 113 | foregroundImage: AssetImage("assets/images/avatar.png"), 114 | ), 115 | decoration: BoxDecoration( 116 | color: Colors.transparent, 117 | // 背景图片 118 | // image: DecorationImage( 119 | // image: AssetImage("assets/images/vector-3.png"), 120 | // fit: BoxFit.cover, 121 | // ), 122 | ), 123 | otherAccountsPictures: [ 124 | Stack( 125 | children: [ 126 | Align( 127 | alignment: Alignment.bottomRight, 128 | child: IconButton( 129 | onPressed: () async { 130 | // 退出登录 131 | if (!await BigAlert.showConfirm(context: context, title: "警告", content: "确定要退出登录吗?")) { 132 | return; 133 | } 134 | LocalStorage.remove("x-token"); 135 | Get.offAllNamed(Routes.LOGIN); 136 | }, 137 | icon: Icon( 138 | Icons.exit_to_app, 139 | size: 24.0, 140 | ), 141 | ), 142 | ), 143 | ], 144 | ), 145 | ], 146 | onDetailsPressed: () { 147 | BotToast.showText(text: "点击详情"); 148 | }, 149 | ); 150 | } 151 | }), 152 | ); 153 | } 154 | 155 | bool isSingleNode(Module module) { 156 | if (module.childs == null) { 157 | return true; 158 | } 159 | for (var m in module.childs!) { 160 | if (!m.hidden) { 161 | return false; 162 | } 163 | } 164 | return true; 165 | } 166 | 167 | // index为不可展开的菜单项的累计下标 168 | Widget buildMenuItem(Module module, int level) { 169 | if (isSingleNode(module)) { 170 | // module.index = index; 171 | return Obx(() { 172 | return ListTile( 173 | // leading: CircleAvatar( 174 | // backgroundColor: Colors.transparent, 175 | // child: Icon(Icons.menu), 176 | // ), 177 | // leading: Icon(Icons.menu), 178 | // title: Text(module.name), 179 | selected: module.id == controller.selectedModule.value.id, 180 | title: Row( 181 | children: [ 182 | Padding( 183 | padding: EdgeInsets.fromLTRB(level * 8.0, 0, 0, 0), 184 | // flutter暂时无法通过图标名称来获取IconData 185 | child: Icon(module.name == "仪表盘" ? Icons.dashboard : Icons.view_module), 186 | ), 187 | Padding( 188 | padding: EdgeInsets.symmetric(horizontal: 8.0), 189 | child: Text(module.name), 190 | ), 191 | ], 192 | ), 193 | // contentPadding: EdgeInsets.symmetric(horizontal: module.name == "仪表盘" ? 8.0 : 16.0), // 调整水平间距 194 | onTap: () { 195 | // 处理 ListTile 的点击事件 196 | controller.selectedModule.value = module; 197 | // 根据module动态查找对应path的下标 198 | var index = controller.getMenuIndexByPath(module.path); 199 | if (index >= 0) { 200 | controller.pageController.jumpToPage(index); 201 | } 202 | }, 203 | ); 204 | }); 205 | } else { 206 | return ExpansionTile( 207 | // leading: CircleAvatar( 208 | // backgroundColor: Colors.transparent, 209 | // child: Icon(Icons.menu), 210 | // ), 211 | title: Row( 212 | children: [ 213 | Padding( 214 | padding: EdgeInsets.fromLTRB(level * 8.0, 0, 0, 0), 215 | // flutter暂时无法通过图标名称来获取IconData 216 | child: Icon(Icons.view_list), 217 | ), 218 | Padding( 219 | padding: EdgeInsets.symmetric(horizontal: 8.0), 220 | child: Text(module.name), 221 | ), 222 | ], 223 | ), 224 | onExpansionChanged: (bool expanded) {}, 225 | // 控制是否展开的属性 226 | initiallyExpanded: false, 227 | children: module.childs!.map((child) { 228 | return buildMenuItem(child, level + 1); 229 | }).toList(), 230 | ); 231 | } 232 | } 233 | 234 | FutureBuilder> buildMenu() { 235 | return FutureBuilder>( 236 | future: controller.fetchData(), 237 | builder: (context, snapshot) { 238 | if (snapshot.connectionState == ConnectionState.waiting) { 239 | return Center(child: CircularProgressIndicator()); // 加载中的UI 240 | } else if (snapshot.hasError) { 241 | return Text("Error: ${snapshot.error}"); // 错误时的UI 242 | } else { 243 | List modules = snapshot.data!; 244 | return ListView.builder( 245 | itemCount: modules.length + 1, 246 | itemBuilder: (BuildContext context, int index) { 247 | if (index == 0) { 248 | return buildProfileView(); 249 | } 250 | var module = modules[index - 1]; 251 | return buildMenuItem(module, 0); 252 | }, 253 | ); 254 | } 255 | }, 256 | ); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_data_grid/big_data_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/api/base_api.dart'; 5 | import 'package:go_author_client/app/models/query_param.dart'; 6 | import 'package:go_author_client/app/utils/datatype_util.dart'; 7 | import 'package:syncfusion_flutter_datagrid/datagrid.dart'; 8 | 9 | import 'big_data_column.dart'; 10 | import 'big_grid_def.dart'; 11 | import 'grid_table_source.dart'; 12 | 13 | // 数据表格控件,包含: 表格、分页控件 14 | // 注意必须为当前控件指定高度,否则放到Column等容器将报错 15 | class BigDataGrid extends StatelessWidget { 16 | String searchUrl; 17 | String updateUrl; 18 | String deleteUrl; 19 | double rowHeight; 20 | List columns; 21 | double? width; 22 | double? height; 23 | 24 | // 计算属性 25 | final scaffoldKey = GlobalKey(); 26 | final formKey = GlobalKey(); 27 | late GridTableSource dataSource; 28 | final DataGridController dataGridController = DataGridController(); 29 | RxMap columnWidths = RxMap({}); 30 | 31 | late BuildContext context; 32 | // 查询条件 33 | QueryParam queryParam; 34 | 35 | OnCellSubmited? onCellSubmit; 36 | OnLoadCompleted? onLoadCompleted; 37 | OnRefresh? onRefresh; 38 | 39 | TextStyle? cellTextStyle; 40 | 41 | // 是否显示遮盖层 42 | RxBool coverVisible = false.obs; 43 | Widget? coverWidget; 44 | bool pagerVisable = false; 45 | 46 | // 系统参数 47 | bool showCheckboxColumn; 48 | bool allowPullToRefresh; 49 | bool allowEditing; 50 | bool allowFiltering; 51 | bool allowSorting; 52 | 53 | BigDataGrid({ 54 | super.key, 55 | required this.searchUrl, 56 | required this.updateUrl, 57 | required this.deleteUrl, 58 | required this.queryParam, 59 | required this.columns, 60 | // 最小300, 再小就不能显示分页控件 61 | this.width = 300, 62 | required this.height, 63 | this.rowHeight = 49, 64 | this.onLoadCompleted, 65 | this.onCellSubmit, 66 | this.cellTextStyle, 67 | this.coverWidget, 68 | this.onRefresh, 69 | this.pagerVisable = true, 70 | this.showCheckboxColumn = true, 71 | this.allowPullToRefresh = true, 72 | this.allowEditing = true, 73 | this.allowFiltering = true, 74 | this.allowSorting = true, 75 | }) { 76 | // 初始化列宽列表 77 | for (var col in this.columns) { 78 | this.columnWidths[col.name] = col.minimumWidth; 79 | } 80 | dataSource = GridTableSource(searchUrl: searchUrl, columns: this.columns, style: cellTextStyle); 81 | dataSource.onLoadCompleted = this.onLoadCompleted; 82 | dataSource.onCellSubmited = ( 83 | DataGridRow dataGridRow, 84 | GridColumn column, 85 | int id, 86 | int dataRowIndex, 87 | int dataCellIndex, 88 | dynamic oldValue, 89 | dynamic newValue, 90 | ) async { 91 | // 数据类型转换 92 | BigDataColumn? col = this.getColumn(column.columnName); 93 | if (col == null) { 94 | BotToast.showText(text: '当前是未知列'); 95 | return false; 96 | } 97 | try { 98 | // 如果是多选列需要进行转换 99 | if (!col.multipleSelect) { 100 | if (newValue is List) { 101 | newValue = newValue.isNotEmpty ? newValue.first : null; 102 | } 103 | } 104 | 105 | newValue = DataTypeUtil.convertDataType(newValue, col.dataType); 106 | } catch (e) { 107 | BotToast.showText(text: '数据类型转换出错'); 108 | return false; 109 | } 110 | if (oldValue == newValue) { 111 | return false; 112 | } 113 | try { 114 | if (this.onCellSubmit != null) { 115 | if (!await this.onCellSubmit!(dataGridRow, column, id, dataRowIndex, dataCellIndex, oldValue, newValue)) { 116 | return false; 117 | } 118 | } 119 | var resp = await BaseApi().post(this.updateUrl, {"id": id, column.columnName: newValue}, {}); 120 | if (resp != null && resp["code"] == 200) { 121 | dataSource.updateCell(dataGridRow, column.columnName, id, dataRowIndex, dataCellIndex, newValue); 122 | BotToast.showText(text: "保存成功"); 123 | } else { 124 | BotToast.showText(text: resp != null && resp["message"] != null ? resp['message'] : '请求失败'); 125 | } 126 | } catch (e) { 127 | BotToast.showText(text: '提交数据出错'); 128 | return false; 129 | } 130 | return false; 131 | }; 132 | } 133 | 134 | void openCover() { 135 | this.coverVisible.value = true; 136 | } 137 | 138 | void closeCover() { 139 | this.coverVisible.value = false; 140 | } 141 | 142 | BigDataColumn? getColumn(String name) { 143 | for (BigDataColumn col in this.columns) { 144 | if (col.name == name) { 145 | return col; 146 | } 147 | } 148 | return null; 149 | } 150 | 151 | @override 152 | Widget build(BuildContext context) { 153 | this.context = context; 154 | LayoutBuilder( 155 | builder: (context, constraints) { 156 | if (this.pagerVisable && constraints.biggest.width < 300) { 157 | throw Exception(["显示页脚的情况下,宽度不能小于300"]); 158 | } 159 | return Container(); 160 | }, 161 | ); 162 | return Obx(() { 163 | return SizedBox( 164 | height: height, 165 | width: width, 166 | child: Stack( 167 | children: [ 168 | // 数据表格 169 | getDataGrid(), 170 | // 加载条 171 | // if (dataSource.isLoading.value) 172 | // 第一种方式实现进度条,并可自定义的遮盖层 173 | Visibility( 174 | visible: coverVisible.value || this.dataSource.isLoading.value, 175 | // 控件容器 176 | child: Positioned( 177 | child: Container( 178 | color: Colors.black26.withOpacity(0.5), // 半透明 179 | child: Center( 180 | // 放进度条或者自定义控件 181 | child: this.dataSource.isLoading.value 182 | ? Column( 183 | mainAxisAlignment: MainAxisAlignment.center, 184 | children: [ 185 | CircularProgressIndicator(value: 80), 186 | SizedBox(height: 8), 187 | TextButton( 188 | onPressed: () { 189 | this.dataSource.isLoading.value = false; 190 | }, 191 | child: Text("取消", style: TextStyle(fontSize: 16))), 192 | ], 193 | ) 194 | : this.coverWidget, 195 | ), 196 | ), 197 | ), 198 | ), 199 | // 第二种方式实现进度条 200 | // Positioned.fill( 201 | // child: AbsorbPointer( 202 | // absorbing: true, // Disable mouse interaction 203 | // child: Container( 204 | // color: Colors.black26, // Background color 205 | // child: Center( 206 | // child: CircularProgressIndicator(value: 80), 207 | // ), 208 | // ), 209 | // ), 210 | // ), 211 | ], 212 | ), 213 | ); 214 | }); 215 | } 216 | 217 | Widget getDataGrid() { 218 | return Column( 219 | children: [ 220 | Expanded( 221 | child: SfDataGrid( 222 | controller: dataGridController, 223 | source: dataSource, 224 | selectionMode: SelectionMode.multiple, 225 | showCheckboxColumn: showCheckboxColumn, 226 | allowPullToRefresh: allowPullToRefresh, 227 | allowEditing: allowEditing, 228 | allowFiltering: allowFiltering, 229 | allowSorting: allowSorting, 230 | rowHeight: rowHeight, 231 | navigationMode: GridNavigationMode.cell, 232 | // 鼠标悬念时才显示过滤按钮和排序按钮 233 | showColumnHeaderIconOnHover: true, 234 | // 冻结左侧列数 235 | frozenColumnsCount: 2, 236 | // 显示网格线 237 | // gridLinesVisibility: GridLinesVisibility.both, 238 | headerGridLinesVisibility: GridLinesVisibility.both, 239 | // 列宽模式: 默认内容不折叠 240 | columnWidthMode: ColumnWidthMode.fitByCellValue, 241 | allowColumnsResizing: true, 242 | onColumnResizeUpdate: (ColumnResizeUpdateDetails details) { 243 | this.columnWidths[details.column.columnName] = details.width; 244 | return true; 245 | }, 246 | allowSwiping: true, 247 | swipeMaxOffset: 100.0, 248 | startSwipeActionsBuilder: (BuildContext context, DataGridRow row, int rowIndex) { 249 | return GestureDetector( 250 | onTap: () {}, 251 | child: Container( 252 | color: Colors.greenAccent, 253 | child: Center( 254 | child: Icon(Icons.add), 255 | ), 256 | ), 257 | ); 258 | }, 259 | endSwipeActionsBuilder: (BuildContext context, DataGridRow row, int rowIndex) { 260 | return GestureDetector( 261 | onTap: () {}, 262 | child: Container( 263 | color: Colors.redAccent, 264 | child: Center( 265 | child: Icon(Icons.delete), 266 | ), 267 | ), 268 | ); 269 | }, 270 | columns: this.columns.map((col) { 271 | try { 272 | // 加载列表项 273 | if (col.ctrlType == BIGDATAGRID_CTRLTYPE_DROPDOWN && col.optionsUrl != null) { 274 | BaseApi().getAllList(col.optionsUrl!, QueryParam()).then((resp) { 275 | if (resp == null) { 276 | BotToast.showText(text: "获取${col.name}列表失败"); 277 | return; 278 | } 279 | if (resp["code"] != 200 && resp["message"] != null) { 280 | BotToast.showText(text: resp["message"]); 281 | return; 282 | } 283 | col.options = []; 284 | for (var item in resp["data"]) { 285 | col.options.add({ 286 | "name": item[col.optionsColName].toString(), 287 | "value": item[col.optionsColValue], 288 | }); 289 | } 290 | // if (col.options.isEmpty) { 291 | // col.options.add({"name": "无数据", "value": 0}); 292 | // } 293 | // var list = resp["data"].map((item) { 294 | // return {"name": item[col.optionsColName].toString(), "value": item[col.optionsColValue].toString()}; 295 | // }).toList() as List>; 296 | // col.options = list; 297 | }); 298 | if (col.onDataLoadCompleted != null) { 299 | col.onDataLoadCompleted!(col.options); 300 | } 301 | } 302 | } catch (e) { 303 | BotToast.showText(text: "加载${col.name}列表失败"); 304 | } 305 | 306 | return GridColumn( 307 | columnName: col.name, 308 | label: Container( 309 | padding: EdgeInsets.symmetric(horizontal: 16.0), 310 | alignment: Alignment.centerLeft, 311 | child: Text(col.label, overflow: TextOverflow.ellipsis), 312 | ), 313 | width: col.allowResizeWidth ? columnWidths[col.name]! : double.nan, 314 | columnWidthMode: col.columnWidthMode, 315 | visible: col.visible, 316 | allowSorting: col.allowSorting, 317 | sortIconPosition: col.sortIconPosition, 318 | filterIconPosition: col.filterIconPosition, 319 | autoFitPadding: col.autoFitPadding, 320 | minimumWidth: col.minimumWidth, 321 | maximumWidth: col.maximumWidth, 322 | allowEditing: col.allowEditing, 323 | allowFiltering: col.allowFiltering, 324 | filterPopupMenuOptions: col.filterPopupMenuOptions, 325 | filterIconPadding: col.filterIconPadding, 326 | ); 327 | }).toList(), 328 | ), 329 | ), 330 | if (this.pagerVisable) 331 | SfDataPager( 332 | pageCount: dataSource.pageCount.value.toDouble(), 333 | availableRowsPerPage: dataSource.pageSizeList, 334 | direction: Axis.horizontal, 335 | onPageNavigationStart: (int pageIndex) {}, 336 | delegate: dataSource, 337 | onPageNavigationEnd: (int pageIndex) { 338 | // 分页切换时滚动到顶部 339 | if (pageIndex >= 1) { 340 | dataGridController.scrollToRow(0); 341 | } 342 | }, 343 | onRowsPerPageChanged: (int? rowsPerPage) async { 344 | // 分页数量改变回调事件 345 | dataSource.onRowsPerPageChanged(rowsPerPage!); 346 | dataSource.updateDataGridDataSource(); 347 | }, 348 | ), 349 | ], 350 | ); 351 | } 352 | 353 | void refresh(bool reset) { 354 | // 刷新 355 | dataSource.isLoading.value = true; 356 | dataGridController.selectedRows.clear(); 357 | // 不能清空该数据,如果当前已设置了搜索条件,需要保留 358 | if (reset) { 359 | dataSource.reset(); 360 | } 361 | dataSource.handleRefresh(); 362 | if (onRefresh != null) { 363 | onRefresh!(); 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /lib/app/modules/components/big_tree/big_tree.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:go_author_client/app/modules/components/dashed_line/dashed_line.dart'; 4 | 5 | import 'big_node.dart'; 6 | 7 | class BigTree extends StatelessWidget { 8 | late BuildContext context; 9 | final double indent = 25; 10 | late EdgeInsetsGeometry titlePadding; 11 | late RxList nodes; 12 | bool checkable; 13 | bool checkedAll = false; 14 | bool verticalLineVisable = false; 15 | bool horizontalLineVisable = false; 16 | // 所有节点的"线性"缓存 17 | late Map nodesMap = {}; 18 | // 半选节点 19 | Set halfCheckedNodes = {}; 20 | // 全选节点 21 | Set fullCheckedNodes = {}; 22 | // 初始化时选中的节点 23 | RxList initCheckedNodes; 24 | 25 | Function(int status, BigNode node, Set halfCheckedNodes, Set fullCheckedNodes)? onChange; 26 | Function(bool expanded, BigNode node)? onExpanded; 27 | 28 | BigTree({ 29 | super.key, 30 | this.titlePadding = const EdgeInsets.symmetric(horizontal: 0), 31 | // 必须是树型结构数据 32 | required this.nodes, 33 | // 构造参数,只需要设置全选节点,初始化过程中会自动将父节点设置为半选 34 | required this.initCheckedNodes, 35 | this.onChange, 36 | this.onExpanded, 37 | this.checkable = false, 38 | this.checkedAll = false, 39 | this.verticalLineVisable = false, 40 | this.horizontalLineVisable = false, 41 | }); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | this.context = context; 46 | nodesToMap(this.nodes); 47 | fullCheckedNodes.addAll(initCheckedNodes); 48 | selectNodeIds(BIGTREE_STATUS_CHECKED_FULL, initCheckedNodes); 49 | return ListView.builder( 50 | itemCount: nodes.length, 51 | itemBuilder: (BuildContext context, int index) { 52 | return buildNode(nodes[index], 0); 53 | }, 54 | ); 55 | } 56 | 57 | bool isSingleNode(BigNode node) { 58 | return node.childs.isEmpty; 59 | } 60 | 61 | void nodesToMap(List nodes) { 62 | for (var node in nodes) { 63 | if (this.checkedAll) { 64 | node.checked!.value = BIGTREE_STATUS_CHECKED_FULL; 65 | } 66 | nodesMap[node.id] = node; 67 | nodesToMap(node.childs); 68 | } 69 | } 70 | 71 | // index为不可展开的菜单项的累计下标 72 | Widget buildNode(BigNode node, int level) { 73 | if (isSingleNode(node)) { 74 | return Obx(() { 75 | return ListTile( 76 | // leading: VerticalDivider(width: 2), 77 | title: Row( 78 | children: [ 79 | // 左侧缩进 80 | if (!verticalLineVisable) SizedBox(width: level * indent), 81 | if (verticalLineVisable) 82 | for (int i = 0; i < level; i++) 83 | // 缩进线 84 | SizedBox( 85 | width: indent, 86 | height: 40.0, 87 | // 实线 88 | // decoration: BoxDecoration( 89 | // border: Border( 90 | // right: BorderSide(width: 0.8, color: Colors.grey), 91 | // top: BorderSide(width: 0), 92 | // bottom: BorderSide(width: 0), 93 | // ), 94 | // ), 95 | // 虚线 96 | child: Align( 97 | alignment: Alignment.centerRight, 98 | child: DashedLine( 99 | axis: Axis.vertical, 100 | dashedWidth: 1, 101 | dashedHeight: 1, 102 | dashedTotalLengthWith: 38, 103 | ), 104 | ), 105 | ), 106 | 107 | // 带缩进线时添加的占位 108 | if (verticalLineVisable) 109 | SizedBox( 110 | width: indent / 2, 111 | child: Align( 112 | alignment: Alignment.centerRight, 113 | // 水平线 114 | child: horizontalLineVisable 115 | ? DashedLine( 116 | axis: Axis.horizontal, 117 | padding: EdgeInsets.fromLTRB(4, 0, 0, 0), 118 | dashedWidth: 2, 119 | dashedHeight: 1, 120 | count: 4, 121 | ) 122 | : null, 123 | ), 124 | ), 125 | 126 | // 展开图标, 这里只是占位符 127 | SizedBox( 128 | width: 24, 129 | child: Icon(Icons.arrow_right), 130 | ), 131 | // 可点击部分, 这里点击复选框图标和标题都是选中,如果希望只点复选框就选中,直接使用Checkbox即可 132 | InkWell( 133 | child: Row( 134 | children: [ 135 | // 选择框, 由于使用了obx, 所以第二个条件设置为永远false 136 | if (this.checkable || node.checked!.value == -1) 137 | node.checked!.value == BIGTREE_STATUS_CHECKED_FULL // 138 | ? Icon(Icons.check_box_outlined) // 139 | : (node.checked!.value == BIGTREE_STATUS_CHECKED_HALF ? Icon(Icons.square_sharp) : Icon(Icons.check_box_outline_blank)), 140 | // 节点名 141 | Padding( 142 | padding: titlePadding, 143 | child: Text(node.title, style: TextStyle(fontSize: 16)), 144 | ), 145 | ], 146 | ), 147 | onTap: () { 148 | int newStatus = node.checked!.value > 0 ? BIGTREE_STATUS_CHECKED_NONE : BIGTREE_STATUS_CHECKED_FULL; 149 | selectNode(newStatus, node); 150 | if (this.onChange != null) { 151 | this.onChange!(newStatus, node, halfCheckedNodes, fullCheckedNodes); 152 | } 153 | }, 154 | ), 155 | ], 156 | ), 157 | ); 158 | }); 159 | } else { 160 | return Obx(() { 161 | return Theme( 162 | data: Theme.of(context).copyWith( 163 | // 去掉展开时的上下横线 164 | dividerColor: Colors.transparent, 165 | // 缩小行距, 目前不支持自定义行距 166 | listTileTheme: ListTileTheme.of(context).copyWith( 167 | dense: true, 168 | minVerticalPadding: 0, 169 | contentPadding: EdgeInsets.all(0), 170 | ), 171 | ), 172 | child: ExpansionTile( 173 | // backgroundColor: node.id % 2 == 0 ? Colors.blue : Colors.green, 174 | // 隐藏右侧箭头图标 175 | trailing: Container(width: 0, height: 0, color: Colors.red), 176 | leading: null, 177 | childrenPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 0), 178 | title: Row( 179 | children: [ 180 | // 左侧缩进 181 | if (!verticalLineVisable) SizedBox(width: level * indent), 182 | if (verticalLineVisable) 183 | for (int i = 0; i < level; i++) 184 | // 缩进线 185 | SizedBox( 186 | width: indent, 187 | height: 40.0, 188 | // 实线 189 | // decoration: BoxDecoration( 190 | // border: Border( 191 | // right: BorderSide(width: 0.8, color: Colors.grey), 192 | // top: BorderSide(width: 0), 193 | // bottom: BorderSide(width: 0), 194 | // ), 195 | // ), 196 | // 虚线 197 | child: Align( 198 | alignment: Alignment.centerRight, 199 | child: DashedLine( 200 | axis: Axis.vertical, 201 | dashedWidth: 1, 202 | dashedHeight: 1, 203 | dashedTotalLengthWith: 38, 204 | ), 205 | ), 206 | ), 207 | 208 | // 带缩进线时添加的占位 209 | if (verticalLineVisable) 210 | SizedBox( 211 | width: indent / 2, 212 | child: Align( 213 | alignment: Alignment.centerRight, 214 | // 水平线 215 | child: horizontalLineVisable && node.parentId > 0 216 | ? DashedLine( 217 | axis: Axis.horizontal, 218 | padding: EdgeInsets.fromLTRB(4, 0, 0, 0), 219 | dashedWidth: 2, 220 | dashedHeight: 1, 221 | count: 4, 222 | ) 223 | : null, 224 | ), 225 | ), 226 | 227 | // 展开图标 228 | SizedBox( 229 | width: 24, 230 | child: !node.expanded!.value ? Icon(Icons.chevron_right) : Icon(Icons.expand_more), 231 | ), 232 | // 可点击部分, 这里点击复选框图标和标题都是选中,如果希望只点复选框就选中,直接使用Checkbox即可 233 | InkWell( 234 | child: Row( 235 | children: [ 236 | // 选择框, 由于使用了obx, 所以第二个条件设置为永远false 237 | if (this.checkable || node.checked!.value == -1) 238 | node.checked!.value == BIGTREE_STATUS_CHECKED_FULL // 239 | ? Icon(Icons.check_box_outlined) // 240 | : (node.checked!.value == BIGTREE_STATUS_CHECKED_HALF ? Icon(Icons.square_sharp) : Icon(Icons.check_box_outline_blank)), 241 | // 节点名 242 | Padding( 243 | padding: titlePadding, 244 | child: Text(node.title, style: TextStyle(fontSize: 16)), 245 | ), 246 | ], 247 | ), 248 | onTap: () { 249 | // 被点击的节点,只会出现未选或全选的状态,半选只有子节点选中状态变化才会出现 250 | int newStatus = node.checked!.value <= BIGTREE_STATUS_CHECKED_HALF ? BIGTREE_STATUS_CHECKED_FULL : BIGTREE_STATUS_CHECKED_NONE; 251 | selectNode(newStatus, node); 252 | if (this.onChange != null) { 253 | this.onChange!(newStatus, node, halfCheckedNodes, fullCheckedNodes); 254 | } 255 | }, 256 | ), 257 | ], 258 | ), 259 | // 控制是否展开的属性 260 | initiallyExpanded: node.expanded!.value, 261 | children: node.childs.map((child) { 262 | return buildNode(child, level + 1); 263 | }).toList(), 264 | onExpansionChanged: (bool expanded) { 265 | node.expanded!.value = expanded; 266 | // 由于ExpansionTile收起时,会将所有嵌套的子ExpansionTile也同时收起 267 | // 所以需要同步子ExpansionTile的属性 268 | syncExpansionTile(expanded, node); 269 | if (this.onExpanded != null) { 270 | this.onExpanded!(expanded, node); 271 | } 272 | }, 273 | ), 274 | ); 275 | }); 276 | } 277 | } 278 | 279 | // 获取子节点选中数量来判断是否是全选或者半选 280 | int getCheckedCount(BigNode node) { 281 | int count = 0; 282 | for (var n in node.childs) { 283 | if (n.checked!.value > BIGTREE_STATUS_CHECKED_NONE) { 284 | count++; 285 | } 286 | } 287 | return count; 288 | } 289 | 290 | // 收起复合节点时同步子复合节点的图标状态 291 | void syncExpansionTile(bool expanded, BigNode node) { 292 | if (expanded) { 293 | return; 294 | } 295 | for (var child in node.childs) { 296 | if (!isSingleNode(child)) { 297 | child.expanded!.value = expanded; 298 | syncExpansionTile(expanded, child); 299 | } 300 | } 301 | } 302 | 303 | // 同步选中状态缓存数组 304 | void syncCheckedCache(BigNode node) { 305 | switch (node.checked!.value) { 306 | case BIGTREE_STATUS_CHECKED_FULL: 307 | fullCheckedNodes.add(node.id); 308 | halfCheckedNodes.remove(node.id); 309 | break; 310 | case BIGTREE_STATUS_CHECKED_HALF: 311 | halfCheckedNodes.add(node.id); 312 | fullCheckedNodes.remove(node.id); 313 | break; 314 | default: 315 | fullCheckedNodes.remove(node.id); 316 | halfCheckedNodes.remove(node.id); 317 | break; 318 | } 319 | } 320 | 321 | // 递归选择子节点 322 | void selectChilds(int status, BigNode node) { 323 | node.checked!.value = status; 324 | syncCheckedCache(node); 325 | for (var child in node.childs) { 326 | child.checked!.value = status != BIGTREE_STATUS_CHECKED_NONE ? BIGTREE_STATUS_CHECKED_FULL : BIGTREE_STATUS_CHECKED_NONE; 327 | syncCheckedCache(child); 328 | if (child.childs.isNotEmpty) { 329 | selectChilds(status, child); 330 | } 331 | } 332 | } 333 | 334 | // 递归选择父节点 335 | void selectParent(int status, BigNode? parent) { 336 | if (parent == null) { 337 | return; 338 | } 339 | // 再来检测是否是半选 340 | int selectCount = getCheckedCount(parent); 341 | if (selectCount <= 0) { 342 | // 未选状态 343 | parent.checked!.value = BIGTREE_STATUS_CHECKED_NONE; 344 | } else if (selectCount >= parent.childs.length) { 345 | // 全选状态 346 | parent.checked!.value = BIGTREE_STATUS_CHECKED_FULL; 347 | } else { 348 | // 半选状态 349 | parent.checked!.value = BIGTREE_STATUS_CHECKED_HALF; 350 | } 351 | syncCheckedCache(parent); 352 | if (parent.parentId > 0) { 353 | selectParent(status, nodesMap[parent.parentId]); 354 | } 355 | } 356 | 357 | // 设置节点选中状态, 包括子节点和父节点的选择状态同步 358 | void selectNode(int status, BigNode node) { 359 | node.checked!.value = status; 360 | syncCheckedCache(node); 361 | // 设置父节点 362 | if (node.parentId > 0) { 363 | selectParent(status, nodesMap[node.parentId]); 364 | } 365 | // 设置子节点 366 | for (BigNode child in node.childs) { 367 | selectChilds(status, child); 368 | } 369 | } 370 | 371 | // 批量选中节点 372 | void selectNodes(int status, List nodes) { 373 | for (var node in nodes) { 374 | selectNode(status, node); 375 | } 376 | } 377 | 378 | // 批量选中节点 379 | void selectNodeIds(int status, List ids) { 380 | for (var id in ids) { 381 | var node = nodesMap[id]; 382 | if (node != null) { 383 | selectNode(status, node); 384 | } 385 | } 386 | } 387 | 388 | List idsToNodes(Set ids) { 389 | List list = []; 390 | for (var id in ids) { 391 | var node = nodesMap[id]; 392 | if (node != null) { 393 | list.add(node); 394 | } 395 | } 396 | return list; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /lib/app/modules/login/login_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:bot_toast/bot_toast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:go_author_client/app/modules/components/dialog/big_alert.dart'; 5 | import 'package:go_author_client/app/utils/local_storage.dart'; 6 | 7 | import 'login_controller.dart'; 8 | 9 | class LoginView extends GetView { 10 | final GlobalKey _formKey = GlobalKey(); 11 | Color _eyeColor = Colors.grey; 12 | final _textFieldFontSize = 16.0; 13 | final _textFieldLabelSize = 18.0; 14 | 15 | final TextEditingController _usernameController = TextEditingController(); 16 | final TextEditingController _passController = TextEditingController(); 17 | 18 | LoginView({Key? key}) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | controller.username.value = _usernameController.text; 23 | controller.password.value = _passController.text; 24 | controller.rememberMe.value = LocalStorage.hasKey("remember-me") ? LocalStorage.get("remember-me") : false; 25 | // 如果当前是需要记住我,且当前没有设置默认值的情况 26 | if (controller.rememberMe.value && _usernameController.text.isEmpty && _passController.text.isEmpty) { 27 | controller.username.value = LocalStorage.get("username").toString(); 28 | _usernameController.text = controller.username.value; 29 | controller.password.value = LocalStorage.get("password").toString(); 30 | _passController.text = controller.password.value; 31 | } 32 | return Scaffold( 33 | key: _formKey, 34 | // backgroundColor: Colors.white, 35 | body: Stack( 36 | fit: StackFit.expand, // 让背景图片占满整个屏幕 37 | children: [ 38 | // 背景图片 39 | // Image.asset( 40 | // "assets/images/login_bg.jpg", // 你的背景图片路径 41 | // fit: BoxFit.cover, // 图片适应方式,可根据需要调整 42 | // ), 43 | ListView( 44 | padding: EdgeInsets.symmetric(horizontal: 0), 45 | children: [ 46 | // Image.asset( 47 | // "assets/images/vector-1.png", 48 | // // width: 413, 49 | // // height: 457, 50 | // ), 51 | SizedBox( 52 | height: 18, 53 | ), 54 | Form( 55 | // 我们这里不自动检验,检验提示会影响输入框样式 56 | // autovalidateMode: AutovalidateMode.onUserInteraction, 57 | child: Padding( 58 | padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20), 59 | child: Column( 60 | textDirection: TextDirection.ltr, 61 | crossAxisAlignment: CrossAxisAlignment.center, 62 | children: [ 63 | Text( 64 | '登录', 65 | style: TextStyle( 66 | color: Color(0xFF755DC1), 67 | fontSize: 27, 68 | fontWeight: FontWeight.w500, 69 | ), 70 | ), 71 | SizedBox( 72 | height: 50, 73 | ), 74 | SizedBox( 75 | width: 320, 76 | child: Column( 77 | children: [ 78 | buildAccountField(context), 79 | SizedBox( 80 | height: 30, 81 | ), 82 | buildPasswordField(context), 83 | SizedBox( 84 | height: 25, 85 | ), 86 | SizedBox( 87 | width: double.infinity, 88 | height: 50, 89 | child: ClipRRect( 90 | borderRadius: const BorderRadius.all(Radius.circular(10)), 91 | child: ElevatedButton( 92 | style: ElevatedButton.styleFrom( 93 | backgroundColor: const Color(0xFF9F7BFF), 94 | ), 95 | child: Text( 96 | '登录', 97 | style: TextStyle( 98 | color: Colors.white, 99 | fontSize: 15, 100 | fontWeight: FontWeight.w500, 101 | ), 102 | ), 103 | onPressed: () { 104 | controller.onLogin(); 105 | }, 106 | ), 107 | ), 108 | ), 109 | SizedBox( 110 | height: 15, 111 | ), 112 | Row( 113 | mainAxisAlignment: MainAxisAlignment.spaceBetween, // 使元素两端对齐 114 | children: [ 115 | InkWell( 116 | onTap: () { 117 | // 处理记住我逻辑 118 | controller.rememberMe.value = !controller.rememberMe.value; 119 | }, 120 | child: Row( 121 | children: [ 122 | Obx( 123 | () => Checkbox( 124 | value: controller.rememberMe.value, 125 | shape: CircleBorder(), 126 | onChanged: (bool? newValue) { 127 | controller.rememberMe.value = newValue!; 128 | }, 129 | ), 130 | ), 131 | Text("记住我"), 132 | ], 133 | ), 134 | ), 135 | InkWell( 136 | onTap: () { 137 | // 同意协议逻辑 138 | controller.agreement.value = !controller.agreement.value; 139 | }, 140 | child: Row( 141 | children: [ 142 | Obx( 143 | () => Checkbox( 144 | value: controller.agreement.value, 145 | shape: CircleBorder(), 146 | onChanged: (bool? newValue) { 147 | controller.agreement.value = newValue!; 148 | }, 149 | ), 150 | ), 151 | Text("同意协议"), 152 | ], 153 | ), 154 | ), 155 | ], 156 | ), 157 | SizedBox( 158 | height: 25, 159 | ), 160 | Row( 161 | children: [ 162 | Text( 163 | '还没有账号?', 164 | style: TextStyle( 165 | color: Color(0xFF837E93), 166 | fontSize: _textFieldFontSize, 167 | fontWeight: FontWeight.w500, 168 | ), 169 | ), 170 | SizedBox( 171 | width: 2.5, 172 | ), 173 | InkWell( 174 | onTap: () { 175 | // widget.controller.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.ease); 176 | }, 177 | child: Text( 178 | '注册', 179 | style: TextStyle( 180 | color: Color(0xFF755DC1), 181 | fontSize: _textFieldFontSize, 182 | fontWeight: FontWeight.w500, 183 | ), 184 | ), 185 | ), 186 | ], 187 | ), 188 | SizedBox( 189 | height: 8, 190 | ), 191 | Row( 192 | children: [ 193 | InkWell( 194 | onTap: () {}, 195 | child: Text( 196 | '忘记密码?', 197 | style: TextStyle( 198 | color: Color(0xFF755DC1), 199 | fontSize: _textFieldFontSize, 200 | fontWeight: FontWeight.w500, 201 | ), 202 | ), 203 | ), 204 | ], 205 | ), 206 | SizedBox( 207 | height: 8, 208 | ), 209 | Row( 210 | children: [ 211 | InkWell( 212 | onTap: () { 213 | BigAlert.showConfirm( 214 | context: context, 215 | title: "警告", 216 | content: "确定要清除缓存吗?", 217 | onYesCallback: () { 218 | LocalStorage.clear(); 219 | _usernameController.text = ""; 220 | _passController.text = ""; 221 | controller.username.value = ""; 222 | controller.password.value = ""; 223 | controller.rememberMe.value = false; 224 | BotToast.showText(text: "缓存清除完成"); 225 | }, 226 | ); 227 | }, 228 | child: Text( 229 | '清除缓存?', 230 | style: TextStyle( 231 | color: Color(0xFF755DC1), 232 | fontSize: _textFieldFontSize, 233 | fontWeight: FontWeight.w500, 234 | ), 235 | ), 236 | ), 237 | ], 238 | ), 239 | ], 240 | ), 241 | ), 242 | ], 243 | ), 244 | ), 245 | ), 246 | SizedBox( 247 | height: 36, 248 | ), 249 | ], 250 | ), 251 | ], 252 | ), 253 | ); 254 | } 255 | 256 | Widget buildAccountField(BuildContext context) { 257 | _usernameController.addListener(() { 258 | controller.username.value = _usernameController.text; 259 | }); 260 | return Obx(() { 261 | return TextFormField( 262 | controller: _usernameController, 263 | textAlign: TextAlign.center, 264 | style: TextStyle( 265 | fontSize: _textFieldFontSize, 266 | fontWeight: FontWeight.w400, 267 | ), 268 | // 检验规则 269 | validator: (v) { 270 | if (v == null || v.length < 3) { 271 | return "长度不能小于3位"; 272 | } 273 | // var reg = RegExp(r"1[3-9]\d{9}$"); 274 | // if (!reg.hasMatch(v!)) { 275 | // return '请输入合法的手机号码'; 276 | // } 277 | return null; 278 | }, 279 | onChanged: (value) { 280 | if (value.isNotEmpty) {} 281 | }, 282 | decoration: InputDecoration( 283 | labelText: '账号', 284 | labelStyle: TextStyle( 285 | color: Color(0xFF755DC1), 286 | fontSize: _textFieldLabelSize, 287 | fontWeight: FontWeight.w600, 288 | ), 289 | enabledBorder: OutlineInputBorder( 290 | borderRadius: BorderRadius.all(Radius.circular(10)), 291 | borderSide: BorderSide( 292 | width: 1, 293 | color: Color(0xFF837E93), 294 | ), 295 | ), 296 | focusedBorder: OutlineInputBorder( 297 | borderRadius: BorderRadius.all(Radius.circular(10)), 298 | borderSide: BorderSide( 299 | width: 1, 300 | color: Color(0xFF9F7BFF), 301 | ), 302 | ), 303 | // 空图标占位,保持两个输入框文本对齐 304 | suffixIcon: controller.username.value.isEmpty 305 | ? Icon(Icons.clear_sharp, color: Colors.transparent) 306 | : IconButton( 307 | icon: Icon(Icons.clear_sharp), 308 | // color: Colors.transparent, 309 | onPressed: () { 310 | controller.username.value = ""; 311 | _usernameController.text = ""; 312 | }, 313 | ), 314 | ), 315 | ); 316 | }); 317 | } 318 | 319 | Widget buildPasswordField(BuildContext context) { 320 | _passController.addListener(() { 321 | controller.password.value = _passController.text; 322 | }); 323 | return Obx(() { 324 | return TextFormField( 325 | controller: _passController, 326 | textAlign: TextAlign.center, 327 | style: TextStyle( 328 | fontSize: _textFieldFontSize, 329 | fontWeight: FontWeight.w400, 330 | ), 331 | validator: (v) { 332 | if (v == null || v.length < 3) { 333 | return "长度不能小于3位"; 334 | } 335 | return null; 336 | }, 337 | obscureText: controller.isObscure.value, // 是否显示文字 338 | decoration: InputDecoration( 339 | labelText: '密码', 340 | labelStyle: TextStyle( 341 | color: Color(0xFF755DC1), 342 | fontSize: _textFieldLabelSize, 343 | fontWeight: FontWeight.w600, 344 | ), 345 | enabledBorder: OutlineInputBorder( 346 | borderRadius: BorderRadius.all(Radius.circular(10)), 347 | borderSide: BorderSide( 348 | width: 1, 349 | color: Color(0xFF837E93), 350 | ), 351 | ), 352 | focusedBorder: OutlineInputBorder( 353 | borderRadius: BorderRadius.all(Radius.circular(10)), 354 | borderSide: BorderSide( 355 | width: 1, 356 | color: Color(0xFF9F7BFF), 357 | ), 358 | ), 359 | suffixIcon: IconButton( 360 | icon: Icon( 361 | controller.isObscure.value ? Icons.visibility_off : Icons.visibility, 362 | color: _eyeColor, 363 | ), 364 | onPressed: () { 365 | // 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState() 366 | controller.isObscure.value = !controller.isObscure.value; 367 | _eyeColor = (controller.isObscure.value ? Colors.grey : Theme.of(context).iconTheme.color)!; 368 | }, 369 | ), 370 | ), 371 | ); 372 | }); 373 | } 374 | } 375 | --------------------------------------------------------------------------------