Compare commits
No commits in common. "fb412d90e382a1aa5d0e94b964728f577f3ca6e3" and "2fcd538649aeebeb77ae96d6cd748b93174e3bee" have entirely different histories.
fb412d90e3
...
2fcd538649
|
|
@ -14,7 +14,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "com.qysz.qgxgf"
|
||||
namespace = "com.company.myapp2"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "28.1.13356709"
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.qysz.qgxgf"
|
||||
applicationId = "com.company.myapp2"
|
||||
minSdk = 24
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
<!-- FileProvider 配置 -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.qysz.qgxgf.fileprovider"
|
||||
android:authorities="com.company.myapp2.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package com.company.myapp2
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.FileProvider
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.File
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val CHANNEL = "app.install"
|
||||
private val REQ_INSTALL_UNKNOWN = 9999
|
||||
|
||||
// 暂存安装请求(仅在跳转设置并等待返回时使用)
|
||||
private var pendingApkPath: String? = null
|
||||
private var pendingResult: MethodChannel.Result? = null
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"installApk" -> {
|
||||
val path = call.argument<String>("path")
|
||||
if (path == null) {
|
||||
result.error("NO_PATH", "no path provided", null)
|
||||
return@setMethodCallHandler
|
||||
}
|
||||
handleInstallRequest(path, result)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInstallRequest(path: String, result: MethodChannel.Result) {
|
||||
val file = File(path)
|
||||
if (!file.exists()) {
|
||||
result.error("NO_FILE", "file not exist", null)
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// 8.0+ 需要 app 级别未知来源授权
|
||||
if (!packageManager.canRequestPackageInstalls()) {
|
||||
// 存储请求信息以便用户返回后继续
|
||||
pendingApkPath = path
|
||||
pendingResult = result
|
||||
|
||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
|
||||
Uri.parse("package:$packageName"))
|
||||
// 使用 startActivityForResult 以便用户返回后可以继续安装
|
||||
startActivityForResult(intent, REQ_INSTALL_UNKNOWN)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 已有授权 或 非 8.0+:直接安装
|
||||
installApkInternal(path, result)
|
||||
}
|
||||
|
||||
// 真正执行安装的函数(假定有权限)
|
||||
private fun installApkInternal(path: String, result: MethodChannel.Result) {
|
||||
val file = File(path)
|
||||
if (!file.exists()) {
|
||||
result.error("NO_FILE", "file not exist", null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val apkUri: Uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
startActivity(intent)
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
result.error("INSTALL_FAILED", e.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQ_INSTALL_UNKNOWN) {
|
||||
// 用户从系统设置页返回后,检查是否已授权
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (packageManager.canRequestPackageInstalls()) {
|
||||
// 授权已开:继续安装
|
||||
val path = pendingApkPath
|
||||
val res = pendingResult
|
||||
// 清理 pending 状态
|
||||
pendingApkPath = null
|
||||
pendingResult = null
|
||||
if (path != null && res != null) {
|
||||
installApkInternal(path, res)
|
||||
} else {
|
||||
// 安全兜底:若没有 pending 数据,通知 caller 重新触发
|
||||
res?.error("NO_PENDING", "no pending install info", null)
|
||||
}
|
||||
} else {
|
||||
// 用户仍未授权
|
||||
pendingApkPath = null
|
||||
pendingResult?.error("NEED_INSTALL_PERMISSION", "user did not allow install unknown apps", null)
|
||||
pendingResult = null
|
||||
}
|
||||
} else {
|
||||
// API < 26:尝试直接安装一次作为尝试(某些 ROM 无法精准判断)
|
||||
val path = pendingApkPath
|
||||
val res = pendingResult
|
||||
pendingApkPath = null
|
||||
pendingResult = null
|
||||
if (path != null && res != null) {
|
||||
installApkInternal(path, res)
|
||||
} else {
|
||||
res?.error("NO_PENDING", "no pending install info", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.qysz.qgxgf
|
||||
package com.company.myapp2
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
|
|
@ -1,581 +0,0 @@
|
|||
{
|
||||
"extValues": {},
|
||||
"success": true,
|
||||
"errCode": null,
|
||||
"errMessage": null,
|
||||
"exception": null,
|
||||
"traceId": "18491030037470208",
|
||||
"data": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387265",
|
||||
"menuName": "首页",
|
||||
"menuUrl": "/dashboard",
|
||||
"parentId": "0",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard",
|
||||
"menuType": 1,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 0,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387268",
|
||||
"menuName": "单位管理",
|
||||
"menuUrl": "/dashboard/Unit/Management",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Unit-Management",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 3,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387286",
|
||||
"menuName": "就职单位",
|
||||
"menuUrl": "/dashboard/Unit/Management/Employment/Unit",
|
||||
"parentId": "2030925300149387268",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Unit-Management-Employment-Unit",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 21,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387287",
|
||||
"menuName": "服务单位管理",
|
||||
"menuUrl": "/dashboard/Unit/Management/Managee/Service/Unit/Management",
|
||||
"parentId": "2030925300149387268",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Unit-Management-Managee-Service-Unit-Management",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 22,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387269",
|
||||
"menuName": "通知公告",
|
||||
"menuUrl": "/dashboard/Notice/Announcement",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Notice-Announcement",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 4,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387270",
|
||||
"menuName": "口门门禁",
|
||||
"menuUrl": "/dashboard/Gate/Access/Control",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Gate-Access-Control",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 5,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387288",
|
||||
"menuName": "进港口门申请",
|
||||
"menuUrl": "/dashboard/Gate/Access/Control/Port/Gate/Entry/Application",
|
||||
"parentId": "2030925300149387270",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Gate-Access-Control-Port-Gate-Entry-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 23,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387292",
|
||||
"menuName": "人员申请",
|
||||
"menuUrl": "/dashboard/Port/Gate/Entry/Application/Personnel/Application",
|
||||
"parentId": "2030925300149387288",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Port-Gate-Entry-Application-Personnel-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 27,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387293",
|
||||
"menuName": "车辆申请",
|
||||
"menuUrl": "/dashboard/Port/Gate/Entry/Application/Vehicle/Application",
|
||||
"parentId": "2030925300149387288",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Port-Gate-Entry-Application-Vehicle-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 28,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387289",
|
||||
"menuName": "进港口门申请记录",
|
||||
"menuUrl": "/dashboard/Gate/Access/Crdtrol/Port/Gate/Entry/Record",
|
||||
"parentId": "2030925300149387270",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Gate-Access-Crdtrol-Port-Gate-Entry-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 24,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387294",
|
||||
"menuName": "人员申请记录",
|
||||
"menuUrl": "/dashboard/Port/Gate/Entry/Record/Record/Personnel/Application/Record",
|
||||
"parentId": "2030925300149387289",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Port-Gate-Entry-Record-Record-Personnel-Application-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 29,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387295",
|
||||
"menuName": "车辆申请记录",
|
||||
"menuUrl": "/dashboard/Port/Gate/Entry/Record/Record/Vehicle/Application/Record",
|
||||
"parentId": "2030925300149387289",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Port-Gate-Entry-Record-Record-Vehicle-Application-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 30,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387290",
|
||||
"menuName": "封闭区域口门申请",
|
||||
"menuUrl": "/dashboard/Area/Access/Cionsedrol/Closed/Area/Gate/Application",
|
||||
"parentId": "2030925300149387270",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Area-Access-Cionsedrol-Closed-Area-Gate-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 25,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387296",
|
||||
"menuName": "人员申请",
|
||||
"menuUrl": "/dashboard/Closed/Area/Gate/Application/Personnel/Application",
|
||||
"parentId": "2030925300149387290",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Closed-Area-Gate-Application-Personnel-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 31,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387297",
|
||||
"menuName": "车辆申请",
|
||||
"menuUrl": "/dashboard/Closed/Area/Gate/Application/Vehicle/Application",
|
||||
"parentId": "2030925300149387290",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Closed-Area-Gate-Application-Vehicle-Application",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 32,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387291",
|
||||
"menuName": "封闭区域口门申请记录",
|
||||
"menuUrl": "/dashboard/Area/Access/Crdsedrol/Closed/Area/Gate/Record",
|
||||
"parentId": "2030925300149387270",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Area-Access-Crdsedrol-Closed-Area-Gate-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 26,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387298",
|
||||
"menuName": "人员申请记录",
|
||||
"menuUrl": "/dashboard/Closed/Area/Gate/Record/Record/Personnel/Application/Record",
|
||||
"parentId": "2030925300149387291",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Closed-Area-Gate-Record-Record-Personnel-Application-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 33,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387299",
|
||||
"menuName": "车辆申请记录",
|
||||
"menuUrl": "/dashboard/Closed/Area/Gate/Record/Record/Vehicle/Application/Record",
|
||||
"parentId": "2030925300149387291",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Closed-Area-Gate-Record-Record-Vehicle-Application-Record",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 34,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387271",
|
||||
"menuName": "现场监管",
|
||||
"menuUrl": "/dashboard/Site/Supervision",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Site-Supervision",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 6,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387272",
|
||||
"menuName": "危险作业",
|
||||
"menuUrl": "/dashboard/Hazardous/Work",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Hazardous-Work",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 7,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387273",
|
||||
"menuName": "隐患治理",
|
||||
"menuUrl": "/dashboard/Hazard/Management",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Hazard-Management",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 8,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030935458707537921",
|
||||
"menuName": "入港培训",
|
||||
"menuUrl": "/dashboard/Study/Training",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-Study-Training",
|
||||
"menuType": 1,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 11,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387300",
|
||||
"menuName": "首页扫码",
|
||||
"menuUrl": "/dashboard/scan",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-scan",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 35,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387301",
|
||||
"menuName": "首页滚动通知",
|
||||
"menuUrl": "/dashboard/roll-notice",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-roll-notice",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 36,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387302",
|
||||
"menuName": "首页待办梳理",
|
||||
"menuUrl": "/dashboard/todo-sort",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-todo-sort",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 37,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387303",
|
||||
"menuName": "首页待办事项",
|
||||
"menuUrl": "/dashboard/todo-list",
|
||||
"parentId": "2030925300149387265",
|
||||
"parentIds": null,
|
||||
"menuPerms": "dashboard-todo-list",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 38,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387266",
|
||||
"menuName": "通知",
|
||||
"menuUrl": "/notice",
|
||||
"parentId": "0",
|
||||
"parentIds": null,
|
||||
"menuPerms": "notice",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 1,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387274",
|
||||
"menuName": "公告通知",
|
||||
"menuUrl": "/notice/Announcement/Notice",
|
||||
"parentId": "2030925300149387266",
|
||||
"parentIds": null,
|
||||
"menuPerms": "notice-Announcement-Notice",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 9,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387267",
|
||||
"menuName": "我的",
|
||||
"menuUrl": "/my-center",
|
||||
"parentId": "0",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 2,
|
||||
"showFlag": 1,
|
||||
"children": [
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387275",
|
||||
"menuName": "我的信息",
|
||||
"menuUrl": "/my-center/My/Information",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-My-Information",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 10,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387276",
|
||||
"menuName": "扫码入职",
|
||||
"menuUrl": "/my-center/Scan/Code/Onboarding",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Scan-Code-Onboarding",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 11,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387277",
|
||||
"menuName": "人脸认证",
|
||||
"menuUrl": "/my-center/Face/Authentication",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Face-Authentication",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 12,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387278",
|
||||
"menuName": "证书信息",
|
||||
"menuUrl": "/my-center/Certificate/Information",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Certificate-Information",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 13,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387279",
|
||||
"menuName": "问题反馈",
|
||||
"menuUrl": "/my-center/Feedback",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Feedback",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 14,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387280",
|
||||
"menuName": "版本更新",
|
||||
"menuUrl": "/my-center/Version/Update",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Version-Update",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 15,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387281",
|
||||
"menuName": "关于我们",
|
||||
"menuUrl": "/my-center/About/Us",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-About-Us",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 16,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387282",
|
||||
"menuName": "切换账号",
|
||||
"menuUrl": "/my-center/Switch/Account",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Switch-Account",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 17,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387283",
|
||||
"menuName": "修改密码",
|
||||
"menuUrl": "/my-center/Change/Password",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Change-Password",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 18,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387284",
|
||||
"menuName": "用户注销",
|
||||
"menuUrl": "/my-center/User/Logout",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-User-Logout",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 19,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"extValues": {},
|
||||
"id": "2030925300149387285",
|
||||
"menuName": "退出登录",
|
||||
"menuUrl": "/my-center/Logout",
|
||||
"parentId": "2030925300149387267",
|
||||
"parentIds": null,
|
||||
"menuPerms": "my-center-Logout",
|
||||
"menuType": 2,
|
||||
"menuAttribution": "QINGANG_RELATED_PARTIES",
|
||||
"sort": 20,
|
||||
"showFlag": 1,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"notEmpty": true,
|
||||
"empty": false
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ PODS:
|
|||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- photo_manager (3.9.0):
|
||||
- photo_manager (3.8.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SDWebImage (5.21.1):
|
||||
|
|
@ -216,7 +216,7 @@ SPEC CHECKSUMS:
|
|||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
pdfx: 77f4dddc48361fbb01486fa2bdee4532cbb97ef3
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
photo_manager: 343d78032bf7ebe944d2ab9702204dc2eda07338
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
|
|
|
|||
|
|
@ -498,17 +498,17 @@
|
|||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港相关方";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-dev";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
@ -525,7 +525,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.company.myapp2.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
|
|
@ -544,7 +544,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.company.myapp2.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
|
|
@ -561,7 +561,7 @@
|
|||
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.company.myapp2.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
|
|
@ -694,17 +694,17 @@
|
|||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港相关方";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-dev";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -720,24 +720,24 @@
|
|||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Qinhuangdao Zhuoyun Technology Co., Ltd (8AKCJ9LW7D)";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 62;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港相关方";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-des";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>秦港相关方</string>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>秦港相关方</string>
|
||||
<string>秦港双控</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
|
@ -28,6 +28,8 @@
|
|||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
<string>需要NFC权限来读取和写入标签</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<?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/>
|
||||
<dict>
|
||||
<key>com.apple.developer.nfc.readersession.formats</key>
|
||||
<array>
|
||||
<string>TAG</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -2,107 +2,61 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class RouteModel {
|
||||
// 映射到后端新字段
|
||||
final String id;
|
||||
final String menuName; // 原来的 title
|
||||
final String menuUrl; // 原来的 path
|
||||
final String parentId;
|
||||
final String parentIds;
|
||||
final String menuPerms;
|
||||
final int menuType; // 1/2 ...
|
||||
final String menuAttribution;
|
||||
final int sort;
|
||||
final int showFlag; // 1 可见,0 隐藏
|
||||
final Map<String, dynamic> extValues;
|
||||
|
||||
final String target;
|
||||
final List<RouteModel> children;
|
||||
final bool hasMenu;
|
||||
final String parentId;
|
||||
final String routeId;
|
||||
final String component;
|
||||
final String path;
|
||||
final String title;
|
||||
final String parentIds;
|
||||
final String meta;
|
||||
final String routeOrder;
|
||||
|
||||
RouteModel({
|
||||
required this.id,
|
||||
required this.menuName,
|
||||
required this.menuUrl,
|
||||
required this.parentId,
|
||||
required this.parentIds,
|
||||
required this.menuPerms,
|
||||
required this.menuType,
|
||||
required this.menuAttribution,
|
||||
required this.sort,
|
||||
required this.showFlag,
|
||||
required this.extValues,
|
||||
required this.target,
|
||||
required this.children,
|
||||
required this.hasMenu,
|
||||
required this.parentId,
|
||||
required this.routeId,
|
||||
required this.component,
|
||||
required this.title,
|
||||
required this.path,
|
||||
required this.parentIds,
|
||||
required this.meta,
|
||||
required this.routeOrder,
|
||||
});
|
||||
|
||||
factory RouteModel.fromJson(Map<String, dynamic> json) {
|
||||
// 安全解析工具
|
||||
String _s(dynamic v) => v == null ? '' : v.toString();
|
||||
int _i(dynamic v) {
|
||||
if (v == null) return 0;
|
||||
if (v is int) return v;
|
||||
return int.tryParse(v.toString()) ?? 0;
|
||||
}
|
||||
|
||||
final rawChildren = json['children'];
|
||||
final children = <RouteModel>[];
|
||||
if (rawChildren is List) {
|
||||
for (final c in rawChildren) {
|
||||
if (c is Map<String, dynamic>) {
|
||||
children.add(RouteModel.fromJson(c));
|
||||
} else if (c is Map) {
|
||||
children.add(RouteModel.fromJson(Map<String, dynamic>.from(c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extValues 兼容
|
||||
Map<String, dynamic> ext = {};
|
||||
if (json['extValues'] is Map) {
|
||||
ext = Map<String, dynamic>.from(json['extValues']);
|
||||
}
|
||||
|
||||
return RouteModel(
|
||||
id: _s(json['id']),
|
||||
menuName: _s(json['menuName']),
|
||||
menuUrl: _s(json['menuUrl']),
|
||||
parentId: _s(json['parentId']),
|
||||
parentIds: _s(json['parentIds']),
|
||||
menuPerms: _s(json['menuPerms']),
|
||||
menuType: _i(json['menuType']),
|
||||
menuAttribution: _s(json['menuAttribution']),
|
||||
sort: _i(json['sort']),
|
||||
showFlag: _i(json['showFlag']),
|
||||
extValues: ext,
|
||||
children: children,
|
||||
target: json['target'] ?? '',
|
||||
children: (json['children'] as List<dynamic>? ?? [])
|
||||
.map((child) => RouteModel.fromJson(child))
|
||||
.toList(),
|
||||
hasMenu: json['hasMenu'] ?? false,
|
||||
parentId: json['parent_ID'] ?? '',
|
||||
routeId: json['route_ID'] ?? '',
|
||||
component: json['component'] ?? '',
|
||||
parentIds: json['parent_IDS'] ?? '',
|
||||
meta: json['meta'] ?? '',
|
||||
path: json['path'] ?? '',
|
||||
title: json['path'] ?? '',
|
||||
routeOrder: json['route_ORDER'] ?? '0',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'menuName': menuName,
|
||||
'menuUrl': menuUrl,
|
||||
'parentId': parentId,
|
||||
'parentIds': parentIds,
|
||||
'menuPerms': menuPerms,
|
||||
'menuType': menuType,
|
||||
'menuAttribution': menuAttribution,
|
||||
'sort': sort,
|
||||
'showFlag': showFlag,
|
||||
'extValues': extValues,
|
||||
'children': children.map((c) => c.toJson()).toList(),
|
||||
};
|
||||
// // 解析meta字段获取title
|
||||
// String get title {
|
||||
// if (meta.isEmpty) return '';
|
||||
// try {
|
||||
// final metaMap = jsonDecode(meta) as Map<String, dynamic>;
|
||||
// return metaMap['title'] ?? '';
|
||||
// } catch (e) {
|
||||
// return '';
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 是否可见(供显示逻辑判断)
|
||||
bool get visible => showFlag == 1;
|
||||
|
||||
/// 是否是菜单项(接口好像用 menuType 表示层级/类型)
|
||||
bool get isMenu => menuType == 2;
|
||||
|
||||
/// 叶子节点判定(无子节点)
|
||||
// 判断是否是叶子节点(没有子节点的路由)
|
||||
bool get isLeaf => children.isEmpty;
|
||||
|
||||
/// 页面标题:优先 menuName,如果 extValues 中含 title 则优先使用
|
||||
String get title {
|
||||
if (menuName.isNotEmpty) return menuName;
|
||||
if (extValues.containsKey('title')) return extValues['title']?.toString() ?? '';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +1,34 @@
|
|||
// route_service.dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:qhd_prevention/common/route_model.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
|
||||
class RouteService extends ChangeNotifier {
|
||||
/// 路由管理
|
||||
class RouteService {
|
||||
static final RouteService _instance = RouteService._internal();
|
||||
factory RouteService() => _instance;
|
||||
RouteService._internal();
|
||||
|
||||
// 存储顶级菜单(直接从接口解析的数组)
|
||||
// 存储所有路由配置
|
||||
List<RouteModel> _allRoutes = [];
|
||||
|
||||
/// 对外暴露全部顶级 routes(如果需要遍历所有顶级项)
|
||||
List<RouteModel> get allRoutes => _allRoutes;
|
||||
// 获取主Tab路由(第一级children)
|
||||
List<RouteModel> get mainTabs => _allRoutes.isNotEmpty
|
||||
? _allRoutes.first.children
|
||||
: [];
|
||||
|
||||
/// 初始化路由配置(允许传 null)
|
||||
void initializeRoutes(List<dynamic>? routeList) {
|
||||
_allRoutes = [];
|
||||
if (routeList == null) return;
|
||||
for (final item in routeList) {
|
||||
try {
|
||||
if (item is Map<String, dynamic>) {
|
||||
_allRoutes.add(RouteModel.fromJson(item));
|
||||
} else if (item is Map) {
|
||||
_allRoutes.add(RouteModel.fromJson(Map<String, dynamic>.from(item)));
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('RouteService: parse route item failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 对顶级和子节点进行排序(如果有 sort 字段)
|
||||
try {
|
||||
_allRoutes.sort((a, b) => a.sort.compareTo(b.sort));
|
||||
for (final r in _allRoutes) {
|
||||
_sortRecursive(r);
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
notifyListeners();
|
||||
// 初始化路由配置
|
||||
void initializeRoutes(List<dynamic> routeList) {
|
||||
_allRoutes = routeList.map((route) => RouteModel.fromJson(route)).toList();
|
||||
}
|
||||
|
||||
void _sortRecursive(RouteModel node) {
|
||||
try {
|
||||
node.children.sort((a, b) => a.sort.compareTo(b.sort));
|
||||
for (final c in node.children) {
|
||||
_sortRecursive(c);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/// 返回所有顶级(parentId == '0' 或 parentId 为空)的菜单作为主Tab(不在这里筛 visible)
|
||||
List<RouteModel> get mainTabs {
|
||||
final tabs = _allRoutes.where((m) {
|
||||
final isTop = m.parentId == '0' || m.parentId.isEmpty;
|
||||
return isTop && m.visible; // 只取可见的顶级项
|
||||
}).toList();
|
||||
try {
|
||||
tabs.sort((a, b) => a.sort.compareTo(b.sort));
|
||||
} catch (_) {}
|
||||
return tabs;
|
||||
}
|
||||
|
||||
// 遍历查找(按 menuUrl)
|
||||
// 根据路径查找路由
|
||||
RouteModel? findRouteByPath(String path) {
|
||||
if (path.isEmpty) return null;
|
||||
final needle = path.trim();
|
||||
for (final route in _allRoutes) {
|
||||
final found = _findRouteRecursive(route, needle);
|
||||
final found = _findRouteRecursive(route, path);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
RouteModel? _findRouteRecursive(RouteModel route, String path) {
|
||||
// 如果当前节点不可见,则按照你的要求:不再查找其子级(直接返回 null)
|
||||
if (!route.visible) return null;
|
||||
|
||||
final routeUrl = route.menuUrl.trim();
|
||||
if (routeUrl == path) return route;
|
||||
|
||||
if (route.path == path) return route;
|
||||
for (final child in route.children) {
|
||||
final found = _findRouteRecursive(child, path);
|
||||
if (found != null) return found;
|
||||
|
|
@ -89,178 +36,23 @@ class RouteService extends ChangeNotifier {
|
|||
return null;
|
||||
}
|
||||
|
||||
// 获取某个Tab下的所有可显示路由(visible == true,且收集叶子节点)
|
||||
// 获取某个Tab下的所有可显示的路由(hasMenu为true的叶子节点)
|
||||
List<RouteModel> getRoutesForTab(RouteModel tab) {
|
||||
final routes = <RouteModel>[];
|
||||
_collectVisibleLeafRoutes(tab, routes);
|
||||
_collectLeafRoutes(tab, routes);
|
||||
return routes;
|
||||
}
|
||||
|
||||
/// 关键修改:如果当前节点不可见,则不再递归其 children(按你的要求)
|
||||
void _collectVisibleLeafRoutes(RouteModel route, List<RouteModel> collector) {
|
||||
if (!route.visible) return; // 如果父节点不可见,跳过整个子树
|
||||
if (route.isLeaf) {
|
||||
void _collectLeafRoutes(RouteModel route, List<RouteModel> collector) {
|
||||
if (route.hasMenu) {
|
||||
collector.add(route);
|
||||
return;
|
||||
}
|
||||
for (final child in route.children) {
|
||||
_collectVisibleLeafRoutes(child, collector);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------- 权限检查相关 ---------------------
|
||||
|
||||
/// 判断整个路由树(所有顶级及其子孙)是否存在 menuPerms == perm 且可见的节点
|
||||
/// 如果父节点不可见,会跳过该父及其子树(按你的要求)
|
||||
bool hasPerm(String perm) {
|
||||
if (perm.isEmpty) return false;
|
||||
final needle = perm.trim();
|
||||
bool found = false;
|
||||
|
||||
void visit(RouteModel m) {
|
||||
if (found) return;
|
||||
// 若父节点不可见,跳过(不再遍历子节点)
|
||||
if (!m.visible) return;
|
||||
|
||||
final mp = (m.menuPerms ?? '').trim();
|
||||
if (mp.isNotEmpty && mp == needle) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
for (final c in m.children) {
|
||||
visit(c);
|
||||
if (found) return;
|
||||
}
|
||||
}
|
||||
|
||||
for (final top in _allRoutes) {
|
||||
visit(top);
|
||||
if (found) break;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool hasAnyPerms(List<String> perms) {
|
||||
for (final p in perms) {
|
||||
if (hasPerm(p)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<String, bool> permsMap(List<String> perms) {
|
||||
final Map<String, bool> map = {};
|
||||
for (final p in perms) {
|
||||
map[p] = hasPerm(p);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// 尝试按 menuPerms 找到第一个匹配的 RouteModel(若需要路由信息)
|
||||
/// 如果某个父节点不可见,则不会进入其子树
|
||||
RouteModel? findRouteByPerm(String perm) {
|
||||
if (perm.isEmpty) return null;
|
||||
final needle = perm.trim();
|
||||
RouteModel? result;
|
||||
|
||||
void visit(RouteModel m) {
|
||||
// printLongString(json.encode(m.toJson()));
|
||||
if (result != null) return;
|
||||
if (!m.visible) return; // 父不可见,跳过
|
||||
final mp = (m.menuPerms ?? '').trim();
|
||||
if (mp.isNotEmpty && mp == needle) {
|
||||
result = m;
|
||||
return;
|
||||
}
|
||||
for (final c in m.children) {
|
||||
visit(c);
|
||||
if (result != null) return;
|
||||
}
|
||||
}
|
||||
|
||||
for (final top in _allRoutes) {
|
||||
visit(top);
|
||||
if (result != null) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 可返回所有收集到的 menuPerms(仅包含可见节点及其可见子节点)
|
||||
List<String> collectAllPerms() {
|
||||
final List<String> perms = [];
|
||||
void visit(RouteModel m) {
|
||||
if (!m.visible) return; // 父不可见则跳过子树
|
||||
final mp = (m.menuPerms ?? '').trim();
|
||||
if (mp.isNotEmpty) perms.add(mp);
|
||||
for (final c in m.children) visit(c);
|
||||
}
|
||||
|
||||
for (final top in _allRoutes) visit(top);
|
||||
return perms;
|
||||
}
|
||||
/// 严格查找某个子树(仅包含可见节点及其可见子节点)
|
||||
static Future<String> getMenuPath(parentPerm,targetPerm) async {
|
||||
try {
|
||||
final routeService = RouteService();
|
||||
// 如果子节点为'',那么查父节点children中第一个
|
||||
if (targetPerm.isEmpty) {
|
||||
final route = routeService.findRouteByPerm(parentPerm);
|
||||
if (route != null) {
|
||||
// 优先在该节点的子孙中找第一个可见且有 menuUrl 的节点
|
||||
final childUrl = findFirstVisibleChildUrl(route);
|
||||
if (childUrl.isNotEmpty) return childUrl;
|
||||
return '';
|
||||
|
||||
if (!route.isLeaf) {
|
||||
for (final child in route.children) {
|
||||
_collectLeafRoutes(child, collector);
|
||||
}
|
||||
}
|
||||
//branchCompany-plan-execute-inspection-records
|
||||
RouteModel? parent = routeService.findRouteByPerm(parentPerm);
|
||||
if (parent != null) {
|
||||
// 在 parent 子树中严格查找 targetPerm
|
||||
final RouteModel? foundInParent = _findRouteInSubtreeByPerm(parent, targetPerm);
|
||||
if (foundInParent != null && foundInParent.menuUrl.trim().isNotEmpty) {
|
||||
return foundInParent.menuUrl.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到 -> 返回空字符串(调用方需做好空串处理)
|
||||
return '';
|
||||
} catch (e, st) {
|
||||
debugPrint('_getMenuPath error: $e\n$st');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
/// 在给定节点的子树中(含自身)查找 menuPerm 完全匹配的节点(只返回可见节点)
|
||||
static RouteModel? _findRouteInSubtreeByPerm(RouteModel node, String perm) {
|
||||
if (node.menuPerms.trim() == perm && node.visible) return node;
|
||||
for (final c in node.children) {
|
||||
final res = _findRouteInSubtreeByPerm(c, perm);
|
||||
if (res != null) return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 在整个路由列表中查找 menuPerm 完全匹配的节点(只返回可见节点)
|
||||
RouteModel? _findRouteInAllByPerm(List<RouteModel> roots, String perm) {
|
||||
for (final r in roots) {
|
||||
final res = _findRouteInSubtreeByPerm(r, perm);
|
||||
if (res != null) return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 递归在 node 的子孙中按顺序查找第一个 visible 且有 menuUrl 的节点
|
||||
/// 如果没找到返回空字符串
|
||||
static String findFirstVisibleChildUrl(RouteModel node) {
|
||||
final children = node.children;
|
||||
if (children == null || children.isEmpty) return '';
|
||||
|
||||
for (final c in children) {
|
||||
// 若该子节点可见并有 menuUrl,直接返回
|
||||
if ((c.showFlag == 1) && (c.menuUrl ?? '').isNotEmpty) {
|
||||
return c.menuUrl;
|
||||
}
|
||||
// return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,9 +104,6 @@ class MediaPickerRow extends StatefulWidget {
|
|||
/// 新增:网格列数(默认 4),可在需要单列/自适应宽度时指定 1
|
||||
final int crossAxisCount;
|
||||
|
||||
/// 可选:1 只有拍照 2 只有相册 3 都有
|
||||
final int selectPictureType;
|
||||
|
||||
const MediaPickerRow({
|
||||
Key? key,
|
||||
this.maxCount = 4,
|
||||
|
|
@ -121,7 +118,6 @@ class MediaPickerRow extends StatefulWidget {
|
|||
this.isCamera = false,
|
||||
this.followInitialUpdates = false, // 默认 false
|
||||
this.crossAxisCount = 4, // 默认 4 列
|
||||
this.selectPictureType = 3,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -267,26 +263,24 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
builder: (_) => SafeArea(
|
||||
child: Wrap(
|
||||
children: [
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==1)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam),
|
||||
title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_pickCamera();
|
||||
},
|
||||
),
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==2)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_pickGallery();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam),
|
||||
title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_pickCamera();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
_pickGallery();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: const Icon(Icons.close),
|
||||
|
|
@ -650,8 +644,6 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
final bool inlineSingle;
|
||||
/// 可选:当 inlineSingle 为 true 时,可以定制缩略图宽度(px)
|
||||
final double inlineImageWidth;
|
||||
/// 可选:1 只有拍照 2 只有相册 3 都有
|
||||
final int selectPictureType;
|
||||
|
||||
const RepairedPhotoSection({
|
||||
Key? key,
|
||||
|
|
@ -676,7 +668,6 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
this.sectionKey = kAcceptVideoSectionKey,
|
||||
this.inlineSingle = false,
|
||||
this.inlineImageWidth = 88.0,
|
||||
this.selectPictureType = 3,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -811,75 +802,73 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
builder: (ctx) => SafeArea(
|
||||
child: Wrap(
|
||||
children: [
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==1)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam),
|
||||
title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'),
|
||||
onTap: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
// 触发 MediaPickerRow 中的 camera 逻辑:我们在这里简单复用 ImagePicker
|
||||
final picker = ImagePicker();
|
||||
try {
|
||||
if (widget.mediaType == MediaType.image) {
|
||||
final x = await picker.pickImage(source: ImageSource.camera);
|
||||
if (x != null) {
|
||||
setState(() {
|
||||
_mediaPaths = [x.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(x.path);
|
||||
}
|
||||
} else {
|
||||
final x = await picker.pickVideo(source: ImageSource.camera);
|
||||
if (x != null) {
|
||||
// 若需要转码、压缩请复用 VideoCompress
|
||||
setState(() {
|
||||
_mediaPaths = [x.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(x.path);
|
||||
}
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam),
|
||||
title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'),
|
||||
onTap: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
// 触发 MediaPickerRow 中的 camera 逻辑:我们在这里简单复用 ImagePicker
|
||||
final picker = ImagePicker();
|
||||
try {
|
||||
if (widget.mediaType == MediaType.image) {
|
||||
final x = await picker.pickImage(source: ImageSource.camera);
|
||||
if (x != null) {
|
||||
setState(() {
|
||||
_mediaPaths = [x.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(x.path);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('camera pick error: $e');
|
||||
ToastUtil.showNormal(context, '拍摄失败');
|
||||
}
|
||||
},
|
||||
),
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==2)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'),
|
||||
onTap: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
// 这里直接调用 AssetPicker(与 MediaPickerRow 的行为保持一致)
|
||||
try {
|
||||
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
|
||||
context,
|
||||
pickerConfig: AssetPickerConfig(
|
||||
requestType: widget.mediaType == MediaType.image ? RequestType.image : RequestType.video,
|
||||
maxAssets: 1,
|
||||
gridCount: 4,
|
||||
),
|
||||
);
|
||||
if (assets != null && assets.isNotEmpty) {
|
||||
final file = await assets.first.file;
|
||||
if (file != null) {
|
||||
setState(() {
|
||||
_mediaPaths = [file.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(file.path);
|
||||
}
|
||||
} else {
|
||||
final x = await picker.pickVideo(source: ImageSource.camera);
|
||||
if (x != null) {
|
||||
// 若需要转码、压缩请复用 VideoCompress
|
||||
setState(() {
|
||||
_mediaPaths = [x.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(x.path);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('pick asset error: $e');
|
||||
ToastUtil.showNormal(context, '选择图片失败');
|
||||
}
|
||||
},
|
||||
),
|
||||
} catch (e) {
|
||||
debugPrint('camera pick error: $e');
|
||||
ToastUtil.showNormal(context, '拍摄失败');
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'),
|
||||
onTap: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
// 这里直接调用 AssetPicker(与 MediaPickerRow 的行为保持一致)
|
||||
try {
|
||||
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
|
||||
context,
|
||||
pickerConfig: AssetPickerConfig(
|
||||
requestType: widget.mediaType == MediaType.image ? RequestType.image : RequestType.video,
|
||||
maxAssets: 1,
|
||||
gridCount: 4,
|
||||
),
|
||||
);
|
||||
if (assets != null && assets.isNotEmpty) {
|
||||
final file = await assets.first.file;
|
||||
if (file != null) {
|
||||
setState(() {
|
||||
_mediaPaths = [file.path];
|
||||
});
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(file.path);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('pick asset error: $e');
|
||||
ToastUtil.showNormal(context, '选择图片失败');
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: const Icon(Icons.close),
|
||||
|
|
@ -935,8 +924,7 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding),
|
||||
child: ListItemFactory.createRowSpaceBetweenItem(
|
||||
leftText: widget.title,
|
||||
// rightText: widget.isShowNum ? '${_mediaPaths.length}/${widget.maxCount}' : '',
|
||||
rightText: widget.isShowNum ? '${_getCurrentCount()}/${widget.maxCount}' : '',
|
||||
rightText: widget.isShowNum ? '${_mediaPaths.length}/${widget.maxCount}' : '',
|
||||
isRequired: widget.isRequired,
|
||||
),
|
||||
),
|
||||
|
|
@ -949,7 +937,6 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
initialMediaPaths: _mediaPaths,
|
||||
onMediaRemovedForIndex: widget.onMediaRemovedForIndex,
|
||||
isCamera: widget.isCamera,
|
||||
selectPictureType:widget.selectPictureType,
|
||||
onChanged: (files) {
|
||||
final newPaths = files.map((f) => f.path).toList();
|
||||
setState(() {
|
||||
|
|
@ -1001,17 +988,5 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
int _getCurrentCount() {
|
||||
// 如果 followInitialUpdates 为 true,使用内部状态
|
||||
// 如果为 false,优先使用外部数据,因为外部数据才是真实数据源
|
||||
if (widget.followInitialUpdates) {
|
||||
return _mediaPaths.length;
|
||||
} else {
|
||||
return widget.initialMediaPaths?.length ?? _mediaPaths.length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ApiService {
|
|||
isProduct
|
||||
? "https://jpfz.qhdsafety.com/gbsFileTest/"
|
||||
: "http://192.168.20.240:9787/mnt/"; //内网图片地址
|
||||
// static final String baseImgPath = "https://skqhdg.porthebei.com:9004/file/uploadFiles2/";
|
||||
// static final String baseImgPath = "https://skqhdg.porthebei.com:9004/file/";
|
||||
|
||||
|
||||
static const publicKey =
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:qhd_prevention/http/ApiService.dart';
|
||||
import 'package:qhd_prevention/http/HttpManager.dart';
|
||||
import 'package:qhd_prevention/services/SessionService.dart';
|
||||
|
||||
class AppMenuApi {
|
||||
static Future<Map<String, dynamic>> getAppMenu() async {
|
||||
return HttpManager().request(
|
||||
ApiService.basePath,
|
||||
'/appmenu/appMenu/appListTree',
|
||||
method: Method.get,
|
||||
data: {
|
||||
'menuAttribution': 'QINGANG_RELATED_PARTIES',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ class CertificateApi {
|
|||
static Future<Map<String, dynamic>> getCertificateList(Map data) {
|
||||
return HttpManager().request(
|
||||
ApiService.basePath + '/certificate',
|
||||
'/userCertificate/listPage',
|
||||
'/userCertificate/list',
|
||||
method: Method.post,
|
||||
data: {...data},
|
||||
);
|
||||
|
|
@ -221,3 +221,17 @@ class CertificateApi {
|
|||
}
|
||||
|
||||
}
|
||||
// 待办事项
|
||||
class TodoApi {
|
||||
static Future<Map<String, dynamic>> getTodoList(Map data) {
|
||||
return HttpManager().request(
|
||||
ApiService.basePath + '/appmenu',
|
||||
'/todoList/list',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'eqFlag' : 1,
|
||||
...data
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class EduApi {
|
|||
static Future<Map<String, dynamic>> getSignInList(Map data) async {
|
||||
return HttpManager().request(
|
||||
'${ApiService.basePath}/edu',
|
||||
'/app/studentSign/listAllNoGroup',
|
||||
'/app/studentSign/listAll',
|
||||
method: Method.post,
|
||||
data: {
|
||||
...data,
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ Future<T?> showModalBottomSheetAfterUnfocus<T>({
|
|||
void main( ) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
StorageService.instance.init();
|
||||
/**
|
||||
// 1) 同意 SDK 隐私(百度 SDK 要求)
|
||||
BMFMapSDK.setAgreePrivacy(true);
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ void main( ) async {
|
|||
BMFMapSDK.setApiKeyAndCoordType('43G1sKuHV6oRTrdR9VTIGPF9soej7V5a', BMF_COORD_TYPE.BD09LL);
|
||||
await BMFAndroidVersion.initAndroidVersion(); // 可选,插件示例中有
|
||||
}
|
||||
*/
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import 'package:qhd_prevention/pages/mine/face_ecognition_page.dart';
|
|||
import 'package:qhd_prevention/pages/mine/mine_sign_page.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/services/SessionService.dart';
|
||||
import 'package:qhd_prevention/services/scan_service.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
|
||||
|
|
@ -147,7 +146,6 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
|||
await pushPage(SigninInformationListPage(info: item), context);
|
||||
},
|
||||
),
|
||||
if (item['examination'] != 0)
|
||||
CustomButton(
|
||||
text: '考试记录',
|
||||
height: 35,
|
||||
|
|
@ -227,11 +225,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
|||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
final item = list[index];
|
||||
if (item['state'] != 1) {
|
||||
return _buildListItem(item);
|
||||
}
|
||||
return SizedBox(height: 0);
|
||||
return _buildListItem(list[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -243,7 +237,68 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
|||
ToastUtil.showNormal(context, '未扫描到二维码');
|
||||
return;
|
||||
}
|
||||
ScanService.scan(context, result);
|
||||
|
||||
int type = result['type'] ?? 0;
|
||||
|
||||
final data = {
|
||||
...result,
|
||||
'phone': SessionService.instance.userData?.phone ?? '',
|
||||
'type': type,
|
||||
};
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
// 验证是否可以签到
|
||||
final response = await EduApi.checkSignIn(data);
|
||||
LoadingDialogHelper.hide();
|
||||
|
||||
|
||||
if (response['success']) {
|
||||
// 进行人脸识别
|
||||
final filePath = await pushPage(
|
||||
const FaceRecognitionPage(
|
||||
studentId: '',
|
||||
data: {},
|
||||
mode: FaceMode.study,
|
||||
),
|
||||
context,
|
||||
);
|
||||
|
||||
final faceData = response['data'];
|
||||
|
||||
if (filePath != null) {
|
||||
// 对比人脸
|
||||
try {
|
||||
LoadingDialogHelper.show();
|
||||
final response = await EduApi.compareFace({
|
||||
'type': data['type'],
|
||||
'studentId': faceData['studentId'],
|
||||
}, filePath);
|
||||
final faceResultData = response['data'];
|
||||
if (response['success']) {
|
||||
final signData = {
|
||||
'id': faceResultData['id'] ?? '',
|
||||
'studentId': faceResultData['studentId'] ?? '',
|
||||
'type': data['type']?? '',
|
||||
'classId': faceResultData['classId'] ?? '',
|
||||
'studentSignId':faceResultData['studentSignId']??''
|
||||
};
|
||||
_signUpload(signData, type);
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, response['errMessage'] ?? '验证失败');
|
||||
}
|
||||
} catch (e) {
|
||||
ToastUtil.showNormal(context,'验证失败');
|
||||
LoadingDialogHelper.hide();
|
||||
print(e);
|
||||
}
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, '签到失败');
|
||||
}
|
||||
} else {
|
||||
ToastUtil.showNormal(context, response['errMessage'] ?? '签到失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 上传签到签字
|
||||
|
|
|
|||
|
|
@ -242,9 +242,7 @@ class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
|
|||
if (res['success']) {
|
||||
final data = res['data'] as Map<String, dynamic>? ?? {};
|
||||
final score = data['examScore'] ?? 0;
|
||||
var passed = data['result'] == 1;
|
||||
// 剩余考试次数
|
||||
bool remain = (data['surplusExamNum'] ?? 0) <= 0;
|
||||
final passed = data['result'] == 1;
|
||||
|
||||
// 弹窗告诉用户结果:通过直接返回上一页;未通过给“继续考试 / 确定”两个按钮
|
||||
final result = await CustomAlertDialog.showConfirm(
|
||||
|
|
@ -253,7 +251,7 @@ class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
|
|||
content: passed
|
||||
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
|
||||
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
|
||||
cancelText: remain ? '' : '继续考试',
|
||||
cancelText: passed ? '' : '继续考试',
|
||||
confirmText: '确定',
|
||||
);
|
||||
|
||||
|
|
@ -267,7 +265,7 @@ class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
|
|||
_resetForRetry();
|
||||
} else {
|
||||
// 可选:处理失败情况并提示错误信息
|
||||
final msg = res['errMessage'] ?? '提交失败,请重试';
|
||||
final msg = res['message'] ?? '提交失败,请重试';
|
||||
ToastUtil.showError(context, msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,4 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/common/route_model.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
import 'package:qhd_prevention/customWidget/work_tab_icon_grid.dart';
|
||||
import 'package:qhd_prevention/http/ApiService.dart';
|
||||
import 'package:qhd_prevention/customWidget/IconBadgeButton.dart';
|
||||
|
|
@ -9,7 +6,6 @@ import 'package:qhd_prevention/pages/home/unit/unit_join_list_page.dart';
|
|||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
import 'package:qhd_prevention/common/route_aware_state.dart';
|
||||
import 'package:qhd_prevention/common/route_service.dart';
|
||||
|
||||
class UnitTabPage extends StatefulWidget {
|
||||
const UnitTabPage({super.key});
|
||||
|
|
@ -19,166 +15,47 @@ class UnitTabPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _UnitTabPageState extends RouteAwareState<UnitTabPage> {
|
||||
// 原始 master 定义(固定顺序,不随权限变动)
|
||||
final List<Map<String, dynamic>> _masterButtons = [
|
||||
late List<Map<String, dynamic>> buttonInfos = [
|
||||
{
|
||||
"icon": "assets/images/unit_ico1.png",
|
||||
"title": "服务单位管理",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
}
|
||||
,{
|
||||
"icon": "assets/images/unit_ico2.png",
|
||||
"title": "就职单位管理",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
// title -> 后端 menuPerm 映射(基于你提供的路由配置)
|
||||
final Map<String, String> _permMapping = {
|
||||
"服务单位管理": "dashboard-Unit-Management-Managee-Service-Unit-Management",
|
||||
"就职单位管理": "dashboard-Unit-Management-Employment-Unit",
|
||||
};
|
||||
|
||||
// 当前哪些按钮应显示(与 master 顺序对应)
|
||||
late List<bool> _visible;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 初始:全部显示,避免短暂白屏(路由加载后会更新)
|
||||
_visible = List<bool>.filled(_masterButtons.length, true);
|
||||
|
||||
// 监听路由变化,路由加载完成或变更时刷新可见性
|
||||
RouteService().addListener(_onRouteUpdated);
|
||||
|
||||
// 尝试一次应用路由(如果已经加载)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _updateVisibilityFromRoute());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
RouteService().removeListener(_onRouteUpdated);
|
||||
} catch (_) {}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onRouteUpdated() {
|
||||
// 当路由配置更新时重计算按钮可见性
|
||||
_updateVisibilityFromRoute();
|
||||
}
|
||||
|
||||
void _updateVisibilityFromRoute() {
|
||||
final routeService = RouteService();
|
||||
|
||||
// routesLoaded: 当 mainTabs 非空时认为路由加载完毕(开始严格按后端配置显示)
|
||||
final bool routesLoaded = routeService.mainTabs.isNotEmpty;
|
||||
|
||||
final List<bool> next = List<bool>.filled(_masterButtons.length, false);
|
||||
|
||||
for (int i = 0; i < _masterButtons.length; i++) {
|
||||
final title = _masterButtons[i]['title'] as String;
|
||||
final perm = _permMapping[title] ?? '';
|
||||
|
||||
if (!routesLoaded) {
|
||||
// 路由还没加载:保持默认显示(避免闪烁)
|
||||
next[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (perm.isEmpty) {
|
||||
// 没有映射:默认隐藏
|
||||
next[i] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 优先使用 RouteService.findRouteByPerm 获取路由节点(该方法会考虑节点 visible)
|
||||
final RouteModel? node = routeService.findRouteByPerm(perm);
|
||||
|
||||
if (node != null) {
|
||||
// 按你的要求:以 showFlag == 1 判断是否显示(兼容旧字段)
|
||||
next[i] = (node.showFlag == 1) || (node.visible);
|
||||
} else {
|
||||
// 如果没有找到完全匹配的 menuPerm,有可能后端使用不同层级或不同 perms 命名,
|
||||
// 这里做一次容错:在所有顶级下递归查找 menuPerm 相等项(不依赖 findRouteByPerm)。
|
||||
RouteModel? fallback;
|
||||
for (final top in routeService.allRoutes) {
|
||||
fallback = _findRouteRecursiveByPerm(top, perm);
|
||||
if (fallback != null) break;
|
||||
}
|
||||
next[i] = fallback != null ? ((fallback.showFlag == 1) || (fallback.visible)) : false;
|
||||
}
|
||||
}
|
||||
|
||||
// 只有在发生变化时才 setState
|
||||
if (!listEquals(next, _visible)) {
|
||||
setState(() {
|
||||
_visible = next;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 递归:在以 node 为根的子树中查找第一个 menuPerms 等于 targetPerm 的节点
|
||||
/// (不依赖 RouteService.findRouteByPerm,作为 fallback)
|
||||
RouteModel? _findRouteRecursiveByPerm(RouteModel node, String targetPerm) {
|
||||
if ((node.menuPerms ?? '') == targetPerm) return node;
|
||||
for (final c in node.children) {
|
||||
final res = _findRouteRecursiveByPerm(c, targetPerm);
|
||||
if (res != null) return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 当页面可见时拉取数据
|
||||
@override
|
||||
Future<void> onVisible() async {
|
||||
await _getData();
|
||||
_getData();
|
||||
}
|
||||
|
||||
Future<void> _getData() async {
|
||||
// TODO: 如果需要从后端拉取角标/统计,在这里实现并 setState 更新 _masterButtons[*]['unreadCount']
|
||||
// await Future.delayed(Duration(milliseconds: 100));
|
||||
|
||||
}
|
||||
|
||||
void _handleIconTap(int index) async {
|
||||
final title = _masterButtons[index]['title'] as String;
|
||||
switch (title) {
|
||||
case '服务单位管理':
|
||||
ToastUtil.showNormal(context, '您还没有参与项目');
|
||||
switch (index) {
|
||||
case 0:
|
||||
break;
|
||||
case '就职单位管理':
|
||||
case 1:
|
||||
pushPage(UnitJoinListPage(), context);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 点击后可以刷新数据
|
||||
await _getData();
|
||||
_getData();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double screenW = MediaQuery.of(context).size.width;
|
||||
final double bannerHeight = 618 / 1125 * screenW;
|
||||
|
||||
// 根据可见按钮数量决定 icon 区高度(每行最多 4 个)
|
||||
final visibleButtons = <Map<String, dynamic>>[];
|
||||
for (int i = 0; i < _masterButtons.length; i++) {
|
||||
if (i < _visible.length && _visible[i]) visibleButtons.add(_masterButtons[i]);
|
||||
}
|
||||
final int visibleCount = visibleButtons.length;
|
||||
final int perRow = 4;
|
||||
final int rows = visibleCount == 0 ? 0 : ((visibleCount + perRow - 1) ~/ perRow);
|
||||
|
||||
// 样式参数(如需微调)
|
||||
const double verticalPadding = 30.0;
|
||||
const double perRowHeight = 110.0; // 单行高度(图标 + 文本 + 内间距)
|
||||
const double rowSpacing = 20.0;
|
||||
final double iconSectionHeight = visibleCount == 0 ? 150.0 : (verticalPadding + rows * perRowHeight + (rows - 1) * rowSpacing);
|
||||
|
||||
const double iconOverlapBanner = 30.0;
|
||||
|
||||
double bannerHeight = 618/1125 * MediaQuery.of(context).size.width;
|
||||
const double iconSectionHeight = 150.0;
|
||||
const double iconOverlapBanner = 30.0; // 图标区覆盖 banner 的高度
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
child: Scaffold(
|
||||
|
|
@ -208,7 +85,7 @@ class _UnitTabPageState extends RouteAwareState<UnitTabPage> {
|
|||
right: 10,
|
||||
top: bannerHeight - iconOverlapBanner,
|
||||
height: iconSectionHeight,
|
||||
child: _buildIconSection(context, visibleButtons, rows),
|
||||
child: _buildIconSection(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -217,12 +94,13 @@ class _UnitTabPageState extends RouteAwareState<UnitTabPage> {
|
|||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Banner
|
||||
}
|
||||
// 构建顶部 Banner
|
||||
Widget _buildBannerSection(double height) {
|
||||
return Stack(
|
||||
children: [
|
||||
// 背景图片
|
||||
Image.asset(
|
||||
"assets/images/unit_banner.jpg",
|
||||
width: MediaQuery.of(context).size.width,
|
||||
|
|
@ -232,80 +110,54 @@ class _UnitTabPageState extends RouteAwareState<UnitTabPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 根据 visibleButtons 与行数构建 icon 区
|
||||
Widget _buildIconSection(BuildContext context, List<Map<String, dynamic>> visibleButtons, int rows) {
|
||||
if (visibleButtons.isEmpty) {
|
||||
if (RouteService().mainTabs.isNotEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2))],
|
||||
),
|
||||
child: const Center(child: Text('暂无权限访问的功能')),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2))],
|
||||
),
|
||||
child: const Center(child: SizedBox()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建每一行(每行最多 4 个)
|
||||
final List<Widget> rowsWidgets = [];
|
||||
final int perRow = 4;
|
||||
for (int r = 0; r < rows; r++) {
|
||||
final start = r * perRow;
|
||||
final end = (start + perRow) > visibleButtons.length ? visibleButtons.length : (start + perRow);
|
||||
final rowItems = visibleButtons.sublist(start, end);
|
||||
|
||||
rowsWidgets.add(
|
||||
Row(
|
||||
children: List.generate(perRow, (i) {
|
||||
final idx = start + i;
|
||||
if (idx < visibleButtons.length) {
|
||||
final btn = visibleButtons[idx];
|
||||
// 找到在 master 中的真实索引(用于 onTap)
|
||||
final masterIndex = _masterButtons.indexWhere((m) => m['title'] == btn['title']);
|
||||
return Expanded(
|
||||
child: Center(child: _buildIconButton(btn, masterIndex)),
|
||||
);
|
||||
} else {
|
||||
return const Expanded(child: SizedBox.shrink());
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (r != rows - 1) rowsWidgets.add(const SizedBox(height: 20));
|
||||
}
|
||||
|
||||
Widget _buildIconSection(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2))],
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
blurRadius: 6,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildIconRow(startIndex: 0),
|
||||
|
||||
],
|
||||
),
|
||||
child: Column(children: rowsWidgets),
|
||||
);
|
||||
}
|
||||
Widget _buildIconRow({required int startIndex}) {
|
||||
final List<Widget> cells = List.generate(4, (i) {
|
||||
final int idx = startIndex + i;
|
||||
if (idx < buttonInfos.length) {
|
||||
return Expanded(
|
||||
child: Center(child: _buildIconButton(buttonInfos[idx], idx, context)),
|
||||
);
|
||||
} else {
|
||||
return const Expanded(
|
||||
child: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Widget _buildIconButton(Map<String, dynamic> info, int masterIndex) {
|
||||
final unread = (info['unreadCount'] ?? 0) is int ? info['unreadCount'] as int : int.tryParse('${info['unreadCount']}') ?? 0;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: cells,
|
||||
);
|
||||
}
|
||||
Widget _buildIconButton(Map<String, dynamic> info, int index, BuildContext context) {
|
||||
return IconBadgeButton(
|
||||
iconPath: info['icon'] ?? '',
|
||||
title: info['title'] ?? '',
|
||||
unreadCount: unread,
|
||||
onTap: () => _handleIconTap(masterIndex),
|
||||
unreadCount: (info['unreadCount'] ?? 0) is int ? info['unreadCount'] as int : int.tryParse('${info['unreadCount']}') ?? 0,
|
||||
onTap: () => _handleIconTap(index),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,15 @@
|
|||
// lib/pages/new/main_page.dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:qhd_prevention/common/route_service.dart';
|
||||
import 'package:qhd_prevention/http/modules/appmenu_api.dart';
|
||||
import 'package:qhd_prevention/pages/badge_manager.dart';
|
||||
import 'package:qhd_prevention/pages/home/home_page.dart';
|
||||
import 'package:qhd_prevention/pages/mine/mine_page.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/pages/notif/notif_page.dart';
|
||||
import 'package:qhd_prevention/services/heartbeat_service.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
|
||||
import 'mine/mine_page.dart';
|
||||
|
||||
/// 用于向子树公布当前 tab 索引
|
||||
class CurrentTabNotifier extends InheritedWidget {
|
||||
final int currentIndex;
|
||||
|
||||
const CurrentTabNotifier({
|
||||
required this.currentIndex,
|
||||
required Widget child,
|
||||
|
|
@ -45,14 +38,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||
int _currentIndex = 0;
|
||||
final GlobalKey<HomePageState> _homeKey = GlobalKey<HomePageState>();
|
||||
final GlobalKey<NotifPageState> _notifKey = GlobalKey<NotifPageState>();
|
||||
// final GlobalKey<ApplicationPageTwoState> _appKey = GlobalKey<ApplicationPageTwoState>();
|
||||
final GlobalKey<MinePageState> _mineKey = GlobalKey<MinePageState>();
|
||||
|
||||
// 固定页面顺序(不要修改顺序,否则对应 _tabVisibility 的索引会错)
|
||||
late final List<Widget> _pages;
|
||||
|
||||
// 存储每个固定Tab是否可见(对应 _pages 的顺序)
|
||||
// [首页, 通知, 我的]
|
||||
late List<bool> _tabVisibility;
|
||||
late List<Widget> _pages;
|
||||
late List<bool> _tabVisibility; // 存储每个Tab的显示状态
|
||||
|
||||
// 添加 BadgeManager 实例
|
||||
late final BadgeManager _badgeManager;
|
||||
|
|
@ -60,158 +49,53 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_badgeManager = BadgeManager();
|
||||
_badgeManager.addListener(_onBadgeChanged);
|
||||
|
||||
// 初始化 BadgeManager
|
||||
if (widget.isChooseFirm) {
|
||||
_badgeManager.initAllModules();
|
||||
}
|
||||
_badgeManager = BadgeManager();
|
||||
_badgeManager.initAllModules();
|
||||
|
||||
// 监听 BadgeManager 的变化
|
||||
_badgeManager.addListener(_onBadgeChanged);
|
||||
|
||||
// 初始化固定页面(顺序固定) — **这里保持你要求的构造不变**
|
||||
// 注册生命周期监听
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// 初始化所有Tab
|
||||
_tabVisibility = [true, true, true];
|
||||
_pages = <Widget>[
|
||||
HomePage(key: _homeKey, isChooseFirm: widget.isChooseFirm),
|
||||
NotifPage(key: _notifKey),
|
||||
MinePage(key: _mineKey, isChooseFirm: widget.isChooseFirm),
|
||||
];
|
||||
|
||||
// 初始化时默认隐藏,等待路由加载后再决定显示哪些 tab
|
||||
_tabVisibility = [false, false, false];
|
||||
|
||||
// 监听 RouteService 的更新:当路由数据加载/变更时更新可见性
|
||||
RouteService().addListener(_onRoutesUpdated);
|
||||
|
||||
// 拉取路由(优先接口,失败则回退本地 assets)
|
||||
_getRoute();
|
||||
}
|
||||
|
||||
/// 拉取路由:优先通过接口获取;如果接口失败或返回异常,回退到 assets/route/routes.txt
|
||||
Future<void> _getRoute() async {
|
||||
try {
|
||||
Map? route;
|
||||
// 接口获取
|
||||
// try {
|
||||
// LoadingDialogHelper.show(message: '加载中...');
|
||||
|
||||
// final res = await AppMenuApi.getAppMenu();
|
||||
// if (res != null && res['success'] == true && res['data'] is List) {
|
||||
// route = res;
|
||||
// } else {
|
||||
// debugPrint('AppMenuApi.getAppMenu returned no data or failed; fallback to local assets.');
|
||||
// }
|
||||
// } catch (e) {
|
||||
// debugPrint('AppMenuApi.getAppMenu error: $e -> fallback to local assets.');
|
||||
// }
|
||||
// 本地获取
|
||||
try {
|
||||
final routeString = await loadFromAssets();
|
||||
route = jsonDecode(routeString) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
debugPrint('loadFromAssets error: $e');
|
||||
}
|
||||
|
||||
if (route != null && route['data'] is List) {
|
||||
final data = route['data'] as List<dynamic>;
|
||||
RouteService().initializeRoutes(data);
|
||||
// initializeRoutes 内部会 notifyListeners -> _onRoutesUpdated 被调用
|
||||
} else {
|
||||
debugPrint('No valid route data to initialize RouteService.');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('获取路由配置失败: $e');
|
||||
} finally {
|
||||
LoadingDialogHelper.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> loadFromAssets() async {
|
||||
return await rootBundle.loadString('assets/route/routes.txt');
|
||||
}
|
||||
|
||||
void _onRoutesUpdated() {
|
||||
// 路由服务更新时重新计算三个模块的可见性
|
||||
_updateTabVisibilityFromRoutes();
|
||||
}
|
||||
|
||||
/// 根据 RouteService 中解析的路由来设置 _tabVisibility
|
||||
/// 规则:
|
||||
/// - 首页:menuPerms == 'dashboard'
|
||||
/// - 通知:menuPerms == 'notice'
|
||||
/// - 我的:menuPerms == 'my-center'
|
||||
void _updateTabVisibilityFromRoutes() {
|
||||
final routeService = RouteService();
|
||||
|
||||
// 使用 mainTabs,如果为空(尚未有路由或后端返回空),保持当前 _tabVisibility(即不自动显示)
|
||||
final mainTabs = routeService.mainTabs;
|
||||
if (mainTabs.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool homeVisible = false;
|
||||
bool notifVisible = false;
|
||||
bool mineVisible = false;
|
||||
|
||||
for (final m in mainTabs) {
|
||||
final perms = (m.menuPerms ?? '').toString();
|
||||
if (!homeVisible && perms == 'dashboard' && m.visible) {
|
||||
homeVisible = true;
|
||||
}
|
||||
if (!notifVisible && perms == 'notice'&& m.visible) {
|
||||
notifVisible = true;
|
||||
}
|
||||
if (!mineVisible && perms == 'my-center'&& m.visible) {
|
||||
mineVisible = true;
|
||||
}
|
||||
if (homeVisible && notifVisible && mineVisible) break;
|
||||
}
|
||||
|
||||
// 后端未匹配到就隐藏(不兜底)
|
||||
setState(() {
|
||||
_tabVisibility = [homeVisible, widget.isChooseFirm ? notifVisible : false, mineVisible];
|
||||
|
||||
// 若当前激活的 tab 被隐藏,则切换到第一个可见 tab(若没有可见 tab,则保持当前索引为 0)
|
||||
if (!_isIndexVisible(_currentIndex)) {
|
||||
final first = _firstVisibleIndexOrDefault(_currentIndex);
|
||||
_currentIndex = first;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 返回第一个可见 tab 的索引;如果没有可见项,返回给定的 fallback(默认0)
|
||||
int _firstVisibleIndexOrDefault([int fallback = 0]) {
|
||||
for (int i = 0; i < _tabVisibility.length; i++) {
|
||||
if (_tabVisibility[i]) return i;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool _isIndexVisible(int index) {
|
||||
if (index < 0 || index >= _tabVisibility.length) return false;
|
||||
return _tabVisibility[index];
|
||||
// 启动心跳服务
|
||||
HeartbeatService().start();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 移除监听
|
||||
_badgeManager.removeListener(_onBadgeChanged);
|
||||
RouteService().removeListener(_onRoutesUpdated);
|
||||
// 移除生命周期监听
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
||||
// 停止心跳服务
|
||||
HeartbeatService().stop();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
// 应用回到前台,恢复心跳
|
||||
HeartbeatService().resume();
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.hidden:
|
||||
// 应用进入后台,暂停心跳
|
||||
HeartbeatService().pause();
|
||||
break;
|
||||
}
|
||||
|
|
@ -250,53 +134,33 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// 将原始索引(固定顺序 0..n) 转为 可见索引 (visiblePages 索引)
|
||||
// 若 originalIndex 在当前不可见,则返回第一个可见的索引(若无可见则返回 0)
|
||||
int _originalToVisibleIndex(int originalIndex, List<bool> visibility) {
|
||||
int visibleIndex = 0;
|
||||
for (int i = 0; i < visibility.length; i++) {
|
||||
if (!visibility[i]) continue;
|
||||
if (i == originalIndex) return visibleIndex;
|
||||
visibleIndex++;
|
||||
}
|
||||
// originalIndex 不可见 -> 返回第一个可见索引(如果没有可见返回 0)
|
||||
return visibility.contains(true) ? 0 : 0;
|
||||
}
|
||||
|
||||
// 将可见索引映射回原始索引(用于 BottomNavigationBar 的 onTap)
|
||||
// 若 visibleIndex 超界,返回第一个可见原始索引或 0
|
||||
int _visibleToOriginalIndex(int visibleIndex, List<bool> visibility) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < visibility.length; i++) {
|
||||
if (!visibility[i]) continue;
|
||||
if (count == visibleIndex) return i;
|
||||
count++;
|
||||
}
|
||||
// 若没有找到,返回第一个可见原始索引或 0
|
||||
for (int i = 0; i < visibility.length; i++) {
|
||||
if (visibility[i]) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 使用 _badgeManager 而不是 BadgeManager()
|
||||
final bm = _badgeManager;
|
||||
|
||||
// 构建可见的底部导航项与页面(基于 _tabVisibility)
|
||||
// 构建可见的底部导航项
|
||||
final List<BottomNavigationBarItem> visibleItems = [];
|
||||
final List<Widget> visiblePages = [];
|
||||
|
||||
for (int i = 0; i < _pages.length; i++) {
|
||||
for (int i = 0; i < _tabVisibility.length; i++) {
|
||||
if (_tabVisibility[i]) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
visibleItems.add(BottomNavigationBarItem(
|
||||
icon: Image.asset('assets/tabbar/basics.png', width: 24, height: 24),
|
||||
activeIcon: Image.asset('assets/tabbar/basics_cur.png', width: 24, height: 24),
|
||||
label: '首页',
|
||||
));
|
||||
visibleItems.add(
|
||||
BottomNavigationBarItem(
|
||||
icon: Image.asset(
|
||||
'assets/tabbar/basics.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
activeIcon: Image.asset(
|
||||
'assets/tabbar/basics_cur.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
label: '首页',
|
||||
),
|
||||
);
|
||||
visiblePages.add(_pages[i]);
|
||||
break;
|
||||
case 1:
|
||||
|
|
@ -314,105 +178,83 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
|||
visiblePages.add(_pages[i]);
|
||||
break;
|
||||
case 2:
|
||||
visibleItems.add(BottomNavigationBarItem(
|
||||
icon: Image.asset('assets/tabbar/my.png', width: 24, height: 24),
|
||||
activeIcon: Image.asset('assets/tabbar/my_cur.png', width: 24, height: 24),
|
||||
label: '我的',
|
||||
));
|
||||
visibleItems.add(
|
||||
BottomNavigationBarItem(
|
||||
icon: Image.asset(
|
||||
'assets/tabbar/my.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
activeIcon: Image.asset(
|
||||
'assets/tabbar/my_cur.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
label: '我的',
|
||||
),
|
||||
);
|
||||
visiblePages.add(_pages[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何可见页面,body 显示空占位(按你之前要求不兜底)
|
||||
final bool hasVisiblePages = visiblePages.isNotEmpty;
|
||||
|
||||
// 将当前索引映射到可见Tab的索引(用于 IndexedStack/BottomNavigationBar)
|
||||
final visibleCurrentIndex = _originalToVisibleIndex(_currentIndex, _tabVisibility);
|
||||
|
||||
// ---------- 关键:根据 visibleItems 个数选择底栏渲染方式 ----------
|
||||
Widget? bottomBarWidget;
|
||||
if (visibleItems.length >= 2) {
|
||||
// 正常使用 BottomNavigationBar(至少 2 个 item)
|
||||
bottomBarWidget = BottomNavigationBar(
|
||||
currentIndex: visibleCurrentIndex,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: Colors.blue,
|
||||
unselectedItemColor: Colors.grey,
|
||||
onTap: (visibleIndex) {
|
||||
final originalIndex = _visibleToOriginalIndex(visibleIndex, _tabVisibility);
|
||||
setState(() => _currentIndex = originalIndex);
|
||||
},
|
||||
items: visibleItems,
|
||||
);
|
||||
} else if (visibleItems.length == 1) {
|
||||
// 自定义单个 tab 底栏(避免 BottomNavigationBar 的断言)
|
||||
final single = visibleItems[0];
|
||||
final singleVisibleOriginalIndex = _visibleToOriginalIndex(0, _tabVisibility);
|
||||
final isSelected = _currentIndex == singleVisibleOriginalIndex;
|
||||
|
||||
// 取选中或未选中的 icon 小部件
|
||||
final Widget iconWidget = isSelected && single.activeIcon != null ? single.activeIcon! : single.icon;
|
||||
|
||||
bottomBarWidget = Material(
|
||||
elevation: 8,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_currentIndex = singleVisibleOriginalIndex;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
height: kBottomNavigationBarHeight,
|
||||
color: Colors.white,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 图标(上)
|
||||
SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: Center(child: iconWidget),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// 文本(下)
|
||||
Text(
|
||||
single.label ?? '',
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.blue : Colors.grey,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 没有可见项 -> 不显示底栏
|
||||
bottomBarWidget = null;
|
||||
// 将当前索引映射到可见Tab的索引
|
||||
int getVisibleIndex(int originalIndex) {
|
||||
int visibleIndex = 0;
|
||||
for (int i = 0; i <= originalIndex; i++) {
|
||||
if (_tabVisibility[i]) {
|
||||
if (i == originalIndex) return visibleIndex;
|
||||
visibleIndex++;
|
||||
}
|
||||
}
|
||||
return 0; // 默认返回第一个可见Tab
|
||||
}
|
||||
|
||||
final visibleCurrentIndex = getVisibleIndex(_currentIndex);
|
||||
|
||||
return CurrentTabNotifier(
|
||||
currentIndex: _currentIndex,
|
||||
child: Scaffold(
|
||||
appBar: null,
|
||||
body: hasVisiblePages
|
||||
? IndexedStack(
|
||||
index: visibleCurrentIndex,
|
||||
children: visiblePages,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
bottomNavigationBar: bottomBarWidget,
|
||||
body: IndexedStack(index: visibleCurrentIndex, children: visiblePages),
|
||||
bottomNavigationBar:
|
||||
visibleItems.length >= 2
|
||||
? BottomNavigationBar(
|
||||
currentIndex: visibleCurrentIndex,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: Colors.blue,
|
||||
unselectedItemColor: Colors.grey,
|
||||
onTap: (visibleIndex) {
|
||||
int originalIndex = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < _tabVisibility.length; i++) {
|
||||
if (_tabVisibility[i]) {
|
||||
if (count == visibleIndex) {
|
||||
originalIndex = i;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
setState(() => _currentIndex = originalIndex);
|
||||
},
|
||||
items: visibleItems,
|
||||
)
|
||||
: null, // 如果没有可见的Tab,隐藏底部导航栏
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void _onBadgeChanged() {
|
||||
// 当角标数据变化时,只更新需要重建的部分
|
||||
if (mounted) setState(() {});
|
||||
// 但这里我们只需要触发 build 来更新底部导航栏的角标
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
// 只触发重建,不改变数据
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
|
|||
],
|
||||
|
||||
ItemListWidget.selectableLineTitleTextRightButton(
|
||||
label: '证书类型:',
|
||||
label: '证书作业类型:',
|
||||
isEditable: widget.model == CertifitcateEditMode.add,
|
||||
text: pd['typeName'] ?? '请选择',
|
||||
isRequired: widget.model == CertifitcateEditMode.add,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
// file: mine_page.dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
|
|
@ -21,13 +19,10 @@ import 'package:qhd_prevention/tools/tools.dart';
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'certificate/certificate_list_page.dart';
|
||||
import 'package:qhd_prevention/common/route_service.dart';
|
||||
import 'package:qhd_prevention/common/route_model.dart';
|
||||
|
||||
class MinePage extends StatefulWidget {
|
||||
const MinePage({super.key, required this.isChooseFirm});
|
||||
final bool isChooseFirm;
|
||||
|
||||
final bool isChooseFirm;
|
||||
@override
|
||||
State<MinePage> createState() => MinePageState();
|
||||
}
|
||||
|
|
@ -44,152 +39,22 @@ class MinePageState extends State<MinePage> {
|
|||
String name = '登录/注册';
|
||||
String phone = '';
|
||||
|
||||
/// 配置:UI 顺序/图标/对应后端 menuPerm(s)
|
||||
/// perm 列表为空表示不受后端配置控制(始终显示)
|
||||
final List<Map<String, dynamic>> _allSettings = [
|
||||
{
|
||||
'title': '我的信息',
|
||||
'icon': 'assets/images/ico9.png',
|
||||
'perms': ['my-center-My-Information'],
|
||||
'action': 'userinfo',
|
||||
},
|
||||
{
|
||||
'title': '修改密码',
|
||||
'icon': 'assets/images/ico16.png',
|
||||
'perms': ['my-center-Change-Password'],
|
||||
'action': 'changePwd',
|
||||
},
|
||||
{
|
||||
'title': '扫码入职',
|
||||
'icon': 'assets/images/ico10.png',
|
||||
// 如果你希望扫码入职也受后端控制,可以保留下面 perm;否则置空 []
|
||||
'perms': ['dashboard-scan'],
|
||||
'action': 'scanOnboarding',
|
||||
},
|
||||
{
|
||||
'title': '人脸认证',
|
||||
'icon': 'assets/images/ico11.png',
|
||||
'perms': ['my-center-Face-Authentication'],
|
||||
'action': 'face',
|
||||
},
|
||||
{
|
||||
'title': '证书信息',
|
||||
'icon': 'assets/images/ico12.png',
|
||||
'perms': ['my-center-Certificate-Information'],
|
||||
'action': 'certificate',
|
||||
},
|
||||
// {
|
||||
// 'title': '版本更新',
|
||||
// 'icon': 'assets/images/ico14.png',
|
||||
// 'perms': ['my-center-Version-Update'],
|
||||
// 'action': 'version',
|
||||
// },
|
||||
// {
|
||||
// 'title': '关于我们',
|
||||
// 'icon': 'assets/images/ico15.png',
|
||||
// 'perms': ['my-center-About-Us'],
|
||||
// 'action': 'about',
|
||||
// },
|
||||
{
|
||||
'title': '切换账户',
|
||||
'icon': 'assets/images/ico_switch.png',
|
||||
// 切换账户仍只在 widget.isChooseFirm 为 true 时显示(参见构建函数)
|
||||
'perms': ['my-center-Switch-Account'],
|
||||
'action': 'changeAccount',
|
||||
},
|
||||
{
|
||||
'title': '账户注销',
|
||||
'icon': 'assets/images/ico_quit.png',
|
||||
// 后端可能有多种命名,两者任一存在即显示
|
||||
'perms': ['my-center-User-Logout', 'my-center-Logout'],
|
||||
'action': 'logout',
|
||||
},
|
||||
];
|
||||
|
||||
// 当前可见项(与 _allSettings 顺序对应)
|
||||
late List<bool> _visible;
|
||||
|
||||
void onRouteConfigLoaded() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
// 这里不直接修改业务逻辑,仅触发重建(update 会在 listener 中执行)
|
||||
// _updateMenuVisibility();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
|
||||
_getUserInfo();
|
||||
|
||||
// 初始默认都显示,避免短时白屏/闪烁
|
||||
_visible = List<bool>.filled(_allSettings.length, true);
|
||||
|
||||
// 监听路由变化来决定哪些设置项应显示
|
||||
RouteService().addListener(_onRouteServiceUpdated);
|
||||
|
||||
// 尝试立即应用(如果 route 已加载)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _updateVisibilityFromRoute());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
RouteService().removeListener(_onRouteServiceUpdated);
|
||||
} catch (_) {}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onRouteServiceUpdated() {
|
||||
_updateVisibilityFromRoute();
|
||||
}
|
||||
|
||||
/// 根据 RouteService 的路由配置计算每个条目的可见性
|
||||
void _updateVisibilityFromRoute() {
|
||||
final rs = RouteService();
|
||||
|
||||
// 如果 mainTabs 为空,说明路由还没加载;保持当前 visible(避免闪烁)
|
||||
if (rs.mainTabs.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<bool> next = List<bool>.filled(_allSettings.length, false);
|
||||
|
||||
for (int i = 0; i < _allSettings.length; i++) {
|
||||
final perms = (_allSettings[i]['perms'] ?? []) as List<String>;
|
||||
|
||||
if (perms.isEmpty) {
|
||||
// 无后端控制的项默认显示(比如你希望用户协议/隐私总是能见到)
|
||||
next[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 只要找到任一 perm 在路由树中可见,即认为该项可见
|
||||
bool anyFound = false;
|
||||
for (final p in perms) {
|
||||
final node = rs.findRouteByPerm(p);
|
||||
if (node != null) {
|
||||
// rs.findRouteByPerm 已经会考虑父节点 visible 的情况(按照 RouteService 实现)
|
||||
// 这里再用 showFlag==1 做额外判断:当后端返回节点但 showFlag==0 (不显示) 则视为不可见
|
||||
if (node.showFlag == 1 || node.visible) {
|
||||
anyFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
next[i] = anyFound;
|
||||
}
|
||||
|
||||
// 与当前 _visible 比较,只有变更时才 setState
|
||||
if (!listEquals(next, _visible)) {
|
||||
setState(() {
|
||||
_visible = next;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息(保持原逻辑)
|
||||
// 获取用户信息
|
||||
Future<void> _getUserInfo() async {
|
||||
final res = await BasicInfoApi.getUserMessage(
|
||||
'${SessionService.instance.accountId}',
|
||||
|
|
@ -204,8 +69,111 @@ class MinePageState extends State<MinePage> {
|
|||
});
|
||||
}
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double headerHeight = 300.0;
|
||||
final double overlap = 100.0;
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: headerHeight,
|
||||
child: _buildHeaderSection(),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||
onNotification: (overscroll) {
|
||||
overscroll.disallowIndicator();
|
||||
return false;
|
||||
},
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(
|
||||
top: headerHeight - overlap,
|
||||
bottom: 24,
|
||||
left: 0,
|
||||
right: 0,
|
||||
),
|
||||
children: [
|
||||
_buildSettingsList(),
|
||||
|
||||
SizedBox(height: 15),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsetsGeometry.symmetric(horizontal: 15),
|
||||
child: CustomButton(
|
||||
text: '退出登录',
|
||||
textStyle: TextStyle(fontSize: 16),
|
||||
backgroundColor: Colors.blue,
|
||||
// borderRadius: 15,
|
||||
onPressed: () {
|
||||
CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
title: '确认退出',
|
||||
content: '确定要退出当前账号吗',
|
||||
onConfirm: () async {
|
||||
// await AuthService.logout(); // ✅ 等待登出完成
|
||||
// if (!mounted) return;
|
||||
// 清除用户登录状态
|
||||
await _clearUserSession();
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const LoginPage(),
|
||||
),
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderSection() {
|
||||
return Stack(
|
||||
alignment: const FractionalOffset(0.5, 0),
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
|
||||
child:
|
||||
Image.asset(
|
||||
"assets/images/my_bg.png",
|
||||
width: MediaQuery.of(context).size.width, // 获取屏幕宽度
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 51,
|
||||
child: Text(
|
||||
"我的",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 标语区域
|
||||
_buildSloganSection(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _logout() async {
|
||||
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
/// 获取用户在职列表
|
||||
|
|
@ -224,7 +192,8 @@ class MinePageState extends State<MinePage> {
|
|||
context,
|
||||
title: '温馨提示',
|
||||
content: '注销后您的所有信息将会被删除\n请确认是否注销。 ',
|
||||
onConfirm: () {},
|
||||
onConfirm: () {
|
||||
},
|
||||
);
|
||||
if (result) {
|
||||
CustomAlertDialog.showInputWithCode(
|
||||
|
|
@ -240,7 +209,8 @@ class MinePageState extends State<MinePage> {
|
|||
},
|
||||
onConfirm: (code) async {
|
||||
_quit(code);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -253,8 +223,8 @@ class MinePageState extends State<MinePage> {
|
|||
Future<void> _quit(String code) async {
|
||||
LoadingDialogHelper.show();
|
||||
Map data = {
|
||||
'id': SessionService.instance.accountId,
|
||||
'phoneCode': code,
|
||||
'id' : SessionService.instance.accountId,
|
||||
'phoneCode' : code,
|
||||
};
|
||||
await BasicInfoApi.logout(data).then((res) async {
|
||||
LoadingDialogHelper.dismiss();
|
||||
|
|
@ -263,7 +233,8 @@ class MinePageState extends State<MinePage> {
|
|||
await SessionService.instance.clear(clearPrefs: true);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const LoginPage()),
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const LoginPage()),
|
||||
);
|
||||
} else {
|
||||
ToastUtil.showNormal(context, res['errMessage'] ?? '');
|
||||
|
|
@ -275,21 +246,28 @@ class MinePageState extends State<MinePage> {
|
|||
final headerUrl = SessionService.instance.userData?.userAvatarUrl ?? '';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(0, 100, 0, 0),
|
||||
margin: EdgeInsets.fromLTRB(0, 100, 0, 0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 水平居中
|
||||
// crossAxisAlignment: CrossAxisAlignment.center, // 垂直居中(可选)
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
backgroundImage: AssetImage("assets/images/yingyong11.png"),
|
||||
radius: 30,
|
||||
),
|
||||
headerUrl.isEmpty
|
||||
? const CircleAvatar(
|
||||
backgroundImage: AssetImage("assets/images/my_bg.png"),
|
||||
radius: 30,
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundImage: NetworkImage(ApiService.baseImgPath + headerUrl),
|
||||
radius: 30,
|
||||
),
|
||||
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
|
|
@ -303,6 +281,136 @@ class MinePageState extends State<MinePage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingsList() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildSettingItem(
|
||||
title: "我的信息",
|
||||
icon: "assets/images/ico9.png",
|
||||
value: notificationsEnabled,
|
||||
onChanged: (value) async {
|
||||
await pushPage(
|
||||
FullUserinfoPage(isEidt: false, isChooseFirm: true),
|
||||
context,
|
||||
);
|
||||
|
||||
},
|
||||
),
|
||||
|
||||
_buildSettingItem(
|
||||
title: "修改密码",
|
||||
icon: "assets/images/ico16.png",
|
||||
value: notificationsEnabled,
|
||||
onChanged: (value) async {
|
||||
await pushPage(MineSetPwdPage('0'), context);
|
||||
},
|
||||
),
|
||||
_buildSettingItem(
|
||||
title: "扫码入职",
|
||||
icon: "assets/images/ico10.png",
|
||||
value: scanAuthentication,
|
||||
onChanged: (value) async {
|
||||
final result = await pushPage(
|
||||
ScanPage(type: ScanType.Onboarding),
|
||||
context,
|
||||
);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
pushPage(OnboardingFullPage(scanData: result), context);
|
||||
},
|
||||
),
|
||||
|
||||
_buildSettingItem(
|
||||
title: "人脸认证",
|
||||
icon: "assets/images/ico11.png",
|
||||
value: faceAuthentication,
|
||||
onChanged: (value) {
|
||||
pushPage(
|
||||
const FaceRecognitionPage(
|
||||
studentId: '',
|
||||
data: {},
|
||||
mode: FaceMode.setUpdata,
|
||||
),
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
_buildSettingItem(
|
||||
title: "证书信息",
|
||||
icon: "assets/images/ico12.png",
|
||||
value: passwordChanged,
|
||||
onChanged: (value) {
|
||||
pushPage(
|
||||
const CertificateListPage(),
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// _buildSettingItem(
|
||||
// title: "问题反馈",
|
||||
// icon: "assets/images/ico13.png",
|
||||
// value: passwordChanged,
|
||||
// onChanged: (value) {
|
||||
// ToastUtil.showNormal(context, '需求待定');
|
||||
// // pushPage(FeedbackPage(), context);
|
||||
// },
|
||||
// ),
|
||||
|
||||
// const Divider(height: 1, indent: 60),
|
||||
_buildSettingItem(
|
||||
title: "版本更新",
|
||||
icon: "assets/images/ico14.png",
|
||||
value: updateAvailable,
|
||||
onChanged: (value) => setState(() => updateAvailable = value!),
|
||||
),
|
||||
|
||||
_buildSettingItem(
|
||||
title: "关于我们",
|
||||
icon: "assets/images/ico15.png",
|
||||
value: logoutSelected,
|
||||
onChanged: (value) {
|
||||
setState(() => logoutSelected = value!);
|
||||
},
|
||||
),
|
||||
if (widget.isChooseFirm)
|
||||
_buildSettingItem(
|
||||
title: "切换账户",
|
||||
icon: "assets/images/ico_switch.png",
|
||||
value: logoutSelected,
|
||||
onChanged: (value) {
|
||||
pushPage(MineChangeFirmPage(), context);
|
||||
},
|
||||
),
|
||||
_buildSettingItem(
|
||||
title: "账户注销",
|
||||
icon: "assets/images/ico_quit.png",
|
||||
value: logoutSelected,
|
||||
onChanged: (value) {
|
||||
_logout();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingItem({
|
||||
required String title,
|
||||
required String icon,
|
||||
|
|
@ -327,9 +435,16 @@ class MinePageState extends State<MinePage> {
|
|||
title,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
|
||||
trailing: Transform.scale(
|
||||
scale: 1.2,
|
||||
child: const Icon(Icons.chevron_right),
|
||||
child: Icon(Icons.chevron_right),
|
||||
// Image.asset(
|
||||
// "assets/images/right.png",
|
||||
// fit: BoxFit.cover,
|
||||
// width: 15,
|
||||
// height: 15,
|
||||
// ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -339,160 +454,4 @@ class MinePageState extends State<MinePage> {
|
|||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('isLoggedIn'); // 清除登录状态
|
||||
}
|
||||
|
||||
void _onSettingTapAction(String action) async {
|
||||
switch (action) {
|
||||
case 'userinfo':
|
||||
await pushPage(FullUserinfoPage(isEidt: false, isChooseFirm: true), context);
|
||||
break;
|
||||
case 'changePwd':
|
||||
await pushPage(MineSetPwdPage('0'), context);
|
||||
break;
|
||||
case 'scanOnboarding':
|
||||
final result = await pushPage(ScanPage(type: ScanType.Onboarding), context);
|
||||
if (result == null) return;
|
||||
pushPage(OnboardingFullPage(scanData: result), context);
|
||||
break;
|
||||
case 'face':
|
||||
pushPage(const FaceRecognitionPage(studentId: '', data: {}, mode: FaceMode.setUpdata), context);
|
||||
break;
|
||||
case 'certificate':
|
||||
pushPage(const CertificateListPage(), context);
|
||||
break;
|
||||
case 'version':
|
||||
// 版本检查逻辑(占位)
|
||||
ToastUtil.showNormal(context, '功能开发中...');
|
||||
break;
|
||||
case 'about':
|
||||
// 关于我们(占位)
|
||||
ToastUtil.showNormal(context, '功能开发中...');
|
||||
|
||||
break;
|
||||
case 'changeAccount':
|
||||
pushPage(MineChangeFirmPage(), context);
|
||||
break;
|
||||
case 'logout':
|
||||
_logout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSettingsList() {
|
||||
final children = <Widget>[];
|
||||
for (int i = 0; i < _allSettings.length; i++) {
|
||||
// 保证索引范围并且按照 _visible 判定
|
||||
if (i >= _visible.length) continue;
|
||||
if (!_visible[i]) continue;
|
||||
|
||||
final item = _allSettings[i];
|
||||
final title = item['title'] as String;
|
||||
|
||||
// 只在 isChooseFirm 为 true 时显示
|
||||
if (title == '切换账户' && !widget.isChooseFirm) continue;
|
||||
|
||||
children.add(_buildSettingItem(
|
||||
title: title,
|
||||
icon: item['icon'] as String,
|
||||
value: false,
|
||||
onChanged: (_) => _onSettingTapAction(item['action'] as String),
|
||||
));
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double headerHeight = 300.0;
|
||||
final double overlap = 100.0;
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(top: 0, left: 0, right: 0, height: headerHeight, child: _buildHeaderSection()),
|
||||
Positioned.fill(
|
||||
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||
onNotification: (overscroll) {
|
||||
overscroll.disallowIndicator();
|
||||
return false;
|
||||
},
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(top: headerHeight - overlap, bottom: 24, left: 0, right: 0),
|
||||
children: [
|
||||
_buildSettingsList(),
|
||||
const SizedBox(height: 15),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: CustomButton(
|
||||
text: '退出登录',
|
||||
textStyle: const TextStyle(fontSize: 16),
|
||||
backgroundColor: Colors.blue,
|
||||
onPressed: () {
|
||||
CustomAlertDialog.showConfirm(
|
||||
context,
|
||||
title: '确认退出',
|
||||
content: '确定要退出当前账号吗',
|
||||
onConfirm: () async {
|
||||
await _clearUserSession();
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderSection() {
|
||||
return Stack(
|
||||
alignment: const FractionalOffset(0.5, 0),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 10),
|
||||
child: Image.asset(
|
||||
"assets/images/my_bg.png",
|
||||
width: MediaQuery.of(context).size.width,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
top: 51,
|
||||
child: Text(
|
||||
"我的",
|
||||
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
_buildSloganSection(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/constants/app_enums.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
import 'package:qhd_prevention/http/modules/edu_api.dart';
|
||||
import 'package:qhd_prevention/http/modules/file_api.dart';
|
||||
import 'package:qhd_prevention/pages/home/Study/study_take_exam_page.dart';
|
||||
import 'package:qhd_prevention/pages/mine/face_ecognition_page.dart';
|
||||
import 'package:qhd_prevention/pages/mine/mine_sign_page.dart';
|
||||
import 'package:qhd_prevention/pages/mine/onboarding_full_page.dart';
|
||||
import 'package:qhd_prevention/services/SessionService.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
|
||||
class ScanService {
|
||||
static void scan(BuildContext context, final result) async {
|
||||
if (FormUtils.hasValue(result, 'classId')) { // 线上培训相关
|
||||
int type = result['type'] ?? 0;
|
||||
|
||||
final data = {
|
||||
...result,
|
||||
'phone': SessionService.instance.userData?.phone ?? '',
|
||||
'type': type,
|
||||
};
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
// 验证是否可以签到
|
||||
final response = await EduApi.checkSignIn(data);
|
||||
LoadingDialogHelper.hide();
|
||||
|
||||
if (response['success']) {
|
||||
// 进行人脸识别
|
||||
final filePath = await pushPage(
|
||||
const FaceRecognitionPage(
|
||||
studentId: '',
|
||||
data: {},
|
||||
mode: FaceMode.study,
|
||||
),
|
||||
context,
|
||||
);
|
||||
|
||||
final faceData = response['data'];
|
||||
|
||||
if (filePath != null) {
|
||||
// 对比人脸
|
||||
try {
|
||||
LoadingDialogHelper.show();
|
||||
final response = await EduApi.compareFace({
|
||||
'type': data['type'],
|
||||
'studentId': faceData['studentId'],
|
||||
}, filePath);
|
||||
final faceResultData = response['data'];
|
||||
if (response['success']) {
|
||||
final signData = {
|
||||
'id': faceResultData['id'] ?? '',
|
||||
'studentId': faceResultData['studentId'] ?? '',
|
||||
'type': data['type']?? '',
|
||||
'classId': faceResultData['classId'] ?? '',
|
||||
'studentSignId':faceResultData['studentSignId']??''
|
||||
};
|
||||
ScanService.signUpload(signData, type, context);
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, response['errMessage'] ?? '验证失败');
|
||||
}
|
||||
} catch (e) {
|
||||
ToastUtil.showNormal(context,'验证失败');
|
||||
LoadingDialogHelper.hide();
|
||||
print(e);
|
||||
}
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, '签到失败');
|
||||
}
|
||||
} else {
|
||||
ToastUtil.showNormal(context, response['errMessage'] ?? '签到失败');
|
||||
}
|
||||
}
|
||||
if (FormUtils.hasValue(result, 'corpinfoId')) { // 入职
|
||||
pushPage(OnboardingFullPage(scanData: result), context);
|
||||
}
|
||||
|
||||
}
|
||||
// 上传签到签字
|
||||
static Future<void> signUpload(Map data, int type, BuildContext context) async {
|
||||
LoadingDialogHelper.hide();
|
||||
UploadFileType fileType =
|
||||
type == 1
|
||||
? UploadFileType.onlineLearningSignSignature
|
||||
: UploadFileType.onlineLearningExamSignature;
|
||||
final signPath = await pushPage(MineSignPage(), context);
|
||||
if (signPath != null) {
|
||||
// 先上传签字图片
|
||||
try {
|
||||
LoadingDialogHelper.show();
|
||||
// 上传图片
|
||||
final response = await FileApi.uploadFile(signPath, fileType, '');
|
||||
if (response['success']) {
|
||||
data['signUrl'] = response['data']['filePath'];
|
||||
final signResult = await EduApi.uploadSignature(data);
|
||||
LoadingDialogHelper.hide();
|
||||
if (signResult['success']) {
|
||||
if (type == 1) {
|
||||
ToastUtil.showNormal(context, '签到成功');
|
||||
} else {
|
||||
// 获取试卷详情
|
||||
final examResult = await EduApi.getExamDetail(
|
||||
data['classId'] ?? '',
|
||||
);
|
||||
LoadingDialogHelper.hide();
|
||||
// 跳转考试页面
|
||||
pushPage(
|
||||
StudyTakeExamPage(
|
||||
examInfo: examResult['data'] ?? {}, signInfo: data),
|
||||
context,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, signResult['errMessage'] ?? '');
|
||||
}
|
||||
} else {
|
||||
LoadingDialogHelper.hide();
|
||||
ToastUtil.showNormal(context, response['errMessage'] ?? '');
|
||||
}
|
||||
} catch (e) {
|
||||
LoadingDialogHelper.hide();
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -953,10 +953,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: photo_manager
|
||||
sha256: fb3bc8ea653370f88742b3baa304700107c83d12748aa58b2b9f2ed3ef15e6c2
|
||||
sha256: "99355f3b3591a00416cc787bbf7f04510f672d602814e0063bf4dc40603041f0"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
version: "3.8.0"
|
||||
photo_manager_image_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ dependencies:
|
|||
# 相册
|
||||
image_picker: ^1.1.2
|
||||
wechat_assets_picker: ^9.5.1
|
||||
photo_manager: ^3.9.0
|
||||
photo_manager: ^3.7.1
|
||||
file_picker: ^10.3.2
|
||||
# 日历
|
||||
table_calendar: ^3.2.0
|
||||
|
|
@ -155,7 +155,6 @@ flutter:
|
|||
- assets/map/
|
||||
- assets/tabbar/
|
||||
- assets/study/
|
||||
- assets/route/routes.txt
|
||||
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
|
|
|||
Loading…
Reference in New Issue