136 Commits

Author SHA1 Message Date
384de27f2c Merge branch 'dev' 2026-03-13 17:18:43 +08:00
84b174c4a5 协议通知后注册推送 2026-03-13 17:09:30 +08:00
02e1946319 文字边界 2026-03-05 11:20:14 +08:00
d5083c1939 Merge branch 'dev'
v1.2.4
2026-03-04 14:58:45 +08:00
fe44848529 号码 2026-03-04 14:50:21 +08:00
572c416827 枪号回填,v1.2.4 2026-03-04 13:38:26 +08:00
8df16f0787 pdf查看 2026-03-03 17:51:06 +08:00
ce6bd3edd2 bugfix 2026-03-03 13:09:10 +08:00
6997b4ac9e 调整权限和库 2026-03-02 11:28:44 +08:00
a26d2478f3 样式 2026-02-28 17:31:28 +08:00
0dded3b928 ocr识别,历史新增类型 2026-02-28 17:15:42 +08:00
b7caf58adf 加氢站-查看证件 2026-02-28 15:00:56 +08:00
0df1902df2 无预约 2026-02-28 11:59:07 +08:00
a8314d8a7a 关闭弹窗 2026-02-27 10:55:55 +08:00
39cae906e9 加氢站-预约 2026-02-27 10:54:55 +08:00
14fd6c75d0 fix 2026-02-25 15:35:33 +08:00
1724852a39 司机-可上传证件 2026-02-25 15:35:02 +08:00
a05d4ebb9b 调整 2026-02-12 18:01:56 +08:00
600cea4379 进度条 2026-02-12 17:34:06 +08:00
3dfc1dfc2c 样式调整 2026-02-12 16:54:50 +08:00
909dc95771 增加 提示 2026-02-11 17:41:30 +08:00
cf0896453b 样式 2026-02-11 11:28:49 +08:00
dce9718320 显示周边加氢站 2026-02-11 09:35:42 +08:00
4491aa9b91 ui调整 2026-02-10 16:35:02 +08:00
5364612a6f 更新样式 2026-02-10 13:37:24 +08:00
10867178fa 筛选框样式 2026-02-10 13:35:22 +08:00
a5e2a89e4f 预约列表样式 2026-02-10 11:51:47 +08:00
26c5f9d67a 消息样式修改 2026-02-09 17:57:00 +08:00
9cd87b0535 规则和历史 2026-02-09 17:28:12 +08:00
45e45d8160 积分兑换 2026-02-09 15:10:00 +08:00
87e890f97e 积分兑换首页 2026-02-06 17:37:43 +08:00
dcf925b8c1 商场页 2026-02-06 15:13:33 +08:00
c45863eda6 增加商城页面 2026-02-06 15:11:12 +08:00
756bf53cf5 司机预约时间调整 2026-02-06 14:16:26 +08:00
f68c2d0938 未车辆显示 2026-02-05 14:50:03 +08:00
211d0225e4 车辆图片动态 2026-02-05 13:54:10 +08:00
7d9b4d99e8 应用更新 2026-02-05 10:30:31 +08:00
3dd583a278 401增加节流 2026-02-03 10:59:05 +08:00
35bd3a78a5 Merge branch 'dev' 2026-01-30 17:33:15 +08:00
1278c38b7e 地图样式调整 2026-01-30 17:32:54 +08:00
032e60d362 Merge branch 'dev'
ui调整
# Conflicts:
#	ln_jq_app/lib/pages/login/view.dart
2026-01-30 17:08:49 +08:00
171f556b40 viersion 1.2.3 2026-01-30 16:06:58 +08:00
55eade54b6 问题修改 2026-01-30 16:02:35 +08:00
bc99ffd691 增加参数 2026-01-30 13:01:50 +08:00
aa52a56bcf 地图相关的修改 2026-01-30 11:36:33 +08:00
73343ca297 调整 2026-01-30 10:01:31 +08:00
d09faac1d2 调整 2026-01-29 19:26:59 +08:00
1177be821a 样式调整 2026-01-29 17:01:21 +08:00
e59b89c225 Merge branch 'dev_feature' into dev
ui调整
# Conflicts:
#	ln_jq_app/lib/pages/b_page/reservation/controller.dart
#	ln_jq_app/lib/pages/b_page/site/controller.dart
#	ln_jq_app/lib/pages/b_page/site/view.dart
#	ln_jq_app/lib/pages/c_page/mine/view.dart
#	ln_jq_app/lib/pages/c_page/reservation/controller.dart
#	ln_jq_app/lib/pages/c_page/reservation/view.dart
#	ln_jq_app/lib/pages/login/view.dart
2026-01-29 11:45:17 +08:00
79fe3257b5 状态样式 2026-01-28 17:59:26 +08:00
55569839a7 搜索框 2026-01-28 16:27:37 +08:00
7112d70aba 站点样式 2026-01-28 15:00:30 +08:00
f8a8ecb0ed 司机 预约 样式修改 2026-01-27 17:35:37 +08:00
18c04272e2 车辆信息的部分 2026-01-27 13:57:14 +08:00
14e7fb3d78 首页ui 2026-01-27 13:15:53 +08:00
5ffaf81223 欢迎页 登录 2026-01-27 09:21:36 +08:00
907983a1d1 登录修改 2026-01-26 14:07:58 +08:00
9fdca9136d 导航栏 2026-01-23 17:00:17 +08:00
16bae6a1e9 车辆信息 ui 2026-01-23 15:02:16 +08:00
aabfbfae0c 车辆样式调整 2026-01-23 10:49:02 +08:00
5236670e7c ui修改 2026-01-23 09:22:07 +08:00
cf3ad579d3 新ui调整 2026-01-22 17:29:54 +08:00
70a752b6e5 协议同意 2026-01-21 14:29:03 +08:00
45f5035d1b 更换logo 2026-01-19 10:13:37 +08:00
f792915429 bugfix 2026-01-16 17:47:53 +08:00
edbacc502b 线上域名修改 2026-01-16 17:25:52 +08:00
5722e3ace0 ios build 2026-01-16 15:00:45 +08:00
d41b21654a 改用账号推送 2026-01-16 14:35:11 +08:00
2eb059defd 定时器黄字2位小数 2026-01-16 13:35:38 +08:00
fbcc85af2a 站点增加消息入口 2026-01-16 13:06:35 +08:00
9a97b56505 定时器显示调整 2026-01-15 17:55:07 +08:00
8302d7c179 优化问题修改 2026-01-15 16:28:59 +08:00
e7a9e4483a 优化定时器 弹窗 2026-01-15 13:29:02 +08:00
9b64fdfa52 优化定时器 2026-01-15 13:24:02 +08:00
d8f335eb4e 优化 2026-01-15 09:30:47 +08:00
d1b7a9eb76 v1.2.2 version 2026-01-14 13:21:54 +08:00
f25feaa55a 联调修改结构 2026-01-14 13:18:22 +08:00
16639e2384 站点增加广播 2026-01-12 17:52:08 +08:00
20ef495571 非营业状态 增加时间选择 2026-01-12 16:41:36 +08:00
285a20f070 地址切换 2026-01-12 09:13:37 +08:00
baee5dba83 消息中心,待测试 2026-01-08 15:33:40 +08:00
7d9c879a4e 消息中心 2026-01-07 17:43:22 +08:00
953e5e773c 消息入口 2026-01-07 16:02:10 +08:00
8a4bc1d1ab 取消预约按钮 2026-01-07 09:59:12 +08:00
c57c849073 样式交互修改 2026-01-06 17:57:56 +08:00
6cc123f272 更换导航栏位置 2026-01-05 14:09:15 +08:00
5168b23609 增加请求头 2026-01-04 16:59:08 +08:00
c5299dd655 增加版本号 2026-01-04 10:27:39 +08:00
4bedd8c04b 优化 2025-12-31 17:37:07 +08:00
295b71c819 推送配置,测试 2025-12-31 17:22:13 +08:00
6629c8047f Merge branch 'dev' 2025-12-25 10:41:04 +08:00
bfa615a7f4 扫码无权限优化,司机预约多弹窗 2025-12-25 10:40:26 +08:00
288d629f99 bugfix 2025-12-24 10:53:34 +08:00
15fdbc7043 v1.2.1 2025-12-23 10:43:12 +08:00
f2f2348b54 逆地理编码 2025-12-23 10:18:44 +08:00
9ba152b3c3 v1.2.1 待发布 2025-12-22 16:15:32 +08:00
21a528d6d1 调整和修改 2025-12-19 13:18:04 +08:00
62ca3888d3 历史统计数据,错误提示 2025-12-18 15:20:54 +08:00
3ec56a925c 修改搜索条件 2025-12-18 10:33:24 +08:00
95c08818cb 取消dark主题 2025-12-18 09:03:08 +08:00
42355bd1ef 站点-新增历史记录 2025-12-17 17:40:02 +08:00
fe2ce75cec 修改绑定车辆接口
登录后查询车辆信息
2025-12-17 09:18:34 +08:00
98cac8a0a5 预约列表增加筛选条件 2025-12-16 16:18:28 +08:00
9ce46a0c7d 加氢站默认选择,演示账号数据
附件默认显示,详情
2025-12-16 11:58:00 +08:00
266a43c09d 主题色 2025-12-16 09:54:32 +08:00
8434301d1f 版本号 2025-12-12 10:34:24 +08:00
adfe3bf34e 版本号修改 2025-12-11 15:50:25 +08:00
6a3c6db7a8 Merge branch 'dev'
司机端-加氢预约  预约时间段修改  1、开始结束时间整合成一个  2、时间只开放当日和次日
司机端-加氢预约  提交预约不可提交限制 1、非营业站点 2、预约时间不可重复
司机端-预约列表 1、新增到站时间、氢量修改 2、显示拒绝加氢原因
加氢站-加氢预约  1、新增当日预约加车牌/手机号筛选  2、新增加氢总量 未加氢量  3、 确认拒绝流程优化:完成可填写具体加氢量,新增拒绝原因
# Conflicts:
#	ln_jq_app/lib/common/styles/theme.dart
2025-12-11 15:48:10 +08:00
4ace3c9f27 刷新条件 2025-12-11 13:45:26 +08:00
b52659df6c 司机预约 显示拒绝原因 2025-12-11 13:27:28 +08:00
20877a3eb1 增加弹窗限制条件 2025-12-11 10:36:59 +08:00
69875f27ad 搜索字段 2025-12-10 17:51:00 +08:00
829f5e454e 文本处理 2025-12-10 17:37:56 +08:00
1c916a7fad 调整司机预约时间
站点工单状态
2025-12-10 17:34:46 +08:00
88b24b5228 修改配置ios 2025-12-10 13:16:10 +08:00
ee8cbde296 logo 2025-12-09 13:16:09 +08:00
f638704fba ios 说明 2025-12-09 11:24:45 +08:00
4c63d5ebf3 bugfix 2025-12-08 17:52:55 +08:00
a3fb0d8018 站点 确认拒绝流程 2025-12-08 09:32:06 +08:00
88b16ca69e 今日预约搜索 2025-12-03 16:00:18 +08:00
ba3467810c 新增数据 2025-12-03 15:04:58 +08:00
0bfedd54cb 可编辑预约 2025-12-03 10:39:34 +08:00
f25d7e4567 增加 加氢站可用标记 2025-12-02 10:36:56 +08:00
4cdedff654 默认时间段修改 2025-12-01 17:03:16 +08:00
4f99ab4164 修改选择器 2025-12-01 15:27:00 +08:00
cdc5af7f45 限制 2025-12-01 13:59:02 +08:00
20e5e58ded 增加提交预约限制 2025-12-01 13:56:17 +08:00
93a39e440a v1.1.0 2025-11-28 14:48:03 +08:00
ec1c554eb3 Merge branch 'dev'
v1.1.0
2025-11-28 10:12:44 +08:00
d3b7ab8fbe 先限制输入起点 2025-11-28 10:09:18 +08:00
5609594439 地图修改 2025-11-27 17:21:07 +08:00
da4149ec60 调整地图样式 2025-11-27 15:25:58 +08:00
aa30d13b91 更新插件 2025-11-27 09:30:52 +08:00
b854d295f9 调整预约时间格式 2025-11-26 17:33:04 +08:00
26a24efeb8 限制条件 2025-11-26 13:44:00 +08:00
496d77482d logo 预约弹出列表
车辆无绑定弹窗
2025-11-26 09:20:42 +08:00
138 changed files with 11495 additions and 3275 deletions

