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'; import 'controller.dart'; ///加氢预约 class SitePage extends GetView { const SitePage({super.key}); @override Widget build(BuildContext context) { return GetBuilder( init: SiteController(), id: 'site', builder: (_) { return Scaffold( backgroundColor: Color.fromRGBO(247, 249, 251, 1), body: SingleChildScrollView(child: _buildView(context)), ); }, ); } // 主视图 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: [ _buildSearchView(), controller.hasReservationData ? _buildReservationListView() : _buildEmptyReservationView(), ], ), 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, ), ), SizedBox(height: 6.h), Text( "数据每五分钟自动刷新,如需实时更新请下拉页面", 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, ), ), ], ), ], ), ), 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, ), ), ), ], ), 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: EdgeInsets.fromLTRB(0, 0, 0, 8.h), child: Row( children: [ Expanded( child: SizedBox( height: 42.h, child: TextField( textAlignVertical: TextAlignVertical.center, controller: controller.searchController, // 绑定控制器 decoration: InputDecoration( filled: true, isDense: true, fillColor: Colors.white, hintText: '输入车牌号或手机号搜索...', contentPadding: const EdgeInsets.symmetric( vertical: 12, horizontal: 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color.fromRGBO(229, 231, 235, 1)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color.fromRGBO(229, 231, 235, 1)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: Color.fromRGBO(229, 231, 235, 1), width: 1.5, ), ), // 清除按钮 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) { // 用户在键盘上点击“完成”或“搜索”时触发 controller.fetchReservationData(); }, ), ), ), ], ), ); } /// 构建“暂无预约数据”的视图 Widget _buildEmptyReservationView() { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 48.0), color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ 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), ], ), ); } /// 构建“有预约数据”的列表视图 Widget _buildReservationListView() { 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(16), border: Border.all(color: const Color(0xFFF0F0F0)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 车牌与状态标签 Row( children: [ Text( item.plateNumber, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), 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; Color color; switch (status) { case ReservationStatus.pending: text = '待加氢'; color = Colors.orange; break; case ReservationStatus.completed: text = '已加氢'; color = Colors.greenAccent; break; case ReservationStatus.rejected: text = '拒绝加氢'; color = Colors.red; break; case ReservationStatus.unadded: text = '未加氢'; color = Colors.red; break; case ReservationStatus.cancel: text = '已取消'; color = Colors.red; break; default: text = '未知状态'; color = Colors.grey; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.circle, color: color, size: 8), const SizedBox(width: 4), Text( text, style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), ); } /// 构建信息详情行 Widget _buildDetailRow( IconData icon, String label, String value, { Color valueColor = Colors.black87, }) { return Row( children: [ Icon(icon, color: Colors.grey, size: 20), const SizedBox(width: 8), Text('$label: ', style: const TextStyle(fontSize: 14, color: Colors.grey)), Expanded( child: Text( value, style: TextStyle( fontSize: 14, color: valueColor, fontWeight: FontWeight.w500, ), ), ), ], ); } /// 底部构建带图标的提示信息行 Widget _buildInfoItem(IconData icon, String text) { return Row( children: [ Icon(icon, color: Colors.blue, size: 20), const SizedBox(width: 8), Text(text, style: const TextStyle(fontSize: 14, color: Colors.black54)), ], ); } }