diff --git a/lib/common/utils/logger/logger_tool.dart b/lib/common/utils/logger/logger_tool.dart index 349261a..b1e55a3 100644 --- a/lib/common/utils/logger/logger_tool.dart +++ b/lib/common/utils/logger/logger_tool.dart @@ -8,6 +8,7 @@ import 'package:logger/logger.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; final logger = Logger( + filter: AppLogFilter(), printer: customPrinter, output: ConsoleOutput(), ); @@ -18,38 +19,58 @@ final customPrinter = PrettyPrinter( colors: true, printEmojis: false, printTime: true, + noBoxingByDefault: true, ); +class AppLogFilter extends LogFilter { + @override + bool shouldLog(LogEvent event) { + return true; + } +} + class ConsoleOutput extends LogOutput { String? _filePath; IOSink? _sink; Timer? _timer; + Future _queue = Future.value(); + + Future _enqueue(Future Function() action) { + _queue = _queue.then((_) => action()).catchError((e, stack) { + print("写入日志错误error:|$e|stack:|$stack|"); + }); + return _queue; + } @override Future init() async { try { - // 关闭旧的sink并创建新的 - await _sink?.flush(); - await _sink?.close(); - _filePath = await AppFileTool.getLoggerBackupPath(); - if (ObjectUtil.isNotEmpty(_filePath)) { - _sink = File(_filePath!).openWrite( - mode: FileMode.writeOnlyAppend, - encoding: utf8, - ); - } + await _enqueue(() async { + await _sink?.flush(); + await _sink?.close(); + _filePath = await AppFileTool.getLoggerBackupPath(); + if (ObjectUtil.isNotEmpty(_filePath)) { + _sink = File(_filePath!).openWrite( + mode: FileMode.writeOnlyAppend, + encoding: utf8, + ); + } else { + _sink = null; + } + }); _timer = Timer.periodic(const Duration(seconds: 60 * 60 * 2), (Timer timer) async { final path = await AppFileTool.getLoggerBackupPath(); if (path == null) return; - // 关闭旧的sink并创建新的 - await _sink?.flush(); - await _sink?.close(); - _filePath = path; - _sink = File(_filePath!).openWrite( - mode: FileMode.writeOnlyAppend, - encoding: utf8, - ); + unawaited(_enqueue(() async { + await _sink?.flush(); + await _sink?.close(); + _filePath = path; + _sink = File(_filePath!).openWrite( + mode: FileMode.writeOnlyAppend, + encoding: utf8, + ); + })); }); } catch (e, stack) { print("初始化日志错误error:|$e|stack:|$stack|"); @@ -58,9 +79,13 @@ class ConsoleOutput extends LogOutput { @override Future destroy() async { - await _sink?.flush(); - await _sink?.close(); _timer?.cancel(); + _timer = null; + await _enqueue(() async { + await _sink?.flush(); + await _sink?.close(); + _sink = null; + }); } @override @@ -70,12 +95,14 @@ class ConsoleOutput extends LogOutput { for (final v in event.lines) { print(v); } - } else { - if (_filePath == null) return; - if (_sink == null) return; - if (Level.debug == event.level) return; - _sink?.writeAll(event.lines, '\n'); } + unawaited(_enqueue(() async { + if (_filePath == null || _sink == null) return; + if (Level.debug == event.level) return; + _sink!.writeAll(event.lines, '\n'); + _sink!.writeln(); + await _sink!.flush(); + })); } catch (e, stack) { print("写入日志错误error:|$e|stack:|$stack|"); } diff --git a/lib/common/utils/sync/sync_check_tool.dart b/lib/common/utils/sync/sync_check_tool.dart index 9d5c945..f7e4c38 100644 --- a/lib/common/utils/sync/sync_check_tool.dart +++ b/lib/common/utils/sync/sync_check_tool.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:mqtt_client/mqtt_client.dart'; @@ -44,6 +45,7 @@ class SyncCheckTool { checkPrint(); checkBroadcast(); checkDeviceStatus(); + checkExpiredLog(); } void dispose() { @@ -156,4 +158,54 @@ class SyncCheckTool { } }); } + + // 检查过期日志(超过3个月的日志文件都要删除) + checkExpiredLog() async { + const logMsg = "检查过期日志"; + try { + final backupPath = await AppFileTool.getLoggerBackupPath(); + if (backupPath == null || backupPath.isEmpty) { + logger.w("|$_tag|$logMsg,日志备份路径为空"); + return; + } + final loggerBackupDir = Directory(backupPath).parent.parent; + if (!await loggerBackupDir.exists()) { + logger.i("|$_tag|$logMsg,日志目录不存在 path:${loggerBackupDir.path}"); + return; + } + final expireBefore = DateTime( + DateTime.now().year, + DateTime.now().month - 3, + DateTime.now().day, + ); + final entities = loggerBackupDir.listSync(recursive: true); + int deleteCount = 0; + for (final entity in entities) { + if (entity is! File) continue; + if (!entity.path.endsWith(".log")) continue; + final stat = await entity.stat(); + if (stat.modified.isBefore(expireBefore)) { + await entity.delete(); + deleteCount++; + logger.i("|$_tag|$logMsg,删除过期日志 path:${entity.path}"); + } + } + final monthDirs = loggerBackupDir + .listSync() + .whereType() + .toList() + ..sort((a, b) => b.path.compareTo(a.path)); + for (final dir in monthDirs) { + if (!await dir.exists()) continue; + final isEmpty = dir.listSync().isEmpty; + if (isEmpty) { + await dir.delete(); + logger.i("|$_tag|$logMsg,删除空目录 path:${dir.path}"); + } + } + logger.i("|$_tag|$logMsg,执行完成,删除日志数量:$deleteCount"); + } catch (e, stack) { + logger.e("|$_tag|$logMsg,执行失败,error:$e,stack:$stack"); + } + } } diff --git a/lib/common/widgets/app/terminal_shell.dart b/lib/common/widgets/app/terminal_shell.dart index e3d6ad2..156c4d0 100644 --- a/lib/common/widgets/app/terminal_shell.dart +++ b/lib/common/widgets/app/terminal_shell.dart @@ -92,6 +92,7 @@ class TerminalHeader extends StatelessWidget { @override Widget build(BuildContext context) { final theme = FTheme.of(context); + final menuController = Get.find(); return Container( height: 80.h, padding: EdgeInsets.symmetric(horizontal: 20.w), @@ -108,7 +109,17 @@ class TerminalHeader extends StatelessWidget { child: IntrinsicWidth( child: Row( children: [ - const TerminalBrand(), + Obx(() { + final soleMenu = menuController.soleVisibleBusinessMenu; + return TerminalBrand( + targetMenu: soleMenu, + onTap: soleMenu == null + ? null + : () => TerminalNavigationTool.open( + _terminalNavItem(soleMenu).route, + ), + ); + }), SizedBox(width: 20.w), Container( width: 1, @@ -192,12 +203,15 @@ class TerminalHeader extends StatelessWidget { /// 这里使用受约束的 `FittedBox` 保留 React 原型中的斜体、紧字距效果,同时避免 /// 大字号设置把品牌挤出 80 高的终端顶栏。 class TerminalBrand extends StatelessWidget { - const TerminalBrand({super.key}); + const TerminalBrand({super.key, this.targetMenu, this.onTap}); + + final TerminalMenuItemType? targetMenu; + final VoidCallback? onTap; @override Widget build(BuildContext context) { final theme = FTheme.of(context); - return SizedBox( + final brand = SizedBox( width: 100.w, height: 55.h, child: FittedBox( @@ -214,6 +228,22 @@ class TerminalBrand extends StatelessWidget { ), ), ); + if (onTap == null || targetMenu == null) return brand; + + // 侧栏因仅剩一个业务菜单而收起时,品牌作为返回该入口的固定兜底; + // 实际跳转仍交给统一导航守卫处理绑定、联网和设置密码校验。 + return Semantics( + button: true, + label: '返回${targetMenu!.title}', + child: Tooltip( + message: '返回${targetMenu!.title}', + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius), + child: brand, + ), + ), + ); } } diff --git a/lib/pages/terminal/setting/face_setting_controller.dart b/lib/pages/terminal/setting/face_setting_controller.dart index cb91f84..86665ff 100644 --- a/lib/pages/terminal/setting/face_setting_controller.dart +++ b/lib/pages/terminal/setting/face_setting_controller.dart @@ -120,11 +120,11 @@ class FaceSettingController extends GetxController { } } - Future resetRecognitionConfiguration() async { + Future restoreDefaultRecognitionConfiguration() async { if (isSavingRecognitionConfiguration.value) return; final confirmed = await appMessageDialog( Get.context!, - '是否恢复默认人脸识别策略?活体检测将恢复为关闭,相似度阈值恢复为 80%。', + '是否还原为默认人脸识别策略?活体检测将关闭,相似度阈值将还原为 80%。', ); if (!confirmed) return; isSavingRecognitionConfiguration.value = true; @@ -135,23 +135,19 @@ class FaceSettingController extends GetxController { final active = Get.isRegistered() && Get.find().isActive; Toast.success( - msg: active ? '已恢复默认配置,将在当前识别结束后生效' : '已恢复默认人脸识别配置', + msg: active ? '已还原默认配置,将在当前识别结束后生效' : '已还原默认人脸识别配置', ); } on PlatformException catch (e, stack) { - logger.e('|$_tag|恢复识别策略失败error:|$e|stack:|$stack|'); - Toast.error(msg: e.message ?? '恢复默认配置失败'); + logger.e('|$_tag|还原识别策略失败error:|$e|stack:|$stack|'); + Toast.error(msg: e.message ?? '还原默认配置失败'); } catch (e, stack) { - logger.e('|$_tag|恢复识别策略失败error:|$e|stack:|$stack|'); - Toast.error(msg: '恢复默认配置失败'); + logger.e('|$_tag|还原识别策略失败error:|$e|stack:|$stack|'); + Toast.error(msg: '还原默认配置失败'); } finally { isSavingRecognitionConfiguration.value = false; } } - void discardRecognitionConfigurationChanges() { - recognitionConfiguration.value = savedRecognitionConfiguration.value; - } - void toggleAdvancedRecognitionConfiguration() { showAdvancedRecognitionConfiguration.toggle(); } diff --git a/lib/pages/terminal/setting/widgets/setting_face_plugin.dart b/lib/pages/terminal/setting/widgets/setting_face_plugin.dart index 4bb7be1..c7c890e 100644 --- a/lib/pages/terminal/setting/widgets/setting_face_plugin.dart +++ b/lib/pages/terminal/setting/widgets/setting_face_plugin.dart @@ -271,18 +271,11 @@ class _RecognitionConfigurationGroup extends StatelessWidget { _CompactActionBar( children: [ SettingActionButton( - label: '恢复默认', + label: '还原默认', icon: FAssets.icons.rotateCcw, - enabled: !saving && !configuration.usesDefaults, - onTap: controller.resetRecognitionConfiguration, + enabled: !saving && (!configuration.usesDefaults || changed), + onTap: controller.restoreDefaultRecognitionConfiguration, ), - if (changed) - SettingActionButton( - label: '放弃修改', - icon: FAssets.icons.x, - enabled: !saving, - onTap: controller.discardRecognitionConfigurationChanges, - ), SettingActionButton( label: saving ? '保存中...' : '保存配置', icon: FAssets.icons.save,