Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68e0ca30ad | |||
| a8c684ad0c | |||
| 0779bf57e3 |
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.39] - 2026-06-15
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- 打印预览弹窗:打印完成后自动关闭
|
||||||
|
- 打印预览弹窗:打印机列表改为仅显示热敏打印机,无热敏打印机时显示"无打印机可用"并禁用打印按钮
|
||||||
|
- 库存列表表格行"打标签"按钮点击无反应
|
||||||
|
- Windows/macOS 单实例限制:同一台电脑不允许重复打开多个客户端窗口
|
||||||
|
|
||||||
## [1.0.38] - 2026-06-15
|
## [1.0.38] - 2026-06-15
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
// Keep the RAF open so the OS lock stays alive for the lifetime of the process.
|
// Keep the server socket alive for the lifetime of the process.
|
||||||
// ignore: unused_element
|
// The OS releases the port automatically when the process exits.
|
||||||
RandomAccessFile? _raf;
|
ServerSocket? _server;
|
||||||
|
|
||||||
|
// Port unique to this app; chosen from the private range 49152–65535.
|
||||||
|
const _kLockPort = 54317;
|
||||||
|
|
||||||
/// Returns true if this process successfully acquired the single-instance lock.
|
/// Returns true if this process successfully acquired the single-instance lock.
|
||||||
/// Returns false if another instance is already running.
|
/// Returns false if another instance is already running.
|
||||||
@@ -15,11 +17,11 @@ Future<bool> acquire() async {
|
|||||||
if (!Platform.isWindows && !Platform.isMacOS) return true;
|
if (!Platform.isWindows && !Platform.isMacOS) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final dir = await getApplicationSupportDirectory();
|
_server = await ServerSocket.bind(
|
||||||
final lockFile = File('${dir.path}/jiu.lock');
|
InternetAddress.loopbackIPv4,
|
||||||
final raf = await lockFile.open(mode: FileMode.write);
|
_kLockPort,
|
||||||
await raf.lock(FileLock.exclusive);
|
shared: false,
|
||||||
_raf = raf;
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ Future<List<String>> listLabelPrintersImpl() async {
|
|||||||
return (r.stdout as String)
|
return (r.stdout as String)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty && _isThermalName(s))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -511,7 +511,10 @@ Future<List<String>> listLabelPrintersImpl() async {
|
|||||||
}
|
}
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
try {
|
try {
|
||||||
return (await Printing.listPrinters()).map((p) => p.name).toList();
|
return (await Printing.listPrinters())
|
||||||
|
.map((p) => p.name)
|
||||||
|
.where((n) => _isThermalName(n))
|
||||||
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('[label] listPrinters 失败: $e');
|
debugPrint('[label] listPrinters 失败: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -668,27 +668,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
|||||||
DataCell(
|
DataCell(
|
||||||
item.productId != null
|
item.productId != null
|
||||||
? TextButton(
|
? TextButton(
|
||||||
onPressed: () async {
|
onPressed: () => _printLabel(context, item),
|
||||||
final qrBytes = await ref
|
|
||||||
.read(productRepositoryProvider)
|
|
||||||
.getQRCodeBytes(item.productId!);
|
|
||||||
final shopInfo = ref.read(shopInfoProvider).valueOrNull;
|
|
||||||
if (context.mounted) {
|
|
||||||
await safePrint(context, () => printProductLabel(
|
|
||||||
qrBytes: qrBytes,
|
|
||||||
name: item.productName,
|
|
||||||
code: item.productCode,
|
|
||||||
series: item.series.isEmpty ? null : item.series,
|
|
||||||
spec: item.spec.isEmpty ? null : item.spec,
|
|
||||||
batchNo: item.batchNo.isEmpty ? null : item.batchNo,
|
|
||||||
productionDate: item.productionDate,
|
|
||||||
remark: item.remark.isEmpty ? null : item.remark,
|
|
||||||
shopName: shopInfo?.name ?? '',
|
|
||||||
shopAddress: shopInfo?.address ?? '',
|
|
||||||
shopPhone: shopInfo?.phone ?? '',
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('打标签',
|
child: const Text('打标签',
|
||||||
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
|
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class _LabelPreviewDialogState extends State<LabelPreviewDialog> {
|
|||||||
List<String> _printers = [];
|
List<String> _printers = [];
|
||||||
String? _printer;
|
String? _printer;
|
||||||
bool _printing = false;
|
bool _printing = false;
|
||||||
|
bool _printersChecked = false;
|
||||||
String _status = '';
|
String _status = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -63,6 +64,7 @@ class _LabelPreviewDialogState extends State<LabelPreviewDialog> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_printers = printers;
|
_printers = printers;
|
||||||
_printer = selected;
|
_printer = selected;
|
||||||
|
_printersChecked = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +149,7 @@ class _LabelPreviewDialogState extends State<LabelPreviewDialog> {
|
|||||||
_printing = false;
|
_printing = false;
|
||||||
_status = '完成,共打印 $done 张';
|
_status = '完成,共打印 $done 张';
|
||||||
});
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,8 +332,9 @@ class _LabelPreviewDialogState extends State<LabelPreviewDialog> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _printers.isEmpty
|
child: _printers.isEmpty
|
||||||
? const Text('检测中或未检测到打印机...',
|
? Text(
|
||||||
style: TextStyle(
|
_printersChecked ? '无打印机可用' : '正在检测打印机...',
|
||||||
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: AppTheme.textSecondary))
|
color: AppTheme.textSecondary))
|
||||||
: DropdownButtonHideUnderline(
|
: DropdownButtonHideUnderline(
|
||||||
@@ -419,7 +423,9 @@ class _LabelPreviewDialogState extends State<LabelPreviewDialog> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: (_printing || totalCount == 0) ? null : _doPrint,
|
onPressed: (_printing || totalCount == 0 || _printer == null)
|
||||||
|
? null
|
||||||
|
: _doPrint,
|
||||||
child: Text(
|
child: Text(
|
||||||
_printing ? '打印中...' : '打印 $totalCount 张'),
|
_printing ? '打印中...' : '打印 $totalCount 张'),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user