20 Commits

Author SHA1 Message Date
bec6952707 Merge branch 'dev_map' into map_dev 2026-04-02 09:11:14 +08:00
fdd6653fce Merge branch 'dev_map' into map_dev
# Conflicts:
#	ln_jq_app/ios/Podfile.lock
#	ln_jq_app/lib/pages/c_page/base_widgets/view.dart
地图导航相关
2026-03-31 10:07:43 +08:00
909dc95771 增加 提示 2026-02-11 17:41:30 +08:00
cf0896453b 样式 2026-02-11 11:28:49 +08:00
dce9718320 显示周边加氢站 2026-02-11 09:35:42 +08:00
4491aa9b91 ui调整 2026-02-10 16:35:02 +08:00
5364612a6f 更新样式 2026-02-10 13:37:24 +08:00
10867178fa 筛选框样式 2026-02-10 13:35:22 +08:00
a5e2a89e4f 预约列表样式 2026-02-10 11:51:47 +08:00
26c5f9d67a 消息样式修改 2026-02-09 17:57:00 +08:00
9cd87b0535 规则和历史 2026-02-09 17:28:12 +08:00
45e45d8160 积分兑换 2026-02-09 15:10:00 +08:00
87e890f97e 积分兑换首页 2026-02-06 17:37:43 +08:00
dcf925b8c1 商场页 2026-02-06 15:13:33 +08:00
c45863eda6 增加商城页面 2026-02-06 15:11:12 +08:00
756bf53cf5 司机预约时间调整 2026-02-06 14:16:26 +08:00
f68c2d0938 未车辆显示 2026-02-05 14:50:03 +08:00
211d0225e4 车辆图片动态 2026-02-05 13:54:10 +08:00
7d9b4d99e8 应用更新 2026-02-05 10:30:31 +08:00
3dd583a278 401增加节流 2026-02-03 10:59:05 +08:00
74 changed files with 2634 additions and 2863 deletions

View File

@@ -58,4 +58,31 @@
-dontwarn org.android.agoo.** -dontwarn org.android.agoo.**
-dontwarn anetwork.** -dontwarn anetwork.**
-dontwarn com.ut.** -dontwarn com.ut.**
-dontwarn com.ta.** -dontwarn com.ta.**
3D 地图 V5.0.0之前:
-keep class com.amap.api.maps.**{*;}
-keep class com.autonavi.amap.mapcore.*{*;}
-keep class com.amap.api.trace.**{*;}
3D 地图 V5.0.0之后:
-keep class com.amap.api.maps.**{*;}
-keep class com.autonavi.**{*;}
-keep class com.amap.api.trace.**{*;}
定位
-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}
-keep class com.autonavi.aps.amapapi.model.**{*;}
搜索
-keep class com.amap.api.services.**{*;}
2D地图
-keep class com.amap.api.maps2d.**{*;}
-keep class com.amap.api.mapcore2d.**{*;}
导航
-keep class com.amap.api.navi.**{*;}
-keep class com.autonavi.**{*;}

View File

