Compare commits
3 Commits
9242dcaf70
...
dev_map
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ba9547992 | ||
|
|
5c74c7ccc0 | ||
|
|
08dcc64ef4 |
@@ -37,8 +37,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = 7
|
||||
versionName = "1.2.4"
|
||||
versionCode = 6
|
||||
versionName = "1.2.3"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
27
ln_jq_app/android/app/proguard-rules.pro
vendored
@@ -59,30 +59,3 @@
|
||||
-dontwarn anetwork.**
|
||||
-dontwarn com.ut.**
|
||||
-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.**{*;}
|
||||
@@ -1,36 +1,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<!--定位权限-->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<!--用于申请调用A-GPS模块-->
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>
|
||||
<!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明-->
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:label="小羚羚"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/logo"
|
||||
android:label="小羚羚">
|
||||
android:icon="@mipmap/logo">
|
||||
|
||||
<!-- 高德地图Key -->
|
||||
<meta-data
|
||||
@@ -52,23 +48,24 @@
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
@@ -78,13 +75,30 @@
|
||||
android:value="2" />
|
||||
|
||||
<!-- 请填写你自己的- appKey -->
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appkey"
|
||||
android:value="335642645" />
|
||||
<meta-data android:name="com.alibaba.app.appkey" android:value="335642645"/>
|
||||
<!-- 请填写你自己的appSecret -->
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appsecret"
|
||||
android:value="39628204345a4240b5b645b68a5896c7" />
|
||||
<meta-data android:name="com.alibaba.app.appsecret" android:value="39628204345a4240b5b645b68a5896c7"/>
|
||||
<!-- 华为通道的参数appid -->
|
||||
<meta-data android:name="com.huawei.hms.client.appid" android:value="" />
|
||||
|
||||
<!-- vivo通道的参数api_key为appkey -->
|
||||
<meta-data android:name="com.vivo.push.api_key" android:value="" />
|
||||
<meta-data android:name="com.vivo.push.app_id" android:value="" />
|
||||
|
||||
<!-- honor通道的参数-->
|
||||
<meta-data android:name="com.hihonor.push.app_id" android:value="" />
|
||||
|
||||
<!-- oppo -->
|
||||
<meta-data android:name="com.oppo.push.key" android:value="" />
|
||||
<meta-data android:name="com.oppo.push.secret" android:value="" />
|
||||
|
||||
<!-- 小米-->
|
||||
<meta-data android:name="com.xiaomi.push.id" android:value="id=2222222222222222222" />
|
||||
<meta-data android:name="com.xiaomi.push.key" android:value="id=5555555555555" />
|
||||
|
||||
<!-- 魅族-->
|
||||
<meta-data android:name="com.meizu.push.id" android:value="" />
|
||||
<meta-data android:name="com.meizu.push.key" android:value="" />
|
||||
|
||||
|
||||
|
||||
@@ -109,7 +123,6 @@
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
@@ -128,8 +141,8 @@
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -91,7 +91,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -160,17 +159,19 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
this.mContext = context;
|
||||
mActivity = getActivityFromContext(context);
|
||||
|
||||
MapsInitializer.updatePrivacyShow(mActivity, true, true);
|
||||
MapsInitializer.updatePrivacyAgree(mActivity, true);
|
||||
|
||||
mapView = new MapView(context);
|
||||
mapView.onCreate(null);
|
||||
aMap = mapView.getMap();
|
||||
MapsInitializer.updatePrivacyShow(context, true, true);
|
||||
MapsInitializer.updatePrivacyAgree(context, true);
|
||||
|
||||
container = new FrameLayout(context);
|
||||
container.setClickable(true);
|
||||
container.setFocusable(true);
|
||||
container.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
|
||||
|
||||
|
||||
mapView = new MapView(context);
|
||||
mapView.onCreate(null);
|
||||
aMap = mapView.getMap();
|
||||
|
||||
container.addView(mapView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
initLoadingView(context);
|
||||
@@ -264,20 +265,7 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
suggestionList.setDividerHeight(1);
|
||||
suggestionList.setDivider(new ColorDrawable(Color.LTGRAY));
|
||||
suggestionList.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
|
||||
suggestionAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, new ArrayList<>()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
// 获取默认的 View
|
||||
View view = super.getView(position, convertView, parent);
|
||||
TextView text = view.findViewById(android.R.id.text1);
|
||||
text.setTextColor(Color.parseColor("#333333"));
|
||||
text.setTextSize(14);
|
||||
view.setBackgroundColor(Color.TRANSPARENT);
|
||||
|
||||
return view;
|
||||
}
|
||||
};
|
||||
suggestionAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1);
|
||||
suggestionList.setAdapter(suggestionAdapter);
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
suggestionList.setOnItemClickListener((parent, view, position, id) -> {
|
||||
@@ -287,8 +275,6 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
String name = tip.getName();
|
||||
String district = tip.getDistrict();
|
||||
endName = name;
|
||||
endAddress = tip.getAddress();
|
||||
isGetInputtips = true;
|
||||
isUserSelectedDestination = true; // 标识用户手动选择了目的地
|
||||
isProgrammaticTextChange = true; // 标识是程序自动设置文本
|
||||
endInput.setText(district != null && !district.isEmpty() ? name + " " + district : name);
|
||||
@@ -312,8 +298,8 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
if (s.length() > 1) {
|
||||
// 使用当前位置的城市进行搜索
|
||||
String city = currentLatLng != null ? "" : "";
|
||||
InputtipsQuery query = new InputtipsQuery(s.toString(), "");
|
||||
query.setCityLimit(false);
|
||||
InputtipsQuery query = new InputtipsQuery(s.toString(), city);
|
||||
query.setCityLimit(true);
|
||||
Inputtips inputtips = new Inputtips(mContext, query);
|
||||
inputtips.setInputtipsListener(NativeMapView.this);
|
||||
inputtips.requestInputtipsAsyn();
|
||||
@@ -527,8 +513,6 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
return panel;
|
||||
}
|
||||
|
||||
private boolean isGetInputtips = false;
|
||||
|
||||
private void calculateRouteBeforeNavi() {
|
||||
if (startPoint == null) {
|
||||
Toast.makeText(mContext, "正在定位...", Toast.LENGTH_SHORT).show();
|
||||
@@ -540,18 +524,7 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
return;
|
||||
}
|
||||
|
||||
//如果是输入地址提示内容
|
||||
|
||||
if (isGetInputtips) {
|
||||
// 开始规划前隐藏输入框面板
|
||||
searchArea.setVisibility(View.GONE);
|
||||
|
||||
RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(new LatLonPoint(startPoint.latitude, startPoint.longitude), new LatLonPoint(endPoint.latitude, endPoint.longitude));
|
||||
RouteSearch.DriveRouteQuery query = new RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DRIVING_SINGLE_DEFAULT, null, null, "");
|
||||
routeSearch.calculateDriveRouteAsyn(query);
|
||||
} else {
|
||||
fetchTruckRouteAlgorithm(mLoc);
|
||||
}
|
||||
fetchTruckRouteAlgorithm(mLoc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -577,9 +550,6 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
if (suggestionNames.size() > 0) {
|
||||
suggestionList.setVisibility(View.VISIBLE);
|
||||
|
||||
suggestionList.setBackgroundColor(Color.WHITE);
|
||||
|
||||
// 限制显示的高度
|
||||
int height = Math.min(suggestionNames.size() * dp2px(45), dp2px(200));
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
@@ -599,58 +569,34 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
|
||||
@Override
|
||||
public void onDriveRouteSearched(DriveRouteResult result, int rCode) {
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (rCode == AMapException.CODE_AMAP_SUCCESS && result != null && !result.getPaths().isEmpty()) {
|
||||
DrivePath path = result.getPaths().get(0);
|
||||
|
||||
// 规划成功,显示详情面板,隐藏模式选择
|
||||
detailPanel.setVisibility(View.VISIBLE);
|
||||
planToggleBtn.setVisibility(View.GONE);
|
||||
modeMenu.setVisibility(View.GONE);
|
||||
|
||||
tvStationName.setText(endName);
|
||||
tvStationAddr.setText(endAddress);
|
||||
|
||||
|
||||
if (rCode == AMapException.CODE_AMAP_SUCCESS && result != null && !result.getPaths().isEmpty()) {
|
||||
DrivePath path = result.getPaths().get(0);
|
||||
double distanceKm = path.getDistance() / 1000f;
|
||||
long durationMin = path.getDuration() / 60;
|
||||
double tolls = path.getTolls();
|
||||
String hydrogenCost = truckRouteData.algorithmPath.hydrogenCost;
|
||||
|
||||
// 规划成功,显示详情面板,隐藏模式选择
|
||||
if (detailPanel != null)
|
||||
detailPanel.setVisibility(View.VISIBLE);
|
||||
if (planToggleBtn != null)
|
||||
planToggleBtn.setVisibility(View.GONE);
|
||||
if (modeMenu != null)
|
||||
modeMenu.setVisibility(View.GONE);
|
||||
tvDuration.setText("预计时间:" + durationMin + "分钟");
|
||||
tvDistance.setText("行驶里程:" + String.format("%.1f", distanceKm) + "公里");
|
||||
tvTolls.setText("过路费:" + (int) tolls + "元");
|
||||
tvTollsFuel.setText("加氢费用:" + hydrogenCost + "元");
|
||||
|
||||
tvStationName.setText(endName);
|
||||
tvStationAddr.setText(endAddress);
|
||||
|
||||
double distanceKm = path.getDistance() / 1000f;
|
||||
long durationMin = path.getDuration() / 60;
|
||||
double tolls = path.getTolls();
|
||||
|
||||
String hydrogenCost = "--"; // 默认显示横线
|
||||
try {
|
||||
// 增加多级非空校验,防止点击搜索条目时崩溃
|
||||
if (truckRouteData != null &&
|
||||
truckRouteData.algorithmPath != null &&
|
||||
truckRouteData.algorithmPath.hydrogenCost != null &&
|
||||
!truckRouteData.algorithmPath.hydrogenCost.isEmpty()) {
|
||||
hydrogenCost = truckRouteData.algorithmPath.hydrogenCost;
|
||||
}
|
||||
|
||||
tvDuration.setText("预计时间:" + durationMin + "分钟");
|
||||
tvDistance.setText("行驶里程:" + String.format("%.1f", distanceKm) + "公里");
|
||||
tvTolls.setText("过路费:" + (int) tolls + "元");
|
||||
tvTollsFuel.setText("加氢费用:" + (isGetInputtips ? "--" : hydrogenCost) + "元");
|
||||
|
||||
isGetInputtips = false;
|
||||
|
||||
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(endPoint, 13f));
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "获取加氢费用失败", e);
|
||||
}
|
||||
} else {
|
||||
// 规划失败回退面板
|
||||
searchArea.setVisibility(View.VISIBLE);
|
||||
Toast.makeText(mContext, "路径规划失败: " + rCode, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(endPoint, 13f));
|
||||
} else {
|
||||
// 规划失败回退面板
|
||||
searchArea.setVisibility(View.VISIBLE);
|
||||
Toast.makeText(mContext, "路径规划失败: " + rCode, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void startRouteSearch() {
|
||||
@@ -718,20 +664,16 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
//车辆信息
|
||||
AMapCarInfo carInfo = new AMapCarInfo();
|
||||
carInfo.setCarNumber(plateNumber);
|
||||
|
||||
if (truckRouteData != null) {
|
||||
carInfo.setCarType(String.valueOf(truckRouteData.truckDto.mcarType));
|
||||
carInfo.setVehicleAxis(String.valueOf(truckRouteData.truckDto.mvehicleAxis));
|
||||
carInfo.setVehicleHeight(truckRouteData.truckDto.mvehicleHeight);
|
||||
carInfo.setVehicleLength(truckRouteData.truckDto.mvehicleLength);
|
||||
carInfo.setVehicleWidth(truckRouteData.truckDto.mvehicleWidth);
|
||||
carInfo.setVehicleSize(String.valueOf(truckRouteData.truckDto.mvehicleSize));
|
||||
carInfo.setVehicleLoad(truckRouteData.truckDto.mvehicleLoad);
|
||||
carInfo.setVehicleWeight(truckRouteData.truckDto.mvehicleWeight);
|
||||
carInfo.setRestriction(truckRouteData.truckDto.isRestriction);
|
||||
carInfo.setVehicleLoadSwitch(truckRouteData.truckDto.mvehicleLoadSwitch);
|
||||
}
|
||||
|
||||
carInfo.setCarType(String.valueOf(truckRouteData.truckDto.mcarType));
|
||||
carInfo.setVehicleAxis(String.valueOf(truckRouteData.truckDto.mvehicleAxis));
|
||||
carInfo.setVehicleHeight(truckRouteData.truckDto.mvehicleHeight);
|
||||
carInfo.setVehicleLength(truckRouteData.truckDto.mvehicleLength);
|
||||
carInfo.setVehicleWidth(truckRouteData.truckDto.mvehicleWidth);
|
||||
carInfo.setVehicleSize(String.valueOf(truckRouteData.truckDto.mvehicleSize));
|
||||
carInfo.setVehicleLoad(truckRouteData.truckDto.mvehicleLoad);
|
||||
carInfo.setVehicleWeight(truckRouteData.truckDto.mvehicleWeight);
|
||||
carInfo.setRestriction(truckRouteData.truckDto.isRestriction);
|
||||
carInfo.setVehicleLoadSwitch(truckRouteData.truckDto.mvehicleLoadSwitch);
|
||||
|
||||
// 检查途径点数量,决定使用哪种导航方式
|
||||
int wayPointsCount = waysPoiIds.size();
|
||||
@@ -844,17 +786,12 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation
|
||||
|
||||
dismissLoading();
|
||||
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 开始规划前隐藏输入框面板
|
||||
searchArea.setVisibility(View.GONE);
|
||||
// 开始规划前隐藏输入框面板
|
||||
searchArea.setVisibility(View.GONE);
|
||||
|
||||
RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(new LatLonPoint(startPoint.latitude, startPoint.longitude), new LatLonPoint(endPoint.latitude, endPoint.longitude));
|
||||
RouteSearch.DriveRouteQuery query = new RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DRIVING_SINGLE_DEFAULT, null, null, "");
|
||||
routeSearch.calculateDriveRouteAsyn(query);
|
||||
}
|
||||
});
|
||||
RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(new LatLonPoint(startPoint.latitude, startPoint.longitude), new LatLonPoint(endPoint.latitude, endPoint.longitude));
|
||||
RouteSearch.DriveRouteQuery query = new RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DRIVING_SINGLE_DEFAULT, null, null, "");
|
||||
routeSearch.calculateDriveRouteAsyn(query);
|
||||
} else {
|
||||
dismissLoading();
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
.amap-callamap,
|
||||
.amap-lib-driving-callBtn,
|
||||
.amap-copyright,
|
||||
.amap-logo{bottom: 60px}
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 去除高德默认的 label 边框 and 背景 */
|
||||
/* 去除高德默认的 label 边框和背景 */
|
||||
.amap-marker-label {
|
||||
border: none !important;
|
||||
background-color: transparent !important;
|
||||
@@ -107,7 +109,7 @@
|
||||
/* --- 导航结果面板 (底部弹出) --- */
|
||||
#panel {
|
||||
position: fixed;
|
||||
bottom: 95px;
|
||||
bottom: 75px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 35%;
|
||||
@@ -127,7 +129,7 @@
|
||||
#location-btn {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 105px;
|
||||
bottom: 75px;
|
||||
/* 默认位置 */
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
@@ -157,7 +159,7 @@
|
||||
/* --- 调整比例尺位置 --- */
|
||||
.amap-scalecontrol {
|
||||
/* 初始状态:避开底部的定位按钮或留出安全间距 */
|
||||
bottom: 110px !important;
|
||||
bottom: 80px !important;
|
||||
left: 10px !important;
|
||||
transition: bottom 0.3s ease;
|
||||
/* 增加平滑动画 */
|
||||
@@ -193,10 +195,10 @@
|
||||
|
||||
<div id="search-box">
|
||||
<div class="input-row">
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||
<button onclick="startRouteSearch()">路径规划</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +221,6 @@
|
||||
var currentLat, currentLng;
|
||||
var isTruckMode = false;
|
||||
var isInitialLocationSet = false;
|
||||
var stationMarkers = []; // 存储所有站点的标记
|
||||
|
||||
|
||||
function initMap() {
|
||||
@@ -242,11 +243,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 点击地图空白处重置状态
|
||||
map.on('click', function() {
|
||||
resetSearchState();
|
||||
});
|
||||
|
||||
// 添加基础控件
|
||||
map.addControl(new AMap.Scale());
|
||||
map.addControl(new AMap.ToolBar({
|
||||
@@ -288,19 +284,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置搜索状态,隐藏面板和路线
|
||||
*/
|
||||
function resetSearchState() {
|
||||
if (document.body.classList.contains('panel-active')) {
|
||||
console.log("JS->: 重置地图状态");
|
||||
document.body.classList.remove('panel-active');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'none';
|
||||
if (driving) driving.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||||
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||||
@@ -353,8 +336,6 @@
|
||||
fetchStationInfo(addressComponent.province, addressComponent.city,
|
||||
addressComponent.district, lat, lng);
|
||||
|
||||
fetchStationInfoList(lat, lng);
|
||||
|
||||
// 策略1: 优先使用最近的、类型合适的POI的名称
|
||||
if (pois && pois.length > 0) {
|
||||
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
||||
@@ -416,6 +397,7 @@
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
province: province,
|
||||
@@ -455,79 +437,6 @@
|
||||
.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 () {
|
||||
var stationName = station.name || "目的地";
|
||||
document.getElementById('endInput').value = station.address ||
|
||||
stationName;
|
||||
|
||||
// 更新当前的 destMarker
|
||||
if (destMarker && destMarker !== sMarker) destMarker.setMap(null);
|
||||
destMarker = sMarker;
|
||||
|
||||
// 直接传入坐标对象,避免关键字搜索失败
|
||||
var loc = new AMap.LngLat(station.longitude, station.latitude);
|
||||
startRouteSearch(loc);
|
||||
});
|
||||
|
||||
stationMarkers.push(sMarker);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("JS->: 业务报错或无数据:", res.message);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* 地理编码并在地图标记终点
|
||||
*/
|
||||
@@ -538,6 +447,7 @@
|
||||
if (destMarker) destMarker.setMap(null);
|
||||
|
||||
// 2. 创建自定义图标
|
||||
// 假设图标大小为 32x32,你可以根据实际图片尺寸调整 Size
|
||||
var destIcon = new AMap.Icon({
|
||||
size: new AMap.Size(32, 32), // 图标尺寸
|
||||
image: 'ic_tag.png', // 本地图片路径
|
||||
@@ -549,6 +459,8 @@
|
||||
map: map,
|
||||
position: [longitude, latitude],
|
||||
icon: destIcon, // 使用自定义图标
|
||||
// 偏移量:如果图标底部中心是尖角,offset 设为宽的一半的负数,高度的负数
|
||||
// 这样能确保图片的底部尖端指向地图上的精确位置
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: name,
|
||||
label: {
|
||||
@@ -557,7 +469,17 @@
|
||||
}
|
||||
});
|
||||
|
||||
console.log("JS->: 终点标记已添加", address);
|
||||
// 4. 打印调试信息
|
||||
console.log("JS->: 终点标记已添加", address, loc.toString());
|
||||
|
||||
// 5. 自动调整视野包含起点和终点
|
||||
// if (marker) {
|
||||
// // 如果起点标志已存在,缩放地图以展示两者
|
||||
// map.setFitView([marker, destMarker], false, [60, 60, 60, 60]);
|
||||
// } else {
|
||||
// // 如果没有起点,直接跳到终点
|
||||
// map.setCenter(loc);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,12 +498,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路径规划
|
||||
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
|
||||
* 路径规划
|
||||
*/
|
||||
function startRouteSearch(destLoc) {
|
||||
function startRouteSearch() {
|
||||
// 获取输入框的文字
|
||||
var startKw = document.getElementById('startInput').value;
|
||||
var endKw = document.getElementById('endInput').value;
|
||||
|
||||
@@ -589,59 +510,63 @@
|
||||
alert("请输入起点");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endKw) {
|
||||
alert("请输入终点");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除旧路线
|
||||
if (driving) driving.clear();
|
||||
|
||||
// 收起键盘
|
||||
document.getElementById('startInput').blur();
|
||||
document.getElementById('endInput').blur();
|
||||
|
||||
// --- 构造路径规划的点 (使用数组方式,更灵活) ---
|
||||
var points = [];
|
||||
|
||||
// 1. 起点逻辑
|
||||
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
|
||||
// 1. 处理起点逻辑
|
||||
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标
|
||||
if (!startKw || startKw === '我的位置') {
|
||||
if (!currentLng || !currentLat) {
|
||||
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
|
||||
// 如果还没获取到定位
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('requestLocation');
|
||||
}
|
||||
alert("正在获取定位,请稍后...");
|
||||
return;
|
||||
}
|
||||
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
|
||||
points.push({
|
||||
keyword: '我的位置',
|
||||
location: new AMap.LngLat(currentLng, currentLat)
|
||||
keyword: '我的位置', // 用于显示的名字
|
||||
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标
|
||||
});
|
||||
} else {
|
||||
// 如果用户手动输入了地点 (例如 "北京南站")
|
||||
// 直接存入关键词,让高德自己去搜
|
||||
points.push({
|
||||
keyword: startKw
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
|
||||
if (destLoc) {
|
||||
points.push({
|
||||
keyword: endKw,
|
||||
location: destLoc // 关键:使用精确坐标
|
||||
});
|
||||
} else {
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
}
|
||||
// 2. 处理终点逻辑 (通常是关键词)
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
|
||||
// 3. 发起搜索
|
||||
// points 数组里现在是一个起点对象和一个终点对象
|
||||
driving.search(points, function (status, result) {
|
||||
if (status === 'complete') {
|
||||
console.log('JS: 规划成功');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'block';
|
||||
document.body.classList.add('panel-active');
|
||||
} else {
|
||||
console.log('JS: 规划失败', result);
|
||||
alert("规划失败,请检查起终点名称");
|
||||
}
|
||||
// else {
|
||||
// console.error('JS: 规划失败', result);
|
||||
// // 如果坐标规划都失败了,通常是由于起终点距离过近或政策限制(如货车禁行)
|
||||
// alert("路径规划未成功,请尝试微调起终点");
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 892 B |
|
Before Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 573 B |
|
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 636 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 545 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 844 B |
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 909 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 864 B |
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// 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; // 箭头尖端 X(向右偏移ah = 45度)
|
||||
CGFloat arrowTipY = bh + ah; // 箭头尖端 Y(向下偏移ah = 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) ──
|
||||
// 子视图宽度统一约束到 kBubbleWidth(160),气泡主体实际宽度
|
||||
[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; // 箭头尖端 X(右下45度)
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// 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
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ARoutePlaneController : ABaseViewController
|
||||
|
||||
@interface ARoutePlaneController : ABaseViewController<AMapNaviDriveDataRepresentable>
|
||||
@property (nonatomic , strong) AMapNaviDriveView * driveView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
#import "AMapPrivacyUtility.h"
|
||||
|
||||
#import "AStationDetailPopupController.h"
|
||||
#import "AStationDetailPopupView.h"
|
||||
|
||||
#define kRouteIndicatorViewHeight 64.f
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
#import "AMapNavHttpUtil.h"
|
||||
#import "ACustomStepView.h"
|
||||
#import "ABottomBarView.h"
|
||||
#import "AAddHPopView.h"
|
||||
|
||||
@interface ARoutePlaneController ()<MAMapViewDelegate, AMapNaviDriveManagerDelegate,AMapNaviCompositeManagerDelegate , AMapLocationManagerDelegate , UITextFieldDelegate , AStationDetailPopupDelegate, ABottomBarViewDelegate>
|
||||
@interface ARoutePlaneController ()<MAMapViewDelegate, AMapNaviDriveManagerDelegate,AMapNaviCompositeManagerDelegate , AMapLocationManagerDelegate , UITextFieldDelegate , AStationDetailPopupViewDelegate, ABottomBarViewDelegate>
|
||||
@property (nonatomic, strong) UITextField *textField;
|
||||
|
||||
/// 底部搜索+规划路线栏
|
||||
@@ -65,11 +66,16 @@
|
||||
@property (nonatomic , strong)ACustomStepView * stepView;
|
||||
|
||||
/// 当前弹出的站点详情弹框
|
||||
@property (nonatomic , strong)AStationDetailPopupController * stationDetailPopup;
|
||||
@property (nonatomic , strong)AStationDetailPopupView * stationDetailPopup;
|
||||
@property (nonatomic, strong) ANavPointModel *pointModel; //当前选的目的点
|
||||
@property (nonatomic, strong) ATripCalcDataModel * tjdPathInfoModel;//途经点信息
|
||||
|
||||
@property (nonatomic, strong) CLLocationManager * locationManager;
|
||||
|
||||
/// 加氢规划按钮(用于气泡弹框定位)
|
||||
@property (nonatomic, weak) UIButton *addHBtn;
|
||||
|
||||
@property (nonatomic ,strong)MAAnnotationView * currAnnotionView;
|
||||
@end
|
||||
|
||||
@implementation ARoutePlaneController
|
||||
@@ -156,6 +162,8 @@
|
||||
|
||||
|
||||
-(void)requestRoutePathWithParms:(NSDictionary*)dic completeHandle:(void(^)(ATripCalcDataModel * tjd))blk {
|
||||
///清除上一次数据影响
|
||||
self.tjdPathInfoModel = nil;
|
||||
|
||||
NSString * token = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.token"];
|
||||
NSString * carNo = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.plateNumber"];
|
||||
@@ -168,8 +176,8 @@
|
||||
}
|
||||
|
||||
NSMutableDictionary * dic2 = [NSMutableDictionary dictionary];
|
||||
dic2[@"longitude"] = [NSString stringWithFormat:@"%f" , self.pointModel.coordinate.longitude];
|
||||
dic2[@"latitude"] = [NSString stringWithFormat:@"%f" , self.pointModel.coordinate.latitude];
|
||||
dic2[@"longitude"] = [NSString stringWithFormat:@"%f" , self.longitude];
|
||||
dic2[@"latitude"] = [NSString stringWithFormat:@"%f" , self.latitude];
|
||||
dic2[@"plateNumber"] = carNo;
|
||||
dic2[@"hydrogenSiteId"] = [NSString stringWithFormat:@"%@" , self.pointModel.stationID];
|
||||
|
||||
@@ -279,7 +287,8 @@
|
||||
|
||||
[bottomBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.bottom.equalTo(self.view);
|
||||
// make.bottom.equalTo(self.view).offset(-AMP_TabbarHeight - 13);
|
||||
make.bottom.equalTo(self.view).offset(0);
|
||||
}];
|
||||
|
||||
// ── 兼容旧逻辑:保留 startTf / dstTf 属性(隐藏,不再显示) ──
|
||||
@@ -319,25 +328,55 @@
|
||||
[stepView addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
|
||||
self.stepView = stepView;
|
||||
|
||||
stepView.hidden = YES;
|
||||
|
||||
///当前位置按钮
|
||||
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[btn setImage:[AMapNavCommonUtil imageWithName3x:@"my_location_icon"] forState:UIControlStateNormal];
|
||||
btn.backgroundColor = [UIColor lightGrayColor];
|
||||
[btn setBackgroundImage:[AMapNavCommonUtil imageWithName3x:@"my_location_icon"] forState:UIControlStateNormal];
|
||||
|
||||
btn.backgroundColor = [UIColor whiteColor];
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
btn.layer.cornerRadius = 20;
|
||||
btn.layer.cornerRadius = 22;
|
||||
|
||||
[btn addTarget:self action:@selector(updateUserLocalAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self.view addSubview:btn];
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.view).offset(-10);
|
||||
make.width.height.equalTo(@40);
|
||||
make.bottom.equalTo(bottomBar.mas_top).offset(-85);
|
||||
make.width.height.equalTo(@44);
|
||||
make.bottom.equalTo(bottomBar.mas_top).offset(-105);
|
||||
}];
|
||||
|
||||
///加氢规划按钮
|
||||
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 {
|
||||
if ([keyPath isEqualToString:@"value"]) {
|
||||
self.mapView.zoomLevel = [change[NSKeyValueChangeNewKey] doubleValue];
|
||||
@@ -363,6 +402,7 @@
|
||||
self.mapView.userTrackingMode = MAUserTrackingModeFollowWithHeading;
|
||||
self.mapView.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // 定位精度
|
||||
_mapView.showsScale= YES;
|
||||
_mapView.showsCompass = NO;
|
||||
|
||||
_mapView.logoCenter = CGPointMake(CGRectGetWidth(self.view.bounds)-55, 450);
|
||||
self.mapView.zoomLevel = 11;
|
||||
@@ -389,7 +429,7 @@
|
||||
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(self.latitude, self.longitude);
|
||||
|
||||
[_mapView setCenterCoordinate:coord animated:YES];
|
||||
// [_mapView setZoomLevel:10 animated:YES];
|
||||
[_mapView setZoomLevel:15.0 animated:YES];
|
||||
} else {
|
||||
// 如果尚未获取到位置,进入跟踪模式等待回调
|
||||
[_mapView setUserTrackingMode:MAUserTrackingModeFollow animated:YES];
|
||||
@@ -508,7 +548,7 @@
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
strategy = AMapNaviDrivingStrategyMultipleDefault;//使用默认策略
|
||||
strategy = AMapNaviDrivingStrategyMultipleDefault;//使用默认策略,DRIVING_MULTIPLE_ROUTES_DEFAULT
|
||||
|
||||
id delegate = [AMapNaviDriveManager sharedInstance].delegate;
|
||||
if (!delegate) {
|
||||
@@ -780,6 +820,7 @@
|
||||
[config setNeedCalculateRouteWhenPresent:NO];//不在算路
|
||||
[config setMultipleRouteNaviMode:NO];//直接单线路径导航
|
||||
// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
|
||||
[config setShowDrivingStrategyPreferenceView:NO];
|
||||
|
||||
[self.compositeManager presentRoutePlanViewControllerWithOptions:config];
|
||||
}
|
||||
@@ -797,6 +838,8 @@
|
||||
// [config setMultipleRouteNaviMode:NO];//直接单线路径导航
|
||||
// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
|
||||
|
||||
// [config setShowDrivingStrategyPreferenceView:NO];
|
||||
|
||||
[self.compositeManager presentRoutePlanViewControllerWithOptions:config];
|
||||
}
|
||||
|
||||
@@ -978,6 +1021,7 @@
|
||||
}
|
||||
|
||||
- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view {
|
||||
self.currAnnotionView = view;
|
||||
|
||||
id pointAnnotation = view.annotation;
|
||||
if ([pointAnnotation isMemberOfClass:ACustomPointAnnotation.class]) {
|
||||
@@ -995,9 +1039,13 @@
|
||||
|
||||
[self updateUIWithData:aoi textField:self.dstTf];
|
||||
|
||||
///地图选点加氢站,直接弹出开始导航弹窗(显示时间、费用、里程、路费等),不用再回填名称
|
||||
//详情弹框
|
||||
[self willRequestTJDInfo];
|
||||
|
||||
// 同步更新底部栏目的地文字
|
||||
self.bottomBarView.destinationText = aoi.name;
|
||||
// self.bottomBarView.destinationText = aoi.name;
|
||||
self.bottomBarView.destinationText = nil;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1009,6 +1057,7 @@
|
||||
self.dstPoi = nil;
|
||||
self.dstTf.text = nil;
|
||||
self.bottomBarView.destinationText = nil;
|
||||
self.currAnnotionView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,6 +1105,7 @@
|
||||
|
||||
AMapPOI *_dstPoi = self.dstPoi ? self.dstPoi : self.defaultDstPoi;
|
||||
if (!_dstPoi) {
|
||||
[AMapNavCommonUtil showMsg:@"请先选择目的地"];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1066,9 +1116,19 @@
|
||||
navPoint.stationID = _dstPoi.uid;
|
||||
self.pointModel = navPoint;
|
||||
|
||||
if (self.stationDetailPopup) {
|
||||
[self.stationDetailPopup resetUI];
|
||||
}
|
||||
|
||||
///有_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;
|
||||
}else {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
@@ -1084,7 +1144,7 @@
|
||||
|
||||
// --- 弹出站点详情弹框 ---
|
||||
if (!self.stationDetailPopup) {
|
||||
AStationDetailPopupController *popup = [[AStationDetailPopupController alloc] init];
|
||||
AStationDetailPopupView *popup = [[AStationDetailPopupView alloc] init];
|
||||
popup.delegate = self;
|
||||
self.stationDetailPopup = popup;
|
||||
}
|
||||
@@ -1101,13 +1161,25 @@
|
||||
///油费
|
||||
self.stationDetailPopup.tollFee = self.tjdPathInfoModel.pathDto.tolls;
|
||||
|
||||
[self.stationDetailPopup presentInViewController:self];
|
||||
///新增字段信息
|
||||
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 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 - AStationDetailPopupDelegate
|
||||
#pragma mark - AStationDetailPopupViewDelegate
|
||||
|
||||
- (void)stationDetailPopupDidTapStartNavi:(AStationDetailPopupController *)popup {
|
||||
- (void)stationDetailPopupViewDidTapStartNavi:(AStationDetailPopupView *)popup {
|
||||
self.stationDetailPopup = nil;
|
||||
///点击开始导航:
|
||||
///1、有途经点,计算路线,然后再直接导航
|
||||
@@ -1126,13 +1198,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stationDetailPopupDidTapClose:(AStationDetailPopupController *)popup {
|
||||
- (void)stationDetailPopupViewDidTapClose:(AStationDetailPopupView *)popup {
|
||||
self.stationDetailPopup = nil;
|
||||
}
|
||||
|
||||
#pragma mark - ABottomBarViewDelegate
|
||||
|
||||
- (void)bottomBarViewDidTapCalRoute:(ABottomBarView *)barView {
|
||||
if (self.currAnnotionView) {
|
||||
[self.mapView deselectAnnotation:self.currAnnotionView.annotation animated:NO];
|
||||
self.currAnnotionView = nil;
|
||||
}
|
||||
|
||||
//详情弹框
|
||||
[self willRequestTJDInfo];
|
||||
@@ -1140,6 +1216,14 @@
|
||||
}
|
||||
|
||||
- (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];
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
@@ -1160,7 +1244,8 @@
|
||||
|
||||
-(void)updateUIWithData: (AMapPOI*)poi textField: (UITextField*)tf {
|
||||
BOOL isStart = tf.tag == 100;
|
||||
tf.text = poi.name;
|
||||
///不显示内容
|
||||
// tf.text = poi.name;
|
||||
|
||||
if (isStart) {
|
||||
self.startPoi = poi;
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
@property (nonatomic , strong) UIBarButtonItem *rightItem;
|
||||
@property (nonatomic ,strong)UIButton * backBtn;
|
||||
|
||||
@property (nonatomic , strong) NSArray *dataArr;
|
||||
@property (nonatomic , strong) NSMutableArray *dataArr;
|
||||
|
||||
@property (nonatomic, strong) UITextField *inputAddressTf;
|
||||
@property (nonatomic, strong) AMapSearchAPI *search;
|
||||
@property (nonatomic,assign)NSInteger currPage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASearchAddressController
|
||||
@@ -30,6 +32,9 @@
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.title = @"选择地点";
|
||||
self.currPage = 1;
|
||||
_dataArr = [NSMutableArray array];
|
||||
|
||||
|
||||
[self initSubview];
|
||||
|
||||
@@ -65,8 +70,9 @@
|
||||
|
||||
[inputAddressTf mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
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.height.mas_equalTo(@30);
|
||||
make.height.mas_equalTo(@35);
|
||||
}];
|
||||
|
||||
self.inputAddressTf = inputAddressTf;
|
||||
@@ -80,6 +86,7 @@
|
||||
btn.layer.borderWidth = 1;
|
||||
btn.layer.cornerRadius = 5;
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:12];
|
||||
btn.hidden = YES;
|
||||
|
||||
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
|
||||
[btn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
@@ -134,6 +141,14 @@
|
||||
// [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 -
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
@@ -178,28 +193,32 @@
|
||||
return;
|
||||
}
|
||||
|
||||
[self requestAddressWithAddress:addr atPage:1];
|
||||
|
||||
}
|
||||
|
||||
-(void)requestAddressWithAddress:(NSString *)addr atPage:(NSInteger)page {
|
||||
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
|
||||
|
||||
request.keywords = addr;
|
||||
|
||||
AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
|
||||
request.city = sdk.localCity;
|
||||
// request.city = sdk.localCity;
|
||||
|
||||
|
||||
// request.types = @"高等院校";
|
||||
// request.requireExtension = YES;
|
||||
request.offset =20;
|
||||
request.page = page;
|
||||
|
||||
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
|
||||
request.cityLimit = YES;
|
||||
// request.cityLimit = YES;
|
||||
// request.requireSubPOIs = YES;
|
||||
|
||||
|
||||
[self.search AMapPOIKeywordsSearch:request];
|
||||
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
/* POI 搜索回调. */
|
||||
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
|
||||
@@ -212,7 +231,8 @@
|
||||
|
||||
//解析response获取POI信息,具体解析见 Demo
|
||||
|
||||
self.dataArr = [NSArray arrayWithArray:pois];
|
||||
[self.dataArr addObjectsFromArray:pois];
|
||||
|
||||
[self.tableView reloadData];
|
||||
|
||||
}
|
||||
@@ -221,6 +241,8 @@
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
[textField resignFirstResponder];
|
||||
[self.dataArr removeAllObjects];
|
||||
self.currPage = 1;
|
||||
|
||||
[self startSearchWithAddress:textField.text];
|
||||
|
||||
|
||||
@@ -39,6 +39,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 过路费,如 @"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<AStationDetailPopupDelegate> delegate;
|
||||
|
||||
/// 以半透明蒙层方式弹出在目标控制器上
|
||||
@@ -50,6 +63,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 关闭弹框,动画结束后执行 completion(用于关闭后再 present 其他页面)
|
||||
- (void)dismissWithCompletion:(nullable void(^)(void))completion;
|
||||
|
||||
|
||||
-(void)resetUI;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -57,6 +57,21 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
/// 卡片 bottom constraint(动画用)
|
||||
@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
|
||||
|
||||
@implementation AStationDetailPopupController
|
||||
@@ -121,6 +136,26 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -168,10 +203,13 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
[card addSubview:closeBtn];
|
||||
self.closeButton = closeBtn;
|
||||
|
||||
UIColor * headTextColor = [UIColor hp_colorWithRGBHex:0x1D2129];
|
||||
UIFont * headTextFont = [UIFont hp_pingFangMedium:14];
|
||||
|
||||
// ── 站点名称 ──
|
||||
UILabel *nameLabel = [[UILabel alloc] init];
|
||||
nameLabel.font = [UIFont boldSystemFontOfSize:18];
|
||||
nameLabel.textColor = [UIColor colorWithWhite:0.1 alpha:1];
|
||||
nameLabel.font = [UIFont hp_pingFangMedium:18];
|
||||
nameLabel.textColor = headTextColor;
|
||||
nameLabel.numberOfLines = 2;
|
||||
// nameLabel.adjustsFontSizeToFitWidth = YES;
|
||||
nameLabel.minimumScaleFactor = 0.8;
|
||||
@@ -180,8 +218,8 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
|
||||
// ── 预计加氢费用(名称右侧,间距20pt) ──
|
||||
UILabel *costLabel = [[UILabel alloc] init];
|
||||
costLabel.font = [UIFont systemFontOfSize:14];
|
||||
costLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
|
||||
costLabel.font = headTextFont;
|
||||
costLabel.textColor = headTextColor;
|
||||
costLabel.numberOfLines = 1;
|
||||
costLabel.textAlignment = NSTextAlignmentLeft;
|
||||
// [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
@@ -189,10 +227,18 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
[card addSubview: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];
|
||||
addrLabel.font = [UIFont systemFontOfSize:13];
|
||||
addrLabel.textColor = [UIColor colorWithWhite:0.5 alpha:1];
|
||||
addrLabel.font = [UIFont hp_pingFangRegular:14];
|
||||
addrLabel.textColor = [UIColor hp_colorWithRGBHex:0x86909C];
|
||||
addrLabel.numberOfLines = 2;
|
||||
[card addSubview:addrLabel];
|
||||
self.addressLabel = addrLabel;
|
||||
@@ -212,45 +258,84 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
// ── 预计时间图标 ──
|
||||
UIImageView *timeIcon = [[UIImageView alloc] init];
|
||||
timeIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_time_icon"];
|
||||
timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_time"];
|
||||
[card addSubview:timeIcon];
|
||||
self.timeIconView = timeIcon;
|
||||
|
||||
// ── 预计时间文字 ──
|
||||
UILabel *timeLabel = [[UILabel alloc] init];
|
||||
timeLabel.font = [UIFont systemFontOfSize:14];
|
||||
timeLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
|
||||
timeLabel.font = headTextFont;
|
||||
timeLabel.textColor = headTextColor;
|
||||
[card addSubview:timeLabel];
|
||||
self.timeLabel = timeLabel;
|
||||
|
||||
// ── 行驶里程图标 ──
|
||||
UIImageView *distIcon = [[UIImageView alloc] init];
|
||||
distIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
distIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_distance_icon"];
|
||||
distIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_mileage"];
|
||||
[card addSubview:distIcon];
|
||||
self.distanceIconView = distIcon;
|
||||
|
||||
// ── 行驶里程文字 ──
|
||||
UILabel *distLabel = [[UILabel alloc] init];
|
||||
distLabel.font = [UIFont systemFontOfSize:14];
|
||||
distLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
|
||||
distLabel.font = headTextFont;
|
||||
distLabel.textColor = headTextColor;
|
||||
[card addSubview:distLabel];
|
||||
self.distanceLabel = distLabel;
|
||||
|
||||
// ── 过路费图标 ──
|
||||
UIImageView *tollIcon = [[UIImageView alloc] init];
|
||||
tollIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_cost_icon"];
|
||||
tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_toll"];
|
||||
[card addSubview:tollIcon];
|
||||
self.tollIconView = tollIcon;
|
||||
|
||||
// ── 过路费文字 ──
|
||||
UILabel *tollLabel = [[UILabel alloc] init];
|
||||
tollLabel.font = [UIFont systemFontOfSize:14];
|
||||
tollLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
|
||||
tollLabel.font = headTextFont;
|
||||
tollLabel.textColor = headTextColor;
|
||||
[card addSubview: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];
|
||||
[naviBtn setTitle:@"开始导航" forState:UIControlStateNormal];
|
||||
@@ -301,24 +386,34 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
make.right.equalTo(self.closeButton.mas_left).offset(-12);
|
||||
}];
|
||||
|
||||
// ── 地址(名称下方,6pt间距) ──
|
||||
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// ── 营业时间(站点名称下方,6pt间距) ──
|
||||
[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);
|
||||
}];
|
||||
|
||||
// ── 地址(营业时间下方,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间距) ──
|
||||
[self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.addressLabel.mas_bottom).offset(15);
|
||||
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;
|
||||
|
||||
// ── 预计时间行(分割线下方,14pt) ──
|
||||
[self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separator.mas_bottom).offset(15);
|
||||
make.top.equalTo(self.separator.mas_bottom).offset(12);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
@@ -333,7 +428,7 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
///cost
|
||||
[self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.timeIconView);
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(30);
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(40);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
@@ -346,7 +441,7 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
|
||||
// ── 行驶里程 + 过路费行(时间行下方,12pt) ──
|
||||
[self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.timeIconView.mas_bottom).offset(15);
|
||||
make.top.equalTo(self.timeIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
@@ -372,9 +467,49 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 开始导航按钮(里程行下方18pt,距卡片底部30pt) ──
|
||||
// ── 站点联系人行(过路费行下方,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);
|
||||
}];
|
||||
|
||||
// ── 加氢价格(与站点联系人同行,右侧对齐 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.distanceIconView.mas_bottom).offset(50);
|
||||
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);
|
||||
@@ -386,29 +521,63 @@ static inline UIColor *AStationThemeGreen(void) {
|
||||
|
||||
- (void)_updateUI {
|
||||
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)
|
||||
? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost]
|
||||
: @"预计加氢费用:--元";
|
||||
: @"预计加氢费用:-";
|
||||
|
||||
self.addressLabel.text = (self.pointModel.address.length > 0)
|
||||
? self.pointModel.address : @"--";
|
||||
? self.pointModel.address : @"-";
|
||||
|
||||
// ── 预计时间(始终显示,无值显示"-- 分钟") ──
|
||||
self.timeLabel.text = (self.estimatedTime.length > 0)
|
||||
? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime]
|
||||
: @"预计时间:--分钟";
|
||||
: @"预计时间:-";
|
||||
|
||||
// ── 行驶里程(始终显示,无值显示"-- 公里") ──
|
||||
self.distanceLabel.text = (self.driveDistance.length > 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]
|
||||
: @"联系方式:-";
|
||||
}
|
||||
|
||||
// 重置 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
|
||||
|
||||
@@ -59,6 +59,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||
@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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "UIColor+ANavMap.h"
|
||||
#import "UIFont+HP.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -34,6 +36,8 @@ BOOL stringIsEmpty(NSString *str);
|
||||
/// 判断字符串是否非空
|
||||
BOOL stringIsNotEmpty(NSString *str);
|
||||
|
||||
+(NSString *)stringValueFromStr:(NSString *)str;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -117,6 +117,15 @@ BOOL stringIsNotEmpty (NSString *str)
|
||||
return ! stringIsEmpty(str);
|
||||
}
|
||||
|
||||
/// 字符串转数值
|
||||
+(NSString *)stringValueFromStr:(NSString *)str {
|
||||
if (stringIsEmpty(str)) {
|
||||
return @"";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - 获取图片
|
||||
+(UIImage *)imageWithName:(NSString *)name {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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
|
||||
@@ -163,6 +163,7 @@ static inline UIColor *ABottomBarThemeGreen(void) {
|
||||
self.calRouteButton = btn;
|
||||
|
||||
CGFloat off_y = AMP_TabbarHeight;
|
||||
off_y = 0;
|
||||
#ifdef kAMapSDKDebugFlag
|
||||
off_y = 0;
|
||||
#endif
|
||||
@@ -172,7 +173,7 @@ static inline UIColor *ABottomBarThemeGreen(void) {
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(48);
|
||||
make.bottom.equalTo(card).offset(-40 - off_y);
|
||||
make.bottom.equalTo(card).offset(-40 + (-AMP_TabbarHeight - 13));
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,707 @@
|
||||
//
|
||||
// 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
|
||||
@@ -26,8 +26,6 @@ PODS:
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_update (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
@@ -47,16 +45,14 @@ PODS:
|
||||
- Masonry (1.1.0)
|
||||
- MBProgressHUD (1.2.0)
|
||||
- MJExtension (3.4.2)
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- OrderedSet (6.0.3)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- saver_gallery (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -69,16 +65,14 @@ DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- 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`)
|
||||
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
@@ -109,8 +103,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_app_update:
|
||||
:path: ".symlinks/plugins/flutter_app_update/ios"
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_native_splash:
|
||||
@@ -121,14 +113,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
saver_gallery:
|
||||
:path: ".symlinks/plugins/saver_gallery/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
url_launcher_ios:
|
||||
@@ -138,31 +128,29 @@ SPEC CHECKSUMS:
|
||||
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
|
||||
AlicloudPush: 52cbf38ffc20c07f039cbc72d5738745fd986215
|
||||
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
|
||||
aliyun_push_flutter: 0fc2f048a08687ef256c0cfdd72dd7a550ef3347
|
||||
aliyun_push_flutter: ab0bf7112ef3797f506770a7a9f47f004635a9f6
|
||||
AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
|
||||
AMapLocation-NO-IDFA: 590fd42af0c8ea9eac26978348221bbc16be4ef9
|
||||
AMapNavi-NO-IDFA: 22edfa7d6a81d75c91756e31b6c26b7746152233
|
||||
AMapNavIOSSDK: e06adcb48ac8abeace46ea31f72191564e54a186
|
||||
AMapNavIOSSDK: 092382d55290f43b282ffcc522c274996794e2bc
|
||||
AMapSearch-NO-IDFA: 53b2193244be8f07f3be0a4d5161200236960587
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_pdfview: 2e4d13ffb774858562ffbdfdb61b40744b191adc
|
||||
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
|
||||
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
|
||||
MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||
|
||||
PODFILE CHECKSUM: b4931d4490f04261e0fda802d44e275ab3619244
|
||||
|
||||
|
||||
@@ -508,6 +508,8 @@
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
@@ -543,6 +545,8 @@
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
@@ -768,6 +772,8 @@
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
@@ -803,6 +809,8 @@
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
@@ -865,6 +873,8 @@
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
|
||||
@@ -76,26 +76,5 @@
|
||||
</array>
|
||||
<key>uses</key>
|
||||
<string></string>
|
||||
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>zh-Hans</string>
|
||||
<string>en</string>
|
||||
</array>
|
||||
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<!-- 允许在“文件”App中直接打开文档 -->
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,7 @@ class StationModel {
|
||||
final String address;
|
||||
final String price;
|
||||
final String siteStatusName; // 例如 "维修中"
|
||||
final int isSelect; // 1是可用 0是不可用
|
||||
final String startBusiness; // 新增:可预约最早开始时间,如 "06:00:00"
|
||||
final String endBusiness; // 新增:可预约最晚结束时间,如 "22:00:00"
|
||||
final int isSelect; // 新增字段 1是可用 0是不可用
|
||||
|
||||
StationModel({
|
||||
required this.hydrogenId,
|
||||
@@ -15,10 +13,9 @@ class StationModel {
|
||||
required this.price,
|
||||
required this.siteStatusName,
|
||||
required this.isSelect,
|
||||
required this.startBusiness,
|
||||
required this.endBusiness,
|
||||
});
|
||||
|
||||
// 从 JSON map 创建对象的工厂构造函数
|
||||
factory StationModel.fromJson(Map<String, dynamic> json) {
|
||||
return StationModel(
|
||||
hydrogenId: json['hydrogenId'] ?? '',
|
||||
@@ -26,9 +23,7 @@ class StationModel {
|
||||
address: json['address'] ?? '地址未知',
|
||||
price: json['price']?.toString() ?? '0.00',
|
||||
siteStatusName: json['siteStatusName'] ?? '',
|
||||
isSelect: json['isSelect'] as int? ?? 0,
|
||||
startBusiness: json['startBusiness'] ?? '00:00:00', // 默认全天
|
||||
endBusiness: json['endBusiness'] ?? '23:59:59', // 默认全天
|
||||
isSelect: json['isSelect'] as int? ?? 0, // 新增字段的解析,默认为 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class AppTheme {
|
||||
static const Color themeColor = Color(0xFF017137);
|
||||
|
||||
//是否开放域名切换
|
||||
static const bool is_show_host = true;
|
||||
static const bool is_show_host = false;
|
||||
|
||||
//http://192.168.110.222:8080/
|
||||
//http://192.168.110.44:8080/
|
||||
|
||||
@@ -24,6 +24,7 @@ class TokenInterceptor extends Interceptor {
|
||||
if (!options.headers.containsKey(tokenKey)) {
|
||||
// 使用我们自定义的 key 添加 token
|
||||
options.headers[tokenKey] = token;
|
||||
print("head.token: $token");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:get_storage/get_storage.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/token_interceptor.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
@@ -66,7 +65,6 @@ void main() async {
|
||||
void initHttpSet() {
|
||||
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.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
||||
HttpService.to.setOnResponseHandler((response) async {
|
||||
@@ -78,7 +76,8 @@ void initHttpSet() {
|
||||
if (baseModel.code == 0 || baseModel.code == 200) {
|
||||
return null;
|
||||
} else if (baseModel.code == 401) {
|
||||
await AuthGuard.handle401(baseModel.message);
|
||||
await StorageService.to.clearLoginInfo();
|
||||
Get.offAll(() => const LoginPage());
|
||||
return baseModel.message;
|
||||
} else {
|
||||
return (baseModel.error.toString()).isEmpty
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||
|
||||
class HistoryController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
String get builderId => 'history';
|
||||
|
||||
class HistoryController extends GetxController {
|
||||
// --- 定义 API 需要的日期格式化器 ---
|
||||
final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
@@ -14,8 +13,8 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
final Rx<DateTime> endDate = DateTime.now().obs;
|
||||
final TextEditingController plateNumberController = TextEditingController();
|
||||
|
||||
final RxString totalHydrogen = '0'.obs;
|
||||
final RxString totalCompletions = '0'.obs;
|
||||
final RxString totalHydrogen = '0 kg'.obs;
|
||||
final RxString totalCompletions = '0 次'.obs;
|
||||
|
||||
final RxList<ReservationModel> historyList = <ReservationModel>[].obs;
|
||||
final RxBool isLoading = true.obs;
|
||||
@@ -24,31 +23,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
||||
|
||||
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
||||
|
||||
String stationName = "";
|
||||
|
||||
final Map<String, String> statusOptions = {
|
||||
'': '全部',
|
||||
'100': '未预约加氢',
|
||||
'0': '待加氢',
|
||||
'1': '已加氢',
|
||||
'2': '未加氢',
|
||||
'5': '拒绝加氢',
|
||||
};
|
||||
|
||||
final RxString selectedStatus = ''.obs;
|
||||
final RxString selectedDateType = ''.obs; // week, month, three_month
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
final args = Get.arguments as Map<String, dynamic>;
|
||||
stationName = args['stationName'] as String? ?? "";
|
||||
refreshData();
|
||||
}
|
||||
|
||||
void refreshData() {
|
||||
getAllOrderCounts();
|
||||
final args = Get.arguments as Map<String, dynamic>;
|
||||
stationName = args['stationName'] as String;
|
||||
fetchHistoryData();
|
||||
}
|
||||
|
||||
@@ -56,50 +38,51 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
var response = await HttpService.to.post(
|
||||
"appointment/orderAddHyd/getAllOrderCounts",
|
||||
data: {
|
||||
/*'startTime': _apiDateFormat.format(startDate.value),
|
||||
'endTime': _apiDateFormat.format(endDate.value),*/
|
||||
// --- 直接使用 DateFormat 来格式化日期 ---
|
||||
'startTime': _apiDateFormat.format(startDate.value),
|
||||
'endTime': _apiDateFormat.format(endDate.value),
|
||||
'plateNumber': plateNumberController.text,
|
||||
'stationName': stationName,
|
||||
"status": selectedStatus.value,
|
||||
"dateType": selectedDateType.value,
|
||||
'stationName': stationName, // 加氢站名称
|
||||
},
|
||||
);
|
||||
if (response == null || response.data == null) {
|
||||
totalHydrogen.value = '0';
|
||||
totalCompletions.value = '0';
|
||||
totalHydrogen.value = '0 kg';
|
||||
totalCompletions.value = '0 次';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0}';
|
||||
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}';
|
||||
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0} kg';
|
||||
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0} 次';
|
||||
} catch (e) {
|
||||
totalHydrogen.value = '0';
|
||||
totalCompletions.value = '0';
|
||||
totalHydrogen.value = '0 kg';
|
||||
totalCompletions.value = '0 次';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchHistoryData() async {
|
||||
isLoading.value = true;
|
||||
updateUi();
|
||||
|
||||
//获取数据
|
||||
getAllOrderCounts();
|
||||
|
||||
try {
|
||||
var response = await HttpService.to.post(
|
||||
"appointment/orderAddHyd/sitOrderPage",
|
||||
data: {
|
||||
/*'startTime': _apiDateFormat.format(startDate.value),
|
||||
'endTime': _apiDateFormat.format(endDate.value),*/
|
||||
// --- 直接使用 DateFormat 来格式化日期 ---
|
||||
'startTime': _apiDateFormat.format(startDate.value),
|
||||
'endTime': _apiDateFormat.format(endDate.value),
|
||||
'plateNumber': plateNumberController.text,
|
||||
'pageNum': 1,
|
||||
'pageSize': 50,
|
||||
'stationName': stationName,
|
||||
"status": selectedStatus.value,
|
||||
"dateType": selectedDateType.value,
|
||||
'stationName': stationName, // 加氢站名称
|
||||
},
|
||||
);
|
||||
|
||||
if (response == null || response.data == null) {
|
||||
showToast('无法获取历史记录');
|
||||
_resetData();
|
||||
return;
|
||||
}
|
||||
@@ -107,6 +90,7 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||||
if (baseModel.code == 0 && baseModel.data != null) {
|
||||
final dataMap = baseModel.data as Map<String, dynamic>;
|
||||
|
||||
final List<dynamic> listFromServer = dataMap['records'] ?? [];
|
||||
historyList.assignAll(
|
||||
listFromServer
|
||||
@@ -115,13 +99,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
);
|
||||
hasData.value = historyList.isNotEmpty;
|
||||
} else {
|
||||
showToast(baseModel.message);
|
||||
_resetData();
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('获取历史记录失败: $e');
|
||||
_resetData();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,15 +115,97 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
hasData.value = false;
|
||||
}
|
||||
|
||||
void onStatusSelected(String status) {
|
||||
if (selectedStatus.value == status) return;
|
||||
selectedStatus.value = status;
|
||||
refreshData();
|
||||
}
|
||||
void pickDate(BuildContext context, bool isStartDate) {
|
||||
// 确定当前操作的日期和临时存储变量
|
||||
final DateTime initialDate = isStartDate ? startDate.value : endDate.value;
|
||||
DateTime tempDate = initialDate;
|
||||
|
||||
void onDateTypeSelected(String type) {
|
||||
selectedDateType.value = type;
|
||||
refreshData();
|
||||
// 定义全局的最早可选日期
|
||||
final DateTime globalMinimumDate = DateTime(2025, 12, 1);
|
||||
|
||||
// 动态计算当前选择器的最小/最大日期范围
|
||||
DateTime minimumDate;
|
||||
DateTime? maximumDate; // 声明为可空,因为两个日期都可能没有最大限制
|
||||
|
||||
if (isStartDate) {
|
||||
// 当选择【开始日期】时 它的最小日期就是全局最小日期
|
||||
minimumDate = globalMinimumDate;
|
||||
// 最大日期没有限制
|
||||
maximumDate = null;
|
||||
} else {
|
||||
// 当选择【结束日期】时 它的最小日期不能早于当前的开始日期
|
||||
minimumDate = startDate.value;
|
||||
// 确认结束日期没有最大限制 ---
|
||||
//最大日期没有限制
|
||||
maximumDate = null;
|
||||
}
|
||||
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: 300,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 顶部的取消和确认按钮
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消', style: TextStyle(color: Colors.grey)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// 4. 确认后,更新对应的日期变量
|
||||
if (isStartDate) {
|
||||
startDate.value = tempDate;
|
||||
// 如果新的开始日期晚于结束日期,自动将结束日期调整为同一天
|
||||
if (tempDate.isAfter(endDate.value)) {
|
||||
endDate.value = tempDate;
|
||||
}
|
||||
} else {
|
||||
endDate.value = tempDate;
|
||||
}
|
||||
Get.back();
|
||||
|
||||
// 选择日期后自动刷新数据
|
||||
fetchHistoryData();
|
||||
},
|
||||
child: const Text(
|
||||
'确认',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
// 日期选择器
|
||||
Expanded(
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.date,
|
||||
initialDateTime: initialDate,
|
||||
// 应用动态计算好的最小/最大日期
|
||||
minimumDate: minimumDate,
|
||||
maximumDate: maximumDate,
|
||||
onDateTimeChanged: (DateTime newDate) {
|
||||
tempDate = newDate;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent, // 使底部工作表外的区域透明
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,173 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/history/controller.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||
|
||||
class HistoryPage extends GetView<HistoryController> {
|
||||
const HistoryPage({super.key});
|
||||
const HistoryPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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()),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Get.put(HistoryController());
|
||||
|
||||
Widget _buildSearchBox() {
|
||||
return Container(
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF2F3F5),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller.plateNumberController,
|
||||
onSubmitted: (v) => controller.refreshData(),
|
||||
decoration: const InputDecoration(
|
||||
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),
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('历史记录'), centerTitle: true),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildFilterCard(context),
|
||||
const SizedBox(height: 12),
|
||||
_buildSummaryCard(),
|
||||
const SizedBox(height: 12),
|
||||
_buildListHeader(),
|
||||
Expanded(child: _buildHistoryList()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterBar() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: controller.statusOptions.entries.map((entry) {
|
||||
return Obx(() {
|
||||
bool isSelected = controller.selectedStatus.value == entry.key;
|
||||
return GestureDetector(
|
||||
onTap: () => controller.onStatusSelected(entry.key),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF006633) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Text(
|
||||
entry.value,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? Colors.white
|
||||
: Color.fromRGBO(148, 163, 184, 1),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}).toList(),
|
||||
Widget _buildFilterCard(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('时间范围', style: TextStyle(fontSize: 14, color: Colors.grey)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildDateField(context, true)),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text('至'),
|
||||
),
|
||||
Expanded(child: _buildDateField(context, false)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text('车牌号', style: TextStyle(fontSize: 14, color: Colors.grey)),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 44,
|
||||
child: TextField(
|
||||
controller: controller.plateNumberController,
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入车牌号',
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildTimeFilterIcon(),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
FocusScope.of(context).unfocus(); // Hide keyboard
|
||||
controller.fetchHistoryData();
|
||||
},
|
||||
icon: const Icon(Icons.search, size: 20),
|
||||
label: const Text('查询'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 44),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _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() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16,bottom: 12),
|
||||
padding: const EdgeInsets.all(20),
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/history_bg.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
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,
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Obx(
|
||||
() => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildSummaryItem('实际加氢总量', controller.totalHydrogen.value, Colors.blue),
|
||||
const SizedBox(width: 1, height: 40, child: VerticalDivider()),
|
||||
_buildSummaryItem(
|
||||
'预约完成次数',
|
||||
controller.totalCompletions.value,
|
||||
Colors.green,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,138 +110,143 @@ class HistoryPage extends GetView<HistoryController> {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (controller.historyList.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('暂无相关记录', style: TextStyle(color: Color(0xFF999999))),
|
||||
);
|
||||
if (!controller.hasData.value) {
|
||||
return const Center(child: Text('没有找到相关记录'));
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: controller.historyList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildHistoryItem(controller.historyList[index]);
|
||||
final ReservationModel item = controller.historyList[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
title: Text('车牌号: ${item.plateNumber}'),
|
||||
subtitle: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '加氢站: ${item.stationName}\n',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
TextSpan(
|
||||
text: '时间: ${item.time}\n',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
TextSpan(
|
||||
text: '加氢量:',
|
||||
),
|
||||
TextSpan(
|
||||
text: '${item.amount}',
|
||||
style: TextStyle(fontSize: 16, color: AppTheme.themeColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
,
|
||||
trailing:
|
||||
// 状态标签
|
||||
_buildStatusChip(item.status),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildHistoryItem(ReservationModel item) {
|
||||
return Container(
|
||||
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;
|
||||
|
||||
Widget _buildStatusChip(ReservationStatus status) {
|
||||
String text;
|
||||
Color color;
|
||||
switch (status) {
|
||||
case ReservationStatus.pending:
|
||||
text = '待加氢';
|
||||
bgColor = const Color(0xFFFFF7E8);
|
||||
textColor = const Color(0xFFFF9800);
|
||||
color = Colors.orange;
|
||||
break;
|
||||
case ReservationStatus.completed:
|
||||
text = '已加氢';
|
||||
bgColor = const Color(0xFFE8F5E9);
|
||||
textColor = const Color(0xFF4CAF50);
|
||||
color = Colors.greenAccent;
|
||||
break;
|
||||
case ReservationStatus.rejected:
|
||||
text = '拒绝加氢';
|
||||
bgColor = const Color(0xFFFFEBEE);
|
||||
textColor = const Color(0xFFF44336);
|
||||
color = Colors.red;
|
||||
break;
|
||||
case ReservationStatus.unadded:
|
||||
text = '未加氢';
|
||||
bgColor = const Color(0xFFFFEBEE);
|
||||
textColor = const Color(0xFFF44336);
|
||||
color = Colors.red;
|
||||
break;
|
||||
case ReservationStatus.cancel:
|
||||
text = '已取消';
|
||||
bgColor = const Color(0xFFFFEBEE);
|
||||
textColor = const Color(0xFFF44336);
|
||||
color = Colors.red;
|
||||
break;
|
||||
default:
|
||||
text = '未知状态';
|
||||
bgColor = Colors.grey;
|
||||
textColor = Colors.grey;
|
||||
color = Colors.grey;
|
||||
break;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: textColor.withOpacity(0.3)),
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.circle, color: color, size: 8),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateField(BuildContext context, bool isStart) {
|
||||
return Obx(
|
||||
() => InkWell(
|
||||
onTap: () => controller.pickDate(context, isStart),
|
||||
child: Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade400),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(isStart ? controller.formattedStartDate : controller.formattedEndDate),
|
||||
const Icon(Icons.calendar_today, size: 18, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryItem(String label, String value, Color color) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(color: color, fontSize: 22, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListHeader() {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 14.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('加氢明细', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/reservation/controller.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:ln_jq_app/pages/common/webview/view.dart';
|
||||
|
||||
class ReservationPage extends GetView<ReservationController> {
|
||||
const ReservationPage({super.key});
|
||||
@@ -40,31 +36,7 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
_buildSystemTips(),
|
||||
SizedBox(height: 24),
|
||||
_buildLogoutButton(),
|
||||
SizedBox(height: 15.h),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '《用户协议》',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
openPage("用户协议", "https://lnh2e.com/user_agreement.html");
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: '《隐私政策》',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 95.h),
|
||||
SizedBox(height: 75.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -76,14 +48,6 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
);
|
||||
}
|
||||
|
||||
void openPage(String title, String url) {
|
||||
if (Platform.isIOS) {
|
||||
openWebPage(url);
|
||||
return;
|
||||
}
|
||||
Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url});
|
||||
}
|
||||
|
||||
/// 1. 顶部个人信息及统计栏
|
||||
Widget _buildTopSection(BuildContext context) {
|
||||
return Container(
|
||||
@@ -114,15 +78,11 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
controller.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
Text(
|
||||
controller.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -138,11 +98,12 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
onPressed: () async{
|
||||
var scanResult = await Get.to(() => const MessagePage());
|
||||
if (scanResult == null) {
|
||||
controller.msgNotice();
|
||||
}
|
||||
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.grey[100],
|
||||
@@ -150,7 +111,9 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
),
|
||||
icon: Badge(
|
||||
smallSize: 8,
|
||||
backgroundColor: controller.isNotice ? Colors.red : Colors.transparent,
|
||||
backgroundColor: controller.isNotice
|
||||
? Colors.red
|
||||
: Colors.transparent,
|
||||
child: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Colors.black87,
|
||||
@@ -269,17 +232,12 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
label,
|
||||
style: TextStyle(color: Colors.grey, fontSize: 11.sp),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.right,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: const Color(0xFF333333),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF333333),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -557,15 +515,14 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: kPrimaryColor,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
child: const Text("发送广播", style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/history/view.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
@@ -56,33 +57,17 @@ class SitePage extends GetView<SiteController> {
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// 手动录入
|
||||
controller.confirmReservation("", isAdd: true);
|
||||
Get.to(
|
||||
() => HistoryPage(),
|
||||
arguments: {'stationName': controller.name},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: const Color(0xFFEEEEEE)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.add_circle_outline,
|
||||
size: 18,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"无预约车辆加氢",
|
||||
style: TextStyle(
|
||||
color: Color.fromRGBO(51, 51, 51, 1),
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w400
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Text(
|
||||
'历史记录',
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromRGBO(156, 163, 175, 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -93,12 +78,12 @@ class SitePage extends GetView<SiteController> {
|
||||
Column(
|
||||
children: [
|
||||
_buildSearchView(),
|
||||
SizedBox(height: 15.h),
|
||||
controller.hasReservationData
|
||||
? _buildReservationListView()
|
||||
: _buildEmptyReservationView(),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 35.h),
|
||||
//第三部分
|
||||
Container(
|
||||
@@ -151,7 +136,7 @@ class SitePage extends GetView<SiteController> {
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 105.h),
|
||||
SizedBox(height: 75.h),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -200,7 +185,27 @@ class SitePage extends GetView<SiteController> {
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildDropdownMenu(),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
var scanResult = await Get.to(() => const MessagePage());
|
||||
if (scanResult == null) {
|
||||
controller.msgNotice();
|
||||
}
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.grey[100],
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
icon: Badge(
|
||||
smallSize: 8,
|
||||
backgroundColor: controller.isNotice ? Colors.red : Colors.transparent,
|
||||
child: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Colors.black87,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
@@ -228,40 +233,6 @@ class SitePage extends GetView<SiteController> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownMenu() {
|
||||
return PopupMenuButton<String>(
|
||||
icon: Container(child: const Icon(Icons.grid_view_rounded, size: 24)),
|
||||
onSelected: (value) async {
|
||||
if (value == 'message') {
|
||||
var scanResult = await Get.to(() => const MessagePage());
|
||||
if (scanResult == null) {
|
||||
controller.msgNotice();
|
||||
}
|
||||
} else if (value == 'history') {
|
||||
Get.to(() => const HistoryPage(), arguments: {'stationName': controller.name});
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
const PopupMenuItem(
|
||||
value: 'message',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.notifications_none, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Text('消息中心'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'history',
|
||||
child: Row(
|
||||
children: [Icon(Icons.history, size: 20), SizedBox(width: 8), Text('加氢历史')],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatBox(String title, String enTitle, String value, String unit) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
@@ -420,9 +391,8 @@ class SitePage extends GetView<SiteController> {
|
||||
|
||||
/// 构建“有预约数据”的列表视图
|
||||
Widget _buildReservationListView() {
|
||||
return ListView.builder(
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// 因为外层已有滚动,这里禁用内部滚动
|
||||
itemCount: controller.reservationList.length,
|
||||
@@ -431,6 +401,7 @@ class SitePage extends GetView<SiteController> {
|
||||
// 调用新的方法来构建每一项
|
||||
return _buildReservationItem(index, item);
|
||||
},
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 0), // 列表项之间的间距
|
||||
);
|
||||
}
|
||||
|
||||
@@ -495,7 +466,7 @@ class SitePage extends GetView<SiteController> {
|
||||
/// 右侧具体数据卡片
|
||||
Widget _buildInfoCard(ReservationModel item) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: EdgeInsets.only(left: 16.w, top: 8.5, bottom: 8.5, right: 16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -537,82 +508,59 @@ class SitePage extends GetView<SiteController> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 联系信息
|
||||
Text(
|
||||
item.contactPerson.isEmpty || item.contactPhone.isEmpty
|
||||
? ""
|
||||
: "${item.contactPerson} | ${item.contactPhone}",
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (item.hasDrivingAttachment)
|
||||
controller.buildInfoTag('行驶证',item.drivingAttachments),
|
||||
if (item.hasHydrogenationAttachment) ...[
|
||||
SizedBox(width: 8.w),
|
||||
controller.buildInfoTag('加氢证',item.hydrogenationAttachments)
|
||||
],
|
||||
Spacer(),
|
||||
if (item.isEdit == "1") ...[
|
||||
const SizedBox(height: 15),
|
||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: _buildSmallButton(
|
||||
"修改信息",
|
||||
isOutline: true,
|
||||
onTap: () {
|
||||
controller.confirmReservation(item.id, isEdit: true);
|
||||
},
|
||||
),
|
||||
Text(
|
||||
"${item.contactPerson} | ${item.contactPhone}",
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
] else if (item.status == ReservationStatus.pending) ...[
|
||||
const SizedBox(height: 15),
|
||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildSmallButton(
|
||||
"拒绝",
|
||||
isOutline: true,
|
||||
onTap: () {
|
||||
controller.rejectReservation(item.id);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildSmallButton(
|
||||
"确认",
|
||||
isOutline: false,
|
||||
onTap: () {
|
||||
controller.confirmReservation(item.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
//操作按钮(仅在待处理状态显示)
|
||||
if (item.status == ReservationStatus.pending) ...[
|
||||
const SizedBox(height: 15),
|
||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildSmallButton(
|
||||
"拒绝",
|
||||
isOutline: true,
|
||||
onTap: () {
|
||||
controller.rejectReservation(item.id);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildSmallButton(
|
||||
"确认",
|
||||
isOutline: false,
|
||||
onTap: () {
|
||||
controller.confirmReservation(item.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// 通用小按钮
|
||||
Widget _buildSmallButton(
|
||||
String text, {
|
||||
required bool isOutline,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
const kPrimaryGreen = Color(0xFF006D35);
|
||||
var kDangerRed = text.contains('修改') ? Colors.red : Color.fromRGBO(255, 142, 98, 1);
|
||||
const kDangerRed = Color(0xFFFF7D7D);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
@@ -686,4 +634,139 @@ class SitePage extends GetView<SiteController> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 右侧操作按钮(拒绝/确认)
|
||||
Widget _buildActionButtons(ReservationModel item) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 拒绝按钮(空心)
|
||||
GestureDetector(
|
||||
onTap: () => controller.rejectReservation(item.id),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFFFF7D7D)),
|
||||
),
|
||||
child: const Text(
|
||||
"拒绝",
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF7D7D),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// 确认按钮(实心深绿)
|
||||
GestureDetector(
|
||||
onTap: () => controller.confirmReservation(item.id),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF006D35),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Text(
|
||||
"确认",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建状态标签
|
||||
Widget _buildStatusChip(ReservationStatus status) {
|
||||
String text;
|
||||
Color color;
|
||||
switch (status) {
|
||||
case ReservationStatus.pending:
|
||||
text = '待加氢';
|
||||
color = Colors.orange;
|
||||
break;
|
||||
case ReservationStatus.completed:
|
||||
text = '已加氢';
|
||||
color = Colors.greenAccent;
|
||||
break;
|
||||
case ReservationStatus.rejected:
|
||||
text = '拒绝加氢';
|
||||
color = Colors.red;
|
||||
break;
|
||||
case ReservationStatus.unadded:
|
||||
text = '未加氢';
|
||||
color = Colors.red;
|
||||
break;
|
||||
case ReservationStatus.cancel:
|
||||
text = '已取消';
|
||||
color = Colors.red;
|
||||
break;
|
||||
default:
|
||||
text = '未知状态';
|
||||
color = Colors.grey;
|
||||
break;
|
||||
}
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.circle, color: color, size: 8),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建信息详情行
|
||||
Widget _buildDetailRow(
|
||||
IconData icon,
|
||||
String label,
|
||||
String value, {
|
||||
Color valueColor = Colors.black87,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, color: Colors.grey, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text('$label: ', style: const TextStyle(fontSize: 14, color: Colors.grey)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: valueColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 底部构建带图标的提示信息行
|
||||
Widget _buildInfoItem(IconData icon, String text) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, color: Colors.blue, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(text, style: const TextStyle(fontSize: 14, color: Colors.black54)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,45 +5,21 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:getx_scaffold/common/common.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
|
||||
/// 原生地图页面
|
||||
class NativePageIOS extends StatefulWidget {
|
||||
class NativePageIOS extends StatelessWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
if (Platform.isIOS) {
|
||||
return _buildIOSView(context);
|
||||
} else if (Platform.isAndroid) {
|
||||
// 如果安卓权限还没处理完,显示加载中,不加载原生 View
|
||||
if (!_androidPermissionReady) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: Color(0xFF017137)),
|
||||
);
|
||||
}
|
||||
return _buildAndroidView(context);
|
||||
} else {
|
||||
return const Center(child: Text('不支持的平台', style: TextStyle(fontSize: 16)));
|
||||
return const Center(
|
||||
child: Text('不支持的平台', style: TextStyle(fontSize: 16)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +30,7 @@ class _NativePageIOSState extends State<NativePageIOS> {
|
||||
height: MediaQuery.of(context).size.height - 100,
|
||||
color: Colors.white,
|
||||
child: UiKitView(
|
||||
viewType: 'NativeFirstPage',
|
||||
// 与iOS原生端注册的标识一致
|
||||
viewType: 'NativeFirstPage', // 与iOS原生端注册的标识一致
|
||||
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{}.toSet(),
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
@@ -82,16 +57,15 @@ class _NativePageIOSState extends State<NativePageIOS> {
|
||||
onCreatePlatformView: (params) {
|
||||
// 使用 initSurfaceAndroidView 强制开启 Hybrid Composition
|
||||
return PlatformViewsService.initSurfaceAndroidView(
|
||||
id: params.id,
|
||||
viewType: 'NativeFirstPage',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: {},
|
||||
// 你的参数
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onFocus: () {
|
||||
params.onFocusChanged(true);
|
||||
},
|
||||
)
|
||||
id: params.id,
|
||||
viewType: 'NativeFirstPage',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: {}, // 你的参数
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
onFocus: () {
|
||||
params.onFocusChanged(true);
|
||||
},
|
||||
)
|
||||
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
|
||||
..create();
|
||||
},
|
||||
@@ -99,31 +73,28 @@ class _NativePageIOSState extends State<NativePageIOS> {
|
||||
);
|
||||
}
|
||||
|
||||
void requestAndroidPermission() async {
|
||||
try {
|
||||
final deviceInfo = await DeviceInfoPlugin().androidInfo;
|
||||
final sdkInt = deviceInfo.version.sdkInt;
|
||||
|
||||
List<Permission> permissions = [Permission.location];
|
||||
|
||||
if (sdkInt < 33) {
|
||||
permissions.add(Permission.storage);
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
Map<Permission, PermissionStatus> statuses = await permissions.request();
|
||||
|
||||
if (statuses[Permission.location]?.isDenied ?? false) {
|
||||
Toast.show("定位权限被拒绝,会影响相关功能使用");
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("权限申请异常: $e");
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_androidPermissionReady = true;
|
||||
});
|
||||
}
|
||||
/// 处理点击事件(如需要)
|
||||
void _handleTap(BuildContext context) {
|
||||
if (kDebugMode) {
|
||||
print("NativePage被点击");
|
||||
}
|
||||
_showDialog(context, '提示', '点击了原生地图页面');
|
||||
}
|
||||
|
||||
/// 显示对话框
|
||||
void _showDialog(BuildContext context, String title, String content) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(content),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import 'package:getx_scaffold/getx_scaffold.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/car_info/view.dart';
|
||||
|
||||
import 'package:ln_jq_app/pages/c_page/mine/view.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
|
||||
|
||||
@@ -40,7 +39,7 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
Widget _buildNavigationBar() {
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
height: Get.height * 0.05,
|
||||
height: 50.h,
|
||||
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
|
||||
@@ -58,9 +57,8 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
children: [
|
||||
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
|
||||
_buildNavItem(1, "ic_map_select@2x", "ic_map@2x"),
|
||||
_buildNavItem(2, "ic_mall_select@2x", "ic_mall@2x"),
|
||||
_buildNavItem(3, "ic_car_select@2x", "ic_car@2x"),
|
||||
_buildNavItem(4, "ic_user_select@2x", "ic_user@2x"),
|
||||
_buildNavItem(2, "ic_car_select@2x", "ic_car@2x"),
|
||||
_buildNavItem(3, "ic_user_select@2x", "ic_user@2x"),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -83,8 +81,7 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
child: SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),
|
||||
),
|
||||
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart' as dio;
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_picker_platform_interface/src/types/image_source.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/car_info/attachment_viewer_page.dart';
|
||||
@@ -18,9 +15,9 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// --- 车辆基本信息 ---
|
||||
String plateNumber = "";
|
||||
String vin = "-";
|
||||
String modelName = "-";
|
||||
String brandName = "-";
|
||||
String vin = "未知";
|
||||
String modelName = "未知";
|
||||
String brandName = "未知";
|
||||
|
||||
// --- 证件附件列表 ---
|
||||
final RxList<String> drivingAttachments = <String>[].obs;
|
||||
@@ -92,113 +89,6 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
//上传证件照
|
||||
void pickImage(String title, ImageSource source) async {
|
||||
if (source == ImageSource.camera) {
|
||||
takePhotoAndRecognize(title);
|
||||
} else if (source == ImageSource.gallery) {
|
||||
//相册选择逻辑
|
||||
var status = await Permission.photos.request();
|
||||
if (!status.isGranted) {
|
||||
if (status.isPermanentlyDenied) openAppSettings();
|
||||
showErrorToast("需要相册权限才能拍照上传");
|
||||
return;
|
||||
}
|
||||
final XFile? image = await ImagePicker().pickImage(
|
||||
source: ImageSource.gallery,
|
||||
imageQuality: 80,
|
||||
);
|
||||
if (image != null) {
|
||||
_uploadAndSaveCertificate(title, image.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void takePhotoAndRecognize(String title) async {
|
||||
var status = await Permission.camera.request();
|
||||
if (!status.isGranted) {
|
||||
if (status.isPermanentlyDenied) openAppSettings();
|
||||
showErrorToast("需要相机权限才能拍照上传");
|
||||
return;
|
||||
}
|
||||
|
||||
final XFile? photo = await ImagePicker().pickImage(
|
||||
source: ImageSource.camera,
|
||||
imageQuality: 80, // 压缩图片质量以加快上传
|
||||
);
|
||||
if (photo == null) return;
|
||||
|
||||
_uploadAndSaveCertificate(title, photo.path);
|
||||
}
|
||||
|
||||
/// 提取共用的上传与关联证件逻辑
|
||||
void _uploadAndSaveCertificate(String title, String filePath) async {
|
||||
// 上传文件
|
||||
String? imageUrl = await uploadFile(filePath);
|
||||
if (imageUrl == null) return;
|
||||
|
||||
// 根据标题映射业务类型
|
||||
String type = "";
|
||||
switch (title) {
|
||||
case "行驶证":
|
||||
type = "9";
|
||||
break;
|
||||
case "营运证":
|
||||
type = "10";
|
||||
break;
|
||||
case "加氢证":
|
||||
type = "12";
|
||||
break;
|
||||
case "登记证":
|
||||
type = "13";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用后台接口关联证件
|
||||
var response = await HttpService.to.post(
|
||||
"appointment/truck/uploadCertificatePic",
|
||||
data: {
|
||||
"plateNumber": plateNumber,
|
||||
"imageUrl": imageUrl,
|
||||
"type": type,
|
||||
},
|
||||
);
|
||||
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("上传成功");
|
||||
getUserBindCarInfo(); // 重新拉取数据更新UI
|
||||
} else {
|
||||
showErrorToast(result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传图片
|
||||
Future<String?> uploadFile(String filePath) async {
|
||||
showLoading("正在上传...");
|
||||
try {
|
||||
dio.FormData formData = dio.FormData.fromMap({
|
||||
'file': await dio.MultipartFile.fromFile(filePath, filename: 'ocr_identity.jpg'),
|
||||
});
|
||||
|
||||
var response = await HttpService.to.post("appointment/ocr/upload", data: formData);
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0) return result.data.toString();
|
||||
showErrorToast(result.error);
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorToast("图片上传失败");
|
||||
} finally {
|
||||
dismissLoading();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void getUserBindCarInfo() async {
|
||||
if (StorageService.to.hasVehicleInfo) {
|
||||
VehicleInfo? bean = StorageService.to.vehicleInfo;
|
||||
@@ -212,7 +102,7 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// 获取证件信息
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/vehicle/getPicInfoByVin?vin=$vin&plateNumber=$plateNumber',
|
||||
'appointment/vehicle/getPicInfoByVin?vin=$vin',
|
||||
);
|
||||
|
||||
if (response != null && response.data != null) {
|
||||
@@ -244,10 +134,10 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
...registerAttachments,
|
||||
];
|
||||
|
||||
color = data['color'].toString();
|
||||
hydrogenCapacity = data['hydrogenCapacity'].toString();
|
||||
rentFromCompany = data['rentFromCompany'].toString();
|
||||
address = data['address'].toString();
|
||||
color = data['color'].toString();
|
||||
hydrogenCapacity = data['hydrogenCapacity'].toString();
|
||||
rentFromCompany = data['rentFromCompany'].toString();
|
||||
address = data['address'].toString();
|
||||
|
||||
loadAllPdfs();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
@@ -134,7 +133,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
onPressed: () async{
|
||||
var scanResult = await Get.to(() => const MessagePage());
|
||||
if (scanResult == null) {
|
||||
controller.msgNotice();
|
||||
@@ -164,26 +163,11 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildModernStatItem(
|
||||
'本月里程数',
|
||||
'Accumulated',
|
||||
StorageService.to.hasVehicleInfo ? '2,852km' : '-',
|
||||
'',
|
||||
),
|
||||
_buildModernStatItem('本月里程数', 'Accumulated', '2,852km', ''),
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem(
|
||||
'总里程',
|
||||
'Refuel Count',
|
||||
StorageService.to.hasVehicleInfo ? "2.5W km" : '-',
|
||||
'',
|
||||
),
|
||||
_buildModernStatItem('总里程', 'Refuel Count', "2.5W km", ''),
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem(
|
||||
'服务评分',
|
||||
'Driver rating',
|
||||
StorageService.to.hasVehicleInfo ? "4.9分" : '-',
|
||||
'',
|
||||
),
|
||||
_buildModernStatItem('服务评分', 'Driver rating', "4.9分", ''),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -316,20 +300,20 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: StorageService.to.hasVehicleInfo ? 0.75 : 0,
|
||||
child: const LinearProgressIndicator(
|
||||
value: 0.75,
|
||||
minHeight: 8,
|
||||
backgroundColor: Color(0xFFF0F2F5),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(16, 185, 129, 1)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("H2 Level", style: TextStyle(fontSize: 11, color: Colors.grey)),
|
||||
Text(
|
||||
StorageService.to.hasVehicleInfo ? "75%" : "0%",
|
||||
"75%",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Color.fromRGBO(16, 185, 129, 1),
|
||||
@@ -369,7 +353,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
children: [
|
||||
_buildCertificateContent('行驶证', controller.drivingAttachments),
|
||||
_buildCertificateContent('营运证', controller.operationAttachments),
|
||||
_buildCertificateContent('加氢证', controller.hydrogenationAttachments),
|
||||
_buildCertificateContent('加氢资格证', controller.hydrogenationAttachments),
|
||||
_buildCertificateContent('登记证', controller.registerAttachments),
|
||||
],
|
||||
),
|
||||
@@ -389,7 +373,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: attachments.isEmpty
|
||||
? _buildEmptyCertificateState(title)
|
||||
? const Center(child: Text('暂无相关证件信息'))
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -398,11 +382,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildCertDetailItem(
|
||||
'所属公司',
|
||||
controller.rentFromCompany,
|
||||
isFull: false,
|
||||
),
|
||||
_buildCertDetailItem('所属公司', controller.rentFromCompany, isFull: false),
|
||||
_buildCertDetailItem('运营城市', controller.address),
|
||||
],
|
||||
),
|
||||
@@ -441,158 +421,6 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildEmptyCertificateState(String title) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/ic_attention@2x.png', // 请替换为您的实际图片路径
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'您未上传“$title”',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color.fromRGBO(51, 51, 51, 1),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'上传后可提前通知加氢站报备\n大幅减少加氢站等待时间',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color: Color.fromRGBO(156, 163, 175, 1),
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
height: 44,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showUploadDialog(title);
|
||||
},
|
||||
icon: const Text(
|
||||
'立即上传',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
label: Image.asset(
|
||||
'assets/images/ic_upload@2x.png',
|
||||
height: 20.h,
|
||||
width: 20.w,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF017137),
|
||||
foregroundColor: Colors.white,
|
||||
shape: StadiumBorder(),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showUploadDialog(String title) {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'上传$title',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'请确保拍摄证件清晰可见,关键文字无反光遮挡,这将有助于快速通过审核',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey[400], height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildUploadOption(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
label: '拍照上传',
|
||||
onTap: () {
|
||||
controller.pickImage(title, ImageSource.camera);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildUploadOption(
|
||||
icon: Icons.image_outlined,
|
||||
label: '相册上传',
|
||||
onTap: () {
|
||||
controller.pickImage(title, ImageSource.gallery);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 构建弹窗内的选择按钮
|
||||
Widget _buildUploadOption({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF2F9F7), // 浅绿色背景
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF017137), // 深绿色圆圈
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 28),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF333333),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCertDetailItem(
|
||||
String label,
|
||||
String value, {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,63 +12,40 @@ class MessagePage extends GetView<MessageController> {
|
||||
Get.put(MessageController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F9FB),
|
||||
appBar: AppBar(
|
||||
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(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(title: const Text('消息通知'), centerTitle: true),
|
||||
body: Column(
|
||||
children: [
|
||||
Obx(() => SmartRefresher(
|
||||
controller: controller.refreshController,
|
||||
enablePullUp: true,
|
||||
onRefresh: controller.onRefresh,
|
||||
onLoading: controller.onLoading,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
||||
itemCount: controller.messageList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildMessageItem(context, controller.messageList[index]);
|
||||
},
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
child: Obx(() => SmartRefresher(
|
||||
controller: controller.refreshController,
|
||||
enablePullUp: true,
|
||||
onRefresh: controller.onRefresh,
|
||||
onLoading: controller.onLoading,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: controller.messageList.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
return _buildMessageItem(context, controller.messageList[index]);
|
||||
},
|
||||
),
|
||||
)),
|
||||
),
|
||||
Obx(() => !controller.allRead.value
|
||||
? Positioned(
|
||||
right: 20,
|
||||
bottom: 50,
|
||||
child: GestureDetector(
|
||||
onTap: controller.markAllRead,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF007A45),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
? Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: Colors.white,
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.markAllRead,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
minimumSize: const Size(double.infinity, 44),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: const Text('全部标为已读', style: TextStyle(fontSize: 16, color: Colors.white)),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
@@ -76,92 +53,54 @@ class MessagePage extends GetView<MessageController> {
|
||||
}
|
||||
|
||||
Widget _buildMessageItem(BuildContext context, MessageModel item) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 左侧时间轴线条和圆点
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Container(
|
||||
width: 1.5,
|
||||
color: const Color(0xFFD8E2EE),
|
||||
),
|
||||
Positioned(
|
||||
top: 25,
|
||||
child: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
controller.markRead(item);
|
||||
_showMessageDialog(context, item);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 6, right: 12),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: item.isRead == 1 ? Colors.grey[300] : const Color(0xFFFAAD14),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
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]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -172,7 +111,7 @@ class MessagePage extends GetView<MessageController> {
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
@@ -183,22 +122,22 @@ class MessagePage extends GetView<MessageController> {
|
||||
item.title,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
item.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, height: 1.5, color: Color(0xFF333333)),
|
||||
style: const TextStyle(fontSize: 15, height: 1.5, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: const Color(0xFF007A45),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: Colors.blue),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
),
|
||||
child: const Text('确认',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
child: const Text('确认', style: TextStyle(color: Colors.blue)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/common/index.dart';
|
||||
import 'package:getx_scaffold/common/widgets/index.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:ln_jq_app/pages/common/webview/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
@@ -43,30 +38,6 @@ class MinePage extends GetView<MineController> {
|
||||
_buildSafetyReminderCard(),
|
||||
SizedBox(height: 24.h),
|
||||
_buildLogoutButton(),
|
||||
SizedBox(height: 15.h),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 13),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '《用户协议》',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
openPage("用户协议", "https://lnh2e.com/user_agreement.html");
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: '《隐私政策》',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 13),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 95.h),
|
||||
],
|
||||
),
|
||||
@@ -80,15 +51,6 @@ class MinePage extends GetView<MineController> {
|
||||
);
|
||||
}
|
||||
|
||||
void openPage(String title, String url) {
|
||||
if (Platform.isIOS) {
|
||||
openWebPage(url);
|
||||
return;
|
||||
}
|
||||
Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url});
|
||||
}
|
||||
|
||||
|
||||
/// 构建顶部用户信息卡片
|
||||
Widget _buildUserInfoCard() {
|
||||
return Card(
|
||||
|
||||
@@ -69,34 +69,239 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
String get formattedTimeSlot =>
|
||||
'${_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() {
|
||||
final now = DateTime.now();
|
||||
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)) {
|
||||
// 如果是今天:起始时间 = max(当前小时, 营业开始小时),且上限为营业结束小时
|
||||
int targetHour = now.hour;
|
||||
if (targetHour < bizStartHour) targetHour = bizStartHour;
|
||||
if (targetHour > bizEndHour) targetHour = bizEndHour;
|
||||
|
||||
startTime.value = TimeOfDay(hour: targetHour, minute: 0);
|
||||
// 如果是今天,就将时间重置为当前时间所在的半小时区间
|
||||
startTime.value = _calculateInitialStartTime(now);
|
||||
endTime.value = TimeOfDay.fromDateTime(
|
||||
_getDateTimeFromTimeOfDay(startTime.value).add(const Duration(minutes: 30)),
|
||||
);
|
||||
} else {
|
||||
// 如果是明天:起始时间直接重置为营业开始小时
|
||||
startTime.value = TimeOfDay(hour: bizStartHour, minute: 0);
|
||||
// 如果是明天(或其他未来日期),则可以将时间重置为一天的最早可用时间,例如 00:00
|
||||
startTime.value = const TimeOfDay(hour: 0, 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束时间默认顺延1小时
|
||||
endTime.value = TimeOfDay(hour: (startTime.value.hour + 1) % 24, minute: 0);
|
||||
if (availableSlots.isEmpty) {
|
||||
showToast('今天已没有可预约的时间段');
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// 用于存储上一次成功预约的信息
|
||||
@@ -221,15 +426,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
stateName: '',
|
||||
addStatus: '',
|
||||
addStatusName: '',
|
||||
hasEdit: true,
|
||||
rejectReason: '',
|
||||
isTruckAttachment: 0,
|
||||
hasHydrogenationAttachment: true,
|
||||
hasDrivingAttachment: true,
|
||||
isEdit: '',
|
||||
drivingAttachments: [],
|
||||
hydrogenationAttachments: [],
|
||||
gunNumber: '',
|
||||
hasEdit: true,
|
||||
);
|
||||
|
||||
//打开预约列表
|
||||
@@ -260,7 +458,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
if (_debounce?.isActive ?? false) {
|
||||
return;
|
||||
}
|
||||
_debounce = Timer(const Duration(milliseconds: 200), () {});
|
||||
_debounce = Timer(const Duration(seconds: 1), () {});
|
||||
|
||||
showLoading("加载中");
|
||||
|
||||
@@ -338,13 +536,12 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
String workEfficiency = "-";
|
||||
String fillingWeight = "-";
|
||||
String fillingTimes = "-";
|
||||
String modeImage = "";
|
||||
String workEfficiency = "0";
|
||||
String fillingWeight = "0";
|
||||
String fillingTimes = "0";
|
||||
String plateNumber = "";
|
||||
String vin = "";
|
||||
String leftHydrogen = "-";
|
||||
String leftHydrogen = "0";
|
||||
num maxHydrogen = 0;
|
||||
String difference = "";
|
||||
var progressValue = 0.0;
|
||||
@@ -400,7 +597,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// 创建一个每1分钟执行一次的周期性定时器
|
||||
_refreshTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||
getSiteList(showloading: false);
|
||||
getSiteList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -453,7 +650,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
try {
|
||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||
var responseData = await HttpService.to.get(
|
||||
'appointment/truck/history-filling-summary?vin=$vin&plateNumber=$plateNumber',
|
||||
'appointment/truck/history-filling-summary?vin=$vin',
|
||||
);
|
||||
if (responseData == null || responseData.data == null) {
|
||||
showToast('服务暂不可用,请稍后');
|
||||
@@ -467,7 +664,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
fillingWeight = "$formatted${result.data["fillingWeightUnit"]}";
|
||||
fillingTimes = "${result.data["fillingTimes"]}${result.data["fillingTimesUnit"]}";
|
||||
modeImage = result.data["modeImage"].toString();
|
||||
|
||||
updateUi();
|
||||
} catch (e) {
|
||||
@@ -493,8 +689,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
|
||||
leftHydrogen = "${result.data["leftHydrogen"]}Kg";
|
||||
workEfficiency = "${result.data["workEfficiency"]}Kg";
|
||||
leftHydrogen = result.data["leftHydrogen"].toString();
|
||||
workEfficiency = result.data["workEfficiency"].toString();
|
||||
|
||||
final leftHydrogenNum = double.tryParse(leftHydrogen) ?? 0.0;
|
||||
difference = (maxHydrogen - leftHydrogenNum).toStringAsFixed(2);
|
||||
@@ -528,9 +724,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void getSiteList({showloading = true}) async {
|
||||
if (StorageService.to.phone == "13344444444" ||
|
||||
StorageService.to.phone == "13888888888") {
|
||||
void getSiteList() async {
|
||||
if (StorageService.to.phone == "13344444444") {
|
||||
//该账号给stationOptions手动添加一个数据
|
||||
final testStation = StationModel(
|
||||
hydrogenId: '1142167389150920704',
|
||||
@@ -540,9 +735,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
// 价格
|
||||
siteStatusName: '营运中',
|
||||
// 状态
|
||||
isSelect: 1,
|
||||
startBusiness: '08:00:00',
|
||||
endBusiness: '20:00:00', // 默认可选
|
||||
isSelect: 1, // 默认可选
|
||||
);
|
||||
// 使用 assignAll 可以确保列表只包含这个测试数据
|
||||
stationOptions.assignAll([testStation]);
|
||||
@@ -554,9 +747,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
|
||||
try {
|
||||
if (showloading) {
|
||||
showLoading("加氢站数据加载中");
|
||||
}
|
||||
showLoading("加氢站数据加载中");
|
||||
|
||||
var responseData = await HttpService.to.get(
|
||||
"appointment/station/queryHydrogenSiteInfo",
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/common/index.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/reservation/controller.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/reservation_edit/controller.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/reservation_edit/view.dart';
|
||||
@@ -37,19 +36,19 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: Get.height * 0.6,
|
||||
height: Get.height * 0.55,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromRGBO(247, 249, 251, 1),
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 构建标题和下拉框
|
||||
_buildHeader(),
|
||||
const Divider(height: 1),
|
||||
// 下拉筛选框
|
||||
_buildChoice(),
|
||||
// 构建列表(使用 Obx 监听数据变化)
|
||||
@@ -59,64 +58,60 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChoice() {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: _statusOptions.entries.map((entry) {
|
||||
bool isSelected = _selectedStatus == entry.key;
|
||||
return GestureDetector(
|
||||
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,
|
||||
),
|
||||
),
|
||||
Container _buildChoice() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 0, 0),
|
||||
alignment: AlignmentGeometry.centerLeft,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
child: DropdownButton<String>(
|
||||
value: _selectedStatus,
|
||||
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() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(20, 20, 8, 8),
|
||||
child: const Text(
|
||||
'我的预约',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -139,8 +134,8 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
return Card(
|
||||
color: Colors.white,
|
||||
margin: const EdgeInsets.only(bottom: 12.0),
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -156,20 +151,17 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: reservation.state == "-1"
|
||||
? Color.fromRGBO(241, 67, 56, 0.1)
|
||||
: Color.fromRGBO(230, 249, 243, 1),
|
||||
color: Colors.blue.shade50, // 淡蓝色背景
|
||||
borderRadius: BorderRadius.circular(4), // 小圆角
|
||||
// 可以选择去掉边框,或者用极淡的边框
|
||||
border: Border.all(color: Colors.blue.shade100),
|
||||
),
|
||||
child: Text(
|
||||
"${reservation.stateName}-${reservation.addStatusName}",
|
||||
style: TextStyle(
|
||||
color: reservation.state == "-1"
|
||||
? Color.fromRGBO(241, 67, 56, 0.8)
|
||||
: Color.fromRGBO(49, 186, 133, 1),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue.shade700,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -179,10 +171,12 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
SizedBox(
|
||||
height: 28, // 限制按钮高度,显得精致
|
||||
child: OutlinedButton(
|
||||
onPressed: () async {
|
||||
onPressed: () async{
|
||||
var responseData = await HttpService.to.post(
|
||||
'appointment/orderAddHyd/vehicle-cancel',
|
||||
data: {'id': reservation.id},
|
||||
data: {
|
||||
'id': reservation.id,
|
||||
},
|
||||
);
|
||||
|
||||
if (responseData == null || responseData.data == null) {
|
||||
@@ -204,27 +198,24 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
side: BorderSide(color: Colors.grey.shade400),
|
||||
shape: const StadiumBorder(),
|
||||
side: BorderSide(color: Colors.grey.shade400), // 灰色边框
|
||||
shape: const StadiumBorder(), // 胶囊形状
|
||||
),
|
||||
child: Text(
|
||||
'取消预约',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 11.sp,
|
||||
),
|
||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
SizedBox(width: 10.w,),
|
||||
// 修改按钮 (仅在 hasEdit 为 true 时显示)
|
||||
if (reservation.hasEdit)
|
||||
SizedBox(
|
||||
height: 28,
|
||||
child: OutlinedButton(
|
||||
onPressed: () async {
|
||||
onPressed: () async{
|
||||
var result = await Get.to(
|
||||
() => ReservationEditPage(),
|
||||
() => ReservationEditPage(),
|
||||
arguments: {
|
||||
'reservation': reservation,
|
||||
'difference': _controller.difference,
|
||||
@@ -242,22 +233,21 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
side: BorderSide(color: AppTheme.themeColor),
|
||||
side: const BorderSide(color: Colors.blue), // 蓝色边框
|
||||
shape: const StadiumBorder(),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
child: Text(
|
||||
child: const Text(
|
||||
'修改',
|
||||
style: TextStyle(color: AppTheme.themeColor, fontSize: 11.sp),
|
||||
style: TextStyle(color: Colors.blue, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildDetailRow('车牌号:', reservation.plateNumber),
|
||||
_buildDetailRow('预约日期:', reservation.date),
|
||||
_buildDetailRow('预约氢量:', "${reservation.hydAmount} KG"),
|
||||
_buildDetailRow('预约氢量:', reservation.hydAmount),
|
||||
_buildDetailRow('加氢站:', reservation.stationName),
|
||||
_buildDetailRow('开始时间:', reservation.startTime),
|
||||
_buildDetailRow('结束时间:', reservation.endTime),
|
||||
@@ -281,26 +271,11 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: Color.fromRGBO(51, 51, 51, 0.6),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Text(label, style: const TextStyle(color: Colors.grey)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: label.contains("预约氢量:")
|
||||
? Color.fromRGBO(27, 168, 85, 1)
|
||||
: Color.fromRGBO(51, 51, 51, 1),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(value, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -49,7 +49,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
Positioned(
|
||||
left: 20.w,
|
||||
right: 20.w,
|
||||
bottom: Get.height * (Get.height < 826 ? 0.08 : 0.11),
|
||||
bottom: 110.h,
|
||||
child: _buildReservationItem(context),
|
||||
),
|
||||
],
|
||||
@@ -148,11 +148,9 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
controller.stopAutoRefresh();
|
||||
var scanResult = await Get.to(() => const MessagePage());
|
||||
if (scanResult == null) {
|
||||
controller.msgNotice();
|
||||
controller.startAutoRefresh();
|
||||
}
|
||||
},
|
||||
icon: Badge(
|
||||
@@ -179,12 +177,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem('总加氢次数', '', controller.fillingTimes, ''),
|
||||
const SizedBox(width: 8),
|
||||
_buildModernStatItem(
|
||||
'今日里程',
|
||||
'',
|
||||
StorageService.to.hasVehicleInfo ? "7kg" : "-",
|
||||
'',
|
||||
),
|
||||
_buildModernStatItem('今日里程', '', "7kg", ''),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -244,33 +237,17 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
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'));
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(flex: 4, child: LoginUtil.getAssImg('ic_car_bg@2x')),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCarDataItem(
|
||||
'剩余电量',
|
||||
StorageService.to.hasVehicleInfo ? '36.8%' : '-',
|
||||
),
|
||||
_buildCarDataItem('剩余电量', '36.8%'),
|
||||
const SizedBox(height: 8),
|
||||
_buildCarDataItem('剩余氢量', controller.leftHydrogen),
|
||||
_buildCarDataItem('剩余氢量', '${controller.leftHydrogen}Kg'),
|
||||
const SizedBox(height: 8),
|
||||
_buildCarDataItem('百公里氢耗', controller.workEfficiency),
|
||||
_buildCarDataItem('百公里氢耗', '${controller.workEfficiency}Kg'),
|
||||
const SizedBox(height: 12),
|
||||
Column(
|
||||
children: [
|
||||
@@ -298,7 +275,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
controller.leftHydrogen,
|
||||
"${controller.leftHydrogen}Kg",
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: Color(0xFF006633),
|
||||
@@ -457,34 +434,8 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
/// 时间 Slider 选择器
|
||||
Widget _buildTimeSlider(BuildContext context) {
|
||||
return Obx(() {
|
||||
// 获取站点信息
|
||||
final station = controller.stationOptions.firstWhereOrNull(
|
||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
||||
);
|
||||
|
||||
// 如果没有站点数据,默认隐藏
|
||||
if (station == null) {
|
||||
return const SizedBox(height: 100);
|
||||
}
|
||||
|
||||
// 解析营业范围
|
||||
final startParts = station.startBusiness.split(':');
|
||||
final endParts = station.endBusiness.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;
|
||||
if (bizEndMinute == 0 && bizEndHour > bizStartHour) bizEndHour--;
|
||||
|
||||
//确定当前滑块值
|
||||
int currentHour = controller.startTime.value.hour;
|
||||
double sliderValue = currentHour.toDouble().clamp(
|
||||
bizStartHour.toDouble(),
|
||||
bizEndHour.toDouble(),
|
||||
);
|
||||
|
||||
double minVal = bizStartHour.toDouble();
|
||||
double maxVal = bizEndHour.toDouble();
|
||||
if (minVal >= maxVal) maxVal = minVal + 1;
|
||||
// 这里的逻辑对应 Controller 中的 24 小时可用 Slot
|
||||
int currentIdx = controller.startTime.value.hour;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -533,20 +484,23 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
min: minVal,
|
||||
max: maxVal,
|
||||
// divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1,
|
||||
value: currentIdx.toDouble(),
|
||||
min: 0,
|
||||
max: 23,
|
||||
divisions: 23,
|
||||
onChanged: (val) {
|
||||
int hour = val.toInt();
|
||||
// 模拟 Controller 中的 pickTime 逻辑校验
|
||||
final now = DateTime.now();
|
||||
final isToday =
|
||||
controller.selectedDate.value.year == now.year &&
|
||||
controller.selectedDate.value.month == now.month &&
|
||||
controller.selectedDate.value.day == now.day;
|
||||
|
||||
// 如果是今天,判断不可选过去的时间点
|
||||
if (isToday && hour < now.hour) return;
|
||||
if (isToday && hour < now.hour) {
|
||||
// 如果是今天且小时数小于当前,则忽略
|
||||
return;
|
||||
}
|
||||
|
||||
controller.startTime.value = TimeOfDay(hour: hour, minute: 0);
|
||||
controller.endTime.value = TimeOfDay(hour: (hour + 1) % 24, minute: 0);
|
||||
@@ -673,7 +627,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
controller.selectedStationId.value = value;
|
||||
controller.resetTimeForSelectedDate();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -2,11 +2,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.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: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/pages/b_page/base_widgets/view.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
||||
@@ -23,199 +20,107 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
@override
|
||||
bool get listenLifecycleEvent => true;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initAliyunPush();
|
||||
addPushCallback();
|
||||
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() {
|
||||
requestPermission();
|
||||
//登录状态跳转
|
||||
if (StorageService.to.isLoggedIn) {
|
||||
// 检查是否同意过隐私政策,只有同意后才初始化推送
|
||||
if (StorageService.to.isPrivacyAgreed) {
|
||||
requestPermission();
|
||||
initAliyunPush();
|
||||
addPushCallback();
|
||||
}
|
||||
|
||||
// 如果已登录,再判断是哪个渠道
|
||||
if (StorageService.to.loginChannel == LoginChannel.station) {
|
||||
return B_BaseWidgetsPage();
|
||||
return B_BaseWidgetsPage(); // 站点首页
|
||||
} else if (StorageService.to.loginChannel == LoginChannel.driver) {
|
||||
return BaseWidgetsPage();
|
||||
return BaseWidgetsPage(); // 司机首页
|
||||
} else {
|
||||
return LoginPage();
|
||||
}
|
||||
} else {
|
||||
// 未登录,直接去登录页
|
||||
return LoginPage();
|
||||
}
|
||||
}
|
||||
|
||||
void requestPermission() async {
|
||||
PermissionStatus status = await Permission.notification.status;
|
||||
if (status.isGranted) return;
|
||||
if (status.isGranted) {
|
||||
Logger.d("通知权限已开启");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.isDenied) {
|
||||
// 建议此处增加一个应用内的 Rationale (解释说明) 弹窗
|
||||
status = await Permission.notification.request();
|
||||
}
|
||||
if (status.isGranted) {
|
||||
// 授权成功
|
||||
Logger.d('通知已开启');
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
Logger.d('通知权限已被拒绝,请到系统设置中开启');
|
||||
} else if (status.isDenied) {
|
||||
Logger.d('请授予通知权限,以便接收加氢站通知');
|
||||
}
|
||||
}
|
||||
|
||||
///推送相关初始化 (保持原样)
|
||||
///推送相关
|
||||
Future<void> initAliyunPush() async {
|
||||
// 1. 配置分离:建议将 Key 提取到外部或配置文件中
|
||||
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
||||
final String appSecret = Platform.isIOS
|
||||
? AppTheme.ios_appsecret
|
||||
: AppTheme.android_appsecret;
|
||||
|
||||
try {
|
||||
// 初始化推送
|
||||
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) {
|
||||
await _setupIOSConfig();
|
||||
} else if (Platform.isAndroid) {
|
||||
await _setupAndroidConfig();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.d('初始化异常: $e');
|
||||
Logger.d('初始化过程中发生异常: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// iOS 专属配置
|
||||
Future<void> _setupIOSConfig() async {
|
||||
await _aliyunPush.showIOSNoticeWhenForeground(true);
|
||||
final res = await _aliyunPush.showIOSNoticeWhenForeground(true);
|
||||
if (res['code'] == kAliyunPushSuccessCode) {
|
||||
Logger.d('iOS 前台通知展示已开启');
|
||||
} else {
|
||||
Logger.d('iOS 前台通知开启失败: ${res['errorMsg']}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Android 专属配置
|
||||
Future<void> _setupAndroidConfig() async {
|
||||
await _aliyunPush.setNotificationInGroup(true);
|
||||
await _aliyunPush.createAndroidChannel(
|
||||
final res = await _aliyunPush.createAndroidChannel(
|
||||
"xll_push_android",
|
||||
'新消息通知',
|
||||
4,
|
||||
'用于接收加氢站实时状态提醒',
|
||||
);
|
||||
if (res['code'] == kAliyunPushSuccessCode) {
|
||||
Logger.d('Android 通知通道创建成功');
|
||||
} else {
|
||||
Logger.d('Android 通道创建失败: ${res['code']} - ${res['errorMsg']}');
|
||||
}
|
||||
}
|
||||
|
||||
void addPushCallback() {
|
||||
@@ -234,23 +139,40 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
|
||||
Future<void> _onAndroidNotificationClickedWithNoAction(
|
||||
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 {
|
||||
Logger.d('onNotificationOpened ====> $message');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,18 @@ import 'package:ln_jq_app/pages/home/controller.dart';
|
||||
class HomePage extends GetView<HomeController> {
|
||||
const HomePage({super.key});
|
||||
|
||||
// 主视图
|
||||
Widget _buildView() {
|
||||
return <Widget>[Text('主页面')].toColumn(mainAxisSize: MainAxisSize.min).center();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<HomeController>(
|
||||
init: HomeController(),
|
||||
id: 'home',
|
||||
builder: (_) {
|
||||
return Scaffold(body: controller.getHomePage());
|
||||
return controller.getHomePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
@@ -18,8 +17,6 @@ import 'package:ln_jq_app/pages/login/controller.dart';
|
||||
import 'package:ln_jq_app/pages/url_host/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
import '../c_page/message/view.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@@ -33,7 +30,6 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
bool _obscureText = true;
|
||||
bool _rememberPassword = true;
|
||||
bool _credentialsLoaded = false;
|
||||
bool isPushInitialized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -140,10 +136,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
// 根据 Tab 显示不同的输入框
|
||||
if (_isAgreed)
|
||||
(_tabController.index == 0
|
||||
? _buildDriverInputFields(controller)
|
||||
: _buildStationInputFields(controller)),
|
||||
_tabController.index == 0
|
||||
? _buildDriverInputFields(controller)
|
||||
: _buildStationInputFields(controller),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
// 统一登录按钮
|
||||
@@ -151,8 +146,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
|
||||
const SizedBox(height: 10),
|
||||
buildAgreement(),
|
||||
const SizedBox(height: 80),
|
||||
_buildFooterSlogan(),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -161,6 +155,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(left: 0, right: 0, bottom: 33.h, child: _buildFooterSlogan()),
|
||||
if (AppTheme.is_show_host)
|
||||
Positioned(
|
||||
top: 40.h,
|
||||
@@ -209,9 +204,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"欢迎使用小羚羚 ",
|
||||
"欢迎使用 ",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color.fromRGBO(51, 51, 51, 1),
|
||||
),
|
||||
@@ -246,12 +241,10 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
child: TextField(
|
||||
controller: controller.phoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
maxLength: 11,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '请输入手机号',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
counterText: ""
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -395,28 +388,13 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
content: _buildDialogContent(),
|
||||
confirmText: '同意',
|
||||
cancelText: '拒绝',
|
||||
onConfirm: () async {
|
||||
onConfirm: () {
|
||||
_isAgreed = true;
|
||||
controller.updateUi();
|
||||
|
||||
// 保存隐私政策同意状态
|
||||
await StorageService.to.savePrivacyAgreed(true);
|
||||
|
||||
// 申请通知权限
|
||||
await _requestNotificationPermission();
|
||||
|
||||
// 初始化阿里云推送
|
||||
await _initPushService();
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经同意过,但推送还没初始化,则初始化
|
||||
if (!isPushInitialized) {
|
||||
await _initPushService();
|
||||
}
|
||||
|
||||
_tabController.index == 0
|
||||
? _handleDriverLogin(controller)
|
||||
: _handleStationLogin(controller);
|
||||
@@ -478,6 +456,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_processLoginResponse(responseData, "station", account);
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
@@ -541,6 +520,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
Logger.d("暂时不处理 查询车辆信息失败的情况");
|
||||
}
|
||||
|
||||
|
||||
dismissLoading();
|
||||
Get.offAll(() => BaseWidgetsPage());
|
||||
} else {
|
||||
@@ -556,60 +536,6 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
addAlias(identifier);
|
||||
}
|
||||
|
||||
// 申请通知权限
|
||||
Future<void> _requestNotificationPermission() async {
|
||||
final PermissionStatus status = await Permission.notification.request();
|
||||
if (status.isGranted) {
|
||||
Logger.d('通知权限已授予');
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
Logger.d('通知权限被永久拒绝');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化推送服务
|
||||
Future<void> _initPushService() async {
|
||||
try {
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
// 初始化推送
|
||||
final String appKey = Platform.isIOS ? AppTheme.ios_key : AppTheme.android_key;
|
||||
final String appSecret = Platform.isIOS
|
||||
? AppTheme.ios_appsecret
|
||||
: AppTheme.android_appsecret;
|
||||
|
||||
final result = await _aliyunPush.initPush(appKey: appKey, appSecret: appSecret);
|
||||
if (result['code'] != kAliyunPushSuccessCode) {
|
||||
Logger.d('推送初始化失败: ${result['errorMsg']}');
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置平台特定设置
|
||||
if (Platform.isIOS) {
|
||||
await _aliyunPush.showIOSNoticeWhenForeground(true);
|
||||
} else if (Platform.isAndroid) {
|
||||
await _aliyunPush.setNotificationInGroup(true);
|
||||
await _aliyunPush.createAndroidChannel(
|
||||
"xll_push_android",
|
||||
'新消息通知',
|
||||
4,
|
||||
'用于接收加氢站实时状态提醒',
|
||||
);
|
||||
}
|
||||
|
||||
// 添加推送回调
|
||||
_aliyunPush.addMessageReceiver(onNotificationOpened: _onNotificationOpened);
|
||||
|
||||
isPushInitialized = true;
|
||||
Logger.d('推送服务初始化成功');
|
||||
} catch (e) {
|
||||
Logger.d('推送服务初始化异常: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
||||
await Get.to(() => const MessagePage());
|
||||
}
|
||||
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
void addAlias(String alias) async {
|
||||
|
||||
@@ -1,103 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/common/components/index.dart';
|
||||
import 'package:getx_scaffold/common/widgets/rich_text_x.dart';
|
||||
import 'package:ln_jq_app/pages/home/view.dart';
|
||||
import 'package:ln_jq_app/pages/login/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
import '../common/webview/view.dart';
|
||||
|
||||
class WelcomeController extends GetxController {
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// 移除原生闪屏页(如果有的话)
|
||||
FlutterNativeSplash.remove();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
if (StorageService.to.isPrivacyAgreed) {
|
||||
_startTimer();
|
||||
} else {
|
||||
showPrivacyDialog();
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
_startTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void showPrivacyDialog() {
|
||||
DialogX.to.showConfirmDialog(
|
||||
title: "个人信息保护提示",
|
||||
content: _buildDialogContent(),
|
||||
confirmText: '同意',
|
||||
cancelText: '不同意',
|
||||
onConfirm: () async {
|
||||
await StorageService.to.savePrivacyAgreed(true);
|
||||
Get.offAll(() => const HomePage());
|
||||
},
|
||||
onCancel: () {
|
||||
DialogX.to.showConfirmDialog(
|
||||
title: "温馨提示",
|
||||
content: RichTextX(
|
||||
children: [
|
||||
TextSpanItem('如果您不同意'),
|
||||
TextSpanItem(
|
||||
'《隐私协议》',
|
||||
onTap: () => openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html"),
|
||||
),
|
||||
TextSpanItem('和'),
|
||||
TextSpanItem(
|
||||
'《用户政策》',
|
||||
onTap: () => openPage("用户协议", "https://lnh2e.com/user_agreement.html"),
|
||||
),
|
||||
TextSpanItem(
|
||||
',很遗憾我们将无法为您提供服务。您需要同意以上协议后,才能使用本应用。\n\n我们将严格按照相关法律法规要求,坚决保护您的个人隐私和信息安全。',
|
||||
),
|
||||
TextSpanItem('\n点击“同意”按钮,表示您已知情并同意以上协议。'),
|
||||
],
|
||||
),
|
||||
confirmText: '同意并继续',
|
||||
cancelText: '不同意',
|
||||
onConfirm: () async {
|
||||
await StorageService.to.savePrivacyAgreed(true);
|
||||
Get.offAll(() => const HomePage());
|
||||
},
|
||||
onCancel: () {
|
||||
SystemNavigator.pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDialogContent() {
|
||||
return RichTextX(
|
||||
children: [
|
||||
TextSpanItem('欢迎使用小羚羚!\n我们将通过'),
|
||||
TextSpanItem(
|
||||
'《隐私协议》',
|
||||
onTap: () => openPage("隐私政策", "https://lnh2e.com/privacy_agreement.html"),
|
||||
),
|
||||
TextSpanItem('和'),
|
||||
TextSpanItem(
|
||||
'《用户政策》',
|
||||
onTap: () => openPage("用户协议", "https://lnh2e.com/user_agreement.html"),
|
||||
),
|
||||
TextSpanItem(
|
||||
',帮助您了解我们为您提供的服务、我们如何处理个人信息以及您享有的权利。我们会严格按照相关法律法规要求,采取各种安全措施来保护您的个人信息。',
|
||||
),
|
||||
TextSpanItem('\n点击“同意”按钮,表示您已知情并同意以上协议。'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void openPage(String title, String url) {
|
||||
Get.to(() => const WebViewPage(), arguments: {'title': title, 'url': url});
|
||||
_startTimer();
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
|
||||
@@ -23,7 +23,7 @@ class WelcomePage extends GetView<WelcomeController> {
|
||||
right: 0,
|
||||
child: Image.asset(
|
||||
'assets/images/welcome.png',
|
||||
fit: BoxFit.cover
|
||||
fit: BoxFit.fill
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -26,14 +26,11 @@ class StorageService extends GetxService {
|
||||
static const String _stationAccountKey = 'station_account';
|
||||
static const String _stationPasswordKey = 'station_password';
|
||||
|
||||
// 新增:用于标记”绑定车辆”弹窗是否已在本会话中显示过
|
||||
// 新增:用于标记“绑定车辆”弹窗是否已在本会话中显示过
|
||||
static const String _bindDialogShownKey = 'bind_vehicle_dialog_shown';
|
||||
|
||||
static const String _hostUrlKey = 'host_url';
|
||||
|
||||
// 隐私政策相关
|
||||
static const String _privacyAgreedKey = 'privacy_agreed';
|
||||
|
||||
static StorageService get to => Get.find();
|
||||
|
||||
Future<StorageService> init() async {
|
||||
@@ -67,12 +64,9 @@ class StorageService extends GetxService {
|
||||
|
||||
String? get stationPassword => _box.read<String?>(_stationPasswordKey);
|
||||
|
||||
// 新增:获取”绑定车辆”弹窗是否已显示的标志
|
||||
// 新增:获取“绑定车辆”弹窗是否已显示的标志
|
||||
bool get hasShownBindVehicleDialog => _box.read<bool>(_bindDialogShownKey) ?? false;
|
||||
|
||||
// 获取隐私政策是否已同意
|
||||
bool get isPrivacyAgreed => _box.read<bool>(_privacyAgreedKey) ?? false;
|
||||
|
||||
VehicleInfo? get vehicleInfo {
|
||||
final vehicleJson = _box.read<String?>(_vehicleInfoKey);
|
||||
if (vehicleJson != null) {
|
||||
@@ -123,11 +117,6 @@ class StorageService extends GetxService {
|
||||
await _box.write(_stationPasswordKey, password);
|
||||
}
|
||||
|
||||
// 保存隐私政策同意状态
|
||||
Future<void> savePrivacyAgreed(bool agreed) async {
|
||||
await _box.write(_privacyAgreedKey, agreed);
|
||||
}
|
||||
|
||||
// 新增:标记“绑定车辆”弹窗已显示
|
||||
Future<void> markBindVehicleDialogAsShown() async {
|
||||
await _box.write(_bindDialogShownKey, true);
|
||||
|
||||
@@ -16,12 +16,12 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.2.4+7
|
||||
version: 1.2.3+6
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0
|
||||
|
||||
# Dependencies specify other packages tha。。。t your package needs in order to work.
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
@@ -46,15 +46,15 @@ dependencies:
|
||||
dropdown_button2: ^2.3.8
|
||||
image_picker: ^1.2.1 # 用于从相册选择图片
|
||||
image: ^4.5.4
|
||||
mobile_scanner: ^7.1.4
|
||||
flutter_pdfview: 1.4.3 #显示pdf
|
||||
photo_view: ^0.15.0 #操作图片
|
||||
flutter_inappwebview: ^6.1.5 # WebView插件
|
||||
geolocator: ^14.0.2 # 获取精确定位
|
||||
aliyun_push_flutter: ^1.3.6
|
||||
pull_to_refresh: ^2.0.0
|
||||
flutter_app_update: ^3.2.2
|
||||
saver_gallery: ^4.0.0
|
||||
path_provider: ^2.1.5
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -65,7 +65,6 @@ dev_dependencies:
|
||||
|
||||
dependency_overrides:
|
||||
intl: 0.19.0
|
||||
device_info_plus: ^12.3.0
|
||||
|
||||
flutter:
|
||||
|
||||
|
||||