用户实测两个问题(先装好驱动的机器): 1. 双击 App 图标不一定能打开——根因 single_instance:唤醒端口 38210 被 残留占用(上次异常退出/TIME_WAIT)时,connect 无应答仍返回 false → main 直接 exit(0)。修复:端口被占但无应答判定为残留占用,照常启动 (仅唤醒功能降级,FileLog 留痕);有应答才作为第二实例退出。 2. 向导第三步点"完成"没反应——根因 _finish 只有 finally 没有 catch: Autostart/UrlScheme(reg 三连)/FirstRun 任一抛异常被静默吞,Navigator.pop 永不执行=对话框锁死。修复:每步独立 try-catch + 分步日志,成败都关闭 向导,失败汇总 SnackBar 提示"详见运行日志"。 本地日志文件系统(用户要求:仔细记录、可发回分析、定时删除): - lib/utils/file_log.dart:按天滚动 app-YYYYMMDD.log、单文件 5MB 轮转加 序号、启动清理 7 天前旧文件(按文件名日期判定);全同步追加写(低频 日志,保证异常退出前落盘);目录 mac ~/Library/Logs/zax-desktop-plugin/ win %APPDATA%\zax-desktop-plugin\logs\(不引第三方依赖) - AgentLog.write 挂文件输出(一处改动覆盖全部 22 个现有调用点); main.dart FileLog.init 提到最前,bootWarning/startError 全部落日志, 启动横幅记录平台/exe 路径/参数 - pavo isolate 的 [pavo-diag] 诊断日志传日志路径(纯数据,isolate 安全) 直接写文件——GUI 模式下 isolate stdout 本来就丢 - 插件页技术详情加"打开日志文件夹"按钮(Explorer/Finder 直达,用户打包 app-*.log 发回分析) 19 个测试全过、analyze 零问题。
128 lines
4.6 KiB
Dart
128 lines
4.6 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:window_manager/window_manager.dart';
|
||
|
||
import 'agent.dart';
|
||
import 'setup/single_instance.dart';
|
||
import 'setup/url_scheme.dart';
|
||
import 'ui/app_shell.dart';
|
||
import 'ui/app_tray.dart';
|
||
import 'utils/file_log.dart';
|
||
|
||
/// 资安信插件助手(desktop-plugin):桌面插件集成软件。
|
||
/// 当前内置插件:饭卡打印机(card-printer);左侧菜单与插件页均由 lib/ui/routes.dart 驱动。
|
||
/// 关闭窗口 = 最小化到后台托盘,服务持续运行;退出入口在托盘菜单。
|
||
///
|
||
/// 健壮性铁律:runApp 之前的任何非关键步骤(托盘/scheme 注册/单实例)一律
|
||
/// try-catch 吞错并记日志——否则在目标机环境差异下抛异常会让界面永远起不来
|
||
/// (窗口由原生创建、Dart 未 runApp = 白屏窗口),开发机上无法复现。
|
||
Future<void> main(List<String> args) async {
|
||
WidgetsFlutterBinding.ensureInitialized();
|
||
|
||
// 文件日志最先初始化(此后的每一步现场都可追溯;目录不可用时自动禁用)
|
||
FileLog.init();
|
||
FileLog.write('==== 启动 ====');
|
||
FileLog.write(
|
||
'环境: ${Platform.operatingSystem} ${Platform.operatingSystemVersion} / exe: ${Platform.resolvedExecutable} / args: $args');
|
||
|
||
String? bootWarning;
|
||
try {
|
||
await windowManager.ensureInitialized();
|
||
} catch (e) {
|
||
bootWarning = '窗口管理初始化失败:$e';
|
||
FileLog.write(bootWarning);
|
||
}
|
||
|
||
// 单实例:zaxprint:// 唤起或重复双击会启动第二进程——通知常驻实例弹窗后自己退出
|
||
bool isPrimary = true;
|
||
try {
|
||
isPrimary = await SingleInstance.acquire(
|
||
onWake: () async {
|
||
FileLog.write('收到唤醒(zaxprint:// 或重复双击):显示主窗口');
|
||
await windowManager.show();
|
||
await windowManager.focus();
|
||
},
|
||
);
|
||
} catch (e) {
|
||
bootWarning = '${bootWarning == null ? "" : "$bootWarning\n"}单实例检测失败:$e';
|
||
FileLog.write(bootWarning);
|
||
}
|
||
if (!isPrimary) {
|
||
FileLog.write('已有常驻实例(唤醒通知已发),本进程退出');
|
||
exit(0);
|
||
}
|
||
|
||
final agent = Agent(AgentOptions.parse(args));
|
||
String? startError;
|
||
try {
|
||
await agent.start();
|
||
} catch (e) {
|
||
startError = '$e';
|
||
agent.log.write('服务启动失败:$startError');
|
||
}
|
||
|
||
try {
|
||
await UrlScheme.ensureRegistered(); // Windows 运行时注册 zaxprint://;mac 由 Info.plist 声明
|
||
} catch (e) {
|
||
agent.log.write('网页唤起协议注册失败(不影响其他功能):$e');
|
||
}
|
||
|
||
final appTray = AppTray(agent: agent);
|
||
try {
|
||
await appTray.start();
|
||
} catch (e) {
|
||
agent.log.write('托盘初始化失败(不影响其他功能):$e');
|
||
}
|
||
|
||
agent.log.write('界面启动(日志目录:${FileLog.dir ?? "不可用"})');
|
||
runApp(DesktopPluginApp(agent: agent, startError: startError ?? bootWarning));
|
||
}
|
||
|
||
class DesktopPluginApp extends StatelessWidget {
|
||
const DesktopPluginApp({super.key, required this.agent, this.startError});
|
||
|
||
final Agent agent;
|
||
final String? startError;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MaterialApp(
|
||
title: '资安信插件助手',
|
||
debugShowCheckedModeBanner: false,
|
||
theme: _darkTheme(),
|
||
home: AppShell(agent: agent, startError: startError),
|
||
);
|
||
}
|
||
|
||
/// 深色运维风格(ui-ux-pro-max 设计系统:OLED 深底 + 运维三色语义)。
|
||
ThemeData _darkTheme() {
|
||
final base = ThemeData(brightness: Brightness.dark, useMaterial3: true);
|
||
final scheme = base.colorScheme.copyWith(
|
||
// 正常/主操作:operational green
|
||
primary: const Color(0xFF4ADE80),
|
||
onPrimary: const Color(0xFF052E16),
|
||
primaryContainer: const Color(0xFF14532D),
|
||
onPrimaryContainer: const Color(0xFFDCFCE7),
|
||
// 故障红
|
||
error: const Color(0xFFF87171),
|
||
errorContainer: const Color(0xFF7F1D1D),
|
||
onErrorContainer: const Color(0xFFFEE2E2),
|
||
// 表面层级:#0F172A 底 / #111827 卡 / #1E293B 强调
|
||
surface: const Color(0xFF0F172A),
|
||
onSurface: const Color(0xFFF8FAFC),
|
||
onSurfaceVariant: const Color(0xFFCBD5E1),
|
||
surfaceContainerLow: const Color(0xFF111827),
|
||
surfaceContainerHighest: const Color(0xFF1E293B),
|
||
outlineVariant: const Color(0xFF334155),
|
||
);
|
||
return base.copyWith(
|
||
colorScheme: scheme,
|
||
scaffoldBackgroundColor: const Color(0xFF0F172A),
|
||
cardTheme: const CardTheme(color: Color(0xFF111827)),
|
||
snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating),
|
||
dividerTheme: const DividerThemeData(color: Color(0xFF334155), thickness: 1),
|
||
);
|
||
}
|
||
}
|