From dce971832085771d250884de630b195ea071c9cc Mon Sep 17 00:00:00 2001 From: userGyl Date: Wed, 11 Feb 2026 09:35:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=91=A8=E8=BE=B9=E5=8A=A0?= =?UTF-8?q?=E6=B0=A2=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ln_jq_app/assets/html/map.html | 106 +++++++++---- .../lib/pages/b_page/history/controller.dart | 150 ++++++++++++++++++ .../lib/pages/c_page/mall/mall_view.dart | 1 - .../reservation_list_bottomsheet.dart | 13 +- 4 files changed, 235 insertions(+), 35 deletions(-) create mode 100644 ln_jq_app/lib/pages/b_page/history/controller.dart diff --git a/ln_jq_app/assets/html/map.html b/ln_jq_app/assets/html/map.html index 10f4855..4f0d753 100644 --- a/ln_jq_app/assets/html/map.html +++ b/ln_jq_app/assets/html/map.html @@ -25,7 +25,7 @@ display: none !important; } - /* 去除高德默认的 label 边框和背景 */ + /* 去除高德默认的 label 边框 and 背景 */ .amap-marker-label { border: none !important; background-color: transparent !important; @@ -221,6 +221,7 @@ var currentLat, currentLng; var isTruckMode = false; var isInitialLocationSet = false; + var stationMarkers = []; // 存储所有站点的标记 function initMap() { @@ -336,6 +337,8 @@ fetchStationInfo(addressComponent.province, addressComponent.city, addressComponent.district, lat, lng); + fetchStationInfoList(lat, lng); + // 策略1: 优先使用最近的、类型合适的POI的名称 if (pois && pois.length > 0) { // 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名 @@ -397,7 +400,6 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - // "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da" }, body: JSON.stringify({ province: province, @@ -437,6 +439,73 @@ .catch(err => console.error('JS->:获取站点信息失败:', err)); } + /** + * 获取站点列表 + */ + function fetchStationInfoList(lat, lng) { + fetch('https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + longitude: lng, + latitude: lat, + }) + }) + .then(response => { + if (!response.ok) { + throw new Error('网络响应错误: ' + response.status); + } + return response.json(); // 解析 JSON + }) + .then(res => { + console.log("JS->:2 接口完整返回:", JSON.stringify(res)); + if (res.code === 0 && res.data && Array.isArray(res.data)) { + // 1. 清除旧的站点标记 + stationMarkers.forEach(m => m.setMap(null)); + stationMarkers = []; + + // 2. 循环标记所有加氢站 + res.data.forEach(station => { + var stationIcon = new AMap.Icon({ + size: new AMap.Size(32, 32), + image: 'ic_tag.png', + imageSize: new AMap.Size(32, 32) + }); + + var sMarker = new AMap.Marker({ + map: map, + position: [station.longitude, station.latitude], + icon: stationIcon, + offset: new AMap.Pixel(-16, -32), + title: station.name, + label: { + content: '
' + station.name + '
', + direction: 'top' + } + }); + + // 3. 绑定点击事件:选中即为目的地,并开始规划 + sMarker.on('click', function() { + document.getElementById('endInput').value = station.address || station.name; + // 更新当前的 destMarker (如果需要) + if (destMarker) destMarker.setMap(null); + destMarker = sMarker; + + startRouteSearch(); + }); + + stationMarkers.push(sMarker); + }); + + } else { + console.log("JS->: 业务报错或无数据:", res.message); + } + }) + .catch(err => console.error('JS->:获取站点信息失败:', err)); + } + /** * 地理编码并在地图标记终点 */ @@ -447,7 +516,6 @@ if (destMarker) destMarker.setMap(null); // 2. 创建自定义图标 - // 假设图标大小为 32x32,你可以根据实际图片尺寸调整 Size var destIcon = new AMap.Icon({ size: new AMap.Size(32, 32), // 图标尺寸 image: 'ic_tag.png', // 本地图片路径 @@ -459,8 +527,6 @@ map: map, position: [longitude, latitude], icon: destIcon, // 使用自定义图标 - // 偏移量:如果图标底部中心是尖角,offset 设为宽的一半的负数,高度的负数 - // 这样能确保图片的底部尖端指向地图上的精确位置 offset: new AMap.Pixel(-16, -32), title: name, label: { @@ -469,17 +535,7 @@ } }); - // 4. 打印调试信息 - console.log("JS->: 终点标记已添加", address, loc.toString()); - - // 5. 自动调整视野包含起点和终点 - // if (marker) { - // // 如果起点标志已存在,缩放地图以展示两者 - // map.setFitView([marker, destMarker], false, [60, 60, 60, 60]); - // } else { - // // 如果没有起点,直接跳到终点 - // map.setCenter(loc); - // } + console.log("JS->: 终点标记已添加", address); } /** @@ -523,40 +579,34 @@ document.getElementById('startInput').blur(); document.getElementById('endInput').blur(); - // --- 构造路径规划的点 (使用数组方式,更灵活) --- + // --- 构造路径规划的点 --- var points = []; // 1. 处理起点逻辑 - // 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标 - if (!startKw || startKw === '我的位置') { + if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) { if (!currentLng || !currentLat) { - // 如果还没获取到定位 if (window.flutter_inappwebview) { window.flutter_inappwebview.callHandler('requestLocation'); } alert("正在获取定位,请稍后..."); return; } - // 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词) points.push({ - keyword: '我的位置', // 用于显示的名字 - location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标 + keyword: '我的位置', + location: new AMap.LngLat(currentLng, currentLat) }); } else { - // 如果用户手动输入了地点 (例如 "北京南站") - // 直接存入关键词,让高德自己去搜 points.push({ keyword: startKw }); } - // 2. 处理终点逻辑 (通常是关键词) + // 2. 处理终点逻辑 points.push({ keyword: endKw }); // 3. 发起搜索 - // points 数组里现在是一个起点对象和一个终点对象 driving.search(points, function (status, result) { if (status === 'complete') { console.log('JS: 规划成功'); @@ -591,4 +641,4 @@ - \ No newline at end of file + 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..45d6f58 --- /dev/null +++ b/ln_jq_app/lib/pages/b_page/history/controller.dart @@ -0,0 +1,150 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:intl/intl.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 with BaseControllerMixin { + @override + String get builderId => 'history'; + + // --- 定义 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'.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); + + String stationName = ""; + + final Map statusOptions = { + '': '全部', + '0': '待加氢', + '1': '已加氢', + '2': '未加氢', + '5': '拒绝加氢', + }; + + final RxString selectedStatus = ''.obs; + final RxString selectedDateType = ''.obs; // week, month, three_month + + @override + void onInit() { + super.onInit(); + final args = Get.arguments as Map; + stationName = args['stationName'] as String? ?? ""; + refreshData(); + } + + void refreshData() { + getAllOrderCounts(); + fetchHistoryData(); + } + + Future getAllOrderCounts() async { + var response = await HttpService.to.post( + "appointment/orderAddHyd/getAllOrderCounts", + data: { + /*'startTime': _apiDateFormat.format(startDate.value), + 'endTime': _apiDateFormat.format(endDate.value),*/ + 'plateNumber': plateNumberController.text, + 'stationName': stationName, + "status": selectedStatus.value, + "dateType": selectedDateType.value, + }, + ); + if (response == null || response.data == null) { + totalHydrogen.value = '0'; + totalCompletions.value = '0'; + return; + } + try { + final baseModel = BaseModel.fromJson(response.data); + final dataMap = baseModel.data as Map; + totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0}'; + totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}'; + } catch (e) { + totalHydrogen.value = '0'; + totalCompletions.value = '0'; + } + } + + Future fetchHistoryData() async { + isLoading.value = true; + updateUi(); + + try { + var response = await HttpService.to.post( + "appointment/orderAddHyd/sitOrderPage", + data: { + /*'startTime': _apiDateFormat.format(startDate.value), + 'endTime': _apiDateFormat.format(endDate.value),*/ + 'plateNumber': plateNumberController.text, + 'pageNum': 1, + 'pageSize': 50, + 'stationName': stationName, + "status": selectedStatus.value, + "dateType": selectedDateType.value, + }, + ); + + if (response == null || response.data == null) { + _resetData(); + return; + } + + final baseModel = BaseModel.fromJson(response.data); + if (baseModel.code == 0 && baseModel.data != null) { + final dataMap = baseModel.data as Map; + final List listFromServer = dataMap['records'] ?? []; + historyList.assignAll( + listFromServer + .map((item) => ReservationModel.fromJson(item as Map)) + .toList(), + ); + hasData.value = historyList.isNotEmpty; + } else { + _resetData(); + } + } catch (e) { + _resetData(); + } finally { + isLoading.value = false; + updateUi(); + } + } + + void _resetData() { + historyList.clear(); + hasData.value = false; + } + + void onStatusSelected(String status) { + if (selectedStatus.value == status) return; + selectedStatus.value = status; + refreshData(); + } + + void onDateTypeSelected(String type) { + selectedDateType.value = type; + refreshData(); + } + + @override + void onClose() { + plateNumberController.dispose(); + super.onClose(); + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/mall_view.dart b/ln_jq_app/lib/pages/c_page/mall/mall_view.dart index edc480d..49f249a 100644 --- a/ln_jq_app/lib/pages/c_page/mall/mall_view.dart +++ b/ln_jq_app/lib/pages/c_page/mall/mall_view.dart @@ -153,7 +153,6 @@ class MallPage extends GetView { ), ], ), - SizedBox(height: 12.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/ln_jq_app/lib/pages/c_page/reservation/reservation_list_bottomsheet.dart b/ln_jq_app/lib/pages/c_page/reservation/reservation_list_bottomsheet.dart index 4b6e0f2..055bce9 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/reservation_list_bottomsheet.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/reservation_list_bottomsheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/common/index.dart'; import 'package:ln_jq_app/common/model/base_model.dart'; +import 'package:ln_jq_app/common/styles/theme.dart'; import 'package:ln_jq_app/pages/c_page/reservation/controller.dart'; import 'package:ln_jq_app/pages/c_page/reservation_edit/controller.dart'; import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart'; @@ -203,14 +204,14 @@ class _ReservationListBottomSheetState extends State }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12), - side: BorderSide(color: Colors.grey.shade400), // 灰色边框 - shape: const StadiumBorder(), // 胶囊形状 + side: BorderSide(color: Colors.grey.shade400), + shape: const StadiumBorder(), ), child: Text( '取消预约', style: TextStyle( color: Colors.grey.shade600, - fontSize: 12, + fontSize: 11.sp, ), ), ), @@ -241,13 +242,13 @@ class _ReservationListBottomSheetState extends State }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12), - side: const BorderSide(color: Colors.blue), // 蓝色边框 + side: BorderSide(color: AppTheme.themeColor), shape: const StadiumBorder(), backgroundColor: Colors.white, ), - child: const Text( + child: Text( '修改', - style: TextStyle(color: Colors.blue, fontSize: 12), + style: TextStyle(color: AppTheme.themeColor, fontSize: 11.sp), ), ), ),