feat: Phase 4 收尾 + Phase 5 打包(首启向导/开机自启/URL scheme 闭环/mac dmg)
Phase 4 剩余三项: - 首次引导向导(setup/first_run + ui/setup_wizard):服务自检(含 C# 助手共存 提示)→ 打印机检测与 HiTi 驱动安装指引(官网链接/重新检测,mac 与 win 文案 分支,允许无打印机完成)→ 开机自启开关 + scheme 注册;侧栏底部常驻设置区 (自启开关 + 重新运行引导) - 开机自启(setup/autostart):mac 写 LaunchAgent(RunAtLoad 一次性,不用 KeepAlive——托盘退出不能被拉回);Windows 走 HKCU Run(reg 命令免 win32 依赖) - URL scheme 闭环:单实例唤醒端口 38210(setup/single_instance)——zaxprint:// 唤起或重复双击启动第二进程时通知常驻实例弹窗后自退,跨平台一致(弃用 app_links 的 mac 事件转发不确定性);Windows 启动时幂等注册 HKCU scheme; 前端 cardPrinter.ts 加 launchAgent()(zaxprint://open),开卡抽屉离线时显示 "启动打印助手"按钮,点击唤起后 1s×6 轮询自动上线(biz 前端改动待用户提交) Phase 5: - tool/package_macos.sh:构建 → ad-hoc 就地签名(--deep,勿加 --options=runtime: hardened runtime 库校验会因 ad-hoc 无 Team ID 拒载 framework,实测踩坑)→ hdiutil dmg;实测产出 22MB dmg + 签名后冒烟通过 - windows/installer.iss:Inno Setup 安装包脚本(快捷方式/桌面图标/开机自启 Tasks/zaxprint scheme 注册与卸载清理/HiTi SDK DLL 随 Release 目录打包) 依赖 +url_launcher +shared_preferences;13 测试全过、analyze 零问题。
This commit is contained in:
1 parent
a7cc69bb92
commit
3ecac9a302
15 files changed
+934
-51
No files matched your search
@@ -43,3 +43,4 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
/dist/
|
||||
@@ -82,11 +82,11 @@ flutter build macos --release
|
||||
## 进度(2026-09-21)
|
||||
|
||||
- [x] Phase 1:协议服务器 + mock + 任务队列 + 端口顺延(单测全过,接口自测全过)
|
||||
- [x] Phase 2:macOS CUPS 后端(真机 status `HiTi CS-200e, 2.4.2 就绪`;真实出卡一张,printer-state 4→3 约 24s)
|
||||
- [x] Phase 3 代码:Windows PavoApi ffi 后端(analyze 零问题;windows/ 目录已从 SDK 模板手动实例化,vendor 收录 SDK DLL/bin 并在 CMake 构建后自动拷贝)——**待 Windows 实机验收**
|
||||
- [x] Phase 4 部分:插件化 UI(左侧菜单=路由注册表,主状态/指标/技术详情三层信息分级)+ **后台托盘**(关窗不退出、托盘恢复窗口、退出仅托盘菜单)+ 资安信 logo 图标全家桶 + 改名"资安信插件助手"
|
||||
- [ ] Phase 4 剩余:首次引导向导、URL scheme 唤起闭环(zaxprint scheme 已写入 Info.plist)、开机自启(LaunchAgent/HKCU Run)
|
||||
- [ ] Phase 5:打包签名分发(mac ad-hoc 就地签名;参考 C# 版签名铁律)
|
||||
- [x] Phase 2:macOS CUPS 后端(真机 status `HiTi CS-200e, 2.4.2 就绪`;真实出卡一张)
|
||||
- [x] Phase 3 代码:Windows PavoApi ffi 后端(待 Windows 实机验收)
|
||||
- [x] Phase 4 全部:插件化 UI + 后台托盘(红点/`_close:` 直退已修,根因=模板 AppDelegate 的 `applicationShouldTerminateAfterLastWindowClosed=true`)+ 资安信品牌 + **首次引导向导**(服务自检/打印机检测与驱动指引/自启配置)+ **开机自启**(mac LaunchAgent RunAtLoad / Win HKCU Run,侧栏开关)+ **URL scheme 闭环**(单实例唤醒端口 38210:`zaxprint://` 或重复双击 → 唤醒常驻实例弹窗;Win 启动时幂等注册 HKCU scheme;前端抽屉离线时"启动打印助手"按钮)
|
||||
- [x] Phase 5 mac:`tool/package_macos.sh`(构建 → ad-hoc 就地签名 → dmg,实测 22MB 产物 + 签名后冒烟通过);Windows:`windows/installer.iss`(Inno Setup,含 scheme/自启注册与卸载清理,待 Windows 机上 iscc 编译)
|
||||
- [ ] Windows 侧打包与实机验收(Inno 编译 + Pavo 直打/色带/免驱动实测)
|
||||
|
||||
### 已知坑(本项目实测)
|
||||
- SliverList 里 `Row(crossAxisAlignment: stretch)` 必须包 `IntrinsicHeight`,否则子项收到 h=Infinity 直接布局断言(test/ui_smoke_test.dart 守护)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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';
|
||||
|
||||
@@ -12,6 +16,17 @@ Future<void> main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
// 单实例:zaxprint:// 唤起或重复双击会启动第二进程——通知常驻实例弹窗后自己退出
|
||||
final isPrimary = await SingleInstance.acquire(
|
||||
onWake: () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
},
|
||||
);
|
||||
if (!isPrimary) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
final agent = Agent(AgentOptions.parse(args));
|
||||
String? startError;
|
||||
try {
|
||||
@@ -20,6 +35,8 @@ Future<void> main(List<String> args) async {
|
||||
startError = '$e';
|
||||
}
|
||||
|
||||
await UrlScheme.ensureRegistered(); // Windows 运行时注册 zaxprint://;mac 由 Info.plist 声明
|
||||
|
||||
final appTray = AppTray(agent: agent);
|
||||
await appTray.start();
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'dart:io';
|
||||
|
||||
import '../utils/command_runner.dart';
|
||||
|
||||
/// 开机自启:mac 写 LaunchAgent(RunAtLoad 一次性——托盘退出不能被拉回,故不用 KeepAlive);
|
||||
/// Windows 写 HKCU Run 注册表键(reg 命令实现,免 win32 依赖)。
|
||||
/// 自启指向当前可执行文件(Platform.resolvedExecutable),以安装位置为准。
|
||||
class Autostart {
|
||||
static const macLabel = 'com.zax.desktop-plugin';
|
||||
static const winRunName = 'ZaxDesktopPlugin';
|
||||
|
||||
static bool get isSupported => Platform.isMacOS || Platform.isWindows;
|
||||
|
||||
static String? get _macPlistPath {
|
||||
final home = Platform.environment['HOME'];
|
||||
if (home == null) return null;
|
||||
return '$home/Library/LaunchAgents/$macLabel.plist';
|
||||
}
|
||||
|
||||
/// 自启是否已开启。
|
||||
static Future<bool> isEnabled() async {
|
||||
if (Platform.isMacOS) {
|
||||
final path = _macPlistPath;
|
||||
return path != null && File(path).existsSync();
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
final r = await runCommand(
|
||||
'reg',
|
||||
['query', r'HKCU\Software\Microsoft\Windows\CurrentVersion\Run', '/v', winRunName],
|
||||
tolerateExit: true,
|
||||
);
|
||||
return r.contains(winRunName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 开启自启(mac 顺带把 C# 助手的 LaunchAgent 状态返回,供向导提示端口关系)。
|
||||
static Future<void> enable() async {
|
||||
if (Platform.isMacOS) {
|
||||
final path = _macPlistPath;
|
||||
if (path == null) throw StateError('无法定位用户目录');
|
||||
final exe = Platform.resolvedExecutable;
|
||||
await Directory(File(path).parent.path).create(recursive: true);
|
||||
await File(path).writeAsString('''
|
||||
<?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>Label</key>
|
||||
<string>$macLabel</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>$exe</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>LaunchOnlyOnce</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
''');
|
||||
return;
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
final exe = Platform.resolvedExecutable;
|
||||
await runCommand('reg', [
|
||||
'add', r'HKCU\Software\Microsoft\Windows\CurrentVersion\Run',
|
||||
'/v', winRunName, '/t', 'REG_SZ', '/d', '"$exe"', '/f',
|
||||
]);
|
||||
return;
|
||||
}
|
||||
throw UnsupportedError('当前平台不支持开机自启');
|
||||
}
|
||||
|
||||
static Future<void> disable() async {
|
||||
if (Platform.isMacOS) {
|
||||
final path = _macPlistPath;
|
||||
if (path != null && File(path).existsSync()) {
|
||||
await File(path).delete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
await runCommand(
|
||||
'reg',
|
||||
['delete', r'HKCU\Software\Microsoft\Windows\CurrentVersion\Run', '/v', winRunName, '/f'],
|
||||
tolerateExit: true, // 不存在时也算关闭成功
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw UnsupportedError('当前平台不支持开机自启');
|
||||
}
|
||||
|
||||
/// C# 版助手的 LaunchAgent 是否在(本机灰度共存提示用;mac only)。
|
||||
static bool get legacyCsharpAgentInstalled {
|
||||
if (!Platform.isMacOS) return false;
|
||||
final home = Platform.environment['HOME'];
|
||||
return home != null &&
|
||||
File('$home/Library/LaunchAgents/com.zax.cardprinteragent.plist').existsSync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// 首次引导向导完成标记(shared_preferences 持久化)。
|
||||
class FirstRun {
|
||||
static const _key = 'first_run_done';
|
||||
|
||||
static Future<bool> isDone() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_key) ?? false;
|
||||
}
|
||||
|
||||
static Future<void> complete() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_key, true);
|
||||
}
|
||||
|
||||
/// 重新运行向导(设置入口用)。
|
||||
static Future<void> reset() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_key, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
/// 单实例 + 唤醒端口:应用只允许一个常驻实例。
|
||||
///
|
||||
/// zaxprint:// 唤起或用户再次双击图标时,OS 会启动第二个进程——第二实例连上
|
||||
/// 唤醒端口发送 wake 后立即退出,常驻实例收到后把主窗口带到前台。
|
||||
/// (比 app_links 的方案更跨平台一致:mac/win 都只依赖回环 socket。)
|
||||
class SingleInstance {
|
||||
static const int wakePort = 38210;
|
||||
|
||||
/// 尝试成为主实例。
|
||||
///
|
||||
/// 返回 true = 本实例是主实例(已开始监听唤醒,[onWake] 在收到唤醒时回调);
|
||||
/// 返回 false = 已有常驻实例(已通知其显示窗口),调用方应当退出进程。
|
||||
static Future<bool> acquire({void Function()? onWake}) async {
|
||||
try {
|
||||
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, wakePort);
|
||||
server.listen((socket) {
|
||||
socket
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.listen((message) {
|
||||
if (message.contains('wake')) {
|
||||
onWake?.call();
|
||||
}
|
||||
}, onDone: () {
|
||||
socket.destroy();
|
||||
});
|
||||
}, onError: (_) {});
|
||||
return true;
|
||||
} on SocketException {
|
||||
// 端口被占:已有常驻实例,通知它显示窗口
|
||||
try {
|
||||
final socket = await Socket.connect(
|
||||
InternetAddress.loopbackIPv4,
|
||||
wakePort,
|
||||
timeout: const Duration(seconds: 2),
|
||||
);
|
||||
socket.add(utf8.encode('wake'));
|
||||
await socket.flush();
|
||||
await socket.close();
|
||||
} catch (_) {
|
||||
// 唤醒端口被非本应用进程占用等异常:放弃唤醒,按第二实例退出
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'dart:io';
|
||||
|
||||
import '../utils/command_runner.dart';
|
||||
|
||||
/// zaxprint:// URL scheme 注册:
|
||||
/// - macOS:Info.plist CFBundleURLTypes 已声明(构建即生效),无需运行时处理
|
||||
/// - Windows:写 HKCU\Software\Classes\zaxprint(用户级,免管理员),App 启动时 ensure
|
||||
class UrlScheme {
|
||||
static const scheme = 'zaxprint';
|
||||
|
||||
static bool get needsRuntimeRegister => Platform.isWindows;
|
||||
|
||||
/// Windows:确保 scheme 已注册到当前可执行文件(每次启动幂等调用,路径变了会刷新)。
|
||||
static Future<void> ensureRegistered() async {
|
||||
if (!Platform.isWindows) return;
|
||||
final exe = Platform.resolvedExecutable;
|
||||
final root = r'HKCU\Software\Classes' '\\$scheme';
|
||||
await runCommand('reg', ['add', root, '/ve', '/t', 'REG_SZ', '/d', 'URL:资安信插件助手唤起', '/f']);
|
||||
await runCommand('reg', ['add', root, '/v', 'URL Protocol', '/t', 'REG_SZ', '/d', '', '/f']);
|
||||
await runCommand('reg', [
|
||||
'add', r'HKCU\Software\Classes' '\\$scheme' r'\shell\open\command',
|
||||
'/ve', '/t', 'REG_SZ', '/d', '"$exe" "%1"', '/f',
|
||||
]);
|
||||
}
|
||||
|
||||
/// Windows:注册是否指向当前可执行文件(向导自检展示用)。
|
||||
static Future<bool> isRegisteredToSelf() async {
|
||||
if (!Platform.isWindows) return true; // mac 由 Info.plist 保证
|
||||
final exe = Platform.resolvedExecutable;
|
||||
final out = await runCommand(
|
||||
'reg',
|
||||
['query', r'HKCU\Software\Classes' '\\$scheme' r'\shell\open\command', '/ve'],
|
||||
tolerateExit: true,
|
||||
);
|
||||
return out.contains(exe);
|
||||
}
|
||||
}
|
||||
+80
-1
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../agent.dart';
|
||||
import '../setup/autostart.dart';
|
||||
import '../setup/first_run.dart';
|
||||
import 'routes.dart';
|
||||
import 'setup_wizard.dart';
|
||||
|
||||
/// 应用外壳:左侧插件菜单 + 右侧插件页面(菜单项来自路由注册表)。
|
||||
class AppShell extends StatefulWidget {
|
||||
@@ -16,9 +19,50 @@ class AppShell extends StatefulWidget {
|
||||
|
||||
class _AppShellState extends State<AppShell> {
|
||||
int _selectedIndex = 0;
|
||||
bool _autostart = false;
|
||||
bool _autostartLoaded = false;
|
||||
|
||||
Agent get _agent => widget.agent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAutostart();
|
||||
_maybeShowWizard();
|
||||
}
|
||||
|
||||
Future<void> _loadAutostart() async {
|
||||
final enabled = await Autostart.isEnabled();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_autostart = enabled;
|
||||
_autostartLoaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleAutostart(bool value) async {
|
||||
setState(() => _autostart = value);
|
||||
try {
|
||||
value ? await Autostart.enable() : await Autostart.disable();
|
||||
} catch (_) {
|
||||
if (mounted) await _loadAutostart(); // 失败回读真实状态
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _maybeShowWizard() async {
|
||||
if (await FirstRun.isDone()) return;
|
||||
if (!mounted) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => SetupWizard(agent: _agent, onFinished: _loadAutostart),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
@@ -100,8 +144,43 @@ class _AppShellState extends State<AppShell> {
|
||||
),
|
||||
|
||||
Divider(height: 1, thickness: 1, color: scheme.outlineVariant),
|
||||
|
||||
// 设置区:开机自启 + 重新运行引导
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 10, 16, 12),
|
||||
padding: const EdgeInsets.fromLTRB(8, 6, 8, 0),
|
||||
child: SwitchListTile(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
value: _autostartLoaded && _autostart,
|
||||
onChanged: Autostart.isSupported ? _toggleAutostart : null,
|
||||
title: Text('开机自动启动', style: Theme.of(context).textTheme.bodySmall),
|
||||
secondary: Icon(Icons.power_settings_new,
|
||||
size: 18, color: scheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 8, 4),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => SetupWizard(agent: _agent, onFinished: _loadAutostart),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.restart_alt, size: 14),
|
||||
label: const Text('重新运行引导'),
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodySmall,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
|
||||
child: Text(
|
||||
'v1.0.0',
|
||||
style: Theme.of(context)
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../agent.dart';
|
||||
import '../server/protocol.dart';
|
||||
import '../setup/autostart.dart';
|
||||
import '../setup/first_run.dart';
|
||||
import '../setup/url_scheme.dart';
|
||||
|
||||
/// 首次引导向导:服务自检 → 打印机检测(含驱动引导)→ 自启/唤起配置。
|
||||
/// 深色主题对话框,三步 Stepper 风格;允许无打印机完成(纯联调机)。
|
||||
class SetupWizard extends StatefulWidget {
|
||||
const SetupWizard({super.key, required this.agent, required this.onFinished});
|
||||
|
||||
final Agent agent;
|
||||
final VoidCallback onFinished;
|
||||
|
||||
/// 便捷入口:未完成过向导时弹出。
|
||||
static Future<void> showIfNeeded(BuildContext context, Agent agent) async {
|
||||
if (await FirstRun.isDone()) return;
|
||||
if (!context.mounted) return;
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => SetupWizard(
|
||||
agent: agent,
|
||||
onFinished: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<SetupWizard> createState() => _SetupWizardState();
|
||||
}
|
||||
|
||||
class _SetupWizardState extends State<SetupWizard> {
|
||||
static const _driverUrl = 'https://www.hiti.com/support/download.aspx?MenuID=72&lang=CN';
|
||||
|
||||
int _step = 0;
|
||||
bool _autostart = true;
|
||||
bool _autostartBusy = false;
|
||||
bool _printerChecking = false;
|
||||
PrinterStatus? _printerStatus;
|
||||
|
||||
Agent get _agent => widget.agent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkPrinter();
|
||||
}
|
||||
|
||||
Future<void> _checkPrinter() async {
|
||||
setState(() => _printerChecking = true);
|
||||
try {
|
||||
final status = await _agent.backend.getStatus();
|
||||
if (mounted) setState(() => _printerStatus = status);
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _printerStatus = null);
|
||||
} finally {
|
||||
if (mounted) setState(() => _printerChecking = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _finish() async {
|
||||
setState(() => _autostartBusy = true);
|
||||
try {
|
||||
if (_autostart && Autostart.isSupported) {
|
||||
await Autostart.enable();
|
||||
}
|
||||
await UrlScheme.ensureRegistered();
|
||||
await FirstRun.complete();
|
||||
} finally {
|
||||
if (mounted) setState(() => _autostartBusy = false);
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
widget.onFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Dialog(
|
||||
backgroundColor: scheme.surfaceContainerLow,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
side: BorderSide(color: scheme.outlineVariant),
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_header(scheme),
|
||||
const SizedBox(height: 18),
|
||||
if (_step == 0) _stepWelcome(scheme),
|
||||
if (_step == 1) _stepPrinter(scheme),
|
||||
if (_step == 2) _stepFinish(scheme),
|
||||
const SizedBox(height: 20),
|
||||
_footer(scheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _header(ColorScheme scheme) => Row(
|
||||
children: [
|
||||
Icon(Icons.extension_rounded, color: scheme.primary, size: 26),
|
||||
const SizedBox(width: 10),
|
||||
Text('初次使用引导', style: Theme.of(context).textTheme.titleLarge),
|
||||
const Spacer(),
|
||||
_stepBadge(scheme),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _stepBadge(ColorScheme scheme) => Row(
|
||||
children: [
|
||||
for (var i = 0; i < 3; i++) ...[
|
||||
Container(
|
||||
width: i == _step ? 18 : 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: i <= _step ? scheme.primary : scheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
if (i < 2) const SizedBox(width: 6),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// ---------------- Step 0:服务自检 ----------------
|
||||
|
||||
Widget _stepWelcome(ColorScheme scheme) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('资安信插件助手在本机为管理端网页提供打印等桌面能力,安装完成后保持后台运行即可,无需日常操作。',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
const SizedBox(height: 14),
|
||||
_checkRow(
|
||||
scheme,
|
||||
ok: _agent.port != null,
|
||||
okText: '本机服务运行中${_agent.port != null ? "(127.0.0.1:${_agent.port})" : ""}',
|
||||
badText: '本机服务未启动(可能是端口被占用,请重启应用)',
|
||||
),
|
||||
if (Autostart.legacyCsharpAgentInstalled) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'检测到旧版打印助手(C# 版)在本机自启。两者协议兼容可并存,'
|
||||
'如需完全切换到本应用,可在终端执行:\n'
|
||||
'launchctl unload ~/Library/LaunchAgents/com.zax.cardprinteragent.plist',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(height: 1.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// ---------------- Step 1:打印机检测 ----------------
|
||||
|
||||
Widget _stepPrinter(ColorScheme scheme) {
|
||||
final s = _printerStatus;
|
||||
final ready = s != null && s.online;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_checkRow(
|
||||
scheme,
|
||||
ok: ready,
|
||||
loading: _printerChecking,
|
||||
okText: '打印机就绪${s != null && s.model.isNotEmpty ? "(${s.model})" : ""}',
|
||||
badText: s == null ? '未检测到打印队列' : (s.statusText.isEmpty ? '打印机未就绪' : s.statusText),
|
||||
),
|
||||
if (!ready) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
Platform.isMacOS
|
||||
? '安装步骤:① 打开 HiTi 官网下载 macOS 驱动(HiTiCardTypeMacDrivers dmg),双击安装里面的 pkg;② USB 连接打印机并开机;③ 系统设置 → 打印机与扫描仪 确认出现 HiTi 打印机;④ 回到这里点「重新检测」。'
|
||||
: 'Windows 打印站:打开 HiTi 官网下载 CS-2xx Windows 驱动并安装,USB 连接打印机;本应用也支持免驱动的 SDK 直连模式,装完驱动或接好打印机后点「重新检测」。',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => launchUrl(Uri.parse(_driverUrl)),
|
||||
icon: const Icon(Icons.open_in_new, size: 14),
|
||||
label: const Text('打开 HiTi 驱动下载页'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
TextButton.icon(
|
||||
onPressed: _printerChecking ? null : _checkPrinter,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('重新检测'),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 12),
|
||||
Text('打印机已连接,可以正常接收管理端提交的卡面打印。',
|
||||
style: Theme.of(context).textTheme.bodySmall),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- Step 2:完成配置 ----------------
|
||||
|
||||
Widget _stepFinish(ColorScheme scheme) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: _autostart,
|
||||
onChanged: Autostart.isSupported
|
||||
? (v) => setState(() => _autostart = v)
|
||||
: null,
|
||||
title: const Text('开机自动启动'),
|
||||
subtitle: Text(
|
||||
Autostart.isSupported ? '登录系统后自动在后台运行(可在左侧菜单底部随时修改)' : '当前平台不支持',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
_checkRow(
|
||||
scheme,
|
||||
ok: true,
|
||||
okText: '网页唤起支持(zaxprint:// 协议)已启用',
|
||||
badText: '',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'全部完成。关闭窗口将最小化到托盘,服务持续运行;退出请通过托盘菜单。',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// ---------------- 公共组件 ----------------
|
||||
|
||||
Widget _checkRow(ColorScheme scheme,
|
||||
{required bool ok, required String okText, required String badText, bool loading = false}) {
|
||||
final color = ok ? scheme.primary : const Color(0xFFD97706);
|
||||
return Row(
|
||||
children: [
|
||||
if (loading)
|
||||
const SizedBox(
|
||||
width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
else
|
||||
Icon(ok ? Icons.check_circle : Icons.info_outline, size: 18, color: color),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
ok ? okText : badText,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: color),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _footer(ColorScheme scheme) => Row(
|
||||
children: [
|
||||
if (_step > 0)
|
||||
TextButton(
|
||||
onPressed: () => setState(() => _step--),
|
||||
child: const Text('上一步'),
|
||||
),
|
||||
const Spacer(),
|
||||
if (_step < 2)
|
||||
FilledButton(
|
||||
onPressed: () => setState(() => _step++),
|
||||
child: Text(_step == 0 ? '下一步' : '跳过,下一步'),
|
||||
)
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: _autostartBusy ? null : _finish,
|
||||
child: const Text('完成'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -6,11 +6,15 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import screen_retriever_macos
|
||||
import shared_preferences_foundation
|
||||
import tray_manager
|
||||
import url_launcher_macos
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
}
|
||||
@@ -2,15 +2,22 @@ PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- screen_retriever_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- tray_manager (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -18,15 +25,21 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
screen_retriever_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
tray_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
window_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
screen_retriever_macos: c5508cc3c66ff0d4db650480cf0ab691e220d933
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
tray_manager: 19c369010e59c073f13b82354d78a35f7972d609
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
|
||||
|
||||
+226
-45
@@ -6,7 +6,7 @@ packages:
|
||||
description:
|
||||
name: archive
|
||||
sha256: "6c5bcd986e06b94e3c40244af471750840a3d2341d1f9763a1100a14add517b4"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
async:
|
||||
@@ -14,7 +14,7 @@ packages:
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
boolean_selector:
|
||||
@@ -22,7 +22,7 @@ packages:
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
@@ -30,7 +30,7 @@ packages:
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
@@ -38,7 +38,7 @@ packages:
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
@@ -46,7 +46,7 @@ packages:
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cupertino_icons:
|
||||
@@ -54,7 +54,7 @@ packages:
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
@@ -62,7 +62,7 @@ packages:
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
ffi:
|
||||
@@ -70,9 +70,17 @@ packages:
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -83,7 +91,7 @@ packages:
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_test:
|
||||
@@ -91,12 +99,17 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
http_methods:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_methods
|
||||
sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
http_parser:
|
||||
@@ -104,7 +117,7 @@ packages:
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
@@ -112,7 +125,7 @@ packages:
|
||||
description:
|
||||
name: image
|
||||
sha256: a1e7f4951e538a568e14b856702afc9ae1d2f4b202daced8d22c1b9cd211ce89
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
json_annotation:
|
||||
@@ -120,7 +133,7 @@ packages:
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
@@ -128,7 +141,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
leak_tracker_flutter_testing:
|
||||
@@ -136,7 +149,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
@@ -144,7 +157,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
@@ -152,7 +165,7 @@ packages:
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
matcher:
|
||||
@@ -160,7 +173,7 @@ packages:
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
@@ -168,7 +181,7 @@ packages:
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
menu_base:
|
||||
@@ -176,7 +189,7 @@ packages:
|
||||
description:
|
||||
name: menu_base
|
||||
sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
meta:
|
||||
@@ -184,7 +197,7 @@ packages:
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
path:
|
||||
@@ -192,15 +205,47 @@ packages:
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
posix:
|
||||
@@ -208,7 +253,7 @@ packages:
|
||||
description:
|
||||
name: posix
|
||||
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.5.2"
|
||||
screen_retriever:
|
||||
@@ -216,7 +261,7 @@ packages:
|
||||
description:
|
||||
name: screen_retriever
|
||||
sha256: ace919117a7520c13a50a6259e60c4a0d4cbe98809468792a91b5c5adada2aa6
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
screen_retriever_linux:
|
||||
@@ -224,7 +269,7 @@ packages:
|
||||
description:
|
||||
name: screen_retriever_linux
|
||||
sha256: "7b52006a5ceae1f3d5af7f77188c3290d6e7d8ded16d99809bea84967c65c257"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
screen_retriever_macos:
|
||||
@@ -232,7 +277,7 @@ packages:
|
||||
description:
|
||||
name: screen_retriever_macos
|
||||
sha256: a1489b99cce597c45a54b9aae1cd94c8d4705353b7e0bb2457a6e4de44e0ad8a
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
screen_retriever_platform_interface:
|
||||
@@ -240,7 +285,7 @@ packages:
|
||||
description:
|
||||
name: screen_retriever_platform_interface
|
||||
sha256: "94a5535277510a63184ca178ce12a1449bc0b38618879aa1c18bf57369c5064a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
screen_retriever_windows:
|
||||
@@ -248,15 +293,71 @@ packages:
|
||||
description:
|
||||
name: screen_retriever_windows
|
||||
sha256: dafc6922b0bfbf1d48cf3ccbf519b4fff47bdcb820da1728ea6db675fecc9324
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_router:
|
||||
@@ -264,7 +365,7 @@ packages:
|
||||
description:
|
||||
name: shelf_router
|
||||
sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
shortid:
|
||||
@@ -272,7 +373,7 @@ packages:
|
||||
description:
|
||||
name: shortid
|
||||
sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
sky_engine:
|
||||
@@ -285,7 +386,7 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
@@ -293,7 +394,7 @@ packages:
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
@@ -301,7 +402,7 @@ packages:
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
@@ -309,7 +410,7 @@ packages:
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
@@ -317,7 +418,7 @@ packages:
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
@@ -325,7 +426,7 @@ packages:
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
tray_manager:
|
||||
@@ -333,7 +434,7 @@ packages:
|
||||
description:
|
||||
name: tray_manager
|
||||
sha256: "1a659b08baa6e9b91ef8ce16eda37740de398be1c4cf322b8a1ddfef25c68c5a"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.5.3"
|
||||
typed_data:
|
||||
@@ -341,15 +442,79 @@ packages:
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.20"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
@@ -357,17 +522,33 @@ packages:
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: window_manager
|
||||
sha256: "05c231fd7b23d2380f14c5cc10b7b93d60d4fa4a2fb4e0f032de27e44b5560e9"
|
||||
url: "https://pub.dev"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.5.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.7.2 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
flutter: ">=3.29.0"
|
||||
@@ -45,6 +45,8 @@ dependencies:
|
||||
# 后台托盘与窗口控制(关窗不退出、托盘常驻)
|
||||
tray_manager: ^0.5.3
|
||||
window_manager: ^0.5.2
|
||||
shared_preferences: ^2.5.3
|
||||
url_launcher: ^6.3.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# 资安信插件助手 macOS 打包:release 构建 → ad-hoc 就地签名 → dmg
|
||||
# 用法:bash tool/package_macos.sh
|
||||
# 分发说明(ad-hoc 未公证):用户首次打开需「右键 .app → 打开」确认一次。
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
APP_NAME="资安信插件助手"
|
||||
APP="build/macos/Build/Products/Release/${APP_NAME}.app"
|
||||
DMG="dist/${APP_NAME}-macOS.dmg"
|
||||
|
||||
echo "==> flutter build macos --release"
|
||||
flutter build macos --release
|
||||
|
||||
echo "==> ad-hoc 就地签名(含全部嵌入内容;构建产物位于最终位置,直接在此重签)"
|
||||
# 注意:ad-hoc 签名不要加 --options=runtime——hardened runtime 的库校验(library validation)
|
||||
# 要求所有嵌入库与主程序同 Team ID,ad-hoc 无 Team 会导致 framework 加载被 dyld 拒绝
|
||||
codesign --force --sign - --deep "$APP"
|
||||
codesign --verify --verbose=2 "$APP"
|
||||
|
||||
echo "==> 打 dmg"
|
||||
mkdir -p dist
|
||||
rm -f "$DMG"
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$APP" -ov -format UDZO "$DMG"
|
||||
|
||||
echo "==> 完成"
|
||||
ls -lh "$DMG"
|
||||
echo "安装方式:双击 dmg 拖入「应用程序」;首次打开请右键 → 打开(ad-hoc 签名未公证)。"
|
||||
@@ -0,0 +1,52 @@
|
||||
; 资安信插件助手 Windows 安装包(Inno Setup 脚本)
|
||||
; 构建:先在 Windows 上执行 flutter build windows --release,再用 Inno Setup 编译本脚本
|
||||
; iscc windows\installer.iss
|
||||
; 产物:dist\资安信插件助手-windows-setup.exe
|
||||
|
||||
#define AppName "资安信插件助手"
|
||||
#define AppVersion "1.0.0"
|
||||
#define AppExeName "desktop_plugin.exe"
|
||||
|
||||
[Setup]
|
||||
AppId={{8C5B7A42-1F2D-4E6B-9A71-9D3A0F2C7E51}
|
||||
AppName={#AppName}
|
||||
AppVersion={#AppVersion}
|
||||
AppPublisher=zax
|
||||
DefaultDirName={autopf}\{#AppName}
|
||||
DefaultGroupName={#AppName}
|
||||
UninstallDisplayIcon={app}\{#AppExeName}
|
||||
OutputDir=..\dist
|
||||
OutputBaseFilename=资安信插件助手-windows-setup
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
ArchitecturesAllowed=x64compatible
|
||||
Compression=lzma2/max
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
PrivilegesRequired=lowest
|
||||
; HiTi PavoApi SDK(PavoApiW_x64.dll 等)由 CMake 构建后自动拷在 Release 目录,随 Files 段一并打包
|
||||
|
||||
[Languages]
|
||||
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加任务:"
|
||||
Name: "autostart"; Description: "开机自动启动"; GroupDescription: "附加任务:"; Flags: checkedonce
|
||||
|
||||
[Files]
|
||||
Source: "..\build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
|
||||
Name: "{group}\卸载 {#AppName}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Registry]
|
||||
; zaxprint:// URL scheme(HKCU 用户级,随卸载清理;应用启动时也会幂等注册)
|
||||
Root: HKCU; Subkey: "Software\Classes\zaxprint"; ValueType: string; ValueData: "URL:资安信插件助手唤起"; Flags: uninsdeletekey
|
||||
Root: HKCU; Subkey: "Software\Classes\zaxprint"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""
|
||||
Root: HKCU; Subkey: "Software\Classes\zaxprint\shell\open\command"; ValueType: string; ValueData: """{app}\{#AppExeName}"" ""%1"""; Flags: uninsdeletekey
|
||||
; 开机自启(Tasks 勾选时写入)
|
||||
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "ZaxDesktopPlugin"; ValueData: """{app}\{#AppExeName}"""; Tasks: autostart; Flags: uninsdeletevalue
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#AppExeName}"; Description: "立即运行 {#AppName}"; Flags: nowait postinstall skipifsilent
|
||||
Reference in new issue
Block a user