修改包名
parent
0b5f169a91
commit
96c52d91bb
|
|
@ -14,7 +14,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "com.company.myapp2"
|
||||
namespace = "com.qysz.qgxgf"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "28.1.13356709"
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.company.myapp2"
|
||||
applicationId = "com.qysz.qgxgf"
|
||||
minSdk = 24
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
<!-- FileProvider 配置 -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.company.myapp2.fileprovider"
|
||||
android:authorities="com.qysz.qgxgf.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
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.company.myapp2
|
||||
package com.qysz.qgxgf
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ PODS:
|
|||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- photo_manager (3.8.0):
|
||||
- photo_manager (3.9.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: 343d78032bf7ebe944d2ab9702204dc2eda07338
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
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 = uni.UNI85F7A17;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-dev";
|
||||
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.company.myapp2.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.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.company.myapp2.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.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.company.myapp2.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf.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 = uni.UNI85F7A17;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-dev";
|
||||
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 Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Qinhuangdao Zhuoyun Technology Co., Ltd (8AKCJ9LW7D)";
|
||||
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 = uni.UNI85F7A17;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.qysz.qgxgf;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qgxgf-des";
|
||||
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>${PRODUCT_NAME}</string>
|
||||
<string>秦港相关方</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,8 +28,6 @@
|
|||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
<string>需要NFC权限来读取和写入标签</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.nfc.readersession.formats</key>
|
||||
<array>
|
||||
<string>TAG</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -2,61 +2,107 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class RouteModel {
|
||||
final String target;
|
||||
final List<RouteModel> children;
|
||||
final bool hasMenu;
|
||||
// 映射到后端新字段
|
||||
final String id;
|
||||
final String menuName; // 原来的 title
|
||||
final String menuUrl; // 原来的 path
|
||||
final String parentId;
|
||||
final String routeId;
|
||||
final String component;
|
||||
final String path;
|
||||
final String title;
|
||||
final String parentIds;
|
||||
final String meta;
|
||||
final String routeOrder;
|
||||
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 List<RouteModel> children;
|
||||
|
||||
RouteModel({
|
||||
required this.target,
|
||||
required this.children,
|
||||
required this.hasMenu,
|
||||
required this.id,
|
||||
required this.menuName,
|
||||
required this.menuUrl,
|
||||
required this.parentId,
|
||||
required this.routeId,
|
||||
required this.component,
|
||||
required this.title,
|
||||
required this.path,
|
||||
required this.parentIds,
|
||||
required this.meta,
|
||||
required this.routeOrder,
|
||||
required this.menuPerms,
|
||||
required this.menuType,
|
||||
required this.menuAttribution,
|
||||
required this.sort,
|
||||
required this.showFlag,
|
||||
required this.extValues,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
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(
|
||||
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',
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// // 解析meta字段获取title
|
||||
// String get title {
|
||||
// if (meta.isEmpty) return '';
|
||||
// try {
|
||||
// final metaMap = jsonDecode(meta) as Map<String, dynamic>;
|
||||
// return metaMap['title'] ?? '';
|
||||
// } catch (e) {
|
||||
// return '';
|
||||
// }
|
||||
// }
|
||||
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(),
|
||||
};
|
||||
|
||||
// 判断是否是叶子节点(没有子节点的路由)
|
||||
/// 是否可见(供显示逻辑判断)
|
||||
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,34 +1,87 @@
|
|||
// route_service.dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:qhd_prevention/common/route_model.dart';
|
||||
/// 路由管理
|
||||
class RouteService {
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
|
||||
class RouteService extends ChangeNotifier {
|
||||
static final RouteService _instance = RouteService._internal();
|
||||
factory RouteService() => _instance;
|
||||
RouteService._internal();
|
||||
|
||||
// 存储所有路由配置
|
||||
// 存储顶级菜单(直接从接口解析的数组)
|
||||
List<RouteModel> _allRoutes = [];
|
||||
|
||||
// 获取主Tab路由(第一级children)
|
||||
List<RouteModel> get mainTabs => _allRoutes.isNotEmpty
|
||||
? _allRoutes.first.children
|
||||
: [];
|
||||
/// 对外暴露全部顶级 routes(如果需要遍历所有顶级项)
|
||||
List<RouteModel> get allRoutes => _allRoutes;
|
||||
|
||||
// 初始化路由配置
|
||||
void initializeRoutes(List<dynamic> routeList) {
|
||||
_allRoutes = routeList.map((route) => RouteModel.fromJson(route)).toList();
|
||||
/// 初始化路由配置(允许传 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 _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, path);
|
||||
final found = _findRouteRecursive(route, needle);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
RouteModel? _findRouteRecursive(RouteModel route, String path) {
|
||||
if (route.path == path) return route;
|
||||
// 如果当前节点不可见,则按照你的要求:不再查找其子级(直接返回 null)
|
||||
if (!route.visible) return null;
|
||||
|
||||
final routeUrl = route.menuUrl.trim();
|
||||
if (routeUrl == path) return route;
|
||||
|
||||
for (final child in route.children) {
|
||||
final found = _findRouteRecursive(child, path);
|
||||
if (found != null) return found;
|
||||
|
|
@ -36,23 +89,178 @@ class RouteService {
|
|||
return null;
|
||||
}
|
||||
|
||||
// 获取某个Tab下的所有可显示的路由(hasMenu为true的叶子节点)
|
||||
// 获取某个Tab下的所有可显示路由(visible == true,且收集叶子节点)
|
||||
List<RouteModel> getRoutesForTab(RouteModel tab) {
|
||||
final routes = <RouteModel>[];
|
||||
_collectLeafRoutes(tab, routes);
|
||||
_collectVisibleLeafRoutes(tab, routes);
|
||||
return routes;
|
||||
}
|
||||
|
||||
void _collectLeafRoutes(RouteModel route, List<RouteModel> collector) {
|
||||
if (route.hasMenu) {
|
||||
/// 关键修改:如果当前节点不可见,则不再递归其 children(按你的要求)
|
||||
void _collectVisibleLeafRoutes(RouteModel route, List<RouteModel> collector) {
|
||||
if (!route.visible) return; // 如果父节点不可见,跳过整个子树
|
||||
if (route.isLeaf) {
|
||||
collector.add(route);
|
||||
if (!route.isLeaf) {
|
||||
return;
|
||||
}
|
||||
for (final child in route.children) {
|
||||
_collectLeafRoutes(child, collector);
|
||||
}
|
||||
}
|
||||
_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 '';
|
||||
|
||||
}
|
||||
}
|
||||
//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,6 +104,9 @@ class MediaPickerRow extends StatefulWidget {
|
|||
/// 新增:网格列数(默认 4),可在需要单列/自适应宽度时指定 1
|
||||
final int crossAxisCount;
|
||||
|
||||
/// 可选:1 只有拍照 2 只有相册 3 都有
|
||||
final int selectPictureType;
|
||||
|
||||
const MediaPickerRow({
|
||||
Key? key,
|
||||
this.maxCount = 4,
|
||||
|
|
@ -118,6 +121,7 @@ class MediaPickerRow extends StatefulWidget {
|
|||
this.isCamera = false,
|
||||
this.followInitialUpdates = false, // 默认 false
|
||||
this.crossAxisCount = 4, // 默认 4 列
|
||||
this.selectPictureType = 3,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -263,6 +267,7 @@ 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),
|
||||
|
|
@ -272,6 +277,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
_pickCamera();
|
||||
},
|
||||
),
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==2)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
|
|
@ -644,6 +650,8 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
final bool inlineSingle;
|
||||
/// 可选:当 inlineSingle 为 true 时,可以定制缩略图宽度(px)
|
||||
final double inlineImageWidth;
|
||||
/// 可选:1 只有拍照 2 只有相册 3 都有
|
||||
final int selectPictureType;
|
||||
|
||||
const RepairedPhotoSection({
|
||||
Key? key,
|
||||
|
|
@ -668,6 +676,7 @@ class RepairedPhotoSection extends StatefulWidget {
|
|||
this.sectionKey = kAcceptVideoSectionKey,
|
||||
this.inlineSingle = false,
|
||||
this.inlineImageWidth = 88.0,
|
||||
this.selectPictureType = 3,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -802,6 +811,7 @@ 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),
|
||||
|
|
@ -837,6 +847,7 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
}
|
||||
},
|
||||
),
|
||||
if(widget.selectPictureType==3||widget.selectPictureType==2)
|
||||
ListTile(
|
||||
titleAlignment: ListTileTitleAlignment.center,
|
||||
leading: Icon(widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library),
|
||||
|
|
@ -924,7 +935,8 @@ 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 ? '${_mediaPaths.length}/${widget.maxCount}' : '',
|
||||
rightText: widget.isShowNum ? '${_getCurrentCount()}/${widget.maxCount}' : '',
|
||||
isRequired: widget.isRequired,
|
||||
),
|
||||
),
|
||||
|
|
@ -937,6 +949,7 @@ 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(() {
|
||||
|
|
@ -988,5 +1001,17 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
int _getCurrentCount() {
|
||||
// 如果 followInitialUpdates 为 true,使用内部状态
|
||||
// 如果为 false,优先使用外部数据,因为外部数据才是真实数据源
|
||||
if (widget.followInitialUpdates) {
|
||||
return _mediaPaths.length;
|
||||
} else {
|
||||
return widget.initialMediaPaths?.length ?? _mediaPaths.length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
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',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -221,17 +221,3 @@ 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
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,7 +242,9 @@ class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
|
|||
if (res['success']) {
|
||||
final data = res['data'] as Map<String, dynamic>? ?? {};
|
||||
final score = data['examScore'] ?? 0;
|
||||
final passed = data['result'] == 1;
|
||||
var passed = data['result'] == 1;
|
||||
// 剩余考试次数
|
||||
final remain = data['surplusExamNum'] ?? 0 <= 0;
|
||||
|
||||
// 弹窗告诉用户结果:通过直接返回上一页;未通过给“继续考试 / 确定”两个按钮
|
||||
final result = await CustomAlertDialog.showConfirm(
|
||||
|
|
@ -251,7 +253,7 @@ class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
|
|||
content: passed
|
||||
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
|
||||
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
|
||||
cancelText: passed ? '' : '继续考试',
|
||||
cancelText: remain ? '' : '继续考试',
|
||||
confirmText: '确定',
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,6 @@ class HomePageState extends RouteAwareState<HomePage>
|
|||
|
||||
// 通知滚动 PageController
|
||||
_notifPageController = PageController(initialPage: 0);
|
||||
_getToDoWorkList();
|
||||
|
||||
// 启动定时器:每 3 秒切换一条通知
|
||||
_notifTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
|
||||
|
|
@ -203,13 +202,7 @@ class HomePageState extends RouteAwareState<HomePage>
|
|||
});
|
||||
|
||||
}
|
||||
// 获取待办事项
|
||||
void _getToDoWorkList() async {
|
||||
final result = await TodoApi.getTodoList({});
|
||||
setState(() {
|
||||
totalList = result['data'];
|
||||
});
|
||||
}
|
||||
|
||||
/// 校验是否入职
|
||||
Future<void> _getNeedSafetyCommitment() async {
|
||||
if (_isShowCheckLogin) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -953,10 +953,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: photo_manager
|
||||
sha256: "99355f3b3591a00416cc787bbf7f04510f672d602814e0063bf4dc40603041f0"
|
||||
sha256: fb3bc8ea653370f88742b3baa304700107c83d12748aa58b2b9f2ed3ef15e6c2
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.8.0"
|
||||
version: "3.9.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.7.1
|
||||
photo_manager: ^3.9.0
|
||||
file_picker: ^10.3.2
|
||||
# 日历
|
||||
table_calendar: ^3.2.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue