Compare commits

...

5 Commits

Author SHA1 Message Date
wangjia 022443476c docs(changelog): 1.0.14(Windows 打印修复 + 关于我们真实信息/配置化)
Deploy / build-linux-web (push) Successful in 50s
Deploy / build-windows (push) Failing after 1m29s
Deploy / build-macos (push) Successful in 1m1s
Deploy / release-deploy (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 07:48:27 +08:00
wangjia 93f4f87e44 fix(client): 打印字体改 TrueType,修复 Windows 打印卡死
打印字体实为 OpenType/CFF,dart_pdf 只支持 TrueType(glyf),渲染中文
抛 'does not support Unicode';标签打印时异常抛在原生 onLayout 回调内
导致打印插件死等->卡死。用 fonttools 将字体转为 TrueType/glyf 替换。
加回归测试防止退回 CFF 字体。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 01:08:45 +08:00
wangjia 20e7d07be3 feat(client): 关于我们内容改用配置文件 + 窗口标题改岩美酒库
- 新增 assets/config/app_info.json(服务商/网站/邮箱真实值),启动时
  AppInfo.load() 读取,不硬编码、不走环境变量
- 关于我们/续费/反馈处的服务商、网站、邮箱统一读配置
- Windows 窗口标题 jiu_client -> 岩美酒库(u8 + UTF-8→UTF-16 防乱码)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 01:08:45 +08:00
wangjia 1834740676 fix(ci): Inno Setup 去掉中文语言段,用内置英文向导
Deploy / build-windows (push) Successful in 1m37s
Deploy / build-linux-web (push) Successful in 46s
Deploy / build-macos (push) Successful in 1m0s
Deploy / release-deploy (push) Successful in 1m21s
ChineseSimplified.isl 默认不随 Inno Setup 安装,引用它导致编译报
找不到文件。去掉 [Languages] 段用内置默认(英文)向导;app 名称与
快捷方式仍为中文。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:34:44 +08:00
wangjia 90a194fc00 fix(ci): ISCC 调用关闭 MSYS 参数路径转换,修复 more than one script filename
Deploy / build-linux-web (push) Successful in 50s
Deploy / build-windows (push) Failing after 1m22s
Deploy / build-macos (push) Successful in 59s
Deploy / release-deploy (push) Has been skipped
Git Bash(MSYS) 把 /D 开头的参数当 POSIX 路径自动转换,导致 ISCC 的
/DAppVer /DSrcDir /DOutDir 丢失 /D 前缀、被当成多个脚本文件名而报错。
调用 ISCC 时设 MSYS_NO_PATHCONV=1 + MSYS2_ARG_CONV_EXCL='*' 关闭转换。
install-innosetup.ps1 顺带加 /LOG 失败时打印安装日志。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:05:09 +08:00
12 changed files with 116 additions and 25 deletions
+11 -3
View File
@@ -5,14 +5,22 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.11] - 2026-06-04
## [1.0.14] - 2026-06-05
### 修复
- 修复 Windows 桌面端打印卡死:打印字体由 OpenType/CFF 改为 TrueType(glyf)dart_pdf 渲染中文不再抛错(此前点「打印」后卡死)
### 改进
- 「关于我们」内容改为真实信息(服务商:岩美技术有限公司;网站:jiu.yanmei.com;邮箱:yammy2023@163.com),且统一从配置文件读取,不再硬编码
- Windows 桌面端窗口标题由 jiu_client 改为「岩美酒库」
## [1.0.13] - 2026-06-04
### 新功能
- Windows 客户端改为提供安装程序(Inno Setup 打包的 `jiu-windows-x64-setup.exe`,含中文向导、桌面/开始菜单快捷方式、卸载项),不再是解压版 zip
- Windows 客户端改为提供安装程序(Inno Setup 打包的 `jiu-windows-x64-setup.exe`,含安装向导、桌面/开始菜单快捷方式、卸载项),不再是解压版 zip
### 修复
- 修复客户端「无法安全下载」:下载地址从内网 HTTP Forgejo`http://192.168.3.200:3000`,公网不可达且被浏览器拦截)改为同源 HTTPS `https://jiu.51yanmei.com/downloads/...`Windows、macOS 均生效
- 修复 Windows runner 安装 Inno Setup 失败:改用健壮的 `install-innosetup.ps1`(校验下载大小 + 检查安装退出码 + 双 Program Files 目录定位 ISCC),替代会静默成功但实际没装上的内联命令
- 部署时将最新安装包发布到 web 服务(`/opt/jiu/downloads/`nginx `/downloads/` 提供),仅保留最新版本(清旧放新)
## [1.0.9] - 2026-06-04
+5
View File
@@ -0,0 +1,5 @@
{
"provider": "岩美技术有限公司",
"website": "https://jiu.yanmei.com",
"email": "yammy2023@163.com"
}
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
/// 应用「关于我们」等品牌信息,从配置文件 assets/config/app_info.json 读取,
/// 不硬编码、不走环境变量。在 main() 启动时调用 [load] 加载一次。
class AppInfo {
const AppInfo._();
static const _assetPath = 'assets/config/app_info.json';
static String provider = '';
static String website = '';
static String email = '';
/// 加载配置文件。失败时保留为空(不影响应用启动)。
static Future<void> load() async {
try {
final raw = await rootBundle.loadString(_assetPath);
final map = jsonDecode(raw) as Map<String, dynamic>;
provider = (map['provider'] as String?) ?? '';
website = (map['website'] as String?) ?? '';
email = (map['email'] as String?) ?? '';
} catch (e) {
debugPrint('AppInfo.load failed: $e');
}
}
}
+6 -1
View File
@@ -4,6 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
import 'core/auth/auth_state.dart';
import 'core/config/app_info.dart';
import 'core/errors/error_reporter.dart';
import 'core/router/app_router.dart';
import 'core/theme/app_theme.dart';
@@ -24,7 +25,11 @@ void main() {
};
runZonedGuarded(
() => runApp(const ProviderScope(child: JiuApp())),
() async {
WidgetsFlutterBinding.ensureInitialized();
await AppInfo.load(); // 从配置文件加载「关于我们」品牌信息
runApp(const ProviderScope(child: JiuApp()));
},
(error, stack) {
debugPrint('═══ Zone Error ═══════════════════════════════');
debugPrint(error.toString());
@@ -11,6 +11,7 @@ import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/auth/auth_state.dart';
import '../../core/config/app_config.dart';
import '../../core/config/app_info.dart';
import '../../core/theme/app_theme.dart';
import '../../models/number_rule.dart';
import '../../models/user.dart';
@@ -828,9 +829,9 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
_AboutSection(
title: '关于我们',
children: [
const _AboutRow(label: '开发', value: '酒库科技有限公司'),
const _AboutRow(label: '官方网站', value: 'https://jiu.example.com'),
const _AboutRow(label: '联系邮箱', value: 'support@jiu.example.com'),
_AboutRow(label: '服务', value: AppInfo.provider),
_AboutRow(label: '官方网站', value: AppInfo.website),
_AboutRow(label: '联系邮箱', value: AppInfo.email),
const _AboutRow(label: '技术支持', value: '周一至周五 9:00 - 18:00'),
const SizedBox(height: 8),
Wrap(
@@ -838,8 +839,8 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
children: [
OutlinedButton.icon(
onPressed: () async {
final uri = Uri.parse('mailto:support@jiu.example.com'
'?subject=酒库管理系统咨询');
final uri = Uri.parse(
'mailto:${AppInfo.email}?subject=酒库管理系统咨询');
if (await canLaunchUrl(uri)) launchUrl(uri);
},
icon: const Icon(Icons.email_outlined, size: 16),
@@ -887,17 +888,14 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
context: context,
builder: (ctx) => AlertDialog(
title: const Text('续费 / 升级授权'),
content: const Column(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('请联系我们获取续费报价:'),
SizedBox(height: 12),
SelectableText('📧 support@jiu.example.com',
style: TextStyle(fontSize: 13)),
SizedBox(height: 6),
SelectableText('📞 400-000-0000',
style: TextStyle(fontSize: 13)),
const Text('请联系我们获取续费报价:'),
const SizedBox(height: 12),
SelectableText('📧 ${AppInfo.email}',
style: const TextStyle(fontSize: 13)),
],
),
actions: [
@@ -908,7 +906,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
ElevatedButton(
onPressed: () async {
await Clipboard.setData(
const ClipboardData(text: 'support@jiu.example.com'));
ClipboardData(text: AppInfo.email));
if (ctx.mounted) {
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
@@ -952,7 +950,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
final subject = Uri.encodeComponent(isBug ? 'Bug反馈' : '功能建议');
final body = Uri.encodeComponent(ctrl.text);
final uri = Uri.parse(
'mailto:support@jiu.example.com?subject=$subject&body=$body');
'mailto:${AppInfo.email}?subject=$subject&body=$body');
if (await canLaunchUrl(uri)) launchUrl(uri);
if (ctx.mounted) Navigator.pop(ctx);
},
+1
View File
@@ -37,3 +37,4 @@ flutter:
assets:
- assets/fonts/NotoSansSC-Regular.ttf
- assets/config/app_info.json
+26
View File
@@ -0,0 +1,26 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/services.dart';
import 'package:pdf/widgets.dart' as pw;
// 回归测试:打印用的中文字体必须是 TrueType(glyf) 轮廓。
// 若误用 OpenType/CFF 字体(如 Noto 的 .otf),dart_pdf 渲染中文会抛
// "This font does not support Unicode characters",标签打印会在原生 onLayout
// 回调里抛异常导致 Windows 打印卡死。此测试确保打印 PDF 能正常生成中文。
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('打印字体能渲染中文(防止退回 CFF 字体导致打印卡死)', () async {
final data = await rootBundle.load('assets/fonts/NotoSansSC-Regular.ttf');
final font = pw.Font.ttf(data);
final doc = pw.Document();
doc.addPage(pw.Page(
build: (_) => pw.Text('入库单 茅台飞天 53度 500ml 扫码溯源',
style: pw.TextStyle(font: font, fontSize: 12)),
));
// 若字体是 CFF,会在此抛 Latin1/Unicode 异常。
final bytes = await doc.save();
expect(bytes.isNotEmpty, true);
});
}
+12 -1
View File
@@ -2,6 +2,8 @@
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include <string>
#include "flutter_window.h"
#include "utils.h"
@@ -27,7 +29,16 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"jiu_client", origin, size)) {
// 窗口标题:用 u8 字面量 + UTF-8→UTF-16 转换,避免源码编码导致中文乱码。
const std::string title_utf8 = u8"岩美酒库";
int title_len = ::MultiByteToWideChar(CP_UTF8, 0, title_utf8.c_str(), -1,
nullptr, 0);
std::wstring window_title(title_len > 0 ? title_len - 1 : 0, L'\0');
if (title_len > 0) {
::MultiByteToWideChar(CP_UTF8, 0, title_utf8.c_str(), -1, &window_title[0],
title_len);
}
if (!window.Create(window_title, origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
+3 -2
View File
@@ -35,8 +35,9 @@ ArchitecturesInstallIn64BitMode=x64
UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExe}
[Languages]
Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
; 说明:未声明 [Languages],使用 Inno Setup 内置英文向导(简体中文语言文件
; ChineseSimplified.isl 默认不随 Inno Setup 安装,依赖它会导致编译找不到文件)。
; app 名称与快捷方式仍为中文(下方 UTF-8 字符串,与向导语言无关)。
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
+4 -1
View File
@@ -60,7 +60,10 @@ fi
cp deploy/windows/jiu-installer.iss "${BUILD_ROOT}/jiu-installer.iss"
mkdir -p "${BUILD_ROOT}/dist"
echo "==> building installer with Inno Setup (version ${VER})"
"$ISCC" \
# MSYS_NO_PATHCONV / MSYS2_ARG_CONV_EXCL disable Git Bash's automatic conversion
# of /-leading args into Windows paths, which otherwise mangles ISCC's /D defines
# (turning them into extra "script filenames").
MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' "$ISCC" \
"/DAppVer=${VER}" \
"/DSrcDir=C:\\jiu-build\\client\\build\\windows\\x64\\runner\\Release" \
"/DOutDir=C:\\jiu-build\\dist" \
+7 -2
View File
@@ -23,9 +23,14 @@ Write-Output "Downloaded $size bytes"
if ($size -lt 1000000) { throw "Downloaded file too small ($size bytes) - not a valid installer" }
Write-Output "Installing silently..."
$p = Start-Process -FilePath $out -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/SP-','/NOICONS' -Wait -PassThru
$log = Join-Path $env:TEMP 'innosetup-install.log'
$p = Start-Process -FilePath $out -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/SP-','/NOICONS',"/LOG=$log" -Wait -PassThru
Write-Output "Installer exit code: $($p.ExitCode)"
if ($p.ExitCode -ne 0) { throw "Inno Setup installer failed with exit code $($p.ExitCode)" }
if ($p.ExitCode -ne 0) {
Write-Output "----- install log tail -----"
if (Test-Path $log) { Get-Content $log -Tail 40 | ForEach-Object { Write-Output $_ } }
throw "Inno Setup installer failed with exit code $($p.ExitCode)"
}
foreach ($c in $candidates) {
if (Test-Path $c) { Write-Output "ISCC installed: $c"; exit 0 }