View File

@@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
@@ -5,6 +8,14 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.inputStream().use { input ->
keystoreProperties.load(input.reader(Charsets.UTF_8))
}
}
android {
namespace = "com.lnkj.ln_jq_app"
compileSdk = flutter.compileSdkVersion
@@ -21,20 +32,35 @@ 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 = 7
versionName = "1.2.4"
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
val storeFilePath = keystoreProperties["storeFile"] as String?
storeFile = if (storeFilePath != null) file(storeFilePath) else null
storePassword = keystoreProperties["storePassword"] as String?
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
getByName("release") {
// 使用上面定义的 release 签名
signingConfig = signingConfigs.getByName("release")
// 修复混淆规则引用语法
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}

View File

@@ -0,0 +1,61 @@
# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
# 忽略 Google Play Core 相关的缺失警告(解决你目前的报错)
-dontwarn com.google.android.play.core.**
# Flutter 基础规则
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-dontwarn com.huawei.android.os.BuildEx$VERSION
-dontwarn com.huawei.hianalytics.process.HiAnalyticsConfig$Builder
-dontwarn com.huawei.hianalytics.process.HiAnalyticsConfig
-dontwarn com.huawei.hianalytics.process.HiAnalyticsInstance$Builder
-dontwarn com.huawei.hianalytics.process.HiAnalyticsInstance
-dontwarn com.huawei.hianalytics.process.HiAnalyticsManager
-dontwarn com.huawei.hianalytics.util.HiAnalyticTools
-dontwarn com.huawei.hms.availableupdate.UpdateAdapterMgr
-dontwarn com.huawei.libcore.io.ExternalStorageFile
-dontwarn com.huawei.libcore.io.ExternalStorageFileInputStream
-dontwarn com.huawei.libcore.io.ExternalStorageFileOutputStream
-dontwarn com.huawei.libcore.io.ExternalStorageRandomAccessFile
-dontwarn org.android.netutil.PingEntry
-dontwarn org.android.netutil.PingResponse
-dontwarn org.android.netutil.PingTask
-dontwarn org.bouncycastle.crypto.BlockCipher
-dontwarn org.bouncycastle.crypto.engines.AESEngine
-dontwarn org.bouncycastle.crypto.prng.SP800SecureRandom
-dontwarn org.bouncycastle.crypto.prng.SP800SecureRandomBuilder
-keepclasseswithmembernames class ** {
native <methods>;
}
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-keep class com.ut.** {*;}
-keep class com.ta.** {*;}
-keep class anet.**{*;}
-keep class anetwork.**{*;}
-keep class org.android.spdy.**{*;}
-keep class org.android.agoo.**{*;}
-keep class android.os.**{*;}
-keep class org.json.**{*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
-dontwarn anet.**
-dontwarn org.android.spdy.**
-dontwarn org.android.agoo.**
-dontwarn anetwork.**
-dontwarn com.ut.**
-dontwarn com.ta.**

View File

@@ -1,45 +1,50 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!--定位权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="小羚羚"
android:requestLegacyExternalStorage="true"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/logo"
android:label="小羚羚">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
@@ -48,6 +53,88 @@
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安全建议设置不可导出如需对其他应用开放可通过androidpermission进行限制 -->
<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:
@@ -57,8 +144,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -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()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,224 +1,671 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>导航规划</title>
<style>
html, body, #container { width: 100%; height: 100%; margin: 0; }
<title>路径规划</title>
/* 搜索栏样式 */
<style>
/* --- 全局样式 --- */
html,
body,
#container {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
/* --- 暴力隐藏高德自带的导流链接和Logo (关键) --- */
.amap-callamap,
.amap-lib-driving-callBtn,
.amap-copyright,
.amap-logo {
display: none !important;
}
/* 去除高德默认的 label 边框 and 背景 */
.amap-marker-label {
border: none !important;
background-color: transparent !important;
}
/* 自定义气泡样式 */
.custom-bubble {
position: relative;
background: rgba(51, 51, 51, 0.7);
/* #33333399 对应 rgba(51,51,51,0.7) */
color: #fff;
padding: 6px 15px;
border-radius: 20px;
/* 圆角 */
font-size: 13px;
white-space: nowrap;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
text-align: center;
line-height: 1.4;
}
/* 气泡下方的向下小箭头 */
.custom-bubble::after {
content: '';
position: absolute;
bottom: -6px;
/* 箭头高度 */
left: 50%;
transform: translateX(-50%);
border-width: 6px 6px 0 6px;
border-style: solid;
border-color: rgba(51, 51, 51, 0.7) transparent transparent transparent;
}
#panel .amap-call {
display: none;
}
/* --- 搜索栏样式 --- */
#search-box {
position: absolute;
top: 10px;
top: 40px;
left: 10px;
right: 10px;
z-index: 100;
background: #fff;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
gap: 8px;
}
.input-row { display: flex; gap: 5px; }
input { flex: 1; padding: 8px; border: 1px solid #eee; border-radius: 4px; }
button { padding: 0 15px; background: #3366FF; color: #fff; border: none; border-radius: 4px; font-weight: bold; }
/* 导航结果面板 (仿高德原生) */
.input-row {
display: flex;
gap: 8px;
align-items: center;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
background-color: #f9f9f9;
font-size: 14px;
}
button {
padding: 0 15px;
height: 38px;
background: #017143FF;
color: #fff;
border: none;
border-radius: 4px;
font-weight: bold;
font-size: 14px;
}
/* --- 导航结果面板 (底部弹出) --- */
#panel {
position: fixed;
bottom: 0;
bottom: 95px;
left: 0;
width: 100%;
height: 30%; /* 占据底部30% */
height: 35%;
/* 面板高度 */
background-color: white;
overflow-y: auto;
border-top: 1px solid #ccc;
border-top: 1px solid #eee;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 99;
display: none; /* 默认隐藏,规划成功后显示 */
display: none;
/* 默认隐藏 */
}
/* --- 自定义定位按钮样式 --- */
#location-btn {
position: fixed;
right: 10px;
bottom: 105px;
/* 默认位置 */
width: 44px;
height: 44px;
background-color: #fff;
border-radius: 50%;
/* 圆形更符合现代审美 */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
z-index: 150;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: bottom 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* 平滑动画 */
}
#location-btn:active {
background-color: #f2f2f2;
}
#location-btn svg {
width: 24px;
height: 24px;
fill: #555;
}
/* --- 调整比例尺位置 --- */
.amap-scalecontrol {
/* 初始状态:避开底部的定位按钮或留出安全间距 */
bottom: 110px !important;
left: 10px !important;
transition: bottom 0.3s ease;
/* 增加平滑动画 */
}
/* --- 当路径规划面板显示时,比例尺自动上移 --- */
body.panel-active .amap-scalecontrol {
bottom: 38% !important;
/* 移动到面板上方 (面板高度35% + 3%间距) */
}
/* --- 关键:当 body 有 panel-active 类时,按钮上移 --- */
body.panel-active #location-btn {
bottom: 45%;
/* 对应 #panel 的 height + 一点间距 */
}
</style>
<!-- 1. 必须最先配置安全密钥 -->
<!-- 1. 配置安全密钥 -->
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: '0529b72df6bf0c577ff2182cb8b1d970',
securityJsCode: 'aa3a22c19ed76b27f8a587555d6981c8',
}
</script>
<!-- 2. 加载地图和插件 -->
<!-- 注意如果你要货车规划需要在plugin里加上 AMap.TruckDriving -->
<script src="https://webapi.amap.com/maps?v=2.0&key=2cc1d822e313307fe311c3127a1deeb5&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete"></script>
<!-- 2. 加载地图和插件 (去掉了 Geolocation 插件,避免弹窗) -->
<script
src="https://webapi.amap.com/maps?v=2.0&key=ecd74ece8cb14c9dad67675f83c3274d&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Scale,AMap.Geocoder">
</script>
</head>
<body>
<div id="search-box">
<div class="input-row">
<input id="startInput" placeholder="起点: 默认使用当前位置" />
<div id="search-box">
<div class="input-row">
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
</div>
<div class="input-row">
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
<button onclick="startRouteSearch()">路径规划</button>
</div>
</div>
<div class="input-row">
<input id="endInput" placeholder="终点: 请输入目的地" />
<button onclick="startRouteSearch()">路径规划</button>
<div id="container"></div>
<div id="panel"></div>
<!-- 自定义定位按钮 -->
<div id="location-btn" onclick="backToLocation()">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M512 85.333333a426.666667 426.666667 0 1 0 0 853.333334 426.666667 426.666667 0 0 0 0-853.333334z m0 149.333334a277.333333 277.333333 0 1 1 0 554.666666 277.333333 277.333333 0 0 1 0-554.666666z m0 149.333333a128 128 0 1 0 0 256 128 128 0 0 0 0-256z"
fill="#666666"></path>
</svg>
</div>
</div>
<div id="container"></div>
<div id="panel"></div>
<script>
var map, marker, destMarker, driving, truckDriving, geocoder;
var currentLat, currentLng;
var isTruckMode = false;
var isInitialLocationSet = false;
var stationMarkers = []; // 存储所有站点的标记
<script>
var map, marker, driving, truckDriving;
var currentLat, currentLng;
// 标记是否使用货车模式 (true: 货车, false: 轿车)
var isTruckMode = false;
function initMap() {
map = new AMap.Map('container', {
resizeEnable: true,
zoom: 17,
viewMode: '3D'
});
function initMap() {
map = new AMap.Map('container', {
resizeEnable: true,
zoom: 15,
viewMode: '3D'
});
// --- 2. 初始化 geocoder ---
geocoder = new AMap.Geocoder({
city: "全国" // 设置地理编码范围
});
// --- 初始化轿车规划 ---
driving = new AMap.Driving({
map: map,
panel: "panel", // 结果显示在 panel div 中
policy: AMap.DrivingPolicy.LEAST_TIME
});
// 通知 Flutter 地图加载完毕
map.on('complete', function () {
console.log("JS->: Map is ready.");
if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler('mapReady');
}
});
// --- 初始化货车规划 (按需开启) ---
// 货车需要设置 size (1:微型, 2:轻型, 3:中型, 4:重型)
if(AMap.TruckDriving) {
truckDriving = new AMap.TruckDriving({
// 点击地图空白处重置状态
map.on('click', function() {
resetSearchState();
});
// 添加基础控件
map.addControl(new AMap.Scale());
map.addControl(new AMap.ToolBar({
visible: true,
position: {
top: '200px',
right: '10px'
} // 稍微避开定位按钮
}));
// --- 初始化轿车规划 ---
driving = new AMap.Driving({
map: map,
panel: "panel",
size: 2,
policy: 0, // 0: 躲避拥堵
width: 2.5,
height: 2,
load: 1,
weight: 10,
axlesNum: 2,
policy: AMap.DrivingPolicy.LEAST_TIME
});
// --- 初始化货车规划 ---
if (AMap.TruckDriving) {
truckDriving = new AMap.TruckDriving({
map: map,
panel: "panel",
size: 2,
policy: 0,
width: 2.5,
height: 2,
load: 1,
weight: 10,
axlesNum: 2,
});
}
// --- 输入提示 ---
new AMap.AutoComplete({
input: "startInput"
});
new AMap.AutoComplete({
input: "endInput"
});
}
// --- 输入提示 (自动补全) ---
new AMap.AutoComplete({ input: "startInput" });
new AMap.AutoComplete({ input: "endInput" });
}
/**
* 重置搜索状态,隐藏面板和路线
*/
function resetSearchState() {
if (document.body.classList.contains('panel-active')) {
console.log("JS->: 重置地图状态");
document.body.classList.remove('panel-active');
var panel = document.getElementById('panel');
panel.style.display = 'none';
if (driving) driving.clear();
}
}
// --- 被动接收 Flutter 传来的定位 ---
function updateMyLocation(lat, lng, angle) {
currentLat = lat;
currentLng = lng;
var position = [lng, lat];
/**
* 核心功能 1: 接收 Flutter 传来的定位数据
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
* 纬度 经度
*/
function updateMyLocation(lat, lng, angle) {
var rawLat = parseFloat(lat);
var rawLng = parseFloat(lng);
var rawAngle = parseFloat(angle);
var gps = [rawLng, rawLat];
if (!marker) {
marker = new AMap.Marker({
AMap.convertFrom(gps, 'gps', function (status, result) {
if (result.info === 'ok') {
var mPoint = result.locations[0];
currentLng = mPoint.lng;
currentLat = mPoint.lat;
var position = [currentLng, currentLat];
// 更新车辆标记位置 (保持不变)
if (!marker) {
marker = new AMap.Marker({
map: map,
position: position,
icon: "car.png",
offset: new AMap.Pixel(-23.5, -15),
autoRotation: true,
angle: isNaN(rawAngle) ? 0 : rawAngle,
});
map.setCenter(position);
map.setZoom(13);
} else {
marker.moveTo(position, {
duration: 1000,
autoRotation: true
});
if (!isNaN(rawAngle)) marker.setAngle(rawAngle);
}
// --- 4. 逆地理编码并设置默认起点 ---
// 只有在第一次获取到位置时,才设置默认起点,避免覆盖用户手动输入的起点
if (!isInitialLocationSet) {
geocoder.getAddress(position, function (status, result) {
if (status === 'complete' && result.regeocode) {
let shortAddress = '';
const regeo = result.regeocode;
const addressComponent = regeo.addressComponent;
const pois = regeo.pois;
console.log("地理:" + JSON.stringify(result));
fetchStationInfo(addressComponent.province, addressComponent.city,
addressComponent.district, lat, lng);
fetchStationInfoList(lat, lng);
// 策略1: 优先使用最近的、类型合适的POI的名称
if (pois && pois.length > 0) {
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI这类POI通常是具体的建筑或地点名
const significantPoi = pois.find(p => p.type.indexOf('商务住宅') === -
1 && p.type.indexOf('地名地址信息') === -1);
if (significantPoi) {
shortAddress = significantPoi.name;
} else {
// 如果找不到就用第一个POI的名字
shortAddress = pois[0].name;
}
}
// 策略2: 如果没有POI使用"道路+门牌号"
else if (addressComponent.street && addressComponent.streetNumber) {
shortAddress = addressComponent.district +
addressComponent.township +
addressComponent.street + addressComponent.streetNumber;
}
// 策略3: 如果还没有,使用"区+乡镇"
else if (addressComponent.district) {
shortAddress = addressComponent.district + (addressComponent
.township || '');
}
// 策略4: 降级到使用完整的、但可能很长的地址
else {
shortAddress = regeo.formattedAddress;
}
// 如果拼接出的地址过长,可以再做一次截断
if (shortAddress.length > 20) {
// 可以在这里添加更复杂的截断逻辑,比如按关键字
shortAddress = regeo.formattedAddress.substring(0, 20) + '...';
}
// 将获取到的地址填充到起点输入框
document.getElementById('startInput').value = shortAddress;
isInitialLocationSet = true; // 标记为已设置,不再更新
} else {
// 如果逆地理编码失败,依然使用“当前位置”作为提示
document.getElementById('startInput').placeholder = "当前位置";
console.error('逆地理编码失败:', result);
}
});
}
}
});
}
/**
* 调用后端接口获取站点
*/
function fetchStationInfo(province, city, district, lat, lng) {
// 注意:某些直辖市在高德中 city 字段可能为空,需做兼容处理
console.log("JS->: 开始请求." + province + city + district);
var cityName = (typeof city === 'string' && city.length > 0) ? city : province;
fetch('https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
province: province,
city: cityName,
district: district,
longitude: lng,
latitude: lat,
})
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应错误: ' + response.status);
}
return response.json(); // 解析 JSON
})
.then(res => {
// 打印完整的返回结果,方便调试观察结构
console.log("JS->: 接口完整返回:", JSON.stringify(res));
// 安全校验:判断 res.data 是否存在
if (res.code === 0 && res.data) {
if (res.data.address) {
console.log("JS->: 找到地址:", res.data.address);
var destAddress = res.data.address;
document.getElementById('endInput').value = destAddress;
// 标记终点
markDestination(destAddress, res.data.name || "目的地",
res.data.longitude, res.data.latitude
);
} else {
console.log("JS->: 接口请求成功,但该区域暂无站点地址");
}
} else {
console.log("JS->: 业务报错或无数据:", res.message);
}
})
.catch(err => console.error('JS->:获取站点信息失败:', err));
}
/**
* 获取站点列表
*/
function fetchStationInfoList(lat, lng) {
fetch('https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
longitude: lng,
latitude: lat,
})
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应错误: ' + response.status);
}
return response.json(); // 解析 JSON
})
.then(res => {
console.log("JS->:2 接口完整返回:", JSON.stringify(res));
if (res.code === 0 && res.data && Array.isArray(res.data)) {
// 1. 清除旧的站点标记
stationMarkers.forEach(m => m.setMap(null));
stationMarkers = [];
// 2. 循环标记所有加氢站
res.data.forEach(station => {
var stationIcon = new AMap.Icon({
size: new AMap.Size(32, 32),
image: 'ic_tag.png',
imageSize: new AMap.Size(32, 32)
});
var sMarker = new AMap.Marker({
map: map,
position: [station.longitude, station.latitude],
icon: stationIcon,
offset: new AMap.Pixel(-16, -32),
title: station.name,
label: {
content: '<div class="custom-bubble">' + station.name +
'</div>',
direction: 'top'
}
});
// 3. 绑定点击事件:选中即为目的地,并开始规划
sMarker.on('click', function () {
var stationName = station.name || "目的地";
document.getElementById('endInput').value = station.address ||
stationName;
// 更新当前的 destMarker
if (destMarker && destMarker !== sMarker) destMarker.setMap(null);
destMarker = sMarker;
// 直接传入坐标对象,避免关键字搜索失败
var loc = new AMap.LngLat(station.longitude, station.latitude);
startRouteSearch(loc);
});
stationMarkers.push(sMarker);
});
} else {
console.log("JS->: 业务报错或无数据:", res.message);
}
})
.catch(err => console.error('JS->:获取站点信息失败:', err));
}
/**
* 地理编码并在地图标记终点
*/
function markDestination(address, name, longitude, latitude) {
// 1. 清除旧的终点标记
if (destMarker) destMarker.setMap(null);
// 2. 创建自定义图标
var destIcon = new AMap.Icon({
size: new AMap.Size(32, 32), // 图标尺寸
image: 'ic_tag.png', // 本地图片路径
imageSize: new AMap.Size(32, 32) // 图片在图标内拉伸的大小
});
// 3. 创建标记
destMarker = new AMap.Marker({
map: map,
position: position,
icon: "https://webapi.amap.com/images/car.png",
offset: new AMap.Pixel(-26, -13),
autoRotation: true,
angle: angle || 0,
position: [longitude, latitude],
icon: destIcon, // 使用自定义图标
offset: new AMap.Pixel(-16, -32),
title: name,
label: {
content: '<div class="custom-bubble">' + name + '</div>',
direction: 'top'
}
});
// 第一次定位移动中心
map.setCenter(position);
} else {
marker.setPosition(position);
if (angle) marker.setAngle(angle);
}
}
// --- 核心:规划路线 ---
function startRouteSearch() {
var startKw = document.getElementById('startInput').value;
var endKw = document.getElementById('endInput').value;
if(!endKw) {
console.log("FlutterLog: 终点不能为空");
return;
console.log("JS->: 终点标记已添加", address);
}
console.log("FlutterLog: 开始规划..." + (isTruckMode ? "货车" : "轿车"));
// 清除之前的路线
if(driving) driving.clear();
if(truckDriving) truckDriving.clear();
// 构造起点和终点参数
// 高德API Search方法支持点对象数组[{keyword: '名字', city: '城市'}, {keyword: '...'}]
var points = [];
// 1. 处理起点
if (startKw) {
// 如果用户输入了文字
points.push({ keyword: startKw });
} else {
// 如果用户没输入,使用当前定位
/**
* 点击按钮回到当前位置
*/
function backToLocation() {
if (currentLng && currentLat) {
// 关键点:当混合使用坐标和关键字时,必须构建 LngLat 对象
map.panTo([currentLng, currentLat]);
map.setZoom(17);
console.log("JS: 已回到当前位置");
} else {
console.log("JS: 暂无定位数据,向 Flutter 请求...");
if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler('requestLocation');
}
}
}
/**
* 路径规划
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
*/
function startRouteSearch(destLoc) {
var startKw = document.getElementById('startInput').value;
var endKw = document.getElementById('endInput').value;
if (!startKw) {
alert("请输入起点");
return;
}
if (!endKw) {
alert("请输入终点");
return;
}
if (driving) driving.clear();
document.getElementById('startInput').blur();
document.getElementById('endInput').blur();
var points = [];
// 1. 起点逻辑
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
if (!currentLng || !currentLat) {
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
alert("正在获取定位,请稍后...");
return;
}
points.push({
keyword: '我的位置',
location: new AMap.LngLat(currentLng, currentLat)
});
} else {
console.log("FlutterLog: 无定位数据且无输入");
alert("未获取到定位,请输入起点");
return;
points.push({
keyword: startKw
});
}
}
// 2. 处理终点
points.push({ keyword: endKw });
// 显示底部面板
document.getElementById('panel').style.display = 'block';
// 3. 发起请求
if (isTruckMode && truckDriving) {
// 货车接口略有不同,需要传入 path 数组
// truckDriving.search(path, callback)
// 这里为了简化,我们先演示轿车。货车通常需要具体的经纬度,建议先通过 Geocoder 把 endKw 转成经纬度再传给货车接口
console.log("FlutterLog: 货车API需要更严格的经纬度参数建议先使用轿车演示");
}
// 默认使用轿车规划 (支持 keyword + location 混合)
driving.search(points, function(status, result) {
if (status === 'complete') {
console.log('FlutterLog: 规划成功');
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
if (destLoc) {
points.push({
keyword: endKw,
location: destLoc // 关键:使用精确坐标
});
} else {
console.log('FlutterLog: 规划失败: ' + result);
// 常见错误: "USER_DAILY_QUERY_OVER_LIMIT" (Key额度超限)
// "INVALID_USER_KEY" (Key错误)
// "no_data" (地点没找到)
points.push({
keyword: endKw
});
}
});
}
// --- 绘制自定义路径 (保持原有功能) ---
function drawCustomRoute(pointsJsonString) {
// 如果是从Flutter传来的JSON字符串需要Parse如果是对象则直接用
var path = typeof pointsJsonString === 'string' ? JSON.parse(pointsJsonString) : pointsJsonString;
// 3. 发起搜索
driving.search(points, function (status, result) {
if (status === 'complete') {
console.log('JS: 规划成功');
var panel = document.getElementById('panel');
panel.style.display = 'block';
document.body.classList.add('panel-active');
}
// else {
// console.error('JS: 规划失败', result);
// // 如果坐标规划都失败了,通常是由于起终点距离过近或政策限制(如货车禁行)
// alert("路径规划未成功,请尝试微调起终点");
// }
});
}
if (driving) driving.clear();
// 辅助功能:手动绘制路线 (Flutter 可能用到)
function drawCustomRoute(pointsJsonString) {
var path = typeof pointsJsonString === 'string' ? JSON.parse(pointsJsonString) : pointsJsonString;
if (driving) driving.clear();
var polyline = new AMap.Polyline({
path: path,
isOutline: true,
outlineColor: '#ffeeee',
borderWeight: 3,
strokeColor: "#3366FF",
strokeWeight: 6,
lineJoin: 'round'
});
map.add(polyline);
map.setFitView([polyline]);
}
var polyline = new AMap.Polyline({
path: path,
isOutline: true,
outlineColor: '#ffeeee',
borderWeight: 3,
strokeColor: "#3366FF",
strokeWeight: 6,
lineJoin: 'round'
});
map.add(polyline);
map.setFitView([polyline]);
}
window.onload = initMap;
</script>
window.onload = initMap;
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

