diff --git a/ln_jq_app/assets/html/car.png b/ln_jq_app/assets/html/car.png
new file mode 100644
index 0000000..5f5ba0a
Binary files /dev/null and b/ln_jq_app/assets/html/car.png differ
diff --git a/ln_jq_app/assets/html/map.html b/ln_jq_app/assets/html/map.html
index 3ef4926..3df0453 100644
--- a/ln_jq_app/assets/html/map.html
+++ b/ln_jq_app/assets/html/map.html
@@ -248,8 +248,8 @@
marker = new AMap.Marker({
map: map,
position: position,
- icon: "https://webapi.amap.com/images/car.png",
- offset: new AMap.Pixel(-26, -13),
+ icon: "car.png",
+ offset: new AMap.Pixel(-23.5, -15),
autoRotation: true,
angle: isNaN(rawAngle) ? 0 : rawAngle,
});
diff --git a/ln_jq_app/lib/pages/b_page/history/controller.dart b/ln_jq_app/lib/pages/b_page/history/controller.dart
new file mode 100644
index 0000000..3a17016
--- /dev/null
+++ b/ln_jq_app/lib/pages/b_page/history/controller.dart
@@ -0,0 +1,186 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:getx_scaffold/getx_scaffold.dart';
+import 'package:ln_jq_app/common/model/base_model.dart';
+import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
+
+class HistoryController extends GetxController {
+ // --- 定义 API 需要的日期格式化器 ---
+ final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
+
+ // 默认查询最近7天
+ final Rx startDate = DateTime.now().subtract(const Duration(days: 7)).obs;
+ final Rx endDate = DateTime.now().obs;
+ final TextEditingController plateNumberController = TextEditingController();
+
+ final RxString totalHydrogen = '0 kg'.obs;
+ final RxString totalCompletions = '0 次'.obs;
+
+ final RxList historyList = [].obs;
+ final RxBool isLoading = true.obs;
+ final RxBool hasData = false.obs;
+
+ String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
+
+ String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
+
+ @override
+ void onInit() {
+ super.onInit();
+ fetchHistoryData();
+ }
+
+ Future fetchHistoryData() async {
+ isLoading.value = true;
+ try {
+ var response = await HttpService.to.post(
+ "appointment/orderAddHyd/sitOrderPage",
+ data: {
+ // --- 直接使用 DateFormat 来格式化日期 ---
+ 'startDate': _apiDateFormat.format(startDate.value),
+ 'endDate': _apiDateFormat.format(endDate.value),
+ 'plateNumber': plateNumberController.text,
+ 'pageNum': 1,
+ 'pageSize': 50,
+ 'stationName': "", // 加氢站名称
+ },
+ );
+
+ if (response == null || response.data == null) {
+ showToast('无法获取历史记录');
+ _resetData();
+ return;
+ }
+
+ final baseModel = BaseModel.fromJson(response.data);
+ if (baseModel.code == 0 && baseModel.data != null) {
+ final dataMap = baseModel.data as Map;
+
+ totalHydrogen.value = '${dataMap['totalHydrogen'] ?? 0} kg';
+ totalCompletions.value = '${dataMap['totalCompletions'] ?? 0} 次';
+
+ final List listFromServer = dataMap['records'] ?? [];
+ historyList.assignAll(
+ listFromServer
+ .map((item) => ReservationModel.fromJson(item as Map))
+ .toList(),
+ );
+ hasData.value = historyList.isNotEmpty;
+ } else {
+ showToast(baseModel.message);
+ _resetData();
+ }
+ } catch (e) {
+ showToast('获取历史记录失败: $e');
+ _resetData();
+ } finally {
+ isLoading.value = false;
+ }
+ }
+
+ void _resetData() {
+ totalHydrogen.value = '0 kg';
+ totalCompletions.value = '0 次';
+ historyList.clear();
+ hasData.value = false;
+ }
+
+ void pickDate(BuildContext context, bool isStartDate) {
+ // 确定当前操作的日期和临时存储变量
+ final DateTime initialDate = isStartDate ? startDate.value : endDate.value;
+ DateTime tempDate = initialDate;
+
+ // 定义全局的最早可选日期
+ final DateTime globalMinimumDate = DateTime(2025, 12, 1);
+
+ // 动态计算当前选择器的最小/最大日期范围
+ DateTime minimumDate;
+ DateTime? maximumDate; // 声明为可空,因为两个日期都可能没有最大限制
+
+ if (isStartDate) {
+ // 当选择【开始日期】时 它的最小日期就是全局最小日期
+ minimumDate = globalMinimumDate;
+ // 最大日期没有限制
+ maximumDate = null;
+ } else {
+ // 当选择【结束日期】时 它的最小日期不能早于当前的开始日期
+ minimumDate = startDate.value;
+ // 确认结束日期没有最大限制 ---
+ //最大日期没有限制
+ maximumDate = null;
+ }
+
+ Get.bottomSheet(
+ Container(
+ height: 300,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(16),
+ topRight: Radius.circular(16),
+ ),
+ ),
+ child: Column(
+ children: [
+ // 顶部的取消和确认按钮
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: const Text('取消', style: TextStyle(color: Colors.grey)),
+ ),
+ TextButton(
+ onPressed: () {
+ // 4. 确认后,更新对应的日期变量
+ if (isStartDate) {
+ startDate.value = tempDate;
+ // 如果新的开始日期晚于结束日期,自动将结束日期调整为同一天
+ if (tempDate.isAfter(endDate.value)) {
+ endDate.value = tempDate;
+ }
+ } else {
+ endDate.value = tempDate;
+ }
+ Get.back();
+
+ // 选择日期后自动刷新数据
+ fetchHistoryData();
+ },
+ child: const Text(
+ '确认',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Divider(height: 1),
+ // 日期选择器
+ Expanded(
+ child: CupertinoDatePicker(
+ mode: CupertinoDatePickerMode.date,
+ initialDateTime: initialDate,
+ // 应用动态计算好的最小/最大日期
+ minimumDate: minimumDate,
+ maximumDate: maximumDate,
+ onDateTimeChanged: (DateTime newDate) {
+ tempDate = newDate;
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ backgroundColor: Colors.transparent, // 使底部工作表外的区域透明
+ );
+ }
+
+ @override
+ void onClose() {
+ plateNumberController.dispose();
+ super.onClose();
+ }
+}
diff --git a/ln_jq_app/lib/pages/b_page/history/view.dart b/ln_jq_app/lib/pages/b_page/history/view.dart
new file mode 100644
index 0000000..fb3d9c4
--- /dev/null
+++ b/ln_jq_app/lib/pages/b_page/history/view.dart
@@ -0,0 +1,249 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:ln_jq_app/common/styles/theme.dart';
+import 'package:ln_jq_app/pages/b_page/history/controller.dart';
+import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
+
+class HistoryPage extends GetView {
+ const HistoryPage({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ Get.put(HistoryController());
+
+ return Scaffold(
+ appBar: AppBar(title: const Text('历史记录'), centerTitle: true),
+ body: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ children: [
+ _buildFilterCard(context),
+ const SizedBox(height: 12),
+ _buildSummaryCard(),
+ const SizedBox(height: 12),
+ _buildListHeader(),
+ Expanded(child: _buildHistoryList()),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildFilterCard(BuildContext context) {
+ return Card(
+ elevation: 2,
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('时间范围', style: TextStyle(fontSize: 14, color: Colors.grey)),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Expanded(child: _buildDateField(context, true)),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.0),
+ child: Text('至'),
+ ),
+ Expanded(child: _buildDateField(context, false)),
+ ],
+ ),
+ const SizedBox(height: 16),
+ const Text('车牌号', style: TextStyle(fontSize: 14, color: Colors.grey)),
+ const SizedBox(height: 8),
+ SizedBox(
+ height: 44,
+ child: TextField(
+ controller: controller.plateNumberController,
+ decoration: InputDecoration(
+ hintText: '请输入车牌号',
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ contentPadding: const EdgeInsets.symmetric(horizontal: 12),
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton.icon(
+ onPressed: () {
+ FocusScope.of(context).unfocus(); // Hide keyboard
+ controller.fetchHistoryData();
+ },
+ icon: const Icon(Icons.search, size: 20),
+ label: const Text('查询'),
+ style: ElevatedButton.styleFrom(
+ minimumSize: const Size(double.infinity, 44),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSummaryCard() {
+ return Card(
+ elevation: 2,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 20.0),
+ child: Obx(
+ () => Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ _buildSummaryItem('实际加氢总量', controller.totalHydrogen.value, Colors.blue),
+ const SizedBox(width: 1, height: 40, child: VerticalDivider()),
+ _buildSummaryItem(
+ '预约完成次数',
+ controller.totalCompletions.value,
+ Colors.green,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHistoryList() {
+ return Obx(() {
+ if (controller.isLoading.value) {
+ return const Center(child: CircularProgressIndicator());
+ }
+ if (!controller.hasData.value) {
+ return const Center(child: Text('没有找到相关记录'));
+ }
+ return ListView.builder(
+ itemCount: controller.historyList.length,
+ itemBuilder: (context, index) {
+ final ReservationModel item = controller.historyList[index];
+ return Card(
+ margin: const EdgeInsets.only(bottom: 8),
+ child: ListTile(
+ title: Text('车牌号: ${item.plateNumber}'),
+ subtitle: Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: '加氢站: ${item.stationName}\n',
+ style: TextStyle(fontSize: 16),
+ ),
+ TextSpan(
+ text: '时间: ${item.time}\n',
+ style: TextStyle(fontSize: 16),
+ ),
+ TextSpan(
+ text: '加氢量:',
+ ),
+ TextSpan(
+ text: '${item.amount}',
+ style: TextStyle(fontSize: 16, color: AppTheme.themeColor),
+ ),
+ ],
+ ),
+ )
+ ,
+ trailing:
+ // 状态标签
+ _buildStatusChip(item.status),
+ ),
+ );
+ },
+ );
+ });
+ }
+
+ 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 _buildDateField(BuildContext context, bool isStart) {
+ return Obx(
+ () => InkWell(
+ onTap: () => controller.pickDate(context, isStart),
+ child: Container(
+ height: 44,
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.grey.shade400),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(isStart ? controller.formattedStartDate : controller.formattedEndDate),
+ const Icon(Icons.calendar_today, size: 18, color: Colors.grey),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSummaryItem(String label, String value, Color color) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(label, style: const TextStyle(color: Colors.grey, fontSize: 14)),
+ const SizedBox(height: 8),
+ Text(
+ value,
+ style: TextStyle(color: color, fontSize: 22, fontWeight: FontWeight.bold),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildListHeader() {
+ return const Padding(
+ padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 14.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text('加氢明细', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
+ ],
+ ),
+ );
+ }
+}
diff --git a/ln_jq_app/lib/pages/b_page/site/view.dart b/ln_jq_app/lib/pages/b_page/site/view.dart
index abdc2dc..9012495 100644
--- a/ln_jq_app/lib/pages/b_page/site/view.dart
+++ b/ln_jq_app/lib/pages/b_page/site/view.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/styles/theme.dart';
+import 'package:ln_jq_app/pages/b_page/history/view.dart';
import 'controller.dart';
@@ -114,48 +115,59 @@ class SitePage extends GetView {
Card(
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
- margin: const EdgeInsets.only(bottom: 12),
+ margin: EdgeInsets.only(bottom: 12),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
Container(
color: Colors.blue,
- padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
+ padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Row(
children: [
- const Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '今日预约信息',
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.bold,
- color: Colors.white,
+ Expanded(
+ child: GestureDetector(
+ onTap: () {
+ controller.renderData();
+ },
+ child: Row(
+ children: [
+ Text(
+ '今日预约信息',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
),
- ),
- SizedBox(height: 2),
- Text(
- 'Reservation Information',
- style: TextStyle(fontSize: 12, color: Colors.white70),
- ),
- ],
+ SizedBox(
+ width: 32,
+ height: 32,
+ child: const Icon(
+ Icons.refresh,
+ size: 18,
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
),
),
- ElevatedButton.icon(
+ ElevatedButton(
onPressed: () {
- controller.renderData();
+ Get.to(() => const HistoryPage());
},
- icon: const Icon(Icons.refresh, size: 16),
- label: const Text('刷新'),
style: ElevatedButton.styleFrom(
- foregroundColor: Colors.blue,
- backgroundColor: Colors.white,
- padding: const EdgeInsets.symmetric(horizontal: 12),
+ backgroundColor: Colors.blue.shade700,
+ foregroundColor: Colors.white,
+ padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(16),
+ borderRadius: BorderRadius.circular(5),
),
+ elevation: 2,
+ ),
+ child: const Text(
+ '历史记录',
+ style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
),
],