Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b6f93ca95 | |||
| eb41ecaec4 | |||
| 384de27f2c | |||
| 84b174c4a5 | |||
| 02e1946319 | |||
| d5083c1939 | |||
| fe44848529 | |||
| 572c416827 | |||
| 8df16f0787 | |||
| ce6bd3edd2 | |||
| 6997b4ac9e | |||
| a26d2478f3 | |||
| 0dded3b928 | |||
| b7caf58adf | |||
| 0df1902df2 | |||
| a8314d8a7a | |||
| 39cae906e9 | |||
| 14fd6c75d0 | |||
| 1724852a39 | |||
| a05d4ebb9b | |||
| 600cea4379 | |||
| 3dfc1dfc2c | |||
| 35bd3a78a5 | |||
| 032e60d362 | |||
| 45f5035d1b | |||
| f792915429 | |||
| edbacc502b |
@@ -37,8 +37,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = 6
|
||||
versionName = "1.2.3"
|
||||
versionCode = 7
|
||||
versionName = "1.2.4"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -1,47 +1,50 @@
|
||||
<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.FOREGROUND_SERVICE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="小羚羚"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/logo">
|
||||
android:icon="@mipmap/logo"
|
||||
android:label="小羚羚">
|
||||
<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.
|
||||
@@ -51,32 +54,13 @@
|
||||
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"/>
|
||||
<!-- 华为通道的参数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="" />
|
||||
|
||||
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appsecret"
|
||||
android:value="39628204345a4240b5b645b68a5896c7" />
|
||||
|
||||
<!-- 接收推送消息 -->
|
||||
<receiver
|
||||
@@ -99,6 +83,7 @@
|
||||
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" />
|
||||
|
||||
@@ -117,8 +102,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>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
BIN
ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
@@ -21,9 +21,7 @@
|
||||
.amap-callamap,
|
||||
.amap-lib-driving-callBtn,
|
||||
.amap-copyright,
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.amap-logo{bottom: 60px}
|
||||
|
||||
/* 去除高德默认的 label 边框 and 背景 */
|
||||
.amap-marker-label {
|
||||
@@ -195,10 +193,10 @@
|
||||
|
||||
<div id="search-box">
|
||||
<div class="input-row">
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
|
||||
<button onclick="startRouteSearch()">路径规划</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,6 +242,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 点击地图空白处重置状态
|
||||
map.on('click', function() {
|
||||
resetSearchState();
|
||||
});
|
||||
|
||||
// 添加基础控件
|
||||
map.addControl(new AMap.Scale());
|
||||
map.addControl(new AMap.ToolBar({
|
||||
@@ -285,6 +288,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置搜索状态,隐藏面板和路线
|
||||
*/
|
||||
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(...)")
|
||||
@@ -481,19 +497,25 @@
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: station.name,
|
||||
label: {
|
||||
content: '<div class="custom-bubble">' + station.name + '</div>',
|
||||
content: '<div class="custom-bubble">' + station.name +
|
||||
'</div>',
|
||||
direction: 'top'
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 绑定点击事件:选中即为目的地,并开始规划
|
||||
sMarker.on('click', function() {
|
||||
document.getElementById('endInput').value = station.address || station.name;
|
||||
// 更新当前的 destMarker (如果需要)
|
||||
if (destMarker) destMarker.setMap(null);
|
||||
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;
|
||||
|
||||
startRouteSearch();
|
||||
// 直接传入坐标对象,避免关键字搜索失败
|
||||
var loc = new AMap.LngLat(station.longitude, station.latitude);
|
||||
startRouteSearch(loc);
|
||||
});
|
||||
|
||||
stationMarkers.push(sMarker);
|
||||
@@ -554,11 +576,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路径规划
|
||||
* 路径规划
|
||||
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
|
||||
*/
|
||||
function startRouteSearch() {
|
||||
// 获取输入框的文字
|
||||
function startRouteSearch(destLoc) {
|
||||
var startKw = document.getElementById('startInput').value;
|
||||
var endKw = document.getElementById('endInput').value;
|
||||
|
||||
@@ -566,28 +589,21 @@
|
||||
alert("请输入起点");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endKw) {
|
||||
alert("请输入终点");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除旧路线
|
||||
if (driving) driving.clear();
|
||||
|
||||
// 收起键盘
|
||||
document.getElementById('startInput').blur();
|
||||
document.getElementById('endInput').blur();
|
||||
|
||||
// --- 构造路径规划的点 ---
|
||||
var points = [];
|
||||
|
||||
// 1. 处理起点逻辑
|
||||
// 1. 起点逻辑
|
||||
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
|
||||
if (!currentLng || !currentLat) {
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('requestLocation');
|
||||
}
|
||||
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
|
||||
alert("正在获取定位,请稍后...");
|
||||
return;
|
||||
}
|
||||
@@ -601,10 +617,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 处理终点逻辑
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
|
||||
if (destLoc) {
|
||||
points.push({
|
||||
keyword: endKw,
|
||||
location: destLoc // 关键:使用精确坐标
|
||||
});
|
||||
} else {
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 发起搜索
|
||||
driving.search(points, function (status, result) {
|
||||
@@ -613,10 +636,12 @@
|
||||
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("路径规划未成功,请尝试微调起终点");
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
BIN
ln_jq_app/assets/images/ic_attention@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
ln_jq_app/assets/images/ic_close@2x.png
Normal file
|
After Width: | Height: | Size: 892 B |
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 594 B |
BIN
ln_jq_app/assets/images/ic_upload@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
@@ -41,6 +41,8 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- saver_gallery (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -62,6 +64,7 @@ DEPENDENCIES:
|
||||
- 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`)
|
||||
|
||||
@@ -103,6 +106,8 @@ EXTERNAL SOURCES:
|
||||
: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:
|
||||
@@ -127,6 +132,7 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"filename" : "Icon-App-29x29 1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
@@ -31,25 +31,25 @@
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-80x80.jpg",
|
||||
"filename" : "Icon-App-80x80.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-120x120.jpg",
|
||||
"filename" : "Icon-App-120x120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-120x120 1.jpg",
|
||||
"filename" : "Icon-App-120x120 1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-180.jpg",
|
||||
"filename" : "Icon-App-180.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
@@ -61,19 +61,19 @@
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"filename" : "Icon-App-20x20@2x 1.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"filename" : "Icon-App-29x29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"filename" : "Icon-App-29x29@2x 1.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
@@ -91,7 +91,7 @@
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"filename" : "Icon-App-76x76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 509 B After Width: | Height: | Size: 465 B |
|
After Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 613 B |
|
After Width: | Height: | Size: 613 B |
|
Before Width: | Height: | Size: 707 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -76,5 +76,11 @@
|
||||
<string>en</string>
|
||||
</array>
|
||||
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<!-- 允许在“文件”App中直接打开文档 -->
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,11 +8,11 @@ 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/
|
||||
static String test_service_url = "https://beta-esg.api.lnh2e.com/";
|
||||
static String test_service_url = "http://47.101.201.13:8443/api/";
|
||||
static const String release_service_url = "";
|
||||
|
||||
//加氢站相关查询
|
||||
|
||||
@@ -17,7 +17,7 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
WidgetsBinding widgetsBinding = await init(
|
||||
isDebug: true,
|
||||
isDebug: false,
|
||||
logTag: '小羚羚',
|
||||
supportedLocales: [const Locale('zh', 'CN')],
|
||||
);
|
||||
@@ -35,7 +35,7 @@ void main() async {
|
||||
// 设计稿尺寸 单位:dp
|
||||
designSize: const Size(390, 844),
|
||||
// Getx Log
|
||||
enableLog: true,
|
||||
enableLog: false,
|
||||
// 默认的跳转动画
|
||||
defaultTransition: Transition.rightToLeft,
|
||||
// 主题模式
|
||||
@@ -71,6 +71,9 @@ void initHttpSet() {
|
||||
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
|
||||
HttpService.to.setOnResponseHandler((response) async {
|
||||
try {
|
||||
if (response.data == null) {
|
||||
return null;
|
||||
}
|
||||
final baseModel = BaseModel.fromJson(response.data);
|
||||
if (baseModel.code == 0 || baseModel.code == 200) {
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
|
||||
|
||||
@@ -25,12 +22,14 @@ class HistoryController extends GetxController with BaseControllerMixin {
|
||||
final RxBool hasData = false.obs;
|
||||
|
||||
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
|
||||
|
||||
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
|
||||
|
||||
String stationName = "";
|
||||
|
||||
final Map<String, String> statusOptions = {
|
||||
'': '全部',
|
||||
'100': '未预约加氢',
|
||||
'0': '待加氢',
|
||||
'1': '已加氢',
|
||||
'2': '未加氢',
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
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});
|
||||
@@ -36,6 +40,30 @@ 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),
|
||||
],
|
||||
),
|
||||
@@ -48,6 +76,14 @@ 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(
|
||||
@@ -78,11 +114,15 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
controller.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Flexible(
|
||||
child: Text(
|
||||
controller.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -98,12 +138,11 @@ 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],
|
||||
@@ -111,9 +150,7 @@ 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,
|
||||
@@ -232,12 +269,17 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
label,
|
||||
style: TextStyle(color: Colors.grey, fontSize: 11.sp),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF333333),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.right,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: const Color(0xFF333333),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -515,14 +557,15 @@ 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,7 +1,6 @@
|
||||
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';
|
||||
@@ -57,17 +56,33 @@ class SitePage extends GetView<SiteController> {
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(
|
||||
() => HistoryPage(),
|
||||
arguments: {'stationName': controller.name},
|
||||
);
|
||||
// 手动录入
|
||||
controller.confirmReservation("", isAdd: true);
|
||||
},
|
||||
child: Text(
|
||||
'历史记录',
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromRGBO(156, 163, 175, 1),
|
||||
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
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -185,27 +200,7 @@ class SitePage extends GetView<SiteController> {
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildDropdownMenu(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
@@ -233,6 +228,40 @@ 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(
|
||||
@@ -466,7 +495,7 @@ class SitePage extends GetView<SiteController> {
|
||||
/// 右侧具体数据卡片
|
||||
Widget _buildInfoCard(ReservationModel item) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 16.w, top: 8.5, bottom: 8.5, right: 16.w),
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -508,59 +537,82 @@ class SitePage extends GetView<SiteController> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 联系信息
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.contactPerson} | ${item.contactPhone}",
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
item.contactPerson.isEmpty || item.contactPhone.isEmpty
|
||||
? ""
|
||||
: "${item.contactPerson} | ${item.contactPhone}",
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
|
||||
//操作按钮(仅在待处理状态显示)
|
||||
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);
|
||||
},
|
||||
SizedBox(height: 6.h),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
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);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildSmallButton(
|
||||
"确认",
|
||||
isOutline: false,
|
||||
onTap: () {
|
||||
controller.confirmReservation(item.id);
|
||||
},
|
||||
] else if (item.status == ReservationStatus.pending) ...[
|
||||
const SizedBox(height: 15),
|
||||
const Divider(height: 1, color: Color(0xFFF5F5F5)),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildSmallButton(
|
||||
"拒绝",
|
||||
isOutline: true,
|
||||
onTap: () {
|
||||
controller.rejectReservation(item.id);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildSmallButton(
|
||||
"确认",
|
||||
isOutline: false,
|
||||
onTap: () {
|
||||
controller.confirmReservation(item.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 通用小按钮
|
||||
|
||||
|
||||
Widget _buildSmallButton(
|
||||
String text, {
|
||||
required bool isOutline,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
const kPrimaryGreen = Color(0xFF006D35);
|
||||
const kDangerRed = Color(0xFFFF7D7D);
|
||||
var kDangerRed = text.contains('修改') ? Colors.red : Color.fromRGBO(255, 142, 98, 1);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
@@ -634,139 +686,4 @@ 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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
Widget _buildNavigationBar() {
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
height: 50.h,
|
||||
height: Get.height * 0.05,
|
||||
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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';
|
||||
@@ -89,6 +92,113 @@ 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;
|
||||
@@ -102,7 +212,7 @@ class CarInfoController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// 获取证件信息
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/vehicle/getPicInfoByVin?vin=$vin',
|
||||
'appointment/vehicle/getPicInfoByVin?vin=$vin&plateNumber=$plateNumber',
|
||||
);
|
||||
|
||||
if (response != null && response.data != null) {
|
||||
@@ -134,10 +244,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,6 +2,7 @@ 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';
|
||||
@@ -368,7 +369,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
children: [
|
||||
_buildCertificateContent('行驶证', controller.drivingAttachments),
|
||||
_buildCertificateContent('营运证', controller.operationAttachments),
|
||||
_buildCertificateContent('加氢资格证', controller.hydrogenationAttachments),
|
||||
_buildCertificateContent('加氢证', controller.hydrogenationAttachments),
|
||||
_buildCertificateContent('登记证', controller.registerAttachments),
|
||||
],
|
||||
),
|
||||
@@ -388,7 +389,7 @@ class CarInfoPage extends GetView<CarInfoController> {
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: attachments.isEmpty
|
||||
? const Center(child: Text('暂无相关证件信息'))
|
||||
? _buildEmptyCertificateState(title)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -440,6 +441,158 @@ 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,10 +1,15 @@
|
||||
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';
|
||||
|
||||
@@ -38,6 +43,30 @@ 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),
|
||||
],
|
||||
),
|
||||
@@ -51,6 +80,15 @@ 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(
|
||||
|
||||
@@ -221,8 +221,14 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
stateName: '',
|
||||
addStatus: '',
|
||||
addStatusName: '',
|
||||
rejectReason: '',
|
||||
hasEdit: true,
|
||||
rejectReason: '',
|
||||
isTruckAttachment: 0,
|
||||
hasHydrogenationAttachment: true,
|
||||
hasDrivingAttachment: true,
|
||||
isEdit: '',
|
||||
drivingAttachments: [],
|
||||
hydrogenationAttachments: [], gunNumber: '',
|
||||
);
|
||||
|
||||
//打开预约列表
|
||||
@@ -253,7 +259,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
if (_debounce?.isActive ?? false) {
|
||||
return;
|
||||
}
|
||||
_debounce = Timer(const Duration(seconds: 1), () {});
|
||||
_debounce = Timer(const Duration(milliseconds: 200), () {});
|
||||
|
||||
showLoading("加载中");
|
||||
|
||||
@@ -393,7 +399,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// 创建一个每1分钟执行一次的周期性定时器
|
||||
_refreshTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||
getSiteList();
|
||||
getSiteList(showloading: false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -521,8 +527,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void getSiteList() async {
|
||||
if (StorageService.to.phone == "13888888888") {
|
||||
void getSiteList({showloading = true}) async {
|
||||
if (StorageService.to.phone == "13344444444") {
|
||||
//该账号给stationOptions手动添加一个数据
|
||||
final testStation = StationModel(
|
||||
hydrogenId: '1142167389150920704',
|
||||
@@ -546,7 +552,9 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
|
||||
try {
|
||||
showLoading("加氢站数据加载中");
|
||||
if (showloading) {
|
||||
showLoading("加氢站数据加载中");
|
||||
}
|
||||
|
||||
var responseData = await HttpService.to.get(
|
||||
"appointment/station/queryHydrogenSiteInfo",
|
||||
|
||||
@@ -49,7 +49,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
Positioned(
|
||||
left: 20.w,
|
||||
right: 20.w,
|
||||
bottom: 110.h,
|
||||
bottom: Get.height * (Get.height < 826 ? 0.08 : 0.11),
|
||||
child: _buildReservationItem(context),
|
||||
),
|
||||
],
|
||||
@@ -457,27 +457,34 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
/// 时间 Slider 选择器
|
||||
Widget _buildTimeSlider(BuildContext context) {
|
||||
return Obx(() {
|
||||
// 获取当前小时作为滑块值 (0-23)
|
||||
int currentHour = controller.startTime.value.hour;
|
||||
|
||||
// 动态获取站点的营业范围限制
|
||||
// 获取站点信息
|
||||
final station = controller.stationOptions.firstWhereOrNull(
|
||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
||||
);
|
||||
// 解析营业时间
|
||||
// 处理格式如 "09:00" 或 "09:00:00"
|
||||
final startParts = (station?.startBusiness ?? "00:00").split(':');
|
||||
final endParts = (station?.endBusiness ?? "23:59").split(':');
|
||||
|
||||
// 如果没有站点数据,默认隐藏
|
||||
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--;
|
||||
|
||||
// 优化结束小时逻辑
|
||||
// 如果分钟为 0 (例如 18:00),说明该小时整点已关门,最大可选小时应减 1
|
||||
if (bizEndMinute == 0 && bizEndHour > 0) {
|
||||
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;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -526,12 +533,9 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
overlayColor: const Color(0xFF006633).withOpacity(0.1),
|
||||
),
|
||||
child: Slider(
|
||||
value: currentHour.toDouble().clamp(
|
||||
bizStartHour.toDouble(),
|
||||
bizEndHour.toDouble(),
|
||||
),
|
||||
min: bizStartHour.toDouble(),
|
||||
max: bizEndHour.toDouble(),
|
||||
value: sliderValue,
|
||||
min: minVal,
|
||||
max: maxVal,
|
||||
// divisions: bizEndHour - bizStartHour > 0 ? bizEndHour - bizStartHour : 1,
|
||||
onChanged: (val) {
|
||||
int hour = val.toInt();
|
||||
|
||||
@@ -29,8 +29,14 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initAliyunPush();
|
||||
addPushCallback();
|
||||
|
||||
// 检查是否同意过隐私政策,只有同意后才初始化推送
|
||||
if (StorageService.to.isPrivacyAgreed) {
|
||||
requestPermission();
|
||||
initAliyunPush();
|
||||
addPushCallback();
|
||||
}
|
||||
|
||||
FlutterNativeSplash.remove();
|
||||
log('page-init');
|
||||
|
||||
@@ -152,7 +158,6 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
|
||||
// 根据登录状态和登录渠道返回不同的首页
|
||||
Widget getHomePage() {
|
||||
requestPermission();
|
||||
if (StorageService.to.isLoggedIn) {
|
||||
if (StorageService.to.loginChannel == LoginChannel.station) {
|
||||
return B_BaseWidgetsPage();
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -17,6 +18,8 @@ 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});
|
||||
|
||||
@@ -30,6 +33,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
bool _obscureText = true;
|
||||
bool _rememberPassword = true;
|
||||
bool _credentialsLoaded = false;
|
||||
bool isPushInitialized = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -146,7 +150,8 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
|
||||
const SizedBox(height: 10),
|
||||
buildAgreement(),
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 80),
|
||||
_buildFooterSlogan()
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -155,7 +160,6 @@ 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,
|
||||
@@ -388,13 +392,28 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
content: _buildDialogContent(),
|
||||
confirmText: '同意',
|
||||
cancelText: '拒绝',
|
||||
onConfirm: () {
|
||||
onConfirm: () async {
|
||||
_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);
|
||||
@@ -536,6 +555,62 @@ 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 {
|
||||
|
||||
@@ -25,11 +25,14 @@ 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 {
|
||||
@@ -63,9 +66,12 @@ 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) {
|
||||
@@ -110,6 +116,11 @@ 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);
|
||||
|
||||
@@ -162,13 +162,13 @@ packages:
|
||||
source: hosted
|
||||
version: "3.2.4"
|
||||
device_info_plus:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
|
||||
sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.1.2"
|
||||
version: "12.3.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -798,7 +798,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
@@ -957,6 +957,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
saver_gallery:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: saver_gallery
|
||||
sha256: "1d942bd7f4fedc162d9a751e156ebac592e4b81fc2e757af82de9077f3437003"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1222,10 +1230,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
|
||||
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
version: "2.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -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.3+6
|
||||
version: 1.2.4+7
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# Dependencies specify other packages tha。。。t 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
|
||||
@@ -53,7 +53,8 @@ dependencies:
|
||||
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:
|
||||
@@ -64,6 +65,7 @@ dev_dependencies:
|
||||
|
||||
dependency_overrides:
|
||||
intl: 0.19.0
|
||||
device_info_plus: ^12.3.0
|
||||
|
||||
flutter:
|
||||
|
||||
|
||||