View File

@@ -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'

View File

@@ -1,9 +1,19 @@
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):
- Flutter
- Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
@@ -20,7 +30,9 @@ PODS:
- FlutterMacOS
- image_picker_ios (0.0.1):
- Flutter
- MTBBarcodeScanner (5.0.11)
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
@@ -29,9 +41,8 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- qr_code_scanner_plus (0.2.6):
- saver_gallery (0.0.1):
- Flutter
- MTBBarcodeScanner
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -39,33 +50,44 @@ 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`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- qr_code_scanner_plus (from `.symlinks/plugins/qr_code_scanner_plus/ios`)
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
https://gitee.com/aliyun/aliyun-specs.git:
- AlicloudUTDID
https://github.com/aliyun/aliyun-specs.git:
- AlicloudELS
- AlicloudPush
trunk:
- MTBBarcodeScanner
- OrderedSet
EXTERNAL SOURCES:
aliyun_push_flutter:
:path: ".symlinks/plugins/aliyun_push_flutter/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
flutter_app_update:
:path: ".symlinks/plugins/flutter_app_update/ios"
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_native_splash:
@@ -76,37 +98,44 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/geolocator_apple/darwin"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
qr_code_scanner_plus:
:path: ".symlinks/plugins/qr_code_scanner_plus/ios"
saver_gallery:
:path: ".symlinks/plugins/saver_gallery/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
: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
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_pdfview: 54e283d5851b0b247b3cc57877d35f1a05a204de
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
qr_code_scanner_plus: 7e087021bc69873140e0754750eb87d867bed755
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
PODFILE CHECKSUM: 6416011b1bc721211379eaad259ff1cba3dbfad2
PODFILE CHECKSUM: 357c01ff4e7591871e8c4fd6462220a8c7447220
COCOAPODS: 1.16.2

View File

@@ -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;

View File

@@ -1,122 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"size" : "29x29",
"filename" : "Icon-App-29x29 1.png",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
"scale" : "3x",
"size" : "29x29"
},
{
"size" : "40x40",
"filename" : "Icon-App-80x80.png",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "60x60",
"filename" : "Icon-App-120x120.png",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
"scale" : "3x",
"size" : "40x40"
},
{
"size" : "60x60",
"filename" : "Icon-App-120x120 1.png",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-App-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"size" : "20x20",
"filename" : "Icon-App-20x20@2x 1.png",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size" : "29x29",
"filename" : "Icon-App-29x29.png",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"size" : "29x29",
"filename" : "Icon-App-29x29@2x 1.png",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "76x76",
"filename" : "Icon-App-76x76.png",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "76x76"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-App-83.5x83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -2,16 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要您的位置信息以在地图上展示</string>
<!-- 建议添加即使你只申请WhenInUse有些插件逻辑可能会检查这个key -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>我们需要您的位置来规划路线</string>
<!-- 建议添加旧版本iOS兼容 -->
<key>NSLocationAlwaysUsageDescription</key>
<string>我们需要您的位置来规划路线</string>
<key></key>
<string></string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@@ -32,8 +26,24 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>需要访问您的相机以扫描二维码</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>我们需要您的位置来规划路线</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>我们需要您的位置来规划路线</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要您的位置信息以在地图上展示</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问您的相册以选择二维码图片进行识别</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问您的相册以选择二维码图片进行识别</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -51,17 +61,26 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>uses</key>
<string></string>
<key>NSCameraUsageDescription</key>
<string>需要访问您的相机以扫描二维码</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问您的相册以选择二维码图片进行识别</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要访问您的相册以选择二维码图片进行识别</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>zh-Hans</string>
<string>en</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<!-- 允许在“文件”App中直接打开文档 -->
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View 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>

View File

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

View File

@@ -1,4 +1,5 @@
import 'package:encrypt/encrypt.dart';
import 'package:flutter/material.dart' as ui;
class LoginUtil {
static final _keyString = '915eae87951a448c86c47796e44c1fcf';
@@ -26,5 +27,9 @@ class LoginUtil {
final decrypted = _encrypter.decrypt(encrypted);
return decrypted;
}
static ui.Image getAssImg(String imgName){
return ui.Image(image: ui.AssetImage('assets/images/$imgName.png'),fit: ui.BoxFit.cover,);
}
}

View File

@@ -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,
};

View File

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

View File

@@ -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() => {

View File

@@ -5,13 +5,20 @@ class AppTheme {
static const String Font_YuYang = 'YuYang';
static const Color themeColor = Color(0xFF0c83c3);
static const Color themeColor = Color(0xFF017137);
//是否开放域名切换
static const bool is_show_host = false;
static const String test_service_url = "http://47.100.49.118:8090/api/";
//http://192.168.110.222:8080/
//http://192.168.110.44:8080/
static String test_service_url = "http://47.101.201.13:8443/api/";
static const String release_service_url = "";
//加氢站相关查询
static const String jiaqing_service_url = "https://lnh2e.com/api/lingniu-manager-v1/v1/";
static const String jiaqing_service_url =
"https://beta.lnh2e.com/api/lingniu-manager-v1/v1/";
//车辆信息
static const String car_service_url = "http://47.99.166.38:20000/";
@@ -19,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,
@@ -56,12 +68,9 @@ class AppTheme {
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromARGB(255, 34, 34, 34),
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
titleTextStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
bottomAppBarTheme: BottomAppBarThemeData(
bottomAppBarTheme: BottomAppBarThemeData(
color: Color.fromARGB(255, 34, 34, 34),
elevation: 4.0,
),

View File

@@ -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);

View File

@@ -1,23 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:get_storage/get_storage.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/AuthGuard.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/common/token_interceptor.dart';
import 'package:ln_jq_app/storage_service.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'common/styles/theme.dart';
import 'pages/home/view.dart';
import 'pages/login/view.dart';
import 'pages/welcome/view.dart'; // 引入启动页
void main() async {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding widgetsBinding = await init(
isDebug: true,
isDebug: false,
logTag: '小羚羚',
supportedLocales: [Locale('zh', 'CN')],
supportedLocales: [const Locale('zh', 'CN')],
);
// 保持原生闪屏页,直到 WelcomeController 调用 remove()
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await GetStorage.init();
@@ -38,23 +43,20 @@ void main() async {
// 主题
theme: AppTheme.light,
// Dark主题
darkTheme: AppTheme.dark,
darkTheme: AppTheme.light,
// AppTitle
title: '小羚羚',
// 首页入口
home: HomePage(),
//组件国际化
fallbackLocale: Locale('zh', 'CN'),
supportedLocales: [Locale('zh', 'CN')],
// 将入口改为启动页
home: const WelcomePage(),
fallbackLocale: const Locale('zh', 'CN'),
supportedLocales: const [Locale('zh', 'CN')],
localizationsDelegates: const [
RefreshLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
// Builder
builder: (context, widget) {
// do something....
return widget!;
},
),
@@ -62,22 +64,26 @@ void main() async {
}
void initHttpSet() {
// 设置基础 URL
AppTheme.test_service_url = StorageService.to.hostUrl ?? AppTheme.test_service_url;
HttpService.to.init(timeout: 15);
HttpService.to.setBaseUrl(AppTheme.test_service_url);
//指定请求头
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
// 设置全局响应处理器
HttpService.to.setOnResponseHandler((response) async {
try {
final baseModel = BaseModel<dynamic>.fromJson(response.data);
if (response.data == null) {
return null;
}
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());
await AuthGuard.handle401(baseModel.message);
return baseModel.message;
} else {
return baseModel.message;
return (baseModel.error.toString()).isEmpty
? "服务繁忙,稍后重试"
: baseModel.error.toString();
}
} on Exception catch (e) {
e.printInfo();

View File

@@ -1,22 +1,25 @@
import 'package:flutter/material.dart';
import 'package:getx_scaffold/common/index.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart';
import 'package:ln_jq_app/pages/b_page/base_widgets/controller.dart';
import 'package:ln_jq_app/pages/b_page/reservation/view.dart';
import 'package:ln_jq_app/pages/b_page/site/view.dart';
class B_BaseWidgetsPage extends GetView<B_BaseWidgetsController> {
B_BaseWidgetsPage({super.key});
B_BaseWidgetsPage({super.key});
final PageController _pageController = PageController();
// 主视图
Widget _buildView() {
return PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(), // 禁止滑动
onPageChanged: (index) {
jumpTabAndPage(index);
},
children: _buildPages(), // 页面的列表
children: _buildPages(),
);
}
@@ -25,35 +28,59 @@ class B_BaseWidgetsPage extends GetView<B_BaseWidgetsController> {
controller.updateUi(); // 更新 UI
_pageController.jumpToPage(controller.pageIndex);
}
// 对应的页面
List<Widget> _buildPages() {
return [
SitePage(),
ReservationPage(),
];
return [SitePage(), ReservationPage()];
}
//导航栏
// 自定义导航栏 (悬浮胶囊样式)
Widget _buildNavigationBar() {
return NavigationX(
currentIndex: controller.pageIndex, // 当前选中的tab索引
onTap: (index) {
jumpTabAndPage(index);
}, // 切换tab事件
items: [
NavigationItemModel(
label: '加氢预约',
icon: AntdIcon.orderedlist,
selectedIcon: AntdIcon.calendar_fill,
badge: '99+',
dot: true,
return SafeArea(
child: Container(
height: 50.h,
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
decoration: BoxDecoration(
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
NavigationItemModel(
label: '站点信息',
icon: AntdIcon.car,
selectedIcon: AntdIcon.car_fill,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
_buildNavItem(1, "ic_h2_my@2x", "ic_h2_my_select@2x"),
],
),
],
),
);
}
// 构建单个导航项
Widget _buildNavItem(int index, String icon, String selectedIcon) {
bool isSelected = controller.pageIndex == index;
return GestureDetector(
onTap: () => jumpTabAndPage(index),
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(horizontal: 50.w, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF006633) : Colors.transparent, // 选中时的深绿色背景
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
height: 24,
width: 24,
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),
),
),
);
}
@@ -64,10 +91,10 @@ class B_BaseWidgetsPage extends GetView<B_BaseWidgetsController> {
id: 'b_baseWidgets',
builder: (_) {
return Scaffold(
extendBody: false,
extendBody: true,
resizeToAvoidBottomInset: false,
bottomNavigationBar: _buildNavigationBar(),
body: SafeArea(child: _buildView()),
body: _buildView(),
);
},
);

View File

@@ -0,0 +1,149 @@
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/b_page/site/controller.dart'; // Reuse ReservationModel
class HistoryController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'history';
// --- 定义 API 需要的日期格式化器 ---
final DateFormat _apiDateFormat = DateFormat('yyyy-MM-dd');
// 默认查询最近7天
final Rx<DateTime> startDate = DateTime.now().subtract(const Duration(days: 7)).obs;
final Rx<DateTime> endDate = DateTime.now().obs;
final TextEditingController plateNumberController = TextEditingController();
final RxString totalHydrogen = '0'.obs;
final RxString totalCompletions = '0'.obs;
final RxList<ReservationModel> historyList = <ReservationModel>[].obs;
final RxBool isLoading = true.obs;
final RxBool hasData = false.obs;
String get formattedStartDate => DateFormat('yyyy/MM/dd').format(startDate.value);
String get formattedEndDate => DateFormat('yyyy/MM/dd').format(endDate.value);
String stationName = "";
final Map<String, String> statusOptions = {
'': '全部',
'100': '未预约加氢',
'0': '待加氢',
'1': '已加氢',
'2': '未加氢',
'5': '拒绝加氢',
};
final RxString selectedStatus = ''.obs;
final RxString selectedDateType = ''.obs; // week, month, three_month
@override
void onInit() {
super.onInit();
final args = Get.arguments as Map<String, dynamic>;
stationName = args['stationName'] as String? ?? "";
refreshData();
}
void refreshData() {
getAllOrderCounts();
fetchHistoryData();
}
Future<void> getAllOrderCounts() async {
var response = await HttpService.to.post(
"appointment/orderAddHyd/getAllOrderCounts",
data: {
/*'startTime': _apiDateFormat.format(startDate.value),
'endTime': _apiDateFormat.format(endDate.value),*/
'plateNumber': plateNumberController.text,
'stationName': stationName,
"status": selectedStatus.value,
"dateType": selectedDateType.value,
},
);
if (response == null || response.data == null) {
totalHydrogen.value = '0';
totalCompletions.value = '0';
return;
}
try {
final baseModel = BaseModel<dynamic>.fromJson(response.data);
final dataMap = baseModel.data as Map<String, dynamic>;
totalHydrogen.value = '${dataMap['totalAddAmount'] ?? 0}';
totalCompletions.value = '${dataMap['orderCompleteCount'] ?? 0}';
} catch (e) {
totalHydrogen.value = '0';
totalCompletions.value = '0';
}
}
Future<void> fetchHistoryData() async {
isLoading.value = true;
updateUi();
try {
var response = await HttpService.to.post(
"appointment/orderAddHyd/sitOrderPage",
data: {
/*'startTime': _apiDateFormat.format(startDate.value),
'endTime': _apiDateFormat.format(endDate.value),*/
'plateNumber': plateNumberController.text,
'pageNum': 1,
'pageSize': 50,
'stationName': stationName,
"status": selectedStatus.value,
"dateType": selectedDateType.value,
},
);
if (response == null || response.data == null) {
_resetData();
return;
}
final baseModel = BaseModel<dynamic>.fromJson(response.data);
if (baseModel.code == 0 && baseModel.data != null) {
final dataMap = baseModel.data as Map<String, dynamic>;
final List<dynamic> listFromServer = dataMap['records'] ?? [];
historyList.assignAll(
listFromServer
.map((item) => ReservationModel.fromJson(item as Map<String, dynamic>))
.toList(),
);
hasData.value = historyList.isNotEmpty;
} else {
_resetData();
}
} catch (e) {
_resetData();
} finally {
isLoading.value = false;
updateUi();
}
}
void _resetData() {
historyList.clear();
hasData.value = false;
}
void onStatusSelected(String status) {
if (selectedStatus.value == status) return;
selectedStatus.value = status;
refreshData();
}
void onDateTypeSelected(String type) {
selectedDateType.value = type;
refreshData();
}
@override
void onClose() {
plateNumberController.dispose();
super.onClose();
}
}

View File

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

View File

@@ -1,42 +1,194 @@
import 'dart:async';
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
bool get listenLifecycleEvent => true;
@override
void onInit() {
super.onInit();
// 1. 初始化默认时间
customStartTime = DateTime.now();
customEndTime = customStartTime!.add(const Duration(days: 1));
renderData();
msgNotice(); // 红点消息
startAutoRefresh();
}
@override
void onPaused() {
stopAutoRefresh();
super.onPaused();
}
@override
void onClose() {
stopAutoRefresh();
broadcastTitleController.dispose();
broadcastContentController.dispose();
super.onClose();
}
void startAutoRefresh() {
// 先停止已存在的定时器,防止重复启动
stopAutoRefresh();
// 创建一个每5分钟执行一次的周期性定时器
_refreshTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
renderData();
});
}
///停止定时器的方法
void stopAutoRefresh() {
// 如果定时器存在并且是激活状态,就取消它
_refreshTimer?.cancel();
_refreshTimer = null; // 置为null方便判断
}
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 = "";
Timer? _refreshTimer;
bool isNotice = false;
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 {
final List<dynamic> dataList = jobDataResult.data is List
? jobDataResult.data
: [];
final firstJob = dataList[0];
jobId = firstJob["id"] ?? "";
String endTime = firstJob["endTime"] ?? "";
String beginTime = firstJob["beginTime"] ?? "";
String hydStatus = firstJob["hydStatus"].toString() ?? "";
String hydStatusStr = "";
if (hydStatus == "0") {
hydStatusStr = "营运中";
} else if (hydStatus == "1") {
hydStatusStr = "维修中";
} else if (hydStatus == "2") {
hydStatusStr = "站点关闭";
} else if (hydStatus == "3") {
hydStatusStr = "暂停营业";
}
//现在的时间晚于开始时间就不显示文案
bool isJobStarted = false;
try {
if (beginTime.isNotEmpty) {
DateTime beginDateTime = DateTime.parse(beginTime);
if (DateTime.now().isAfter(beginDateTime)) {
isJobStarted = true;
}
}
} catch (e) {
print("开始时间解析失败: $e");
}
if (isJobStarted) {
jobTipStr = "";
}
//结束时间
if (endTime.isNotEmpty) {
try {
// 解析时间字符串
DateTime endDateTime = DateTime.parse(endTime);
DateTime beginDateTime = DateTime.parse(beginTime);
DateTime now = DateTime.now(); //计算时间差 (endTime - now)
Duration diff = endDateTime.difference(now);
// 计算小时数 (允许小数,例如 0.5)
// inMinutes / 60 可以得到更精确的小数小时
double hoursLeft = diff.inMinutes / 60.0;
//计算当前时间-开始时间
Duration startDiff = beginDateTime.difference(now);
double hoursUntilStart = startDiff.inMinutes / 60.0;
// 只有在【当前时间早于开始时间】且【剩余时间大于0】时才显示文案
if (now.isBefore(beginDateTime) && hoursLeft > 0) {
// 如果是正数,表示还有多久结束
String timeTip = " ${hoursUntilStart.toStringAsFixed(2)}小时后";
jobTipStr = "$timeTip$hydStatusStr";
} else {
jobTipStr = "";
}
jobDetailsStr =
"当前站点已设置$beginTime至$endTime,共${hoursLeft.toStringAsFixed(2)}小时,为$hydStatusStr状态";
// 如果是处于非营运状态,自动回填开始和结束时间
// 假设 customStartTime 是现在customEndTime 是接口返回的结束时间
customStartTime = beginDateTime;
customEndTime = endDateTime;
} catch (e) {
print("时间解析失败: $e");
}
}
} catch (e) {
Logger.d("解析失败或者没返回信息: $e");
jobTipStr = "";
}
}
}
//获取站点信息
var responseData = await HttpService.to.get(
'appointment/station/getStationInfoById?hydrogenId=${StorageService.to.userId}',
);
@@ -49,32 +201,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 +236,39 @@ class ReservationController extends GetxController with BaseControllerMixin {
operatingEnterprise = operatingEnterprise.isEmpty ? "暂未设置" : operatingEnterprise;
updateUi();
dismissLoading();
} catch (e) {
// 如果解析 JSON 失败
dismissLoading();
showToast('数据异常');
}
} catch (e) {
dismissLoading();
} finally {
updateUi();
}
}
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;
updateUi();
}
}
}
/// 更新运营状态的方法
void onOperationStatusChanged(String? newValue) {
if (newValue != null) {
selectedOperationStatus = newValue;
@@ -106,14 +276,105 @@ 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',
data: {
'hydrogenId': hydrogenId,
'name': name,
'siteStatus': selectedOperationStatus == "营运中"
? "0"
: selectedOperationStatus == "维修中"
@@ -123,18 +384,27 @@ 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("保存成功,已同步通知对应司机");
//重新刷新页面
renderData();
}
dismissLoading();
} catch (e) {
@@ -142,11 +412,87 @@ 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());
}
}

View File

@@ -1,11 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter/services.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart';
import 'package:ln_jq_app/pages/b_page/reservation/controller.dart';
import 'package:ln_jq_app/pages/c_page/message/view.dart';
class ReservationPage extends GetView<ReservationController> {
const ReservationPage({super.key});
// 定义主题色
static const kPrimaryColor = Color(0xFF006D35); // 效果图深绿色
static const kBgColor = Color(0xFFF5F7F9); // 背景灰
@override
Widget build(BuildContext context) {
return GetBuilder<ReservationController>(
@@ -13,21 +19,28 @@ class ReservationPage extends GetView<ReservationController> {
id: 'b_reservation',
builder: (_) {
return Scaffold(
backgroundColor: kBgColor,
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeaderCard(),
const SizedBox(height: 12),
_buildInfoFormCard(context),
const SizedBox(height: 12),
_buildTipsCard(),
const SizedBox(height: 12),
_buildLogoutButton(),
],
),
child: Column(
children: [
_buildTopSection(context),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
SizedBox(height: 16),
_buildBasicInfoCard(),
SizedBox(height: 16),
_buildOperationContentCard(context),
SizedBox(height: 16.h),
_buildSystemTips(),
SizedBox(height: 24),
_buildLogoutButton(),
SizedBox(height: 95.h),
],
),
),
],
),
),
);
@@ -35,126 +48,150 @@ class ReservationPage extends GetView<ReservationController> {
);
}
/// 构建顶部的站点信息头卡片
Widget _buildHeaderCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
/// 1. 顶部个人信息及统计栏
Widget _buildTopSection(BuildContext context) {
return Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 10,
left: 20,
right: 20,
bottom: 25,
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.local_gas_station, color: Colors.blue, size: 40),
title: Text(
controller.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
subtitle: Text(controller.address),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(12),
Row(
children: [
CircleAvatar(
radius: 25,
backgroundColor: Colors.white,
child: LoginUtil.getAssImg('ic_user_logo@2x'),
),
child: Text(
controller.selectedOperationStatus,
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 12,
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
controller.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
_buildStatusTag(),
],
),
const SizedBox(height: 4),
Text(
"站点:${controller.address}",
style: TextStyle(color: Colors.grey[500], fontSize: 13),
),
],
),
),
),
IconButton(
onPressed: () async {
var scanResult = await Get.to(() => const MessagePage());
if (scanResult == null) {
controller.msgNotice();
}
},
style: IconButton.styleFrom(
backgroundColor: Colors.grey[100],
padding: const EdgeInsets.all(8),
),
icon: Badge(
smallSize: 8,
backgroundColor: controller.isNotice ? Colors.red : Colors.transparent,
child: const Icon(
Icons.notifications_outlined,
color: Colors.black87,
size: 30,
),
),
),
],
),
const Divider(height: 1, indent: 16, endIndent: 16),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildHeaderStat(controller.costPrice, '氢气价格'),
_buildHeaderStat(controller.timeStr, '营业时间'),
_buildHeaderStat('98%', '设备状态'),
],
),
const SizedBox(height: 25),
Row(
children: [
_buildStatBox("氢气价格", "Hydrogen price", controller.customerPrice, "/kg"),
SizedBox(width: 4.w),
_buildStatBox("营业时间", "Opening time", controller.timeStr, ""),
SizedBox(width: 4.w),
_buildStatBox("设备状态", "Anlagenzustand", "98", "%"),
],
),
],
),
);
}
/// 构建头部卡片中的单个统计项
Widget _buildHeaderStat(String value, String label) {
return Column(
children: [
Text(
value,
style: const TextStyle(
color: Colors.blue,
fontSize: 20,
fontWeight: FontWeight.bold,
),
Widget _buildStatusTag() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFE1F5FE),
borderRadius: BorderRadius.circular(10),
),
child: Text(
controller.selectedOperationStatus,
style: TextStyle(
color: Color(0xFF03A9F4),
fontSize: 12.sp,
fontWeight: FontWeight.w600,
),
const SizedBox(height: 4),
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
),
);
}
/// 构建包含所有信息表单的卡片
Widget _buildInfoFormCard(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Padding(
padding: const EdgeInsets.all(16.0),
Widget _buildStatBox(String title, String enTitle, String value, String unit) {
return Expanded(
child: Container(
padding: EdgeInsets.only(left: 12.w, top: 4.h, bottom: 4.h),
decoration: BoxDecoration(
color: kBgColor,
borderRadius: BorderRadius.circular(12),
),
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.costPrice),
_buildDisplayField(label: '官方价格 (元/kg)', value: controller.customerPrice),
const SizedBox(height: 16),
// --- 运营信息 ---
_buildSectionTitle('运营信息'),
Text('运营状态', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
Text(
title,
style: TextStyle(
fontSize: 12.sp,
color: Color.fromRGBO(51, 51, 51, 0.8),
fontWeight: FontWeight.w400,
),
),
Text(enTitle, style: const TextStyle(fontSize: 9, color: Colors.grey)),
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: 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)),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: Color(0xFF333333),
),
),
const SizedBox(width: 2),
Text(unit, style: const TextStyle(fontSize: 11, color: Colors.grey)),
],
),
],
),
@@ -162,72 +199,269 @@ class ReservationPage extends GetView<ReservationController> {
);
}
/// 构建静态提示信息卡片
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'),
],
),
/// 2. 站点基本信息
Widget _buildBasicInfoCard() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
);
}
/// 构建退出登录按钮
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 _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(width: 4, height: 16, color: Colors.blue),
const SizedBox(width: 8),
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
Text(
"站点基本信息",
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 15),
_buildInfoRow("站点名称", controller.name),
_buildInfoRow("运营企业", controller.operatingEnterprise),
_buildInfoRow("站点地址", controller.address),
],
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(color: Colors.grey, fontSize: 11.sp),
),
Expanded(
child: Text(
value,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
color: const Color(0xFF333333),
fontSize: 12.sp,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
/// 3. 运营信息/站点广播 Tab 及内容
Widget _buildOperationContentCard(BuildContext context) {
return GestureDetector(
onTap: hideKeyboard,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
// 自定义 TabBar
Obx(
() => Padding(
padding: const EdgeInsets.only(left: 16, top: 16),
child: Row(
children: [
_buildTabTitle(0, "运营信息"),
const SizedBox(width: 30),
_buildTabTitle(1, "站点广播"),
],
),
),
),
Obx(
() => controller.selectedTabIndex.value == 0
? _buildOperatingForm(context)
: _buildBroadcastForm(),
),
],
),
),
);
}
Widget _buildTabTitle(int index, String title) {
bool isSelected = controller.selectedTabIndex.value == index;
return GestureDetector(
onTap: () => controller.selectedTabIndex.value = index,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: isSelected ? Colors.black87 : Colors.grey,
),
),
if (isSelected)
Container(
margin: const EdgeInsets.only(top: 4),
width: 25,
height: 3,
decoration: BoxDecoration(
color: const Color(0xFF00A870), // 效果图中的亮绿色横线
borderRadius: BorderRadius.circular(2),
),
),
],
),
);
}
Widget _buildOperatingForm(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'运营状态',
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 1),
fontSize: 12.sp,
fontWeight: FontWeight.bold,
),
),
//加氢站未执行的状态修改任务
if (controller.jobTipStr.isNotEmpty)
GestureDetector(
onTap: controller.showJob,
child: Row(
children: [
Text(
controller.jobTipStr,
style: TextStyle(color: Colors.yellow[800], fontSize: 14),
),
SizedBox(width: 2.w),
Icon(AntdIcon.info_circle, size: 14, color: Colors.yellow[800]),
],
),
),
],
),
const SizedBox(height: 12),
// 状态网格选择
Wrap(
spacing: 4,
runSpacing: 4,
children: controller.operationStatusOptions.map((status) {
bool isSelected = controller.selectedOperationStatus == status;
return GestureDetector(
onTap: () => controller.onOperationStatusChanged(status),
child: Container(
width: (Get.width - 80) / 2,
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isSelected ? kPrimaryColor : const Color(0xFFEBEBEB),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Text(
status,
style: TextStyle(
fontSize: 13.sp,
color: isSelected ? Colors.white : Color.fromRGBO(51, 51, 51, 1),
fontWeight: FontWeight.w500,
),
),
),
);
}).toList(),
),
SizedBox(height: 12.h),
if (controller.selectedOperationStatus == "营运中")
_buildDisplayField(label: '营业时间', value: controller.timeStr)
else
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInputLabel("开始时间"),
_buildDateTimePicker(
controller.customStartTimeStr,
() => controller.pickDateTime(context, true),
),
const SizedBox(height: 15),
_buildInputLabel("结束时间"),
_buildDateTimePicker(
controller.customEndTimeStr,
() => controller.pickDateTime(context, false),
),
const SizedBox(height: 15),
],
),
_buildDisplayField(label: '联系电话', value: controller.phone),
const SizedBox(height: 25),
Row(
children: [
Expanded(
flex: 1,
child: OutlinedButton(
onPressed: () {
controller.renderData();
}, // 重置逻辑
style: OutlinedButton.styleFrom(
side: const BorderSide(color: kPrimaryColor),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("重置", style: TextStyle(color: kPrimaryColor)),
),
),
const SizedBox(width: 15),
Expanded(
flex: 2,
child: ElevatedButton(
onPressed: controller.saveInfo,
style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("保存设置", style: TextStyle(color: Colors.white)),
),
),
],
),
],
),
);
}
/// 构建一个“标签+纯文本”的显示行
Widget _buildDisplayField({required String label, required String value}) {
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)),
Text(
label,
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 1),
fontSize: 12.sp,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Container(
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,16 +475,178 @@ class ReservationPage extends GetView<ReservationController> {
);
}
/// 构建带图标的提示信息行
Widget _buildInfoItem(IconData icon, String text) {
return Row(
children: [
Icon(icon, color: Colors.blue, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(text, style: const TextStyle(fontSize: 14, color: Colors.black54)),
Widget _buildBroadcastForm() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInputLabel("通知标题"),
TextField(
controller: controller.broadcastTitleController,
decoration: _inputDecoration("例如:临时闭站通知"),
),
const SizedBox(height: 15),
_buildInputLabel("通知内容"),
TextField(
controller: controller.broadcastContentController,
maxLines: 4,
decoration: _inputDecoration("请输入通知内容..."),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
flex: 1,
child: OutlinedButton(
onPressed: () {
controller.broadcastTitleController.clear();
controller.broadcastContentController.clear();
}, // 重置逻辑
style: OutlinedButton.styleFrom(
side: const BorderSide(color: kPrimaryColor),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("重置", style: TextStyle(color: kPrimaryColor)),
),
),
const SizedBox(width: 15),
Expanded(
flex: 2,
child: ElevatedButton(
onPressed: controller.sendBroadcast,
style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor,
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("发送广播", style: TextStyle(color: Colors.white)),
),
),
],
),
],
),
);
}
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(left: 0, bottom: 8),
child: Text(
label,
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 1),
fontSize: 12.sp,
fontWeight: FontWeight.bold,
),
],
),
);
}
Widget _buildDateTimePicker(String value, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFF00A870).withOpacity(0.5)),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(value, style: const TextStyle(color: Colors.black87)),
const Icon(Icons.calendar_today_outlined, size: 18, color: Color(0xFF00A870)),
],
),
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(color: Colors.grey, fontSize: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFE0E0E0)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFE0E0E0)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
);
}
/// 4. 系统提醒
Widget _buildSystemTips() {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: const Color(0xFFF1F9F6), // 极浅绿色背景
borderRadius: BorderRadius.circular(10),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.info_outline, color: Color.fromRGBO(1, 113, 55, 1), size: 20),
SizedBox(width: 8.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"系统提醒",
style: TextStyle(
color: Color.fromRGBO(1, 113, 55, 1),
fontWeight: FontWeight.bold,
fontSize: 14.sp,
),
),
SizedBox(height: 6.h),
Text(
"请您确保所提供的信息准确无误,价格信息也将实时\n更新至用户端",
style: TextStyle(color: Color.fromRGBO(1, 113, 55, 0.8), fontSize: 12.sp),
),
SizedBox(height: 6.h),
Text(
"如有疑问请联系客服400-021-1773",
style: TextStyle(color: Color.fromRGBO(1, 113, 55, 0.8), fontSize: 12.sp),
),
],
),
],
),
);
}
/// 5. 退出登录按钮
Widget _buildLogoutButton() {
return SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: controller.logout,
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(204, 52, 46, 1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
elevation: 0,
),
child: const Text(
"退出登录",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:getx_scaffold/common/index.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/pages/c_page/car_info/view.dart';
import 'package:ln_jq_app/pages/c_page/mall/mall_view.dart';
import 'package:ln_jq_app/pages/c_page/map/view.dart';
import 'package:ln_jq_app/pages/c_page/mine/view.dart';
import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
@@ -9,9 +11,10 @@ import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
import 'index.dart';
class BaseWidgetsPage extends GetView<BaseWidgetsController> {
BaseWidgetsPage({super.key});
BaseWidgetsPage({super.key});
final PageController _pageController = PageController();
// 主视图
Widget _buildView() {
return PageView(
@@ -20,57 +23,70 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
onPageChanged: (index) {
jumpTabAndPage(index);
},
children: _buildPages(), // 页面的列表
children: _buildPages(),
);
}
void jumpTabAndPage(int index) {
controller.pageIndex = index; // 更新页面索引
controller.updateUi(); // 更新 UI
controller.pageIndex = index;
controller.updateUi();
_pageController.jumpToPage(controller.pageIndex);
}
// 对应的页面
List<Widget> _buildPages() {
return [
MapPage(),
ReservationPage(),
CarInfoPage(),
MinePage(),
];
return [ReservationPage(), MapPage(), MallPage(), CarInfoPage(), MinePage()];
}
//导航栏
// 自定义导航栏 (悬浮胶囊样式)
Widget _buildNavigationBar() {
return NavigationX(
currentIndex: controller.pageIndex, // 当前选中的tab索引
onTap: (index) {
jumpTabAndPage(index);
}, // 切换tab事件
items: [
NavigationItemModel(
label: '地图',
icon: AntdIcon.location,
selectedIcon: AntdIcon.location_fill,
dot: false,
return SafeArea(
child: Container(
height: Get.height * 0.05,
margin: const EdgeInsets.fromLTRB(24, 0, 24, 10), // 悬浮边距
decoration: BoxDecoration(
color: Color.fromRGBO(240, 244, 247, 1), // 浅灰色背景
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
NavigationItemModel(
label: '加氢预约',
icon: AntdIcon.orderedlist,
selectedIcon: AntdIcon.calendar_fill,
badge: '99+',
dot: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(0, "ic_h2_select@2x", "ic_h2@2x"),
_buildNavItem(1, "ic_map_select@2x", "ic_map@2x"),
_buildNavItem(2, "ic_mall_select@2x", "ic_mall@2x"),
_buildNavItem(3, "ic_car_select@2x", "ic_car@2x"),
_buildNavItem(4, "ic_user_select@2x", "ic_user@2x"),
],
),
NavigationItemModel(
label: '车辆信息',
icon: AntdIcon.car,
selectedIcon: AntdIcon.car_fill,
),
);
}
// 构建单个导航项
Widget _buildNavItem(int index, String icon, String selectedIcon) {
bool isSelected = controller.pageIndex == index;
return GestureDetector(
onTap: () => jumpTabAndPage(index),
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF006633) : Colors.transparent, // 选中时的深绿色背景
borderRadius: BorderRadius.circular(20),
),
NavigationItemModel(
label: '我的',
icon: AntdIcon.user,
selectedIcon: AntdIcon.user,
child: SizedBox(
height: 24,
width: 24,
child: LoginUtil.getAssImg(isSelected ? selectedIcon : icon),
),
],
),
);
}
@@ -81,10 +97,10 @@ class BaseWidgetsPage extends GetView<BaseWidgetsController> {
id: 'baseWidgets',
builder: (_) {
return Scaffold(
extendBody: false,
extendBody: true, // 重要:让 body 延伸到导航栏后面
resizeToAvoidBottomInset: false,
bottomNavigationBar: _buildNavigationBar(),
body: SafeArea(child: _buildView()),
body: _buildView(), // 移除 SafeArea 以获得更好的全屏沉浸感
);
},
);

View File

@@ -48,9 +48,9 @@ class AttachmentViewerController extends GetxController {
}
},
);
localFilePath.value = savePath;
} catch (e) {
showErrorToast('PDF文件加载失败请检查网络或文件链接');
print('PDF Download Error: $e');

View File

@@ -12,12 +12,12 @@ class AttachmentViewerPage extends GetView<AttachmentViewerController> {
@override
Widget build(BuildContext context) {
Get.put(AttachmentViewerController());
final fileName = controller.url.split('/').last;
// final fileName = controller.url.split('/').last;
return Scaffold(
appBar: AppBar(
title: Text(
fileName,
"证件详情",
style: const TextStyle(fontSize: 16),
overflow: TextOverflow.ellipsis,
),
@@ -40,11 +40,10 @@ class AttachmentViewerPage extends GetView<AttachmentViewerController> {
if (controller.fileType == 'pdf') {
if (controller.localFilePath.isNotEmpty) {
return PDFView(
key: ValueKey(controller.localFilePath.value),
filePath: controller.localFilePath.value,
enableSwipe: true,
swipeHorizontal: false,
autoSpacing: false,
swipeHorizontal: true,
autoSpacing: true,
pageFling: true,
onRender: (pages) {
print("PDF rendered with $pages pages.");

View File

@@ -1,36 +1,87 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'attachment_viewer_page.dart';
class CertificateViewerController extends GetxController {
class CertificateViewerController extends GetxController with BaseControllerMixin {
late final String title;
late final List<String> attachments;
// --- 新增: 状态管理 ---
/// 用于存储网络PDF的本地路径key是网络urlvalue是本地路径
final RxMap<String, String> localPdfPaths = <String, String>{}.obs;
/// 用于跟踪每个附件的加载状态key是网络url
final RxMap<String, bool> isLoading = <String, bool>{}.obs;
@override
String get builderId => "certificateviewer";
@override
void onInit() {
super.onInit();
// 从 Get.to 的 arguments 中获取标题和附件列表
title = Get.arguments['title'] ?? '证件详情';
attachments = List<String>.from(Get.arguments['attachments'] ?? []);
// --- 新增: 初始化时开始加载所有PDF ---
_loadAllPdfs();
}
/// 导航到通用的附件查看器页面
/// 遍历所有附件如果是PDF则进行下载
void _loadAllPdfs() {
for (var url in attachments) {
if (isPdf(url)) {
_downloadPdf(url);
}
}
}
/// 下载单个PDF文件
Future<void> _downloadPdf(String url) async {
if (url.isEmpty) return;
// 开始加载
isLoading[url] = true;
try {
final dio = Dio();
final Directory tempDir = await getTemporaryDirectory();
final String savePath = '${tempDir.path}/${url.split('/').last}';
// 检查文件是否已存在,避免重复下载
if (await File(savePath).exists()) {
localPdfPaths[url] = savePath;
isLoading[url] = false;
return;
}
await dio.download(url, savePath);
// 下载成功后,更新本地路径
localPdfPaths[url] = savePath;
} catch (e) {
print('PDF download error for $url: $e');
// 出错时也可以更新状态以便UI显示错误提示
} finally {
// 结束加载
isLoading[url] = false;
}
}
/// 导航到通用的附件查看器页面 (此方法保持不变)
void openAttachment(String url) {
if (url.isEmpty) {
showErrorToast('附件链接无效');
return;
}
Get.to(
() => const AttachmentViewerPage(),
arguments: {
'url': url,
},
);
Get.to(() => const AttachmentViewerPage(), arguments: {'url': url});
}
/// 检查 URL 是否为 PDF,以便在视图中显示不同的图标
/// 检查 URL 是否为 PDF (此方法保持不变)
bool isPdf(String url) {
return url.toLowerCase().endsWith('.pdf');
}

Some files were not shown because too many files have changed in this diff Show More