Files
ln-ios/ln_jq_app/lib/pages/b_page/site/view.dart
2026-02-27 10:54:55 +08:00

669 lines
22 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/pages/b_page/history/view.dart';
import 'package:ln_jq_app/pages/c_page/message/view.dart';
import 'package:pull_to_refresh/pull_to_refresh.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: SmartRefresher(
controller: controller.refreshController,
enablePullUp: false,
onRefresh: controller.onRefresh,
child: 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(),
SizedBox(height: 15.h),
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: 105.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: () async {
var scanResult = await Get.to(() => const MessagePage());
if (scanResult == null) {
controller.msgNotice();
}
},
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.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
// 因为外层已有滚动,这里禁用内部滚动
itemCount: controller.reservationList.length,
itemBuilder: (context, index) {
final item = controller.reservationList[index];
// 调用新的方法来构建每一项
return _buildReservationItem(index, item);
},
);
}
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.all(16),
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: 13.sp,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
// 联系信息
Text(
"${item.contactPerson} | ${item.contactPhone}",
style: TextStyle(
color: Color(0xFF999999),
fontSize: 13.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 6.h),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (item.hasDrivingAttachment) _buildInfoTag('行驶证'),
if (item.hasHydrogenationAttachment) ...[
SizedBox(width: 8.w),
_buildInfoTag('加氢证'),
],
Spacer(),
if (item.isEdit == "1") ...[
const SizedBox(height: 15),
const Divider(height: 1, color: Color(0xFFF5F5F5)),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerRight,
child: _buildSmallButton(
"修改信息",
isOutline: true,
onTap: () {
controller.confirmReservation(item.id, isEdit: true);
},
),
),
] else 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 _buildInfoTag(String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: TextStyle(color: Color.fromRGBO(78, 89, 105, 1), fontSize: 11.sp),
),
);
}
Widget _buildSmallButton(
String text, {
required bool isOutline,
required VoidCallback onTap,
}) {
const kPrimaryGreen = Color(0xFF006D35);
var kDangerRed = text.contains('修改') ? Colors.red : Color.fromRGBO(255, 142, 98, 1);
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),
),
);
}
}