@@ -159,19 +159,17 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
this.mContext = context; this.mContext = context;
mActivity = getActivityFromContext(context); mActivity = getActivityFromContext(context);
MapsInitializer.updatePrivacyShow(context, true, true); MapsInitializer.updatePrivacyShow(mActivity, true, true);
MapsInitializer.updatePrivacyAgree(context, true); MapsInitializer.updatePrivacyAgree(mActivity, true);
container = new FrameLayout(context);
container.setClickable(true);
container.setFocusable(true);
container.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mapView = new MapView(context); mapView = new MapView(context);
mapView.onCreate(null); mapView.onCreate(null);
aMap = mapView.getMap(); aMap = mapView.getMap();
container = new FrameLayout(context);
container.setClickable(true);
container.setFocusable(true);
container.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
container.addView(mapView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); container.addView(mapView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
initLoadingView(context); initLoadingView(context);

View File

@@ -25,7 +25,7 @@
display: none !important; display: none !important;
} }
/* 去除高德默认的 label 边框背景 */ /* 去除高德默认的 label 边框 and 背景 */
.amap-marker-label { .amap-marker-label {
border: none !important; border: none !important;
background-color: transparent !important; background-color: transparent !important;
@@ -109,7 +109,7 @@
/* --- 导航结果面板 (底部弹出) --- */ /* --- 导航结果面板 (底部弹出) --- */
#panel { #panel {
position: fixed; position: fixed;
bottom: 75px; bottom: 95px;
left: 0; left: 0;
width: 100%; width: 100%;
height: 35%; height: 35%;
@@ -129,7 +129,7 @@
#location-btn { #location-btn {
position: fixed; position: fixed;
right: 10px; right: 10px;
bottom: 75px; bottom: 105px;
/* 默认位置 */ /* 默认位置 */
width: 44px; width: 44px;
height: 44px; height: 44px;
@@ -159,7 +159,7 @@
/* --- 调整比例尺位置 --- */ /* --- 调整比例尺位置 --- */
.amap-scalecontrol { .amap-scalecontrol {
/* 初始状态:避开底部的定位按钮或留出安全间距 */ /* 初始状态:避开底部的定位按钮或留出安全间距 */
bottom: 80px !important; bottom: 110px !important;
left: 10px !important; left: 10px !important;
transition: bottom 0.3s ease; transition: bottom 0.3s ease;
/* 增加平滑动画 */ /* 增加平滑动画 */
@@ -221,6 +221,7 @@
var currentLat, currentLng; var currentLat, currentLng;
var isTruckMode = false; var isTruckMode = false;
var isInitialLocationSet = false; var isInitialLocationSet = false;
var stationMarkers = []; // 存储所有站点的标记
function initMap() { function initMap() {
@@ -336,6 +337,8 @@
fetchStationInfo(addressComponent.province, addressComponent.city, fetchStationInfo(addressComponent.province, addressComponent.city,
addressComponent.district, lat, lng); addressComponent.district, lat, lng);
fetchStationInfoList(lat, lng);
// 策略1: 优先使用最近的、类型合适的POI的名称 // 策略1: 优先使用最近的、类型合适的POI的名称
if (pois && pois.length > 0) { if (pois && pois.length > 0) {
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI这类POI通常是具体的建筑或地点名 // 查找第一个类型不是“商务住宅”或“地名地址信息”的POI这类POI通常是具体的建筑或地点名
@@ -397,7 +400,6 @@
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
// "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da"
}, },
body: JSON.stringify({ body: JSON.stringify({
province: province, province: province,
@@ -437,6 +439,73 @@
.catch(err => console.error('JS->:获取站点信息失败:', err)); .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: '<div class="custom-bubble">' + station.name + '</div>',
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); if (destMarker) destMarker.setMap(null);
// 2. 创建自定义图标 // 2. 创建自定义图标
// 假设图标大小为 32x32你可以根据实际图片尺寸调整 Size
var destIcon = new AMap.Icon({ var destIcon = new AMap.Icon({
size: new AMap.Size(32, 32), // 图标尺寸 size: new AMap.Size(32, 32), // 图标尺寸
image: 'ic_tag.png', // 本地图片路径 image: 'ic_tag.png', // 本地图片路径
@@ -459,8 +527,6 @@
map: map, map: map,
position: [longitude, latitude], position: [longitude, latitude],
icon: destIcon, // 使用自定义图标 icon: destIcon, // 使用自定义图标
// 偏移量如果图标底部中心是尖角offset 设为宽的一半的负数,高度的负数
// 这样能确保图片的底部尖端指向地图上的精确位置
offset: new AMap.Pixel(-16, -32), offset: new AMap.Pixel(-16, -32),
title: name, title: name,
label: { label: {
@@ -469,17 +535,7 @@
} }
}); });
// 4. 打印调试信息 console.log("JS->: 终点标记已添加", address);
console.log("JS->: 终点标记已添加", address, loc.toString());
// 5. 自动调整视野包含起点和终点
// if (marker) {
// // 如果起点标志已存在,缩放地图以展示两者
// map.setFitView([marker, destMarker], false, [60, 60, 60, 60]);
// } else {
// // 如果没有起点,直接跳到终点
// map.setCenter(loc);
// }
} }
/** /**
@@ -523,40 +579,34 @@
document.getElementById('startInput').blur(); document.getElementById('startInput').blur();
document.getElementById('endInput').blur(); document.getElementById('endInput').blur();
// --- 构造路径规划的点 (使用数组方式,更灵活) --- // --- 构造路径规划的点 ---
var points = []; var points = [];
// 1. 处理起点逻辑 // 1. 处理起点逻辑
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标 if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
if (!startKw || startKw === '我的位置') {
if (!currentLng || !currentLat) { if (!currentLng || !currentLat) {
// 如果还没获取到定位
if (window.flutter_inappwebview) { if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler('requestLocation'); window.flutter_inappwebview.callHandler('requestLocation');
} }
alert("正在获取定位,请稍后..."); alert("正在获取定位,请稍后...");
return; return;
} }
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
points.push({ points.push({
keyword: '我的位置', // 用于显示的名字 keyword: '我的位置',
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标 location: new AMap.LngLat(currentLng, currentLat)
}); });
} else { } else {
// 如果用户手动输入了地点 (例如 "北京南站")
// 直接存入关键词,让高德自己去搜
points.push({ points.push({
keyword: startKw keyword: startKw
}); });
} }
// 2. 处理终点逻辑 (通常是关键词) // 2. 处理终点逻辑
points.push({ points.push({
keyword: endKw keyword: endKw
}); });
// 3. 发起搜索 // 3. 发起搜索
// points 数组里现在是一个起点对象和一个终点对象
driving.search(points, function (status, result) { driving.search(points, function (status, result) {
if (status === 'complete') { if (status === 'complete') {
console.log('JS: 规划成功'); console.log('JS: 规划成功');
@@ -591,4 +641,4 @@
</script> </script>
</body> </body>
</html> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

View File

@@ -1,30 +0,0 @@
//
// AAddHPopView.h
// AMapNavIOSSDK
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* 气泡弹框视图
*
* 用法:
* AAddHPopView *pop = [[AAddHPopView alloc] init];
* [pop showInView:self.view sourceView:addHbtn];
*
* - 点击气泡外部自动消失
* - 箭头在右下角45度指向 sourceView 中心
*/
@interface AAddHPopView : UIView
/// 展示气泡sourceView 为箭头指向的锚定按钮(坐标系属于 view
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView;
/// 手动消失(点击空白处会自动调用)
- (void)dismiss;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,359 +0,0 @@
//
// AAddHPopView.m
// AMapNavIOSSDK
//
#import "AAddHPopView.h"
#import <Masonry/Masonry.h>
#import "UIColor+ANavMap.h"
//
static const CGFloat kBubbleWidth = 200.f; //
static const CGFloat kHeaderHeight = 50.f; // 绿
static const CGFloat kOptionHeight = 60.f; //
static const CGFloat kArrowWidth = 50.f; // aw = ah*2 使45
static const CGFloat kArrowHeight = 15.f; //
static const CGFloat kBubbleRadius = 15.f; //
static const CGFloat kBubbleGap = 5.f; // sourceView
static const CGFloat kScreenPadding = 15.f; //
// +
@interface _ABubbleContainerView : UIView
/// X view showInView
@property (nonatomic, assign) CGFloat arrowCenterX;
@end
@implementation _ABubbleContainerView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
_arrowCenterX = kBubbleWidth / 2.f;
}
return self;
}
- (void)setArrowCenterX:(CGFloat)arrowCenterX {
_arrowCenterX = arrowCenterX;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGFloat bw = kBubbleWidth; // 160
CGFloat bh = kHeaderHeight + kOptionHeight * 2.f; // 150
CGFloat r = kBubbleRadius; // 10
CGFloat aw = kArrowWidth; // 16
CGFloat ah = kArrowHeight; // 10
// 45
// : (arrowX-aw/2, bh) (arrowX, bh)
// : (arrowX + ah, bh + ah) 45
CGFloat arrowX = bw - 15.f; // X
CGFloat arrowTipX = arrowX + ah; // Xah = 45
CGFloat arrowTipY = bh + ah; // Yah = 45
UIBezierPath *path = [UIBezierPath bezierPath];
//
[path moveToPoint:CGPointMake(r, 0)];
[path addLineToPoint:CGPointMake(bw - r, 0)];
[path addArcWithCenter:CGPointMake(bw - r, r) radius:r
startAngle:-M_PI_2 endAngle:0 clockwise:YES];
//
[path addLineToPoint:CGPointMake(bw, bh - r)];
[path addArcWithCenter:CGPointMake(bw - r, bh - r) radius:r
startAngle:0 endAngle:M_PI_2 clockwise:YES];
//
[path addLineToPoint:CGPointMake(arrowX, bh)];
// 45
[path addLineToPoint:CGPointMake(arrowTipX, arrowTipY)];
//
[path addLineToPoint:CGPointMake(arrowX - ah, bh)];
//
[path addArcWithCenter:CGPointMake(r, bh - r) radius:r
startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
//
[path addLineToPoint:CGPointMake(0, r)];
[path addArcWithCenter:CGPointMake(r, r) radius:r
startAngle:M_PI endAngle:-M_PI_2 clockwise:YES];
[path closePath];
// +
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 3), 10.f,
[[UIColor blackColor] colorWithAlphaComponent:0.15f].CGColor);
[[UIColor whiteColor] setFill];
[path fill];
CGContextRestoreGState(ctx);
[[UIColor whiteColor] setFill];
[path fill];
//
_arrowTipX = arrowTipX;
_arrowTipY = arrowTipY;
}
// 访
static CGFloat _arrowTipX = 0;
static CGFloat _arrowTipY = 0;
@end
// AAddHPopView
@interface AAddHPopView ()
@property (nonatomic, strong) _ABubbleContainerView *bubbleContainer;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *separatorLine1;
@property (nonatomic, strong) UILabel *option1Label;
@property (nonatomic, strong) UIView *separatorLine2;
@property (nonatomic, strong) UILabel *option2Label;
@end
@implementation AAddHPopView
#pragma mark - Init
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self p_setupUI];
}
return self;
}
#pragma mark - Setup UI
- (void)p_setupUI {
self.backgroundColor = [UIColor clearColor];
// self
UITapGestureRecognizer *bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(dismiss)];
bgTap.cancelsTouchesInView = NO;
[self addGestureRecognizer:bgTap];
//
[self addSubview:self.bubbleContainer];
// header绿
[self.bubbleContainer addSubview:self.headerView];
[self.headerView addSubview:self.titleLabel];
// 线 1
[self.bubbleContainer addSubview:self.separatorLine1];
// 1
[self.bubbleContainer addSubview:self.option1Label];
// 线 2
[self.bubbleContainer addSubview:self.separatorLine2];
// 2
[self.bubbleContainer addSubview:self.option2Label];
// bgTap
UITapGestureRecognizer *bubbleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(p_bubbleTapped)];
[self.bubbleContainer addGestureRecognizer:bubbleTap];
// Masonry bubbleContainer
// kBubbleWidth160
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(self.bubbleContainer);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kHeaderHeight);
}];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.headerView);
}];
[self.separatorLine1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.headerView.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(0.5);
}];
[self.option1Label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separatorLine1.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kOptionHeight);
}];
[self.separatorLine2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.option1Label.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(0.5);
}];
[self.option2Label mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separatorLine2.mas_bottom);
make.left.mas_equalTo(@0);
make.width.mas_equalTo(kBubbleWidth);
make.height.mas_equalTo(kOptionHeight);
}];
}
#pragma mark - Show / Dismiss
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView {
self.frame = view.bounds;
[view addSubview:self];
// sourceView view frame
CGRect srcFrame = [sourceView convertRect:sourceView.bounds toView:view];
//
CGFloat containerW = kBubbleWidth; // 160
CGFloat containerH = kHeaderHeight + kOptionHeight * 2.f; // 150
CGFloat ah = kArrowHeight; // 16
// bubbleContainer
// drawRect arrowX = bw - 35, arrowTipX = arrowX + ah
CGFloat arrowX_inContainer = containerW - 25.f; // X
CGFloat arrowTipX_inContainer = arrowX_inContainer + ah; // X45
CGFloat arrowTipY_inContainer = containerH + ah; // Y
//
CGFloat srcCenterX = CGRectGetMidX(srcFrame);
CGFloat srcCenterY = CGRectGetMidY(srcFrame);
//
CGFloat containerX = srcCenterX - arrowTipX_inContainer;
containerX = MAX(kScreenPadding, MIN(containerX, view.bounds.size.width - containerW - kScreenPadding));
//
CGFloat containerY = srcCenterY - arrowTipY_inContainer;
containerY = MAX(kScreenPadding, containerY);
self.bubbleContainer.bounds = CGRectMake(0, 0, containerW, containerH + ah);
//
self.bubbleContainer.layer.anchorPoint = CGPointMake(arrowTipX_inContainer / containerW,
arrowTipY_inContainer / (containerH + ah));
self.bubbleContainer.center = CGPointMake(srcCenterX - 25, srcCenterY - 20);
//
self.alpha = 0.f;
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
[UIView animateWithDuration:0.25f
delay:0.f
usingSpringWithDamping:0.72f
initialSpringVelocity:0.3f
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.alpha = 1.f;
self.bubbleContainer.transform = CGAffineTransformIdentity;
} completion:nil];
}
- (void)dismiss {
[UIView animateWithDuration:0.18f
delay:0.f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.alpha = 0.f;
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
#pragma mark - Private Actions
- (void)p_bubbleTapped {
//
}
#pragma mark - Lazy Load
- (_ABubbleContainerView *)bubbleContainer {
if (!_bubbleContainer) {
_bubbleContainer = [[_ABubbleContainerView alloc] init];
}
return _bubbleContainer;
}
- (UIView *)headerView {
if (!_headerView) {
_headerView = [[UIView alloc] init];
_headerView.backgroundColor = [UIColor hp_colorWithRGBHex:0x1BA855];
if (@available(iOS 11.0, *)) {
_headerView.layer.cornerRadius = kBubbleRadius;
_headerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
_headerView.clipsToBounds = YES;
}
}
return _headerView;
}
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.text = @"加氢规划模式";
_titleLabel.textColor = [UIColor whiteColor];
_titleLabel.font = [UIFont boldSystemFontOfSize:15.f];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}
- (UIView *)separatorLine1 {
if (!_separatorLine1) {
_separatorLine1 = [[UIView alloc] init];
_separatorLine1.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
}
return _separatorLine1;
}
- (UILabel *)option1Label {
if (!_option1Label) {
_option1Label = [[UILabel alloc] init];
_option1Label.text = @"送货规划模式";
_option1Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
_option1Label.font = [UIFont boldSystemFontOfSize:14.f];
_option1Label.textAlignment = NSTextAlignmentCenter;
}
return _option1Label;
}
- (UIView *)separatorLine2 {
if (!_separatorLine2) {
_separatorLine2 = [[UIView alloc] init];
_separatorLine2.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
}
return _separatorLine2;
}
- (UILabel *)option2Label {
if (!_option2Label) {
_option2Label = [[UILabel alloc] init];
_option2Label.text = @"成本计算模式";
_option2Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
_option2Label.font = [UIFont boldSystemFontOfSize:14.f];
_option2Label.textAlignment = NSTextAlignmentCenter;
}
return _option2Label;
}
@end

View File

@@ -1,16 +0,0 @@
//
// ACustomNaviDriveController.h
// AMapNavIOSSDK
//
// Created by admin on 2026/4/11.
//
#import "ABaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface ACustomNaviDriveController : ABaseViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,22 +0,0 @@
//
// ACustomNaviDriveController.m
// AMapNavIOSSDK
//
// Created by admin on 2026/4/11.
//
#import "ACustomNaviDriveController.h"
@interface ACustomNaviDriveController ()
@end
@implementation ACustomNaviDriveController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
@end

View File

@@ -21,8 +21,8 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface ARoutePlaneController : ABaseViewController<AMapNaviDriveDataRepresentable> @interface ARoutePlaneController : ABaseViewController
@property (nonatomic , strong) AMapNaviDriveView * driveView;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -18,7 +18,7 @@
#import "AMapPrivacyUtility.h" #import "AMapPrivacyUtility.h"
#import "AStationDetailPopupView.h" #import "AStationDetailPopupController.h"
#define kRouteIndicatorViewHeight 64.f #define kRouteIndicatorViewHeight 64.f
@@ -26,9 +26,8 @@
#import "AMapNavHttpUtil.h" #import "AMapNavHttpUtil.h"
#import "ACustomStepView.h" #import "ACustomStepView.h"
#import "ABottomBarView.h" #import "ABottomBarView.h"
#import "AAddHPopView.h"
@interface ARoutePlaneController ()<MAMapViewDelegate, AMapNaviDriveManagerDelegate,AMapNaviCompositeManagerDelegate , AMapLocationManagerDelegate , UITextFieldDelegate , AStationDetailPopupViewDelegate, ABottomBarViewDelegate> @interface ARoutePlaneController ()<MAMapViewDelegate, AMapNaviDriveManagerDelegate,AMapNaviCompositeManagerDelegate , AMapLocationManagerDelegate , UITextFieldDelegate , AStationDetailPopupDelegate, ABottomBarViewDelegate>
@property (nonatomic, strong) UITextField *textField; @property (nonatomic, strong) UITextField *textField;
/// +线 /// +线
@@ -66,16 +65,11 @@
@property (nonatomic , strong)ACustomStepView * stepView; @property (nonatomic , strong)ACustomStepView * stepView;
/// ///
@property (nonatomic , strong)AStationDetailPopupView * stationDetailPopup; @property (nonatomic , strong)AStationDetailPopupController * stationDetailPopup;
@property (nonatomic, strong) ANavPointModel *pointModel; // @property (nonatomic, strong) ANavPointModel *pointModel; //
@property (nonatomic, strong) ATripCalcDataModel * tjdPathInfoModel;// @property (nonatomic, strong) ATripCalcDataModel * tjdPathInfoModel;//
@property (nonatomic, strong) CLLocationManager * locationManager; @property (nonatomic, strong) CLLocationManager * locationManager;
///
@property (nonatomic, weak) UIButton *addHBtn;
@property (nonatomic ,strong)MAAnnotationView * currAnnotionView;
@end @end
@implementation ARoutePlaneController @implementation ARoutePlaneController
@@ -162,8 +156,6 @@
-(void)requestRoutePathWithParms:(NSDictionary*)dic completeHandle:(void(^)(ATripCalcDataModel * tjd))blk { -(void)requestRoutePathWithParms:(NSDictionary*)dic completeHandle:(void(^)(ATripCalcDataModel * tjd))blk {
///
self.tjdPathInfoModel = nil;
NSString * token = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.token"]; NSString * token = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.token"];
NSString * carNo = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.plateNumber"]; NSString * carNo = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.plateNumber"];
@@ -287,8 +279,7 @@
[bottomBar mas_makeConstraints:^(MASConstraintMaker *make) { [bottomBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view); make.left.right.equalTo(self.view);
// make.bottom.equalTo(self.view).offset(-AMP_TabbarHeight - 13); make.bottom.equalTo(self.view);
make.bottom.equalTo(self.view).offset(0);
}]; }];
// startTf / dstTf // startTf / dstTf
@@ -328,55 +319,25 @@
[stepView addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil]; [stepView addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
self.stepView = stepView; self.stepView = stepView;
stepView.hidden = YES;
/// ///
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setBackgroundImage:[AMapNavCommonUtil imageWithName3x:@"my_location_icon"] forState:UIControlStateNormal]; [btn setImage:[AMapNavCommonUtil imageWithName3x:@"my_location_icon"] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor lightGrayColor];
btn.backgroundColor = [UIColor whiteColor];
btn.titleLabel.font = [UIFont systemFontOfSize:14]; btn.titleLabel.font = [UIFont systemFontOfSize:14];
btn.layer.cornerRadius = 22; btn.layer.cornerRadius = 20;
[btn addTarget:self action:@selector(updateUserLocalAction) forControlEvents:UIControlEventTouchUpInside]; [btn addTarget:self action:@selector(updateUserLocalAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn]; [self.view addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) { [btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.view).offset(-10); make.right.equalTo(self.view).offset(-10);
make.width.height.equalTo(@44); make.width.height.equalTo(@40);
make.bottom.equalTo(bottomBar.mas_top).offset(-105); make.bottom.equalTo(bottomBar.mas_top).offset(-85);
}]; }];
///
UIButton *addHbtn = [UIButton buttonWithType:UIButtonTypeCustom];
addHbtn.backgroundColor = [UIColor hp_colorWithRGBHex:0x017137];
addHbtn.titleLabel.numberOfLines = 2;
[addHbtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[addHbtn setTitle:@"加氢规划" forState:UIControlStateNormal];
addHbtn.titleLabel.font = [UIFont boldSystemFontOfSize:11];
addHbtn.titleEdgeInsets = UIEdgeInsetsMake(0, 3, 0, 3);
addHbtn.layer.cornerRadius = 22;
[addHbtn addTarget:self action:@selector(addHbtnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:addHbtn];
[addHbtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.view).offset(-10);
make.width.height.equalTo(@44);
make.bottom.equalTo(btn.mas_top).offset(-25);
}];
self.addHBtn = addHbtn;
} }
- (void)addHbtnAction:(UIButton *)sender {
//
AAddHPopView *popView = [[AAddHPopView alloc] init];
[popView showInView:self.view sourceView:sender];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"value"]) { if ([keyPath isEqualToString:@"value"]) {
self.mapView.zoomLevel = [change[NSKeyValueChangeNewKey] doubleValue]; self.mapView.zoomLevel = [change[NSKeyValueChangeNewKey] doubleValue];
@@ -402,7 +363,6 @@
self.mapView.userTrackingMode = MAUserTrackingModeFollowWithHeading; self.mapView.userTrackingMode = MAUserTrackingModeFollowWithHeading;
self.mapView.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // self.mapView.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; //
_mapView.showsScale= YES; _mapView.showsScale= YES;
_mapView.showsCompass = NO;
_mapView.logoCenter = CGPointMake(CGRectGetWidth(self.view.bounds)-55, 450); _mapView.logoCenter = CGPointMake(CGRectGetWidth(self.view.bounds)-55, 450);
self.mapView.zoomLevel = 11; self.mapView.zoomLevel = 11;
@@ -429,7 +389,7 @@
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(self.latitude, self.longitude); CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(self.latitude, self.longitude);
[_mapView setCenterCoordinate:coord animated:YES]; [_mapView setCenterCoordinate:coord animated:YES];
[_mapView setZoomLevel:15.0 animated:YES]; // [_mapView setZoomLevel:10 animated:YES];
} else { } else {
// //
[_mapView setUserTrackingMode:MAUserTrackingModeFollow animated:YES]; [_mapView setUserTrackingMode:MAUserTrackingModeFollow animated:YES];
@@ -548,7 +508,7 @@
0, 0,
0, 0,
0); 0);
strategy = AMapNaviDrivingStrategyMultipleDefault;//使DRIVING_MULTIPLE_ROUTES_DEFAULT strategy = AMapNaviDrivingStrategyMultipleDefault;//使
id delegate = [AMapNaviDriveManager sharedInstance].delegate; id delegate = [AMapNaviDriveManager sharedInstance].delegate;
if (!delegate) { if (!delegate) {
@@ -820,8 +780,7 @@
[config setNeedCalculateRouteWhenPresent:NO];// [config setNeedCalculateRouteWhenPresent:NO];//
[config setMultipleRouteNaviMode:NO];//线 [config setMultipleRouteNaviMode:NO];//线
// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO]; // [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
[config setShowDrivingStrategyPreferenceView:NO];
[self.compositeManager presentRoutePlanViewControllerWithOptions:config]; [self.compositeManager presentRoutePlanViewControllerWithOptions:config];
} }
@@ -838,8 +797,6 @@
// [config setMultipleRouteNaviMode:NO];//线 // [config setMultipleRouteNaviMode:NO];//线
// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO]; // [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
// [config setShowDrivingStrategyPreferenceView:NO];
[self.compositeManager presentRoutePlanViewControllerWithOptions:config]; [self.compositeManager presentRoutePlanViewControllerWithOptions:config];
} }
@@ -1021,7 +978,6 @@
} }
- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view { - (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view {
self.currAnnotionView = view;
id pointAnnotation = view.annotation; id pointAnnotation = view.annotation;
if ([pointAnnotation isMemberOfClass:ACustomPointAnnotation.class]) { if ([pointAnnotation isMemberOfClass:ACustomPointAnnotation.class]) {
@@ -1039,13 +995,9 @@
[self updateUIWithData:aoi textField:self.dstTf]; [self updateUIWithData:aoi textField:self.dstTf];
///
//
[self willRequestTJDInfo];
// //
// self.bottomBarView.destinationText = aoi.name; self.bottomBarView.destinationText = aoi.name;
self.bottomBarView.destinationText = nil;
} }
} }
@@ -1057,7 +1009,6 @@
self.dstPoi = nil; self.dstPoi = nil;
self.dstTf.text = nil; self.dstTf.text = nil;
self.bottomBarView.destinationText = nil; self.bottomBarView.destinationText = nil;
self.currAnnotionView = nil;
} }
} }
@@ -1105,7 +1056,6 @@
AMapPOI *_dstPoi = self.dstPoi ? self.dstPoi : self.defaultDstPoi; AMapPOI *_dstPoi = self.dstPoi ? self.dstPoi : self.defaultDstPoi;
if (!_dstPoi) { if (!_dstPoi) {
[AMapNavCommonUtil showMsg:@"请先选择目的地"];
return; return;
} }
@@ -1116,19 +1066,9 @@
navPoint.stationID = _dstPoi.uid; navPoint.stationID = _dstPoi.uid;
self.pointModel = navPoint; self.pointModel = navPoint;
if (self.stationDetailPopup) {
[self.stationDetailPopup resetUI];
}
///_stationID 线 ///_stationID 线
if (!navPoint.stationID) { if (!navPoint.stationID) {
// [self gd_calPathWithNoStationId:navPoint]; [self gd_calPathWithNoStationId:navPoint];
ANavPointModel * model = [ANavPointModel new];
model.name = navPoint.name;
model.address = navPoint.address;
[self showDstInfoPop:model];
return; return;
}else { }else {
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
@@ -1144,7 +1084,7 @@
// --- --- // --- ---
if (!self.stationDetailPopup) { if (!self.stationDetailPopup) {
AStationDetailPopupView *popup = [[AStationDetailPopupView alloc] init]; AStationDetailPopupController *popup = [[AStationDetailPopupController alloc] init];
popup.delegate = self; popup.delegate = self;
self.stationDetailPopup = popup; self.stationDetailPopup = popup;
} }
@@ -1160,26 +1100,14 @@
self.stationDetailPopup.driveDistance = [NSString stringWithFormat:@"%.1f" , self.tjdPathInfoModel.pathDto.distance / 1000.0] ; self.stationDetailPopup.driveDistance = [NSString stringWithFormat:@"%.1f" , self.tjdPathInfoModel.pathDto.distance / 1000.0] ;
/// ///
self.stationDetailPopup.tollFee = self.tjdPathInfoModel.pathDto.tolls; self.stationDetailPopup.tollFee = self.tjdPathInfoModel.pathDto.tolls;
///
self.stationDetailPopup.contactName = self.tjdPathInfoModel.destinationSite.liaisonName;
self.stationDetailPopup.contactPhone = self.tjdPathInfoModel.destinationSite.liaisonPhone;
self.stationDetailPopup.businessHours = [NSString stringWithFormat:@"%@-%@" , [AMapNavCommonUtil stringValueFromStr:self.tjdPathInfoModel.destinationSite.startBusiness] , [AMapNavCommonUtil stringValueFromStr:self.tjdPathInfoModel.destinationSite.endBusiness]];
self.stationDetailPopup.hydrogenPrice = [AMapNavCommonUtil stringValueFromStr:self.tjdPathInfoModel.destinationSite.hydrogenPrice];
[self.stationDetailPopup showInView:self.view]; [self.stationDetailPopup presentInViewController:self];
// [self addChildViewController:self.stationDetailPopup];
//
// CGRect rect = CGRectMake(CGRectGetMinX(self.view.frame), CGRectGetMinY(self.view.frame), CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - (AMP_TabbarHeight + 20) * 2);
// self.stationDetailPopup.view.frame = rect;
//
// [self.view addSubview:self.stationDetailPopup.view];
} }
#pragma mark - AStationDetailPopupViewDelegate #pragma mark - AStationDetailPopupDelegate
- (void)stationDetailPopupViewDidTapStartNavi:(AStationDetailPopupView *)popup { - (void)stationDetailPopupDidTapStartNavi:(AStationDetailPopupController *)popup {
self.stationDetailPopup = nil; self.stationDetailPopup = nil;
/// ///
///1线 ///1线
@@ -1198,17 +1126,13 @@
} }
} }
- (void)stationDetailPopupViewDidTapClose:(AStationDetailPopupView *)popup { - (void)stationDetailPopupDidTapClose:(AStationDetailPopupController *)popup {
self.stationDetailPopup = nil; self.stationDetailPopup = nil;
} }
#pragma mark - ABottomBarViewDelegate #pragma mark - ABottomBarViewDelegate
- (void)bottomBarViewDidTapCalRoute:(ABottomBarView *)barView { - (void)bottomBarViewDidTapCalRoute:(ABottomBarView *)barView {
if (self.currAnnotionView) {
[self.mapView deselectAnnotation:self.currAnnotionView.annotation animated:NO];
self.currAnnotionView = nil;
}
// //
[self willRequestTJDInfo]; [self willRequestTJDInfo];
@@ -1216,14 +1140,6 @@
} }
- (void)bottomBarViewDidTapSearchField:(ABottomBarView *)barView { - (void)bottomBarViewDidTapSearchField:(ABottomBarView *)barView {
///
self.tjdPathInfoModel = nil;
if (self.currAnnotionView) {
[self.mapView deselectAnnotation:self.currAnnotionView.annotation animated:NO];
self.currAnnotionView = nil;
}
// //
ASearchAddressController *vc = [[ASearchAddressController alloc] init]; ASearchAddressController *vc = [[ASearchAddressController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
@@ -1244,8 +1160,7 @@
-(void)updateUIWithData: (AMapPOI*)poi textField: (UITextField*)tf { -(void)updateUIWithData: (AMapPOI*)poi textField: (UITextField*)tf {
BOOL isStart = tf.tag == 100; BOOL isStart = tf.tag == 100;
/// tf.text = poi.name;
// tf.text = poi.name;
if (isStart) { if (isStart) {
self.startPoi = poi; self.startPoi = poi;

View File

@@ -18,12 +18,10 @@
@property (nonatomic , strong) UIBarButtonItem *rightItem; @property (nonatomic , strong) UIBarButtonItem *rightItem;
@property (nonatomic ,strong)UIButton * backBtn; @property (nonatomic ,strong)UIButton * backBtn;
@property (nonatomic , strong) NSMutableArray *dataArr; @property (nonatomic , strong) NSArray *dataArr;
@property (nonatomic, strong) UITextField *inputAddressTf; @property (nonatomic, strong) UITextField *inputAddressTf;
@property (nonatomic, strong) AMapSearchAPI *search; @property (nonatomic, strong) AMapSearchAPI *search;
@property (nonatomic,assign)NSInteger currPage;
@end @end
@implementation ASearchAddressController @implementation ASearchAddressController
@@ -32,9 +30,6 @@
[super viewDidLoad]; [super viewDidLoad];
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
self.title = @"选择地点"; self.title = @"选择地点";
self.currPage = 1;
_dataArr = [NSMutableArray array];
[self initSubview]; [self initSubview];
@@ -70,9 +65,8 @@
[inputAddressTf mas_makeConstraints:^(MASConstraintMaker *make) { [inputAddressTf mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view).offset(10); make.left.mas_equalTo(self.view).offset(10);
make.right.mas_equalTo(self.view).offset(-10);
make.top.mas_equalTo(self.view).offset(kRoutePlanBarHeight + 10); make.top.mas_equalTo(self.view).offset(kRoutePlanBarHeight + 10);
make.height.mas_equalTo(@35); make.height.mas_equalTo(@30);
}]; }];
self.inputAddressTf = inputAddressTf; self.inputAddressTf = inputAddressTf;
@@ -86,8 +80,7 @@
btn.layer.borderWidth = 1; btn.layer.borderWidth = 1;
btn.layer.cornerRadius = 5; btn.layer.cornerRadius = 5;
btn.titleLabel.font = [UIFont systemFontOfSize:12]; btn.titleLabel.font = [UIFont systemFontOfSize:12];
btn.hidden = YES;
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; [btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside]; [btn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn]; [self.view addSubview:btn];
@@ -141,14 +134,6 @@
// [self.navigationController popViewControllerAnimated:YES]; // [self.navigationController popViewControllerAnimated:YES];
} }
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == self.dataArr.count - 1 && (self.dataArr.count % 20 == 0)) {
self.currPage = self.currPage + 1;
[self requestAddressWithAddress:self.inputAddressTf.text atPage:self.currPage];
}
}
#pragma mark - #pragma mark -
- (UITableView *)tableView { - (UITableView *)tableView {
if (!_tableView) { if (!_tableView) {
@@ -193,32 +178,28 @@
return; return;
} }
[self requestAddressWithAddress:addr atPage:1];
}
-(void)requestAddressWithAddress:(NSString *)addr atPage:(NSInteger)page {
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init]; AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
request.keywords = addr; request.keywords = addr;
AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager]; AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
// request.city = sdk.localCity; request.city = sdk.localCity;
// request.types = @"高等院校"; // request.types = @"高等院校";
// request.requireExtension = YES; // request.requireExtension = YES;
request.offset =20; request.offset =20;
request.page = page;
/* SDK 3.2.0 POI*/ /* SDK 3.2.0 POI*/
// request.cityLimit = YES; request.cityLimit = YES;
// request.requireSubPOIs = YES; // request.requireSubPOIs = YES;
[self.search AMapPOIKeywordsSearch:request]; [self.search AMapPOIKeywordsSearch:request];
} }
#pragma mark - #pragma mark -
/* POI . */ /* POI . */
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response - (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
@@ -231,8 +212,7 @@
//responsePOI Demo //responsePOI Demo
[self.dataArr addObjectsFromArray:pois]; self.dataArr = [NSArray arrayWithArray:pois];
[self.tableView reloadData]; [self.tableView reloadData];
} }
@@ -241,8 +221,6 @@
- (BOOL)textFieldShouldReturn:(UITextField *)textField { - (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder]; [textField resignFirstResponder];
[self.dataArr removeAllObjects];
self.currPage = 1;
[self startSearchWithAddress:textField.text]; [self startSearchWithAddress:textField.text];

View File

@@ -39,19 +39,6 @@ NS_ASSUME_NONNULL_BEGIN
/// 过路费,如 @"30元";若 nil 则隐藏 /// 过路费,如 @"30元";若 nil 则隐藏
@property (nonatomic, copy, nullable) NSString *tollFee; @property (nonatomic, copy, nullable) NSString *tollFee;
/// 营业时间,如 @"00:00-24:00";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *businessHours;
/// 站点联系人,如 @"陈凯";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *contactName;
/// 联系方式,如 @"18019187371";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *contactPhone;
/// 加氢价格,如 @"32元/L";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *hydrogenPrice;
@property (nonatomic, weak, nullable) id<AStationDetailPopupDelegate> delegate; @property (nonatomic, weak, nullable) id<AStationDetailPopupDelegate> delegate;
/// 以半透明蒙层方式弹出在目标控制器上 /// 以半透明蒙层方式弹出在目标控制器上
@@ -63,9 +50,6 @@ NS_ASSUME_NONNULL_BEGIN
/// 关闭弹框,动画结束后执行 completion用于关闭后再 present 其他页面) /// 关闭弹框,动画结束后执行 completion用于关闭后再 present 其他页面)
- (void)dismissWithCompletion:(nullable void(^)(void))completion; - (void)dismissWithCompletion:(nullable void(^)(void))completion;
-(void)resetUI;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -57,21 +57,6 @@ static inline UIColor *AStationThemeGreen(void) {
/// bottom constraint /// bottom constraint
@property (nonatomic, strong) MASConstraint *cardBottomConstraint; @property (nonatomic, strong) MASConstraint *cardBottomConstraint;
//
@property (nonatomic, strong) UILabel *businessHoursLabel;
//
@property (nonatomic, strong) UIImageView *contactPersonIconView;
@property (nonatomic, strong) UILabel *contactPersonLabel;
//
@property (nonatomic, strong) UIImageView *priceIconView;
@property (nonatomic, strong) UILabel *priceLabel;
//
@property (nonatomic, strong) UIImageView *phoneIconView;
@property (nonatomic, strong) UILabel *phoneLabel;
@end @end
@implementation AStationDetailPopupController @implementation AStationDetailPopupController
@@ -136,26 +121,6 @@ static inline UIColor *AStationThemeGreen(void) {
if (self.isViewLoaded) [self _updateUI]; if (self.isViewLoaded) [self _updateUI];
} }
- (void)setBusinessHours:(NSString *)businessHours {
_businessHours = [businessHours copy];
if (self.isViewLoaded) [self _updateUI];
}
- (void)setContactName:(NSString *)contactName {
_contactName = [contactName copy];
if (self.isViewLoaded) [self _updateUI];
}
- (void)setContactPhone:(NSString *)contactPhone {
_contactPhone = [contactPhone copy];
if (self.isViewLoaded) [self _updateUI];
}
- (void)setHydrogenPrice:(NSString *)hydrogenPrice {
_hydrogenPrice = [hydrogenPrice copy];
if (self.isViewLoaded) [self _updateUI];
}
#pragma mark - Build UI #pragma mark - Build UI
/** /**
@@ -203,13 +168,10 @@ static inline UIColor *AStationThemeGreen(void) {
[card addSubview:closeBtn]; [card addSubview:closeBtn];
self.closeButton = closeBtn; self.closeButton = closeBtn;
UIColor * headTextColor = [UIColor hp_colorWithRGBHex:0x1D2129];
UIFont * headTextFont = [UIFont hp_pingFangMedium:14];
// //
UILabel *nameLabel = [[UILabel alloc] init]; UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.font = [UIFont hp_pingFangMedium:18]; nameLabel.font = [UIFont boldSystemFontOfSize:18];
nameLabel.textColor = headTextColor; nameLabel.textColor = [UIColor colorWithWhite:0.1 alpha:1];
nameLabel.numberOfLines = 2; nameLabel.numberOfLines = 2;
// nameLabel.adjustsFontSizeToFitWidth = YES; // nameLabel.adjustsFontSizeToFitWidth = YES;
nameLabel.minimumScaleFactor = 0.8; nameLabel.minimumScaleFactor = 0.8;
@@ -218,8 +180,8 @@ static inline UIColor *AStationThemeGreen(void) {
// 20pt // 20pt
UILabel *costLabel = [[UILabel alloc] init]; UILabel *costLabel = [[UILabel alloc] init];
costLabel.font = headTextFont; costLabel.font = [UIFont systemFontOfSize:14];
costLabel.textColor = headTextColor; costLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
costLabel.numberOfLines = 1; costLabel.numberOfLines = 1;
costLabel.textAlignment = NSTextAlignmentLeft; costLabel.textAlignment = NSTextAlignmentLeft;
// [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; // [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
@@ -227,18 +189,10 @@ static inline UIColor *AStationThemeGreen(void) {
[card addSubview:costLabel]; [card addSubview:costLabel];
self.costLabel = costLabel; self.costLabel = costLabel;
// 4pt
UILabel *bizHoursLabel = [[UILabel alloc] init];
bizHoursLabel.font = [UIFont hp_pingFangRegular:14];
bizHoursLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
bizHoursLabel.numberOfLines = 1;
[card addSubview:bizHoursLabel];
self.businessHoursLabel = bizHoursLabel;
// //
UILabel *addrLabel = [[UILabel alloc] init]; UILabel *addrLabel = [[UILabel alloc] init];
addrLabel.font = [UIFont hp_pingFangRegular:14]; addrLabel.font = [UIFont systemFontOfSize:13];
addrLabel.textColor = [UIColor hp_colorWithRGBHex:0x86909C]; addrLabel.textColor = [UIColor colorWithWhite:0.5 alpha:1];
addrLabel.numberOfLines = 2; addrLabel.numberOfLines = 2;
[card addSubview:addrLabel]; [card addSubview:addrLabel];
self.addressLabel = addrLabel; self.addressLabel = addrLabel;
@@ -258,84 +212,45 @@ static inline UIColor *AStationThemeGreen(void) {
// //
UIImageView *timeIcon = [[UIImageView alloc] init]; UIImageView *timeIcon = [[UIImageView alloc] init];
timeIcon.contentMode = UIViewContentModeScaleAspectFit; timeIcon.contentMode = UIViewContentModeScaleAspectFit;
timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_time"]; timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_time_icon"];
[card addSubview:timeIcon]; [card addSubview:timeIcon];
self.timeIconView = timeIcon; self.timeIconView = timeIcon;
// //
UILabel *timeLabel = [[UILabel alloc] init]; UILabel *timeLabel = [[UILabel alloc] init];
timeLabel.font = headTextFont; timeLabel.font = [UIFont systemFontOfSize:14];
timeLabel.textColor = headTextColor; timeLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
[card addSubview:timeLabel]; [card addSubview:timeLabel];
self.timeLabel = timeLabel; self.timeLabel = timeLabel;
// //
UIImageView *distIcon = [[UIImageView alloc] init]; UIImageView *distIcon = [[UIImageView alloc] init];
distIcon.contentMode = UIViewContentModeScaleAspectFit; distIcon.contentMode = UIViewContentModeScaleAspectFit;
distIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_mileage"]; distIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_distance_icon"];
[card addSubview:distIcon]; [card addSubview:distIcon];
self.distanceIconView = distIcon; self.distanceIconView = distIcon;
// //
UILabel *distLabel = [[UILabel alloc] init]; UILabel *distLabel = [[UILabel alloc] init];
distLabel.font = headTextFont; distLabel.font = [UIFont systemFontOfSize:14];
distLabel.textColor = headTextColor; distLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
[card addSubview:distLabel]; [card addSubview:distLabel];
self.distanceLabel = distLabel; self.distanceLabel = distLabel;
// //
UIImageView *tollIcon = [[UIImageView alloc] init]; UIImageView *tollIcon = [[UIImageView alloc] init];
tollIcon.contentMode = UIViewContentModeScaleAspectFit; tollIcon.contentMode = UIViewContentModeScaleAspectFit;
tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_toll"]; tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_cost_icon"];
[card addSubview:tollIcon]; [card addSubview:tollIcon];
self.tollIconView = tollIcon; self.tollIconView = tollIcon;
// //
UILabel *tollLabel = [[UILabel alloc] init]; UILabel *tollLabel = [[UILabel alloc] init];
tollLabel.font = headTextFont; tollLabel.font = [UIFont systemFontOfSize:14];
tollLabel.textColor = headTextColor; tollLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
[card addSubview:tollLabel]; [card addSubview:tollLabel];
self.tollLabel = tollLabel; self.tollLabel = tollLabel;
// &
UIImageView *personIcon = [[UIImageView alloc] init];
personIcon.contentMode = UIViewContentModeScaleAspectFit;
personIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_person"];
[card addSubview:personIcon];
self.contactPersonIconView = personIcon;
UILabel *personLabel = [[UILabel alloc] init];
personLabel.font = headTextFont;
personLabel.textColor = headTextColor;
[card addSubview:personLabel];
self.contactPersonLabel = personLabel;
// &
UIImageView *priceIcon = [[UIImageView alloc] init];
priceIcon.contentMode = UIViewContentModeScaleAspectFit;
priceIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_price"];
[card addSubview:priceIcon];
self.priceIconView = priceIcon;
UILabel *priceLabel = [[UILabel alloc] init];
priceLabel.font = headTextFont;
priceLabel.textColor = headTextColor;
[card addSubview:priceLabel];
self.priceLabel = priceLabel;
// &
UIImageView *phoneIcon = [[UIImageView alloc] init];
phoneIcon.contentMode = UIViewContentModeScaleAspectFit;
phoneIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_phone"];
[card addSubview:phoneIcon];
self.phoneIconView = phoneIcon;
UILabel *phoneLabel = [[UILabel alloc] init];
phoneLabel.font = headTextFont;
phoneLabel.textColor = headTextColor;
[card addSubview:phoneLabel];
self.phoneLabel = phoneLabel;
// //
UIButton *naviBtn = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton *naviBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[naviBtn setTitle:@"开始导航" forState:UIControlStateNormal]; [naviBtn setTitle:@"开始导航" forState:UIControlStateNormal];
@@ -386,34 +301,24 @@ static inline UIColor *AStationThemeGreen(void) {
make.right.equalTo(self.closeButton.mas_left).offset(-12); make.right.equalTo(self.closeButton.mas_left).offset(-12);
}]; }];
// 6pt // 6pt
[self.businessHoursLabel mas_makeConstraints:^(MASConstraintMaker *make) { [self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.stationNameLabel.mas_bottom).offset(10); make.top.equalTo(self.stationNameLabel.mas_bottom).offset(10);
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16); make.right.equalTo(card).offset(-16);
}]; }];
// 6pt
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.businessHoursLabel.mas_bottom).offset(10);
make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16);
}];
self.separator.hidden = YES;
// 线12pt // 线12pt
[self.separator mas_makeConstraints:^(MASConstraintMaker *make) { [self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.addressLabel.mas_bottom).offset(10); make.top.equalTo(self.addressLabel.mas_bottom).offset(15);
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16); make.right.equalTo(card).offset(-16);
make.height.mas_equalTo(0.5); make.height.mas_equalTo(0.5);
}]; }];
CGFloat _offset_y = 18;
// 线14pt // 线14pt
[self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) { [self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separator.mas_bottom).offset(12); make.top.equalTo(self.separator.mas_bottom).offset(15);
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize); make.width.height.mas_equalTo(iconSize);
}]; }];
@@ -428,7 +333,7 @@ static inline UIColor *AStationThemeGreen(void) {
///cost ///cost
[self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) { [self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.timeIconView); make.centerY.equalTo(self.timeIconView);
make.left.equalTo(self.timeLabel.mas_right).offset(40); make.left.equalTo(self.timeLabel.mas_right).offset(30);
make.width.height.mas_equalTo(iconSize); make.width.height.mas_equalTo(iconSize);
}]; }];
@@ -441,7 +346,7 @@ static inline UIColor *AStationThemeGreen(void) {
// + 12pt // + 12pt
[self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) { [self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.timeIconView.mas_bottom).offset(_offset_y); make.top.equalTo(self.timeIconView.mas_bottom).offset(15);
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize); make.width.height.mas_equalTo(iconSize);
}]; }];
@@ -467,49 +372,9 @@ static inline UIColor *AStationThemeGreen(void) {
make.height.mas_equalTo(24); make.height.mas_equalTo(24);
}]; }];
// 14pt // 18pt30pt
[self.contactPersonIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.distanceIconView.mas_bottom).offset(_offset_y);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.contactPersonLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.contactPersonIconView.mas_right).offset(6);
make.height.mas_equalTo(24);
}];
// costIconView
[self.priceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.costIconView.mas_left);
make.width.height.mas_equalTo(iconSize);
}];
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.priceIconView.mas_right).offset(6);
make.right.lessThanOrEqualTo(card).offset(-10);
make.height.mas_equalTo(24);
}];
// 12pt
[self.phoneIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contactPersonIconView.mas_bottom).offset(_offset_y);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.phoneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.phoneIconView);
make.left.equalTo(self.phoneIconView.mas_right).offset(6);
make.height.mas_equalTo(24);
}];
// 18pt safeArea
[self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) { [self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.phoneIconView.mas_bottom).offset(40); make.top.equalTo(self.distanceIconView.mas_bottom).offset(50);
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16); make.right.equalTo(card).offset(-16);
make.height.mas_equalTo(48); make.height.mas_equalTo(48);
@@ -521,63 +386,29 @@ static inline UIColor *AStationThemeGreen(void) {
- (void)_updateUI { - (void)_updateUI {
self.stationNameLabel.text = (self.pointModel.name.length > 0) self.stationNameLabel.text = (self.pointModel.name.length > 0)
? self.pointModel.name : @"-"; ? self.pointModel.name : @"--";
//
self.businessHoursLabel.text = (self.businessHours.length > 0)
? [NSString stringWithFormat:@"营业时间:%@", self.businessHours]
: @"营业时间:-";
self.costLabel.text = (self.estimatedCost.length > 0) self.costLabel.text = (self.estimatedCost.length > 0)
? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost] ? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost]
: @"预计加氢费用:-"; : @"预计加氢费用:--元";
self.addressLabel.text = (self.pointModel.address.length > 0) self.addressLabel.text = (self.pointModel.address.length > 0)
? self.pointModel.address : @"-"; ? self.pointModel.address : @"--";
// "-- 分钟" // "-- 分钟"
self.timeLabel.text = (self.estimatedTime.length > 0) self.timeLabel.text = (self.estimatedTime.length > 0)
? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime] ? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime]
: @"预计时间:-"; : @"预计时间:--分钟";
// "-- 公里" // "-- 公里"
self.distanceLabel.text = (self.driveDistance.length > 0) self.distanceLabel.text = (self.driveDistance.length > 0)
? [NSString stringWithFormat:@"行驶里程:%@公里", self.driveDistance] ? [NSString stringWithFormat:@"行驶里程:%@公里", self.driveDistance]
: @"行驶里程:-"; : @"行驶里程:--公里";
// "-- 元" // "-- 元"
self.tollLabel.text = (self.tollFee.length > 0) self.tollLabel.text = (self.tollFee.length > 0)
? [NSString stringWithFormat:@"过路费:%@元", self.tollFee] ? [NSString stringWithFormat:@"过路费:%@元", self.tollFee]
: @"过路费:-"; : @"过路费:--元";
// -
self.contactPersonLabel.text = (self.contactName.length > 0)
? [NSString stringWithFormat:@"站联系人:%@", self.contactName]
: @"站联系人:-";
// -
self.priceLabel.text = (self.hydrogenPrice.length > 0)
? [NSString stringWithFormat:@"加氢价格:%@/L", self.hydrogenPrice]
: @"加氢价格:-";
// -
self.phoneLabel.text = (self.contactPhone.length > 0)
? [NSString stringWithFormat:@"联系方式:%@", self.contactPhone]
: @"联系方式:-";
}
// UI
-(void)resetUI {
self.stationNameLabel.text = @"-";
self.businessHoursLabel.text = @"营业时间:-";
self.costLabel.text = @"预计加氢费用:-";
self.addressLabel.text = @"地址:-";
self.timeLabel.text = @"预计时间:-";
self.distanceLabel.text = @"行驶里程:-";
self.tollLabel.text = @"过路费:-";
self.contactPersonLabel.text = @"站联系人:-";
self.priceLabel.text = @"加氢价格:-";
self.phoneLabel.text = @"联系方式:-";
} }
#pragma mark - Animation #pragma mark - Animation

View File

@@ -59,11 +59,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, nullable) NSString *latitude; @property (nonatomic, copy, nullable) NSString *latitude;
@property (nonatomic, copy, nullable) NSString *distance; @property (nonatomic, copy, nullable) NSString *distance;
///新增弹框参数4.13
@property (nonatomic, copy, nullable) NSString *hydrogenPrice;
@property (nonatomic, copy, nullable) NSString *liaisonName;
@property (nonatomic, copy, nullable) NSString *liaisonPhone;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -7,8 +7,6 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "UIColor+ANavMap.h"
#import "UIFont+HP.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -36,8 +34,6 @@ BOOL stringIsEmpty(NSString *str);
/// 判断字符串是否非空 /// 判断字符串是否非空
BOOL stringIsNotEmpty(NSString *str); BOOL stringIsNotEmpty(NSString *str);
+(NSString *)stringValueFromStr:(NSString *)str;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -117,15 +117,6 @@ BOOL stringIsNotEmpty (NSString *str)
return ! stringIsEmpty(str); return ! stringIsEmpty(str);
} }
///
+(NSString *)stringValueFromStr:(NSString *)str {
if (stringIsEmpty(str)) {
return @"";
}
return str;
}
#pragma mark - #pragma mark -
+(UIImage *)imageWithName:(NSString *)name { +(UIImage *)imageWithName:(NSString *)name {

View File

@@ -1,20 +0,0 @@
//
// UIColor+ANavMap.h
// AMapNavIOSSDK
//
// Created by admin on 2026/4/11.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIColor (ANavMap)
+ (UIColor *)hp_colorWithRGBHex:(UInt32)hex;
+ (UIColor *)hp_colorWithRGBHex:(UInt32)hex alpha:(CGFloat)alpha;
//格式AHEX
+ (UIColor *)hp_colorWithRGBAHEX:(UInt32)hex;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,53 +0,0 @@
//
// UIColor+ANavMap.m
// AMapNavIOSSDK
//
// Created by admin on 2026/4/11.
//
#import "UIColor+ANavMap.h"
@implementation UIColor (ANavMap)
//HEX
+ (UIColor *)hp_colorWithRGBHex:(UInt32)hex
{
CGFloat r = (hex >> 16) & 0xFF;
CGFloat g = (hex >> 8) & 0xFF;
CGFloat b = (hex) & 0xFF;
CGFloat a = 1.0f;
return [UIColor colorWithRed:r / 255.0f
green:g / 255.0f
blue:b / 255.0f
alpha:a];
}
+ (UIColor *)hp_colorWithRGBHex:(UInt32)hex alpha:(CGFloat)alpha
{
CGFloat r = (hex >> 16) & 0xFF;
CGFloat g = (hex >> 8) & 0xFF;
CGFloat b = (hex) & 0xFF;
CGFloat a = alpha;
return [UIColor colorWithRed:r / 255.0f
green:g / 255.0f
blue:b / 255.0f
alpha:a];
}
//AHEX
+ (UIColor *)hp_colorWithRGBAHEX:(UInt32)hex
{
CGFloat r = (hex >> 24) & 0xFF;
CGFloat g = (hex >> 16) & 0xFF;
CGFloat b = (hex >> 8) & 0xFF;
CGFloat a = (hex) & 0xFF;
return [UIColor colorWithRed:r / 255.0f
green:g / 255.0f
blue:b / 255.0f
alpha:a / 255.0f];
}
@end

View File

@@ -1,36 +0,0 @@
//
// UIFont+HP.h
// Hippo
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* UIFont 快捷分类
* 统一使用 PingFang SC 字族,覆盖 Light / Regular / Medium / Semibold 四个字重。
*
* 使用示例:
* label.font = [UIFont hp_pingFangLight:14];
* label.font = [UIFont hp_pingFangRegular:16];
* label.font = [UIFont hp_pingFangMedium:15];
* label.font = [UIFont hp_pingFangSemibold:18];
*/
@interface UIFont (HP)
#pragma mark - PingFang SC Light细体
+ (UIFont *)hp_pingFangLight:(CGFloat)size;
#pragma mark - PingFang SC Regular常规
+ (UIFont *)hp_pingFangRegular:(CGFloat)size;
#pragma mark - PingFang SC Medium中等
+ (UIFont *)hp_pingFangMedium:(CGFloat)size;
#pragma mark - PingFang SC Semibold半粗
+ (UIFont *)hp_pingFangSemibold:(CGFloat)size;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,45 +0,0 @@
//
// UIFont+HP.m
// Hippo
//
#import "UIFont+HP.h"
// PingFang SC
static NSString * const kHPFontPingFangLight = @"PingFangSC-Light";
static NSString * const kHPFontPingFangRegular = @"PingFangSC-Regular";
static NSString * const kHPFontPingFangMedium = @"PingFangSC-Medium";
static NSString * const kHPFontPingFangSemibold = @"PingFangSC-Semibold";
@implementation UIFont (HP)
#pragma mark - PingFang SC Light
+ (UIFont *)hp_pingFangLight:(CGFloat)size {
UIFont *font = [UIFont fontWithName:kHPFontPingFangLight size:size];
// 退
return font ?: [UIFont systemFontOfSize:size weight:UIFontWeightLight];
}
#pragma mark - PingFang SC Regular
+ (UIFont *)hp_pingFangRegular:(CGFloat)size {
UIFont *font = [UIFont fontWithName:kHPFontPingFangRegular size:size];
return font ?: [UIFont systemFontOfSize:size weight:UIFontWeightRegular];
}
#pragma mark - PingFang SC Medium
+ (UIFont *)hp_pingFangMedium:(CGFloat)size {
UIFont *font = [UIFont fontWithName:kHPFontPingFangMedium size:size];
return font ?: [UIFont systemFontOfSize:size weight:UIFontWeightMedium];
}
#pragma mark - PingFang SC Semibold
+ (UIFont *)hp_pingFangSemibold:(CGFloat)size {
UIFont *font = [UIFont fontWithName:kHPFontPingFangSemibold size:size];
return font ?: [UIFont systemFontOfSize:size weight:UIFontWeightSemibold];
}
@end

View File

@@ -163,7 +163,6 @@ static inline UIColor *ABottomBarThemeGreen(void) {
self.calRouteButton = btn; self.calRouteButton = btn;
CGFloat off_y = AMP_TabbarHeight; CGFloat off_y = AMP_TabbarHeight;
off_y = 0;
#ifdef kAMapSDKDebugFlag #ifdef kAMapSDKDebugFlag
off_y = 0; off_y = 0;
#endif #endif
@@ -173,7 +172,7 @@ static inline UIColor *ABottomBarThemeGreen(void) {
make.left.equalTo(card).offset(16); make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16); make.right.equalTo(card).offset(-16);
make.height.mas_equalTo(48); make.height.mas_equalTo(48);
make.bottom.equalTo(card).offset(-40 + (-AMP_TabbarHeight - 13)); make.bottom.equalTo(card).offset(-40 - off_y);
}]; }];
} }

