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 a0a9611..bbde41d 100644 --- a/ln_jq_app/lib/pages/b_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/b_page/reservation/controller.dart @@ -28,6 +28,11 @@ class ReservationController extends GetxController with BaseControllerMixin { ? DateFormat('yyyy-MM-dd HH:mm').format(customEndTime!) : '点击选择结束时间'; + // --- 站点广播相关 --- + final TextEditingController broadcastTitleController = TextEditingController(); + final TextEditingController broadcastContentController = TextEditingController(); + final RxInt selectedTabIndex = 0.obs; + @override void onInit() { super.onInit(); @@ -120,8 +125,6 @@ class ReservationController extends GetxController with BaseControllerMixin { DateTime now = DateTime.now(); DateTime initialDate = isStart ? (customStartTime ?? now) : (customEndTime ?? now); - // 确保初始时间不早于最小允许时间 - // 将 minimumDate 稍微提前一点点(比如1分钟) DateTime minLimit = isStart ? now.subtract(const Duration(minutes: 1)) : (customStartTime ?? now).subtract(const Duration(minutes: 1)); @@ -157,7 +160,6 @@ class ReservationController extends GetxController with BaseControllerMixin { onPressed: () { if (isStart) { customStartTime = tempDate; - // 如果开始时间变动后,结束时间早于开始时间,自动顺延一天 if (customEndTime != null && customEndTime!.isBefore(customStartTime!)) { customEndTime = customStartTime!.add(const Duration(days: 1)); @@ -250,8 +252,54 @@ class ReservationController extends GetxController with BaseControllerMixin { } } + /// 发送站点广播 + void sendBroadcast() async { + String title = broadcastTitleController.text.trim(); + String content = broadcastContentController.text.trim(); + + if (title.isEmpty) { + showToast("请输入通知标题"); + return; + } + if (content.isEmpty) { + showToast("请输入通知内容"); + return; + } + + showLoading("发送中..."); + try { + var responseData = await HttpService.to.post( + 'appointment/notice/push/station/broadcast', + data: { + 'title': title, + 'content': content, + }, + ); + + dismissLoading(); + if (responseData != null) { + var result = BaseModel.fromJson(responseData.data); + if (result.code == 0) { + showSuccessToast("广播发送成功"); + } else { + showErrorToast(result.error); + } + } + } catch (e) { + dismissLoading(); + showToast("发送失败,请稍后重试"); + } + } + void logout() async { await StorageService.to.clearLoginInfo(); Get.offAll(() => LoginPage()); } + + @override + void onClose() { + broadcastTitleController.dispose(); + broadcastContentController.dispose(); + super.onClose(); + } } 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 15d9ef0..0082b6a 100644 --- a/ln_jq_app/lib/pages/b_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/b_page/reservation/view.dart @@ -46,7 +46,7 @@ class ReservationPage extends GetView { leading: const Icon(Icons.local_gas_station, color: Colors.blue, size: 40), title: Text( controller.name, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), subtitle: Text(controller.address), trailing: Container( @@ -57,7 +57,7 @@ class ReservationPage extends GetView { ), child: Text( controller.selectedOperationStatus, - style: TextStyle( + style: const TextStyle( color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 12, @@ -100,85 +100,204 @@ class ReservationPage extends GetView { ); } - /// 构建包含所有信息表单的卡片 + /// 构建包含所有信息表单的卡片(增加 Tab 切换功能) Widget _buildInfoFormCard(BuildContext context) { return Card( elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - child: Padding( - padding: const EdgeInsets.all(16.0), - 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), + 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)), + ], + ), + ); + } - // --- 价格信息 --- - _buildSectionTitle('价格信息'), - // _buildDisplayField(label: '氢气价格 (元/kg)', value: controller.costPrice), - _buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice), - const SizedBox(height: 16), - - // --- 运营信息 --- - _buildSectionTitle('运营信息'), - Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)), - const SizedBox(height: 8), - - // 下拉选择框 - DropdownButtonFormField( - value: controller.selectedOperationStatus, - items: controller.operationStatusOptions.map((String value) { - return DropdownMenuItem(value: value, child: Text(value)); - }).toList(), - onChanged: controller.onOperationStatusChanged, - decoration: InputDecoration( - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + /// 构建单个 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, ), ), - const SizedBox(height: 16), - - // 根据状态动态显示 UI - if (controller.selectedOperationStatus == "营运中") - _buildDisplayField(label: '营业时间', value: controller.timeStr) - else - Column( - children: [ - _buildClickField( - label: '开始时间', - value: controller.customStartTimeStr, - onTap: () => controller.pickDateTime(context, true), - ), - _buildClickField( - label: '结束时间', - value: controller.customEndTimeStr, - onTap: () => controller.pickDateTime(context, false), - ), - ], + ), + 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], + ), ), - - _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 _buildStationInfo(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + 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('运营信息'), + Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)), + const SizedBox(height: 8), + DropdownButtonFormField( + value: controller.selectedOperationStatus, + items: controller.operationStatusOptions.map((String value) { + return DropdownMenuItem(value: value, child: Text(value)); + }).toList(), + onChanged: controller.onOperationStatusChanged, + decoration: InputDecoration( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + ), + ), + const SizedBox(height: 16), + if (controller.selectedOperationStatus == "营运中") + _buildDisplayField(label: '营业时间', value: controller.timeStr) + else + Column( + children: [ + _buildClickField( + label: '开始时间', + value: controller.customStartTimeStr, + onTap: () => controller.pickDateTime(context, true), + ), + _buildClickField( + label: '结束时间', + value: controller.customEndTimeStr, + onTap: () => controller.pickDateTime(context, false), + ), + ], + ), + _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: [ + 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), + ), + ], + ), + 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( @@ -273,7 +392,7 @@ class ReservationPage extends GetView { const SizedBox(height: 10), Row( children: [ - Icon(Icons.verified_outlined, color: Colors.blue, size: 20), + const Icon(Icons.verified_outlined, color: Colors.blue, size: 20), const SizedBox(width: 10), Expanded( child: FutureBuilder(