Files
ln-ios/ln_jq_app/lib/pages/b_page/site/view.dart
2026-01-28 17:59:26 +08:00

754 lines
24 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<SiteController> {
const SitePage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<SiteController>(
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,
),
),
],
),
],
),
),
],
),
),
],
);
}
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: [
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),
),
],
),
);
}
/// 构建“有预约数据”的列表视图
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;
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;
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)),
],
);
}
}