diff --git a/ln_jq_app/assets/images/android_apk_img.png b/ln_jq_app/assets/images/android_apk_img.png new file mode 100644 index 0000000..ef191ca Binary files /dev/null and b/ln_jq_app/assets/images/android_apk_img.png differ diff --git a/ln_jq_app/assets/images/bg_login.png b/ln_jq_app/assets/images/bg_login.png new file mode 100644 index 0000000..2ebbed0 Binary files /dev/null and b/ln_jq_app/assets/images/bg_login.png differ diff --git a/ln_jq_app/assets/images/bg_map@2x.png b/ln_jq_app/assets/images/bg_map@2x.png new file mode 100644 index 0000000..4e53864 Binary files /dev/null and b/ln_jq_app/assets/images/bg_map@2x.png differ diff --git a/ln_jq_app/assets/images/ic_car@2x.png b/ln_jq_app/assets/images/ic_car@2x.png new file mode 100644 index 0000000..986316b Binary files /dev/null and b/ln_jq_app/assets/images/ic_car@2x.png differ diff --git a/ln_jq_app/assets/images/ic_car_bg@2x.png b/ln_jq_app/assets/images/ic_car_bg@2x.png new file mode 100644 index 0000000..dcfe4fb Binary files /dev/null and b/ln_jq_app/assets/images/ic_car_bg@2x.png differ diff --git a/ln_jq_app/assets/images/ic_car_select@2x.png b/ln_jq_app/assets/images/ic_car_select@2x.png new file mode 100644 index 0000000..c22b9d2 Binary files /dev/null and b/ln_jq_app/assets/images/ic_car_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_h2@2x.png b/ln_jq_app/assets/images/ic_h2@2x.png new file mode 100644 index 0000000..0fac22d Binary files /dev/null and b/ln_jq_app/assets/images/ic_h2@2x.png differ diff --git a/ln_jq_app/assets/images/ic_h2_my@2x.png b/ln_jq_app/assets/images/ic_h2_my@2x.png new file mode 100644 index 0000000..a3bde44 Binary files /dev/null and b/ln_jq_app/assets/images/ic_h2_my@2x.png differ diff --git a/ln_jq_app/assets/images/ic_h2_my_select@2x.png b/ln_jq_app/assets/images/ic_h2_my_select@2x.png new file mode 100644 index 0000000..74cb09a Binary files /dev/null and b/ln_jq_app/assets/images/ic_h2_my_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_h2_select@2x.png b/ln_jq_app/assets/images/ic_h2_select@2x.png new file mode 100644 index 0000000..4b1a721 Binary files /dev/null and b/ln_jq_app/assets/images/ic_h2_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_jqz@2x.png b/ln_jq_app/assets/images/ic_jqz@2x.png new file mode 100644 index 0000000..1b991e4 Binary files /dev/null and b/ln_jq_app/assets/images/ic_jqz@2x.png differ diff --git a/ln_jq_app/assets/images/ic_label@2x.png b/ln_jq_app/assets/images/ic_label@2x.png new file mode 100644 index 0000000..4d36fc8 Binary files /dev/null and b/ln_jq_app/assets/images/ic_label@2x.png differ diff --git a/ln_jq_app/assets/images/ic_login_bg@2x.png b/ln_jq_app/assets/images/ic_login_bg@2x.png new file mode 100644 index 0000000..815b394 Binary files /dev/null and b/ln_jq_app/assets/images/ic_login_bg@2x.png differ diff --git a/ln_jq_app/assets/images/ic_logo@2x.png b/ln_jq_app/assets/images/ic_logo@2x.png new file mode 100644 index 0000000..99b6db0 Binary files /dev/null and b/ln_jq_app/assets/images/ic_logo@2x.png differ diff --git a/ln_jq_app/assets/images/ic_logo_unbg@2x.png b/ln_jq_app/assets/images/ic_logo_unbg@2x.png new file mode 100644 index 0000000..233bd9a Binary files /dev/null and b/ln_jq_app/assets/images/ic_logo_unbg@2x.png differ diff --git a/ln_jq_app/assets/images/ic_mall@2x.png b/ln_jq_app/assets/images/ic_mall@2x.png new file mode 100644 index 0000000..ea32836 Binary files /dev/null and b/ln_jq_app/assets/images/ic_mall@2x.png differ diff --git a/ln_jq_app/assets/images/ic_mall_select@2x.png b/ln_jq_app/assets/images/ic_mall_select@2x.png new file mode 100644 index 0000000..bb83031 Binary files /dev/null and b/ln_jq_app/assets/images/ic_mall_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_map@2x.png b/ln_jq_app/assets/images/ic_map@2x.png new file mode 100644 index 0000000..de99391 Binary files /dev/null and b/ln_jq_app/assets/images/ic_map@2x.png differ diff --git a/ln_jq_app/assets/images/ic_map_select@2x.png b/ln_jq_app/assets/images/ic_map_select@2x.png new file mode 100644 index 0000000..bd7a2ee Binary files /dev/null and b/ln_jq_app/assets/images/ic_map_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_no_data@2x.png b/ln_jq_app/assets/images/ic_no_data@2x.png new file mode 100644 index 0000000..f72c871 Binary files /dev/null and b/ln_jq_app/assets/images/ic_no_data@2x.png differ diff --git a/ln_jq_app/assets/images/ic_pj@2x.png b/ln_jq_app/assets/images/ic_pj@2x.png new file mode 100644 index 0000000..6e42e64 Binary files /dev/null and b/ln_jq_app/assets/images/ic_pj@2x.png differ diff --git a/ln_jq_app/assets/images/ic_px@2x.png b/ln_jq_app/assets/images/ic_px@2x.png new file mode 100644 index 0000000..9233dec Binary files /dev/null and b/ln_jq_app/assets/images/ic_px@2x.png differ diff --git a/ln_jq_app/assets/images/ic_user@2x.png b/ln_jq_app/assets/images/ic_user@2x.png new file mode 100644 index 0000000..ee5a716 Binary files /dev/null and b/ln_jq_app/assets/images/ic_user@2x.png differ diff --git a/ln_jq_app/assets/images/ic_user_logo@2x.png b/ln_jq_app/assets/images/ic_user_logo@2x.png new file mode 100644 index 0000000..182717f Binary files /dev/null and b/ln_jq_app/assets/images/ic_user_logo@2x.png differ diff --git a/ln_jq_app/assets/images/ic_user_select@2x.png b/ln_jq_app/assets/images/ic_user_select@2x.png new file mode 100644 index 0000000..fcfc538 Binary files /dev/null and b/ln_jq_app/assets/images/ic_user_select@2x.png differ diff --git a/ln_jq_app/assets/images/ic_wz@2x.png b/ln_jq_app/assets/images/ic_wz@2x.png new file mode 100644 index 0000000..937c2ab Binary files /dev/null and b/ln_jq_app/assets/images/ic_wz@2x.png differ diff --git a/ln_jq_app/assets/images/welcome.png b/ln_jq_app/assets/images/welcome.png new file mode 100644 index 0000000..3a2d95e Binary files /dev/null and b/ln_jq_app/assets/images/welcome.png differ diff --git a/ln_jq_app/lib/common/login_util.dart b/ln_jq_app/lib/common/login_util.dart index b9e0a5e..6df4527 100644 --- a/ln_jq_app/lib/common/login_util.dart +++ b/ln_jq_app/lib/common/login_util.dart @@ -1,4 +1,5 @@ import 'package:encrypt/encrypt.dart'; +import 'package:flutter/material.dart' as ui; class LoginUtil { static final _keyString = '915eae87951a448c86c47796e44c1fcf'; @@ -26,5 +27,9 @@ class LoginUtil { final decrypted = _encrypter.decrypt(encrypted); return decrypted; } + + static ui.Image getAssImg(String imgName){ + return ui.Image(image: ui.AssetImage('assets/images/$imgName.png'),fit: ui.BoxFit.cover,); + } } diff --git a/ln_jq_app/lib/common/styles/theme.dart b/ln_jq_app/lib/common/styles/theme.dart index 767c678..7d7d951 100644 --- a/ln_jq_app/lib/common/styles/theme.dart +++ b/ln_jq_app/lib/common/styles/theme.dart @@ -5,7 +5,7 @@ class AppTheme { static const String Font_YuYang = 'YuYang'; - static const Color themeColor = Color(0xFF0c83c3); + static const Color themeColor = Color(0xFF017137); //是否开放域名切换 static const bool is_show_host = true; diff --git a/ln_jq_app/lib/main.dart b/ln_jq_app/lib/main.dart index 1a5f1d1..5872e63 100644 --- a/ln_jq_app/lib/main.dart +++ b/ln_jq_app/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:get_storage/get_storage.dart'; @@ -8,8 +9,8 @@ import 'package:ln_jq_app/storage_service.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'common/styles/theme.dart'; -import 'pages/home/view.dart'; import 'pages/login/view.dart'; +import 'pages/welcome/view.dart'; // 引入启动页 void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -17,8 +18,10 @@ void main() async { WidgetsBinding widgetsBinding = await init( isDebug: true, logTag: '小羚羚', - supportedLocales: [Locale('zh', 'CN')], + supportedLocales: [const Locale('zh', 'CN')], ); + + // 保持原生闪屏页,直到 WelcomeController 调用 remove() FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await GetStorage.init(); @@ -42,22 +45,17 @@ void main() async { darkTheme: AppTheme.light, // AppTitle title: '小羚羚', - // 首页入口 - home: HomePage(), - //组件国际化 - fallbackLocale: Locale('zh', 'CN'), - supportedLocales: [Locale('zh', 'CN')], + // 将入口改为启动页 + home: const WelcomePage(), + fallbackLocale: const Locale('zh', 'CN'), + supportedLocales: const [Locale('zh', 'CN')], localizationsDelegates: const [ - //pull_to_refresh RefreshLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - - // Builder builder: (context, widget) { - // do something.... return widget!; }, ), @@ -67,20 +65,16 @@ void main() async { void initHttpSet() { AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url; - // 设置基础 URL HttpService.to.setBaseUrl(AppTheme.test_service_url); - //指定请求头 HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token')); - // 设置全局响应处理器 HttpService.to.setOnResponseHandler((response) async { try { final baseModel = BaseModel.fromJson(response.data); if (baseModel.code == 0 || baseModel.code == 200) { - return null; } else if (baseModel.code == 401) { await StorageService.to.clearLoginInfo(); - Get.offAll(() => LoginPage()); + Get.offAll(() => const LoginPage()); return baseModel.message; } else { return (baseModel.error.toString()).isEmpty diff --git a/ln_jq_app/lib/pages/b_page/base_widgets/view.dart b/ln_jq_app/lib/pages/b_page/base_widgets/view.dart index 931f44e..db7a186 100644 --- a/ln_jq_app/lib/pages/b_page/base_widgets/view.dart +++ b/ln_jq_app/lib/pages/b_page/base_widgets/view.dart @@ -1,22 +1,25 @@ import 'package:flutter/material.dart'; import 'package:getx_scaffold/common/index.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/pages/b_page/base_widgets/controller.dart'; import 'package:ln_jq_app/pages/b_page/reservation/view.dart'; import 'package:ln_jq_app/pages/b_page/site/view.dart'; class B_BaseWidgetsPage extends GetView { - B_BaseWidgetsPage({super.key}); + B_BaseWidgetsPage({super.key}); final PageController _pageController = PageController(); + // 主视图 Widget _buildView() { return PageView( controller: _pageController, + physics: const NeverScrollableScrollPhysics(), // 禁止滑动 onPageChanged: (index) { jumpTabAndPage(index); }, - children: _buildPages(), // 页面的列表 + children: _buildPages(), ); } @@ -25,33 +28,59 @@ class B_BaseWidgetsPage extends GetView { controller.updateUi(); // 更新 UI _pageController.jumpToPage(controller.pageIndex); } + // 对应的页面 List _buildPages() { - return [ - SitePage(), - ReservationPage(), - ]; + return [SitePage(), ReservationPage()]; } - //导航栏 + // 自定义导航栏 (悬浮胶囊样式) Widget _buildNavigationBar() { - return NavigationX( - currentIndex: controller.pageIndex, // 当前选中的tab索引 - onTap: (index) { - jumpTabAndPage(index); - }, // 切换tab事件 - items: [ - NavigationItemModel( - label: '加氢预约', - icon: AntdIcon.orderedlist, - selectedIcon: AntdIcon.calendar_fill, + return SafeArea( + child: Container( + height: 50.h, + margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距 + decoration: BoxDecoration( + color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景 + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 5), + ), + ], ), - NavigationItemModel( - label: '站点信息', - icon: AntdIcon.car, - selectedIcon: AntdIcon.car_fill, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"), + _buildNavItem(1, "ic_h2_my@2x", "ic_h2_my_select@2x"), + ], ), - ], + ), + ); + } + + // 构建单个导航项 + Widget _buildNavItem(int index, String icon, String selectedIcon) { + bool isSelected = controller.pageIndex == index; + return GestureDetector( + onTap: () => jumpTabAndPage(index), + behavior: HitTestBehavior.opaque, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: EdgeInsets.symmetric(horizontal: 50.w, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFF006633) : Colors.transparent, // 选中时的深绿色背景 + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + height: 24, + width: 24, + child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon), + ), + ), ); } diff --git a/ln_jq_app/lib/pages/b_page/reservation/controller.dart b/ln_jq_app/lib/pages/b_page/reservation/controller.dart index 5f7b158..e28000d 100644 --- a/ln_jq_app/lib/pages/b_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/b_page/reservation/controller.dart @@ -45,6 +45,7 @@ class ReservationController extends GetxController with BaseControllerMixin { customStartTime = DateTime.now(); customEndTime = customStartTime!.add(const Duration(days: 1)); renderData(); + _msgNotice(); // 红点消息 startAutoRefresh(); } @@ -94,6 +95,7 @@ class ReservationController extends GetxController with BaseControllerMixin { String jobDetailsStr = ""; String jobId = ""; Timer? _refreshTimer; + bool isNotice = false; Future renderData() async { showLoading("加载中"); @@ -168,7 +170,7 @@ class ReservationController extends GetxController with BaseControllerMixin { } jobDetailsStr = - "当前站点已设置$beginTime至$endTime,共${hoursLeft.toStringAsFixed(2)}小时,为$hydStatusStr状态"; + "当前站点已设置$beginTime至$endTime,共${hoursLeft.toStringAsFixed(2)}小时,为$hydStatusStr状态"; // 如果是处于非营运状态,自动回填开始和结束时间 // 假设 customStartTime 是现在,customEndTime 是接口返回的结束时间 @@ -210,7 +212,7 @@ class ReservationController extends GetxController with BaseControllerMixin { var customerPriceTemp = result.data["customerPrice"]; customerPrice = - (customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty) + (customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty) ? "$customerPriceTemp" : "暂无价格"; @@ -246,6 +248,26 @@ class ReservationController extends GetxController with BaseControllerMixin { } } + Future _msgNotice() async { + final Map requestData = { + 'appFlag': 1, + 'isRead': 1, + 'pageNum': 1, + 'pageSize': 5, + }; + final response = await HttpService.to.get( + 'appointment/unread_notice/page', + params: requestData, + ); + if (response != null) { + final result = BaseModel.fromJson(response.data); + if (result.code == 0 && result.data != null) { + String total = result.data["total"].toString(); + isNotice = int.parse(total) > 0; + } + } + } + void onOperationStatusChanged(String? newValue) { if (newValue != null) { selectedOperationStatus = newValue; diff --git a/ln_jq_app/lib/pages/b_page/reservation/view.dart b/ln_jq_app/lib/pages/b_page/reservation/view.dart index aedac93..0c9d803 100644 --- a/ln_jq_app/lib/pages/b_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/b_page/reservation/view.dart @@ -1,11 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:flutter/services.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/pages/b_page/reservation/controller.dart'; +import 'package:ln_jq_app/pages/c_page/message/view.dart'; class ReservationPage extends GetView { const ReservationPage({super.key}); + // 定义主题色 + static const kPrimaryColor = Color(0xFF006D35); // 效果图深绿色 + static const kBgColor = Color(0xFFF5F7F9); // 背景灰 + @override Widget build(BuildContext context) { return GetBuilder( @@ -13,21 +19,28 @@ class ReservationPage extends GetView { id: 'b_reservation', builder: (_) { return Scaffold( + backgroundColor: kBgColor, body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeaderCard(), - const SizedBox(height: 12), - _buildInfoFormCard(context), - const SizedBox(height: 12), - _buildTipsCard(), - const SizedBox(height: 12), - _buildLogoutButton(), - ], - ), + child: Column( + children: [ + _buildTopSection(context), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + children: [ + SizedBox(height: 16), + _buildBasicInfoCard(), + SizedBox(height: 16), + _buildOperationContentCard(context), + SizedBox(height: 16.h), + _buildSystemTips(), + SizedBox(height: 24), + _buildLogoutButton(), + SizedBox(height: 40), + ], + ), + ), + ], ), ), ); @@ -35,158 +48,280 @@ class ReservationPage extends GetView { ); } - /// 构建顶部的站点信息头卡片 - Widget _buildHeaderCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + /// 1. 顶部个人信息及统计栏 + Widget _buildTopSection(BuildContext context) { + return Container( + width: double.infinity, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)), + ), + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top + 10, + left: 20, + right: 20, + bottom: 25, + ), child: Column( children: [ - ListTile( - leading: const Icon(Icons.local_gas_station, color: Colors.blue, size: 40), - title: Text( - controller.name, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), - ), - subtitle: Text(controller.address), - trailing: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: Colors.blue[100], - borderRadius: BorderRadius.circular(12), + Row( + children: [ + CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + child: LoginUtil.getAssImg('ic_user_logo@2x'), ), - child: Text( - controller.selectedOperationStatus, - style: const TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - fontSize: 12, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + controller.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + _buildStatusTag(), + ], + ), + const SizedBox(height: 4), + Text( + "站点:${controller.address}", + style: TextStyle(color: Colors.grey[500], fontSize: 13), + ), + ], ), ), - ), - ), - const Divider(height: 1, indent: 16, endIndent: 16), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildHeaderStat(controller.customerPrice, '氢气价格'), - _buildHeaderStat(controller.timeStr, '营业时间'), - _buildHeaderStat('98%', '设备状态'), - ], - ), - ), - ], - ), - ); - } - - /// 构建头部卡片中的单个统计项 - Widget _buildHeaderStat(String value, String label) { - return Column( - children: [ - Text( - value, - style: const TextStyle( - color: Colors.blue, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), - ], - ); - } - - /// 构建包含所有信息表单的卡片(增加 Tab 切换功能) - Widget _buildInfoFormCard(BuildContext context) { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - clipBehavior: Clip.antiAlias, // 确保 Tab 背景圆角生效 - child: Column( - children: [ - // Tab 切换栏 - Obx( - () => Container( - color: Colors.grey[50], - child: Row( - children: [ - _buildTabItem(0, Icons.business_outlined, '站点信息'), - _buildTabItem(1, Icons.campaign_outlined, '站点广播'), - ], - ), - ), - ), - const Divider(height: 1), - // 内容区域 - Obx( - () => controller.selectedTabIndex.value == 0 - ? _buildStationInfo(context) - : _buildStationBroadcast(context), - ), - ], - ), - ); - } - - /// 构建单个 Tab 项 - Widget _buildTabItem(int index, IconData icon, String label) { - bool isSelected = controller.selectedTabIndex.value == index; - return Expanded( - child: InkWell( - onTap: () => controller.selectedTabIndex.value = index, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 14), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: isSelected ? Colors.blue : Colors.transparent, - width: 2, - ), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: 20, color: isSelected ? Colors.blue : Colors.grey[600]), - const SizedBox(width: 8), - Text( - label, - style: TextStyle( - fontSize: 15, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? Colors.blue : Colors.grey[600], + IconButton( + onPressed: () { + Get.to(() => const MessagePage()); + }, + style: IconButton.styleFrom( + backgroundColor: Colors.grey[100], + padding: const EdgeInsets.all(8), + ), + icon: Badge( + smallSize: 8, + backgroundColor: controller.isNotice + ? Colors.red + : Colors.transparent, + child: const Icon( + Icons.notifications_outlined, + color: Colors.black87, + size: 30, + ), ), ), ], ), + const SizedBox(height: 25), + Row( + children: [ + _buildStatBox("氢气价格", "Hydrogen price", controller.customerPrice, "/kg"), + SizedBox(width: 4.w), + _buildStatBox("营业时间", "Opening time", controller.timeStr, ""), + SizedBox(width: 4.w), + _buildStatBox("设备状态", "Anlagenzustand", "98", "%"), + ], + ), + ], + ), + ); + } + + Widget _buildStatusTag() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFFE1F5FE), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + controller.selectedOperationStatus, + style: TextStyle( + color: Color(0xFF03A9F4), + fontSize: 12.sp, + fontWeight: FontWeight.w600, ), ), ); } - /// 站点信息子视图 - Widget _buildStationInfo(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), + Widget _buildStatBox(String title, String enTitle, String value, String unit) { + return Expanded( + child: Container( + padding: EdgeInsets.only(left: 12.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration( + color: kBgColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 12.sp, + color: Color.fromRGBO(51, 51, 51, 0.8), + fontWeight: FontWeight.w400, + ), + ), + Text(enTitle, style: const TextStyle(fontSize: 9, color: Colors.grey)), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + value, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: Color(0xFF333333), + ), + ), + const SizedBox(width: 2), + Text(unit, style: const TextStyle(fontSize: 11, color: Colors.grey)), + ], + ), + ], + ), + ), + ); + } + + /// 2. 站点基本信息 + Widget _buildBasicInfoCard() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "站点基本信息", + style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold), + ), + SizedBox(height: 15), + _buildInfoRow("站点名称", controller.name), + _buildInfoRow("运营企业", controller.operatingEnterprise), + _buildInfoRow("站点地址", controller.address), + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle(color: Colors.grey, fontSize: 11.sp), + ), + Text( + value, + style: TextStyle( + color: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + /// 3. 运营信息/站点广播 Tab 及内容 + Widget _buildOperationContentCard(BuildContext context) { + return GestureDetector( + onTap: hideKeyboard, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + // 自定义 TabBar + Obx( + () => Padding( + padding: const EdgeInsets.only(left: 16, top: 16), + child: Row( + children: [ + _buildTabTitle(0, "运营信息"), + const SizedBox(width: 30), + _buildTabTitle(1, "站点广播"), + ], + ), + ), + ), + Obx( + () => controller.selectedTabIndex.value == 0 + ? _buildOperatingForm(context) + : _buildBroadcastForm(), + ), + ], + ), + ), + ); + } + + Widget _buildTabTitle(int index, String title) { + bool isSelected = controller.selectedTabIndex.value == index; + return GestureDetector( + onTap: () => controller.selectedTabIndex.value = index, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: isSelected ? Colors.black87 : Colors.grey, + ), + ), + if (isSelected) + Container( + margin: const EdgeInsets.only(top: 4), + width: 25, + height: 3, + decoration: BoxDecoration( + color: const Color(0xFF00A870), // 效果图中的亮绿色横线 + borderRadius: BorderRadius.circular(2), + ), + ), + ], + ), + ); + } + + Widget _buildOperatingForm(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildSectionTitle('基本信息'), - _buildDisplayField(label: '站点名称', value: controller.name), - _buildDisplayField(label: '运营企业', value: controller.operatingEnterprise), - _buildDisplayField(label: '站点地址', value: controller.address), - const SizedBox(height: 16), - _buildSectionTitle('价格信息'), - _buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice), - const SizedBox(height: 16), - _buildSectionTitle('运营信息'), Row( children: [ - Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)), + Text( + '运营状态', + style: TextStyle( + color: Color.fromRGBO(51, 51, 51, 1), + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), //加氢站未执行的状态修改任务 if (controller.jobTipStr.isNotEmpty) GestureDetector( @@ -204,157 +339,113 @@ class ReservationPage extends GetView { ), ], ), - const SizedBox(height: 8), - DropdownButtonFormField( - value: controller.selectedOperationStatus, - items: controller.operationStatusOptions.map((String value) { - return DropdownMenuItem(value: value, child: Text(value)); + const SizedBox(height: 12), + // 状态网格选择 + Wrap( + spacing: 4, + runSpacing: 4, + children: controller.operationStatusOptions.map((status) { + bool isSelected = controller.selectedOperationStatus == status; + return GestureDetector( + onTap: () => controller.onOperationStatusChanged(status), + child: Container( + width: (Get.width - 80) / 2, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: isSelected ? kPrimaryColor : const Color(0xFFEBEBEB), + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Text( + status, + style: TextStyle( + fontSize: 11.sp, + color: isSelected ? Colors.white : Color.fromRGBO(51, 51, 51, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + ); }).toList(), - onChanged: controller.onOperationStatusChanged, - decoration: InputDecoration( - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), - ), ), - const SizedBox(height: 16), + SizedBox(height: 12.h), if (controller.selectedOperationStatus == "营运中") _buildDisplayField(label: '营业时间', value: controller.timeStr) else Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildClickField( - label: '开始时间', - value: controller.customStartTimeStr, - onTap: () => controller.pickDateTime(context, true), + _buildInputLabel("开始时间"), + _buildDateTimePicker( + controller.customStartTimeStr, + () => controller.pickDateTime(context, true), ), - _buildClickField( - label: '结束时间', - value: controller.customEndTimeStr, - onTap: () => controller.pickDateTime(context, false), + const SizedBox(height: 15), + _buildInputLabel("结束时间"), + _buildDateTimePicker( + controller.customEndTimeStr, + () => controller.pickDateTime(context, false), ), + const SizedBox(height: 15), ], ), _buildDisplayField(label: '联系电话', value: controller.phone), - const SizedBox(height: 24), - ElevatedButton( - onPressed: controller.saveInfo, - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 48), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - ), - child: const Text('保存信息', style: TextStyle(fontSize: 16)), - ), - ], - ), - ); - } - - /// 站点广播子视图 - Widget _buildStationBroadcast(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + const SizedBox(height: 25), Row( children: [ - const Icon(Icons.campaign, color: Colors.blue, size: 28), - const SizedBox(width: 10), - const Text( - '站点广播通知', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, + Expanded( + flex: 1, + child: OutlinedButton( + onPressed: () { + controller.renderData(); + }, // 重置逻辑 + style: OutlinedButton.styleFrom( + side: const BorderSide(color: kPrimaryColor), + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text("重置", style: TextStyle(color: kPrimaryColor)), + ), + ), + const SizedBox(width: 15), + Expanded( + flex: 2, + child: ElevatedButton( + onPressed: controller.saveInfo, + style: ElevatedButton.styleFrom( + backgroundColor: kPrimaryColor, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text("保存设置", style: TextStyle(color: Colors.white)), ), ), ], ), - const SizedBox(height: 13), - _buildTextFieldLabel('通知标题'), - const SizedBox(height: 8), - SizedBox( - height: 45.h, - child: TextField( - controller: controller.broadcastTitleController, - maxLength: 30, - decoration: InputDecoration( - hintText: '例如:临时闭站通知', - hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - counterText: '', // 隐藏原生计数器,我们可以按需自定义 - ), - ), - ), - const SizedBox(height: 20), - _buildTextFieldLabel('通知内容'), - const SizedBox(height: 8), - TextField( - controller: controller.broadcastContentController, - maxLength: 150, - maxLines: 5, - decoration: InputDecoration( - hintText: '请输入通知内容...', - hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - ), - ), - const SizedBox(height: 12), - ElevatedButton( - onPressed: controller.sendBroadcast, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - minimumSize: const Size(double.infinity, 50), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - elevation: 0, - ), - child: const Text( - '发送', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - ), - const SizedBox(height: 20), ], ), ); } - Widget _buildTextFieldLabel(String label) { - return Text( - label, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.black87, - ), - ); - } - - /// 构建带标题的表单区域 - Widget _buildSectionTitle(String title) { - return Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Row( - children: [ - Container(width: 4, height: 16, color: Colors.blue), - const SizedBox(width: 8), - Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - ], - ), - ); - } - - /// 构建一个“标签+纯文本”的显示行 Widget _buildDisplayField({required String label, required String value}) { return Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)), + Text( + label, + style: TextStyle( + color: Color.fromRGBO(51, 51, 51, 1), + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), const SizedBox(height: 8), Container( width: double.infinity, @@ -374,118 +465,177 @@ class ReservationPage extends GetView { ); } - /// 构建一个“可点击”的选择行 - Widget _buildClickField({ - required String label, - required String value, - required VoidCallback onTap, - }) { + Widget _buildBroadcastForm() { return Padding( - padding: const EdgeInsets.only(bottom: 12.0), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)), - const SizedBox(height: 8), - InkWell( - onTap: onTap, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.0), - border: Border.all(color: Colors.blue.withOpacity(0.5)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - value, - style: const TextStyle(fontSize: 14, color: Colors.black87), - ), - const Icon(Icons.calendar_month, size: 18, color: Colors.blue), - ], - ), - ), + _buildInputLabel("通知标题"), + TextField( + controller: controller.broadcastTitleController, + decoration: _inputDecoration("例如:临时闭站通知"), ), + const SizedBox(height: 15), + _buildInputLabel("通知内容"), + TextField( + controller: controller.broadcastContentController, + maxLines: 4, + decoration: _inputDecoration("请输入通知内容..."), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + flex: 1, + child: OutlinedButton( + onPressed: () { + controller.broadcastTitleController.clear(); + controller.broadcastContentController.clear(); + }, // 重置逻辑 + style: OutlinedButton.styleFrom( + side: const BorderSide(color: kPrimaryColor), + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text("重置", style: TextStyle(color: kPrimaryColor)), + ), + ), + const SizedBox(width: 15), + Expanded( + flex: 2, + child: ElevatedButton( + onPressed: controller.sendBroadcast, + style: ElevatedButton.styleFrom( + backgroundColor: kPrimaryColor, + minimumSize: const Size(double.infinity, 50), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + child: const Text("发送广播", style: TextStyle(color: Colors.white)), + ), + ), + ], + ), + ], ), ); } - /// 构建静态提示信息卡片 - Widget _buildTipsCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( + Widget _buildInputLabel(String label) { + return Padding( + padding: const EdgeInsets.only(left: 0, bottom: 8), + child: Text( + label, + style: TextStyle( + color: Color.fromRGBO(51, 51, 51, 1), + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + Widget _buildDateTimePicker(String value, VoidCallback onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xFF00A870).withOpacity(0.5)), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildInfoItem(Icons.info_outline, '请确保信息准确无误'), - const SizedBox(height: 10), - _buildInfoItem(Icons.help_outline, '价格信息将实时更新到用户端'), - const SizedBox(height: 10), - _buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系技术支持: 400-021-1773'), - const SizedBox(height: 10), - Row( - children: [ - const Icon(Icons.verified_outlined, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: FutureBuilder( - future: getVersion(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Text(""); - } - if (snapshot.hasData) { - return TextX.labelSmall( - "当前版本: ${snapshot.data}", - color: Colors.black54, - ); - } - return const Text(""); - }, - ), - ), - ], - ), + Text(value, style: const TextStyle(color: Colors.black87)), + const Icon(Icons.calendar_today_outlined, size: 18, color: Color(0xFF00A870)), ], ), ), ); } - /// 构建退出登录按钮 - Widget _buildLogoutButton() { - return ElevatedButton( - onPressed: controller.logout, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - minimumSize: const Size(double.infinity, 48), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), - elevation: 2, + InputDecoration _inputDecoration(String hint) { + return InputDecoration( + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey, fontSize: 14), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFE0E0E0)), ), - child: const Text( - '退出登录', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: Color(0xFFE0E0E0)), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12), + ); + } + + /// 4. 系统提醒 + Widget _buildSystemTips() { + return Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + color: const Color(0xFFF1F9F6), // 极浅绿色背景 + borderRadius: BorderRadius.circular(10), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: Color.fromRGBO(1, 113, 55, 1), size: 20), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "系统提醒", + style: TextStyle( + color: Color.fromRGBO(1, 113, 55, 1), + fontWeight: FontWeight.bold, + fontSize: 14.sp, + ), + ), + SizedBox(height: 6.h), + Text( + "请您确保所提供的信息准确无误,价格信息也将实时\n更新至用户端", + style: TextStyle(color: Color.fromRGBO(1, 113, 55, 0.8), fontSize: 12.sp), + ), + SizedBox(height: 6.h), + Text( + "如有疑问请联系客服:400-021-1773", + style: TextStyle(color: Color.fromRGBO(1, 113, 55, 0.8), fontSize: 12.sp), + ), + ], + ), + ], ), ); } - /// 构建带图标的提示信息行 - Widget _buildInfoItem(IconData icon, String text) { - return Row( - children: [ - Icon(icon, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: Text(text, style: const TextStyle(fontSize: 14, color: Colors.black54)), + /// 5. 退出登录按钮 + Widget _buildLogoutButton() { + return SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: controller.logout, + style: ElevatedButton.styleFrom( + backgroundColor: Color.fromRGBO(204, 52, 46, 1), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), + elevation: 0, ), - ], + child: const Text( + "退出登录", + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), ); } } diff --git a/ln_jq_app/lib/pages/b_page/site/controller.dart b/ln_jq_app/lib/pages/b_page/site/controller.dart index 63e6ddc..7ade505 100644 --- a/ln_jq_app/lib/pages/b_page/site/controller.dart +++ b/ln_jq_app/lib/pages/b_page/site/controller.dart @@ -143,6 +143,7 @@ class SiteController extends GetxController with BaseControllerMixin { Timer? _refreshTimer; final TextEditingController searchController = TextEditingController(); + bool isNotice = false; @override bool get listenLifecycleEvent => true; @@ -167,7 +168,7 @@ class SiteController extends GetxController with BaseControllerMixin { searchController.dispose(); super.onClose(); } - bool isNotice = false; + Future msgNotice() async { final Map requestData = { 'appFlag': 1, diff --git a/ln_jq_app/lib/pages/b_page/site/view.dart b/ln_jq_app/lib/pages/b_page/site/view.dart index 0ee8bf7..b220993 100644 --- a/ln_jq_app/lib/pages/b_page/site/view.dart +++ b/ln_jq_app/lib/pages/b_page/site/view.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/common/styles/theme.dart'; import 'package:ln_jq_app/pages/b_page/history/view.dart'; -import 'package:ln_jq_app/pages/c_page/message/view.dart' show MessagePage; +import 'package:ln_jq_app/pages/c_page/message/view.dart'; import 'controller.dart'; @@ -16,263 +17,330 @@ class SitePage extends GetView { init: SiteController(), id: 'site', builder: (_) { - return SingleChildScrollView(child: _buildView()); + return Scaffold( + backgroundColor: Color.fromRGBO(247, 249, 251, 1), + body: SingleChildScrollView(child: _buildView(context)), + ); }, ); } // 主视图 - Widget _buildView() { - return Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // 第一个卡片: 今日预约统计 - Card( - elevation: 3, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), - margin: const EdgeInsets.only(bottom: 12), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: Column( + Widget _buildView(BuildContext context) { + return Column( + children: [ + // 第一个卡片: 今日预约统计 + _buildTopSection(context), + Padding( + padding: EdgeInsets.only(left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: EdgeInsets.only(top: 17.h, bottom: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "预约信息", + style: TextStyle( + color: Color.fromRGBO(51, 51, 51, 1), + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ), + GestureDetector( + onTap: () { + Get.to( + () => HistoryPage(), + arguments: {'stationName': controller.name}, + ); + }, + child: Text( + '历史记录', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(156, 163, 175, 1), + ), + ), + ), + ], + ), + ), + // 第二个卡片: 预约信息 + Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - children: [ - const Icon(Icons.calendar_today, color: Colors.blue, size: 32), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '今日预约统计', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(width: 5.w,), - Container( - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - '实时', - style: TextStyle( - color: Colors.blue, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - Text( - "Today's Reservation Statistics", - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - ], - ), - ), - IconButton( - onPressed: () async { - // 跳转消息中心 - var scanResult = await Get.to(() => const MessagePage()); - if (scanResult == null) { - controller.msgNotice(); - } - }, - // 这里的 style 是为了模拟你图片里的灰色圆形背景 - style: IconButton.styleFrom( - backgroundColor: Colors.grey[100], - padding: const EdgeInsets.all(8), - ), - icon: Badge( - // label: Text('3'), // 如果你想显示数字,就加 label - smallSize: 8, - // 红点的大小 - backgroundColor: controller.isNotice - ? Colors.red - : Colors.white, - // 红点颜色 - child: Icon( - Icons.notifications_outlined, - color: Colors.black87, - size: 25, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 10), - const Divider(height: 1, indent: 16, endIndent: 16), - const SizedBox(height: 12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildStatItem(controller.leftHydrogen, '剩余余量'), - _buildStatItem(controller.orderAmount, '预约车辆'), - _buildStatItem(controller.completedAmount, '已完成'), - ], - ), - ), - const SizedBox(height: 12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildStatItem(controller.orderTotalAmount, '加氢总量'), - _buildStatItem(controller.orderUnfinishedAmount, '未加氢总量'), - ], - ), - ), + _buildSearchView(), + controller.hasReservationData + ? _buildReservationListView() + : _buildEmptyReservationView(), ], ), - ), - ), - // 第二个卡片: 预约信息 - Card( - elevation: 3, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), - margin: EdgeInsets.only(bottom: 12), - clipBehavior: Clip.antiAlias, - child: Column( - children: [ - Container( - color: Colors.blue, - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - child: Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () { - controller.renderData(); - }, - child: Row( - children: [ - Text( - '预约信息', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - SizedBox( - width: 32, - height: 32, - child: const Icon( - Icons.refresh, - size: 18, - color: Colors.white, - ), - ), - ], + SizedBox(height: 35.h), + //第三部分 + Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + color: const Color(0xFFF1F9F6), // 极浅绿色背景 + borderRadius: BorderRadius.circular(10), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.info_outline, + color: Color.fromRGBO(1, 113, 55, 1), + size: 20, + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "系统提醒", + style: TextStyle( + color: Color.fromRGBO(1, 113, 55, 1), + fontWeight: FontWeight.bold, + fontSize: 14.sp, ), ), - ), - ElevatedButton( - onPressed: () { - Get.to( - () => HistoryPage(), - arguments: {'stationName': controller.name}, - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue.shade700, - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), + SizedBox(height: 6.h), + Text( + "数据每五分钟自动刷新,如需实时更新请下拉页面", + style: TextStyle( + color: Color.fromRGBO(1, 113, 55, 0.8), + fontSize: 12.sp, ), - elevation: 2, ), - child: const Text( - '历史记录', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + SizedBox(height: 6.h), + Text( + "如有疑问请联系客服:400-021-1773", + style: TextStyle( + color: Color.fromRGBO(1, 113, 55, 0.8), + fontSize: 12.sp, + ), ), - ), - ], + ], + ), + ], + ), + ), + SizedBox(height: 35.h), + ], + ), + ), + ], + ); + } + + Widget _buildTopSection(BuildContext context) { + return Container( + width: double.infinity, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)), + ), + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top + 10, + left: 20, + right: 20, + bottom: 25, + ), + child: Column( + children: [ + Row( + children: [ + CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + child: LoginUtil.getAssImg('ic_user_logo@2x'), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "今日预约统计", + style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w400), + ), + const SizedBox(width: 8), + ], + ), + const SizedBox(height: 4), + Text( + "Today's Reservation Statistics", + style: TextStyle(color: Colors.grey[500], fontSize: 13), + ), + ], + ), + ), + IconButton( + onPressed: () { + Get.to(() => const MessagePage()); + }, + style: IconButton.styleFrom( + backgroundColor: Colors.grey[100], + padding: const EdgeInsets.all(8), + ), + icon: Badge( + smallSize: 8, + backgroundColor: controller.isNotice ? Colors.red : Colors.transparent, + child: const Icon( + Icons.notifications_outlined, + color: Colors.black87, + size: 30, ), ), - _buildSearchView(), - controller.hasReservationData - ? _buildReservationListView() - : _buildEmptyReservationView(), - ], - ), - ), - - //第三部分 - Card( - elevation: 3, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), - margin: const EdgeInsets.only(bottom: 12), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildInfoItem(Icons.info_outline, '数据每5分钟自动刷新一次'), - const SizedBox(height: 8), - _buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'), - ], ), - ), + ], + ), + const SizedBox(height: 25), + Row( + children: [ + _buildStatBox("剩余氢量", "remaining quantity", controller.leftHydrogen, "kg"), + SizedBox(width: 4.w), + _buildStatBox( + "今日加氢量", + "Have been added", + controller.orderTotalAmount, + "kg", + ), + SizedBox(width: 4.w), + _buildStatBox( + "未加氢总量", + "No quantity added", + controller.orderUnfinishedAmount, + "kg", + ), + ], ), ], ), ); } + Widget _buildStatBox(String title, String enTitle, String value, String unit) { + return Expanded( + child: Container( + padding: EdgeInsets.only(left: 12.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration( + color: Color(0xFFF5F7F9), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 12.sp, + color: Color.fromRGBO(51, 51, 51, 0.8), + fontWeight: FontWeight.w400, + ), + ), + Text(enTitle, style: const TextStyle(fontSize: 9, color: Colors.grey)), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + value, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: Color(0xFF333333), + ), + ), + const SizedBox(width: 2), + Text(unit, style: const TextStyle(fontSize: 11, color: Colors.grey)), + ], + ), + ], + ), + ), + ); + } + //搜索输入框,提示可以输入车牌或者手机 Widget _buildSearchView() { return Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), + padding: EdgeInsets.fromLTRB(0, 0, 0, 8.h), child: Row( children: [ Expanded( child: SizedBox( - height: 44, + height: 42.h, child: TextField( + textAlignVertical: TextAlignVertical.center, controller: controller.searchController, // 绑定控制器 decoration: InputDecoration( - hintText: '输入车牌号或完整手机号查询', - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + filled: true, + isDense: true, + fillColor: Colors.white, + hintText: '输入车牌号或手机号搜索...', + contentPadding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(22), - borderSide: BorderSide(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Color.fromRGBO(229, 231, 235, 1)), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(22), - borderSide: BorderSide(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Color.fromRGBO(229, 231, 235, 1)), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(22), - borderSide: BorderSide(color: Get.theme.primaryColor, width: 1.5), + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Color.fromRGBO(229, 231, 235, 1), + width: 1.5, + ), ), // 清除按钮 - suffixIcon: IconButton( - icon: const Icon(Icons.clear, size: 20), - onPressed: () { - controller.searchController.clear(); - controller.fetchReservationData(); // 清除后也刷新一次 - }, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.clear, size: 20), + onPressed: () { + controller.searchController.clear(); + controller.fetchReservationData(); // 清除后也刷新一次 + }, + ), + GestureDetector( + onTap: () { + // 点击“搜索”按钮时触发 + FocusScope.of(Get.context!).unfocus(); // 收起键盘 + controller.fetchReservationData(); + }, + child: Container( + padding: EdgeInsets.only( + left: 16.w, + right: 16.h, + top: 4.5.h, + bottom: 4.5.h, + ), + decoration: BoxDecoration( + color: Color.fromRGBO(1, 113, 55, 1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + "搜索", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 12.sp, + ), + ), + ), + ), + ], ), ), onSubmitted: (value) { @@ -282,40 +350,6 @@ class SitePage extends GetView { ), ), ), - const SizedBox(width: 10), - ElevatedButton( - onPressed: () { - // 点击“搜索”按钮时触发 - FocusScope.of(Get.context!).unfocus(); // 收起键盘 - controller.fetchReservationData(); - }, - style: ElevatedButton.styleFrom( - shape: const CircleBorder(), - padding: const EdgeInsets.all(8), - ), - child: const Icon(Icons.search_rounded), - ), - ], - ), - ); - } - - /// 构建单个统计项 - Widget _buildStatItem(String value, String label, {Color valueColor = Colors.blue}) { - return Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - value, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: valueColor, - ), - ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(fontSize: 14, color: Colors.grey)), ], ), ); @@ -330,14 +364,17 @@ class SitePage extends GetView { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.inventory_2_outlined, size: 80, color: Colors.grey[300]), - const SizedBox(height: 16), - const Text('暂无预约数据', style: TextStyle(fontSize: 16, color: Colors.black54)), - const SizedBox(height: 8), - const Text( - '点击右上角刷新按钮获取最新数据', - style: TextStyle(fontSize: 14, color: Colors.grey), + LoginUtil.getAssImg("ic_no_data@2x"), + SizedBox(height: 16.h), + Text( + '暂无订单', + style: TextStyle( + fontSize: 16.sp, + color: Colors.black54, + fontWeight: FontWeight.w600, + ), ), + const SizedBox(height: 8), ], ), ); @@ -345,137 +382,298 @@ class SitePage extends GetView { /// 构建“有预约数据”的列表视图 Widget _buildReservationListView() { - return Container( - color: Colors.white, - child: ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - // 因为外层已有滚动,这里禁用内部滚动 - itemCount: controller.reservationList.length, - padding: const EdgeInsets.all(12.0), - itemBuilder: (context, index) { - final item = controller.reservationList[index]; - // 调用新的方法来构建每一项 - return _buildReservationItem(index, item); - }, - separatorBuilder: (context, index) => const SizedBox(height: 12), // 列表项之间的间距 - ), + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + // 因为外层已有滚动,这里禁用内部滚动 + itemCount: controller.reservationList.length, + itemBuilder: (context, index) { + final item = controller.reservationList[index]; + // 调用新的方法来构建每一项 + return _buildReservationItem(index, item); + }, + separatorBuilder: (context, index) => const SizedBox(height: 0), // 列表项之间的间距 ); } Widget _buildReservationItem(int index, ReservationModel item) { + const kPrimaryGreen = Color(0xFF006D35); // 深绿 + const kLineColor = Color(0xFFE0E6ED); // 线条颜色 + + return IntrinsicHeight( + // 保证左侧竖线能跟右侧内容高度对齐 + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 1. 左侧样式区域(竖线 + 圆点) + SizedBox( + width: 20, + child: Column( + children: [ + // 顶部的装饰圆点 + Container( + margin: EdgeInsets.only(top: 3.w), + width: 8, + height: 8, + decoration: BoxDecoration( + color: kPrimaryGreen.withOpacity(0.5), + shape: BoxShape.circle, + ), + ), + // 延伸的竖线 + Expanded(child: Container(width: 1, color: kLineColor)), + ], + ), + ), + // 2. 右侧内容区域(时间 + 卡片数据) + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10.w, bottom: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 时间显示 + Text( + item.time, // 格式如 "09:00 - 10:00" + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.bold, + color: Color(0xFF333333), + ), + ), + const SizedBox(height: 10), + + // 数据卡片 + _buildInfoCard(item), + ], + ), + ), + ), + ], + ), + ); + } + + /// 右侧具体数据卡片 + Widget _buildInfoCard(ReservationModel item) { return Container( + padding: EdgeInsets.only(left: 16.w, top: 8.5, bottom: 8.5, right: 16.w), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(8.0), - border: Border.all(color: Colors.grey[200]!, width: 1.0), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFF0F0F0)), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 2), + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 4), ), ], ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 顶部:序号、车牌号、状态 - Padding( - padding: const EdgeInsets.fromLTRB(8, 12, 16, 12), - child: Row( - children: [ - // 序号 - Container( - width: 24, - height: 24, - alignment: Alignment.center, - decoration: const BoxDecoration( - color: Colors.blue, - shape: BoxShape.circle, - ), - child: Text( - "${index + 1}", - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), + // 车牌与状态标签 + Row( + children: [ + Text( + item.plateNumber, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.bold, + color: Color(0xFF333333), ), - const SizedBox(width: 8), - const Icon(Icons.directions_car, color: Colors.black54), - const SizedBox(width: 4), - // 车牌号 - Expanded( - child: Text( - item.plateNumber, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppTheme.themeColor, - ), - ), - ), - // 状态标签 - _buildStatusChip(item.status), - ], - ), - ), - const Divider(height: 1), - // 中部:详细信息 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - child: Column( - children: [ - _buildDetailRow( - Icons.local_gas_station, - '加氢量', - item.amount, - valueColor: Colors.red, - ), - const SizedBox(height: 8), - _buildDetailRow(Icons.access_time, '预约时间', item.time), - const SizedBox(height: 8), - _buildDetailRow(Icons.person, '联系人', item.contactPerson), - const SizedBox(height: 8), - _buildDetailRow( - Icons.phone, - '联系电话', - item.contactPhone, - valueColor: AppTheme.themeColor, - ), - ], - ), - ), - // 底部:操作按钮 (仅在待处理时显示) - if (item.status == ReservationStatus.pending) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () => controller.confirmReservation(item.id), - style: ElevatedButton.styleFrom(backgroundColor: Colors.blue), - child: const Text('确认'), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton( - onPressed: () => controller.rejectReservation(item.id), - style: ElevatedButton.styleFrom(backgroundColor: Colors.red), - child: const Text('拒绝'), - ), - ), - ], ), + const SizedBox(width: 8), + _buildStatusTag(item.status), + Spacer(), + Text( + "预约量:${item.amount}", + style: TextStyle( + color: Color(0xFF00A870), + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + // 联系信息 + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "${item.contactPerson} | ${item.contactPhone}", + style: TextStyle( + color: Color(0xFF999999), + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + + //操作按钮(仅在待处理状态显示) + if (item.status == ReservationStatus.pending) ...[ + const SizedBox(height: 15), + const Divider(height: 1, color: Color(0xFFF5F5F5)), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildSmallButton( + "拒绝", + isOutline: true, + onTap: () { + controller.rejectReservation(item.id); + }, + ), + const SizedBox(width: 12), + _buildSmallButton( + "确认", + isOutline: false, + onTap: () { + controller.confirmReservation(item.id); + }, + ), + ], ), + ], ], ), ); } + /// 通用小按钮 + Widget _buildSmallButton( + String text, { + required bool isOutline, + required VoidCallback onTap, + }) { + const kPrimaryGreen = Color(0xFF006D35); + const kDangerRed = Color(0xFFFF7D7D); + + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: isOutline ? Colors.white : kPrimaryGreen, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: isOutline ? kDangerRed : kPrimaryGreen), + ), + child: Text( + text, + style: TextStyle( + color: isOutline ? kDangerRed : Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + /// 状态标签构建(带圆角和浅色背景) + Widget _buildStatusTag(ReservationStatus status) { + Color textColor; + Color bgColor; + String text; + + switch (status) { + case ReservationStatus.pending: + text = "待加氢"; + textColor = const Color(0xFFFE9E62); // 橘色 + bgColor = const Color(0xFFFFF2E9); + break; + case ReservationStatus.completed: // 假设已加氢状态 + text = "已加氢"; + textColor = const Color(0xFF00A870); // 绿色 + bgColor = const Color(0xFFE6F7F1); + break; + case ReservationStatus.rejected: + text = "拒绝加氢"; + textColor = const Color(0xFFFF7D7D); // 红色 + bgColor = const Color(0xFFFFEEEE); + break; + case ReservationStatus.unadded: + text = '未加氢'; + textColor = const Color(0xFFFF7D7D); // 红色 + bgColor = const Color(0xFFFFEEEE); + break; + case ReservationStatus.cancel: + text = '已取消'; + textColor = const Color(0xFFFF7D7D); // 红色 + bgColor = const Color(0xFFFFEEEE); + break; + default: + text = "未知"; + textColor = Colors.grey; + bgColor = Colors.grey[100]!; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: textColor.withOpacity(0.3)), + ), + child: Text( + text, + style: TextStyle(color: textColor, fontSize: 11, fontWeight: FontWeight.bold), + ), + ); + } + + /// 右侧操作按钮(拒绝/确认) + Widget _buildActionButtons(ReservationModel item) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 拒绝按钮(空心) + GestureDetector( + onTap: () => controller.rejectReservation(item.id), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFFFF7D7D)), + ), + child: const Text( + "拒绝", + style: TextStyle( + color: Color(0xFFFF7D7D), + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(width: 8), + // 确认按钮(实心深绿) + GestureDetector( + onTap: () => controller.confirmReservation(item.id), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: const Color(0xFF006D35), + borderRadius: BorderRadius.circular(10), + ), + child: const Text( + "确认", + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + } + /// 构建状态标签 Widget _buildStatusChip(ReservationStatus status) { String text; diff --git a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart index 81d539a..55a3fcb 100644 --- a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart +++ b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:getx_scaffold/common/index.dart'; +import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/pages/c_page/car_info/view.dart'; import 'package:ln_jq_app/pages/c_page/map/view.dart'; import 'package:ln_jq_app/pages/c_page/mine/view.dart'; @@ -9,9 +10,10 @@ import 'package:ln_jq_app/pages/c_page/reservation/view.dart'; import 'index.dart'; class BaseWidgetsPage extends GetView { - BaseWidgetsPage({super.key}); + BaseWidgetsPage({super.key}); final PageController _pageController = PageController(); + // 主视图 Widget _buildView() { return PageView( @@ -20,54 +22,68 @@ class BaseWidgetsPage extends GetView { onPageChanged: (index) { jumpTabAndPage(index); }, - children: _buildPages(), // 页面的列表 + children: _buildPages(), ); } void jumpTabAndPage(int index) { - controller.pageIndex = index; // 更新页面索引 - controller.updateUi(); // 更新 UI + controller.pageIndex = index; + controller.updateUi(); _pageController.jumpToPage(controller.pageIndex); } - // 对应的页面 + List _buildPages() { - return [ - ReservationPage(), - MapPage(), - CarInfoPage(), - MinePage(), - ]; + return [ReservationPage(), MapPage(), CarInfoPage(), MinePage()]; } - //导航栏 + // 自定义导航栏 (悬浮胶囊样式) Widget _buildNavigationBar() { - return NavigationX( - currentIndex: controller.pageIndex, // 当前选中的tab索引 - onTap: (index) { - jumpTabAndPage(index); - }, // 切换tab事件 - items: [ - NavigationItemModel( - label: '加氢预约', - icon: AntdIcon.orderedlist, - selectedIcon: AntdIcon.calendar_fill, + return SafeArea( + child: Container( + height: 50.h, + margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距 + decoration: BoxDecoration( + color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景 + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 5), + ), + ], ), - NavigationItemModel( - label: '地图', - icon: AntdIcon.location, - selectedIcon: AntdIcon.location_fill, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"), + _buildNavItem(1, "ic_map_select@2x", "ic_map@2x"), + _buildNavItem(2, "ic_car_select@2x", "ic_car@2x"), + _buildNavItem(3, "ic_user_select@2x", "ic_user@2x"), + ], ), - NavigationItemModel( - label: '车辆信息', - icon: AntdIcon.car, - selectedIcon: AntdIcon.car_fill, + ), + ); + } + + // 构建单个导航项 + Widget _buildNavItem(int index, String icon, String selectedIcon) { + bool isSelected = controller.pageIndex == index; + return GestureDetector( + onTap: () => jumpTabAndPage(index), + behavior: HitTestBehavior.opaque, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFF006633) : Colors.transparent, // 选中时的深绿色背景 + borderRadius: BorderRadius.circular(20), ), - NavigationItemModel( - label: '我的', - icon: AntdIcon.user, - selectedIcon: AntdIcon.user, - ), - ], + child: SizedBox( + height: 24, + width: 24, + child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),), + ), ); } @@ -78,10 +94,10 @@ class BaseWidgetsPage extends GetView { id: 'baseWidgets', builder: (_) { return Scaffold( - extendBody: false, + extendBody: true, // 重要:让 body 延伸到导航栏后面 resizeToAvoidBottomInset: false, bottomNavigationBar: _buildNavigationBar(), - body: SafeArea(child: _buildView()), + body: _buildView(), // 移除 SafeArea 以获得更好的全屏沉浸感 ); }, ); diff --git a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart index 666288c..40f24da 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart @@ -12,12 +12,12 @@ class AttachmentViewerPage extends GetView { @override Widget build(BuildContext context) { Get.put(AttachmentViewerController()); - final fileName = controller.url.split('/').last; + // final fileName = controller.url.split('/').last; return Scaffold( appBar: AppBar( title: Text( - fileName, + "证件详情", style: const TextStyle(fontSize: 16), overflow: TextOverflow.ellipsis, ), diff --git a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart index b431d90..215ac78 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart @@ -6,7 +6,7 @@ import 'package:path_provider/path_provider.dart'; import 'attachment_viewer_page.dart'; -class CertificateViewerController extends GetxController with BaseControllerMixin{ +class CertificateViewerController extends GetxController with BaseControllerMixin { late final String title; late final List attachments; @@ -78,18 +78,11 @@ class CertificateViewerController extends GetxController with BaseControllerMixi return; } - Get.to( - () => const AttachmentViewerPage(), - arguments: { - 'url': url, - }, - ); + Get.to(() => const AttachmentViewerPage(), arguments: {'url': url}); } /// 检查 URL 是否为 PDF (此方法保持不变) bool isPdf(String url) { return url.toLowerCase().endsWith('.pdf'); } - - } diff --git a/ln_jq_app/lib/pages/c_page/car_info/controller.dart b/ln_jq_app/lib/pages/c_page/car_info/controller.dart index 3b549f7..27232ee 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/controller.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/controller.dart @@ -2,10 +2,12 @@ import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/model/vehicle_info.dart'; +import 'package:ln_jq_app/pages/c_page/car_info/attachment_viewer_page.dart'; import 'package:ln_jq_app/pages/qr_code/view.dart'; import 'package:ln_jq_app/storage_service.dart'; - +import 'package:path_provider/path_provider.dart'; import 'certificate_viewer_page.dart'; +import 'dart:io'; class CarInfoController extends GetxController with BaseControllerMixin { @override @@ -22,11 +24,37 @@ class CarInfoController extends GetxController with BaseControllerMixin { final RxList operationAttachments = [].obs; final RxList hydrogenationAttachments = [].obs; final RxList registerAttachments = [].obs; + String color = ""; + String hydrogenCapacity = ""; + String rentFromCompany = ""; + String address = ""; + bool isNotice = false; @override void onInit() { super.onInit(); getUserBindCarInfo(); + _msgNotice(); + } + + Future _msgNotice() async { + final Map requestData = { + 'appFlag': 1, + 'isRead': 1, + 'pageNum': 1, + 'pageSize': 5, + }; + final response = await HttpService.to.get( + 'appointment/unread_notice/page', + params: requestData, + ); + if (response != null) { + final result = BaseModel.fromJson(response.data); + if (result.code == 0 && result.data != null) { + String total = result.data["total"].toString(); + isNotice = int.parse(total) > 0; + } + } } @override @@ -96,6 +124,21 @@ class CarInfoController extends GetxController with BaseControllerMixin { parseAttachments(data['hydrogenationAttachment']), ); registerAttachments.assignAll(parseAttachments(data['registerAttachment'])); + + // 初始化时开始加载所有PDF + attachments = [ + ...drivingAttachments, + ...operationAttachments, + ...hydrogenationAttachments, + ...registerAttachments, + ]; + + color = data['color'].toString(); + hydrogenCapacity = data['hydrogenCapacity'].toString(); + rentFromCompany = data['rentFromCompany'].toString(); + address = data['address'].toString(); + + loadAllPdfs(); } } updateUi(); @@ -117,4 +160,69 @@ class CarInfoController extends GetxController with BaseControllerMixin { arguments: {'title': title, 'attachments': attachments}, ); } + + /// 导航到通用的附件查看器页面 + void openAttachment(String url) { + if (url.isEmpty) { + showErrorToast('附件链接无效'); + return; + } + + Get.to(() => const AttachmentViewerPage(), arguments: {'url': url}); + } + + /// 检查 URL 是否为 PDF + bool isPdf(String url) { + return url.toLowerCase().endsWith('.pdf'); + } + + List attachments = []; + + // --- 新增: 状态管理 --- + /// 用于存储网络PDF的本地路径,key是网络url,value是本地路径 + final RxMap localPdfPaths = {}.obs; + + /// 用于跟踪每个附件的加载状态,key是网络url + final RxMap isLoading = {}.obs; + + /// 遍历所有附件,如果是PDF则进行下载 + void loadAllPdfs() { + for (var url in attachments) { + if (isPdf(url)) { + _downloadPdf(url); + } + } + } + + /// 下载单个PDF文件 + Future _downloadPdf(String url) async { + if (url.isEmpty) return; + + // 开始加载 + isLoading[url] = true; + + try { + final dio = Dio(); + final Directory tempDir = await getTemporaryDirectory(); + final String savePath = '${tempDir.path}/${url.split('/').last}'; + + // 检查文件是否已存在,避免重复下载 + if (await File(savePath).exists()) { + localPdfPaths[url] = savePath; + isLoading[url] = false; + return; + } + + await dio.download(url, savePath); + + // 下载成功后,更新本地路径 + localPdfPaths[url] = savePath; + } catch (e) { + print('PDF download error for $url: $e'); + // 出错时也可以更新状态,以便UI显示错误提示 + } finally { + // 结束加载 + isLoading[url] = false; + } + } } diff --git a/ln_jq_app/lib/pages/c_page/car_info/view.dart b/ln_jq_app/lib/pages/c_page/car_info/view.dart index 9022169..6d50d14 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/view.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/view.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; +import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; -import 'package:ln_jq_app/common/styles/theme.dart'; -import 'package:ln_jq_app/pages/qr_code/view.dart'; +import 'package:ln_jq_app/common/login_util.dart'; +import 'package:ln_jq_app/pages/c_page/message/view.dart'; import 'package:ln_jq_app/storage_service.dart'; +import 'package:photo_view/photo_view.dart'; +import '../../../common/styles/theme.dart'; import 'controller.dart'; class CarInfoPage extends GetView { @@ -16,22 +20,26 @@ class CarInfoPage extends GetView { id: 'car_info', builder: (_) { return Scaffold( - backgroundColor: Colors.grey[100], + backgroundColor: const Color.fromRGBO(240, 244, 247, 0.4), body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildDriverInfoCard(), - const SizedBox(height: 5), - _buildCarBindingCard(), - const SizedBox(height: 5), - _buildCertificatesCard(), - const SizedBox(height: 5), - _buildTipsCard(), - ], - ), + child: Column( + children: [ + _buildUserInfoCard(), + Padding( + padding: EdgeInsets.only(left: 20.w, right: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 16), + _buildCarInfoCard(), + _buildCertificatesCard(context), + const SizedBox(height: 12), + _buildSafetyReminderCard(), + SizedBox(height: 95.h), + ], + ), + ), + ], ), ), ); @@ -39,74 +47,124 @@ class CarInfoPage extends GetView { ); } - /// 构建顶部的司机信息卡片 - Widget _buildDriverInfoCard() { + Widget _buildUserInfoCard() { return Card( - elevation: 2, + elevation: 1, + color: Colors.white, margin: EdgeInsets.zero, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 16, top: 50), child: Row( children: [ - const CircleAvatar( - radius: 20, - backgroundColor: Colors.blue, - child: Icon(Icons.person, color: Colors.white, size: 34), + Stack( + children: [ + CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + child: LoginUtil.getAssImg('ic_user_logo@2x'), + ), + Positioned( + right: 0, + bottom: 0, + child: SizedBox( + height: 16.h, + width: 16.w, + child: LoginUtil.getAssImg('ic_logo@2x'), + ), + ), + ], ), - const SizedBox(width: 12), + SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "${StorageService.to.name}", - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + Row( + children: [ + Text( + "${StorageService.to.name}", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 8.w), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color.fromRGBO(236, 255, 234, 1), + border: Border.all(color: const Color(0xFFB7E19F)), + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.eco, size: 12, color: Color(0xFF52C41A)), + SizedBox(width: 4), + Text( + "绿色先锋", + style: TextStyle( + color: Color(0xFF52C41A), + fontSize: 10, + ), + ), + ], + ), + ), + ], ), const SizedBox(height: 4), Text( - "${StorageService.to.phone}", - style: TextStyle(color: Colors.grey, fontSize: 11), + "羚牛ID:${StorageService.to.phone}", + style: const TextStyle(color: Colors.grey, fontSize: 11), ), ], ), ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue, width: 0.5), + IconButton( + onPressed: () { + Get.to(() => const MessagePage()); + }, + style: IconButton.styleFrom( + backgroundColor: Colors.grey[100], + padding: const EdgeInsets.all(8), ), - child: const Row( - children: [ - Icon(Icons.shield_outlined, color: Colors.blue, size: 14), - SizedBox(width: 4), - Text( - '已认证', - style: TextStyle( - color: Colors.blue, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ], + icon: Badge( + smallSize: 8, + backgroundColor: controller.isNotice + ? Colors.red + : Colors.transparent, + child: const Icon( + Icons.notifications_outlined, + color: Colors.black87, + size: 30, + ), ), ), ], ), ), - const Divider(height: 1, indent: 16, endIndent: 16), Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildStatItem('156', '服务天数'), - _buildStatItem('4.9', '评分'), - _buildStatItem('98%', '准时率'), + _buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''), + const SizedBox(width: 8), + _buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''), + const SizedBox(width: 8), + _buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''), ], ), ), @@ -115,207 +173,379 @@ class CarInfoPage extends GetView { ); } - // 司机信息卡片中的小统计项 - Widget _buildStatItem(String value, String label) { - return Column( - children: [ - Text( - value, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.blue, - ), + Widget _buildModernStatItem(String title, String subtitle, String value, String unit) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(12), ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), - ], - ); - } - - /// 构建车辆绑定信息卡片 - Widget _buildCarBindingCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - children: [ - _buildInfoRow('车牌号: ${controller.plateNumber}', '扫码绑定'), - const SizedBox(height: 12), - _buildInfoRow('车架号:', '${controller.vin}'), - const SizedBox(height: 12), - _buildInfoRow('车辆型号:', '${controller.modelName}'), - const SizedBox(height: 12), - _buildInfoRow('车辆品牌:', '${controller.brandName}'), - ], + Text( + title, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.black87, ), ), - const SizedBox(width: 8), - Icon(Icons.propane_rounded, size: 50, color: Colors.blue.withOpacity(0.5)), + Text(subtitle, style: const TextStyle(fontSize: 9, color: Colors.grey)), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + value, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + Text(unit, style: const TextStyle(fontSize: 10, color: Colors.black54)), + ], + ), ], ), ), ); } - // 车辆绑定卡片中的信息行 - Widget _buildInfoRow(String label, String value) { - bool isButton = value == '扫码绑定'; + Widget _buildCarInfoCard() { + return Card( + elevation: 2, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + _buildDetailRow('车牌号', controller.plateNumber, isPlate: true), + const SizedBox(height: 11), + _buildDetailRow('车架号', controller.vin), + const SizedBox(height: 11), + _buildDetailRow('车辆型号', controller.modelName), + const SizedBox(height: 11), + _buildDetailRow('车辆品牌', controller.brandName), + const SizedBox(height: 10), + _buildH2LevelProgress(), + ], + ), + ), + ); + } + + Widget _buildDetailRow(String label, String value, {bool isPlate = false}) { return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(label, style: const TextStyle(fontSize: 13)), - const SizedBox(width: 8), - isButton - ? GestureDetector( - onTap: () async { - controller.doQrCode(); - }, + Text(label, style: const TextStyle(fontSize: 13, color: Colors.grey)), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isPlate) + GestureDetector( + onTap: () => controller.doQrCode(), child: Container( - margin: EdgeInsetsGeometry.only(left: 10.w), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), + margin: const EdgeInsets.only(right: 10), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( - border: Border.all(color: Colors.blue.shade300, width: 1), - borderRadius: BorderRadius.circular(5), - color: Colors.blue.withOpacity(0.05), + border: Border.all(color: const Color.fromRGBO(71, 174, 208, 1)), + borderRadius: BorderRadius.circular(12), + color: const Color.fromRGBO(235, 250, 255, 1), ), child: Row( - mainAxisSize: MainAxisSize.min, // Keep the row compact children: [ - Icon( - StorageService.to.hasVehicleInfo ? Icons.repeat : Icons.search, - size: 13, - color: Colors.blue, + const Icon( + Icons.sync, + size: 12, + color: Color.fromRGBO(71, 174, 208, 1), ), - const SizedBox(width: 3), + const SizedBox(width: 4), Text( - StorageService.to.hasVehicleInfo ? "换车牌" : value, + StorageService.to.hasVehicleInfo ? "换车牌" : "扫码绑定", style: const TextStyle( - color: Colors.blue, - fontSize: 11, - fontWeight: FontWeight.w500, + color: Color.fromRGBO(71, 174, 208, 1), + fontSize: 10, + fontWeight: FontWeight.bold, ), ), ], ), ), - ) - : Text( - value, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), ), + Text( + value, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), ], ); } - /// 3. 构建车辆证件卡片 - Widget _buildCertificatesCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Column( - children: [ - _buildCertificateRow( - icon: Icons.credit_card_rounded, - title: '行驶证', - attachments: controller.drivingAttachments, - ), - const Divider(), - _buildCertificateRow( - icon: Icons.article_rounded, - title: '营运证', - attachments: controller.operationAttachments, - ), - const Divider(), - _buildCertificateRow( - icon: Icons.propane_tank_rounded, - title: '加氢证', - attachments: controller.hydrogenationAttachments, - ), - const Divider(), - _buildCertificateRow( - icon: Icons.app_registration_rounded, - title: '登记证', - attachments: controller.registerAttachments, - ), - ], - ), - ), - ); - } - - /// 证件展示 - Widget _buildCertificateRow({ - required IconData icon, - required String title, - required List attachments, - }) { - return ListTile( - contentPadding: EdgeInsets.zero, - leading: CircleAvatar( - radius: 24, - backgroundColor: Colors.blue.withOpacity(0.1), - child: Icon(icon, color: Colors.blue, size: 28), - ), - title: Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), - ), - // 使用 Obx 响应式地显示附件数量 - subtitle: Obx( - () => Text( - '共 ${attachments.length} 个附件', - style: const TextStyle(color: Colors.grey, fontSize: 12), - ), - ), - trailing: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(8), - ), - child: const Icon(Icons.find_in_page_outlined, color: AppTheme.themeColor), - ), - // 更新 onTap 逻辑 - onTap: () => controller.navigateToCertificateViewer(title, attachments), - ); - } - - Widget _buildTipsCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildTipItem(Icons.info_outline, '请确保车辆证件齐全有效'), - const SizedBox(height: 10), - _buildTipItem(Icons.rule, '定期检查车辆状态和证件有效期'), - const SizedBox(height: 10), - _buildTipItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'), - ], - ), - ), - ); - } - - // 提示信息卡片中的列表项 - Widget _buildTipItem(IconData icon, String text) { - return Row( + Widget _buildH2LevelProgress() { + return Column( children: [ - Icon(icon, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: const LinearProgressIndicator( + value: 0.75, + minHeight: 8, + backgroundColor: Color(0xFFF0F2F5), + valueColor: AlwaysStoppedAnimation(Color.fromRGBO(16, 185, 129, 1)), + ), + ), + const SizedBox(height: 8), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)), + Text( + "75%", + style: TextStyle( + fontSize: 11, + color: Color.fromRGBO(16, 185, 129, 1), + fontWeight: FontWeight.bold, + ), + ), + ], ), ], ); } + + /// 3. 构建车辆证件卡片 (重构为 TabView 样式) + Widget _buildCertificatesCard(BuildContext context) { + return DefaultTabController( + length: 4, + child: Column( + children: [ + TabBar( + isScrollable: false, + indicatorColor: Color.fromRGBO(16, 185, 129, 1), + labelColor: Color.fromRGBO(16, 185, 129, 1), + unselectedLabelColor: Colors.grey, + labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), + indicatorSize: TabBarIndicatorSize.label, + tabs: const [ + Tab(text: '行驶证'), + Tab(text: '营运证'), + Tab(text: '加氢证'), + Tab(text: '登记证'), + ], + ), + const SizedBox(height: 9), + SizedBox( + height: 336.h, // 给定一个高度,或者使用别的方式布局 + child: TabBarView( + children: [ + _buildCertificateContent('行驶证', controller.drivingAttachments), + _buildCertificateContent('营运证', controller.operationAttachments), + _buildCertificateContent('加氢资格证', controller.hydrogenationAttachments), + _buildCertificateContent('登记证', controller.registerAttachments), + ], + ), + ), + ], + ), + ); + } + + /// 构建单个证件的展示内容 + Widget _buildCertificateContent(String title, RxList attachments) { + return Obx(() { + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: EdgeInsets.all(16.0), + child: attachments.isEmpty + ? const Center(child: Text('暂无相关证件信息')) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + //证件文字 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: true), + _buildCertDetailItem('运营城市', controller.address), + ], + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildCertDetailItem( + '车辆颜色', + controller.color, + valueColor: const Color(0xFF52C41A), + ), + _buildCertDetailItem('氢瓶容量', controller.hydrogenCapacity), + ], + ), + const SizedBox(height: 16), + // 附件预览部分 + GestureDetector( + onTap: () { + controller.navigateToCertificateViewer(title, attachments); + }, + child: Container( + height: 184.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Color.fromRGBO(226, 232, 240, 1)), + color: Color.fromRGBO(248, 250, 252, 1), + ), + child: Center(child: _buildAttachmentPreview(attachments[0])), + ), + ), + ], + ), + ), + ); + }); + } + + Widget _buildCertDetailItem( + String label, + String value, { + Color? valueColor, + bool isFull = false, + }) { + return Container( + width: isFull ? null : 140, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 4), + Text( + value, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: valueColor ?? Colors.black87, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } + + /// 附件预览组件 (智能判断 PDF 或图片) + Widget _buildAttachmentPreview(String url) { + return AspectRatio( + aspectRatio: 4 / 3, + child: controller.isPdf(url) + ? Obx(() { + final bool loading = controller.isLoading[url] ?? true; + final String? localPath = controller.localPdfPaths[url]; + + if (loading) { + return _buildLoadingIndicator(); + } else if (localPath != null && localPath.isNotEmpty) { + return IgnorePointer( + ignoring: true, // 设置为 true 来忽略所有指针事件 + child: PDFView( + backgroundColor: Color.fromRGBO(248, 250, 252, 1), + fitEachPage: true, + filePath: localPath, + fitPolicy: FitPolicy.WIDTH, + // 适配宽度 + enableSwipe: false, + swipeHorizontal: false, + autoSpacing: false, + pageFling: false, + preventLinkNavigation: true, // 顺便禁用PDF内部链接的跳转 + ), + ); + } else { + // PDF加载失败 + return _buildErrorIndicator(); + } + }) + : Image.network( + url, + fit: BoxFit.contain, + // 图片加载时显示loading + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return _buildLoadingIndicator(); + }, + // 图片加载失败时显示错误 + errorBuilder: (context, error, stackTrace) { + return _buildErrorIndicator(); + }, + ), + ); + } + + Widget _buildLoadingIndicator() { + return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + } + + Widget _buildErrorIndicator() { + return const SizedBox( + height: 200, + child: Center(child: Icon(Icons.error_outline, color: Colors.red, size: 48)), + ); + } + + /// 安全提醒卡片 + Widget _buildSafetyReminderCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color.fromRGBO(242, 249, 248, 1), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.info_outline, color: AppTheme.themeColor, size: 24), + const SizedBox(width: 8), + Text( + "安全提醒", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(1, 113, 55, 1), + ), + ), + ], + ), + const SizedBox(height: 12), + const Text( + "请确保车辆证件齐全有效,定期检查车辆状态和证件有效期,以确保运输作业合规安全。", + style: TextStyle( + fontSize: 13, + color: Color.fromRGBO(1, 113, 55, 0.8), + height: 1.5, + ), + ), + const SizedBox(height: 8), + const Text( + "如有疑问请联系客服:400-021-1773", + style: TextStyle(fontSize: 13, color: Color.fromRGBO(1, 113, 55, 0.8)), + ), + ], + ), + ); + } } diff --git a/ln_jq_app/lib/pages/c_page/mine/controller.dart b/ln_jq_app/lib/pages/c_page/mine/controller.dart index ff50584..30147ff 100644 --- a/ln_jq_app/lib/pages/c_page/mine/controller.dart +++ b/ln_jq_app/lib/pages/c_page/mine/controller.dart @@ -35,9 +35,9 @@ class MineController extends GetxController with BaseControllerMixin { String historyBreakRules = ""; String vin = ""; String plateNumber = ""; - String violationTotal = "0"; - String violationScore = "0"; - String violationDispose = "0"; + String violationTotal = "0";//违章总数 + String violationScore = "0";//扣分总数 + String violationDispose = "0";//已处理 bool isNotice = false; void renderData() async { diff --git a/ln_jq_app/lib/pages/c_page/mine/view.dart b/ln_jq_app/lib/pages/c_page/mine/view.dart index 966574f..3e7ea1e 100644 --- a/ln_jq_app/lib/pages/c_page/mine/view.dart +++ b/ln_jq_app/lib/pages/c_page/mine/view.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/common/index.dart'; import 'package:getx_scaffold/common/widgets/index.dart'; -import 'package:ln_jq_app/common/styles/theme.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/pages/c_page/message/view.dart'; import 'package:ln_jq_app/storage_service.dart'; import 'controller.dart'; @@ -17,25 +18,32 @@ class MinePage extends GetView { id: 'mine', builder: (_) { return Scaffold( - backgroundColor: Colors.grey[100], + backgroundColor: const Color.fromRGBO(247, 249, 251, 1), body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildUserInfoCard(), - const SizedBox(height: 5), - _buildDriverScoreCard(), - const SizedBox(height: 5), - _buildMonthlyRecordCard(), - const SizedBox(height: 5), - _buildTipsCard(), - const SizedBox(height: 20), - _buildLogoutButton(), - const SizedBox(height: 20), - ], - ), + child: Column( + children: [ + _buildUserInfoCard(), + const SizedBox(height: 8), + // 新 UI 模块开始 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + _buildWalletCard(), + SizedBox(height: 16.h), + _buildGridMenu(), + SizedBox(height: 16.h), + _buildRecommendCard(context), + SizedBox(height: 8.h), + _buildSafetyReminderCard(), + SizedBox(height: 24.h), + _buildLogoutButton(), + SizedBox(height: 95.h), + ], + ), + ), + // 新 UI 模块结束 + ], ), ), ); @@ -43,82 +51,127 @@ class MinePage extends GetView { ); } - /// 1. 构建顶部用户信息卡片 + /// 构建顶部用户信息卡片 Widget _buildUserInfoCard() { return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 1, + color: Colors.white, + margin: EdgeInsets.zero, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 16, top: 40), + // 增加了顶部 padding 适配状态栏 child: Row( children: [ - const CircleAvatar( - radius: 25, - backgroundColor: Colors.blue, - child: Icon(Icons.person, color: Colors.white, size: 40), + Stack( + children: [ + CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + child: LoginUtil.getAssImg('ic_user_logo@2x'), + ), + Positioned( + right: 0, + bottom: 0, + child: SizedBox( + height: 16.h, + width: 16.w, + child: LoginUtil.getAssImg('ic_logo@2x'), + ), + ), + ], ), - const SizedBox(width: 16), + SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "${StorageService.to.name}", - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + Row( + children: [ + Text( + "${StorageService.to.name}", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 8.w), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color.fromRGBO(236, 255, 234, 1), // 极浅绿色背景 + border: Border.all(color: const Color(0xFFB7E19F)), // 边框 + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.eco, size: 12, color: Color(0xFF52C41A)), + // 叶子图标 + SizedBox(width: 4), + Text( + "绿色先锋", + style: TextStyle( + color: Color(0xFF52C41A), + fontSize: 10, + ), + ), + ], + ), + ), + ], ), const SizedBox(height: 4), Text( - "${StorageService.to.phone}", - style: TextStyle(color: Colors.grey, fontSize: 11), - ), - const SizedBox(height: 4), - Text( - StorageService.to.hasVehicleInfo ? "已绑定车辆" : '未绑定车辆', - style: TextStyle(color: Colors.orange, fontSize: 12), + "羚牛ID:${StorageService.to.phone}", + style: const TextStyle(color: Colors.grey, fontSize: 11), ), ], ), ), IconButton( - onPressed: () async { - // 跳转消息中心 - var scanResult = await Get.to(() => const MessagePage()); - if (scanResult == null) { - controller.msgNotice(); - } + onPressed: () { + Get.to(() => const MessagePage()); }, - // 这里的 style 是为了模拟你图片里的灰色圆形背景 style: IconButton.styleFrom( backgroundColor: Colors.grey[100], padding: const EdgeInsets.all(8), ), icon: Badge( - // label: Text('3'), // 如果你想显示数字,就加 label smallSize: 8, - // 红点的大小 - backgroundColor: controller.isNotice ? Colors.red : Colors.white, - // 红点颜色 - child: Icon( + backgroundColor: controller.isNotice + ? Colors.red + : Colors.transparent, + child: const Icon( Icons.notifications_outlined, color: Colors.black87, - size: 25, + size: 30, ), ), ), ], ), ), - const Divider(height: 1), Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildStatItem(controller.violationTotal, '违章总数'), - _buildStatItem(controller.violationScore, '扣分总数'), - _buildStatItem(controller.violationDispose, '已处理'), + _buildModernStatItem('服务天数', 'service days', '156', ''), + const SizedBox(width: 8), + _buildModernStatItem('准时率', 'Punctuality', controller.rate, ''), + const SizedBox(width: 8), + _buildModernStatItem('司机评分', 'Driver rating', controller.rating, ''), ], ), ), @@ -127,242 +180,351 @@ class MinePage extends GetView { ); } - // 用户信息卡片中的小统计项 - Widget _buildStatItem(String value, String label) { + // 统计项 + Widget _buildModernStatItem(String title, String subtitle, String value, String unit) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text(subtitle, style: const TextStyle(fontSize: 9, color: Colors.grey)), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + value, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + Text(unit, style: const TextStyle(fontSize: 10, color: Colors.black54)), + ], + ), + ], + ), + ), + ); + } + + /// 我的钱包卡片 + Widget _buildWalletCard() { + return Card( + elevation: 1, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "我的钱包", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + Text("User wallet", style: TextStyle(fontSize: 12, color: Colors.grey)), + ], + ), + ), + Text( + "¥ 0,00元", + style: TextStyle( + color: Colors.green[700], + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } + + /// 2x2 功能网格菜单 + Widget _buildGridMenu() { return Column( children: [ - Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), + Row( + children: [ + _buildGridItem(Icons.person_search_outlined, "客服评价", "3项可评"), + const SizedBox(width: 19), + _buildGridItem( + Icons.assignment_late_outlined, + "违章处理", + "${controller.historyBreakRules}项待办", + countColor: Colors.red, + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + _buildGridItem(Icons.book_outlined, "安全培训", "0个待看"), + const SizedBox(width: 19), + _buildGridItem( + Icons.verified_user_outlined, + "诚信加氢值", + "845", + isSpecial: true, + backgroundColor: const Color(0xFF006633), + ), + ], + ), ], ); } - /// 2. 构建驾驶得分卡片 - Widget _buildDriverScoreCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), + Widget _buildGridItem( + IconData icon, + String title, + String subtitle, { + Color? countColor, + bool isSpecial = false, + Color? backgroundColor, + }) { + return Expanded( + child: Container( + height: 100, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isSpecial ? backgroundColor : Colors.white, + borderRadius: BorderRadius.circular(16), + ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - '驾驶得分', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + Icon(icon, color: isSpecial ? Colors.white : Colors.black87, size: 28), + const SizedBox(height: 8), + Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: isSpecial ? Colors.white : Colors.black87, + ), ), - const Text('本月表现', style: TextStyle(fontSize: 12, color: Colors.grey)), - const SizedBox(height: 20), - Center( - child: SizedBox( - width: 100, - height: 100, - child: Stack( - fit: StackFit.expand, + Text( + subtitle, + style: TextStyle( + fontSize: 12, + color: isSpecial + ? Colors.white.withOpacity(0.8) + : (countColor ?? Colors.grey), + ), + ), + ], + ), + ), + ); + } + + /// 我要推荐卡片 + Widget _buildRecommendCard(BuildContext context) { + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - CircularProgressIndicator( - value: (double.tryParse(controller.rating) ?? 0) / 10, - strokeWidth: 8, - backgroundColor: Colors.grey[200], - valueColor: AlwaysStoppedAnimation(Colors.blue), + Text( + "我要推荐", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), - Center( - child: Text( - controller.rating, - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.blue, - ), + Text("Recommend", style: TextStyle(fontSize: 12, color: Colors.grey)), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text("累计奖励(积分)", style: TextStyle(fontSize: 11, color: Colors.grey)), + Text( + "0,00", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green, ), ), ], ), - ), - ), - const SizedBox(height: 20), - _buildScoreDetailRow(Icons.directions_car, '安全驾驶', '无违章记录', true), - const Divider(), - _buildScoreDetailRow(Icons.timer, '准时率', '100%准时到达', true), - const Divider(), - _buildScoreDetailRow(Icons.thumb_up, '服务质量', '用户满意度高', true), - const Divider(), - Padding( - padding: const EdgeInsets.only(top: 12.0), - child: Row( - children: [ - const Text( - '优秀驾驶员', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(16), - ), - child: const Text( - 'A+', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } - - // 驾驶得分卡片中的评分项 - Widget _buildScoreDetailRow( - IconData icon, - String title, - String subtitle, - bool isCompleted, - ) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - children: [ - CircleAvatar( - radius: 20, - backgroundColor: Colors.blue.withOpacity(0.1), - child: Icon(icon, color: Colors.blue, size: 24), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ), - Text(subtitle, style: const TextStyle(fontSize: 12, color: Colors.grey)), ], ), - ), - if (isCompleted) const Icon(Icons.check_circle, color: Colors.blue), - ], - ), - ); - } - - /// 3. 构建本月记录卡片 - Widget _buildMonthlyRecordCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '本月记录', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + if (GetPlatform.isIOS) { + // 跳转到 iOS 应用商店 (这里使用一个通用的应用商店链接模板,请确保替换为正式的 AppID) + openWebPage("https://apps.apple.com/cn/app/羚牛氢能/6756245815"); + } else if (GetPlatform.isAndroid) { + // Android 弹出二维码图片 + _showAndroidDownloadDialog(context); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF006633), + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 48), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + elevation: 0, + ), + child: const Text( + "下载推荐", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), ), - const SizedBox(height: 8), - _buildRecordRow(Icons.rate_review, '加氢预约践行率', controller.rate), - const Divider(), - _buildRecordRow( - Icons.report_problem_outlined, - '违章', - "${controller.historyBreakRules}起", - ), - const Divider(), - _buildRecordRow(Icons.car_crash_outlined, '交通事故', "${controller.accident}起"), ], ), ), ); } - // 本月记录中的列表项 - Widget _buildRecordRow(IconData icon, String title, String value) { - return ListTile( - contentPadding: EdgeInsets.zero, - leading: CircleAvatar( - radius: 20, - backgroundColor: Colors.blue.withOpacity(0.1), - child: Icon(icon, color: Colors.blue, size: 24), + /// Android 端下载二维码弹窗 + void _showAndroidDownloadDialog(BuildContext context) { + Get.dialog( + Center( + child: Container( + width: 280.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + const Text( + "扫描二维码下载", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 20), + // 使用 LoginUtil.getAssImg 加载你的图片 android_apk_img.png + SizedBox( + width: 180.w, + height: 180.w, + child: LoginUtil.getAssImg('android_apk_img'), + ), + const SizedBox(height: 16), + const Text( + "请让被推荐人扫描上方二维码进行下载安装", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 13, color: Colors.grey), + ), + ], + ), + ), + const Divider(height: 1), + TextButton( + onPressed: () => Get.back(), + style: TextButton.styleFrom(minimumSize: const Size(double.infinity, 50)), + child: const Text( + "确 定", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), ), - title: Text(title, style: const TextStyle(fontSize: 14)), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(value, style: const TextStyle(color: AppTheme.themeColor, fontSize: 14)), - ], - ), - onTap: () { - // TODO: 处理点击事件 - }, ); } - /// 4. 构建提示信息卡片 - Widget _buildTipsCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildInfoItem(Icons.info_outline, '保持良好的驾驶习惯,提高安全评分'), - const SizedBox(height: 10), - _buildInfoItem(Icons.rule, '遵守交通规则,避免违章扣分'), - const SizedBox(height: 10), - _buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'), - const SizedBox(height: 10), - Row( - children: [ - Icon(Icons.verified_outlined, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: FutureBuilder( - future: getVersion(), - builder: (context, snapshot) { - // 判断是否还在加载 - if (snapshot.connectionState == ConnectionState.waiting) { - return const Text(""); - } - - // 如果加载完成且有数据 - if (snapshot.hasData) { - return TextX.labelSmall( - "当前版本: ${snapshot.data}", - color: Colors.black54, - ); - } - - // 错误处理 + /// 安全提醒卡片 + Widget _buildSafetyReminderCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color.fromRGBO(242, 249, 248, 1), // 极浅绿色背景 + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.info_outline, color: Colors.green[700], size: 24), + const SizedBox(width: 8), + Text( + "安全提醒", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.green[900], + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + "请保持良好驾驶习惯,提高安全评分,遵守交通规则,避免违章扣分。", + style: TextStyle(fontSize: 13, color: Colors.green[800], height: 1.5), + ), + const SizedBox(height: 8), + Text( + "如有疑问请联系客服:400-021-1773", + style: TextStyle(fontSize: 13, color: Colors.green[800]), + ), + Row( + children: [ + Expanded( + child: FutureBuilder( + future: getVersion(), + builder: (context, snapshot) { + // 判断是否还在加载 + if (snapshot.connectionState == ConnectionState.waiting) { return const Text(""); - }, - ), - ), - ], - ), - ], - ), - ), - ); - } + } - // 提示信息卡片中的列表项 - Widget _buildInfoItem(IconData icon, String text) { - return Row( - children: [ - Icon(icon, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)), - ), - ], + // 如果加载完成且有数据 + if (snapshot.hasData) { + return TextX.labelSmall( + "当前版本: ${snapshot.data}", + color: Colors.green[800], + ); + } + + // 错误处理 + return const Text(""); + }, + ), + ), + ], + ), + ], + ), ); } @@ -372,7 +534,7 @@ class MinePage extends GetView { controller.logout(); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red[400], + backgroundColor: Color.fromRGBO(204, 52, 46, 1), foregroundColor: Colors.white, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), diff --git a/ln_jq_app/lib/pages/c_page/reservation/controller.dart b/ln_jq_app/lib/pages/c_page/reservation/controller.dart index c7173c5..d95fb69 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -536,6 +536,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin { String leftHydrogen = "0"; num maxHydrogen = 0; String difference = ""; + var progressValue = 0.0; //用来管理查看预约的弹窗 Worker? _sheetWorker; @@ -551,12 +552,36 @@ class C_ReservationController extends GetxController with BaseControllerMixin { getUserBindCarInfo(); getSiteList(); startAutoRefresh(); + _msgNotice(); + if (!init) { _setupListener(); init = true; } } + bool isNotice = false; + + Future _msgNotice() async { + final Map requestData = { + 'appFlag': 1, + 'isRead': 1, + 'pageNum': 1, + 'pageSize': 5, + }; + final response = await HttpService.to.get( + 'appointment/unread_notice/page', + params: requestData, + ); + if (response != null) { + final result = BaseModel.fromJson(response.data); + if (result.code == 0 && result.data != null) { + String total = result.data["total"].toString(); + isNotice = int.parse(total) > 0; + } + } + } + @override void onPaused() { stopAutoRefresh(); @@ -644,9 +669,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin { void getCatinfo() async { try { - HttpService.to.setBaseUrl(AppTheme.car_service_url); var responseData = await HttpService.to.post( - 'VehicleData/getHydrogenInfoByPlateNumber', + 'appointment/vehicle/getHydrogenInfoByPlateNumber', data: { 'userName': "xll@lingniu", 'password': "4q%3!l6s0p", @@ -671,11 +695,28 @@ class C_ReservationController extends GetxController with BaseControllerMixin { amountController.text = flooredDifference.toString(); } + if (maxHydrogen > 0) { + progressValue = leftHydrogenNum / maxHydrogen; + + // 边界处理:确保值在 0 到 1 之间 + if (progressValue > 1.0) progressValue = 1.0; + if (progressValue < 0.0) progressValue = 0.0; + } + updateUi(); - } catch (e) { - } finally { - HttpService.to.setBaseUrl(AppTheme.test_service_url); - } + } catch (e) {} + renderSliderTheme(); + } + + double current = 0.0; + double maxVal = 0.0; + + void renderSliderTheme() { + current = double.tryParse(amountController.text) ?? 0.0; + maxVal = double.tryParse(difference) ?? 100.0; + if (maxVal <= 0) maxVal = 100.0; + + updateUi(); } void getSiteList() async { diff --git a/ln_jq_app/lib/pages/c_page/reservation/view.dart b/ln_jq_app/lib/pages/c_page/reservation/view.dart index d785b9c..47a22c4 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -1,11 +1,17 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/common/model/station_model.dart'; +import 'package:ln_jq_app/common/styles/theme.dart'; +import 'package:ln_jq_app/pages/c_page/message/view.dart'; +import 'package:ln_jq_app/pages/qr_code/view.dart'; import 'package:ln_jq_app/storage_service.dart'; import 'controller.dart'; +import 'reservation_list_bottomsheet.dart'; ///加氢预约 class ReservationPage extends GetView { @@ -18,25 +24,40 @@ class ReservationPage extends GetView { id: 'reservation', builder: (_) { return Scaffold( - backgroundColor: Colors.grey[100], + backgroundColor: Color.fromRGBO(247, 249, 251, 1), body: GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - }, - child: SingleChildScrollView( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildUserInfoCard(), - const SizedBox(height: 5), - _buildCarInfoCard(), - const SizedBox(height: 5), - _buildReservationFormCard(context), - const SizedBox(height: 5), - _buildTipsCard(), - ], - ), + onTap: () => unfocus(), + child: Stack( + children: [ + Positioned.fill( + child: SingleChildScrollView( + child: Column( + children: [ + _buildUserInfoCard(), + Padding( + padding: EdgeInsets.symmetric(horizontal: 18.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 16.h), + _buildCarInfoCard(), + SizedBox(height: 24.h), + _buildReservationFormCard(context), + SizedBox(height: 180.h), + ], + ), + ), + ], + ), + ), + ), + Positioned( + left: 20.w, + right: 20.w, + bottom: 110.h, + child: _buildReservationItem(context), + ), + ], ), ), ); @@ -44,105 +65,119 @@ class ReservationPage extends GetView { ); } - Widget _buildTipsCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildTipItem(Icons.info_outline, '请提前30分钟到达加氢站'), - const SizedBox(height: 10), - _buildTipItem(Icons.rule, '请确保车辆证件齐全'), - const SizedBox(height: 10), - _buildTipItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'), - ], - ), - ), - ); - } - - // 提示信息卡片中的列表项 - Widget _buildTipItem(IconData icon, String text) { - return Row( - children: [ - Icon(icon, color: Colors.blue, size: 20), - const SizedBox(width: 10), - Expanded( - child: Text(text, style: const TextStyle(fontSize: 12, color: Colors.black54)), - ), - ], - ); - } - /// 构建用户信息卡片 Widget _buildUserInfoCard() { return Card( - elevation: 2, // 轻微的阴影 + elevation: 1, + color: Colors.white, margin: EdgeInsets.zero, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 16, top: 50), child: Row( children: [ - const CircleAvatar( - radius: 20, - backgroundColor: Colors.blue, - child: Icon(Icons.person, color: Colors.white, size: 34), + Stack( + children: [ + CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + child: LoginUtil.getAssImg('ic_user_logo@2x'), + ), + Positioned( + right: 0, + bottom: 0, + child: SizedBox( + height: 16.h, + width: 16.w, + child: LoginUtil.getAssImg('ic_logo@2x'), + ), + ), + ], ), - const SizedBox(width: 12), + SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "${StorageService.to.name}", - style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + Row( + children: [ + Text( + "${StorageService.to.name}", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 8.w), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color.fromRGBO(236, 255, 234, 1), + border: Border.all(color: const Color(0xFFB7E19F)), + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.eco, size: 12, color: Color(0xFF52C41A)), + SizedBox(width: 4), + Text( + "绿色先锋", + style: TextStyle( + color: Color(0xFF52C41A), + fontSize: 10, + ), + ), + ], + ), + ), + ], ), - SizedBox(height: 6), + const SizedBox(height: 4), Text( - "${StorageService.to.phone}", - style: TextStyle(color: Colors.grey, fontSize: 11), + "羚牛ID:${StorageService.to.phone}", + style: const TextStyle(color: Colors.grey, fontSize: 11), ), ], ), ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue, width: 0.5), - ), - child: const Row( - children: [ - Icon(Icons.shield_outlined, color: Colors.blue, size: 14), - SizedBox(width: 4), - Text( - '已认证', - style: TextStyle( - color: Colors.blue, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ], + IconButton( + onPressed: () => Get.to(() => const MessagePage()), + icon: Badge( + smallSize: 8, + backgroundColor: controller.isNotice + ? Colors.red + : Colors.transparent, + child: const Icon( + Icons.notifications_outlined, + color: Colors.black87, + size: 30, + ), ), ), ], ), ), - const Divider(height: 1, indent: 16, endIndent: 16), Padding( - padding: const EdgeInsets.symmetric(vertical: 11.0), + padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildStatItem(controller.fillingWeight, '累计加氢'), - _buildStatItem(controller.fillingTimes, '加氢次数'), + _buildModernStatItem('累计加氢量', '', controller.fillingWeight, ''), + const SizedBox(width: 8), + _buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''), + const SizedBox(width: 8), + _buildModernStatItem('今日里程', '', "7kg", ''), ], ), ), @@ -151,277 +186,554 @@ class ReservationPage extends GetView { ); } - // 用户信息卡片中的小统计项 - Widget _buildStatItem(String value, String label) { - return Column( - children: [ - Text(value, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 11)), - ], - ); - } - - /// 构建车辆信息卡片 - Widget _buildCarInfoCard() { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(11), - child: Row( + Widget _buildModernStatItem(String title, String subtitle, String value, String unit) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - children: [ - _buildInfoRow('车牌号: ${controller.plateNumber}', '扫码绑定'), - const SizedBox(height: 12), - _buildInfoRow('剩余氢量:', '${controller.leftHydrogen}Kg'), - const SizedBox(height: 12), - _buildInfoRow('百公里氢耗:', '${controller.workEfficiency}KG/100KM'), - ], + Text( + title, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.black87, ), ), - const SizedBox(width: 8), - Icon(Icons.propane_rounded, size: 50, color: Colors.blue.withOpacity(0.5)), + Text(subtitle, style: const TextStyle(fontSize: 9, color: Colors.grey)), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + value, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + Text(unit, style: const TextStyle(fontSize: 10, color: Colors.black54)), + ], + ), ], ), ), ); } - // 车辆信息卡片中的信息行 - Widget _buildInfoRow(String label, String value) { - bool isButton = value == '扫码绑定'; - return Row( - children: [ - Text(label, style: const TextStyle(fontSize: 13)), - const SizedBox(width: 8), - isButton - ? GestureDetector( - onTap: () async { - controller.doQrCode(); - }, - child: Container( - margin: EdgeInsetsGeometry.only(left: 10.w), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), - decoration: BoxDecoration( - border: Border.all(color: Colors.blue.shade300, width: 1), - borderRadius: BorderRadius.circular(5), - color: Colors.blue.withOpacity(0.05), - ), - child: Row( - mainAxisSize: MainAxisSize.min, // Keep the row compact + Widget _buildCarInfoCard() { + return Card( + elevation: 2, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded(flex: 4, child: LoginUtil.getAssImg('ic_car_bg@2x')), + const SizedBox(width: 16), + Expanded( + flex: 6, + child: Column( + children: [ + _buildCarDataItem('剩余电量', '36.8%'), + const SizedBox(height: 8), + _buildCarDataItem('剩余氢量', '${controller.leftHydrogen}Kg'), + const SizedBox(height: 8), + _buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'), + const SizedBox(height: 12), + Column( children: [ - Icon( - StorageService.to.hasVehicleInfo ? Icons.repeat : Icons.search, - size: 13, - color: Colors.blue, - ), - const SizedBox(width: 3), - Text( - StorageService.to.hasVehicleInfo ? "换车牌" : value, - style: const TextStyle( - color: Colors.blue, - fontSize: 11, - fontWeight: FontWeight.w500, + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: controller.progressValue, + minHeight: 6, + backgroundColor: const Color(0xFFF0F2F5), + valueColor: const AlwaysStoppedAnimation( + Color(0xFF006633), + ), ), ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "剩余氢量", + style: TextStyle( + fontSize: 10, + color: Colors.grey, + fontWeight: FontWeight.w400, + ), + ), + Text( + "${controller.leftHydrogen}Kg", + style: const TextStyle( + fontSize: 10, + color: Color(0xFF006633), + fontWeight: FontWeight.w600, + ), + ), + ], + ), ], ), - ), - ) - : Text( - value, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), - ), - ], - ); - } - - /// 构建预约表单卡片 - Widget _buildReservationFormCard(BuildContext context) { - return Card( - elevation: 2, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Obx( - () => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildPickerRow( - label: '日期', - value: controller.formattedDate, - icon: Icons.calendar_today_outlined, - onTap: () => controller.pickDate(context), - ), - _buildPickerRow( - label: '预约时间', - value: controller.formattedTimeSlot, - icon: Icons.access_time_outlined, - onTap: () => controller.pickTime(context), - ), - _buildTextField( - label: '预约氢量(KG)', - controller: controller.amountController, - hint: '当前最大可预约氢量${controller.difference}(KG)', - keyboardType: TextInputType.number, - ), - /*_buildTextField( - label: '车牌号', - controller: controller.plateNumberController, - hint: '请输入车牌号', // 修改提示文案 - enabled: false, // 设置为不可编辑 - ),*/ - _buildStationSelector(), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: controller.submitReservation, - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 38), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), - ), - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - ), - child: const Text( - '提交预约', - style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: OutlinedButton( - onPressed: () { - controller.getReservationList(showPopup: true, addStatus: ''); - }, - style: OutlinedButton.styleFrom( - minimumSize: const Size(double.infinity, 38), // 高度与另一个按钮保持一致 - side: const BorderSide(color: Colors.blue), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), - ), - ), - child: const Text( - '查看预约', - style: TextStyle( - color: Colors.blue, - fontSize: 13, - fontWeight: FontWeight.bold, - ), - ), - ), - ), ], ), - ], - ), + ), + ], ), ), ); } - // 表单中的可点击行 (用于日期和时间选择) - Widget _buildPickerRow({ - required String label, - required String value, - required IconData icon, - required VoidCallback onTap, - }) { - return Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13)), - const SizedBox(height: 8), - InkWell( - onTap: onTap, + Widget _buildCarDataItem(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)), + Text( + value, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ); + } + + /// --- 构建预约表单卡片--- + Widget _buildReservationFormCard(BuildContext context) { + return Column( + children: [ + // 1. 顶部:日期与时间选择 + Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "预约日期与时间", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 20), + _buildHorizontalDateSelector(), + const SizedBox(height: 32), + _buildTimeSlider(context), + ], + ), + ), + ), + const SizedBox(height: 12), + // 2. 底部:氢量与站点 + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildAmountSliderSection()), + const SizedBox(width: 12), + Expanded(child: _buildStationCardSection(context)), + ], + ), + ], + ); + } + + /// 水平日期选择器 + Widget _buildHorizontalDateSelector() { + final DateTime today = DateTime( + DateTime.now().year, + DateTime.now().month, + DateTime.now().day, + ); + final DateTime tomorrow = today.add(const Duration(days: 1)); + final List dates = List.generate( + 5, + (index) => today.add(Duration(days: index)), + ); + const List weekMap = ['日', '一', '二', '三', '四', '五', '六']; + + return SizedBox( + height: 75, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: dates.length, + itemBuilder: (context, index) { + DateTime date = dates[index]; + bool isSelectable = + date.isAtSameMomentAs(today) || date.isAtSameMomentAs(tomorrow); + bool isSelected = + controller.selectedDate.value.year == date.year && + controller.selectedDate.value.month == date.month && + controller.selectedDate.value.day == date.day; + + return GestureDetector( + onTap: isSelectable + ? () { + controller.selectedDate.value = date; + controller.resetTimeForSelectedDate(); + controller.updateUi(); + } + : null, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + width: 58, + margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( - border: Border.all(color: Colors.grey[400]!), - borderRadius: BorderRadius.circular(8), + color: isSelected ? const Color(0xFF006633) : const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(14), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(value, style: const TextStyle(fontSize: 14)), - Icon(icon, color: Colors.grey, size: 20), + Text( + weekMap[date.weekday % 7], + style: TextStyle( + fontSize: 12, + color: isSelected + ? Colors.white70 + : (isSelectable ? Colors.grey : Colors.grey[300]), + ), + ), + const SizedBox(height: 6), + Text( + "${date.day}", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isSelected + ? Colors.white + : (isSelectable ? Colors.black87 : Colors.grey[300]), + ), + ), ], ), ), - ), - ], + ); + }, ), ); } - // 表单中的文本输入框 - Widget _buildTextField({ - required String label, - required TextEditingController controller, - required String hint, - TextInputType? keyboardType, - bool enabled = true, - }) { - bool showCounter = keyboardType == TextInputType.number; - return Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13)), - const SizedBox(height: 8), - TextFormField( - controller: controller, - keyboardType: keyboardType, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, // 只允许数字输入 - ], - enabled: enabled, - style: const TextStyle(fontSize: 14), - decoration: InputDecoration( - isDense: true, - hintText: hint, - hintStyle: TextStyle(fontSize: 14), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - filled: !enabled, - fillColor: Colors.grey[100], - // 左侧减号按钮 - prefixIcon: showCounter - ? IconButton( - icon: const Icon(Icons.remove, color: Colors.blue), - onPressed: () => _updateAmount(-1), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: 40, minHeight: 40), - ) - : null, + /// 时间 Slider 选择器 + Widget _buildTimeSlider(BuildContext context) { + return Obx(() { + // 这里的逻辑对应 Controller 中的 24 小时可用 Slot + int currentIdx = controller.startTime.value.hour; - // 右侧加号按钮 - suffixIcon: showCounter - ? IconButton( - icon: const Icon(Icons.add, color: Colors.blue), - onPressed: () => _updateAmount(1), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: 40, minHeight: 40), - ) - : null, + return Column( + children: [ + Stack( + alignment: Alignment.topCenter, + clipBehavior: Clip.none, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFFE6F4EA), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.white, width: 2), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + controller.formattedTimeSlot, + style: const TextStyle( + color: Color(0xFF006633), + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 10, + activeTrackColor: const Color(0xFF006633), + inactiveTrackColor: const Color(0xFFF0F2F5), + thumbColor: Colors.white, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 12, + elevation: 4, + ), + overlayColor: const Color(0xFF006633).withOpacity(0.1), + ), + child: Slider( + value: currentIdx.toDouble(), + min: 0, + max: 23, + divisions: 23, + onChanged: (val) { + int hour = val.toInt(); + // 模拟 Controller 中的 pickTime 逻辑校验 + final now = DateTime.now(); + final isToday = + controller.selectedDate.value.year == now.year && + controller.selectedDate.value.month == now.month && + controller.selectedDate.value.day == now.day; + + if (isToday && hour < now.hour) { + // 如果是今天且小时数小于当前,则忽略 + return; + } + + controller.startTime.value = TimeOfDay(hour: hour, minute: 0); + controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0); + }, ), ), ], + ); + }); + } + + /// 氢量滑块区域 + Widget _buildAmountSliderSection() { + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.all(13.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "预约氢量", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const Text( + "Refuel Amount", + style: TextStyle(fontSize: 10, color: Colors.grey), + ), + const SizedBox(height: 24), + Column( + children: [ + SliderTheme( + data: SliderTheme.of(Get.context!).copyWith( + trackHeight: 6, + activeTrackColor: const Color(0xFF006633), + inactiveTrackColor: const Color(0xFFF0F2F5), + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 9), + ), + child: Slider( + value: controller.current > controller.maxVal + ? controller.maxVal + : controller.current, + min: 0, + max: controller.maxVal, + onChanged: (val) { + final safeVal = val < 1 ? 1 : val; // 最小 1 + controller.amountController.text = safeVal.toStringAsFixed(0); + controller.renderSliderTheme(); + }, + ), + ), + const SizedBox(height: 12), + Container( + height: 40.h, + decoration: BoxDecoration( + color: const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () => _updateAmount(-1), + icon: const Icon(Icons.remove, size: 18, color: Colors.grey), + ), + Text( + "${controller.amountController.text} Kg", + style: const TextStyle(fontSize: 14, color: Colors.black87), + ), + IconButton( + onPressed: () => _updateAmount(1), + icon: const Icon(Icons.add, size: 18, color: Colors.grey), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } + + /// 站点卡片区域 + + Widget _buildStationCardSection(BuildContext context) { + return Obx(() { + return DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + + /// 当前选中值 + value: controller.selectedStationId.value, + + hint: const Text('请选择加氢站', style: TextStyle(fontSize: 14, color: Colors.grey)), + + /// 下拉数据 + items: controller.stationOptions + .map( + (station) => DropdownMenuItem( + value: station.hydrogenId, + enabled: station.isSelect == 1, + child: _buildDropdownItem(station), + ), + ) + .toList(), + + /// 选中回调 + onChanged: (value) { + if (value != null) { + controller.selectedStationId.value = value; + } + }, + + ///作为 Dropdown 的触发按钮 + customButton: _buildStationCard(), + + /// 隐藏按钮自身样式 + buttonStyleData: const ButtonStyleData(padding: EdgeInsets.zero), + + /// 隐藏默认箭头 + iconStyleData: const IconStyleData(icon: SizedBox.shrink()), + + /// 下拉样式 + dropdownStyleData: DropdownStyleData( + maxHeight: 300, + width: MediaQuery.of(context).size.width / 1.3, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), + ), + + /// 下拉项高度 + menuItemStyleData: const MenuItemStyleData(height: 60), + ), + ); + }); + } + + Widget _buildStationCard() { + final stationId = controller.selectedStationId.value; + final station = controller.stationOptions.firstWhereOrNull( + (s) => s.hydrogenId == stationId, + ); + + return Card( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.all(13.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "加氢站", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text("Station", style: TextStyle(fontSize: 10, color: Colors.grey)), + ], + ), + Icon(Icons.more_vert, color: Colors.grey[300], size: 20), + ], + ), + const SizedBox(height: 24), + Container( + width: double.infinity, + height: 100, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + image: const DecorationImage( + image: AssetImage("assets/images/bg_map@2x.png"), + fit: BoxFit.cover, + ), + ), + child: Stack( + children: [ + Center( + child: Icon( + Icons.location_on_outlined, + color: Colors.grey[300], + size: 40, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + child: Text( + "${station?.name ?? '请选择站点'} | ${station?.price ?? '0.00'}/Kg", + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + ), + ], + ), ), ); } - // :更新氢量逻辑 void _updateAmount(int change) { // 获取当前输入框的值,默认为 0 double currentAmount = double.tryParse(controller.amountController.text) ?? 0; @@ -450,98 +762,48 @@ class ReservationPage extends GetView { controller.amountController.selection = TextSelection.fromPosition( TextPosition(offset: controller.amountController.text.length), ); + controller.updateUi(); } - Widget _buildStationSelector() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + Widget _buildReservationItem(BuildContext context) { + return Row( children: [ - Container( - padding: EdgeInsets.all(0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('加氢站', style: TextStyle(color: Colors.grey[600], fontSize: 14)), - TextButton( - onPressed: () { - controller.getSiteList(); - }, - child: const Text('刷新'), + Expanded( + flex: 1, + child: OutlinedButton( + onPressed: () => + controller.getReservationList(showPopup: true, addStatus: ''), + style: OutlinedButton.styleFrom( + minimumSize: Size(double.infinity, 50.h), + side: const BorderSide(color: Color.fromRGBO(226, 232, 240, 1)), + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), + ), + child: const Text( + '查看预约', + style: TextStyle( + color: Color.fromRGBO(119, 119, 119, 1), + fontSize: 14, + fontWeight: FontWeight.bold, ), - ], + ), ), ), - Obx( - () => DropdownButtonHideUnderline( - child: DropdownButton2( - isExpanded: true, - hint: const Text( - '请选择加氢站', - style: TextStyle(fontSize: 14, color: Colors.grey), - ), - // items 列表现在从 stationOptions (StationModel列表) 构建 - items: controller.stationOptions - .map( - (station) => DropdownMenuItem( - value: station.hydrogenId, // value 是站点的唯一ID - enabled: station.isSelect == 1, - child: _buildDropdownItem(station), // child 是自定义的 Widget - ), - ) - .toList(), - value: - // 当前的站点 处理默认 - controller.selectedStationId.value ?? - (controller.stationOptions.isNotEmpty - ? controller.stationOptions.first.hydrogenId - : null), - // 当前选中的是站点ID - onChanged: (value) { - if (value != null) { - controller.selectedStationId.value = value; - } - }, - customButton: Obx(() { - // 优先从已选中的 ID 查找 - var selectedStation = controller.stationOptions.firstWhereOrNull( - (s) => s.hydrogenId == controller.selectedStationId.value, - ); - - // 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认 - final stationToShow = - selectedStation ?? - (controller.stationOptions.isNotEmpty - ? controller.stationOptions.first - : null); - - // 如果有要显示的站点,就构建按钮 - if (stationToShow != null) { - return _buildSelectedStationButton(stationToShow); - } - - // 否则,返回一个空占位符,让 hint 生效 - // DropdownButton2 内部会判断,如果 customButton 返回的不是一个有效Widget(或根据其内部逻辑),就会显示 hint - return const SizedBox.shrink(); - }), - buttonStyleData: ButtonStyleData( - height: 40, // 增加高度以容纳两行文字 - padding: const EdgeInsets.symmetric(horizontal: 12.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey[400]!), - ), - ), - iconStyleData: const IconStyleData( - icon: Icon(Icons.arrow_drop_down, color: Colors.grey), - iconSize: 24, - ), - dropdownStyleData: DropdownStyleData( - maxHeight: 300, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), - ), - menuItemStyleData: const MenuItemStyleData( - height: 60, // 增加下拉项的高度 - ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: ElevatedButton( + onPressed: controller.submitReservation, + style: ElevatedButton.styleFrom( + minimumSize: Size(double.infinity, 50.h), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), + backgroundColor: const Color(0xFF006633), + foregroundColor: Colors.white, + elevation: 4, + ), + child: const Text( + '提交预约', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), ), ), diff --git a/ln_jq_app/lib/pages/common/webview/controller.dart b/ln_jq_app/lib/pages/common/webview/controller.dart new file mode 100644 index 0000000..7094cec --- /dev/null +++ b/ln_jq_app/lib/pages/common/webview/controller.dart @@ -0,0 +1,26 @@ +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:get/get.dart'; + +class WebController extends GetxController { + late String title; + late String url; + + final RxDouble progress = 0.0.obs; + InAppWebViewController? webViewController; + + @override + void onInit() { + super.onInit(); + // 从参数中获取标题和URL + title = Get.arguments['title'] ?? '详情'; + url = Get.arguments['url'] ?? ''; + } + + void onWebViewCreated(InAppWebViewController controller) { + webViewController = controller; + } + + void onProgressChanged(InAppWebViewController controller, int progressValue) { + progress.value = progressValue / 100; + } +} diff --git a/ln_jq_app/lib/pages/common/webview/view.dart b/ln_jq_app/lib/pages/common/webview/view.dart new file mode 100644 index 0000000..910dcc2 --- /dev/null +++ b/ln_jq_app/lib/pages/common/webview/view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:get/get.dart'; + +import 'controller.dart'; + +class WebViewPage extends GetView { + const WebViewPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Get.put(WebController()); + + return Scaffold( + appBar: AppBar( + title: Text(controller.title), + centerTitle: true, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(2.0), + child: Obx( + () => controller.progress.value < 1.0 + ? LinearProgressIndicator( + value: controller.progress.value, + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).primaryColor, + ), + minHeight: 2.0, + ) + : const SizedBox(height: 2.0), + ), + ), + ), + body: InAppWebView( + initialUrlRequest: URLRequest(url: WebUri(controller.url)), + initialSettings: InAppWebViewSettings( + isInspectable: true, + javaScriptEnabled: true, + javaScriptCanOpenWindowsAutomatically: true, + useShouldOverrideUrlLoading: true, + mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW, + mediaPlaybackRequiresUserGesture: false, + allowsInlineMediaPlayback: true, + ), + onWebViewCreated: controller.onWebViewCreated, + onProgressChanged: controller.onProgressChanged, + ), + ); + } +} diff --git a/ln_jq_app/lib/pages/login/controller.dart b/ln_jq_app/lib/pages/login/controller.dart index aa5d726..e0595c6 100644 --- a/ln_jq_app/lib/pages/login/controller.dart +++ b/ln_jq_app/lib/pages/login/controller.dart @@ -1,4 +1,7 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/model/base_model.dart'; class LoginController extends GetxController with BaseControllerMixin { @override @@ -7,19 +10,73 @@ class LoginController extends GetxController with BaseControllerMixin { LoginController(); // 控制输入框的 TextEditingController - final TextEditingController driverIdentityController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController codeController = TextEditingController(); + final TextEditingController stationIdController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + + // --- 倒计时逻辑 --- + final RxInt countdown = 0.obs; + Timer? _timer; + + void startCountdown() async { + if (phoneController.text.isEmpty || !phoneController.text.isPhoneNumber) { + showToast("请输入正确的手机号"); + return; + } + + if (countdown.value > 0) return; + + // 调用发送验证码接口 + var responseData = await HttpService.to.post( + 'appointment/login/sendCode', + data: {"mobile": phoneController.text}, + ); + + if (responseData == null) { + showToast('验证码发送失败,请稍后重试'); + return; + } + + try { + var result = BaseModel.fromJson(responseData.data); + + if (result.code != 0) { + showToast(result.error); + dismissLoading(); + return; + } + + showToast("验证码已发送"); + + countdown.value = 60; + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (countdown.value > 0) { + countdown.value--; + } else { + _timer?.cancel(); + } + }); + } catch (e) { + showToast('验证码服务异常,请稍后重试'); + } + } + @override void onInit() { - super.onInit(); } @override void onClose() { + _timer?.cancel(); + phoneController.dispose(); + codeController.dispose(); + + stationIdController.dispose(); + passwordController.dispose(); super.onClose(); } - } diff --git a/ln_jq_app/lib/pages/login/view.dart b/ln_jq_app/lib/pages/login/view.dart index 37d6f8f..ac8f4b2 100644 --- a/ln_jq_app/lib/pages/login/view.dart +++ b/ln_jq_app/lib/pages/login/view.dart @@ -1,6 +1,9 @@ +import 'dart:io'; + import 'package:aliyun_push_flutter/aliyun_push_flutter.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:ln_jq_app/common/login_util.dart'; @@ -9,6 +12,7 @@ import 'package:ln_jq_app/common/model/vehicle_info.dart'; import 'package:ln_jq_app/common/styles/theme.dart'; import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart'; import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart'; +import 'package:ln_jq_app/pages/common/webview/view.dart'; import 'package:ln_jq_app/pages/login/controller.dart'; import 'package:ln_jq_app/pages/url_host/view.dart'; import 'package:ln_jq_app/storage_service.dart'; @@ -21,429 +25,620 @@ class LoginPage extends StatefulWidget { } class _LoginPageState extends State with SingleTickerProviderStateMixin { - late TabController tabController; - - bool cLogin = true; + late TabController _tabController; + bool _isAgreed = false; bool _obscureText = true; - - // 用于管理“记住密码”的复选框状态 bool _rememberPassword = true; - - // 用于确保凭证只在首次加载时回填一次 bool _credentialsLoaded = false; @override void initState() { super.initState(); - tabController = TabController(length: 2, vsync: this); - tabController.addListener(_tabChangeListener); - } - - void _tabChangeListener() { - if (!tabController.indexIsChanging) { - switchTab(tabController.index); - } - } - - void switchTab(int index) { - setState(() { - cLogin = (index == 0); + _tabController = TabController(length: 2, vsync: this); + _tabController.addListener(() { + if (!_tabController.indexIsChanging) { + setState(() {}); + } }); } @override void dispose() { - tabController.dispose(); + _tabController.dispose(); super.dispose(); } - Widget _buildView(LoginController controller) { - // 在视图构建时,检查并回填已保存的凭证 - if (!_credentialsLoaded) { - final savedAccount = StorageService.to.stationAccount; - final savedPassword = StorageService.to.stationPassword; - if (savedAccount != null && savedPassword != null) { - controller.stationIdController.text = savedAccount; - controller.passwordController.text = savedPassword; - _rememberPassword = true; // 如果有保存的密码,则默认勾选 - } - _credentialsLoaded = true; // 标记为已加载,防止重复执行 - } - - return Container( - color: Color(0xFFEFF4F7), - child: [ - Icon(cLogin ? AntdIcon.car : AntdIcon.USB), - SizedBox(height: 5.h), - TextX.bodyLarge(cLogin ? '司机端' : "加氢站", weight: FontWeight.w700), - SizedBox(height: 5.h), - TextX.bodyLarge(cLogin ? '安全驾驶·智能服务' : "氢能服务·专业运营"), - Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), // 设置圆角弧度 - ), - margin: EdgeInsets.all(15), - elevation: 4, - child: Container( - height: cLogin ? 285.h : 360.h, - padding: EdgeInsets.all(15), - child: Column( - children: [ - Card( - elevation: 2, - child: Container( - height: 55.h, - padding: EdgeInsets.all(3), - child: TabBar( - controller: tabController, - onTap: (index) { - delayed(300, () { - switchTab(index); - }); - }, - labelColor: Colors.white, - unselectedLabelColor: Colors.black, - indicator: BoxDecoration( - color: AppTheme.themeColor, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.blue.withOpacity(0.2), - spreadRadius: 1, - blurRadius: 6, - ), - ], - ), - tabs: [ - Tab(text: '司机端登录'), - Tab(text: '加氢站登录'), - ], - isScrollable: false, - ), - ), - ), - Flexible( - child: TabBarView( - controller: tabController, - children: [ - _driverLoginView(controller), - _stationLoginView(controller), - ], - ), - ), - ], - ), - ), - ), - ].toColumn(mainAxisSize: MainAxisSize.min).center(), - ); - } - - Widget _driverLoginView(LoginController controller) { - return !cLogin - ? SizedBox() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20.h), - TextFormField( - controller: controller.driverIdentityController, - cursorColor: AppTheme.themeColor, - maxLength: 8, - style: TextStyle(fontSize: 14), - decoration: InputDecoration( - hintText: '请输入身份后8位', - border: OutlineInputBorder(), - hintStyle: TextStyle(fontSize: 14), - contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - prefixIcon: Icon(Icons.person_2_outlined, color: Colors.grey), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.themeColor), - ), - ), - ), - SizedBox(height: 20.h), - ElevatedButton( - onPressed: () async { - String password = controller.driverIdentityController.text; - if (password.isEmpty) { - showToast("请输入密码"); - return; - } - showLoading('登录中...'); - try { - var responseData = await HttpService.to.post( - 'appointment/login/loginForDriver', - data: {'idNo': password}, - ); - if (responseData == null && responseData!.data == null) { - dismissLoading(); - showToast('登录失败:无法获取凭证'); - return; - } - //登录信息处理 - try { - var result = BaseModel.fromJson(responseData.data); - - if (result.code != 0) { - showToast(result.error); - dismissLoading(); - return; - } - - String token = result.data['token'] ?? ''; - String idCard = result.data['idCard'] ?? ''; - String name = result.data['name'] ?? ''; - String phone = result.data['phone'] ?? ''; - await StorageService.to.saveLoginInfo( - token: token, - userId: "", - channel: "driver", - idCard: idCard, - name: name, - phone: phone, - ); - - //注册推送别名 - addAlias(phone); - - //登录后查询已绑定车辆信息 - var carInfo = await HttpService.to.get( - "appointment/driver/getTruckInfoByDriver?phone=$phone", - ); - if (carInfo != null) { - var carInforesult = BaseModel.fromJson(carInfo.data); - if (carInforesult.data != null) { - final vehicle = VehicleInfo.fromJson( - carInforesult.data as Map, - ); - //保存使用 - await StorageService.to.saveVehicleInfo(vehicle); - } - } - - //页面操作 - dismissLoading(); - showToast('登录成功,欢迎您'); - Get.offAll(() => BaseWidgetsPage()); - } catch (e) { - dismissLoading(); - showToast('登录失败:数据异常'); - } - } catch (e) { - dismissLoading(); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.themeColor, - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - child: Text('登录'), - ), - ], - ); - } - - Widget _stationLoginView(LoginController controller) { - return cLogin - ? SizedBox() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20), - TextFormField( - controller: controller.stationIdController, - cursorColor: AppTheme.themeColor, - style: TextStyle(fontSize: 14), - decoration: InputDecoration( - hintText: '请输入加氢站编号', - border: OutlineInputBorder(), - hintStyle: TextStyle(fontSize: 14), - contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - prefixIcon: Icon(Icons.person_2_outlined, color: Colors.grey), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.themeColor), - ), - ), - ), - SizedBox(height: 10), - TextFormField( - controller: controller.passwordController, - obscureText: _obscureText, - style: TextStyle(fontSize: 14), - cursorColor: AppTheme.themeColor, - decoration: InputDecoration( - hintStyle: TextStyle(fontSize: 14), - contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - hintText: '请输入密码', - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(_obscureText ? Icons.visibility_off : Icons.visibility), - onPressed: () { - setState(() { - _obscureText = !_obscureText; - }); - }, - ), - prefixIcon: Icon(Icons.lock_outline, color: Colors.grey), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: AppTheme.themeColor), - ), - ), - ), - SizedBox(height: 10), - //记住密码复选框 --- - Row( - children: [ - SizedBox( - height: 24, - width: 24, - child: Checkbox( - value: _rememberPassword, - activeColor: AppTheme.themeColor, - onChanged: (bool? value) { - setState(() { - _rememberPassword = value ?? true; - }); - }, - ), - ), - GestureDetector( - onTap: () => setState(() => _rememberPassword = !_rememberPassword), - child: const Text('记住密码', style: TextStyle(color: Colors.grey)), - ), - ], - ), - SizedBox(height: 20), // 调整间距 - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.themeColor, - minimumSize: Size(double.infinity, 50), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - onPressed: () async { - String account = controller.stationIdController.text; - String password = controller.passwordController.text; - - if (account.isEmpty || password.isEmpty) { - showToast("请输入账号和密码"); - return; - } - - showLoading('登录中...'); - - try { - String encryptedPassword = LoginUtil.encrypt(password); - var responseData = await HttpService.to.post( - 'appointment/login/password', - data: { - 'account': account, - 'password': encryptedPassword, - 'loginType': "station", - }, - ); - - if (responseData == null && responseData!.data == null) { - dismissLoading(); - showToast('登录失败:无法获取凭证'); - return; - } - - try { - var result = BaseModel.fromJson(responseData.data); - - if (result.code != 0) { - showToast(result.error); - dismissLoading(); - return; - } - - String token = result.data['token'] ?? ''; - String userId = result.data['userId'] ?? ''; - String mobile = result.data['mobile'] ?? ''; - - await StorageService.to.saveLoginInfo( - token: token, - userId: userId, - phone: mobile, - channel: "station", - ); - - //注册推送别名 - addAlias(mobile); - - // 根据复选框状态保存或清除密码 --- - if (_rememberPassword) { - await StorageService.to.saveStationCredentials(account, password); - } else { - await StorageService.to.clearStationCredentials(); - } - - dismissLoading(); - showToast('登录成功,欢迎您'); - Get.offAll(() => B_BaseWidgetsPage()); - } catch (e) { - dismissLoading(); - showToast('登录失败:数据异常'); - } - } catch (e) { - dismissLoading(); - } - }, - child: Text('登录'), - ), - ], - ); - } - - final _aliyunPush = AliyunPushFlutter(); - - void addAlias(String alias) async { - var result = await _aliyunPush.bindAccount(alias); - - var code = result['code']; - if (code == kAliyunPushSuccessCode) { - Logger.d('添加别名$alias成功'); - } else { - var errorCode = result['code']; - var errorMsg = result['errorMsg']; - Logger.d('添加别名$alias失败: $errorCode - $errorMsg'); - } - } - @override Widget build(BuildContext context) { return GetBuilder( init: LoginController(), id: 'login', builder: (controller) { + // 站点登录凭证回填逻辑 + if (!_credentialsLoaded) { + final savedAccount = StorageService.to.stationAccount; + final savedPassword = StorageService.to.stationPassword; + if (savedAccount != null && savedPassword != null) { + controller.stationIdController.text = savedAccount; + controller.passwordController.text = savedPassword; + _rememberPassword = true; + } + _credentialsLoaded = true; + } + return Scaffold( - body: Stack( - children: [ - Positioned.fill(child: _buildView(controller)), - if (AppTheme.is_show_host) + backgroundColor: Colors.white, + body: GestureDetector( + onTap: () { + hideKeyboard(); + }, + child: Stack( + children: [ + // 1. 顶部背景与装饰 Positioned( - top: 40.h, - right: 20.w, - child: TextButton( - onPressed: () { - Get.to(() => const UrlHostPage()); - }, - child: const Text( - "域名配置", - style: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, + top: 0, + left: 0, + right: 0, + child: LoginUtil.getAssImg("bg_login"), + ), + Positioned( + top: 0, + left: 0, + child: SizedBox( + width: 180.w, + height: 218.h, + child: LoginUtil.getAssImg("ic_login_bg@2x"), + ), + ), + _buildBrandingHeader(), + + // 2. 登录表单主体 + Positioned( + top: 280.h, + left: 0, + right: 0, + bottom: 0, + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(40), + topRight: Radius.circular(40), ), ), + child: Column( + children: [ + const SizedBox(height: 20), + // TabBar 切换 + Container( + margin: EdgeInsets.symmetric(horizontal: 60.w), + child: TabBar( + controller: _tabController, + indicatorColor: const Color(0xFF006633), + indicatorWeight: 3, + labelColor: const Color(0xFF006633), + unselectedLabelColor: Colors.grey, + labelStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + tabs: const [ + Tab(text: "司机登录"), + Tab(text: "站点登录"), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 30.w), + child: Column( + children: [ + const SizedBox(height: 30), + // 根据 Tab 显示不同的输入框 + _tabController.index == 0 + ? _buildDriverInputFields(controller) + : _buildStationInputFields(controller), + + const SizedBox(height: 30), + // 统一登录按钮 + _buildLoginButton(controller), + + const SizedBox(height: 10), + buildAgreement(), + const SizedBox(height: 40), + ], + ), + ), + ), + ], + ), ), ), - ], + Positioned(left: 0, right: 0, bottom: 33.h, child: _buildFooterSlogan()), + if (AppTheme.is_show_host) + Positioned( + top: 40.h, + right: 20.w, + child: TextButton( + onPressed: () => Get.to(() => const UrlHostPage()), + child: const Text( + "域名配置", + style: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), ), ); }, ); } + + /// 品牌头部 + Widget _buildBrandingHeader() { + return Positioned( + top: 0, + left: 32.w, + right: 0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 100.h), + SizedBox(height: 60.h, child: LoginUtil.getAssImg('ic_logo_unbg@2x')), + SizedBox(height: 30.h), + const Text( + "HELLO,", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w500, + color: Color.fromRGBO(51, 51, 51, 1), + ), + ), + const SizedBox(height: 8), + Row( + children: [ + const Text( + "欢迎使用 ", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Color.fromRGBO(51, 51, 51, 1), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color.fromRGBO(56, 198, 151, 1), + borderRadius: BorderRadius.circular(20), + ), + child: const Text( + "“羚牛氢能智慧服务平台”", + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + ); + } + + /// 司机登录输入框 (手机号+验证码) + Widget _buildDriverInputFields(LoginController controller) { + return Column( + children: [ + _buildInputWrapper( + child: TextField( + controller: controller.phoneController, + keyboardType: TextInputType.phone, + decoration: const InputDecoration( + hintText: '请输入手机号', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 24), + ), + ), + ), + const SizedBox(height: 20), + _buildInputWrapper( + child: Row( + children: [ + Expanded( + child: TextField( + controller: controller.codeController, + keyboardType: TextInputType.number, + inputFormatters: [LengthLimitingTextInputFormatter(6)], + decoration: const InputDecoration( + hintText: '请输入验证码', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 24), + ), + ), + ), + Obx( + () => GestureDetector( + onTap: controller.countdown.value == 0 + ? controller.startCountdown + : null, + child: Padding( + padding: const EdgeInsets.only(right: 24.0), + child: Text( + controller.countdown.value == 0 + ? "获取验证码" + : "${controller.countdown.value}s后重发", + style: TextStyle( + color: controller.countdown.value == 0 + ? const Color(0xFF006633) + : Colors.grey, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ); + } + + /// 站点登录输入框 (账号+密码) + Widget _buildStationInputFields(LoginController controller) { + return Column( + children: [ + _buildInputWrapper( + child: TextField( + controller: controller.stationIdController, + decoration: const InputDecoration( + hintText: '请输入加氢站编号', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 24), + ), + ), + ), + const SizedBox(height: 20), + _buildInputWrapper( + child: Row( + children: [ + Expanded( + child: TextField( + controller: controller.passwordController, + obscureText: _obscureText, + decoration: const InputDecoration( + hintText: '请输入密码', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 24), + ), + ), + ), + IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Colors.grey, + ), + onPressed: () => setState(() => _obscureText = !_obscureText), + ), + ], + ), + ), + const SizedBox(height: 10), + Row( + children: [ + SizedBox( + width: 40, + child: Checkbox( + value: _rememberPassword, + activeColor: const Color(0xFF006633), + onChanged: (val) => setState(() => _rememberPassword = val ?? false), + ), + ), + const Text("记住密码", style: TextStyle(color: Colors.grey, fontSize: 14)), + ], + ), + ], + ); + } + + /// 通用输入框包装 + Widget _buildInputWrapper({required Widget child}) { + return Container( + height: 55.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0xFFF7F9FB), + borderRadius: BorderRadius.circular(28), + ), + child: child, + ); + } + + /// 统一登录按钮逻辑 + Widget _buildLoginButton(LoginController controller) { + return ElevatedButton( + onPressed: () => _handleLogin(controller), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF006633), + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 55), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), + elevation: 0, + ), + child: Text( + _tabController.index == 0 ? "司机登录" : "站点登录", + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ); + } + + void _handleLogin(LoginController controller) async { + if (!_isAgreed) { + DialogX.to.showConfirmDialog( + icon: DialogIcon.warn, + content: _buildDialogContent(), + confirmText: '同意', + cancelText: '拒绝', + onConfirm: () { + _isAgreed = true; + controller.updateUi(); + }, + ); + return; + } + _tabController.index == 0 + ? _handleDriverLogin(controller) + : _handleStationLogin(controller); + } + + /// 司机登录逻辑 (短信登录) + void _handleDriverLogin(LoginController controller) async { + if (!_validateAgreed()) return; + String phone = controller.phoneController.text; + String code = controller.codeController.text; + if (phone.isEmpty || !phone.isPhoneNumber) { + showToast("请输入正确的手机号"); + return; + } + if (code.isEmpty) { + showToast("请输入验证码"); + return; + } + + showLoading('登录中...'); + try { + var responseData = await HttpService.to.post( + 'appointment/login/login', + data: {'mobile': phone, 'code': code}, + ); + _processLoginResponse(responseData, "driver", phone); + } catch (e) { + dismissLoading(); + } + } + + /// 站点登录逻辑 (账号密码) + void _handleStationLogin(LoginController controller) async { + if (!_validateAgreed()) return; + String account = controller.stationIdController.text; + String password = controller.passwordController.text; + if (account.isEmpty || password.isEmpty) { + showToast("请输入账号和密码"); + return; + } + + showLoading('登录中...'); + try { + String encryptedPassword = LoginUtil.encrypt(password); + var responseData = await HttpService.to.post( + 'appointment/login/password', + data: {'account': account, 'password': encryptedPassword, 'loginType': "station"}, + ); + + if (_rememberPassword) { + await StorageService.to.saveStationCredentials(account, password); + } else { + await StorageService.to.clearStationCredentials(); + } + + _processLoginResponse(responseData, "station", account); + } catch (e) { + dismissLoading(); + } + } + + bool _validateAgreed() { + if (!_isAgreed) { + DialogX.to.showConfirmDialog( + icon: DialogIcon.warn, + message: '请阅读并同意用户协议和隐私政策', + confirmText: '确定', + onConfirm: () {}, + ); + return false; + } + return true; + } + + void _processLoginResponse( + dynamic responseData, + String channel, + String identifier, + ) async { + if (responseData == null || responseData.data == null) { + dismissLoading(); + showToast('登录失败'); + return; + } + var result = BaseModel.fromJson(responseData.data); + if (result.code != 0) { + showToast(result.error); + dismissLoading(); + return; + } + + String token = result.data['token'] ?? ''; + if (channel == "driver") { + await StorageService.to.saveLoginInfo( + token: token, + userId: "", + channel: "driver", + name: result.data['name'], + phone: result.data['phone'], + ); + // 成功后自动获取车辆信息 + try { + var carInfo = await HttpService.to.get( + "appointment/driver/getTruckInfoByDriver?phone=${result.data['phone']}", + ); + if (carInfo != null) { + var carInforesult = BaseModel.fromJson(carInfo.data); + if (carInforesult.data != null) { + final vehicle = VehicleInfo.fromJson( + carInforesult.data as Map, + ); + //保存使用 + await StorageService.to.saveVehicleInfo(vehicle); + } + } + } catch (e) { + Logger.d("暂时不处理 查询车辆信息失败的情况"); + } + + + dismissLoading(); + Get.offAll(() => BaseWidgetsPage()); + } else { + await StorageService.to.saveLoginInfo( + token: token, + userId: result.data['userId'], + phone: result.data['mobile'], + channel: "station", + ); + dismissLoading(); + Get.offAll(() => B_BaseWidgetsPage()); + } + addAlias(identifier); + } + + final _aliyunPush = AliyunPushFlutter(); + + void addAlias(String alias) async { + await _aliyunPush.addAlias(alias); + } + + Widget buildAgreement() { + return Padding( + padding: EdgeInsets.only(top: 13.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 勾选框 + SizedBox( + width: 22.w, + height: 22.h, + child: Checkbox( + value: _isAgreed, + activeColor: AppTheme.themeColor, + // 简单的圆角样式 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + onChanged: (bool? value) { + setState(() { + _isAgreed = value ?? false; + }); + }, + ), + ), + const SizedBox(width: 4), + // 富文本协议部分 + Text.rich( + TextSpan( + text: '我已阅读并同意', + style: const TextStyle(color: Colors.grey, fontSize: 13), + children: [ + TextSpan( + text: '《用户协议》', + style: TextStyle(color: AppTheme.themeColor, fontSize: 13), + recognizer: TapGestureRecognizer() + ..onTap = () { + openPage("用户协议", "https://lnh2e.com/user_agreement.html"); + }, + ), + const TextSpan(text: ' 和 '), + TextSpan( + text: '《隐私政策》', + style: TextStyle(color: AppTheme.themeColor, fontSize: 13), + recognizer: TapGestureRecognizer() + ..onTap = () { + openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html"); + }, + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildDialogContent() { + return RichTextX( + children: [ + TextSpanItem('请阅读并同意'), + TextSpanItem( + '《隐私协议》', + onTap: () => openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html"), + ), + TextSpanItem('和'), + TextSpanItem( + '《用户政策》', + onTap: () => openPage("用户协议", "https://lnh2e.com/user_agreement.html"), + ), + TextSpanItem(',我们将在协议框架内为您提供更优质的服务。'), + ], + ); + } + + void openPage(String title, String url) { + if (Platform.isIOS) { + openWebPage(url); + return; + } + Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url}); + } + + Widget _buildFooterSlogan() { + return Center( + child: Column( + children: [ + Text( + "H Y P A I", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(51, 51, 51, 1), + letterSpacing: 8, + ), + ), + Text( + "HYDROGEN MOBILITY", + style: TextStyle( + fontWeight: FontWeight.w300, + fontSize: 9, + color: Colors.grey.shade400, + letterSpacing: 1, + ), + ), + ], + ), + ); + } } diff --git a/ln_jq_app/lib/pages/url_host/controller.dart b/ln_jq_app/lib/pages/url_host/controller.dart index 4f1d78a..b76241f 100644 --- a/ln_jq_app/lib/pages/url_host/controller.dart +++ b/ln_jq_app/lib/pages/url_host/controller.dart @@ -10,12 +10,14 @@ class UrlHostController extends GetxController { // 预设的域名列表 final List presetUrls = [ 'https://beta-esg.api.lnh2e.com/', // 测试环境 + 'http://47.101.201.13:8443/api/', // 测试环境 'http://192.168.110.44:8080/', // 沈辰本地 'http://192.168.110.222:8080/', // 何斐本地 ]; final List urlNames = [ '测试环境', + '线上环境', '沈辰本地环境', '何斐本地环境', ]; diff --git a/ln_jq_app/lib/pages/welcome/controller.dart b/ln_jq_app/lib/pages/welcome/controller.dart new file mode 100644 index 0000000..151e33a --- /dev/null +++ b/ln_jq_app/lib/pages/welcome/controller.dart @@ -0,0 +1,32 @@ +import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:get/get.dart'; +import 'package:ln_jq_app/pages/home/view.dart'; +import 'package:ln_jq_app/pages/login/view.dart'; +import 'package:ln_jq_app/storage_service.dart'; + +class WelcomeController extends GetxController { + @override + void onReady() { + super.onReady(); + // 移除原生闪屏页(如果有的话) + FlutterNativeSplash.remove(); + _startTimer(); + } + + void _startTimer() { + // 1.5秒后执行跳转逻辑 + Future.delayed(const Duration(milliseconds: 1500), () { + Get.offAll(() => const HomePage()); + }); + } + + void _jumpToNextPage() { + if (StorageService.to.isLoggedIn) { + // 已登录,跳转到首页 + Get.offAll(() => const HomePage()); + } else { + // 未登录,跳转到登录页 + Get.offAll(() => const LoginPage()); + } + } +} diff --git a/ln_jq_app/lib/pages/welcome/view.dart b/ln_jq_app/lib/pages/welcome/view.dart new file mode 100644 index 0000000..3f67a90 --- /dev/null +++ b/ln_jq_app/lib/pages/welcome/view.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ln_jq_app/common/login_util.dart'; +import 'controller.dart'; + +class WelcomePage extends GetView { + const WelcomePage({super.key}); + + @override + Widget build(BuildContext context) { + // 初始化控制器 + Get.put(WelcomeController()); + + return Scaffold( + backgroundColor: Colors.white, + body: SizedBox.expand( + child: Stack( + children: [ + Positioned( + top: 0, + bottom: 0, + left: 0, + right: 0, + child: Image.asset( + 'assets/images/welcome.png', + fit: BoxFit.fill + ), + ), + ], + ), + ), + ); + } +}