View File

@@ -1,70 +0,0 @@
//
// AStationDetailPopupView.h
// AMapNavIOSSDK
//
// Created by admin on 2026/3/22.
//
#import <UIKit/UIKit.h>
#import "ANavPointModel.h"
#import "AMapNavSDKHeader.h"
NS_ASSUME_NONNULL_BEGIN
@class AStationDetailPopupView;
@protocol AStationDetailPopupViewDelegate <NSObject>
@optional
/// 点击"开始导航"
- (void)stationDetailPopupViewDidTapStartNavi:(AStationDetailPopupView *)popup;
/// 点击关闭
- (void)stationDetailPopupViewDidTapClose:(AStationDetailPopupView *)popup;
@end
@interface AStationDetailPopupView : UIView
@property (nonatomic, strong, nullable) ANavPointModel *pointModel;
/// 预计加氢费用(元),可由外部传入;若 nil 则隐藏
@property (nonatomic, copy, nullable) NSString *estimatedCost;
/// 预计时间,如 @"15分钟";若 nil 则隐藏
@property (nonatomic, copy, nullable) NSString *estimatedTime;
/// 行驶里程,如 @"23.5公里";若 nil 则隐藏
@property (nonatomic, copy, nullable) NSString *driveDistance;
/// 过路费,如 @"30元";若 nil 则隐藏
@property (nonatomic, copy, nullable) NSString *tollFee;
/// 营业时间,如 @"00:00-24:00";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *businessHours;
/// 站点联系人,如 @"陈凯";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *contactName;
/// 联系方式,如 @"18019187371";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *contactPhone;
/// 加氢价格,如 @"32元/L";有值显示,无值显示 -
@property (nonatomic, copy, nullable) NSString *hydrogenPrice;
@property (nonatomic, weak, nullable) id<AStationDetailPopupViewDelegate> delegate;
/// 显示弹框动画
- (void)showInView:(UIView *)parentView;
/// 隐藏弹框动画
- (void)hideWithCompletion:(nullable void(^)(void))completion;
/// 隐藏弹框动画(无 completion
- (void)hide;
/// 重置 UI 状态
- (void)resetUI;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,707 +0,0 @@
//
// AStationDetailPopupView.m
// AMapNavIOSSDK
//
// Created by admin on 2026/3/22.
//
#import "AStationDetailPopupView.h"
#import "AMapNavCommonUtil.h"
#import <Masonry/Masonry.h>
// 绿
static inline UIColor *AStationThemeGreen(void) {
return [UIColor colorWithRed:0x1A/255.0 green:0x7C/255.0 blue:0x43/255.0 alpha:1.0];
}
@interface AStationDetailPopupView ()
///
@property (nonatomic, strong) UIControl *maskControl;
///
@property (nonatomic, strong) UIView *cardView;
///
@property (nonatomic, strong) UILabel *stationNameLabel;
///
@property (nonatomic, strong) UIImageView *costIconView;
@property (nonatomic, strong) UILabel *costLabel;
///
@property (nonatomic, strong) UILabel *addressLabel;
/// 线
@property (nonatomic, strong) UIView *separator;
///
@property (nonatomic, strong) UIImageView *timeIconView;
@property (nonatomic, strong) UILabel *timeLabel;
///
@property (nonatomic, strong) UIImageView *distanceIconView;
@property (nonatomic, strong) UILabel *distanceLabel;
///
@property (nonatomic, strong) UIImageView *tollIconView;
@property (nonatomic, strong) UILabel *tollLabel;
///
@property (nonatomic, strong) UIButton *closeButton;
///
@property (nonatomic, strong) UIButton *startNaviButton;
//
@property (nonatomic, strong) UILabel *businessHoursLabel;
//
@property (nonatomic, strong) UIImageView *contactPersonIconView;
@property (nonatomic, strong) UILabel *contactPersonLabel;
//
@property (nonatomic, strong) UIImageView *priceIconView;
@property (nonatomic, strong) UILabel *priceLabel;
//
@property (nonatomic, strong) UIImageView *phoneIconView;
@property (nonatomic, strong) UILabel *phoneLabel;
@end
@implementation AStationDetailPopupView
#pragma mark - Init
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
[self setupUI];
}
return self;
}
#pragma mark - Setup UI
- (void)setupUI {
[self addSubview:self.maskControl];
[self.cardView addSubview:self.closeButton];
[self.cardView addSubview:self.stationNameLabel];
[self.cardView addSubview:self.costLabel];
[self.cardView addSubview:self.businessHoursLabel];
[self.cardView addSubview:self.addressLabel];
[self.cardView addSubview:self.costIconView];
[self.cardView addSubview:self.separator];
[self.cardView addSubview:self.timeIconView];
[self.cardView addSubview:self.timeLabel];
[self.cardView addSubview:self.distanceIconView];
[self.cardView addSubview:self.distanceLabel];
[self.cardView addSubview:self.tollIconView];
[self.cardView addSubview:self.tollLabel];
[self.cardView addSubview:self.contactPersonIconView];
[self.cardView addSubview:self.contactPersonLabel];
[self.cardView addSubview:self.priceIconView];
[self.cardView addSubview:self.priceLabel];
[self.cardView addSubview:self.phoneIconView];
[self.cardView addSubview:self.phoneLabel];
[self.cardView addSubview:self.startNaviButton];
[self setupConstraints];
[self updateUI];
}
#pragma mark - Masonry Constraints
- (void)setupConstraints {
UIView *card = self.cardView;
CGFloat iconSize = 16;
//
[self.maskControl mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
// 16
[card mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(0);
make.right.equalTo(self).offset(-0);
make.bottom.equalTo(self).offset(-0);
}];
//
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(card).offset(8);
make.right.equalTo(card).offset(-15);
make.width.height.mas_equalTo(40);
}];
//
[self.stationNameLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[self.stationNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(card).offset(25);
make.left.equalTo(card).offset(16);
make.right.equalTo(self.closeButton.mas_left).offset(-12);
}];
// 10pt
[self.businessHoursLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.stationNameLabel.mas_bottom).offset(10);
make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16);
}];
// 10pt
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.businessHoursLabel.mas_bottom).offset(10);
make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16);
}];
self.separator.hidden = YES;
// 线10pt
[self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.addressLabel.mas_bottom).offset(10);
make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16);
make.height.mas_equalTo(0.5);
}];
CGFloat _offset_y = 18;
// 线12pt
[self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.separator.mas_bottom).offset(12);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.timeIconView);
make.left.equalTo(self.timeIconView.mas_right).offset(6);
make.height.mas_equalTo(24);
}];
/// cost
[self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.timeIconView);
make.left.equalTo(self.timeLabel.mas_right).offset(40);
make.width.height.mas_equalTo(iconSize);
}];
[self.costLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.costIconView);
make.left.equalTo(self.costIconView.mas_right).offset(6);
make.right.lessThanOrEqualTo(card).offset(-10);
}];
// + 12pt
[self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.timeIconView.mas_bottom).offset(_offset_y);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.distanceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.distanceIconView);
make.left.equalTo(self.distanceIconView.mas_right).offset(6);
make.width.mas_lessThanOrEqualTo(130);
make.height.mas_equalTo(24);
}];
[self.tollIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.distanceIconView);
make.left.equalTo(self.costIconView.mas_left);
make.width.height.mas_equalTo(iconSize);
}];
[self.tollLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.distanceIconView);
make.left.equalTo(self.tollIconView.mas_right).offset(6);
make.height.mas_equalTo(24);
}];
// 14pt
[self.contactPersonIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.distanceIconView.mas_bottom).offset(_offset_y);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.contactPersonLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.contactPersonIconView.mas_right).offset(6);
// make.height.mas_equalTo(24);
make.width.lessThanOrEqualTo(self).multipliedBy(0.4);
}];
// costIconView
[self.priceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.costIconView.mas_left);
make.width.height.mas_equalTo(iconSize);
}];
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.contactPersonIconView);
make.left.equalTo(self.priceIconView.mas_right).offset(6);
make.right.lessThanOrEqualTo(card).offset(-10);
make.height.mas_equalTo(24);
}];
// 12pt
[self.phoneIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contactPersonIconView.mas_bottom).offset(_offset_y);
make.left.equalTo(card).offset(16);
make.width.height.mas_equalTo(iconSize);
}];
[self.phoneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.phoneIconView);
make.left.equalTo(self.phoneIconView.mas_right).offset(6);
make.height.mas_equalTo(24);
}];
// 18pt safeArea
[self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.phoneIconView.mas_bottom).offset(40);
make.left.equalTo(card).offset(16);
make.right.equalTo(card).offset(-16);
make.height.mas_equalTo(48);
make.bottom.equalTo(card).offset(-AMP_TabbarSafeBottomMargin + (-AMP_TabbarHeight - 13));
}];
}
#pragma mark - Public
- (void)showInView:(UIView *)parentView {
self.alpha = 0;
self.maskControl.alpha = 0;
[parentView addSubview:self];
[self mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(parentView);
}];
[self playShowAnimation];
}
- (void)hide {
[self hideWithCompletion:nil];
}
- (void)hideWithCompletion:(void(^)(void))completion {
[self playDismissAnimationWithCompletion:^{
[self removeFromSuperview];
if (completion) {
completion();
}
}];
}
#pragma mark - Setter Override
- (void)setPointModel:(ANavPointModel *)pointModel {
_pointModel = pointModel;
[self updateUI];
}
- (void)setEstimatedCost:(NSString *)estimatedCost {
_estimatedCost = [estimatedCost copy];
[self updateUI];
}
- (void)setEstimatedTime:(NSString *)estimatedTime {
_estimatedTime = [estimatedTime copy];
[self updateUI];
}
- (void)setDriveDistance:(NSString *)driveDistance {
_driveDistance = [driveDistance copy];
[self updateUI];
}
- (void)setTollFee:(NSString *)tollFee {
_tollFee = [tollFee copy];
[self updateUI];
}
- (void)setBusinessHours:(NSString *)businessHours {
_businessHours = [businessHours copy];
[self updateUI];
}
- (void)setContactName:(NSString *)contactName {
_contactName = [contactName copy];
[self updateUI];
}
- (void)setContactPhone:(NSString *)contactPhone {
_contactPhone = [contactPhone copy];
[self updateUI];
}
- (void)setHydrogenPrice:(NSString *)hydrogenPrice {
_hydrogenPrice = [hydrogenPrice copy];
[self updateUI];
}
#pragma mark - Data Update
- (void)updateUI {
self.stationNameLabel.text = (self.pointModel.name.length > 0)
? self.pointModel.name : @"-";
//
self.businessHoursLabel.text = (self.businessHours.length > 0)
? [NSString stringWithFormat:@"营业时间:%@", self.businessHours]
: @"营业时间:-";
self.costLabel.text = ((self.estimatedCost.length > 0) && [self.estimatedCost doubleValue])
? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost]
: @"预计加氢费用:-";
self.addressLabel.text = (self.pointModel.address.length > 0)
? self.pointModel.address : @"-";
// "-- 分钟"
self.timeLabel.text = ((self.estimatedTime.length > 0) && [self.estimatedTime doubleValue] > 0)
? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime]
: @"预计时间:-";
// "-- 公里"
self.distanceLabel.text = ((self.driveDistance.length > 0) && [self.driveDistance doubleValue] > 0)
? [NSString stringWithFormat:@"行驶里程:%@公里", self.driveDistance]
: @"行驶里程:-";
// "-- 元"
self.tollLabel.text = (self.tollFee.length > 0)
? [NSString stringWithFormat:@"过路费:%@元", self.tollFee]
: @"过路费:-";
// -
self.contactPersonLabel.text = (self.contactName.length > 0)
? [NSString stringWithFormat:@"站联系人:%@", self.contactName]
: @"站联系人:-";
// -
self.priceLabel.text = (self.hydrogenPrice.length > 0)
? [NSString stringWithFormat:@"加氢价格:%@/L", self.hydrogenPrice]
: @"加氢价格:-";
// -
self.phoneLabel.text = (self.contactPhone.length > 0)
? [NSString stringWithFormat:@"联系方式:%@", self.contactPhone]
: @"联系方式:-";
}
- (void)resetUI {
self.stationNameLabel.text = @"-";
self.businessHoursLabel.text = @"营业时间:-";
self.costLabel.text = @"预计加氢费用:-";
self.addressLabel.text = @"地址:-";
self.timeLabel.text = @"预计时间:-";
self.distanceLabel.text = @"行驶里程:-";
self.tollLabel.text = @"过路费:-";
self.contactPersonLabel.text = @"站联系人:-";
self.priceLabel.text = @"加氢价格:-";
self.phoneLabel.text = @"联系方式:-";
}
#pragma mark - Animation
/**
*/
- (void)playShowAnimation {
//
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(0);
make.right.equalTo(self).offset(-0);
make.top.equalTo(self.mas_bottom).offset(0);
}];
[self layoutIfNeeded];
//
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(0);
make.right.equalTo(self).offset(-0);
make.bottom.equalTo(self).offset(0);
}];
[UIView animateWithDuration:0.36
delay:0
usingSpringWithDamping:0.82
initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.alpha = 1;
self.maskControl.alpha = 1;
[self layoutIfNeeded];
} completion:nil];
}
- (void)playDismissAnimationWithCompletion:(void(^)(void))completion {
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(0);
make.right.equalTo(self).offset(-0);
make.top.equalTo(self.mas_bottom).offset(20);
}];
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.maskControl.alpha = 0;
[self layoutIfNeeded];
} completion:^(BOOL finished) {
if (completion) completion();
}];
}
#pragma mark - Actions
- (void)onMaskTapped {
if ([self.delegate respondsToSelector:@selector(stationDetailPopupViewDidTapClose:)]) {
[self.delegate stationDetailPopupViewDidTapClose:self];
}
[self hide];
}
- (void)onCloseTapped {
if ([self.delegate respondsToSelector:@selector(stationDetailPopupViewDidTapClose:)]) {
[self.delegate stationDetailPopupViewDidTapClose:self];
}
[self hide];
}
- (void)onStartNaviTapped {
__weak typeof(self) weakSelf = self;
[self hideWithCompletion:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([strongSelf.delegate respondsToSelector:@selector(stationDetailPopupViewDidTapStartNavi:)]) {
[strongSelf.delegate stationDetailPopupViewDidTapStartNavi:strongSelf];
}
}];
}
#pragma mark - Lazy Load
- (UIControl *)maskControl {
if (!_maskControl) {
_maskControl = [[UIControl alloc] init];
_maskControl.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
[_maskControl addTarget:self action:@selector(onMaskTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _maskControl;
}
- (UIView *)cardView {
if (!_cardView) {
_cardView = [[UIView alloc] init];
_cardView.backgroundColor = [UIColor whiteColor];
_cardView.layer.cornerRadius = 16;
_cardView.layer.masksToBounds = NO;
_cardView.layer.shadowColor = [UIColor blackColor].CGColor;
_cardView.layer.shadowOpacity = 0.15;
_cardView.layer.shadowRadius = 12;
_cardView.layer.shadowOffset = CGSizeMake(0, -4);
[self addSubview:_cardView];
}
return _cardView;
}
- (UIButton *)closeButton {
if (!_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeButton setImage:[AMapNavCommonUtil imageWithName:@"icon_close"] forState:UIControlStateNormal];
[_closeButton setTitleColor:[UIColor colorWithWhite:0.5 alpha:1] forState:UIControlStateNormal];
_closeButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_closeButton addTarget:self action:@selector(onCloseTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
- (UILabel *)stationNameLabel {
if (!_stationNameLabel) {
_stationNameLabel = [[UILabel alloc] init];
_stationNameLabel.font = [UIFont hp_pingFangMedium:18];
_stationNameLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
_stationNameLabel.numberOfLines = 2;
_stationNameLabel.minimumScaleFactor = 0.8;
}
return _stationNameLabel;
}
- (UILabel *)costLabel {
if (!_costLabel) {
_costLabel = [[UILabel alloc] init];
_costLabel.font = [UIFont hp_pingFangMedium:14];
_costLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
_costLabel.numberOfLines = 1;
_costLabel.textAlignment = NSTextAlignmentLeft;
}
return _costLabel;
}
- (UILabel *)businessHoursLabel {
if (!_businessHoursLabel) {
_businessHoursLabel = [[UILabel alloc] init];
_businessHoursLabel.font = [UIFont hp_pingFangRegular:14];
_businessHoursLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
_businessHoursLabel.numberOfLines = 1;
}
return _businessHoursLabel;
}
- (UILabel *)addressLabel {
if (!_addressLabel) {
_addressLabel = [[UILabel alloc] init];
_addressLabel.font = [UIFont hp_pingFangRegular:14];
_addressLabel.textColor = [UIColor hp_colorWithRGBHex:0x86909C];
_addressLabel.numberOfLines = 2;
}
return _addressLabel;
}
- (UIImageView *)costIconView {
if (!_costIconView) {
_costIconView = [[UIImageView alloc] init];
_costIconView.contentMode = UIViewContentModeScaleAspectFit;
_costIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_fuel"];
}
return _costIconView;
}
- (UIView *)separator {
if (!_separator) {
_separator = [[UIView alloc] init];
_separator.backgroundColor = [UIColor colorWithWhite:0.88 alpha:1];
_separator.hidden = YES;
}
return _separator;
}
- (UIImageView *)timeIconView {
if (!_timeIconView) {
_timeIconView = [[UIImageView alloc] init];
_timeIconView.contentMode = UIViewContentModeScaleAspectFit;
_timeIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_time"];
}
return _timeIconView;
}
- (UILabel *)timeLabel {
if (!_timeLabel) {
_timeLabel = [[UILabel alloc] init];
_timeLabel.font = [UIFont hp_pingFangMedium:14];
_timeLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _timeLabel;
}
- (UIImageView *)distanceIconView {
if (!_distanceIconView) {
_distanceIconView = [[UIImageView alloc] init];
_distanceIconView.contentMode = UIViewContentModeScaleAspectFit;
_distanceIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_mileage"];
}
return _distanceIconView;
}
- (UILabel *)distanceLabel {
if (!_distanceLabel) {
_distanceLabel = [[UILabel alloc] init];
_distanceLabel.font = [UIFont hp_pingFangMedium:14];
_distanceLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _distanceLabel;
}
- (UIImageView *)tollIconView {
if (!_tollIconView) {
_tollIconView = [[UIImageView alloc] init];
_tollIconView.contentMode = UIViewContentModeScaleAspectFit;
_tollIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_toll"];
}
return _tollIconView;
}
- (UILabel *)tollLabel {
if (!_tollLabel) {
_tollLabel = [[UILabel alloc] init];
_tollLabel.font = [UIFont hp_pingFangMedium:14];
_tollLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _tollLabel;
}
- (UIImageView *)contactPersonIconView {
if (!_contactPersonIconView) {
_contactPersonIconView = [[UIImageView alloc] init];
_contactPersonIconView.contentMode = UIViewContentModeScaleAspectFit;
_contactPersonIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_person"];
}
return _contactPersonIconView;
}
- (UILabel *)contactPersonLabel {
if (!_contactPersonLabel) {
_contactPersonLabel = [[UILabel alloc] init];
_contactPersonLabel.font = [UIFont hp_pingFangMedium:14];
_contactPersonLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _contactPersonLabel;
}
- (UIImageView *)priceIconView {
if (!_priceIconView) {
_priceIconView = [[UIImageView alloc] init];
_priceIconView.contentMode = UIViewContentModeScaleAspectFit;
_priceIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_price"];
}
return _priceIconView;
}
- (UILabel *)priceLabel {
if (!_priceLabel) {
_priceLabel = [[UILabel alloc] init];
_priceLabel.font = [UIFont hp_pingFangMedium:14];
_priceLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _priceLabel;
}
- (UIImageView *)phoneIconView {
if (!_phoneIconView) {
_phoneIconView = [[UIImageView alloc] init];
_phoneIconView.contentMode = UIViewContentModeScaleAspectFit;
_phoneIconView.image = [AMapNavCommonUtil imageWithName3x:@"ic_phone"];
}
return _phoneIconView;
}
- (UILabel *)phoneLabel {
if (!_phoneLabel) {
_phoneLabel = [[UILabel alloc] init];
_phoneLabel.font = [UIFont hp_pingFangMedium:14];
_phoneLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
}
return _phoneLabel;
}
- (UIButton *)startNaviButton {
if (!_startNaviButton) {
_startNaviButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_startNaviButton setTitle:@"开始导航" forState:UIControlStateNormal];
[_startNaviButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_startNaviButton.titleLabel.font = [UIFont boldSystemFontOfSize:17];
_startNaviButton.backgroundColor = AStationThemeGreen();
_startNaviButton.layer.cornerRadius = 24;
[_startNaviButton addTarget:self action:@selector(onStartNaviTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _startNaviButton;
}
@end

View File

@@ -26,6 +26,8 @@ PODS:
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
- flutter_inappwebview_ios (0.0.1): - flutter_inappwebview_ios (0.0.1):
- Flutter - Flutter
- flutter_inappwebview_ios/Core (= 0.0.1) - flutter_inappwebview_ios/Core (= 0.0.1)
@@ -51,6 +53,9 @@ PODS:
- OrderedSet (6.0.3) - OrderedSet (6.0.3)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@@ -65,6 +70,7 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`) - flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
@@ -72,6 +78,7 @@ DEPENDENCIES:
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -103,6 +110,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_app_update:
:path: ".symlinks/plugins/flutter_app_update/ios"
flutter_inappwebview_ios: flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios" :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_native_splash: flutter_native_splash:
@@ -117,6 +126,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/mobile_scanner/darwin" :path: ".symlinks/plugins/mobile_scanner/darwin"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation: shared_preferences_foundation:
@@ -128,29 +139,31 @@ SPEC CHECKSUMS:
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41 AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
AlicloudPush: 52cbf38ffc20c07f039cbc72d5738745fd986215 AlicloudPush: 52cbf38ffc20c07f039cbc72d5738745fd986215
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976 AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
aliyun_push_flutter: ab0bf7112ef3797f506770a7a9f47f004635a9f6 aliyun_push_flutter: 0fc2f048a08687ef256c0cfdd72dd7a550ef3347
AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05 AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
AMapLocation-NO-IDFA: 590fd42af0c8ea9eac26978348221bbc16be4ef9 AMapLocation-NO-IDFA: 590fd42af0c8ea9eac26978348221bbc16be4ef9
AMapNavi-NO-IDFA: 22edfa7d6a81d75c91756e31b6c26b7746152233 AMapNavi-NO-IDFA: 22edfa7d6a81d75c91756e31b6c26b7746152233
AMapNavIOSSDK: 092382d55290f43b282ffcc522c274996794e2bc AMapNavIOSSDK: e06adcb48ac8abeace46ea31f72191564e54a186
AMapSearch-NO-IDFA: 53b2193244be8f07f3be0a4d5161200236960587 AMapSearch-NO-IDFA: 53b2193244be8f07f3be0a4d5161200236960587
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_pdfview: 2e4d13ffb774858562ffbdfdb61b40744b191adc flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406 MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8 MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
PODFILE CHECKSUM: b4931d4490f04261e0fda802d44e275ab3619244 PODFILE CHECKSUM: b4931d4490f04261e0fda802d44e275ab3619244

View File

@@ -0,0 +1,22 @@
import 'package:getx_scaffold/common/index.dart';
import 'package:ln_jq_app/pages/login/view.dart';
import 'package:ln_jq_app/storage_service.dart';
class AuthGuard {
static bool _handling401 = false;
static Future<void> handle401(String? message) async {
if (_handling401) return;
_handling401 = true;
try {
await StorageService.to.clearLoginInfo();
Get.offAll(() => const LoginPage());
} finally {
// 防止意外卡死,可视情况是否延迟重置
Future.delayed(const Duration(seconds: 1), () {
_handling401 = false;
});
}
}
}

View File

@@ -4,7 +4,9 @@ class StationModel {
final String address; final String address;
final String price; final String price;
final String siteStatusName; // 例如 "维修中" final String siteStatusName; // 例如 "维修中"
final int isSelect; // 新增字段 1是可用 0是不可用 final int isSelect; // 1是可用 0是不可用
final String startBusiness; // 新增:可预约最早开始时间,如 "06:00:00"
final String endBusiness; // 新增:可预约最晚结束时间,如 "22:00:00"
StationModel({ StationModel({
required this.hydrogenId, required this.hydrogenId,
@@ -13,9 +15,10 @@ class StationModel {
required this.price, required this.price,
required this.siteStatusName, required this.siteStatusName,
required this.isSelect, required this.isSelect,
required this.startBusiness,
required this.endBusiness,
}); });
// 从 JSON map 创建对象的工厂构造函数
factory StationModel.fromJson(Map<String, dynamic> json) { factory StationModel.fromJson(Map<String, dynamic> json) {
return StationModel( return StationModel(
hydrogenId: json['hydrogenId'] ?? '', hydrogenId: json['hydrogenId'] ?? '',
@@ -23,7 +26,9 @@ class StationModel {
address: json['address'] ?? '地址未知', address: json['address'] ?? '地址未知',
price: json['price']?.toString() ?? '0.00', price: json['price']?.toString() ?? '0.00',
siteStatusName: json['siteStatusName'] ?? '', siteStatusName: json['siteStatusName'] ?? '',
isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0 isSelect: json['isSelect'] as int? ?? 0,
startBusiness: json['startBusiness'] ?? '00:00:00', // 默认全天
endBusiness: json['endBusiness'] ?? '23:59:59', // 默认全天
); );
} }
} }

View File

@@ -8,7 +8,7 @@ class AppTheme {
static const Color themeColor = Color(0xFF017137); static const Color themeColor = Color(0xFF017137);
//是否开放域名切换 //是否开放域名切换
static const bool is_show_host = false; static const bool is_show_host = true;
//http://192.168.110.222:8080/ //http://192.168.110.222:8080/
//http://192.168.110.44:8080/ //http://192.168.110.44:8080/

View File

@@ -24,7 +24,6 @@ class TokenInterceptor extends Interceptor {
if (!options.headers.containsKey(tokenKey)) { if (!options.headers.containsKey(tokenKey)) {
// 使用我们自定义的 key 添加 token // 使用我们自定义的 key 添加 token
options.headers[tokenKey] = token; options.headers[tokenKey] = token;
print("head.token: $token");
} }
} }

View File

@@ -3,6 +3,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/AuthGuard.dart';
import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/common/token_interceptor.dart'; import 'package:ln_jq_app/common/token_interceptor.dart';
import 'package:ln_jq_app/storage_service.dart'; import 'package:ln_jq_app/storage_service.dart';
@@ -20,7 +21,7 @@ void main() async {
logTag: '小羚羚', logTag: '小羚羚',
supportedLocales: [const Locale('zh', 'CN')], supportedLocales: [const Locale('zh', 'CN')],
); );
// 保持原生闪屏页,直到 WelcomeController 调用 remove() // 保持原生闪屏页,直到 WelcomeController 调用 remove()
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
@@ -65,6 +66,7 @@ void main() async {
void initHttpSet() { void initHttpSet() {
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url; AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
HttpService.to.init(timeout: 15);
HttpService.to.setBaseUrl(AppTheme.test_service_url); HttpService.to.setBaseUrl(AppTheme.test_service_url);
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token')); HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
HttpService.to.setOnResponseHandler((response) async { HttpService.to.setOnResponseHandler((response) async {
@@ -76,8 +78,7 @@ void initHttpSet() {
if (baseModel.code == 0 || baseModel.code == 200) { if (baseModel.code == 0 || baseModel.code == 200) {
return null; return null;
} else if (baseModel.code == 401) { } else if (baseModel.code == 401) {
await StorageService.to.clearLoginInfo(); await AuthGuard.handle401(baseModel.message);
Get.offAll(() => const LoginPage());
return baseModel.message; return baseModel.message;
} else { } else {
return (baseModel.error.toString()).isEmpty return (baseModel.error.toString()).isEmpty

View File

@@ -1,10 +1,14 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:getx_scaffold/getx_scaffold.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/common/model/base_model.dart';
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
class HistoryController extends GetxController { class HistoryController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'history';
// --- 定义 API 需要的日期格式化器 --- // --- 定义 API 需要的日期格式化器 ---
final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd'); final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
@@ -13,24 +17,39 @@ class HistoryController extends GetxController {
final Rx<DateTime> endDate = DateTime.now().obs; final Rx<DateTime> endDate = DateTime.now().obs;
final TextEditingController plateNumberController = TextEditingController(); final TextEditingController plateNumberController = TextEditingController();
final RxString totalHydrogen = '0 kg'.obs; final RxString totalHydrogen = '0'.obs;
final RxString totalCompletions = '0'.obs; final RxString totalCompletions = '0'.obs;
final RxList<ReservationModel> historyList = <ReservationModel>[].obs; final RxList<ReservationModel> historyList = <ReservationModel>[].obs;
final RxBool isLoading = true.obs; final RxBool isLoading = true.obs;
final RxBool hasData = false.obs; final RxBool hasData = false.obs;
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value); String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value); String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
String stationName = ""; String stationName = "";
final Map<String, String> statusOptions = {
'': '全部',
'0': '待加氢',
'1': '已加氢',
'2': '未加氢',
'5': '拒绝加氢',
};
final RxString selectedStatus = ''.obs;
final RxString selectedDateType = ''.obs; // week, month, three_month
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
final args = Get.arguments as Map<String, dynamic>; final args = Get.arguments as Map<String, dynamic>;
stationName = args['stationName'] as String; stationName = args['stationName'] as String? ?? "";
refreshData();
}
void refreshData() {
getAllOrderCounts();
fetchHistoryData(); fetchHistoryData();
} }
@@ -38,51 +57,50 @@ class HistoryController extends GetxController {
var response = await HttpService.to.post( var response = await HttpService.to.post(
"appointment/orderAddHyd/getAllOrderCounts", "appointment/orderAddHyd/getAllOrderCounts",
data: { data: {
// --- 直接使用 DateFormat 来格式化日期 --- /*'startTime': _apiDateFormat.format(startDate.value),
'startTime': _apiDateFormat.format(startDate.value), 'endTime': _apiDateFormat.format(endDate.value),*/
'endTime': _apiDateFormat.format(endDate.value),
'plateNumber': plateNumberController.text, 'plateNumber': plateNumberController.text,
'stationName': stationName, // 加氢站名称 'stationName': stationName,
"status": selectedStatus.value,
"dateType": selectedDateType.value,
}, },
); );
if (response == null || response.data == null) { if (response == null || response.data == null) {
totalHydrogen.value = '0 kg'; totalHydrogen.value = '0';
totalCompletions.value = '0'; totalCompletions.value = '0';
return; return;
} }
try { try {
final baseModel = BaseModel<dynamic>.fromJson(response.data); final baseModel = BaseModel<dynamic>.fromJson(response.data);
final dataMap = baseModel.data as Map<String, dynamic>; final dataMap = baseModel.data as Map<String, dynamic>;
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0} kg'; totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0}';
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}'; totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}';
} catch (e) { } catch (e) {
totalHydrogen.value = '0 kg'; totalHydrogen.value = '0';
totalCompletions.value = '0'; totalCompletions.value = '0';
} }
} }
Future<void> fetchHistoryData() async { Future<void> fetchHistoryData() async {
isLoading.value = true; isLoading.value = true;
updateUi();
//获取数据
getAllOrderCounts();
try { try {
var response = await HttpService.to.post( var response = await HttpService.to.post(
"appointment/orderAddHyd/sitOrderPage", "appointment/orderAddHyd/sitOrderPage",
data: { data: {
// --- 直接使用 DateFormat 来格式化日期 --- /*'startTime': _apiDateFormat.format(startDate.value),
'startTime': _apiDateFormat.format(startDate.value), 'endTime': _apiDateFormat.format(endDate.value),*/
'endTime': _apiDateFormat.format(endDate.value),
'plateNumber': plateNumberController.text, 'plateNumber': plateNumberController.text,
'pageNum': 1, 'pageNum': 1,
'pageSize': 50, 'pageSize': 50,
'stationName': stationName, // 加氢站名称 'stationName': stationName,
"status": selectedStatus.value,
"dateType": selectedDateType.value,
}, },
); );
if (response == null || response.data == null) { if (response == null || response.data == null) {
showToast('无法获取历史记录');
_resetData(); _resetData();
return; return;
} }
@@ -90,7 +108,6 @@ class HistoryController extends GetxController {
final baseModel = BaseModel<dynamic>.fromJson(response.data); final baseModel = BaseModel<dynamic>.fromJson(response.data);
if (baseModel.code == 0 && baseModel.data != null) { if (baseModel.code == 0 && baseModel.data != null) {
final dataMap = baseModel.data as Map<String, dynamic>; final dataMap = baseModel.data as Map<String, dynamic>;
final List<dynamic> listFromServer = dataMap['records'] ?? []; final List<dynamic> listFromServer = dataMap['records'] ?? [];
historyList.assignAll( historyList.assignAll(
listFromServer listFromServer
@@ -99,14 +116,13 @@ class HistoryController extends GetxController {
); );
hasData.value = historyList.isNotEmpty; hasData.value = historyList.isNotEmpty;
} else { } else {
showToast(baseModel.message);
_resetData(); _resetData();
} }
} catch (e) { } catch (e) {
showToast('获取历史记录失败: $e');
_resetData(); _resetData();
} finally { } finally {
isLoading.value = false; isLoading.value = false;
updateUi();
} }
} }
@@ -115,97 +131,15 @@ class HistoryController extends GetxController {
hasData.value = false; hasData.value = false;
} }
void pickDate(BuildContext context, bool isStartDate) { void onStatusSelected(String status) {
// 确定当前操作的日期和临时存储变量 if (selectedStatus.value == status) return;
final DateTime initialDate = isStartDate ? startDate.value : endDate.value; selectedStatus.value = status;
DateTime tempDate = initialDate; refreshData();
}
// 定义全局的最早可选日期 void onDateTypeSelected(String type) {
final DateTime globalMinimumDate = DateTime(2025, 12, 1); selectedDateType.value = type;
refreshData();
// 动态计算当前选择器的最小/最大日期范围
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 @override

View File

@@ -1,107 +1,173 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/styles/theme.dart'; import 'package:ln_jq_app/common/login_util.dart';
import 'package:ln_jq_app/pages/b_page/history/controller.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 import 'package:ln_jq_app/pages/b_page/site/controller.dart';
class HistoryPage extends GetView<HistoryController> { class HistoryPage extends GetView<HistoryController> {
const HistoryPage({Key? key}) : super(key: key); const HistoryPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(HistoryController()); return GetBuilder<HistoryController>(
init: HistoryController(),
id: 'history',
builder: (_) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black, size: 20),
onPressed: () => Get.back(),
),
title: _buildSearchBox(),
),
body: Column(
children: [
_buildFilterBar(),
_buildSummaryCard(),
Expanded(child: _buildHistoryList()),
],
),
);
},
);
}
return Scaffold( Widget _buildSearchBox() {
appBar: AppBar(title: const Text('历史记录'), centerTitle: true), return Container(
body: Padding( height: 36,
padding: const EdgeInsets.all(12.0), decoration: BoxDecoration(
child: Column( color: const Color(0xFFF2F3F5),
children: [ borderRadius: BorderRadius.circular(18),
_buildFilterCard(context), ),
const SizedBox(height: 12), child: TextField(
_buildSummaryCard(), controller: controller.plateNumberController,
const SizedBox(height: 12), onSubmitted: (v) => controller.refreshData(),
_buildListHeader(), decoration: const InputDecoration(
Expanded(child: _buildHistoryList()), hintText: '搜索车牌号',
], hintStyle: TextStyle(color: Color(0xFFBBBBBB), fontSize: 14),
prefixIcon: Icon(Icons.search_sharp, color: Color(0xFFBBBBBB), size: 20),
border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 12),
), ),
), ),
); );
} }
Widget _buildFilterCard(BuildContext context) { Widget _buildFilterBar() {
return Card( return Container(
elevation: 2, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding( child: Row(
padding: const EdgeInsets.all(16.0), children: [
child: Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: SingleChildScrollView(
children: [ scrollDirection: Axis.horizontal,
const Text('时间范围', style: TextStyle(fontSize: 14, color: Colors.grey)), child: Row(
const SizedBox(height: 8), children: controller.statusOptions.entries.map((entry) {
Row( return Obx(() {
children: [ bool isSelected = controller.selectedStatus.value == entry.key;
Expanded(child: _buildDateField(context, true)), return GestureDetector(
const Padding( onTap: () => controller.onStatusSelected(entry.key),
padding: EdgeInsets.symmetric(horizontal: 8.0), child: Container(
child: Text(''), margin: const EdgeInsets.only(right: 12),
), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
Expanded(child: _buildDateField(context, false)), decoration: BoxDecoration(
], color: isSelected ? const Color(0xFF006633) : Colors.white,
), borderRadius: BorderRadius.circular(15),
const SizedBox(height: 16), ),
const Text('车牌号', style: TextStyle(fontSize: 14, color: Colors.grey)), child: Text(
const SizedBox(height: 8), entry.value,
SizedBox( style: TextStyle(
height: 44, color: isSelected
child: TextField( ? Colors.white
controller: controller.plateNumberController, : Color.fromRGBO(148, 163, 184, 1),
decoration: InputDecoration( fontSize: 12.sp,
hintText: '请输入车牌号', fontWeight: isSelected ? FontWeight.bold : FontWeight.w600,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 12), ),
), ),
);
});
}).toList(),
), ),
), ),
const SizedBox(height: 16), ),
ElevatedButton.icon( _buildTimeFilterIcon(),
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 _buildTimeFilterIcon() {
return PopupMenuButton<String>(
icon: LoginUtil.getAssImg("ic_ex_menu@2x"),
onSelected: controller.onDateTypeSelected,
itemBuilder: (context) => [
const PopupMenuItem(value: 'week', child: Text('最近一周')),
const PopupMenuItem(value: 'month', child: Text('最近一月')),
const PopupMenuItem(value: 'three_month', child: Text('最近三月')),
],
);
}
Widget _buildSummaryCard() { Widget _buildSummaryCard() {
return Card( return Container(
elevation: 2, margin: const EdgeInsets.only(left: 16, right: 16,bottom: 12),
child: Padding( padding: const EdgeInsets.all(20),
padding: const EdgeInsets.symmetric(vertical: 20.0), height: 160,
child: Obx( width: double.infinity,
() => Row( decoration: BoxDecoration(
mainAxisAlignment: MainAxisAlignment.spaceAround, borderRadius: BorderRadius.circular(24),
children: [ image: const DecorationImage(
_buildSummaryItem('实际加氢总量', controller.totalHydrogen.value, Colors.blue), image: AssetImage('assets/images/history_bg.png'),
const SizedBox(width: 1, height: 40, child: VerticalDivider()), fit: BoxFit.cover,
_buildSummaryItem(
'预约完成次数',
controller.totalCompletions.value,
Colors.green,
),
],
),
), ),
), ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('加氢站', style: TextStyle(color: Colors.white70, fontSize: 12)),
Text(
controller.stationName,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Obx(
() => Row(
children: [
_buildSummaryItem('实际加氢量', '${controller.totalHydrogen.value} Kg'),
const SizedBox(width: 40),
_buildSummaryItem('预约完成次数', '${controller.totalCompletions.value}'),
],
),
),
],
),
);
}
Widget _buildSummaryItem(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(color: Colors.white60, fontSize: 12)),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
); );
} }
@@ -110,143 +176,138 @@ class HistoryPage extends GetView<HistoryController> {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (!controller.hasData.value) { if (controller.historyList.isEmpty) {
return const Center(child: Text('没有找到相关记录')); return const Center(
child: Text('暂无相关记录', style: TextStyle(color: Color(0xFF999999))),
);
} }
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: controller.historyList.length, itemCount: controller.historyList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final ReservationModel item = controller.historyList[index]; return _buildHistoryItem(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) { Widget _buildHistoryItem(ReservationModel item) {
String text; return Container(
Color color; margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'车牌号',
style: TextStyle(
color: Color.fromRGBO(148, 163, 184, 1),
fontSize: 12.sp,
),
),
const SizedBox(height: 4),
Text(
item.plateNumber,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
_buildStatusBadge(item.status),
],
),
const SizedBox(height: 16),
Row(
children: [
_buildInfoColumn('加氢时间:', item.time),
_buildInfoColumn('加氢量', '${item.amount} Kg', isRight: true),
],
),
],
),
);
}
Widget _buildInfoColumn(String label, String value, {bool isRight = false}) {
return Expanded(
child: Column(
crossAxisAlignment: isRight ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(color: Color.fromRGBO(148, 163, 184, 1), fontSize: 12.sp),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: isRight ? 16 : 13,
fontWeight: isRight ? FontWeight.bold : FontWeight.normal,
color: const Color(0xFF333333),
),
),
],
),
);
}
Widget _buildStatusBadge(ReservationStatus status) {
String text = '未知';
Color bgColor = Colors.grey.shade100;
Color textColor = Colors.grey;
switch (status) { switch (status) {
case ReservationStatus.pending: case ReservationStatus.pending:
text = '待加氢'; text = '待加氢';
color = Colors.orange; bgColor = const Color(0xFFFFF7E8);
textColor = const Color(0xFFFF9800);
break; break;
case ReservationStatus.completed: case ReservationStatus.completed:
text = '已加氢'; text = '已加氢';
color = Colors.greenAccent; bgColor = const Color(0xFFE8F5E9);
textColor = const Color(0xFF4CAF50);
break; break;
case ReservationStatus.rejected: case ReservationStatus.rejected:
text = '拒绝加氢'; text = '拒绝加氢';
color = Colors.red; bgColor = const Color(0xFFFFEBEE);
textColor = const Color(0xFFF44336);
break; break;
case ReservationStatus.unadded: case ReservationStatus.unadded:
text = '未加氢'; text = '未加氢';
color = Colors.red; bgColor = const Color(0xFFFFEBEE);
textColor = const Color(0xFFF44336);
break; break;
case ReservationStatus.cancel: case ReservationStatus.cancel:
text = '已取消'; text = '已取消';
color = Colors.red; bgColor = const Color(0xFFFFEBEE);
textColor = const Color(0xFFF44336);
break; break;
default: default:
text = '未知状态'; text = '未知状态';
color = Colors.grey; bgColor = Colors.grey;
textColor = Colors.grey;
break; break;
} }
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withOpacity(0.1), color: bgColor,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(8),
border: Border.all(color: textColor.withOpacity(0.3)),
), ),
child: Row( child: Text(
mainAxisSize: MainAxisSize.min, text,
children: [ style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.bold),
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)),
],
), ),
); );
} }

View File

@@ -36,7 +36,7 @@ class ReservationPage extends GetView<ReservationController> {
_buildSystemTips(), _buildSystemTips(),
SizedBox(height: 24), SizedBox(height: 24),
_buildLogoutButton(), _buildLogoutButton(),
SizedBox(height: 75.h), SizedBox(height: 95.h),
], ],
), ),
), ),

View File

@@ -78,12 +78,12 @@ class SitePage extends GetView<SiteController> {
Column( Column(
children: [ children: [
_buildSearchView(), _buildSearchView(),
SizedBox(height: 15.h),
controller.hasReservationData controller.hasReservationData
? _buildReservationListView() ? _buildReservationListView()
: _buildEmptyReservationView(), : _buildEmptyReservationView(),
], ],
), ),
SizedBox(height: 35.h), SizedBox(height: 35.h),
//第三部分 //第三部分
Container( Container(
@@ -136,7 +136,7 @@ class SitePage extends GetView<SiteController> {
], ],
), ),
), ),
SizedBox(height: 75.h), SizedBox(height: 105.h),
], ],
); );
} }
@@ -391,8 +391,9 @@ class SitePage extends GetView<SiteController> {
/// 构建“有预约数据”的列表视图 /// 构建“有预约数据”的列表视图
Widget _buildReservationListView() { Widget _buildReservationListView() {
return ListView.separated( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
// 因为外层已有滚动,这里禁用内部滚动 // 因为外层已有滚动,这里禁用内部滚动
itemCount: controller.reservationList.length, itemCount: controller.reservationList.length,
@@ -401,7 +402,6 @@ class SitePage extends GetView<SiteController> {
// 调用新的方法来构建每一项 // 调用新的方法来构建每一项
return _buildReservationItem(index, item); return _buildReservationItem(index, item);
}, },
separatorBuilder: (context, index) => const SizedBox(height: 0), // 列表项之间的间距
); );
} }

View File

@@ -5,21 +5,45 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:getx_scaffold/common/common.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
/// 原生地图页面 /// 原生地图页面
class NativePageIOS extends StatelessWidget { class NativePageIOS extends StatefulWidget {
const NativePageIOS({super.key}); const NativePageIOS({super.key});
@override
State<NativePageIOS> createState() => _NativePageIOSState();
}
class _NativePageIOSState extends State<NativePageIOS> {
bool _androidPermissionReady = false;
@override
void initState() {
super.initState();
if (Platform.isAndroid) {
requestAndroidPermission();
} else {
// iOS 已经在原生端处理好,直接置为 true
_androidPermissionReady = true;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (Platform.isIOS) { if (Platform.isIOS) {
return _buildIOSView(context); return _buildIOSView(context);
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
// 如果安卓权限还没处理完,显示加载中,不加载原生 View
if (!_androidPermissionReady) {
return const Center(
child: CircularProgressIndicator(color: Color(0xFF017137)),
);
}
return _buildAndroidView(context); return _buildAndroidView(context);
} else { } else {
return const Center( return const Center(child: Text('不支持的平台', style: TextStyle(fontSize: 16)));
child: Text('不支持的平台', style: TextStyle(fontSize: 16)),
);
} }
} }
@@ -30,7 +54,8 @@ class NativePageIOS extends StatelessWidget {
height: MediaQuery.of(context).size.height - 100, height: MediaQuery.of(context).size.height - 100,
color: Colors.white, color: Colors.white,
child: UiKitView( child: UiKitView(
viewType: 'NativeFirstPage', // 与iOS原生端注册的标识一致 viewType: 'NativeFirstPage',
// 与iOS原生端注册的标识一致
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{}.toSet(), gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{}.toSet(),
hitTestBehavior: PlatformViewHitTestBehavior.opaque, hitTestBehavior: PlatformViewHitTestBehavior.opaque,
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
@@ -57,15 +82,16 @@ class NativePageIOS extends StatelessWidget {
onCreatePlatformView: (params) { onCreatePlatformView: (params) {
// 使用 initSurfaceAndroidView 强制开启 Hybrid Composition // 使用 initSurfaceAndroidView 强制开启 Hybrid Composition
return PlatformViewsService.initSurfaceAndroidView( return PlatformViewsService.initSurfaceAndroidView(
id: params.id, id: params.id,
viewType: 'NativeFirstPage', viewType: 'NativeFirstPage',
layoutDirection: TextDirection.ltr, layoutDirection: TextDirection.ltr,
creationParams: {}, // 你的参数 creationParams: {},
creationParamsCodec: const StandardMessageCodec(), // 你的参数
onFocus: () { creationParamsCodec: const StandardMessageCodec(),
params.onFocusChanged(true); onFocus: () {
}, params.onFocusChanged(true);
) },
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create(); ..create();
}, },
@@ -73,28 +99,31 @@ class NativePageIOS extends StatelessWidget {
); );
} }
/// 处理点击事件(如需要) void requestAndroidPermission() async {
void _handleTap(BuildContext context) { try {
if (kDebugMode) { final deviceInfo = await DeviceInfoPlugin().androidInfo;
print("NativePage被点击"); final sdkInt = deviceInfo.version.sdkInt;
}
_showDialog(context, '提示', '点击了原生地图页面');
}
/// 显示对话框 List<Permission> permissions = [Permission.location];
void _showDialog(BuildContext context, String title, String content) {
showDialog( if (sdkInt < 33) {
context: context, permissions.add(Permission.storage);
builder: (context) => AlertDialog( }
title: Text(title),
content: Text(content), // 发起请求
actions: [ Map<Permission, PermissionStatus> statuses = await permissions.request();
TextButton(
onPressed: () => Navigator.pop(context), if (statuses[Permission.location]?.isDenied ?? false) {
child: const Text('确定'), Toast.show("定位权限被拒绝,会影响相关功能使用");
), }
], } catch (e) {
), debugPrint("权限申请异常: $e");
); } finally {
if (mounted) {
setState(() {
_androidPermissionReady = true;
});
}
}
} }
} }

View File

@@ -3,6 +3,7 @@ import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart'; import 'package:ln_jq_app/common/login_util.dart';
import 'package:ln_jq_app/pages/c_page/base_widgets/NativePageIOS.dart'; import 'package:ln_jq_app/pages/c_page/base_widgets/NativePageIOS.dart';
import 'package:ln_jq_app/pages/c_page/car_info/view.dart'; import 'package:ln_jq_app/pages/c_page/car_info/view.dart';
import 'package:ln_jq_app/pages/c_page/mine/view.dart'; import 'package:ln_jq_app/pages/c_page/mine/view.dart';
import 'package:ln_jq_app/pages/c_page/reservation/view.dart'; import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
@@ -57,8 +58,9 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
children: [ children: [
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"), _buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
_buildNavItem(1, "ic_map_select@2x", "ic_map@2x"), _buildNavItem(1, "ic_map_select@2x", "ic_map@2x"),
_buildNavItem(2, "ic_car_select@2x", "ic_car@2x"), _buildNavItem(2, "ic_mall_select@2x", "ic_mall@2x"),
_buildNavItem(3, "ic_user_select@2x", "ic_user@2x"), _buildNavItem(3, "ic_car_select@2x", "ic_car@2x"),
_buildNavItem(4, "ic_user_select@2x", "ic_user@2x"),
], ],
), ),
), ),
@@ -81,7 +83,8 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
child: SizedBox( child: SizedBox(
height: 24, height: 24,
width: 24, width: 24,
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),), child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),
),
), ),
); );
} }

View File

@@ -15,9 +15,9 @@ class CarInfoController extends GetxController with BaseControllerMixin {
// --- 车辆基本信息 --- // --- 车辆基本信息 ---
String plateNumber = ""; String plateNumber = "";
String vin = "未知"; String vin = "-";
String modelName = "未知"; String modelName = "-";
String brandName = "未知"; String brandName = "-";
// --- 证件附件列表 --- // --- 证件附件列表 ---
final RxList<String> drivingAttachments = <String>[].obs; final RxList<String> drivingAttachments = <String>[].obs;

View File

@@ -133,7 +133,7 @@ class CarInfoPage extends GetView<CarInfoController> {
), ),
), ),
IconButton( IconButton(
onPressed: () async{ onPressed: () async {
var scanResult = await Get.to(() => const MessagePage()); var scanResult = await Get.to(() => const MessagePage());
if (scanResult == null) { if (scanResult == null) {
controller.msgNotice(); controller.msgNotice();
@@ -163,11 +163,26 @@ class CarInfoPage extends GetView<CarInfoController> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''), _buildModernStatItem(
'本月里程数',
'Accumulated',
StorageService.to.hasVehicleInfo ? '2,852km' : '-',
'',
),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''), _buildModernStatItem(
'总里程',
'Refuel Count',
StorageService.to.hasVehicleInfo ? "2.5W km" : '-',
'',
),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''), _buildModernStatItem(
'服务评分',
'Driver rating',
StorageService.to.hasVehicleInfo ? "4.9分" : '-',
'',
),
], ],
), ),
), ),
@@ -300,20 +315,20 @@ class CarInfoPage extends GetView<CarInfoController> {
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
child: const LinearProgressIndicator( child: LinearProgressIndicator(
value: 0.75, value: StorageService.to.hasVehicleInfo ? 0.75 : 0,
minHeight: 8, minHeight: 8,
backgroundColor: Color(0xFFF0F2F5), backgroundColor: Color(0xFFF0F2F5),
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)), valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)), Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
Text( Text(
"75%", StorageService.to.hasVehicleInfo ? "75%" : "0%",
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Color.fromRGBO(16, 185, 129, 1), color: Color.fromRGBO(16, 185, 129, 1),
@@ -382,7 +397,11 @@ class CarInfoPage extends GetView<CarInfoController> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: false), _buildCertDetailItem(
'所属公司',
controller.rentFromCompany,
isFull: false,
),
_buildCertDetailItem('运营城市', controller.address), _buildCertDetailItem('运营城市', controller.address),
], ],
), ),

View File

@@ -0,0 +1,113 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/c_page/mall/exchange_success/view.dart';
import '../mall_controller.dart';
class MallDetailController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'mall_detail';
late final int goodsId;
final Rx<GoodsModel?> goodsDetail = Rx<GoodsModel?>(null);
final addressController = TextEditingController();
final nameController = TextEditingController();
final phoneController = TextEditingController();
final formKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
goodsId = Get.arguments['goodsId'] as int;
getGoodsDetail();
}
@override
bool get listenLifecycleEvent => true;
Future<void> getGoodsDetail() async {
try {
var response = await HttpService.to.post(
'appointment/score/getScoreGoodsDetail',
data: {'goodsId': goodsId},
);
if (response != null && response.data != null) {
var result = BaseModel<GoodsModel>.fromJson(
response.data,
dataBuilder: (dataJson) => GoodsModel.fromJson(dataJson),
);
if (result.code == 0 && result.data != null) {
goodsDetail.value = result.data;
} else {
showErrorToast('加载失败: ${result.message}');
Get.back();
}
}
} catch (e) {
log('获取商品详情失败: $e');
showErrorToast('网络异常,请稍后重试');
Get.back();
} finally {
updateUi();
}
}
/// 兑换商品
void exchange() async {
if (!formKey.currentState!.validate()) {
return;
}
/*
final mallController = Get.find<MallController>();
if (mallController.userScore.value < (goodsDetail.value?.score ?? 0)) {
showWarningToast('积分不足');
return;
}*/
// 接口调用预留
showLoading('兑换中...');
final goods = goodsDetail.value;
if (goods == null) {
showErrorToast('兑换失败,请稍后重试');
return;
}
try {
var response = await HttpService.to.post(
'appointment/score/scoreExchange',
data: {
"goodsId": goods.id,
"address": addressController.text,
"name": nameController.text,
"phone": phoneController.text,
},
);
if (response != null && response.data != null) {
var result = BaseModel.fromJson(response.data);
if (result.code == 0) {
Get.off(() => MallExchangeSuccessPage());
}
}
} catch (e) {
log('兑换失败: $e');
showErrorToast('兑换失败,请稍后重试');
} finally {
dismissLoading();
}
}
@override
void onClose() {
addressController.dispose();
nameController.dispose();
phoneController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,281 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'controller.dart';
class MallDetailPage extends GetView<MallDetailController> {
const MallDetailPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MallDetailController>(
init: MallDetailController(),
id: 'mall_detail',
builder: (_) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
appBar: AppBar(
title: const Text('商品兑换'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20),
onPressed: () => Get.back(),
),
),
body: GestureDetector(
onTap: () {
hideKeyboard();
},
child: _buildBody(),
),
bottomNavigationBar: _buildBottomButton(),
);
},
);
}
Widget _buildBody() {
final goods = controller.goodsDetail.value;
if (goods == null) return const Center(child: Text('商品信息不存在'));
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildGoodsInfoCard(goods),
const SizedBox(height: 24),
_buildSectionTitle('填写收货信息'),
const SizedBox(height: 16),
_buildInputLabel('详细地址'),
_buildTextField(
controller: controller.addressController,
hint: '请输入完整的收货地址',
icon: Icons.location_on_outlined,
),
const SizedBox(height: 16),
_buildInputLabel('收货人姓名'),
_buildTextField(
controller: controller.nameController,
hint: '请输入收货人姓名',
icon: Icons.person_outline,
),
const SizedBox(height: 16),
_buildInputLabel('联系电话'),
_buildTextField(
controller: controller.phoneController,
hint: '请输入手机号码',
icon: Icons.phone_android_outlined,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 40),
Center(
child: Text(
'兑换成功后商品会在3个工作日内邮寄\n请注意查收',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xFF999999),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
);
}
Widget _buildGoodsInfoCard(goods) {
return Container(
padding: const EdgeInsets.all(17),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: goods.goodsImage != null
? Image.network(
goods.goodsImage!,
width: 94.w,
height: 94.h,
fit: BoxFit.cover,
)
: Container(
width: 80,
height: 80,
color: Colors.grey[200],
child: const Icon(Icons.image, color: Colors.grey),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
goods.goodsName,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
SizedBox(height: 8.h),
Row(
children: [
Text(
'${goods.score}',
style: TextStyle(
fontSize: 20.sp,
color: Color(0xFF4CAF50),
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 4),
Text(
'积分',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF999999),
),
),
],
),
SizedBox(height: 10.h),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'数量: 1',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w500,
color: Color(0xFF666666),
),
),
),
],
),
),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6.w,
height: 16.h,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(148, 163, 184, 1),
),
),
],
);
}
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String hint,
required IconData icon,
TextInputType? keyboardType,
}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
textAlign: TextAlign.start,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
color: Color.fromRGBO(134, 144, 156, 1),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
prefixIcon: Icon(icon, color: const Color(0xFF999999), size: 20),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '内容不能为空';
}
return null;
},
),
);
}
Widget _buildBottomButton() {
return Container(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 30),
child: ElevatedButton(
onPressed: controller.exchange,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007A45),
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
elevation: 0,
),
child: const Text(
'兑换商品',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
);
}
}

View File

@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/common/index.dart';
import 'package:ln_jq_app/common/login_util.dart';
class MallExchangeSuccessPage extends StatelessWidget {
const MallExchangeSuccessPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
onPressed: () => Get.back(), // 返回首页
),
title: const Text('商品兑换', style: TextStyle(color: Colors.black, fontSize: 18)),
),
body: Center(
child: Column(
children: [
SizedBox(height: 114.h),
_buildSuccessIcon(),
const SizedBox(height: 24),
Text(
'兑换成功',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
const SizedBox(height: 8),
Text(
'预计 3 日内发货\n请留意查收',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14.sp, color: Color(0xFF999999)),
),
const SizedBox(height: 60),
ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007A45),
minimumSize: const Size(140, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
),
child: Text(
'返回首页',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 16.sp,
),
),
),
],
),
),
);
}
Widget _buildSuccessIcon() {
return Container(child: LoginUtil.getAssImg("mall_pay_success@2x"));
}
}

View File

@@ -0,0 +1,160 @@
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/c_page/mall/detail/view.dart';
import 'package:ln_jq_app/pages/c_page/mall/orders/view.dart';
import 'package:ln_jq_app/pages/c_page/mall/rule/view.dart';
class GoodsModel {
final int id;
final String categoryId;
final String goodsName;
final String? goodsImage;
final int originalScore;
final int score;
final int stock;
final int status;
GoodsModel({
required this.id,
required this.categoryId,
required this.goodsName,
this.goodsImage,
required this.originalScore,
required this.score,
required this.stock,
required this.status,
});
factory GoodsModel.fromJson(Map<String, dynamic> json) {
return GoodsModel(
id: json['id'] as int,
categoryId: json['categoryId']?.toString() ?? '',
goodsName: json['goodsName']?.toString() ?? '',
goodsImage: json['goodsImage'],
originalScore: json['originalScore'] as int? ?? 0,
score: json['score'] as int? ?? 0,
stock: json['stock'] as int? ?? 0,
status: json['status'] as int? ?? 1,
);
}
}
class UserScore {
final int score;
final int todaySign;
UserScore({required this.score, required this.todaySign});
factory UserScore.fromJson(Map<String, dynamic> json) {
return UserScore(
score: json['score'] as int? ?? 0,
todaySign: json['todaySign'] as int? ?? 1,
);
}
}
class MallController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'mall';
final RxInt userScore = 0.obs;
final RxInt todaySign = 1.obs; // 0可签到1已签到
final RxList<GoodsModel> goodsList = <GoodsModel>[].obs;
final RxBool isLoading = true.obs;
@override
void onInit() {
super.onInit();
refreshData();
}
Future<void> refreshData() async {
isLoading.value = true;
await Future.wait([getUserScore(), getGoodsList()]);
isLoading.value = false;
updateUi();
}
/// 获取用户积分和签到状态
Future<void> getUserScore() async {
try {
var response = await HttpService.to.post('appointment/score/getUserScore');
if (response != null && response.data != null) {
var result = BaseModel<UserScore>.fromJson(
response.data,
dataBuilder: (dataJson) => UserScore.fromJson(dataJson),
);
if (result.code == 0 && result.data != null) {
userScore.value = result.data!.score;
todaySign.value = result.data!.todaySign;
}
}
} catch (e) {
log('获取积分失败: $e');
}
}
/// 签到逻辑
Future<void> signAction() async {
if (todaySign.value == 1) return;
showLoading('签到中...');
try {
var response = await HttpService.to.post('appointment/score/sign');
dismissLoading();
if (response != null && response.data != null) {
var result = BaseModel.fromJson(response.data);
if (result.code == 0) {
showSuccessToast('签到成功');
getUserScore(); // 签到成功后刷新积分
} else {
showErrorToast(result.message);
}
}
} catch (e) {
dismissLoading();
showErrorToast('签到异常');
}
}
/// 获取商品列表
Future<void> getGoodsList() async {
try {
var response = await HttpService.to.post(
'appointment/score/getScoreGoodsList',
data: {'categoryId': 0},
);
if (response != null && response.data != null) {
var result = BaseModel<List<GoodsModel>>.fromJson(
response.data,
dataBuilder: (dataJson) {
var list = dataJson as List;
return list.map((e) => GoodsModel.fromJson(e)).toList();
},
);
if (result.code == 0 && result.data != null) {
goodsList.assignAll(result.data!);
}
}
} catch (e) {
log('获取商品列表失败: $e');
}
}
/// 兑换商品
void exchangeGoods(GoodsModel goods) {
Get.to(() => const MallDetailPage(), arguments: {'goodsId': goods.id})
?.then((_) => refreshData());
}
///规则说明
void toRuleDes() {
Get.to(() => const MallRulePage());
}
///历史订单
void toOrders() {
Get.to(() => const MallOrdersPage());
}
}

View File

@@ -0,0 +1,345 @@
import 'package:flutter/material.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart';
import 'mall_controller.dart';
class MallPage extends GetView<MallController> {
const MallPage({super.key});
@override
Widget build(BuildContext context) {
return GetX<MallController>(
init: MallController(),
builder: (_) {
return Scaffold(
backgroundColor: Color.fromRGBO(247, 249, 251, 1),
body: RefreshIndicator(
onRefresh: controller.refreshData,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Column(children: [_buildAppBar(), _buildScoreCard()]),
),
),
_buildSectionHeader(),
_buildGoodsGrid(),
SliverToBoxAdapter(child: SizedBox(height: 120.h)),
],
),
),
);
},
);
}
Widget _buildAppBar() {
return Container(
padding: EdgeInsets.only(top: MediaQuery.of(Get.context!).padding.top, bottom: 0),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
borderRadius: BorderRadius.circular(8),
),
child: LoginUtil.getAssImg("mall_bar@2x"),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'氢能商城',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
Row(
children: [
const Text(
'Hydrogen Energy Mall',
style: TextStyle(fontSize: 12, color: Color(0xFF999999)),
),
],
),
],
),
),
/*IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications_none, color: Color(0xFF333333)),
),*/
],
),
);
}
Widget _buildScoreCard() {
return Container(
margin: EdgeInsets.only(top: 22.h),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color.fromRGBO(2, 27, 31, 1), Color.fromRGBO(11, 67, 67, 1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'我的可用积分',
style: TextStyle(color: Colors.white70, fontSize: 14.sp),
),
const SizedBox(width: 4),
GestureDetector(
onTap: (){
controller.toRuleDes();
},
child: const Icon(Icons.help_outline, color: Colors.white70, size: 14),
)
],
),
Text(
'Available points',
style: TextStyle(color: Colors.white38, fontSize: 11.sp),
),
],
),
GestureDetector(
onTap: controller.signAction,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: controller.todaySign.value == 0
? Color.fromRGBO(78, 184, 49, 1)
: Colors.grey,
borderRadius: BorderRadius.circular(10),
),
child: Text(
controller.todaySign.value == 0 ? '立即签到' : '已签到',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
controller.userScore.value.toString(),
style: TextStyle(
color: Color.fromRGBO(236, 236, 236, 1),
fontSize: 18.sp,
fontWeight: FontWeight.w600,
),
),
TextButton(
onPressed: () {
controller.toOrders();
},
child: Text(
'历史订单',
style: TextStyle(
color: Color.fromRGBO(148, 163, 184, 1),
fontSize: 12.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
);
}
Widget _buildSectionHeader() {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
children: [
Container(
width: 4.w,
height: 16.h,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
'热门商品',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(78, 89, 105, 1),
),
),
],
),
),
);
}
Widget _buildGoodsGrid() {
if (controller.goodsList.isEmpty) {
return const SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 50),
child: Text('暂无商品', style: TextStyle(color: Color(0xFF999999))),
),
),
);
}
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 14.h,
crossAxisSpacing: 19.w,
childAspectRatio: 0.75,
),
delegate: SliverChildBuilderDelegate((context, index) {
final goods = controller.goodsList[index];
return _buildGoodsItem(goods);
}, childCount: controller.goodsList.length),
),
);
}
Widget _buildGoodsItem(GoodsModel goods) {
return Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: goods.goodsImage != null
? Image.network(
goods.goodsImage!,
fit: BoxFit.cover,
width: double.infinity,
)
: Container(
color: const Color(0xFFEEEEEE),
child: const Center(
child: Icon(Icons.image, color: Colors.grey, size: 40),
),
),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
goods.goodsName,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'${goods.score}',
style: TextStyle(
fontSize: 16.sp,
color: Color(0xFF4CAF50),
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 4),
Text(
'积分',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Color(0xFF4CAF50),
),
),
],
),
GestureDetector(
onTap: () => controller.exchangeGoods(goods),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFFE8F5E9),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'兑换',
style: TextStyle(
color: Color(0xFF4CAF50),
fontWeight: FontWeight.bold,
fontSize: 13.sp,
),
),
),
),
],
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,94 @@
import 'dart:developer';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
class OrderModel {
final int id;
final int scoreGoodsId;
final String goodsName;
final String? goodsImage;
final String? goodsContent;
final String address;
final String createTime;
final String score;
OrderModel({
required this.id,
required this.scoreGoodsId,
required this.goodsName,
this.goodsImage,
this.goodsContent,
required this.address,
required this.createTime,
required this.score,
});
factory OrderModel.fromJson(Map<String, dynamic> json) {
return OrderModel(
id: json['id'] as int,
scoreGoodsId: json['scoreGoodsId'] as int,
goodsName: json['goodsName']?.toString() ?? '',
goodsImage: json['goodsImage'],
goodsContent: json['goodsContent'],
address: json['address']?.toString() ?? '',
createTime: json['createTime']?.toString() ?? '',
score: json['score']?.toString() ?? '',
);
}
}
class MallOrdersController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'mall_orders';
final RxList<OrderModel> orderList = <OrderModel>[].obs;
final RxBool isLoading = true.obs;
int pageNum = 1;
final int pageSize = 50;
@override
void onInit() {
super.onInit();
getOrders();
}
Future<void> getOrders({bool isRefresh = true}) async {
if (isRefresh) {
pageNum = 1;
isLoading.value = true;
updateUi();
}
try {
var response = await HttpService.to.post(
'appointment/score/getScoreExchangeList',
data: {
"status": "",
"pageNum": pageNum.toString(),
"pageSize": pageSize.toString()
},
);
if (response != null && response.data != null) {
var result = BaseModel<Map<String, dynamic>>.fromJson(response.data);
if (result.code == 0 && result.data != null) {
var records = result.data!['records'] as List;
var list = records.map((e) => OrderModel.fromJson(e)).toList();
if (isRefresh) {
orderList.assignAll(list);
} else {
orderList.addAll(list);
}
pageNum++;
}
}
} catch (e) {
log('获取订单列表失败: $e');
} finally {
isLoading.value = false;
updateUi();
}
}
}

View File

@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'controller.dart';
class MallOrdersPage extends GetView<MallOrdersController> {
const MallOrdersPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MallOrdersController>(
init: MallOrdersController(),
id: 'mall_orders',
builder: (_) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
appBar: AppBar(
title: const Text('历史订单'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20),
onPressed: () => Get.back(),
),
),
body: RefreshIndicator(
onRefresh: () => controller.getOrders(isRefresh: true),
child: controller.isLoading.value && controller.orderList.isEmpty
? const Center(child: CircularProgressIndicator())
: _buildOrderList(),
),
);
},
);
}
Widget _buildOrderList() {
if (controller.orderList.isEmpty) {
return const Center(
child: Text('暂无订单记录', style: TextStyle(color: Color(0xFF999999))),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.orderList.length,
itemBuilder: (context, index) {
final order = controller.orderList[index];
return _buildOrderItem(order);
},
);
}
Widget _buildOrderItem(OrderModel order) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'兑换时间:${order.createTime}',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: Color.fromRGBO(107, 114, 128, 1),
),
),
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: order.goodsImage != null
? Image.network(
order.goodsImage!,
width: 80.w,
height: 80.h,
fit: BoxFit.cover,
)
: Container(
width: 80.w,
height: 80.h,
color: Colors.grey[200],
child: const Icon(Icons.image, color: Colors.grey),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
order.goodsName,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
const SizedBox(height: 8),
Row(
children: [
Text(
order.score,
style: TextStyle(
fontSize: 16.sp,
color: Color(0xFF4CAF50),
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 4),
Text(
'积分',
style: TextStyle(fontSize: 11.sp, color: Color(0xFF4CAF50)),
),
],
),
],
),
),
Text(
'x1',
style: TextStyle(color: Color(0xFFCCCCCC), fontSize: 16.sp),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/common/index.dart';
import 'package:ln_jq_app/common/login_util.dart';
class MallRulePage extends StatelessWidget {
const MallRulePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(64, 199, 154, 1),
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.white, size: 20),
onPressed: () => Get.back(),
),
),
body: Stack(
children: [
// 顶部装饰图
Positioned(
top: 30,
right: Get.width * 0.15,
child: LoginUtil.getAssImg("rule_bg@2x"),
),
Container(
margin: const EdgeInsets.fromLTRB(20, 100, 20, 20),
padding: const EdgeInsets.all(24),
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/rule_bg_1@2x.png'),
fit: BoxFit.fill,
),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'积分获取规则',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Color(0xFF2C3E50),
),
),
const SizedBox(height: 30),
_buildRuleItem(
icon: 'tips_1@2x',
title: '每日首次签到积分规则',
content: '每日首签,立得 1 积分',
),
_buildRuleItem(
icon: 'tips_2@2x',
title: '每日预约加氢积分规则',
content: '每日前 2 次预约加氢,各得 1 积分',
),
_buildRuleItem(
icon: 'tips_3@2x',
title: '连续签到累计赠分规则',
content: '连续签到 3 天赠 2 积分7 天赠 5 积分',
),
_buildRuleItem(
icon: 'tips_4@2x',
title: '连续签到周期及断签重置规则',
content: '7 天为一个签到周期,中途断签则重新从第 1 天计算',
),
_buildRuleItem(
icon: 'tips_5@2x',
title: '积分使用规则',
content:
'积分有效期3个月。个人账户内累计的所有有效积分可在平台积分商城中用于兑换商城内上架的各类商品、权益或服务兑换时将按照商品标注的积分值扣除对应积分积分兑换后不支持撤销、退换商品兑换规则以积分商城内公示为准。',
),
const SizedBox(height: 40),
const Center(
child: Text(
'本活动最终解释权归官方所有,如有疑问可咨询客服。',
style: TextStyle(color: Color(0xFF999999), fontSize: 12),
),
),
],
),
),
),
],
),
);
}
Widget _buildRuleItem({
required String icon,
required String title,
required String content,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LoginUtil.getAssImg(icon),
const SizedBox(width: 8),
Text(
title,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
],
),
const SizedBox(height: 6),
Padding(
padding: const EdgeInsets.only(left: 0),
child: Text(
content,
style: TextStyle(fontSize: 13.sp, color: Color(0xFF666666), height: 1.5),
),
),
],
),
);
}
}

View File

@@ -12,40 +12,63 @@ class MessagePage extends GetView<MessageController> {
Get.put(MessageController()); Get.put(MessageController());
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF5F5F5), backgroundColor: const Color(0xFFF7F9FB),
appBar: AppBar(title: const Text('消息通知'), centerTitle: true), appBar: AppBar(
body: Column( title: const Text('消息通知'),
centerTitle: true,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20),
onPressed: () => Get.back(),
),
),
body: Stack(
children: [ children: [
Expanded( Obx(() => SmartRefresher(
child: Obx(() => SmartRefresher( controller: controller.refreshController,
controller: controller.refreshController, enablePullUp: true,
enablePullUp: true, onRefresh: controller.onRefresh,
onRefresh: controller.onRefresh, onLoading: controller.onLoading,
onLoading: controller.onLoading, child: ListView.builder(
child: ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
padding: const EdgeInsets.all(12), itemCount: controller.messageList.length,
itemCount: controller.messageList.length, itemBuilder: (context, index) {
separatorBuilder: (_, __) => const SizedBox(height: 12), return _buildMessageItem(context, controller.messageList[index]);
itemBuilder: (context, index) { },
return _buildMessageItem(context, controller.messageList[index]); ),
}, )),
),
)),
),
Obx(() => !controller.allRead.value Obx(() => !controller.allRead.value
? Container( ? Positioned(
padding: const EdgeInsets.all(16), right: 20,
color: Colors.white, bottom: 50,
child: ElevatedButton( child: GestureDetector(
onPressed: controller.markAllRead, onTap: controller.markAllRead,
style: ElevatedButton.styleFrom( child: Container(
backgroundColor: Colors.blue, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
minimumSize: const Size(double.infinity, 44), decoration: BoxDecoration(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), color: const Color(0xFF007A45),
), borderRadius: BorderRadius.circular(30),
child: const Text('全部标为已读', style: TextStyle(fontSize: 16, color: Colors.white)), boxShadow: [
), BoxShadow(
) color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Text(
'全部已读',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
),
)
: const SizedBox.shrink()), : const SizedBox.shrink()),
], ],
), ),
@@ -53,54 +76,92 @@ class MessagePage extends GetView<MessageController> {
} }
Widget _buildMessageItem(BuildContext context, MessageModel item) { Widget _buildMessageItem(BuildContext context, MessageModel item) {
return GestureDetector( return IntrinsicHeight(
onTap: () { child: Row(
controller.markRead(item); crossAxisAlignment: CrossAxisAlignment.stretch,
_showMessageDialog(context, item); children: [
}, // 左侧时间轴线条和圆点
child: Container( SizedBox(
padding: const EdgeInsets.all(16), width: 40,
decoration: BoxDecoration( child: Stack(
color: Colors.white, alignment: Alignment.topCenter,
borderRadius: BorderRadius.circular(8), children: [
), Container(
child: Row( width: 1.5,
crossAxisAlignment: CrossAxisAlignment.start, color: const Color(0xFFD8E2EE),
children: [ ),
Container( Positioned(
margin: const EdgeInsets.only(top: 6, right: 12), top: 25,
width: 8, child: Container(
height: 8, width: 12,
decoration: BoxDecoration( height: 12,
color: item.isRead == 1 ? Colors.grey[300] : const Color(0xFFFAAD14), decoration: BoxDecoration(
shape: BoxShape.circle, color: item.isRead == 1
? const Color(0xFFAAB6C3)
: const Color(0xFF4CAF50),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
),
// 右侧内容卡片
Expanded(
child: GestureDetector(
onTap: () {
controller.markRead(item);
_showMessageDialog(context, item);
},
child: Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
const SizedBox(height: 10),
Text(
item.content,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
height: 1.4,
),
),
const SizedBox(height: 12),
Text(
item.createTime,
style: const TextStyle(
fontSize: 12,
color: Color(0xFFCCCCCC),
),
),
],
),
), ),
), ),
Expanded( ),
child: Column( ],
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87),
),
const SizedBox(height: 8),
Text(
item.content,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Text(
item.createTime,
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
),
],
),
),
],
),
), ),
); );
} }
@@ -111,7 +172,7 @@ class MessagePage extends GetView<MessageController> {
barrierDismissible: true, barrierDismissible: true,
builder: (context) { builder: (context) {
return Dialog( return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container( child: Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
@@ -122,22 +183,22 @@ class MessagePage extends GetView<MessageController> {
item.title, item.title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 12), const SizedBox(height: 16),
Text( Text(
item.content, item.content,
style: const TextStyle(fontSize: 15, height: 1.5, color: Colors.black87), style: const TextStyle(
fontSize: 15, height: 1.5, color: Color(0xFF333333)),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: OutlinedButton( child: TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
style: OutlinedButton.styleFrom( style: TextButton.styleFrom(
side: const BorderSide(color: Colors.blue), foregroundColor: const Color(0xFF007A45),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
), ),
child: const Text('确认', style: TextStyle(color: Colors.blue)), child: const Text('确认',
style: TextStyle(fontWeight: FontWeight.bold)),
), ),
), ),
], ],
@@ -147,4 +208,4 @@ class MessagePage extends GetView<MessageController> {
}, },
); );
} }
} }

View File

@@ -69,239 +69,34 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
String get formattedTimeSlot => String get formattedTimeSlot =>
'${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}'; '${_formatTimeOfDay(startTime.value)} - ${_formatTimeOfDay(endTime.value)}';
void pickDate(BuildContext context) {
DateTime tempDate = selectedDate.value;
// 获取今天的日期 (不含时间)
final DateTime today = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
);
//计算明天的日期
final DateTime tomorrow = today.add(const Duration(days: 1));
Get.bottomSheet(
Container(
height: 300,
padding: const EdgeInsets.only(top: 6.0),
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: [
CupertinoButton(
onPressed: () => Get.back(),
child: const Text(
'取消',
style: TextStyle(color: CupertinoColors.systemGrey),
),
),
CupertinoButton(
onPressed: () {
final bool isChangingToToday =
tempDate.isAtSameMomentAs(today) &&
!selectedDate.value.isAtSameMomentAs(today);
final bool isDateChanged = !tempDate.isAtSameMomentAs(
selectedDate.value,
);
// 更新选中的日期
selectedDate.value = tempDate;
Get.back(); // 先关闭弹窗
// 如果日期发生了变化,则重置时间
if (isDateChanged) {
resetTimeForSelectedDate();
}
},
child: const Text(
'确认',
style: TextStyle(
color: AppTheme.themeColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const Divider(height: 1, color: Color(0xFFE5E5E5)),
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: selectedDate.value,
minimumDate: today,
// 最小可选日期为今天
maximumDate: tomorrow,
// 最大可选日期为明天
// ---------------------
onDateTimeChanged: (DateTime newDate) {
tempDate = newDate;
},
),
),
],
),
),
backgroundColor: Colors.transparent,
);
}
void resetTimeForSelectedDate() { void resetTimeForSelectedDate() {
final now = DateTime.now(); final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day); final today = DateTime(now.year, now.month, now.day);
// 判断新选择的日期是不是今天 // 1. 获取当前站点
final station = stationOptions.firstWhereOrNull(
(s) => s.hydrogenId == selectedStationId.value,
);
if (station == null) return;
// 2. 解析营业开始和结束的小时
final bizStartHour = int.tryParse(station.startBusiness.split(':')[0]) ?? 0;
final bizEndHour = int.tryParse(station.endBusiness.split(':')[0]) ?? 23;
if (selectedDate.value.isAtSameMomentAs(today)) { if (selectedDate.value.isAtSameMomentAs(today)) {
// 如果是今天,就将时间重置为当前时间所在的半小时区间 // 如果是今天:起始时间 = max(当前小时, 营业开始小时),且上限为营业结束小时
startTime.value = _calculateInitialStartTime(now); int targetHour = now.hour;
endTime.value = TimeOfDay.fromDateTime( if (targetHour < bizStartHour) targetHour = bizStartHour;
_getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)), if (targetHour > bizEndHour) targetHour = bizEndHour;
);
startTime.value = TimeOfDay(hour: targetHour, minute: 0);
} else { } else {
// 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00 // 如果是明天:起始时间直接重置为营业开始小时
startTime.value = const TimeOfDay(hour: 0, minute: 0); startTime.value = TimeOfDay(hour: bizStartHour, minute: 0);
endTime.value = const TimeOfDay(hour: 0, minute: 30);
}
}
///60 分钟为间隔 时间选择器
void pickTime(BuildContext context) {
final now = DateTime.now();
final isToday =
selectedDate.value.year == now.year &&
selectedDate.value.month == now.month &&
selectedDate.value.day == now.day;
final List<TimeSlot> availableSlots = [];
for (int i = 0; i < 24; i++) {
// 每次增加 60 分钟
final startMinutes = i * 60;
final endMinutes = startMinutes + 60;
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
// 注意endMinutes % 60 始终为 0因为间隔是整小时
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
// 如果不是今天,所有时间段都有效
if (!isToday) {
availableSlots.add(TimeSlot(startTime, endTime));
} else {
// 如果是今天,需要判断该时间段是否可选
// 创建时间段的结束时间对象
final slotEndDateTime = DateTime(
selectedDate.value.year,
selectedDate.value.month,
selectedDate.value.day,
endTime.hour,
endTime.minute,
);
// 注意:如果是跨天的 00:00 (例如 23:00 - 00:00),需要将日期加一天,否则 isAfter 判断会出错
// 但由于我们用的是 endTime.hour % 24当变成 0 时,日期还是 selectedDate
// 这里做一个特殊处理:如果 endTime 是 00:00意味着它实际上是明天的开始
DateTime realEndDateTime = slotEndDateTime;
if (endTime.hour == 0 && endTime.minute == 0) {
realEndDateTime = slotEndDateTime.add(const Duration(days: 1));
}
// 只要时间段的结束时间晚于当前时间,这个时间段就是可预约的
if (realEndDateTime.isAfter(now)) {
availableSlots.add(TimeSlot(startTime, endTime));
}
}
} }
if (availableSlots.isEmpty) { // 结束时间默认顺延1小时
showToast('今天已没有可预约的时间段'); endTime.value = TimeOfDay(hour: (startTime.value.hour + 1) % 24, minute: 0);
return;
}
// 查找当前选中的时间对应的新列表中的索引
int initialItem = availableSlots.indexWhere(
(slot) => slot.start.hour == startTime.value.hour,
);
if (initialItem == -1) {
initialItem = 0;
}
TimeSlot tempSlot = availableSlots[initialItem];
final FixedExtentScrollController scrollController = FixedExtentScrollController(
initialItem: initialItem,
);
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: [
CupertinoButton(
onPressed: () => Get.back(),
child: const Text(
'取消',
style: TextStyle(color: CupertinoColors.systemGrey),
),
),
CupertinoButton(
onPressed: () {
startTime.value = tempSlot.start;
endTime.value = tempSlot.end;
Get.back();
},
child: const Text(
'确认',
style: TextStyle(
color: AppTheme.themeColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const Divider(height: 1, color: Color(0xFFE5E5E5)),
Expanded(
child: CupertinoPicker(
scrollController: scrollController,
itemExtent: 40.0,
onSelectedItemChanged: (index) {
tempSlot = availableSlots[index];
},
children: availableSlots
.map((slot) => Center(child: Text(slot.display)))
.toList(),
),
),
],
),
),
backgroundColor: Colors.transparent,
);
} }
// 用于存储上一次成功预约的信息 // 用于存储上一次成功预约的信息
@@ -536,12 +331,13 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
} }
} }
String workEfficiency = "0"; String workEfficiency = "-";
String fillingWeight = "0"; String fillingWeight = "-";
String fillingTimes = "0"; String fillingTimes = "-";
String modeImage = "";
String plateNumber = ""; String plateNumber = "";
String vin = ""; String vin = "";
String leftHydrogen = "0"; String leftHydrogen = "-";
num maxHydrogen = 0; num maxHydrogen = 0;
String difference = ""; String difference = "";
var progressValue = 0.0; var progressValue = 0.0;
@@ -650,7 +446,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
try { try {
HttpService.to.setBaseUrl(AppTheme.test_service_url); HttpService.to.setBaseUrl(AppTheme.test_service_url);
var responseData = await HttpService.to.get( var responseData = await HttpService.to.get(
'appointment/truck/history-filling-summary?vin=$vin', 'appointment/truck/history-filling-summary?vin=$vin&plateNumber=$plateNumber',
); );
if (responseData == null || responseData.data == null) { if (responseData == null || responseData.data == null) {
showToast('服务暂不可用,请稍后'); showToast('服务暂不可用,请稍后');
@@ -664,6 +460,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}"; fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}"; fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
modeImage = result.data["modeImage"].toString();
updateUi(); updateUi();
} catch (e) { } catch (e) {
@@ -689,8 +486,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
var result = BaseModel.fromJson(responseData.data); var result = BaseModel.fromJson(responseData.data);
leftHydrogen = result.data["leftHydrogen"].toString(); leftHydrogen = "${result.data["leftHydrogen"]}Kg";
workEfficiency = result.data["workEfficiency"].toString(); workEfficiency = "${result.data["workEfficiency"]}Kg";
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0; final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2); difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
@@ -725,7 +522,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
} }
void getSiteList() async { void getSiteList() async {
if (StorageService.to.phone == "13344444444") { if (StorageService.to.phone == "13888888888") {
//该账号给stationOptions手动添加一个数据 //该账号给stationOptions手动添加一个数据
final testStation = StationModel( final testStation = StationModel(
hydrogenId: '1142167389150920704', hydrogenId: '1142167389150920704',
@@ -735,7 +532,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
// 价格 // 价格
siteStatusName: '营运中', siteStatusName: '营运中',
// 状态 // 状态
isSelect: 1, // 默认可选 isSelect: 1,
startBusiness: '08:00:00',
endBusiness: '20:00:00', // 默认可选
); );
// 使用 assignAll 可以确保列表只包含这个测试数据 // 使用 assignAll 可以确保列表只包含这个测试数据
stationOptions.assignAll([testStation]); stationOptions.assignAll([testStation]);

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:getx_scaffold/common/index.dart'; import 'package:getx_scaffold/common/index.dart';
import 'package:ln_jq_app/common/model/base_model.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/controller.dart';
import 'package:ln_jq_app/pages/c_page/reservation_edit/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'; import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart';
@@ -36,19 +37,19 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: Get.height * 0.55, height: Get.height * 0.6,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Color.fromRGBO(247, 249, 251, 1),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
topRight: Radius.circular(16), topRight: Radius.circular(16),
), ),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 构建标题和下拉框 // 构建标题和下拉框
_buildHeader(), _buildHeader(),
const Divider(height: 1),
// 下拉筛选框 // 下拉筛选框
_buildChoice(), _buildChoice(),
// 构建列表(使用 Obx 监听数据变化) // 构建列表(使用 Obx 监听数据变化)
@@ -58,60 +59,64 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
); );
} }
Container _buildChoice() { Widget _buildChoice() {
return Container( return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 8, 0, 0), scrollDirection: Axis.horizontal,
alignment: AlignmentGeometry.centerLeft, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Container( child: Row(
padding: const EdgeInsets.symmetric(horizontal: 8), children: _statusOptions.entries.map((entry) {
decoration: BoxDecoration( bool isSelected = _selectedStatus == entry.key;
color: Colors.grey[100], return GestureDetector(
borderRadius: BorderRadius.circular(8), behavior: HitTestBehavior.opaque,
onTap: () {
if (_selectedStatus == entry.key) return;
// 立即执行刷新逻辑
_controller.getReservationList(addStatus: entry.key);
// 先更新本地状态改变 UI 选中效果
setState(() {
_selectedStatus = entry.key;
});
},
child: Container(
margin: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
// 选中色使用深绿色,未选中保持纯白
color: isSelected ? const Color(0xFF006633) : Colors.white,
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Text(
entry.key == '' ? '全部' : entry.value,
style: TextStyle(
// 未选中文字颜色微调为图片中的灰蓝色
color: isSelected ? Colors.white : const Color(0xFFAAB6C3),
fontSize: 14.sp,
fontWeight: isSelected ? FontWeight.bold : FontWeight.w500,
),
),
), ),
child: DropdownButton<String>( );
value: _selectedStatus, }).toList(),
underline: const SizedBox.shrink(), // 隐藏下划线 ),
items: _statusOptions.entries.map((entry) { );
return DropdownMenuItem<String>(
value: entry.key,
child: Text(entry.value),
);
}).toList(),
onChanged: (newValue) {
if (newValue != null) {
setState(() {
_selectedStatus = newValue;
});
// 当选择新状态时,调用接口刷新数据
_controller.getReservationList(addStatus: _selectedStatus);
}
},
),
),
);
} }
/// 构建标题、关闭按钮和下拉筛选框 /// 构建标题、关闭按钮和下拉筛选框
Widget _buildHeader() { Widget _buildHeader() {
return Container( return Container(
padding: const EdgeInsets.fromLTRB(20, 8, 8, 8), margin: const EdgeInsets.fromLTRB(20, 20, 8, 8),
child: Stack( child: const Text(
alignment: Alignment.center, '我的预约',
children: [ style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Center(child: const Text('我的预约', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.grey[200],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
),
child: const Text('关闭', style: TextStyle(color: Colors.black54)),
),
),
],
), ),
); );
} }
@@ -134,8 +139,8 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
return Card( return Card(
color: Colors.white, color: Colors.white,
margin: const EdgeInsets.only(bottom: 12.0), margin: const EdgeInsets.only(bottom: 12.0),
elevation: 1, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@@ -151,17 +156,20 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
vertical: 5, vertical: 5,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.shade50, // 淡蓝色背景 color: reservation.state == "-1"
? Color.fromRGBO(241, 67, 56, 0.1)
: Color.fromRGBO(230, 249, 243, 1),
borderRadius: BorderRadius.circular(4), // 小圆角 borderRadius: BorderRadius.circular(4), // 小圆角
// 可以选择去掉边框,或者用极淡的边框 // 可以选择去掉边框,或者用极淡的边框
border: Border.all(color: Colors.blue.shade100),
), ),
child: Text( child: Text(
"${reservation.stateName}-${reservation.addStatusName}", "${reservation.stateName}-${reservation.addStatusName}",
style: TextStyle( style: TextStyle(
color: Colors.blue.shade700, color: reservation.state == "-1"
fontSize: 12, ? Color.fromRGBO(241, 67, 56, 0.8)
fontWeight: FontWeight.w500, : Color.fromRGBO(49, 186, 133, 1),
fontSize: 12.sp,
fontWeight: FontWeight.w600,
), ),
), ),
), ),
@@ -171,12 +179,10 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
SizedBox( SizedBox(
height: 28, // 限制按钮高度,显得精致 height: 28, // 限制按钮高度,显得精致
child: OutlinedButton( child: OutlinedButton(
onPressed: () async{ onPressed: () async {
var responseData = await HttpService.to.post( var responseData = await HttpService.to.post(
'appointment/orderAddHyd/vehicle-cancel', 'appointment/orderAddHyd/vehicle-cancel',
data: { data: {'id': reservation.id},
'id': reservation.id,
},
); );
if (responseData == null || responseData.data == null) { if (responseData == null || responseData.data == null) {
@@ -198,24 +204,27 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
side: BorderSide(color: Colors.grey.shade400), // 灰色边框 side: BorderSide(color: Colors.grey.shade400),
shape: const StadiumBorder(), // 胶囊形状 shape: const StadiumBorder(),
), ),
child: Text( child: Text(
'取消预约', '取消预约',
style: TextStyle(color: Colors.grey.shade600, fontSize: 12), style: TextStyle(
color: Colors.grey.shade600,
fontSize: 11.sp,
),
), ),
), ),
), ),
SizedBox(width: 10.w,), SizedBox(width: 10.w),
// 修改按钮 (仅在 hasEdit 为 true 时显示) // 修改按钮 (仅在 hasEdit 为 true 时显示)
if (reservation.hasEdit) if (reservation.hasEdit)
SizedBox( SizedBox(
height: 28, height: 28,
child: OutlinedButton( child: OutlinedButton(
onPressed: () async{ onPressed: () async {
var result = await Get.to( var result = await Get.to(
() => ReservationEditPage(), () => ReservationEditPage(),
arguments: { arguments: {
'reservation': reservation, 'reservation': reservation,
'difference': _controller.difference, 'difference': _controller.difference,
@@ -233,21 +242,22 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
side: const BorderSide(color: Colors.blue), // 蓝色边框 side: BorderSide(color: AppTheme.themeColor),
shape: const StadiumBorder(), shape: const StadiumBorder(),
backgroundColor: Colors.white, backgroundColor: Colors.white,
), ),
child: const Text( child: Text(
'修改', '修改',
style: TextStyle(color: Colors.blue, fontSize: 12), style: TextStyle(color: AppTheme.themeColor, fontSize: 11.sp),
), ),
),), ),
),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildDetailRow('车牌号:', reservation.plateNumber), _buildDetailRow('车牌号:', reservation.plateNumber),
_buildDetailRow('预约日期:', reservation.date), _buildDetailRow('预约日期:', reservation.date),
_buildDetailRow('预约氢量:', reservation.hydAmount), _buildDetailRow('预约氢量:', "${reservation.hydAmount} KG"),
_buildDetailRow('加氢站:', reservation.stationName), _buildDetailRow('加氢站:', reservation.stationName),
_buildDetailRow('开始时间:', reservation.startTime), _buildDetailRow('开始时间:', reservation.startTime),
_buildDetailRow('结束时间:', reservation.endTime), _buildDetailRow('结束时间:', reservation.endTime),
@@ -271,11 +281,26 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(label, style: const TextStyle(color: Colors.grey)), Text(
label,
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 0.6),
fontSize: 14.sp,
fontWeight: FontWeight.w400,
),
),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Text(
child: Text(value, style: const TextStyle(fontWeight: FontWeight.w500)), value,
style: TextStyle(
color: label.contains("预约氢量:")
? Color.fromRGBO(27, 168, 85, 1)
: Color.fromRGBO(51, 51, 51, 1),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
), ),
], ],
), ),

View File

@@ -148,9 +148,11 @@ class ReservationPage extends GetView<C_ReservationController> {
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
controller.stopAutoRefresh();
var scanResult = await Get.to(() => const MessagePage()); var scanResult = await Get.to(() => const MessagePage());
if (scanResult == null) { if (scanResult == null) {
controller.msgNotice(); controller.msgNotice();
controller.startAutoRefresh();
} }
}, },
icon: Badge( icon: Badge(
@@ -177,7 +179,12 @@ class ReservationPage extends GetView<C_ReservationController> {
const SizedBox(width: 8), const SizedBox(width: 8),
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''), _buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildModernStatItem('今日里程', '', "7kg", ''), _buildModernStatItem(
'今日里程',
'',
StorageService.to.hasVehicleInfo ? "7kg" : "-",
'',
),
], ],
), ),
), ),
@@ -237,17 +244,33 @@ class ReservationPage extends GetView<C_ReservationController> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Row( child: Row(
children: [ children: [
Expanded(flex: 4, child: LoginUtil.getAssImg('ic_car_bg@2x')), Expanded(
flex: 4,
child: Image.network(
controller.modeImage,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return Center(child: LoginUtil.getAssImg('ic_car_select@2x'));
},
),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
flex: 6, flex: 6,
child: Column( child: Column(
children: [ children: [
_buildCarDataItem('剩余电量', '36.8%'), _buildCarDataItem(
'剩余电量',
StorageService.to.hasVehicleInfo ? '36.8%' : '-',
),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildCarDataItem('剩余氢量', '${controller.leftHydrogen}Kg'), _buildCarDataItem('剩余氢量', controller.leftHydrogen),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'), _buildCarDataItem('百公里氢耗', controller.workEfficiency),
const SizedBox(height: 12), const SizedBox(height: 12),
Column( Column(
children: [ children: [
@@ -275,7 +298,7 @@ class ReservationPage extends GetView<C_ReservationController> {
), ),
), ),
Text( Text(
"${controller.leftHydrogen}Kg", controller.leftHydrogen,
style: const TextStyle( style: const TextStyle(
fontSize: 10, fontSize: 10,
color: Color(0xFF006633), color: Color(0xFF006633),
@@ -434,8 +457,27 @@ class ReservationPage extends GetView<C_ReservationController> {
/// 时间 Slider 选择器 /// 时间 Slider 选择器
Widget _buildTimeSlider(BuildContext context) { Widget _buildTimeSlider(BuildContext context) {
return Obx(() { return Obx(() {
// 这里的逻辑对应 Controller 中的 24 小时可用 Slot // 获取当前小时作为滑块值 (0-23)
int currentIdx = controller.startTime.value.hour; int currentHour = controller.startTime.value.hour;
// 动态获取站点的营业范围限制
final station = controller.stationOptions.firstWhereOrNull(
(s) => s.hydrogenId == controller.selectedStationId.value,
);
// 解析营业时间
// 处理格式如 "09:00" 或 "09:00:00"
final startParts = (station?.startBusiness ?? "00:00").split(':');
final endParts = (station?.endBusiness ?? "23:59").split(':');
int bizStartHour = int.tryParse(startParts[0]) ?? 0;
int bizEndHour = int.tryParse(endParts[0]) ?? 23;
int bizEndMinute = (endParts.length > 1) ? (int.tryParse(endParts[1]) ?? 0) : 0;
// 优化结束小时逻辑
// 如果分钟为 0 (例如 18:00),说明该小时整点已关门,最大可选小时应减 1
if (bizEndMinute == 0 && bizEndHour > 0) {
bizEndHour--;
}
return Column( return Column(
children: [ children: [
@@ -484,23 +526,23 @@ class ReservationPage extends GetView<C_ReservationController> {
overlayColor: const Color(0xFF006633).withOpacity(0.1), overlayColor: const Color(0xFF006633).withOpacity(0.1),
), ),
child: Slider( child: Slider(
value: currentIdx.toDouble(), value: currentHour.toDouble().clamp(
min: 0, bizStartHour.toDouble(),
max: 23, bizEndHour.toDouble(),
divisions: 23, ),
min: bizStartHour.toDouble(),
max: bizEndHour.toDouble(),
// divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1,
onChanged: (val) { onChanged: (val) {
int hour = val.toInt(); int hour = val.toInt();
// 模拟 Controller 中的 pickTime 逻辑校验
final now = DateTime.now(); final now = DateTime.now();
final isToday = final isToday =
controller.selectedDate.value.year == now.year && controller.selectedDate.value.year == now.year &&
controller.selectedDate.value.month == now.month && controller.selectedDate.value.month == now.month &&
controller.selectedDate.value.day == now.day; controller.selectedDate.value.day == now.day;
if (isToday && hour < now.hour) { // 如果是今天,判断不可选过去的时间点
// 如果是今天且小时数小于当前,则忽略 if (isToday && hour < now.hour) return;
return;
}
controller.startTime.value = TimeOfDay(hour: hour, minute: 0); controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0); controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
@@ -627,6 +669,7 @@ class ReservationPage extends GetView<C_ReservationController> {
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
controller.selectedStationId.value = value; controller.selectedStationId.value = value;
controller.resetTimeForSelectedDate();
} }
}, },

View File

@@ -2,8 +2,11 @@ import 'dart:io';
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart'; import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_app_update/flutter_app_update.dart';
import 'package:flutter_app_update/result_model.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:getx_scaffold/getx_scaffold.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/common/styles/theme.dart';
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart'; import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart'; import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
@@ -20,107 +23,195 @@ class HomeController extends GetxController with BaseControllerMixin {
final _aliyunPush = AliyunPushFlutter(); final _aliyunPush = AliyunPushFlutter();
@override
bool get listenLifecycleEvent => true;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
initAliyunPush(); initAliyunPush();
addPushCallback(); addPushCallback();
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
log('page-init');
// 页面初始化后执行版本检查
checkVersionInfo();
}
String downloadUrl = "";
/// 检查 App 更新信息,增加版本号比对逻辑
void checkVersionInfo() async {
try {
final response = await HttpService.to.get('appointment/appConfig/get');
if (response != null) {
final result = BaseModel.fromJson(response.data);
if (result.code == 0 && result.data != null) {
final data = result.data as Map<String, dynamic>;
bool hasUpdate = data['hasUpdate']?.toString().toLowerCase() == "true";
bool isForce = data['isForce']?.toString().toLowerCase() == "true";
String versionName = data['versionName'] ?? "新版本";
String updateContent = data['updateContent'] ?? "优化系统性能,提升用户体验";
downloadUrl = data['downloadUrl'].toString();
// 获取服务器配置的目标构建号
int serverVersionCode =
int.tryParse(data['versionCode']?.toString() ?? "0") ?? 0;
int serverIosBuildId = int.tryParse(data['iosBuildId']?.toString() ?? "0") ?? 0;
// 获取本地当前的构建号
String currentBuildStr = await getBuildNumber();
int currentBuild = int.tryParse(currentBuildStr) ?? 0;
bool needUpdate = false;
if (GetPlatform.isAndroid) {
needUpdate = currentBuild < serverVersionCode;
} else if (GetPlatform.isIOS) {
needUpdate = currentBuild < serverIosBuildId;
}
// 如果服务器标记有更新,且本地版本号确实较低,则弹出更新
if (hasUpdate && needUpdate) {
_showUpdateDialog("版本:$versionName\n\n更新内容:\n$updateContent", isForce);
}
}
}
} catch (e) {
Logger.d("版本检查失败: $e");
}
}
/// 显示更新弹窗
void _showUpdateDialog(String content, bool isForce) {
DialogX.to.showConfirmDialog(
title: '升级提醒',
confirmText: '立即升级',
content: _buildDialogContent(content),
// 如果是强制更新,取消按钮显示为空,即隐藏
cancelText: isForce ? "" : '以后再说',
// 设置为 false禁止点击背景和物理返回键关闭
barrierDismissible: false,
onConfirm: () {
jumpUpdateApp();
// ios如果是强制更新点击后维持弹窗防止用户进入 App
if (isForce && GetPlatform.isIOS) {
Future.delayed(const Duration(milliseconds: 500), () {
_showUpdateDialog(content, isForce);
});
}
},
);
}
Widget _buildDialogContent(String content) {
return PopScope(
canPop: false, // 关键:禁止 pop
child: TextX.bodyMedium(content).padding(bottom: 16.h),
);
}
void jumpUpdateApp() {
if (GetPlatform.isIOS) {
// 跳转到 iOS 应用商店网页
openWebPage("https://apps.apple.com/cn/app/羚牛氢能/6756245815");
} else if (GetPlatform.isAndroid) {
// Android 执行下载安装流程
showAndroidDownloadDialog();
}
}
void showAndroidDownloadDialog() {
AzhonAppUpdate.listener((ResultModel model) {
if (model.type == ResultType.start) {
DialogX.to.showConfirmDialog(
content: PopScope(
canPop: false,
child: Center(
child: Column(
children: [
TextX.bodyMedium('升级中...').padding(bottom: 45.h),
CircularProgressIndicator(),
],
),
),
),
confirmText: '',
cancelText: "",
barrierDismissible: false,
);
} else if (model.type == ResultType.done) {
Get.back();
}
});
UpdateModel model = UpdateModel(downloadUrl, "xll.apk", "logo", '正在下载最新版本...');
AzhonAppUpdate.update(model);
} }
// 根据登录状态和登录渠道返回不同的首页 // 根据登录状态和登录渠道返回不同的首页
Widget getHomePage() { Widget getHomePage() {
requestPermission(); requestPermission();
//登录状态跳转
if (StorageService.to.isLoggedIn) { if (StorageService.to.isLoggedIn) {
// 如果已登录,再判断是哪个渠道
if (StorageService.to.loginChannel == LoginChannel.station) { if (StorageService.to.loginChannel == LoginChannel.station) {
return B_BaseWidgetsPage(); // 站点首页 return B_BaseWidgetsPage();
} else if (StorageService.to.loginChannel == LoginChannel.driver) { } else if (StorageService.to.loginChannel == LoginChannel.driver) {
return BaseWidgetsPage(); // 司机首页 return BaseWidgetsPage();
} else { } else {
return LoginPage(); return LoginPage();
} }
} else { } else {
// 未登录,直接去登录页
return LoginPage(); return LoginPage();
} }
} }
void requestPermission() async { void requestPermission() async {
PermissionStatus status = await Permission.notification.status; PermissionStatus status = await Permission.notification.status;
if (status.isGranted) { if (status.isGranted) return;
Logger.d("通知权限已开启");
return;
}
if (status.isDenied) { if (status.isDenied) {
// 建议此处增加一个应用内的 Rationale (解释说明) 弹窗
status = await Permission.notification.request(); status = await Permission.notification.request();
} }
if (status.isGranted) { if (status.isGranted) {
// 授权成功
Logger.d('通知已开启'); Logger.d('通知已开启');
} else if (status.isPermanentlyDenied) { } else if (status.isPermanentlyDenied) {
Logger.d('通知权限已被拒绝,请到系统设置中开启'); Logger.d('通知权限已被拒绝,请到系统设置中开启');
} else if (status.isDenied) {
Logger.d('请授予通知权限,以便接收加氢站通知');
} }
} }
///推送相关 ///推送相关初始化 (保持原样)
Future<void> initAliyunPush() async { Future<void> initAliyunPush() async {
// 1. 配置分离:建议将 Key 提取到外部或配置文件中
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key; final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
final String appSecret = Platform.isIOS final String appSecret = Platform.isIOS
? AppTheme.ios_appsecret ? AppTheme.ios_appsecret
: AppTheme.android_appsecret; : AppTheme.android_appsecret;
try { try {
// 初始化推送
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret); final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
if (result['code'] != kAliyunPushSuccessCode) return;
if (result['code'] != kAliyunPushSuccessCode) {
Logger.d('初始化推送失败: ${result['code']} - ${result['errorMsg']}');
return;
}
Logger.d('阿里云推送初始化成功');
// 分平台配置
if (Platform.isIOS) { if (Platform.isIOS) {
await _setupIOSConfig(); await _setupIOSConfig();
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
await _setupAndroidConfig(); await _setupAndroidConfig();
} }
} catch (e) { } catch (e) {
Logger.d('初始化过程中发生异常: $e'); Logger.d('初始化异常: $e');
} }
} }
/// iOS 专属配置
Future<void> _setupIOSConfig() async { Future<void> _setupIOSConfig() async {
final res = await _aliyunPush.showIOSNoticeWhenForeground(true); await _aliyunPush.showIOSNoticeWhenForeground(true);
if (res['code'] == kAliyunPushSuccessCode) {
Logger.d('iOS 前台通知展示已开启');
} else {
Logger.d('iOS 前台通知开启失败: ${res['errorMsg']}');
}
} }
/// Android 专属配置
Future<void> _setupAndroidConfig() async { Future<void> _setupAndroidConfig() async {
await _aliyunPush.setNotificationInGroup(true); await _aliyunPush.setNotificationInGroup(true);
final res = await _aliyunPush.createAndroidChannel( await _aliyunPush.createAndroidChannel(
"xll_push_android", "xll_push_android",
'新消息通知', '新消息通知',
4, 4,
'用于接收加氢站实时状态提醒', '用于接收加氢站实时状态提醒',
); );
if (res['code'] == kAliyunPushSuccessCode) {
Logger.d('Android 通知通道创建成功');
} else {
Logger.d('Android 通道创建失败: ${res['code']} - ${res['errorMsg']}');
}
} }
void addPushCallback() { void addPushCallback() {
@@ -139,40 +230,23 @@ class HomeController extends GetxController with BaseControllerMixin {
Future<void> _onAndroidNotificationClickedWithNoAction( Future<void> _onAndroidNotificationClickedWithNoAction(
Map<dynamic, dynamic> message, Map<dynamic, dynamic> message,
) async { ) async {}
Logger.d('onAndroidNotificationClickedWithNoAction ====> $message');
}
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async { Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {}
Logger.d('onAndroidNotificationReceivedInApp ====> $message');
}
Future<void> _onMessage(Map<dynamic, dynamic> message) async { Future<void> _onMessage(Map<dynamic, dynamic> message) async {}
Logger.d('onMessage ====> $message');
}
Future<void> _onNotification(Map<dynamic, dynamic> message) async { Future<void> _onNotification(Map<dynamic, dynamic> message) async {}
Logger.d('onNotification ====> $message');
}
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async { Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
Logger.d('onNotificationOpened ====> $message');
await Get.to(() => const MessagePage()); await Get.to(() => const MessagePage());
} }
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async { Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {}
Logger.d('onNotificationRemoved ====> $message');
}
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async { Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {}
Logger.d('onIOSChannelOpened ====> $message');
}
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async { Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {}
Logger.d('onIOSRegisterDeviceTokenSuccess ====> $message');
}
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async { Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {}
Logger.d('onIOSRegisterDeviceTokenFailed====> $message');
}
} }

View File

@@ -5,18 +5,13 @@ import 'package:ln_jq_app/pages/home/controller.dart';
class HomePage extends GetView<HomeController> { class HomePage extends GetView<HomeController> {
const HomePage({super.key}); const HomePage({super.key});
// 主视图
Widget _buildView() {
return <Widget>[Text('主页面')].toColumn(mainAxisSize: MainAxisSize.min).center();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<HomeController>( return GetBuilder<HomeController>(
init: HomeController(), init: HomeController(),
id: 'home', id: 'home',
builder: (_) { builder: (_) {
return controller.getHomePage(); return Scaffold(body: controller.getHomePage());
}, },
); );
} }

View File

@@ -204,9 +204,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
Row( Row(
children: [ children: [
const Text( const Text(
"欢迎使用 ", "欢迎使用小羚羚 ",
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 22,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color.fromRGBO(51, 51, 51, 1), color: Color.fromRGBO(51, 51, 51, 1),
), ),

View File

@@ -23,7 +23,7 @@ class WelcomePage extends GetView<WelcomeController> {
right: 0, right: 0,
child: Image.asset( child: Image.asset(
'assets/images/welcome.png', 'assets/images/welcome.png',
fit: BoxFit.fill fit: BoxFit.cover
), ),
), ),
], ],

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ dependencies:
geolocator: ^14.0.2 # 获取精确定位 geolocator: ^14.0.2 # 获取精确定位
aliyun_push_flutter: ^1.3.6 aliyun_push_flutter: ^1.3.6
pull_to_refresh: ^2.0.0 pull_to_refresh: ^2.0.0
flutter_app_update: ^3.2.2
dev_dependencies: dev_dependencies: