Compare commits
26 Commits
bfa615a7f4
...
f25feaa55a
| Author | SHA1 | Date | |
|---|---|---|---|
| f25feaa55a | |||
| 16639e2384 | |||
| 20ef495571 | |||
| 285a20f070 | |||
| baee5dba83 | |||
| 7d9c879a4e | |||
| 953e5e773c | |||
| 8a4bc1d1ab | |||
| c57c849073 | |||
| 6cc123f272 | |||
| 5168b23609 | |||
| c5299dd655 | |||
| 4bedd8c04b | |||
| 295b71c819 | |||
| 6629c8047f | |||
| 288d629f99 | |||
| 15fdbc7043 | |||
| 266a43c09d | |||
| 8434301d1f | |||
| adfe3bf34e | |||
| 6a3c6db7a8 | |||
| 88b24b5228 | |||
| ee8cbde296 | |||
| 93a39e440a | |||
| ec1c554eb3 | |||
| 684f0445ba |
@@ -21,13 +21,13 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.lnkj.ln_jq_app"
|
||||
applicationId = "com.lingniu.driver"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
versionCode = 4
|
||||
versionName = "1.2.1"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<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="小羚羚"
|
||||
@@ -48,6 +50,64 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- 请填写你自己的- appKey -->
|
||||
<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="" />
|
||||
|
||||
|
||||
|
||||
<!-- 接收推送消息 -->
|
||||
<receiver
|
||||
android:name="com.aliyun.ams.push.AliyunPushMessageReceiver"
|
||||
android:exported="false"> <!-- 为保证receiver安全,建议设置不可导出,如需对其他应用开放可通过android:permission进行限制 -->
|
||||
<intent-filter>
|
||||
<action android:name="com.alibaba.push2.action.NOTIFICATION_OPENED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.alibaba.push2.action.NOTIFICATION_REMOVED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.alibaba.sdk.android.push.RECEIVE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- 辅助弹窗Activity -->
|
||||
<activity
|
||||
android:name="com.aliyun.ams.push.PushPopupActivity"
|
||||
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" />
|
||||
|
||||
<data
|
||||
android:host="${applicationId}"
|
||||
android:path="/thirdpush"
|
||||
android:scheme="agoo" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
|
||||
@@ -3,6 +3,17 @@ allprojects {
|
||||
// 使用阿里云镜像
|
||||
maven("https://maven.aliyun.com/repository/public")
|
||||
maven("https://maven.aliyun.com/repository/google")
|
||||
maven(
|
||||
"https://maven.aliyun.com/nexus/content/repositories/releases/"
|
||||
)
|
||||
// 集成华为通道需要配置 HMS Core SDK 的 Maven地址
|
||||
maven(
|
||||
"https://developer.huawei.com/repo/"
|
||||
)
|
||||
|
||||
maven(
|
||||
"https://developer.hihonor.com/repo"
|
||||
)
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '13.0'
|
||||
platform :ios, '13.0'
|
||||
|
||||
source 'https://cdn.cocoapods.org/'
|
||||
source 'https://gitee.com/aliyun/aliyun-specs.git'
|
||||
source 'https://github.com/aliyun/aliyun-specs.git'
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
PODS:
|
||||
- AlicloudELS (1.0.3)
|
||||
- AlicloudPush (3.2.3):
|
||||
- AlicloudELS (= 1.0.3)
|
||||
- AlicloudUTDID (~> 1.0)
|
||||
- AlicloudUTDID (1.6.1)
|
||||
- aliyun_push_flutter (0.0.1):
|
||||
- AlicloudPush (< 4.0, >= 3.2.3)
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
@@ -38,6 +46,7 @@ PODS:
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- aliyun_push_flutter (from `.symlinks/plugins/aliyun_push_flutter/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
@@ -54,10 +63,17 @@ DEPENDENCIES:
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
https://gitee.com/aliyun/aliyun-specs.git:
|
||||
- AlicloudUTDID
|
||||
https://github.com/aliyun/aliyun-specs.git:
|
||||
- AlicloudELS
|
||||
- AlicloudPush
|
||||
trunk:
|
||||
- OrderedSet
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
aliyun_push_flutter:
|
||||
:path: ".symlinks/plugins/aliyun_push_flutter/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
@@ -88,6 +104,10 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
|
||||
AlicloudPush: 95150880af380f64cf1741f5586047c17d36c1d9
|
||||
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
|
||||
aliyun_push_flutter: 0fc2f048a08687ef256c0cfdd72dd7a550ef3347
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
@@ -104,6 +124,6 @@ SPEC CHECKSUMS:
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
|
||||
PODFILE CHECKSUM: 6416011b1bc721211379eaad259ff1cba3dbfad2
|
||||
PODFILE CHECKSUM: 357c01ff4e7591871e8c4fd6462220a8c7447220
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
4B58A54CFC9A912F2BA04FF2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
6AF04C5CFFF0B4098EEDA799 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6D3F89E22F04C32900A154AD /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -143,6 +144,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6D3F89E22F04C32900A154AD /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
@@ -487,7 +489,10 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -495,11 +500,89 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-l\"swiftCoreGraphics\"",
|
||||
"-framework",
|
||||
"\"OrderedSet\"",
|
||||
"-framework",
|
||||
"\"connectivity_plus\"",
|
||||
"-framework",
|
||||
"\"device_info_plus\"",
|
||||
"-framework",
|
||||
"\"flutter_inappwebview_ios\"",
|
||||
"-framework",
|
||||
"\"flutter_native_splash\"",
|
||||
"-framework",
|
||||
"\"flutter_pdfview\"",
|
||||
"-framework",
|
||||
"\"geolocator_apple\"",
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"path_provider_foundation\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
"-framework",
|
||||
"\"shared_preferences_foundation\"",
|
||||
"-framework",
|
||||
"\"url_launcher_ios\"",
|
||||
"-framework",
|
||||
"\"AlicloudELS\"",
|
||||
"-framework",
|
||||
"\"CloudPushSDK\"",
|
||||
"-framework",
|
||||
"\"aliyun_push_flutter\"",
|
||||
);
|
||||
"OTHER_LDFLAGS[arch=*]" = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-l\"swiftCoreGraphics\"",
|
||||
"-framework",
|
||||
"\"OrderedSet\"",
|
||||
"-framework",
|
||||
"\"connectivity_plus\"",
|
||||
"-framework",
|
||||
"\"device_info_plus\"",
|
||||
"-framework",
|
||||
"\"flutter_inappwebview_ios\"",
|
||||
"-framework",
|
||||
"\"flutter_native_splash\"",
|
||||
"-framework",
|
||||
"\"flutter_pdfview\"",
|
||||
"-framework",
|
||||
"\"geolocator_apple\"",
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"path_provider_foundation\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
"-framework",
|
||||
"\"shared_preferences_foundation\"",
|
||||
"-framework",
|
||||
"\"url_launcher_ios\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lnkj.lnJqApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
@@ -674,7 +757,10 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -682,12 +768,90 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-l\"swiftCoreGraphics\"",
|
||||
"-framework",
|
||||
"\"OrderedSet\"",
|
||||
"-framework",
|
||||
"\"connectivity_plus\"",
|
||||
"-framework",
|
||||
"\"device_info_plus\"",
|
||||
"-framework",
|
||||
"\"flutter_inappwebview_ios\"",
|
||||
"-framework",
|
||||
"\"flutter_native_splash\"",
|
||||
"-framework",
|
||||
"\"flutter_pdfview\"",
|
||||
"-framework",
|
||||
"\"geolocator_apple\"",
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"path_provider_foundation\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
"-framework",
|
||||
"\"shared_preferences_foundation\"",
|
||||
"-framework",
|
||||
"\"url_launcher_ios\"",
|
||||
"-framework",
|
||||
"\"AlicloudELS\"",
|
||||
"-framework",
|
||||
"\"CloudPushSDK\"",
|
||||
"-framework",
|
||||
"\"aliyun_push_flutter\"",
|
||||
);
|
||||
"OTHER_LDFLAGS[arch=*]" = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-l\"swiftCoreGraphics\"",
|
||||
"-framework",
|
||||
"\"OrderedSet\"",
|
||||
"-framework",
|
||||
"\"connectivity_plus\"",
|
||||
"-framework",
|
||||
"\"device_info_plus\"",
|
||||
"-framework",
|
||||
"\"flutter_inappwebview_ios\"",
|
||||
"-framework",
|
||||
"\"flutter_native_splash\"",
|
||||
"-framework",
|
||||
"\"flutter_pdfview\"",
|
||||
"-framework",
|
||||
"\"geolocator_apple\"",
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"path_provider_foundation\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
"-framework",
|
||||
"\"shared_preferences_foundation\"",
|
||||
"-framework",
|
||||
"\"url_launcher_ios\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lnkj.lnJqApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -698,7 +862,10 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = 2228B9MS38;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -706,11 +873,56 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.2.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-l\"swiftCoreGraphics\"",
|
||||
"-framework",
|
||||
"\"OrderedSet\"",
|
||||
"-framework",
|
||||
"\"connectivity_plus\"",
|
||||
"-framework",
|
||||
"\"device_info_plus\"",
|
||||
"-framework",
|
||||
"\"flutter_inappwebview_ios\"",
|
||||
"-framework",
|
||||
"\"flutter_native_splash\"",
|
||||
"-framework",
|
||||
"\"flutter_pdfview\"",
|
||||
"-framework",
|
||||
"\"geolocator_apple\"",
|
||||
"-framework",
|
||||
"\"image_picker_ios\"",
|
||||
"-framework",
|
||||
"\"mobile_scanner\"",
|
||||
"-framework",
|
||||
"\"package_info_plus\"",
|
||||
"-framework",
|
||||
"\"path_provider_foundation\"",
|
||||
"-framework",
|
||||
"\"permission_handler_apple\"",
|
||||
"-framework",
|
||||
"\"shared_preferences_foundation\"",
|
||||
"-framework",
|
||||
"\"url_launcher_ios\"",
|
||||
"-framework",
|
||||
"\"AlicloudELS\"",
|
||||
"-framework",
|
||||
"\"CloudPushSDK\"",
|
||||
"-framework",
|
||||
"\"aliyun_push_flutter\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lnkj.lnJqApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -31,25 +31,25 @@
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"filename" : "Icon-App-80x80.jpg",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"filename" : "Icon-App-120x120.jpg",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "icon 1.png",
|
||||
"filename" : "Icon-App-120x120 1.jpg",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"filename" : "Icon-App-180.jpg",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 707 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 848 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -63,5 +63,12 @@
|
||||
</array>
|
||||
<key>uses</key>
|
||||
<string></string>
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
8
ln_jq_app/ios/Runner/Runner.entitlements
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -6,7 +6,6 @@ class BaseModel<T> {
|
||||
final String message; // 消息,例如 "success"
|
||||
final String msg; // 消息,例如 "success"
|
||||
final T? data; // 核心数据,使用泛型 T,可以是任何类型
|
||||
final int time; // 时间戳
|
||||
final dynamic error; // 错误信息,可以是任何类型或 null
|
||||
|
||||
BaseModel({
|
||||
@@ -15,7 +14,6 @@ class BaseModel<T> {
|
||||
required this.message,
|
||||
required this.msg,
|
||||
this.data, // data 可以为 null
|
||||
required this.time,
|
||||
this.error, // error 可以为 null
|
||||
});
|
||||
|
||||
@@ -60,7 +58,6 @@ class BaseModel<T> {
|
||||
status: json['status'] as bool? ?? false,
|
||||
message: json['message'] ?? '暂不可用,请稍后',
|
||||
msg: json['msg'] ?? '暂不可用,请稍后',
|
||||
time: _parseInt(json['time']),
|
||||
data: finalData,
|
||||
error: json['error'],
|
||||
);
|
||||
@@ -72,7 +69,6 @@ class BaseModel<T> {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'msg': msg,
|
||||
'time': time,
|
||||
'data': data,
|
||||
'error': error,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ class VehicleInfo {
|
||||
final String engineNum;
|
||||
final String truckNum;
|
||||
final num hydrogenCapacity;
|
||||
final num maxHydrogen;
|
||||
final String maxHydrogen;
|
||||
|
||||
VehicleInfo({
|
||||
required this.plateNumber,
|
||||
@@ -36,7 +36,7 @@ class VehicleInfo {
|
||||
engineNum: json["engineNum"] ?? '',
|
||||
truckNum: json["truckNum"] ?? '',
|
||||
hydrogenCapacity: json["hydrogenCapacity"] ?? 0,
|
||||
maxHydrogen: json["maxHydrogen"] ?? 0,
|
||||
maxHydrogen: json["maxHydrogen"] ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
||||
@@ -7,8 +7,12 @@ class AppTheme {
|
||||
|
||||
static const Color themeColor = Color(0xFF0c83c3);
|
||||
|
||||
//是否开放域名切换
|
||||
static const bool is_show_host = true;
|
||||
|
||||
//http://192.168.110.222:8080/
|
||||
static const String test_service_url = "https://beta-esg.api.lnh2e.com/";
|
||||
//http://192.168.110.44:8080/
|
||||
static String test_service_url = "https://beta-esg.api.lnh2e.com/";
|
||||
static const String release_service_url = "";
|
||||
|
||||
//加氢站相关查询
|
||||
@@ -22,6 +26,11 @@ class AppTheme {
|
||||
|
||||
static const Color darkThemeColor = Color(0xFF032896);
|
||||
|
||||
static const String android_key = "335642645";
|
||||
static const String android_appsecret = "39628204345a4240b5b645b68a5896c7";
|
||||
static const String ios_key = "335642649";
|
||||
static const String ios_appsecret = "173bc08bd5df422da20c8e3ffbf0521b";
|
||||
|
||||
/// 亮色主题样式
|
||||
static ThemeData light = ThemeData(
|
||||
useMaterial3: false,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
/// 专门用于处理和添加 Token 的拦截器
|
||||
class TokenInterceptor extends Interceptor {
|
||||
// 定义您想要使用的 Token Key
|
||||
final String tokenKey;
|
||||
final String sourceKey;
|
||||
|
||||
// 构造函数,允许外部传入自定义的 Key,默认为 'Authorization'
|
||||
TokenInterceptor({this.tokenKey = 'Authorization'});
|
||||
TokenInterceptor({this.tokenKey = 'Authorization', this.sourceKey = 'source'});
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async{
|
||||
// 从 StorageService 中获取已保存的 token
|
||||
final String? token = StorageService.to.token;
|
||||
|
||||
@@ -23,6 +27,21 @@ class TokenInterceptor extends Interceptor {
|
||||
}
|
||||
}
|
||||
|
||||
String platformSource;
|
||||
if (Platform.isAndroid) {
|
||||
platformSource = 'Android';
|
||||
} else if (Platform.isIOS) {
|
||||
platformSource = 'iOS';
|
||||
} else {
|
||||
platformSource = '';
|
||||
}
|
||||
if (!options.headers.containsKey(sourceKey)) {
|
||||
options.headers[sourceKey] = platformSource;
|
||||
}
|
||||
|
||||
options.headers['appVersion'] = await getVersion();
|
||||
options.headers['brand'] = await getDeviceModel();
|
||||
|
||||
// 调用 handler.next(options) 以继续执行请求
|
||||
// 这一步至关重要,否则请求会被中断
|
||||
super.onRequest(options, handler);
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:getx_scaffold/getx_scaffold.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';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import 'common/styles/theme.dart';
|
||||
import 'pages/home/view.dart';
|
||||
@@ -47,6 +48,8 @@ void main() async {
|
||||
fallbackLocale: Locale('zh', 'CN'),
|
||||
supportedLocales: [Locale('zh', 'CN')],
|
||||
localizationsDelegates: const [
|
||||
//pull_to_refresh
|
||||
RefreshLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
@@ -62,6 +65,8 @@ void main() async {
|
||||
}
|
||||
|
||||
void initHttpSet() {
|
||||
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
|
||||
|
||||
// 设置基础 URL
|
||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||
//指定请求头
|
||||
@@ -69,13 +74,18 @@ void initHttpSet() {
|
||||
// 设置全局响应处理器
|
||||
HttpService.to.setOnResponseHandler((response) async {
|
||||
try {
|
||||
final baseModel = BaseModel<dynamic>.fromJson(response.data);
|
||||
final baseModel = BaseModel.fromJson(response.data);
|
||||
if (baseModel.code == 0 || baseModel.code == 200) {
|
||||
|
||||
return null;
|
||||
} else if (baseModel.code == 401) {
|
||||
await StorageService.to.clearLoginInfo();
|
||||
Get.offAll(() => LoginPage());
|
||||
return baseModel.message;
|
||||
} else {
|
||||
return (baseModel.error.toString()).isEmpty
|
||||
? "服务繁忙,稍后重试"
|
||||
: baseModel.error.toString();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
e.printInfo();
|
||||
|
||||
@@ -1,42 +1,123 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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/login/view.dart';
|
||||
|
||||
import '../../../common/styles/theme.dart';
|
||||
import '../../../storage_service.dart';
|
||||
|
||||
class ReservationController extends GetxController with BaseControllerMixin {
|
||||
@override
|
||||
String get builderId => 'b_reservation'; // 确保ID与View中一致
|
||||
String get builderId => 'b_reservation';
|
||||
|
||||
// --- 运营状态下拉菜单所需的状态 ---
|
||||
// 下拉菜单的选项列表
|
||||
final List<String> operationStatusOptions = ["营运中", "维修中", "暂停营业", "站点关闭"];
|
||||
|
||||
// 当前选中的值,默认为'运营中'
|
||||
String selectedOperationStatus = "营运中";
|
||||
|
||||
// --- 其它状态下的时间选择 ---
|
||||
DateTime? customStartTime;
|
||||
DateTime? customEndTime;
|
||||
|
||||
String get customStartTimeStr => customStartTime != null
|
||||
? DateFormat('yyyy-MM-dd HH:mm').format(customStartTime!)
|
||||
: '点击选择开始时间';
|
||||
|
||||
String get customEndTimeStr => customEndTime != null
|
||||
? DateFormat('yyyy-MM-dd HH:mm').format(customEndTime!)
|
||||
: '点击选择结束时间';
|
||||
|
||||
// --- 站点广播相关 ---
|
||||
final TextEditingController broadcastTitleController = TextEditingController();
|
||||
final TextEditingController broadcastContentController = TextEditingController();
|
||||
final RxInt selectedTabIndex = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// 1. 初始化默认时间
|
||||
customStartTime = DateTime.now();
|
||||
customEndTime = customStartTime!.add(const Duration(days: 1));
|
||||
renderData();
|
||||
}
|
||||
|
||||
String name = "";
|
||||
String address = "";
|
||||
String phone = "";
|
||||
String costPrice = ""; //氢气价格
|
||||
String customerPrice = ""; //官方价格
|
||||
String costPrice = "";
|
||||
String customerPrice = "";
|
||||
String startBusiness = "";
|
||||
String endBusiness = "";
|
||||
String timeStr = "";
|
||||
String operatingEnterprise = "";
|
||||
String hydrogenId = "";
|
||||
|
||||
String jobTipStr = "";
|
||||
String jobDetailsStr = "";
|
||||
String jobId = "";
|
||||
|
||||
Future<void> renderData() async {
|
||||
showLoading("加载中");
|
||||
|
||||
try {
|
||||
//获取加氢站未执行的状态修改任务信息
|
||||
var jobData = await HttpService.to.get('appointment/job/hyd/un-executed');
|
||||
if (jobData != null) {
|
||||
final jobDataResult = BaseModel.fromJson(jobData.data);
|
||||
if (jobDataResult.code == 0) {
|
||||
try{
|
||||
jobId = jobDataResult.data["id"] ?? "";
|
||||
String endTime = jobDataResult.data["endTime"] ?? "";
|
||||
String beginTime = jobDataResult.data["beginTime"] ?? "";
|
||||
String hydStatus = jobDataResult.data["hydStatus"] ?? "";
|
||||
String hydStatusStr = "";
|
||||
if (hydStatus == "0") {
|
||||
hydStatusStr = "营运中";
|
||||
} else if (hydStatus == "1") {
|
||||
hydStatusStr = "维修中";
|
||||
} else if (hydStatus == "2") {
|
||||
hydStatusStr = "站点关闭";
|
||||
} else if (hydStatus == "3") {
|
||||
hydStatusStr = "暂停营业";
|
||||
}
|
||||
|
||||
jobDetailsStr = "当前站点已设置$beginTime-$endTime为$hydStatusStr状态";
|
||||
|
||||
if (endTime.isNotEmpty) {
|
||||
try {
|
||||
// 解析时间字符串
|
||||
DateTime endDateTime = DateTime.parse(endTime);
|
||||
DateTime beginDateTime = DateTime.parse(beginTime);
|
||||
DateTime now = DateTime.now(); // 2. 计算时间差 (endTime - now)
|
||||
Duration diff = endDateTime.difference(now);
|
||||
|
||||
// 计算小时数 (允许小数,例如 0.5)
|
||||
// inMinutes / 60 可以得到更精确的小数小时
|
||||
double hoursLeft = diff.inMinutes / 60.0;
|
||||
|
||||
if (hoursLeft > 0) {
|
||||
// 如果是正数,表示还有多久结束
|
||||
String timeTip = "${hoursLeft.toStringAsFixed(1)}小时后";
|
||||
jobTipStr = "$timeTip$hydStatusStr";
|
||||
} else {
|
||||
jobTipStr = "";
|
||||
}
|
||||
|
||||
// 如果是处于非营运状态,自动回填开始和结束时间
|
||||
// 假设 customStartTime 是现在,customEndTime 是接口返回的结束时间
|
||||
customStartTime = beginDateTime;
|
||||
customEndTime = endDateTime;
|
||||
} catch (e) {
|
||||
print("时间解析失败: $e");
|
||||
}
|
||||
}
|
||||
}catch (e){
|
||||
Logger.d("解析失败: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取站点信息
|
||||
var responseData = await HttpService.to.get(
|
||||
'appointment/station/getStationInfoById?hydrogenId=${StorageService.to.userId}',
|
||||
);
|
||||
@@ -49,32 +130,30 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
try {
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
|
||||
name = result.data["name"];
|
||||
name = result.data["name"] ?? "";
|
||||
hydrogenId = result.data["hydrogenId"].toString();
|
||||
address = result.data["address"];
|
||||
var rawCostPrice = result.data["costPrice"];
|
||||
if (rawCostPrice != null && rawCostPrice.toString().isNotEmpty) {
|
||||
costPrice = "¥$rawCostPrice";
|
||||
} else {
|
||||
costPrice = "暂无价格";
|
||||
}
|
||||
var customerPriceTemp = result.data["customerPrice"];
|
||||
if (customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty) {
|
||||
customerPrice = "$customerPriceTemp";
|
||||
} else {
|
||||
customerPrice = "暂无价格";
|
||||
}
|
||||
address = result.data["address"] ?? "";
|
||||
|
||||
phone = result.data["phone"];
|
||||
startBusiness = result.data["startBusiness"];
|
||||
endBusiness = result.data["endBusiness"];
|
||||
var rawCostPrice = result.data["costPrice"];
|
||||
costPrice = (rawCostPrice != null && rawCostPrice.toString().isNotEmpty)
|
||||
? "¥$rawCostPrice"
|
||||
: "暂无价格";
|
||||
|
||||
var customerPriceTemp = result.data["customerPrice"];
|
||||
customerPrice =
|
||||
(customerPriceTemp != null && customerPriceTemp.toString().isNotEmpty)
|
||||
? "$customerPriceTemp"
|
||||
: "暂无价格";
|
||||
|
||||
phone = result.data["phone"] ?? "";
|
||||
startBusiness = result.data["startBusiness"] ?? "";
|
||||
endBusiness = result.data["endBusiness"] ?? "";
|
||||
operatingEnterprise = result.data["operatingEnterprise"].toString();
|
||||
|
||||
String temp = result.data["siteStatusName"].toString();
|
||||
selectedOperationStatus = (temp.isEmpty || temp == "null") ? "营运中" : temp;
|
||||
|
||||
if (startBusiness.isNotEmpty && endBusiness.isNotEmpty) {
|
||||
// 营业时间为24小时的特殊处理
|
||||
if (startBusiness == "00:00:00" && endBusiness == "23:59:59") {
|
||||
timeStr = "24小时营业";
|
||||
} else {
|
||||
@@ -86,19 +165,18 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
operatingEnterprise = operatingEnterprise.isEmpty ? "暂未设置" : operatingEnterprise;
|
||||
|
||||
updateUi();
|
||||
dismissLoading();
|
||||
} catch (e) {
|
||||
// 如果解析 JSON 失败
|
||||
dismissLoading();
|
||||
showToast('数据异常');
|
||||
}
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
} finally {
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新运营状态的方法
|
||||
void onOperationStatusChanged(String? newValue) {
|
||||
if (newValue != null) {
|
||||
selectedOperationStatus = newValue;
|
||||
@@ -106,9 +184,99 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void saveInfo() async {
|
||||
showLoading("保存中");
|
||||
/// 使用底部滚轮形式选择时间(下拉框效果)
|
||||
void pickDateTime(BuildContext context, bool isStart) {
|
||||
DateTime now = DateTime.now();
|
||||
DateTime initialDate = isStart ? (customStartTime ?? now) : (customEndTime ?? now);
|
||||
|
||||
DateTime minLimit = isStart
|
||||
? now.subtract(const Duration(minutes: 1))
|
||||
: (customStartTime ?? now).subtract(const Duration(minutes: 1));
|
||||
|
||||
if (initialDate.isBefore(minLimit)) {
|
||||
initialDate = isStart ? now : (customStartTime ?? now);
|
||||
}
|
||||
|
||||
DateTime tempDate = initialDate;
|
||||
|
||||
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, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CupertinoButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消', style: TextStyle(color: Colors.grey)),
|
||||
),
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
if (isStart) {
|
||||
customStartTime = tempDate;
|
||||
if (customEndTime != null &&
|
||||
customEndTime!.isBefore(customStartTime!)) {
|
||||
customEndTime = customStartTime!.add(const Duration(days: 1));
|
||||
}
|
||||
} else {
|
||||
if (tempDate.isBefore(customStartTime ?? DateTime.now())) {
|
||||
showToast('结束时间不能早于开始时间');
|
||||
return;
|
||||
}
|
||||
customEndTime = tempDate;
|
||||
}
|
||||
updateUi();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text(
|
||||
'确定',
|
||||
style: TextStyle(
|
||||
color: AppTheme.themeColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.dateAndTime,
|
||||
initialDateTime: initialDate,
|
||||
minimumDate: minLimit,
|
||||
use24hFormat: true,
|
||||
onDateTimeChanged: (DateTime newDate) {
|
||||
tempDate = newDate;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
}
|
||||
|
||||
void saveInfo() async {
|
||||
if (selectedOperationStatus != "营运中") {
|
||||
if (customStartTime == null || customEndTime == null) {
|
||||
showToast("请选择开始和结束时间");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showLoading("保存中");
|
||||
try {
|
||||
var responseData = await HttpService.to.post(
|
||||
'appointment/station/updateStationStatus',
|
||||
@@ -123,18 +291,24 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
: selectedOperationStatus == "暂停营业"
|
||||
? "3"
|
||||
: 0,
|
||||
'beginTime': selectedOperationStatus == "营运中"
|
||||
? null
|
||||
: DateFormat('yyyy-MM-dd HH:mm:ss').format(customStartTime!),
|
||||
'endTime': selectedOperationStatus == "营运中"
|
||||
? null
|
||||
: DateFormat('yyyy-MM-dd HH:mm:ss').format(customEndTime!),
|
||||
'updateTime': getNowDateTimeString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (responseData == null && responseData!.data == null) {
|
||||
if (responseData == null || responseData.data == null) {
|
||||
dismissLoading();
|
||||
showToast('服务暂不可用,请稍后');
|
||||
return;
|
||||
}
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("保存成功");
|
||||
showSuccessToast("保存成功,已同步通知对应司机");
|
||||
}
|
||||
dismissLoading();
|
||||
} catch (e) {
|
||||
@@ -142,11 +316,99 @@ class ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void logout() async {
|
||||
// TODO: 在这里执行退出登录的逻辑
|
||||
//清理本地缓存的用户信息 导航到登录页面
|
||||
await StorageService.to.clearLoginInfo();
|
||||
/// 显示当前未执行任务的详情弹窗
|
||||
void showJob() {
|
||||
if (jobDetailsStr.isEmpty) {
|
||||
showToast("当前没有正在生效的任务设置");
|
||||
return;
|
||||
}
|
||||
|
||||
DialogX.to.showConfirmDialog(
|
||||
title: '当前设置详情',
|
||||
content: Text(
|
||||
jobDetailsStr,
|
||||
style: const TextStyle(fontSize: 15, height: 1.5),
|
||||
),
|
||||
confirmText: '好的',
|
||||
cancelText: '取消设置',
|
||||
onCancel: () {
|
||||
// 点击“取消设置”调用删除接口
|
||||
_cancelJob();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 内部私有方法:调用取消/删除任务接口
|
||||
void _cancelJob() async {
|
||||
showLoading("正在取消...");
|
||||
try {
|
||||
var response = await HttpService.to.delete(
|
||||
'appointment/job/hyd/$jobId',
|
||||
);
|
||||
|
||||
dismissLoading();
|
||||
if (response != null) {
|
||||
var result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("已成功取消该设置");
|
||||
// 成功后重新刷新页面数据,重置状态
|
||||
renderData();
|
||||
} else {
|
||||
showErrorToast(result.error);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
showErrorToast("取消失败,请稍后重试");
|
||||
Logger.d("取消任务失败: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送站点广播
|
||||
void sendBroadcast() async {
|
||||
String title = broadcastTitleController.text.trim();
|
||||
String content = broadcastContentController.text.trim();
|
||||
|
||||
if (title.isEmpty) {
|
||||
showToast("请输入通知标题");
|
||||
return;
|
||||
}
|
||||
if (content.isEmpty) {
|
||||
showToast("请输入通知内容");
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading("发送中...");
|
||||
try {
|
||||
var responseData = await HttpService.to.post(
|
||||
'appointment/notice/push/station/broadcast',
|
||||
data: {'title': title, 'content': content},
|
||||
);
|
||||
|
||||
dismissLoading();
|
||||
if (responseData != null) {
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("广播发送成功");
|
||||
} else {
|
||||
showErrorToast(result.error);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
showToast("发送失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
void logout() async {
|
||||
await StorageService.to.clearLoginInfo();
|
||||
Get.offAll(() => LoginPage());
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
broadcastTitleController.dispose();
|
||||
broadcastContentController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
leading: const Icon(Icons.local_gas_station, color: Colors.blue, size: 40),
|
||||
title: Text(
|
||||
controller.name,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
),
|
||||
subtitle: Text(controller.address),
|
||||
trailing: Container(
|
||||
@@ -57,7 +57,7 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
),
|
||||
child: Text(
|
||||
controller.selectedOperationStatus,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
@@ -100,102 +100,234 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建包含所有信息表单的卡片
|
||||
/// 构建包含所有信息表单的卡片(增加 Tab 切换功能)
|
||||
Widget _buildInfoFormCard(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- 基本信息 ---
|
||||
_buildSectionTitle('基本信息'),
|
||||
_buildDisplayField(label: '站点名称', value: controller.name),
|
||||
_buildDisplayField(label: '运营企业', value: controller.operatingEnterprise),
|
||||
_buildDisplayField(label: '站点地址', value: controller.address),
|
||||
const SizedBox(height: 16),
|
||||
clipBehavior: Clip.antiAlias, // 确保 Tab 背景圆角生效
|
||||
child: Column(
|
||||
children: [
|
||||
// Tab 切换栏
|
||||
Obx(
|
||||
() => Container(
|
||||
color: Colors.grey[50],
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTabItem(0, Icons.business_outlined, '站点信息'),
|
||||
_buildTabItem(1, Icons.campaign_outlined, '站点广播'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
// 内容区域
|
||||
Obx(
|
||||
() => controller.selectedTabIndex.value == 0
|
||||
? _buildStationInfo(context)
|
||||
: _buildStationBroadcast(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- 价格信息 ---
|
||||
_buildSectionTitle('价格信息'),
|
||||
// _buildDisplayField(label: '氢气价格 (元/kg)', value: controller.costPrice),
|
||||
_buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice),
|
||||
const SizedBox(height: 16),
|
||||
/// 构建单个 Tab 项
|
||||
Widget _buildTabItem(int index, IconData icon, String label) {
|
||||
bool isSelected = controller.selectedTabIndex.value == index;
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => controller.selectedTabIndex.value = index,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 20, color: isSelected ? Colors.blue : Colors.grey[600]),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
color: isSelected ? Colors.blue : Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- 运营信息 ---
|
||||
_buildSectionTitle('运营信息'),
|
||||
Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
//下拉选择框
|
||||
DropdownButtonFormField<String>(
|
||||
value: controller.selectedOperationStatus,
|
||||
items: controller.operationStatusOptions.map((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: controller.onOperationStatusChanged,
|
||||
/// 站点信息子视图
|
||||
Widget _buildStationInfo(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('基本信息'),
|
||||
_buildDisplayField(label: '站点名称', value: controller.name),
|
||||
_buildDisplayField(label: '运营企业', value: controller.operatingEnterprise),
|
||||
_buildDisplayField(label: '站点地址', value: controller.address),
|
||||
const SizedBox(height: 16),
|
||||
_buildSectionTitle('价格信息'),
|
||||
_buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice),
|
||||
const SizedBox(height: 16),
|
||||
_buildSectionTitle('运营信息'),
|
||||
Row(
|
||||
children: [
|
||||
Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
|
||||
//加氢站未执行的状态修改任务
|
||||
if (controller.jobTipStr.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: controller.showJob,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
controller.jobTipStr,
|
||||
style: TextStyle(color: Colors.yellow[800], fontSize: 14),
|
||||
),
|
||||
Icon(AntdIcon.question_circle, size: 14, color: Colors.yellow[800]),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: controller.selectedOperationStatus,
|
||||
items: controller.operationStatusOptions.map((String value) {
|
||||
return DropdownMenuItem<String>(value: value, child: Text(value));
|
||||
}).toList(),
|
||||
onChanged: controller.onOperationStatusChanged,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (controller.selectedOperationStatus == "营运中")
|
||||
_buildDisplayField(label: '营业时间', value: controller.timeStr)
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
_buildClickField(
|
||||
label: '开始时间',
|
||||
value: controller.customStartTimeStr,
|
||||
onTap: () => controller.pickDateTime(context, true),
|
||||
),
|
||||
_buildClickField(
|
||||
label: '结束时间',
|
||||
value: controller.customEndTimeStr,
|
||||
onTap: () => controller.pickDateTime(context, false),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildDisplayField(label: '联系电话', value: controller.phone),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: controller.saveInfo,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: const Text('保存信息', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 站点广播子视图
|
||||
Widget _buildStationBroadcast(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.campaign, color: Colors.blue, size: 28),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'站点广播通知',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 13),
|
||||
_buildTextFieldLabel('通知标题'),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 45.h,
|
||||
child: TextField(
|
||||
controller: controller.broadcastTitleController,
|
||||
maxLength: 30,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
hintText: '例如:临时闭站通知',
|
||||
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
counterText: '', // 隐藏原生计数器,我们可以按需自定义
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildDisplayField(label: '营业时间', value: controller.timeStr),
|
||||
_buildDisplayField(label: '联系电话', value: controller.phone),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
//保存按钮
|
||||
ElevatedButton(
|
||||
onPressed: controller.saveInfo,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: const Text('保存信息', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildTextFieldLabel('通知内容'),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: controller.broadcastContentController,
|
||||
maxLength: 150,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入通知内容...',
|
||||
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ElevatedButton(
|
||||
onPressed: controller.sendBroadcast,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
elevation: 0,
|
||||
),
|
||||
child: const Text(
|
||||
'发送',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建静态提示信息卡片
|
||||
Widget _buildTipsCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoItem(Icons.info_outline, '请确保信息准确无误'),
|
||||
const SizedBox(height: 10),
|
||||
_buildInfoItem(Icons.help_outline, '价格信息将实时更新到用户端'),
|
||||
const SizedBox(height: 10),
|
||||
_buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系技术支持: 400-021-1773'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建退出登录按钮
|
||||
Widget _buildLogoutButton() {
|
||||
return ElevatedButton(
|
||||
onPressed: controller.logout,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
elevation: 2,
|
||||
),
|
||||
child: const Text(
|
||||
'退出登录',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
Widget _buildTextFieldLabel(String label) {
|
||||
return Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -227,7 +359,7 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200], // 使用灰色背景以区分
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
@@ -241,6 +373,108 @@ class ReservationPage extends GetView<ReservationController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建一个“可点击”的选择行
|
||||
Widget _buildClickField({
|
||||
required String label,
|
||||
required String value,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
border: Border.all(color: Colors.blue.withOpacity(0.5)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.black87),
|
||||
),
|
||||
const Icon(Icons.calendar_month, size: 18, color: Colors.blue),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建静态提示信息卡片
|
||||
Widget _buildTipsCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoItem(Icons.info_outline, '请确保信息准确无误'),
|
||||
const SizedBox(height: 10),
|
||||
_buildInfoItem(Icons.help_outline, '价格信息将实时更新到用户端'),
|
||||
const SizedBox(height: 10),
|
||||
_buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系技术支持: 400-021-1773'),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.verified_outlined, color: Colors.blue, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: FutureBuilder<String>(
|
||||
future: getVersion(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Text("");
|
||||
}
|
||||
if (snapshot.hasData) {
|
||||
return TextX.labelSmall(
|
||||
"当前版本: ${snapshot.data}",
|
||||
color: Colors.black54,
|
||||
);
|
||||
}
|
||||
return const Text("");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建退出登录按钮
|
||||
Widget _buildLogoutButton() {
|
||||
return ElevatedButton(
|
||||
onPressed: controller.logout,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
elevation: 2,
|
||||
),
|
||||
child: const Text(
|
||||
'退出登录',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建带图标的提示信息行
|
||||
Widget _buildInfoItem(IconData icon, String text) {
|
||||
return Row(
|
||||
|
||||
@@ -32,8 +32,8 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
// 对应的页面
|
||||
List<Widget> _buildPages() {
|
||||
return [
|
||||
MapPage(),
|
||||
ReservationPage(),
|
||||
MapPage(),
|
||||
CarInfoPage(),
|
||||
MinePage(),
|
||||
];
|
||||
@@ -47,16 +47,16 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
|
||||
jumpTabAndPage(index);
|
||||
}, // 切换tab事件
|
||||
items: [
|
||||
NavigationItemModel(
|
||||
label: '地图',
|
||||
icon: AntdIcon.location,
|
||||
selectedIcon: AntdIcon.location_fill,
|
||||
),
|
||||
NavigationItemModel(
|
||||
label: '加氢预约',
|
||||
icon: AntdIcon.orderedlist,
|
||||
selectedIcon: AntdIcon.calendar_fill,
|
||||
),
|
||||
NavigationItemModel(
|
||||
label: '地图',
|
||||
icon: AntdIcon.location,
|
||||
selectedIcon: AntdIcon.location_fill,
|
||||
),
|
||||
NavigationItemModel(
|
||||
label: '车辆信息',
|
||||
icon: AntdIcon.car,
|
||||
|
||||
151
ln_jq_app/lib/pages/c_page/message/controller.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import 'model.dart';
|
||||
|
||||
class MessageController extends GetxController {
|
||||
final RefreshController refreshController = RefreshController(initialRefresh: false);
|
||||
final RxList<MessageModel> messageList = <MessageModel>[].obs;
|
||||
int _pageNum = 1;
|
||||
final int _pageSize = 20;
|
||||
|
||||
// 是否所有已读状态
|
||||
final RxBool allRead = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
Future<void> _loadData({bool isRefresh = false}) async {
|
||||
final int targetPage = isRefresh ? 1 : _pageNum + 1;
|
||||
|
||||
try {
|
||||
final Map<String, dynamic> requestData = {
|
||||
'appFlag': 1,
|
||||
'pageNum': targetPage,
|
||||
'pageSize': _pageSize,
|
||||
};
|
||||
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/unread_notice/page',
|
||||
params: requestData,
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
throw Exception("Response is null");
|
||||
}
|
||||
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
|
||||
if (result.code != 0) {
|
||||
throw Exception(result.message ?? "Business error");
|
||||
}
|
||||
|
||||
final recordsList = result.data?["records"] as List? ?? [];
|
||||
|
||||
final List<MessageModel> newData = recordsList.map((e) {
|
||||
return MessageModel(
|
||||
id: e['id']?.toString() ?? '',
|
||||
title: e['noticeTitle'] ?? '',
|
||||
content: e['noticeContent'] ?? '',
|
||||
createTime: e['createTime'] ?? '',
|
||||
isRead: int.tryParse(e['isRead']?.toString() ?? '0') ?? 0,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
if (isRefresh) {
|
||||
messageList.assignAll(newData);
|
||||
_pageNum = 1; // 只有成功了才重置页码
|
||||
} else {
|
||||
messageList.addAll(newData);
|
||||
if (newData.isNotEmpty) {
|
||||
_pageNum++; // 只有成功且有数据才+1
|
||||
}
|
||||
}
|
||||
|
||||
_updateRefreshState(isRefresh, newData.length);
|
||||
|
||||
// 检查是否全部已读
|
||||
_checkAllRead();
|
||||
} catch (e) {
|
||||
Logger.d("加载失败: $e"); // 建议加上日志
|
||||
if (isRefresh) {
|
||||
refreshController.refreshFailed();
|
||||
} else {
|
||||
refreshController.loadFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 RefreshController 状态
|
||||
void _updateRefreshState(bool isRefresh, int count) {
|
||||
if (isRefresh) {
|
||||
refreshController.refreshCompleted();
|
||||
// 如果刷新后的数据量小于 pageSize,说明没有更多数据了
|
||||
if (count < _pageSize) {
|
||||
refreshController.loadNoData();
|
||||
} else {
|
||||
// 必须重置 footer 状态,否则之前如果是 NoData 状态无法再加载
|
||||
refreshController.resetNoData();
|
||||
}
|
||||
} else {
|
||||
if (count == 0) {
|
||||
refreshController.loadNoData();
|
||||
} else {
|
||||
refreshController.loadComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onRefresh() => _loadData(isRefresh: true);
|
||||
|
||||
void onLoading() => _loadData(isRefresh: false);
|
||||
|
||||
// 标记全部已读
|
||||
void markAllRead() async {
|
||||
showLoading('正在标记...');
|
||||
try {
|
||||
final response = await HttpService.to.post('appointment/unread_notice/read/all');
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0) {
|
||||
for (var msg in messageList) {
|
||||
msg.isRead = 0;
|
||||
}
|
||||
messageList.refresh();
|
||||
allRead.value = true;
|
||||
|
||||
dismissLoading();
|
||||
showSuccessToast('全部已读');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
showErrorToast('操作失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 标记单条已读(弹窗显示时调用)
|
||||
void markRead(MessageModel message) async {
|
||||
if (message.isRead == 0) return;
|
||||
|
||||
final response = await HttpService.to.post(
|
||||
'/appointment/unread_notice/read/${message.id}',
|
||||
);
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0) {
|
||||
message.isRead = 0;
|
||||
messageList.refresh();
|
||||
_checkAllRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkAllRead() {
|
||||
allRead.value = messageList.every((msg) => msg.isRead == 0);
|
||||
}
|
||||
}
|
||||
25
ln_jq_app/lib/pages/c_page/message/model.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
class MessageModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String content;
|
||||
final String createTime;
|
||||
int isRead; // 0: 已读, 1: 未读
|
||||
|
||||
MessageModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.createTime,
|
||||
required this.isRead,
|
||||
});
|
||||
|
||||
factory MessageModel.fromJson(Map<String, dynamic> json) {
|
||||
return MessageModel(
|
||||
id: json['id']?.toString() ?? '',
|
||||
title: json['title']?.toString() ?? '消息通知',
|
||||
content: json['content']?.toString() ?? '',
|
||||
createTime: json['createTime']?.toString() ?? '',
|
||||
isRead: json['isRead'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
150
ln_jq_app/lib/pages/c_page/message/view.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
import 'controller.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class MessagePage extends GetView<MessageController> {
|
||||
const MessagePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(MessageController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(title: const Text('消息通知'), centerTitle: true),
|
||||
body: Column(
|
||||
children: [
|
||||
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
|
||||
? 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()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageItem(BuildContext context, MessageModel item) {
|
||||
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]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMessageDialog(BuildContext context, MessageModel item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
item.content,
|
||||
style: const TextStyle(fontSize: 15, height: 1.5, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
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(color: Colors.blue)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
renderData();
|
||||
renderViolation();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -31,6 +30,7 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
|
||||
String rate = "";
|
||||
String rating = "";
|
||||
String accident = "";
|
||||
String historyBreakRules = "";
|
||||
String vin = "";
|
||||
@@ -38,6 +38,7 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
String violationTotal = "0";
|
||||
String violationScore = "0";
|
||||
String violationDispose = "0";
|
||||
bool isNotice = false;
|
||||
|
||||
void renderData() async {
|
||||
if (StorageService.to.hasVehicleInfo) {
|
||||
@@ -49,14 +50,17 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
plateNumber = bean.plateNumber;
|
||||
}
|
||||
|
||||
showLoading("加载中");
|
||||
|
||||
try {
|
||||
showLoading("加载中");
|
||||
await Future.wait([
|
||||
_fetchCompletionRate(), // 请求1:完成率
|
||||
_fetchAccidentCount(), // 请求2:事故数
|
||||
_fetchBreakRulesCount(), // 请求3:违章数
|
||||
_rating(), // 司机评分
|
||||
_msgNotice(), // 红点消息
|
||||
]);
|
||||
|
||||
await renderViolation();
|
||||
} catch (e, stackTrace) {
|
||||
showErrorToast("加载数据失败,请稍后重试 $e");
|
||||
} finally {
|
||||
@@ -65,6 +69,37 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _msgNotice() async {
|
||||
final Map<String, dynamic> requestData = {
|
||||
'appFlag': 1,
|
||||
'isRead': 1,
|
||||
'pageNum': 1,
|
||||
'pageSize': 5,
|
||||
};
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/unread_notice/page',
|
||||
params: requestData,
|
||||
);
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0 && result.data != null) {
|
||||
String total = result.data["total"].toString();
|
||||
isNotice = int.parse(total) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _rating() async {
|
||||
final response = await HttpService.to.get('appointment/rating/driver');
|
||||
|
||||
if (response != null) {
|
||||
final result = BaseModel.fromJson(response.data);
|
||||
if (result.code == 0 && result.data != null) {
|
||||
rating = result.data.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchCompletionRate() async {
|
||||
final response = await HttpService.to.get(
|
||||
'appointment/orderAddHyd/driverAppointmentCompletionRate?phone=${StorageService.to.phone}',
|
||||
@@ -104,15 +139,11 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void renderViolation() async {
|
||||
Future<void> renderViolation() async {
|
||||
// 违章信息查询
|
||||
final originalHeaders = Map<String, dynamic>.from(HttpService.to.dio.options.headers);
|
||||
try {
|
||||
HttpService.to.setBaseUrl(AppTheme.jiaqing_service_url);
|
||||
HttpService.to.dio.options.headers['appId'] = '97ad10eeb6b346f79e0d6ffd81e4d3c3';
|
||||
|
||||
var responseData = await HttpService.to.get(
|
||||
"vehicleService/violation/queryViolationInfo_V2?plateNum=${plateNumber}",
|
||||
"appointment/truck/queryViolationInfo_V2?plateNum=${plateNumber}",
|
||||
);
|
||||
|
||||
if (responseData == null || responseData.data == null) {
|
||||
@@ -139,8 +170,6 @@ class MineController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||
HttpService.to.dio.options.headers = originalHeaders;
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.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/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/message/view.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
@@ -78,26 +81,27 @@ class MinePage extends GetView<MineController> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.blue, width: 0.5),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// 跳转消息中心
|
||||
Get.to(() => const MessagePage());
|
||||
},
|
||||
// 这里的 style 是为了模拟你图片里的灰色圆形背景
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.grey[100],
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.shield_outlined, color: Colors.blue, size: 14),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'已认证',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
icon: Badge(
|
||||
// label: Text('3'), // 如果你想显示数字,就加 label
|
||||
smallSize: 8,
|
||||
// 红点的大小
|
||||
backgroundColor: controller.isNotice ? Colors.red : Colors.white,
|
||||
// 红点颜色
|
||||
child: Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Colors.black87,
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -155,14 +159,14 @@ class MinePage extends GetView<MineController> {
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: 10, // 假设得分80%
|
||||
value: (double.tryParse(controller.rating) ?? 0) / 10,
|
||||
strokeWidth: 8,
|
||||
backgroundColor: Colors.grey[200],
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
'10',
|
||||
controller.rating,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -311,6 +315,35 @@ class MinePage extends GetView<MineController> {
|
||||
_buildInfoItem(Icons.rule, '遵守交通规则,避免违章扣分'),
|
||||
const SizedBox(height: 10),
|
||||
_buildInfoItem(Icons.headset_mic_outlined, '如有疑问请联系客服: 400-021-1773'),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.verified_outlined, color: Colors.blue, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: FutureBuilder<String>(
|
||||
future: getVersion(),
|
||||
builder: (context, snapshot) {
|
||||
// 判断是否还在加载
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Text("");
|
||||
}
|
||||
|
||||
// 如果加载完成且有数据
|
||||
if (snapshot.hasData) {
|
||||
return TextX.labelSmall(
|
||||
"当前版本: ${snapshot.data}",
|
||||
color: Colors.black54,
|
||||
);
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
return const Text("");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -41,10 +41,10 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
final DateTime _now = DateTime.now();
|
||||
|
||||
// 计算当前时间属于哪个半小时区间
|
||||
// 计算当前时间属于哪个1小时区间
|
||||
late final TimeOfDay _initialStartTime = _calculateInitialStartTime(_now);
|
||||
late final TimeOfDay _initialEndTime = TimeOfDay.fromDateTime(
|
||||
_getDateTimeFromTimeOfDay(_initialStartTime).add(const Duration(minutes: 30)),
|
||||
_getDateTimeFromTimeOfDay(_initialStartTime).add(const Duration(minutes: 60)),
|
||||
);
|
||||
late final Rx<DateTime> selectedDate = DateTime(_now.year, _now.month, _now.day).obs;
|
||||
late final Rx<TimeOfDay> startTime = _initialStartTime.obs;
|
||||
@@ -52,13 +52,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
|
||||
/// 静态辅助方法,用于计算初始的开始时间
|
||||
static TimeOfDay _calculateInitialStartTime(DateTime now) {
|
||||
if (now.minute < 30) {
|
||||
// 如果当前分钟小于30,则开始时间为当前小时的0分
|
||||
return TimeOfDay(hour: now.hour, minute: 0);
|
||||
} else {
|
||||
// 如果当前分钟大于等于30,则开始时间为当前小时的30分
|
||||
return TimeOfDay(hour: now.hour, minute: 30);
|
||||
}
|
||||
return TimeOfDay(hour: now.hour, minute: 0);
|
||||
}
|
||||
|
||||
/// 静态辅助方法,将TimeOfDay转换为DateTime
|
||||
@@ -186,7 +180,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
}
|
||||
|
||||
///30 分钟为间隔 时间选择器
|
||||
///60 分钟为间隔 时间选择器
|
||||
void pickTime(BuildContext context) {
|
||||
final now = DateTime.now();
|
||||
final isToday =
|
||||
@@ -195,21 +189,15 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
selectedDate.value.day == now.day;
|
||||
|
||||
final List<TimeSlot> availableSlots = [];
|
||||
for (int i = 0; i < 48; i++) {
|
||||
final startMinutes = i * 30;
|
||||
final endMinutes = startMinutes + 30;
|
||||
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);
|
||||
|
||||
final slotStartDateTime = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
startTime.hour,
|
||||
startTime.minute,
|
||||
);
|
||||
|
||||
// 如果不是今天,所有时间段都有效
|
||||
if (!isToday) {
|
||||
availableSlots.add(TimeSlot(startTime, endTime));
|
||||
@@ -224,8 +212,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
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 (slotEndDateTime.isAfter(now)) {
|
||||
if (realEndDateTime.isAfter(now)) {
|
||||
availableSlots.add(TimeSlot(startTime, endTime));
|
||||
}
|
||||
}
|
||||
@@ -236,13 +232,11 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找当前选中的时间对应的新列表中的索引
|
||||
int initialItem = availableSlots.indexWhere(
|
||||
(slot) =>
|
||||
slot.start.hour == startTime.value.hour &&
|
||||
(startTime.value.minute < 30
|
||||
? slot.start.minute == 0
|
||||
: slot.start.minute == 30),
|
||||
(slot) => slot.start.hour == startTime.value.hour,
|
||||
);
|
||||
|
||||
if (initialItem == -1) {
|
||||
initialItem = 0;
|
||||
}
|
||||
@@ -399,15 +393,16 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
'hydAmount': ampuntStr,
|
||||
},
|
||||
);
|
||||
var result = BaseModel.fromJson(responseData?.data);
|
||||
|
||||
if (responseData == null) {
|
||||
|
||||
if (responseData == null || result.code != 0) {
|
||||
dismissLoading();
|
||||
showToast('服务暂不可用,请稍后');
|
||||
showToast(result.error);
|
||||
return;
|
||||
}
|
||||
dismissLoading();
|
||||
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("预约成功");
|
||||
|
||||
@@ -442,7 +437,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
}
|
||||
} catch (e) {
|
||||
dismissLoading();
|
||||
showToast('服务暂不可用,请稍后');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +525,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
reservationList.clear();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.d("${e.toString()}");
|
||||
showToast('获取预约数据失败');
|
||||
hasReservationData.value = false;
|
||||
reservationList.clear();
|
||||
@@ -597,7 +592,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
plateNumber = bean.plateNumber;
|
||||
vin = bean.vin;
|
||||
plateNumberController = TextEditingController(text: plateNumber);
|
||||
maxHydrogen = bean.maxHydrogen;
|
||||
maxHydrogen = num.tryParse(bean.maxHydrogen) ?? 0;
|
||||
getCatinfo();
|
||||
getJqinfo();
|
||||
}
|
||||
@@ -676,7 +671,7 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
//该账号给stationOptions手动添加一个数据
|
||||
final testStation = StationModel(
|
||||
hydrogenId: '1142167389150920704',
|
||||
name: '羚牛氢能演示加氢站',
|
||||
name: '演示加氢站',
|
||||
address: '上海市嘉定区于田南路111号于田大厦',
|
||||
price: '35.00',
|
||||
// 价格
|
||||
@@ -693,13 +688,10 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading("加氢站数据加载中");
|
||||
final originalHeaders = Map<String, dynamic>.from(HttpService.to.dio.options.headers);
|
||||
try {
|
||||
HttpService.to.setBaseUrl(AppTheme.jiaqing_service_url);
|
||||
HttpService.to.dio.options.headers['appId'] = '97ad10eeb6b346f79e0d6ffd81e4d3c3';
|
||||
showLoading("加氢站数据加载中");
|
||||
|
||||
var responseData = await HttpService.to.get("hydrogen/queryHydrogenSiteInfo");
|
||||
var responseData = await HttpService.to.get("appointment/station/queryHydrogenSiteInfo");
|
||||
|
||||
if (responseData == null || responseData.data == null) {
|
||||
showToast('暂时无法获取站点信息');
|
||||
@@ -748,9 +740,6 @@ class C_ReservationController extends GetxController with BaseControllerMixin {
|
||||
showToast('数据异常');
|
||||
} finally {
|
||||
dismissLoading();
|
||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||
HttpService.to.dio.options.headers = originalHeaders;
|
||||
|
||||
// 如果未绑定车辆,且本次会话尚未提示过,则弹出提示
|
||||
if (!StorageService.to.hasShownBindVehicleDialog &&
|
||||
StorageService.to.isLoggedIn &&
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/common/index.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
import 'package:ln_jq_app/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';
|
||||
@@ -141,7 +142,6 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 状态标签
|
||||
@@ -151,58 +151,97 @@ class _ReservationListBottomSheetState extends State<ReservationListBottomSheet>
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE6F7FF),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFF91D5FF)),
|
||||
color: Colors.blue.shade50, // 淡蓝色背景
|
||||
borderRadius: BorderRadius.circular(4), // 小圆角
|
||||
// 可以选择去掉边框,或者用极淡的边框
|
||||
border: Border.all(color: Colors.blue.shade100),
|
||||
),
|
||||
child: Text(
|
||||
"${reservation.stateName}-${reservation.addStatusName}",
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF1890FF),
|
||||
fontWeight: FontWeight.bold,
|
||||
style: TextStyle(
|
||||
color: Colors.blue.shade700,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 修改按钮 (仅在 hasEdit 为 true 时显示)
|
||||
Spacer(),
|
||||
//取消预约
|
||||
if (reservation.hasEdit)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
var result = await Get.to(
|
||||
() => ReservationEditPage(),
|
||||
arguments: {
|
||||
'reservation': reservation,
|
||||
'difference': _controller.difference,
|
||||
},
|
||||
binding: BindingsBuilder(() {
|
||||
Get.put(ReservationEditController());
|
||||
}),
|
||||
preventDuplicates: false,
|
||||
);
|
||||
if (result == true) {
|
||||
_controller.getReservationList(
|
||||
addStatus: _selectedStatus,
|
||||
SizedBox(
|
||||
height: 28, // 限制按钮高度,显得精致
|
||||
child: OutlinedButton(
|
||||
onPressed: () async{
|
||||
var responseData = await HttpService.to.post(
|
||||
'appointment/orderAddHyd/vehicle-cancel',
|
||||
data: {
|
||||
'id': reservation.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
|
||||
if (responseData == null || responseData.data == null) {
|
||||
showToast('服务暂不可用,请稍后');
|
||||
dismissLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = BaseModel.fromJson(responseData.data);
|
||||
|
||||
if (result.code == 0) {
|
||||
showSuccessToast("已取消");
|
||||
_controller.getReservationList(
|
||||
addStatus: _selectedStatus,
|
||||
);
|
||||
} else {
|
||||
showToast(result.error);
|
||||
}
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
side: BorderSide(color: Colors.grey.shade400), // 灰色边框
|
||||
shape: const StadiumBorder(), // 胶囊形状
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF7E6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text(
|
||||
"修改",
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFA8C16),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
child: Text(
|
||||
'取消预约',
|
||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w,),
|
||||
// 修改按钮 (仅在 hasEdit 为 true 时显示)
|
||||
if (reservation.hasEdit)
|
||||
SizedBox(
|
||||
height: 28,
|
||||
child: OutlinedButton(
|
||||
onPressed: () async{
|
||||
var result = await Get.to(
|
||||
() => ReservationEditPage(),
|
||||
arguments: {
|
||||
'reservation': reservation,
|
||||
'difference': _controller.difference,
|
||||
},
|
||||
binding: BindingsBuilder(() {
|
||||
Get.put(ReservationEditController());
|
||||
}),
|
||||
preventDuplicates: false,
|
||||
);
|
||||
if (result == true) {
|
||||
_controller.getReservationList(
|
||||
addStatus: _selectedStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
side: const BorderSide(color: Colors.blue), // 蓝色边框
|
||||
shape: const StadiumBorder(),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
child: const Text(
|
||||
'修改',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 12),
|
||||
),
|
||||
),),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -13,15 +13,12 @@ import 'reservation_list_bottomsheet.dart';
|
||||
class ReservationPage extends GetView<C_ReservationController> {
|
||||
ReservationPage({super.key});
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<C_ReservationController>(
|
||||
init: C_ReservationController(),
|
||||
id: 'reservation',
|
||||
builder: (_) {
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
body: GestureDetector(
|
||||
@@ -273,12 +270,12 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
hint: '当前最大可预约氢量${controller.difference}(KG)',
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
_buildTextField(
|
||||
/*_buildTextField(
|
||||
label: '车牌号',
|
||||
controller: controller.plateNumberController,
|
||||
hint: '请输入车牌号', // 修改提示文案
|
||||
enabled: false, // 设置为不可编辑
|
||||
),
|
||||
),*/
|
||||
_buildStationSelector(),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
@@ -332,8 +329,6 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 表单中的可点击行 (用于日期和时间选择)
|
||||
Widget _buildPickerRow({
|
||||
required String label,
|
||||
@@ -378,6 +373,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
TextInputType? keyboardType,
|
||||
bool enabled = true,
|
||||
}) {
|
||||
bool showCounter = keyboardType == TextInputType.number;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
@@ -401,6 +397,25 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
filled: !enabled,
|
||||
fillColor: Colors.grey[100],
|
||||
// 左侧减号按钮
|
||||
prefixIcon: showCounter
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.remove, color: Colors.blue),
|
||||
onPressed: () => _updateAmount(-1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
||||
)
|
||||
: null,
|
||||
|
||||
// 右侧加号按钮
|
||||
suffixIcon: showCounter
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.add, color: Colors.blue),
|
||||
onPressed: () => _updateAmount(1),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -408,6 +423,37 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
);
|
||||
}
|
||||
|
||||
// :更新氢量逻辑
|
||||
void _updateAmount(int change) {
|
||||
// 获取当前输入框的值,默认为 0
|
||||
double currentAmount = double.tryParse(controller.amountController.text) ?? 0;
|
||||
|
||||
// 获取最大值,注意处理 difference 可能为空或非数字的情况
|
||||
double maxAmount = double.tryParse(controller.difference.toString()) ?? 9999;
|
||||
|
||||
// 计算新值
|
||||
double newAmount = currentAmount + change;
|
||||
|
||||
// 边界检查
|
||||
if (newAmount < 1) {
|
||||
newAmount = 1; // 最小不能小于 1
|
||||
} else if (newAmount > maxAmount) {
|
||||
newAmount = maxAmount; // 最大不能超过 difference
|
||||
}
|
||||
|
||||
// 如果是整数,去掉小数点显示
|
||||
if (newAmount == newAmount.toInt()) {
|
||||
controller.amountController.text = newAmount.toInt().toString();
|
||||
} else {
|
||||
controller.amountController.text = newAmount.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
// 移动光标到末尾,防止光标跳到前面
|
||||
controller.amountController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: controller.amountController.text.length),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStationSelector() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -428,7 +474,7 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => DropdownButtonHideUnderline(
|
||||
() => DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
isExpanded: true,
|
||||
hint: const Text(
|
||||
@@ -439,15 +485,15 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
items: controller.stationOptions
|
||||
.map(
|
||||
(station) => DropdownMenuItem<String>(
|
||||
value: station.hydrogenId, // value 是站点的唯一ID
|
||||
enabled: station.isSelect == 1,
|
||||
child: _buildDropdownItem(station), // child 是自定义的 Widget
|
||||
),
|
||||
)
|
||||
value: station.hydrogenId, // value 是站点的唯一ID
|
||||
enabled: station.isSelect == 1,
|
||||
child: _buildDropdownItem(station), // child 是自定义的 Widget
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value:
|
||||
// 当前的站点 处理默认
|
||||
controller.selectedStationId.value ??
|
||||
// 当前的站点 处理默认
|
||||
controller.selectedStationId.value ??
|
||||
(controller.stationOptions.isNotEmpty
|
||||
? controller.stationOptions.first.hydrogenId
|
||||
: null),
|
||||
@@ -460,15 +506,15 @@ class ReservationPage extends GetView<C_ReservationController> {
|
||||
customButton: Obx(() {
|
||||
// 优先从已选中的 ID 查找
|
||||
var selectedStation = controller.stationOptions.firstWhereOrNull(
|
||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
||||
(s) => s.hydrogenId == controller.selectedStationId.value,
|
||||
);
|
||||
|
||||
// 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认
|
||||
final stationToShow =
|
||||
selectedStation ??
|
||||
(controller.stationOptions.isNotEmpty
|
||||
? controller.stationOptions.first
|
||||
: null);
|
||||
(controller.stationOptions.isNotEmpty
|
||||
? controller.stationOptions.first
|
||||
: null);
|
||||
|
||||
// 如果有要显示的站点,就构建按钮
|
||||
if (stationToShow != null) {
|
||||
|
||||
@@ -74,32 +74,45 @@ class ReservationEditController extends GetxController with BaseControllerMixin
|
||||
final now = DateTime.now();
|
||||
final isToday =
|
||||
selectedDate.value.year == now.year &&
|
||||
selectedDate.value.month == now.month &&
|
||||
selectedDate.value.day == now.day;
|
||||
selectedDate.value.month == now.month &&
|
||||
selectedDate.value.day == now.day;
|
||||
|
||||
final List<TimeSlot> availableSlots = [];
|
||||
for (int i = 0; i < 48; i++) {
|
||||
final startMinutes = i * 30;
|
||||
final endMinutes = startMinutes + 30;
|
||||
final slotStartTime = TimeOfDay(
|
||||
hour: startMinutes ~/ 60,
|
||||
minute: startMinutes % 60,
|
||||
);
|
||||
final slotEndTime = TimeOfDay(
|
||||
hour: (endMinutes ~/ 60) % 24,
|
||||
minute: endMinutes % 60,
|
||||
);
|
||||
for (int i = 0; i < 24; i++) {
|
||||
// 每次增加 60 分钟
|
||||
final startMinutes = i * 60;
|
||||
final endMinutes = startMinutes + 60;
|
||||
|
||||
final slotStartDateTime = DateTime(
|
||||
selectedDate.value.year,
|
||||
selectedDate.value.month,
|
||||
selectedDate.value.day,
|
||||
slotStartTime.hour,
|
||||
slotStartTime.minute,
|
||||
);
|
||||
final startTime = TimeOfDay(hour: startMinutes ~/ 60, minute: startMinutes % 60);
|
||||
// 注意:endMinutes % 60 始终为 0,因为间隔是整小时
|
||||
final endTime = TimeOfDay(hour: (endMinutes ~/ 60) % 24, minute: endMinutes % 60);
|
||||
|
||||
if (!isToday || slotStartDateTime.isAfter(now)) {
|
||||
availableSlots.add(TimeSlot(slotStartTime, slotEndTime));
|
||||
// 如果不是今天,所有时间段都有效
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,17 +121,18 @@ class ReservationEditController extends GetxController with BaseControllerMixin
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找当前选中的时间对应的新列表中的索引
|
||||
int initialItem = availableSlots.indexWhere(
|
||||
(slot) =>
|
||||
slot.start.hour == startTime.value.hour &&
|
||||
(startTime.value.minute < 30
|
||||
? slot.start.minute == 0
|
||||
: slot.start.minute == 30),
|
||||
(slot) => slot.start.hour == startTime.value.hour,
|
||||
);
|
||||
if (initialItem == -1) initialItem = 0;
|
||||
|
||||
if (initialItem == -1) {
|
||||
initialItem = 0;
|
||||
}
|
||||
|
||||
TimeSlot tempSlot = availableSlots[initialItem];
|
||||
|
||||
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: 300,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/pages/b_page/base_widgets/view.dart';
|
||||
import 'package:ln_jq_app/pages/c_page/base_widgets/view.dart';
|
||||
import 'package:ln_jq_app/pages/login/view.dart';
|
||||
@@ -12,14 +17,19 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
|
||||
HomeController();
|
||||
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initAliyunPush();
|
||||
addPushCallback();
|
||||
FlutterNativeSplash.remove();
|
||||
}
|
||||
|
||||
// 根据登录状态和登录渠道返回不同的首页
|
||||
Widget getHomePage() {
|
||||
requestPermission();
|
||||
//登录状态跳转
|
||||
if (StorageService.to.isLoggedIn) {
|
||||
// 如果已登录,再判断是哪个渠道
|
||||
@@ -35,4 +45,132 @@ class HomeController extends GetxController with BaseControllerMixin {
|
||||
return LoginPage();
|
||||
}
|
||||
}
|
||||
|
||||
void requestPermission() async {
|
||||
PermissionStatus status = await Permission.notification.status;
|
||||
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) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
/// iOS 专属配置
|
||||
Future<void> _setupIOSConfig() async {
|
||||
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);
|
||||
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() {
|
||||
_aliyunPush.addMessageReceiver(
|
||||
onNotification: _onNotification,
|
||||
onNotificationOpened: _onNotificationOpened,
|
||||
onNotificationRemoved: _onNotificationRemoved,
|
||||
onMessage: _onMessage,
|
||||
onAndroidNotificationReceivedInApp: _onAndroidNotificationReceivedInApp,
|
||||
onAndroidNotificationClickedWithNoAction: _onAndroidNotificationClickedWithNoAction,
|
||||
onIOSChannelOpened: _onIOSChannelOpened,
|
||||
onIOSRegisterDeviceTokenSuccess: _onIOSRegisterDeviceTokenSuccess,
|
||||
onIOSRegisterDeviceTokenFailed: _onIOSRegisterDeviceTokenFailed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onAndroidNotificationClickedWithNoAction(
|
||||
Map<dynamic, dynamic> message,
|
||||
) async {
|
||||
Logger.d('onAndroidNotificationClickedWithNoAction ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onAndroidNotificationReceivedInApp(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onAndroidNotificationReceivedInApp ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onMessage(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onMessage ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onNotification(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onNotification ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onNotificationOpened(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onNotificationOpened ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onNotificationRemoved(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onNotificationRemoved ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onIOSChannelOpened(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onIOSChannelOpened ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onIOSRegisterDeviceTokenSuccess(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onIOSRegisterDeviceTokenSuccess ====> $message');
|
||||
}
|
||||
|
||||
Future<void> _onIOSRegisterDeviceTokenFailed(Map<dynamic, dynamic> message) async {
|
||||
Logger.d('onIOSRegisterDeviceTokenFailed====> $message');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/login_util.dart';
|
||||
import 'package:ln_jq_app/common/model/base_model.dart';
|
||||
@@ -7,6 +10,7 @@ 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';
|
||||
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';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
@@ -21,8 +25,10 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
|
||||
bool cLogin = true;
|
||||
bool _obscureText = true;
|
||||
|
||||
// 用于管理“记住密码”的复选框状态
|
||||
bool _rememberPassword = true;
|
||||
|
||||
// 用于确保凭证只在首次加载时回填一次
|
||||
bool _credentialsLoaded = false;
|
||||
|
||||
@@ -198,20 +204,24 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
phone: phone,
|
||||
);
|
||||
|
||||
//注册推送别名
|
||||
addAlias(phone);
|
||||
|
||||
//登录后查询已绑定车辆信息
|
||||
var carInfo = await HttpService.to.get(
|
||||
"appointment/driver/getTruckInfoByDriver?phone=$phone"
|
||||
"appointment/driver/getTruckInfoByDriver?phone=$phone",
|
||||
);
|
||||
if (carInfo != null) {
|
||||
var carInforesult = BaseModel.fromJson(carInfo.data);
|
||||
if (carInforesult.data != null) {
|
||||
final vehicle = VehicleInfo.fromJson(carInforesult.data as Map<String, dynamic>);
|
||||
final vehicle = VehicleInfo.fromJson(
|
||||
carInforesult.data as Map<String, dynamic>,
|
||||
);
|
||||
//保存使用
|
||||
await StorageService.to.saveVehicleInfo(vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//页面操作
|
||||
dismissLoading();
|
||||
showToast('登录成功,欢迎您');
|
||||
@@ -269,9 +279,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
hintText: '请输入密码',
|
||||
border: OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscureText ? Icons.visibility_off : Icons.visibility,
|
||||
),
|
||||
icon: Icon(_obscureText ? Icons.visibility_off : Icons.visibility),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
@@ -304,7 +312,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _rememberPassword = !_rememberPassword),
|
||||
child: const Text('记住密码', style: TextStyle(color: Colors.grey)),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20), // 调整间距
|
||||
@@ -328,7 +336,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
try {
|
||||
String encryptedPassword = LoginUtil.encrypt(password);
|
||||
var responseData = await HttpService.to.post(
|
||||
'/login/password',
|
||||
'appointment/login/password',
|
||||
data: {
|
||||
'account': account,
|
||||
'password': encryptedPassword,
|
||||
@@ -353,13 +361,18 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
|
||||
String token = result.data['token'] ?? '';
|
||||
String userId = result.data['userId'] ?? '';
|
||||
String mobile = result.data['mobile'] ?? '';
|
||||
|
||||
await StorageService.to.saveLoginInfo(
|
||||
token: token,
|
||||
userId: userId,
|
||||
phone: mobile,
|
||||
channel: "station",
|
||||
);
|
||||
|
||||
//注册推送别名
|
||||
addAlias(mobile);
|
||||
|
||||
// 根据复选框状态保存或清除密码 ---
|
||||
if (_rememberPassword) {
|
||||
await StorageService.to.saveStationCredentials(account, password);
|
||||
@@ -384,13 +397,51 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
||||
);
|
||||
}
|
||||
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
void addAlias(String alias) async {
|
||||
var result = await _aliyunPush.addAlias(alias);
|
||||
var code = result['code'];
|
||||
if (code == kAliyunPushSuccessCode) {
|
||||
Logger.d('添加别名$alias成功');
|
||||
} else {
|
||||
var errorCode = result['code'];
|
||||
var errorMsg = result['errorMsg'];
|
||||
Logger.d('添加别名$alias失败: $errorCode - $errorMsg');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<LoginController>(
|
||||
init: LoginController(),
|
||||
id: 'login',
|
||||
builder: (controller) {
|
||||
return Scaffold(body: _buildView(controller));
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(child: _buildView(controller)),
|
||||
if (AppTheme.is_show_host)
|
||||
Positioned(
|
||||
top: 40.h,
|
||||
right: 20.w,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Get.to(() => const UrlHostPage());
|
||||
},
|
||||
child: const Text(
|
||||
"域名配置",
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
65
ln_jq_app/lib/pages/url_host/controller.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
import 'package:ln_jq_app/common/styles/theme.dart';
|
||||
import 'package:ln_jq_app/storage_service.dart';
|
||||
|
||||
class UrlHostController extends GetxController {
|
||||
final TextEditingController urlController = TextEditingController();
|
||||
|
||||
// 预设的域名列表
|
||||
final List<String> presetUrls = [
|
||||
'https://beta-esg.api.lnh2e.com/', // 测试环境
|
||||
'http://192.168.110.44:8080/', // 沈辰本地
|
||||
'http://192.168.110.222:8080/', // 何斐本地
|
||||
];
|
||||
|
||||
final List<String> urlNames = [
|
||||
'测试环境',
|
||||
'沈辰本地环境',
|
||||
'何斐本地环境',
|
||||
];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// 初始化时,尝试从 StorageService 获取已保存的域名
|
||||
// 如果没有保存过,则使用当前的全局配置
|
||||
String? savedUrl = StorageService.to.hostUrl;
|
||||
if (savedUrl != null && savedUrl.isNotEmpty) {
|
||||
urlController.text = savedUrl;
|
||||
} else {
|
||||
urlController.text = AppTheme.test_service_url;
|
||||
}
|
||||
}
|
||||
|
||||
/// 当用户点击列表中的某一项时,将其填入编辑框
|
||||
void selectUrl(String url) {
|
||||
urlController.text = url;
|
||||
}
|
||||
|
||||
/// 保存配置并关闭页面
|
||||
void saveAndExit() async {
|
||||
String newUrl = urlController.text.trim();
|
||||
if (newUrl.isEmpty) {
|
||||
showToast('请输入有效的域名');
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存到本地存储,以便下次启动时加载
|
||||
await StorageService.to.saveHostUrl(newUrl);
|
||||
AppTheme.test_service_url = newUrl;
|
||||
|
||||
//设置框架
|
||||
HttpService.to.setBaseUrl(AppTheme.test_service_url);
|
||||
|
||||
showSuccessToast('域名已更新:${AppTheme.test_service_url}');
|
||||
Get.back();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
urlController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
82
ln_jq_app/lib/pages/url_host/view.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class UrlHostPage extends GetView<UrlHostController> {
|
||||
const UrlHostPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(UrlHostController());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('域名配置'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'当前环境配置',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: controller.urlController,
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入或选择API域名',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'预设环境',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.presetUrls.length,
|
||||
itemBuilder: (context, index) {
|
||||
final url = controller.presetUrls[index];
|
||||
return Card(
|
||||
elevation: 1,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
title: Text(controller.urlNames[index], style: const TextStyle(fontSize: 14)),
|
||||
subtitle: Text(url, style: const TextStyle(fontSize: 14)),
|
||||
trailing: const Icon(Icons.touch_app, size: 18, color: Colors.grey),
|
||||
onTap: () {
|
||||
// 点击列表项:先填入编辑框,然后直接保存退出
|
||||
controller.selectUrl(url);
|
||||
controller.saveAndExit();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: controller.saveAndExit,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text('保存配置', style: TextStyle(fontSize: 16)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:aliyun_push_flutter/aliyun_push_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:getx_scaffold/common/utils/log_util.dart';
|
||||
import 'package:ln_jq_app/common/model/vehicle_info.dart';
|
||||
|
||||
/// 定义登录渠道的枚举类型
|
||||
enum LoginChannel {
|
||||
station, // 站点
|
||||
driver, // 司机
|
||||
none, // 未登录或未知
|
||||
driver, // 司机
|
||||
none, // 未登录或未知
|
||||
}
|
||||
|
||||
class StorageService extends GetxService {
|
||||
@@ -22,9 +24,11 @@ class StorageService extends GetxService {
|
||||
static const String _vehicleInfoKey = 'vehicle_info';
|
||||
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 StorageService get to => Get.find();
|
||||
|
||||
@@ -34,21 +38,34 @@ class StorageService extends GetxService {
|
||||
}
|
||||
|
||||
// --- Getters ---
|
||||
String? get hostUrl => _box.read<String?>(_hostUrlKey);
|
||||
|
||||
///:保存自定义域名
|
||||
Future<void> saveHostUrl(String url) async {
|
||||
await _box.write(_hostUrlKey, url);
|
||||
}
|
||||
|
||||
bool get isLoggedIn => _box.read<String?>(_tokenKey)?.isNotEmpty ?? false;
|
||||
|
||||
String? get token => _box.read<String?>(_tokenKey);
|
||||
|
||||
String? get userId => _box.read<String?>(_userIdKey);
|
||||
|
||||
String? get name => _box.read<String?>(_nameKey);
|
||||
|
||||
String? get phone => _box.read<String?>(_phoneKey);
|
||||
|
||||
String? get idCard => _box.read<String?>(_idCardKey);
|
||||
|
||||
bool get hasVehicleInfo => _box.hasData(_vehicleInfoKey);
|
||||
|
||||
String? get stationAccount => _box.read<String?>(_stationAccountKey);
|
||||
|
||||
String? get stationPassword => _box.read<String?>(_stationPasswordKey);
|
||||
|
||||
// 新增:获取“绑定车辆”弹窗是否已显示的标志
|
||||
bool get hasShownBindVehicleDialog => _box.read<bool>(_bindDialogShownKey) ?? false;
|
||||
|
||||
|
||||
VehicleInfo? get vehicleInfo {
|
||||
final vehicleJson = _box.read<String?>(_vehicleInfoKey);
|
||||
if (vehicleJson != null) {
|
||||
@@ -98,7 +115,6 @@ class StorageService extends GetxService {
|
||||
await _box.write(_bindDialogShownKey, true);
|
||||
}
|
||||
|
||||
|
||||
Future<void> clearVehicleInfo() async {
|
||||
await _box.remove(_vehicleInfoKey);
|
||||
}
|
||||
@@ -118,5 +134,22 @@ class StorageService extends GetxService {
|
||||
await clearVehicleInfo();
|
||||
// 登出时,清除“绑定车辆”弹窗的显示记录,以便下次登录时可以再次弹出
|
||||
await _box.remove(_bindDialogShownKey);
|
||||
|
||||
delAlias();
|
||||
}
|
||||
|
||||
final _aliyunPush = AliyunPushFlutter();
|
||||
|
||||
void delAlias() async {
|
||||
String phoen = StorageService.to.phone ?? "";
|
||||
var result = await _aliyunPush.removeAlias(phoen);
|
||||
var code = result['code'];
|
||||
if (code == kAliyunPushSuccessCode) {
|
||||
Logger.d('删除别名$phoen成功');
|
||||
} else {
|
||||
var errorCode = result['code'];
|
||||
var errorMsg = result['errorMsg'];
|
||||
Logger.d('删除别名$phoen失败: $errorCode - $errorMsg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
aliyun_push_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: aliyun_push_flutter
|
||||
sha256: fd1a13ab37f30274e1eaa9c0f68001bf268f8f2e1c83730b4520aa4dff46ce70
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.6"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -109,18 +117,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239"
|
||||
sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
version: "0.3.5+1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -253,18 +261,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "88707a3bec4b988aaed3b4df5d7441ee4e987f20b286cddca5d6a8270cab23f2"
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.4+5"
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -277,10 +285,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
version: "0.9.3+5"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -399,10 +407,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687"
|
||||
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.32"
|
||||
version: "2.0.33"
|
||||
flutter_screenutil:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -423,10 +431,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
|
||||
sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.3"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -505,10 +513,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: get
|
||||
sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425
|
||||
sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.7.2"
|
||||
version: "4.7.3"
|
||||
get_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -545,10 +553,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
http_client_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -569,10 +577,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
version: "4.7.2"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -585,26 +593,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: ca2a3b04d34e76157e9ae680ef16014fb4c2d20484e78417eaed6139330056f6
|
||||
sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.13+7"
|
||||
version: "0.8.13+10"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
|
||||
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: e675c22790bcc24e9abd455deead2b7a88de4b79f7327a281812f14de1a56f58
|
||||
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.13+1"
|
||||
version: "0.8.13+3"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -793,18 +801,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.20"
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
version: "2.5.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -925,6 +933,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
pull_to_refresh:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pull_to_refresh
|
||||
sha256: bbadd5a931837b57739cf08736bea63167e284e71fb23b218c8c9a6e042aad12
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
rational:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -937,26 +953,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
version: "2.5.4"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713"
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.15"
|
||||
version: "2.4.18"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b"
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1062,10 +1078,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
|
||||
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.3.1"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1078,34 +1094,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9"
|
||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.24"
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9"
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.5"
|
||||
version: "6.3.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9"
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.4"
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1126,10 +1142,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -16,7 +16,7 @@ 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.0.0+1
|
||||
version: 1.2.1+4
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0
|
||||
@@ -50,6 +50,10 @@ dependencies:
|
||||
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
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||