更新小e界面白屏问题
This commit is contained in:
738
lib/pages/main_bottom/e_page copy 2.dart
Normal file
738
lib/pages/main_bottom/e_page copy 2.dart
Normal file
@@ -0,0 +1,738 @@
|
||||
// import 'package:ef/ef.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
// import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||
// import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||
// import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
// import 'package:vbvs_app/controller/device/device_type_controller.dart';
|
||||
// import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||
// import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
// import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
// import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
// import 'package:vbvs_app/model/api_response.dart';
|
||||
// import 'package:vbvs_app/pages/main_bottom/xiaoe/XiaoeModel.dart';
|
||||
|
||||
//本地 wsl
|
||||
// class EPage extends StatefulWidget {
|
||||
// final String sleepUri;
|
||||
// const EPage({super.key, required this.sleepUri});
|
||||
|
||||
// @override
|
||||
// State<EPage> createState() => _EPageState();
|
||||
// }
|
||||
|
||||
// class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
// GlobalController globalController = Get.find();
|
||||
// UserInfoController userInfoController = Get.find();
|
||||
// BlueteethBindController blueteethBindController = Get.find();
|
||||
// ThemeController themeController = Get.find();
|
||||
// DeviceTypeController deviceTypeController = Get.find();
|
||||
|
||||
// RxList deviceList = [].obs;
|
||||
// RxString finalUri = RxString('');
|
||||
|
||||
// @override
|
||||
// bool get wantKeepAlive => true;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// getDeviceList();
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void dispose() {
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// super.build(context);
|
||||
// bool isLoggedIn = userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
// return LayoutBuilder(
|
||||
// builder: (context, bodySize) => GestureDetector(
|
||||
// child: Obx(() {
|
||||
// int login = userInfoController.model.login ?? 0;
|
||||
// print("login: $login");
|
||||
// final showNoLoginView = !isLoggedIn || deviceList.isEmpty;
|
||||
|
||||
// final backgroundImage = showNoLoginView
|
||||
// ? 'assets/img/xiaoe.png' // 无设备或无登录时的背景
|
||||
// : 'assets/img/bgNoImg.png';
|
||||
|
||||
// return Stack(
|
||||
// children: [
|
||||
// // 背景图片
|
||||
// Container(
|
||||
// decoration: BoxDecoration(
|
||||
// image: DecorationImage(
|
||||
// image: AssetImage(backgroundImage),
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // 遮罩层:未登录或无设备时显示
|
||||
// if (showNoLoginView)
|
||||
// Positioned.fill(
|
||||
// child: Container(
|
||||
// color: Colors.black.withOpacity(0.5),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // 主内容
|
||||
// Scaffold(
|
||||
// backgroundColor: Colors.transparent,
|
||||
// appBar: _buildAppBar(),
|
||||
// body: SafeArea(
|
||||
// top: true,
|
||||
// child: _buildContent(isLoggedIn),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// PreferredSizeWidget _buildAppBar() {
|
||||
// return AppBar(
|
||||
// backgroundColor: themeController.currentColor.sc17,
|
||||
// automaticallyImplyLeading: false,
|
||||
// iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||||
// titleSpacing: 0,
|
||||
// title: Container(
|
||||
// width: double.infinity,
|
||||
// height: 180.rpx,
|
||||
// child: Stack(
|
||||
// alignment: Alignment.center,
|
||||
// children: [
|
||||
// Text(
|
||||
// '菜单.小e'.tr,
|
||||
// style: TextStyle(
|
||||
// fontFamily: 'Readex Pro',
|
||||
// color: themeController.currentColor.sc3,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _buildContent(bool isLoggedIn) {
|
||||
// if (!isLoggedIn) {
|
||||
// return _buildLoggedOutContent();
|
||||
// }
|
||||
|
||||
// return Obx(() {
|
||||
// // 如果设备列表为空
|
||||
// if (deviceList.isEmpty) {
|
||||
// return _buildNoDeviceContent();
|
||||
// }
|
||||
|
||||
// // 设备列表不为空,显示 XiaoeView
|
||||
// return XiaoeView();
|
||||
// });
|
||||
// }
|
||||
|
||||
// Widget _buildLoggedOutContent() {
|
||||
// return GestureDetector(
|
||||
// onTap: () {
|
||||
// NewTopSlideNotification.show(
|
||||
// text: "请先登录".tr,
|
||||
// textColor: themeController.currentColor.sc9,
|
||||
// );
|
||||
// Get.toNamed("/loginPage");
|
||||
// },
|
||||
// child: Center(
|
||||
// child: Image.asset(
|
||||
// "assets/img/xiaoe.png",
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _buildNoDeviceContent() {
|
||||
// return GestureDetector(
|
||||
// onTap: () {
|
||||
// NewTopSlideNotification.show(
|
||||
// text: "请先绑定设备".tr,
|
||||
// textColor: themeController.currentColor.sc9,
|
||||
// );
|
||||
// // Get.toNamed("/mHTDeviceTypePage");
|
||||
// },
|
||||
// child: Center(
|
||||
// child: Image.asset(
|
||||
// "assets/img/xiaoe.png",
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Future<void> getDeviceList() async {
|
||||
// try {
|
||||
// BodyDeviceController bodyDeviceController = Get.find();
|
||||
// ApiResponse apiResponse =
|
||||
// await bodyDeviceController.getDeviceList(isAllDevice: true);
|
||||
|
||||
// if (apiResponse.code == 1) {
|
||||
// // 使用200代替HttpStatusCodes.ok
|
||||
// List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
// // 提取 mac 和 person.name
|
||||
// List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
// String mac = item['mac'] ?? '';
|
||||
// String name = (item['person'] != null &&
|
||||
// item['person']['name'] != null &&
|
||||
// item['person']['name'].toString().trim().isNotEmpty)
|
||||
// ? item['person']['name'] + "_${mac}"
|
||||
// : '体征检测设备'.tr + "_${mac}";
|
||||
// return {
|
||||
// 'mac': mac,
|
||||
// 'name': name,
|
||||
// };
|
||||
// }).toList();
|
||||
|
||||
// deviceList.value = newList;
|
||||
// ef.log("小e页面初始化完成,设备数量:${deviceList.length}");
|
||||
// }
|
||||
// } catch (e) {
|
||||
// ef.log("小e获取设备列表失败: $e");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//本地加载 wyf
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as io;
|
||||
import 'package:shelf_static/shelf_static.dart';
|
||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/device_type_controller.dart';
|
||||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||
import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
import 'package:vbvs_app/model/api_response.dart';
|
||||
|
||||
class EPage extends StatefulWidget {
|
||||
final String sleepUri;
|
||||
const EPage({super.key, required this.sleepUri});
|
||||
|
||||
@override
|
||||
State<EPage> createState() => _EPageState();
|
||||
}
|
||||
|
||||
class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
GlobalController globalController = Get.find();
|
||||
UserInfoController userInfoController = Get.find();
|
||||
BlueteethBindController blueteethBindController = Get.find();
|
||||
ThemeController themeController = Get.find();
|
||||
DeviceTypeController deviceTypeController = Get.find();
|
||||
|
||||
ValueNotifier<bool> isPageLoading = ValueNotifier<bool>(true);
|
||||
ValueNotifier<bool> isServerStarting = ValueNotifier<bool>(true);
|
||||
ValueNotifier<String> serverError = ValueNotifier<String>('');
|
||||
RxList deviceList = [].obs;
|
||||
RxString finalUri = RxString('');
|
||||
|
||||
// 本地服务器相关
|
||||
HttpServer? _localServer;
|
||||
int _serverPort = 0;
|
||||
String _localServerUrl = '';
|
||||
Directory? _tempExtractDir;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 启动本地服务器,成功后获取设备列表
|
||||
_startLocalWebServer().then((success) {
|
||||
if (success) {
|
||||
getDeviceList();
|
||||
} else {
|
||||
isPageLoading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 清理资源
|
||||
_cleanupResources();
|
||||
isPageLoading.dispose();
|
||||
isServerStarting.dispose();
|
||||
serverError.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<bool> _startLocalWebServer() async {
|
||||
try {
|
||||
isServerStarting.value = true;
|
||||
serverError.value = '';
|
||||
|
||||
// 1. 从 assets 加载 ZIP 文件
|
||||
final ByteData zipData;
|
||||
try {
|
||||
zipData = await rootBundle.load('assets/xiaoe/xiaoe_1.0.0.zip');
|
||||
} catch (e) {
|
||||
serverError.value = '找不到小e资源文件: $e';
|
||||
return false;
|
||||
}
|
||||
|
||||
final Uint8List zipBytes = zipData.buffer.asUint8List();
|
||||
|
||||
// 2. 创建临时目录并解压 ZIP
|
||||
_tempExtractDir = await Directory.systemTemp.createTemp('xiaoe_web_');
|
||||
final bool extractSuccess = await _extractZipToDirectory(zipBytes, _tempExtractDir!);
|
||||
|
||||
if (!extractSuccess) {
|
||||
serverError.value = '解压小e资源文件失败';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 查找入口文件 (index.html)
|
||||
final File? entryFile = await _findEntryFile(_tempExtractDir!);
|
||||
if (entryFile == null) {
|
||||
serverError.value = '找不到入口文件 (index.html)';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 创建静态文件处理器
|
||||
final staticHandler = createStaticHandler(
|
||||
_tempExtractDir!.path,
|
||||
defaultDocument: 'index.html',
|
||||
serveFilesOutsidePath: false,
|
||||
);
|
||||
|
||||
// 5. 添加日志中间件(可选)
|
||||
final handler = Pipeline()
|
||||
.addMiddleware(logRequests())
|
||||
.addHandler(staticHandler);
|
||||
|
||||
// 6. 启动服务器(端口 0 表示自动分配)
|
||||
_localServer = await io.serve(handler, InternetAddress.loopbackIPv4, 0);
|
||||
_serverPort = _localServer!.port;
|
||||
_localServerUrl = 'http://127.0.0.1:$_serverPort';
|
||||
|
||||
print('小e本地服务启动成功: $_localServerUrl');
|
||||
print('资源目录: ${_tempExtractDir!.path}');
|
||||
|
||||
isServerStarting.value = false;
|
||||
return true;
|
||||
|
||||
} catch (e, stackTrace) {
|
||||
serverError.value = '启动本地服务器失败: $e';
|
||||
print('服务器启动错误: $e\n$stackTrace');
|
||||
isServerStarting.value = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _extractZipToDirectory(Uint8List zipBytes, Directory targetDir) async {
|
||||
try {
|
||||
// 解码 ZIP 文件
|
||||
final Archive archive = ZipDecoder().decodeBytes(zipBytes);
|
||||
|
||||
for (final ArchiveFile file in archive) {
|
||||
final String filename = file.name;
|
||||
|
||||
// 跳过目录条目(它们会通过文件创建自动生成)
|
||||
if (file.isFile) {
|
||||
// 确保目录存在
|
||||
final String filePath = p.join(targetDir.path, filename);
|
||||
final File outputFile = File(filePath);
|
||||
await outputFile.parent.create(recursive: true);
|
||||
|
||||
// 写入文件内容
|
||||
final List<int> data = file.content as List<int>;
|
||||
await outputFile.writeAsBytes(data, flush: true);
|
||||
}
|
||||
}
|
||||
|
||||
print('解压完成,文件数量: ${archive.files.length}');
|
||||
return true;
|
||||
} catch (e, stackTrace) {
|
||||
print('解压失败: $e\n$stackTrace');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<File?> _findEntryFile(Directory dir) async {
|
||||
// 优先查找 index.html
|
||||
final List<String> possibleEntries = [
|
||||
'index.html',
|
||||
'Index.html',
|
||||
'INDEX.HTML',
|
||||
'main.html',
|
||||
'default.html',
|
||||
];
|
||||
|
||||
for (final entry in possibleEntries) {
|
||||
final File file = File(p.join(dir.path, entry));
|
||||
if (await file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到标准入口,查找任何 .html 文件
|
||||
try {
|
||||
final List<FileSystemEntity> entities = await dir.list(recursive: false).toList();
|
||||
for (final entity in entities) {
|
||||
if (entity is File && entity.path.toLowerCase().endsWith('.html')) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('查找入口文件失败: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void _cleanupResources() {
|
||||
// 关闭服务器
|
||||
if (_localServer != null) {
|
||||
_localServer!.close();
|
||||
_localServer = null;
|
||||
print('本地服务器已关闭');
|
||||
}
|
||||
|
||||
// 清理临时目录
|
||||
if (_tempExtractDir != null && _tempExtractDir!.existsSync()) {
|
||||
try {
|
||||
_tempExtractDir!.deleteSync(recursive: true);
|
||||
print('临时目录已清理: ${_tempExtractDir!.path}');
|
||||
} catch (e) {
|
||||
print('清理临时目录失败: $e');
|
||||
}
|
||||
_tempExtractDir = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
bool isLoggedIn = userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, bodySize) => GestureDetector(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/img/bgNoImg.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: themeController.currentColor.sc17,
|
||||
automaticallyImplyLeading: false,
|
||||
iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||||
titleSpacing: 0,
|
||||
title: Container(
|
||||
width: double.infinity,
|
||||
height: 180.rpx,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'菜单.小e'.tr,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Readex Pro',
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: true,
|
||||
child: isLoggedIn
|
||||
? _buildLoggedInContent()
|
||||
: _buildLoggedOutContent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoggedInContent() {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isServerStarting,
|
||||
builder: (context, isStarting, child) {
|
||||
if (isStarting) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.rpx),
|
||||
Text(
|
||||
'正在启动小e服务...'.tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 28.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ValueListenableBuilder<String>(
|
||||
valueListenable: serverError,
|
||||
builder: (context, error, child) {
|
||||
if (error.isNotEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40.rpx),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 60.rpx,
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
'服务启动失败'.tr,
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 32.rpx,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.rpx),
|
||||
Text(
|
||||
error,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 26.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.rpx),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
serverError.value = '';
|
||||
_startLocalWebServer().then((success) {
|
||||
if (success) {
|
||||
getDeviceList();
|
||||
}
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: themeController.currentColor.sc1,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32.rpx,
|
||||
vertical: 16.rpx,
|
||||
),
|
||||
),
|
||||
child: Text('重试'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
if (finalUri.isEmpty) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (deviceList.isEmpty) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
NewTopSlideNotification.show(
|
||||
text: "请先绑定设备".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png",
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(
|
||||
url: WebUri(finalUri.value),
|
||||
),
|
||||
onLoadStart: (controller, url) {
|
||||
isPageLoading.value = true;
|
||||
print('WebView 开始加载: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) {
|
||||
isPageLoading.value = false;
|
||||
print('WebView 加载完成: $url');
|
||||
},
|
||||
onLoadError: (controller, url, code, message) {
|
||||
isPageLoading.value = false;
|
||||
print('WebView 加载错误: $message (code: $code)');
|
||||
serverError.value = '页面加载失败: $message';
|
||||
},
|
||||
onWebViewCreated: (controller) {
|
||||
// 启用调试(仅在开发模式下)
|
||||
// if (!kReleaseMode) {
|
||||
// controller.setWebContentsDebuggingEnabled(true);
|
||||
// }
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: isPageLoading,
|
||||
builder: (context, isLoading, child) {
|
||||
return isLoading
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoggedOutContent() {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
NewTopSlideNotification.show(
|
||||
text: "必须登录提示".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
Get.toNamed("/otherLoginPage");
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png",
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> getDeviceList() async {
|
||||
try {
|
||||
BodyDeviceController bodyDeviceController = Get.find();
|
||||
ApiResponse apiResponse =
|
||||
await bodyDeviceController.getDeviceList(isAllDevice: true);
|
||||
|
||||
if (apiResponse.code == HttpStatusCodes.ok) {
|
||||
List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
String mac = item['mac'] ?? '';
|
||||
String name = (item['person'] != null &&
|
||||
item['person']['name'] != null &&
|
||||
item['person']['name'].toString().trim().isNotEmpty)
|
||||
? item['person']['name'] + "_${mac}"
|
||||
: '体征检测设备'.tr + "_${mac}";
|
||||
return {
|
||||
'mac': mac,
|
||||
'name': name,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
deviceList.value = newList;
|
||||
|
||||
// 构建最终 URL
|
||||
String baseUrl = _localServerUrl;
|
||||
String queryParams = '?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
if (deviceList.isNotEmpty) {
|
||||
String personParam = Uri.encodeComponent(jsonEncode(deviceList));
|
||||
queryParams += '&person=$personParam';
|
||||
}
|
||||
|
||||
// 添加语言参数
|
||||
String? language = "";
|
||||
// 这里保持你原来的语言逻辑
|
||||
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||
// 假设你有 mhLanguageController
|
||||
// if (mhLanguageController.selectLanguage != null) {
|
||||
// language = mhLanguageController.selectLanguage.value!.language_code;
|
||||
// }
|
||||
} else {
|
||||
// 假设你有 languageController
|
||||
// if (languageController.selectLanguage != null) {
|
||||
// language = languageController.selectLanguage.value!.language_code;
|
||||
// }
|
||||
}
|
||||
|
||||
if (language != null && language.isNotEmpty) {
|
||||
queryParams += '&lang=$language';
|
||||
}
|
||||
|
||||
finalUri.value = baseUrl + queryParams;
|
||||
print('最终加载 URL: ${finalUri.value}');
|
||||
|
||||
} else {
|
||||
// API 调用失败,使用基础 URL
|
||||
finalUri.value = '$_localServerUrl?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
print('获取设备列表失败: $e');
|
||||
// 出错时仍然加载基础页面
|
||||
finalUri.value = '$_localServerUrl?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
}
|
||||
}
|
||||
247
lib/pages/main_bottom/e_page copy.dart
Normal file
247
lib/pages/main_bottom/e_page copy.dart
Normal file
@@ -0,0 +1,247 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/device_type_controller.dart';
|
||||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||
import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
import 'package:vbvs_app/model/api_response.dart';
|
||||
|
||||
//在线使用
|
||||
class EPage extends StatefulWidget {
|
||||
final String sleepUri;
|
||||
const EPage({super.key, required this.sleepUri});
|
||||
|
||||
@override
|
||||
State<EPage> createState() => _EPageState();
|
||||
}
|
||||
|
||||
class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
GlobalController globalController = Get.find();
|
||||
UserInfoController userInfoController = Get.find();
|
||||
BlueteethBindController blueteethBindController = Get.find();
|
||||
ThemeController themeController = Get.find();
|
||||
DeviceTypeController deviceTypeController = Get.find();
|
||||
|
||||
ValueNotifier<bool> isPageLoading = ValueNotifier<bool>(true);
|
||||
RxList deviceList = [].obs;
|
||||
RxString finalUri = RxString('');
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true; // 保持页面状态
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getDeviceList();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isPageLoading.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context); // ⚠️必须调用,保证 keepAlive 生效
|
||||
bool isLoggedIn = userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, bodySize) => GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/img/bgNoImg.png'),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: themeController.currentColor.sc17,
|
||||
automaticallyImplyLeading: false,
|
||||
iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||||
titleSpacing: 0,
|
||||
title: Container(
|
||||
width: double.infinity,
|
||||
height: 180.rpx,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'菜单.小e'.tr,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Readex Pro',
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: true,
|
||||
child: isLoggedIn
|
||||
? _buildLoggedInContent()
|
||||
: _buildLoggedOutContent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoggedInContent() {
|
||||
return Obx(() {
|
||||
if (finalUri.isEmpty) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 如果设备列表为空
|
||||
if (deviceList.isEmpty) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
NewTopSlideNotification.show(
|
||||
text: "请先绑定设备".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png", // 可以显示默认背景
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 设备列表不为空,加载 WebView
|
||||
return Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(
|
||||
url: WebUri(finalUri.value +
|
||||
"?t=${DateTime.now().millisecondsSinceEpoch}")),
|
||||
onLoadStart: (controller, url) {
|
||||
isPageLoading.value = true;
|
||||
},
|
||||
onLoadStop: (controller, url) {
|
||||
isPageLoading.value = false;
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: isPageLoading,
|
||||
builder: (context, isLoading, child) {
|
||||
return isLoading
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildLoggedOutContent() {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
NewTopSlideNotification.show(
|
||||
text: "必须登录提示".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
Get.toNamed("/otherLoginPage");
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png",
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> getDeviceList() async {
|
||||
try {
|
||||
BodyDeviceController bodyDeviceController = Get.find();
|
||||
ApiResponse apiResponse =
|
||||
await bodyDeviceController.getDeviceList(isAllDevice: true);
|
||||
|
||||
if (apiResponse.code == HttpStatusCodes.ok) {
|
||||
List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
// 提取 mac 和 person.name
|
||||
List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
String mac = item['mac'] ?? '';
|
||||
String name = (item['person'] != null &&
|
||||
item['person']['name'] != null &&
|
||||
item['person']['name'].toString().trim().isNotEmpty)
|
||||
? item['person']['name'] + "_${mac}"
|
||||
: '体征检测设备'.tr + "_${mac}";
|
||||
return {
|
||||
'mac': mac,
|
||||
'name': name,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
deviceList.value = newList;
|
||||
|
||||
// 拼接参数 person
|
||||
if (deviceList.isNotEmpty) {
|
||||
// JSON 编码整个 deviceList 对象数组
|
||||
String personParam = Uri.encodeComponent(jsonEncode(deviceList));
|
||||
finalUri.value = "${widget.sleepUri}?person=$personParam";
|
||||
} else {
|
||||
finalUri.value = widget.sleepUri;
|
||||
}
|
||||
}
|
||||
String? language = "";
|
||||
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||
if (mhLanguageController.selectLanguage != null) {
|
||||
language = mhLanguageController.selectLanguage.value!.language_code;
|
||||
}
|
||||
} else {
|
||||
if (languageController.selectLanguage != null) {
|
||||
language = languageController.selectLanguage.value!.language_code;
|
||||
}
|
||||
}
|
||||
|
||||
if (language != null && language.isNotEmpty) {
|
||||
if (finalUri.value.contains("?")) {
|
||||
finalUri.value += "&lang=$language";
|
||||
} else {
|
||||
finalUri.value += "?lang=$language";
|
||||
}
|
||||
}
|
||||
ef.log("msg");
|
||||
} catch (e) {
|
||||
ef.log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as io;
|
||||
import 'package:shelf_static/shelf_static.dart';
|
||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
import 'package:vbvs_app/controller/device/device_type_controller.dart';
|
||||
@@ -18,6 +26,7 @@ import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||
import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
import 'package:vbvs_app/model/api_response.dart';
|
||||
|
||||
//本地使用
|
||||
class EPage extends StatefulWidget {
|
||||
final String sleepUri;
|
||||
const EPage({super.key, required this.sleepUri});
|
||||
@@ -34,33 +43,197 @@ class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
DeviceTypeController deviceTypeController = Get.find();
|
||||
|
||||
ValueNotifier<bool> isPageLoading = ValueNotifier<bool>(true);
|
||||
ValueNotifier<bool> isServerStarting = ValueNotifier<bool>(true);
|
||||
ValueNotifier<String> serverError = ValueNotifier<String>('');
|
||||
RxList deviceList = [].obs;
|
||||
RxString finalUri = RxString('');
|
||||
|
||||
// 本地服务器相关
|
||||
HttpServer? _localServer;
|
||||
int _serverPort = 0;
|
||||
String _localServerUrl = '';
|
||||
Directory? _tempExtractDir;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true; // 保持页面状态
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getDeviceList();
|
||||
// 启动本地服务器,成功后获取设备列表
|
||||
_startLocalWebServer().then((success) {
|
||||
if (success) {
|
||||
getDeviceList();
|
||||
} else {
|
||||
isPageLoading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 清理资源
|
||||
_cleanupResources();
|
||||
isPageLoading.dispose();
|
||||
isServerStarting.dispose();
|
||||
serverError.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<bool> _startLocalWebServer() async {
|
||||
try {
|
||||
isServerStarting.value = true;
|
||||
serverError.value = '';
|
||||
|
||||
// 1. 从 assets 加载 ZIP 文件
|
||||
final ByteData zipData;
|
||||
try {
|
||||
zipData = await rootBundle.load('assets/xiaoe/xiaoe_1.0.0.zip');
|
||||
} catch (e) {
|
||||
serverError.value = '找不到小e资源文件: $e';
|
||||
return false;
|
||||
}
|
||||
|
||||
final Uint8List zipBytes = zipData.buffer.asUint8List();
|
||||
|
||||
// 2. 创建临时目录并解压 ZIP
|
||||
_tempExtractDir = await Directory.systemTemp.createTemp('xiaoe_web_');
|
||||
final bool extractSuccess =
|
||||
await _extractZipToDirectory(zipBytes, _tempExtractDir!);
|
||||
|
||||
if (!extractSuccess) {
|
||||
serverError.value = '解压小e资源文件失败';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 查找入口文件 (index.html)
|
||||
final File? entryFile = await _findEntryFile(_tempExtractDir!);
|
||||
if (entryFile == null) {
|
||||
serverError.value = '找不到入口文件 (index.html)';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 创建静态文件处理器
|
||||
final staticHandler = createStaticHandler(
|
||||
_tempExtractDir!.path,
|
||||
defaultDocument: 'index.html',
|
||||
serveFilesOutsidePath: false,
|
||||
);
|
||||
|
||||
// 5. 添加日志中间件(可选)
|
||||
final handler =
|
||||
Pipeline().addMiddleware(logRequests()).addHandler(staticHandler);
|
||||
|
||||
// 6. 启动服务器(端口 0 表示自动分配)
|
||||
_localServer = await io.serve(handler, InternetAddress.loopbackIPv4, 0);
|
||||
_serverPort = _localServer!.port;
|
||||
_localServerUrl = 'http://127.0.0.1:$_serverPort';
|
||||
|
||||
print('小e本地服务启动成功: $_localServerUrl');
|
||||
print('资源目录: ${_tempExtractDir!.path}');
|
||||
|
||||
isServerStarting.value = false;
|
||||
return true;
|
||||
} catch (e, stackTrace) {
|
||||
serverError.value = '启动本地服务器失败: $e';
|
||||
print('服务器启动错误: $e\n$stackTrace');
|
||||
isServerStarting.value = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _extractZipToDirectory(
|
||||
Uint8List zipBytes, Directory targetDir) async {
|
||||
try {
|
||||
// 解码 ZIP 文件
|
||||
final Archive archive = ZipDecoder().decodeBytes(zipBytes);
|
||||
|
||||
for (final ArchiveFile file in archive) {
|
||||
final String filename = file.name;
|
||||
|
||||
// 跳过目录条目(它们会通过文件创建自动生成)
|
||||
if (file.isFile) {
|
||||
// 确保目录存在
|
||||
final String filePath = p.join(targetDir.path, filename);
|
||||
final File outputFile = File(filePath);
|
||||
await outputFile.parent.create(recursive: true);
|
||||
|
||||
// 写入文件内容
|
||||
final List<int> data = file.content as List<int>;
|
||||
await outputFile.writeAsBytes(data, flush: true);
|
||||
}
|
||||
}
|
||||
|
||||
print('解压完成,文件数量: ${archive.files.length}');
|
||||
return true;
|
||||
} catch (e, stackTrace) {
|
||||
print('解压失败: $e\n$stackTrace');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<File?> _findEntryFile(Directory dir) async {
|
||||
// 优先查找 index.html
|
||||
final List<String> possibleEntries = [
|
||||
'index.html',
|
||||
'Index.html',
|
||||
'INDEX.HTML',
|
||||
'main.html',
|
||||
'default.html',
|
||||
];
|
||||
|
||||
for (final entry in possibleEntries) {
|
||||
final File file = File(p.join(dir.path, entry));
|
||||
if (await file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到标准入口,查找任何 .html 文件
|
||||
try {
|
||||
final List<FileSystemEntity> entities =
|
||||
await dir.list(recursive: false).toList();
|
||||
for (final entity in entities) {
|
||||
if (entity is File && entity.path.toLowerCase().endsWith('.html')) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('查找入口文件失败: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void _cleanupResources() {
|
||||
// 关闭服务器
|
||||
if (_localServer != null) {
|
||||
_localServer!.close();
|
||||
_localServer = null;
|
||||
print('本地服务器已关闭');
|
||||
}
|
||||
|
||||
// 清理临时目录
|
||||
if (_tempExtractDir != null && _tempExtractDir!.existsSync()) {
|
||||
try {
|
||||
_tempExtractDir!.deleteSync(recursive: true);
|
||||
print('临时目录已清理: ${_tempExtractDir!.path}');
|
||||
} catch (e) {
|
||||
print('清理临时目录失败: $e');
|
||||
}
|
||||
_tempExtractDir = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context); // ⚠️必须调用,保证 keepAlive 生效
|
||||
super.build(context);
|
||||
bool isLoggedIn = userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, bodySize) => GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/img/bgNoImg.png'),
|
||||
fit: BoxFit.fill,
|
||||
@@ -104,76 +277,175 @@ class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
|
||||
Widget _buildLoggedInContent() {
|
||||
return Obx(() {
|
||||
if (finalUri.isEmpty) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isServerStarting,
|
||||
builder: (context, isStarting, child) {
|
||||
if (isStarting) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.rpx),
|
||||
Text(
|
||||
'正在启动小e服务...'.tr,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 28.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 如果设备列表为空
|
||||
if (deviceList.isEmpty) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
TopSlideNotification.show(
|
||||
context,
|
||||
text: "请先绑定设备".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png", // 可以显示默认背景
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 设备列表不为空,加载 WebView
|
||||
return Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(
|
||||
url: WebUri(finalUri.value +
|
||||
"?t=${DateTime.now().millisecondsSinceEpoch}")),
|
||||
onLoadStart: (controller, url) {
|
||||
isPageLoading.value = true;
|
||||
},
|
||||
onLoadStop: (controller, url) {
|
||||
isPageLoading.value = false;
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: isPageLoading,
|
||||
builder: (context, isLoading, child) {
|
||||
return isLoading
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
return ValueListenableBuilder<String>(
|
||||
valueListenable: serverError,
|
||||
builder: (context, error, child) {
|
||||
if (error.isNotEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40.rpx),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.red,
|
||||
size: 60.rpx,
|
||||
),
|
||||
SizedBox(height: 20.rpx),
|
||||
Text(
|
||||
'服务启动失败'.tr,
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 32.rpx,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
SizedBox(height: 12.rpx),
|
||||
Text(
|
||||
error,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: themeController.currentColor.sc3,
|
||||
fontSize: 26.rpx,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.rpx),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
serverError.value = '';
|
||||
_startLocalWebServer().then((success) {
|
||||
if (success) {
|
||||
getDeviceList();
|
||||
}
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: themeController.currentColor.sc1,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32.rpx,
|
||||
vertical: 16.rpx,
|
||||
),
|
||||
),
|
||||
child: Text('重试'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
if (finalUri.isEmpty) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (deviceList.isEmpty) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
NewTopSlideNotification.show(
|
||||
text: "请先绑定设备".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
"assets/img/xiaoe.png",
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(
|
||||
url: WebUri(finalUri.value),
|
||||
),
|
||||
onLoadStart: (controller, url) {
|
||||
isPageLoading.value = true;
|
||||
print('WebView 开始加载: $url');
|
||||
},
|
||||
onLoadStop: (controller, url) {
|
||||
isPageLoading.value = false;
|
||||
print('WebView 加载完成: $url');
|
||||
},
|
||||
onLoadError: (controller, url, code, message) {
|
||||
isPageLoading.value = false;
|
||||
print('WebView 加载错误: $message (code: $code)');
|
||||
serverError.value = '页面加载失败: $message';
|
||||
},
|
||||
onWebViewCreated: (controller) {
|
||||
// 启用调试(仅在开发模式下)
|
||||
// if (!kReleaseMode) {
|
||||
// controller.setWebContentsDebuggingEnabled(true);
|
||||
// }
|
||||
},
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: isPageLoading,
|
||||
builder: (context, isLoading, child) {
|
||||
return isLoading
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
themeController.currentColor.sc1,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoggedOutContent() {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
TopSlideNotification.show(
|
||||
context,
|
||||
NewTopSlideNotification.show(
|
||||
text: "必须登录提示".tr,
|
||||
textColor: themeController.currentColor.sc9,
|
||||
);
|
||||
@@ -197,7 +469,6 @@ class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
if (apiResponse.code == HttpStatusCodes.ok) {
|
||||
List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
// 提取 mac 和 person.name
|
||||
List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
String mac = item['mac'] ?? '';
|
||||
String name = (item['person'] != null &&
|
||||
@@ -213,36 +484,46 @@ class _EPageState extends State<EPage> with AutomaticKeepAliveClientMixin {
|
||||
|
||||
deviceList.value = newList;
|
||||
|
||||
// 拼接参数 person
|
||||
if (deviceList.isNotEmpty) {
|
||||
// JSON 编码整个 deviceList 对象数组
|
||||
String personParam = Uri.encodeComponent(jsonEncode(deviceList));
|
||||
finalUri.value = "${widget.sleepUri}?person=$personParam";
|
||||
} else {
|
||||
finalUri.value = widget.sleepUri;
|
||||
}
|
||||
}
|
||||
String? language = "";
|
||||
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||
if (mhLanguageController.selectLanguage != null) {
|
||||
language = mhLanguageController.selectLanguage.value!.language_code;
|
||||
}
|
||||
} else {
|
||||
if (languageController.selectLanguage != null) {
|
||||
language = languageController.selectLanguage.value!.language_code;
|
||||
}
|
||||
}
|
||||
// 构建最终 URL
|
||||
String baseUrl = _localServerUrl;
|
||||
String queryParams = '?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
if (language != null && language.isNotEmpty) {
|
||||
if (finalUri.value.contains("?")) {
|
||||
finalUri.value += "&lang=$language";
|
||||
} else {
|
||||
finalUri.value += "?lang=$language";
|
||||
if (deviceList.isNotEmpty) {
|
||||
String personParam = Uri.encodeComponent(jsonEncode(deviceList));
|
||||
queryParams += '&person=$personParam';
|
||||
}
|
||||
|
||||
// 添加语言参数
|
||||
String? language = "";
|
||||
// 这里保持你原来的语言逻辑
|
||||
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||
// 假设你有 mhLanguageController
|
||||
// if (mhLanguageController.selectLanguage != null) {
|
||||
// language = mhLanguageController.selectLanguage.value!.language_code;
|
||||
// }
|
||||
} else {
|
||||
// 假设你有 languageController
|
||||
// if (languageController.selectLanguage != null) {
|
||||
// language = languageController.selectLanguage.value!.language_code;
|
||||
// }
|
||||
}
|
||||
|
||||
if (language != null && language.isNotEmpty) {
|
||||
queryParams += '&lang=$language';
|
||||
}
|
||||
|
||||
finalUri.value = baseUrl + queryParams;
|
||||
print('最终加载 URL: ${finalUri.value}');
|
||||
} else {
|
||||
// API 调用失败,使用基础 URL
|
||||
finalUri.value =
|
||||
'$_localServerUrl?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
ef.log("msg");
|
||||
} catch (e) {
|
||||
ef.log(e.toString());
|
||||
print('获取设备列表失败: $e');
|
||||
// 出错时仍然加载基础页面
|
||||
finalUri.value =
|
||||
'$_localServerUrl?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
771
lib/pages/main_bottom/xiaoe/XiaoeModel.dart
Normal file
771
lib/pages/main_bottom/xiaoe/XiaoeModel.dart
Normal file
@@ -0,0 +1,771 @@
|
||||
// // lib/controller/xiaoe/xiaoe_controller.dart
|
||||
// import 'dart:async';
|
||||
|
||||
// import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
||||
// import 'package:easyweb/base/minisdk.dart';
|
||||
// import 'package:easyweb/easyweb.dart';
|
||||
// import 'package:ef/ef.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
// import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
// import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
// import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
// import 'package:vbvs_app/model/api_response.dart';
|
||||
// import 'package:vbvs_app/pages/main_bottom/component/main_page_b_bottom_change.dart';
|
||||
|
||||
// class XiaoeModel {
|
||||
// XiaoeModel();
|
||||
// }
|
||||
|
||||
// class XiaoeController extends GetControllerEx<XiaoeModel> {
|
||||
// XiaoeController() : super(XiaoeModel()) {
|
||||
// web = WebviewHelper(
|
||||
// isheadless: false,
|
||||
// jsbridge: buildsdk(
|
||||
// father: this,
|
||||
// ),
|
||||
// settings: buildsettings(),
|
||||
// params: PlatformHeadlessInAppWebViewCreationParams(
|
||||
// onLoadStop: (controller, url) {
|
||||
// setState(() {
|
||||
// ready.value = true;
|
||||
// });
|
||||
// },
|
||||
// onLoadResource: (controller, resource) {
|
||||
// // 资源加载回调
|
||||
// ef.log("msg");
|
||||
// loadRecource.value = true;
|
||||
// updateAll();
|
||||
// },
|
||||
// initialUrlRequest:
|
||||
// URLRequest(url: WebUri("http://127.0.0.1/index.html?"))),
|
||||
// );
|
||||
// }
|
||||
|
||||
// // 变量与WebviewTestController保持一致
|
||||
// var selectDevice = {};
|
||||
// var lastSelectDevice = {};
|
||||
// var bluetooth = 0;
|
||||
// List personList = [];
|
||||
// List instantData = [];
|
||||
// List deviceList = []; // 小e需要的设备列表
|
||||
// RxBool initFlag = false.obs;
|
||||
// Timer? _resourceLoadTimer;
|
||||
// Widget webviewWidget = Container();
|
||||
// var wifiResponseData;
|
||||
|
||||
// late WebviewHelper web;
|
||||
// var ready = false.obs;
|
||||
// var cnt = 0.obs;
|
||||
// var loadRecource = false.obs;
|
||||
// int sourceTime = 0;
|
||||
|
||||
// // 获取设备列表
|
||||
// Future<void> getDeviceList() async {
|
||||
// try {
|
||||
// BodyDeviceController bodyDeviceController = Get.find();
|
||||
// ApiResponse apiResponse =
|
||||
// await bodyDeviceController.getDeviceList(isAllDevice: true);
|
||||
|
||||
// if (apiResponse.code == 200) {
|
||||
// List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
// // 处理设备列表数据
|
||||
// List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
// String mac = item['mac']?.toString() ?? '';
|
||||
// String name = '';
|
||||
|
||||
// if (item['person'] != null &&
|
||||
// item['person']['name'] != null &&
|
||||
// item['person']['name'].toString().trim().isNotEmpty) {
|
||||
// name = '${item['person']['name']}_$mac';
|
||||
// } else {
|
||||
// name = '${'体征检测设备'.tr}_$mac';
|
||||
// }
|
||||
|
||||
// return {
|
||||
// 'mac': mac,
|
||||
// 'name': name,
|
||||
// 'deviceType': item['device_type'] ?? '',
|
||||
// 'person': item['person'] ?? {},
|
||||
// 'rawData': item,
|
||||
// };
|
||||
// }).toList();
|
||||
|
||||
// deviceList = newList;
|
||||
// ef.log("小e获取到${deviceList.length}个设备");
|
||||
// }
|
||||
// } catch (e) {
|
||||
// ef.log("小e获取设备列表错误: $e");
|
||||
// }
|
||||
// }
|
||||
|
||||
// Future<void> startXiaoe() async {
|
||||
// try {
|
||||
// ef.log('开始启动小e应用...');
|
||||
|
||||
// // 先获取设备列表
|
||||
// await getDeviceList();
|
||||
|
||||
// ef.log('开始运行小e Web应用...');
|
||||
// // 运行小e应用
|
||||
// var x1 = await ef.kvRoot.appmanger.find("xiaoe");
|
||||
// var x2 = await ef.kvRoot.appmanger.find("mhtControl");
|
||||
// print("$x1");
|
||||
// print("$x2");
|
||||
// await web.runApp('xiaoe').then((x) {
|
||||
// ef.log('web.runApp 执行完成,结果: $x');
|
||||
// ready.value = true;
|
||||
// ef.log('ready.value 设置为 true');
|
||||
|
||||
// // 设置资源加载超时定时器
|
||||
// _resourceLoadTimer = Timer(Duration(seconds: 15), () {
|
||||
// ef.log('资源加载超时定时器触发,当前 loadRecource.value = ${loadRecource.value}');
|
||||
// if (!loadRecource.value) {
|
||||
// loadRecource.value = true;
|
||||
// updateAll();
|
||||
// ef.log('小e资源加载超时,强制显示页面');
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// } catch (e, s) {
|
||||
// ef.log('启动小e错误: $e, $s');
|
||||
// ef.log('错误堆栈: $s');
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void onInit() {
|
||||
// super.onInit();
|
||||
// ef.log("小e控制器初始化 =>${DateTime.now()}");
|
||||
|
||||
// // 延迟启动
|
||||
// Future.delayed(Duration(milliseconds: 300), () {
|
||||
// startXiaoe();
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void onClose() {
|
||||
// _resourceLoadTimer?.cancel();
|
||||
// super.onClose();
|
||||
// }
|
||||
// }
|
||||
|
||||
// class XiaoeView extends GetComponent<XiaoeController> {
|
||||
// XiaoeView({super.key, super.oncreate});
|
||||
|
||||
// @override
|
||||
// XiaoeController newinstance() {
|
||||
// if (ef.kvRoot.XiaoeController == null) {
|
||||
// ef.kvRoot.XiaoeController = XiaoeController();
|
||||
// if (Get.isRegistered<XiaoeController>() == false) {
|
||||
// Get.put<XiaoeController>(ef.kvRoot.XiaoeController);
|
||||
// XiaoeController xiaoeController = Get.find();
|
||||
// xiaoeController.global = true;
|
||||
// return xiaoeController;
|
||||
// }
|
||||
// }
|
||||
// return ef.kvRoot.XiaoeController;
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Obx(() {
|
||||
// UserInfoController userInfoController = Get.find();
|
||||
// BodyDeviceController bodyDeviceController = Get.find();
|
||||
// final isLoggedIn =
|
||||
// userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
// final hasDevice = bodyDeviceController.deviceList.isNotEmpty;
|
||||
|
||||
// final showWeb = isLoggedIn && hasDevice;
|
||||
// final mainAxisAlignment =
|
||||
// showWeb ? MainAxisAlignment.start : MainAxisAlignment.center;
|
||||
|
||||
// return Scaffold(
|
||||
// backgroundColor: Colors.transparent,
|
||||
// body: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// mainAxisAlignment: mainAxisAlignment,
|
||||
// children: [
|
||||
// // 未登录提示
|
||||
// if (!isLoggedIn)
|
||||
// Center(
|
||||
// child: InkWell(
|
||||
// onTap: () => Get.toNamed("/loginPage"),
|
||||
// child: Container(
|
||||
// padding: EdgeInsets.symmetric(
|
||||
// vertical: 20.rpx, horizontal: 40.rpx),
|
||||
// child: RichText(
|
||||
// text: TextSpan(
|
||||
// children: [
|
||||
// TextSpan(
|
||||
// text: "请先".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// WidgetSpan(
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// Text(
|
||||
// "登录".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.blue,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// Positioned(
|
||||
// bottom: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: Container(
|
||||
// height: 1,
|
||||
// color: Colors.blue,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: "后,再使用小e".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // 已登录但无设备提示
|
||||
// if (isLoggedIn && !hasDevice)
|
||||
// Center(
|
||||
// child: InkWell(
|
||||
// onTap: () {},
|
||||
// child: Container(
|
||||
// padding: EdgeInsets.symmetric(
|
||||
// vertical: 20.rpx, horizontal: 40.rpx),
|
||||
// child: RichText(
|
||||
// text: TextSpan(
|
||||
// children: [
|
||||
// TextSpan(
|
||||
// text: "请先".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// WidgetSpan(
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// Text(
|
||||
// "绑定设备".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.blue,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// Positioned(
|
||||
// bottom: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: Container(
|
||||
// height: 1,
|
||||
// color: Colors.blue,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: "后,再使用小e".tr,
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 30.rpx,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// // 显示 WebView
|
||||
// if (showWeb)
|
||||
// Expanded(
|
||||
// child: Align(
|
||||
// alignment: Alignment.topLeft,
|
||||
// child: Obx(() {
|
||||
// return Stack(
|
||||
// children: [
|
||||
// controller.ready.value
|
||||
// ? controller.web.build()
|
||||
// : Container(),
|
||||
// if (!controller.loadRecource.value)
|
||||
// Positioned.fill(
|
||||
// child: Container(
|
||||
// decoration: BoxDecoration(
|
||||
// image: DecorationImage(
|
||||
// image: AssetImage(
|
||||
// "assets/img/xiaoe.png",
|
||||
// ),
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ),
|
||||
// alignment: Alignment.center,
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// CircularProgressIndicator(
|
||||
// valueColor: AlwaysStoppedAnimation<Color>(
|
||||
// Colors.white),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// lib/controller/xiaoe/xiaoe_controller.dart
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:easyweb/base/minisdk.dart';
|
||||
import 'package:easyweb/easyweb.dart';
|
||||
import 'package:ef/ef.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||
import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||
import 'package:vbvs_app/model/api_response.dart';
|
||||
|
||||
class XiaoeModel {
|
||||
XiaoeModel();
|
||||
}
|
||||
|
||||
class XiaoeController extends GetControllerEx<XiaoeModel> {
|
||||
XiaoeController() : super(XiaoeModel()) {
|
||||
// 初始URL为空,在获取设备列表后设置
|
||||
initialUrl = "";
|
||||
// web = WebviewHelper(
|
||||
// isheadless: false,
|
||||
// jsbridge: buildsdk(
|
||||
// father: this,
|
||||
// ),
|
||||
// settings: buildsettings(),
|
||||
// params: PlatformHeadlessInAppWebViewCreationParams(
|
||||
// onLoadStop: (controller, url) {
|
||||
// setState(() {
|
||||
// ready.value = true;
|
||||
// });
|
||||
// },
|
||||
// onLoadResource: (controller, resource) {
|
||||
// // 资源加载回调
|
||||
// ef.log("msg");
|
||||
// loadRecource.value = true;
|
||||
// updateAll();
|
||||
// },
|
||||
// initialUrlRequest: URLRequest(url: WebUri(initialUrl)),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
String initialUrl = ""; // 存储初始URL
|
||||
|
||||
// 变量与WebviewTestController保持一致
|
||||
var selectDevice = {};
|
||||
var lastSelectDevice = {};
|
||||
var bluetooth = 0;
|
||||
List personList = [];
|
||||
List instantData = [];
|
||||
List deviceList = []; // 小e需要的设备列表
|
||||
RxBool initFlag = false.obs;
|
||||
Timer? _resourceLoadTimer;
|
||||
Widget webviewWidget = Container();
|
||||
var wifiResponseData;
|
||||
|
||||
late WebviewHelper web;
|
||||
var ready = false.obs;
|
||||
var cnt = 0.obs;
|
||||
var loadRecource = false.obs;
|
||||
int sourceTime = 0;
|
||||
|
||||
bool _isStarting = false; // 添加启动标志
|
||||
|
||||
// 构建最终的URL
|
||||
String _buildFinalUrl(String baseUrl) {
|
||||
String queryParams = '?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
if (deviceList.isNotEmpty) {
|
||||
// 提取小e需要的格式
|
||||
List<Map<String, dynamic>> xiaoeDeviceList = deviceList.map((device) {
|
||||
return {
|
||||
'mac': device['mac'] ?? '',
|
||||
'name': device['name'] ?? '',
|
||||
};
|
||||
}).toList();
|
||||
|
||||
String personParam = Uri.encodeComponent(jsonEncode(xiaoeDeviceList));
|
||||
queryParams += '&person=$personParam';
|
||||
}
|
||||
|
||||
// 添加语言参数(根据你的实际逻辑调整)
|
||||
String? language = "";
|
||||
// 这里保持你原来的语言逻辑
|
||||
// if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||
// // 假设你有 mhLanguageController
|
||||
// // if (mhLanguageController.selectLanguage != null) {
|
||||
// // language = mhLanguageController.selectLanguage.value!.language_code;
|
||||
// // }
|
||||
// } else {
|
||||
// // 假设你有 languageController
|
||||
// // if (languageController.selectLanguage != null) {
|
||||
// // language = languageController.selectLanguage.value!.language_code;
|
||||
// // }
|
||||
// }
|
||||
|
||||
if (language != null && language.isNotEmpty) {
|
||||
queryParams += '&lang=$language';
|
||||
}
|
||||
|
||||
return baseUrl + queryParams;
|
||||
}
|
||||
|
||||
// 获取设备列表并构建URL
|
||||
Future<void> getDeviceListAndBuildUrl() async {
|
||||
try {
|
||||
BodyDeviceController bodyDeviceController = Get.find();
|
||||
ApiResponse apiResponse =
|
||||
await bodyDeviceController.getDeviceList(isAllDevice: true);
|
||||
|
||||
if (apiResponse.code == 1) {
|
||||
List<dynamic> rawList = apiResponse.data;
|
||||
|
||||
// 处理设备列表数据
|
||||
List<Map<String, dynamic>> newList = rawList.map((item) {
|
||||
String mac = item['mac']?.toString() ?? '';
|
||||
String name = '';
|
||||
|
||||
if (item['person'] != null &&
|
||||
item['person']['name'] != null &&
|
||||
item['person']['name'].toString().trim().isNotEmpty) {
|
||||
name = '${item['person']['name']}_$mac';
|
||||
} else {
|
||||
name = '${'体征检测设备'.tr}_$mac';
|
||||
}
|
||||
|
||||
return {
|
||||
'mac': mac,
|
||||
'name': name,
|
||||
'deviceType': item['device_type'] ?? '',
|
||||
'person': item['person'] ?? {},
|
||||
'rawData': item,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
deviceList = newList;
|
||||
ef.log("小e获取到${deviceList.length}个设备");
|
||||
|
||||
// 构建最终的URL
|
||||
String baseUrl = "http://127.0.0.1/index.html";
|
||||
initialUrl = _buildFinalUrl(baseUrl);
|
||||
ef.log("小e初始URL: $initialUrl");
|
||||
} else {
|
||||
// API调用失败时使用基础URL
|
||||
String baseUrl = "http://127.0.0.1/index.html";
|
||||
initialUrl = baseUrl + '?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
ef.log("获取设备列表失败,使用基础URL: $initialUrl");
|
||||
}
|
||||
} catch (e) {
|
||||
ef.log("小e获取设备列表错误: $e");
|
||||
// 出错时使用基础URL
|
||||
String baseUrl = "http://127.0.0.1/index.html";
|
||||
initialUrl = baseUrl + '?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> startXiaoe() async {
|
||||
try {
|
||||
if (_isStarting) {
|
||||
ef.log('小e已经在启动中,跳过');
|
||||
return;
|
||||
}
|
||||
_isStarting = true; // 标记为启动中
|
||||
ef.log('开始启动小e应用...');
|
||||
|
||||
// 先获取设备列表并构建URL
|
||||
await getDeviceListAndBuildUrl();
|
||||
|
||||
// 重新创建WebviewHelper,传入新的URL
|
||||
web = WebviewHelper(
|
||||
isheadless: false,
|
||||
jsbridge: buildsdk(father: this),
|
||||
settings: buildsettings(),
|
||||
params: PlatformHeadlessInAppWebViewCreationParams(
|
||||
onLoadStop: (controller, url) {
|
||||
setState(() {
|
||||
ready.value = true;
|
||||
});
|
||||
},
|
||||
onLoadResource: (controller, resource) {
|
||||
ef.log("msg");
|
||||
loadRecource.value = true;
|
||||
updateAll();
|
||||
},
|
||||
initialUrlRequest: URLRequest(url: WebUri(initialUrl)),
|
||||
),
|
||||
);
|
||||
|
||||
ef.log('开始运行小e Web应用...');
|
||||
|
||||
|
||||
|
||||
await web.runApp('xiaoe').then((x) {
|
||||
ef.log('web.runApp 执行完成,结果: $x');
|
||||
ready.value = true;
|
||||
ef.log('ready.value 设置为 true');
|
||||
|
||||
// 设置资源加载超时定时器
|
||||
// _resourceLoadTimer = Timer(Duration(seconds: 15), () {
|
||||
// ef.log('资源加载超时定时器触发,当前 loadRecource.value = ${loadRecource.value}');
|
||||
// if (!loadRecource.value) {
|
||||
// loadRecource.value = true;
|
||||
// updateAll();
|
||||
// ef.log('小e资源加载超时,强制显示页面');
|
||||
// }
|
||||
// });
|
||||
});
|
||||
} catch (e, s) {
|
||||
ef.log('启动小e错误: $e, $s');
|
||||
ef.log('错误堆栈: $s');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
ef.log("小e控制器初始化 =>${DateTime.now()}");
|
||||
|
||||
// 延迟启动
|
||||
Future.delayed(Duration(milliseconds: 300), () {
|
||||
startXiaoe();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_resourceLoadTimer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
class XiaoeView extends GetComponent<XiaoeController> {
|
||||
XiaoeView({super.key, super.oncreate});
|
||||
|
||||
@override
|
||||
XiaoeController newinstance() {
|
||||
if (ef.kvRoot.XiaoeController == null) {
|
||||
ef.kvRoot.XiaoeController = XiaoeController();
|
||||
if (Get.isRegistered<XiaoeController>() == false) {
|
||||
Get.put<XiaoeController>(ef.kvRoot.XiaoeController);
|
||||
XiaoeController xiaoeController = Get.find();
|
||||
xiaoeController.global = true;
|
||||
return xiaoeController;
|
||||
}
|
||||
}
|
||||
return ef.kvRoot.XiaoeController;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
UserInfoController userInfoController = Get.find();
|
||||
BodyDeviceController bodyDeviceController = Get.find();
|
||||
final isLoggedIn =
|
||||
userInfoController.model.login == LoginStatus.LOGIN.code;
|
||||
|
||||
final hasDevice = bodyDeviceController.deviceList.isNotEmpty;
|
||||
|
||||
final showWeb = isLoggedIn && hasDevice;
|
||||
final mainAxisAlignment =
|
||||
showWeb ? MainAxisAlignment.start : MainAxisAlignment.center;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
children: [
|
||||
// 未登录提示
|
||||
if (!isLoggedIn)
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () => Get.toNamed("/loginPage"),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 20.rpx, horizontal: 40.rpx),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "请先".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Stack(
|
||||
children: [
|
||||
Text(
|
||||
"登录".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "后,再使用小e".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 已登录但无设备提示
|
||||
if (isLoggedIn && !hasDevice)
|
||||
Center(
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 20.rpx, horizontal: 40.rpx),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "请先".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Stack(
|
||||
children: [
|
||||
Text(
|
||||
"绑定设备".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: "后,再使用小e".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 30.rpx,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 显示 WebView
|
||||
if (showWeb)
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() {
|
||||
return Stack(
|
||||
children: [
|
||||
controller.ready.value
|
||||
? controller.web.build()
|
||||
: Container(),
|
||||
if (!controller.loadRecource.value)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
"assets/img/xiaoe.png",
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user