import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.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/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 { ReservationPage({super.key}); @override Widget build(BuildContext context) { return GetBuilder( init: C_ReservationController(), id: 'reservation', builder: (_) { return Scaffold( backgroundColor: Color.fromRGBO(247, 249, 251, 1), body: GestureDetector( onTap: () { hideKeyboard(); }, child: Stack( children: [ Positioned( left: 0, right: 0, bottom: 10.h, top: 0, child: SingleChildScrollView( child: Column( children: [ _buildUserInfoCard(), Padding( padding: EdgeInsets.only(left: 20.w, right: 20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(height: 16.h), _buildCarInfoCard(), SizedBox(height: 5), _buildReservationFormCard(context), ], ), ), ], ), ), ), Positioned( left: 20.w, right: 20.w, bottom: 90.h, child: _buildReservationItem(context), ), ], ), ), ); }, ); } /// 构建用户信息卡片 Widget _buildUserInfoCard() { return Card( 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: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 16, top: 50), child: Row( children: [ 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'), ), ), ], ), SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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( "羚牛ID:${StorageService.to.phone}", style: const TextStyle(color: Colors.grey, fontSize: 11), ), ], ), ), 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, ), ), ), ], ), ), Padding( padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildModernStatItem('累计加氢量', '', controller.fillingWeight, ''), const SizedBox(width: 8), _buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''), const SizedBox(width: 8), _buildModernStatItem('今日里程', '', "7kg", ''), ], ), ), ], ), ); } 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 _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( 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'), ], ), ), const SizedBox(width: 8), Icon(Icons.propane_rounded, size: 50, color: Colors.blue.withOpacity(0.5)), ], ), ), ); } // 车辆信息卡片中的信息行 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 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, ), ), ], ), ), ) : 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), ], ), ), ), ); } Widget _buildReservationItem(BuildContext context) { return Row( children: [ Expanded( flex: 1, child: OutlinedButton( onPressed: () { controller.getReservationList(showPopup: true, addStatus: ''); }, style: OutlinedButton.styleFrom( minimumSize: Size(double.infinity, 40.h), // 高度与另一个按钮保持一致 side: const BorderSide(color: Color.fromRGBO(226, 232, 240, 1)), backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), ), child: const Text( '查看预约', style: TextStyle( color: Color.fromRGBO(119, 119, 119, 1), fontSize: 13, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 16), Expanded( flex: 2, child: ElevatedButton( onPressed: controller.submitReservation, style: ElevatedButton.styleFrom( minimumSize: Size(double.infinity, 40.h), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: AppTheme.themeColor, foregroundColor: Colors.white, ), child: const Text( '提交预约', style: TextStyle(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, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( border: Border.all(color: Colors.grey[400]!), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(value, style: const TextStyle(fontSize: 14)), Icon(icon, color: Colors.grey, size: 20), ], ), ), ), ], ), ); } // 表单中的文本输入框 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, // 右侧加号按钮 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, ), ), ], ), ); } // :更新氢量逻辑 void _updateAmount(int change) { // 获取当前输入框的值,默认为 0 double currentAmount = double.tryParse(controller.amountController.text) ?? 0; // 获取最大值,注意处理 difference 可能为空或非数字的情况 double maxAmount = double.tryParse(controller.difference.toString()) ?? 9999; // 计算新值 double newAmount = currentAmount + change; // 边界检查 if (newAmount < 1) { newAmount = 1; // 最小不能小于 1 } else if (newAmount > maxAmount) { newAmount = maxAmount; // 最大不能超过 difference } // 如果是整数,去掉小数点显示 if (newAmount == newAmount.toInt()) { controller.amountController.text = newAmount.toInt().toString(); } else { controller.amountController.text = newAmount.toStringAsFixed(2); } // 移动光标到末尾,防止光标跳到前面 controller.amountController.selection = TextSelection.fromPosition( TextPosition(offset: controller.amountController.text.length), ); } Widget _buildStationSelector() { return Column( crossAxisAlignment: CrossAxisAlignment.start, 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('刷新'), ), ], ), ), 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, // 增加下拉项的高度 ), ), ), ), ], ); } /// 构建下拉菜单中的每一项 Widget _buildDropdownItem(StationModel station) { bool isSelectable = (station.isSelect == 1); //营运并且可用 bool isMaintenance = ((station.siteStatusName != '营运中') && isSelectable); final textColor = isSelectable ? Colors.black : Colors.grey; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ Flexible( child: Text( station.name, style: TextStyle( color: textColor, fontSize: 14, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), Text( ' | ¥${station.price}/kg', style: TextStyle( color: textColor, fontSize: 14, fontWeight: FontWeight.w500, ), ), if (isMaintenance) const SizedBox(width: 8), if (isMaintenance) Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.orange[100], borderRadius: BorderRadius.circular(4), ), child: Text( station.siteStatusName, style: TextStyle( fontSize: 10, color: Colors.orange, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 4), Text( '${station.address}', style: const TextStyle(fontSize: 12, color: Colors.grey), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } Widget _buildSelectedStationButton(StationModel station) { return Container( // 选中后样式 padding: const EdgeInsets.symmetric(horizontal: 12.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[400]!), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: _buildDropdownItem(station)), const Icon(Icons.arrow_drop_down, color: Colors.grey, size: 24), ], ), ); } }