Compare commits

...

3 Commits

Author SHA1 Message Date
wangjia 02082df525 chore: release v1.0.35
Deploy / build-linux-web (push) Successful in 52s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m26s
Deploy / build-android (push) Successful in 1m32s
Deploy / build-ios (push) Successful in 7s
Deploy / release-deploy (push) Successful in 1m51s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:51:22 +08:00
wangjia 4c75f328c7 chore: release v1.0.34
Deploy / build-linux-web (push) Successful in 46s
Deploy / build-windows (push) Successful in 1m47s
Deploy / build-macos (push) Successful in 1m22s
Deploy / build-android (push) Successful in 59s
Deploy / build-ios (push) Successful in 6s
Deploy / release-deploy (push) Successful in 1m47s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:28:01 +08:00
wangjia 8628df5848 chore: release v1.0.33
Deploy / build-linux-web (push) Successful in 49s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m28s
Deploy / build-android (push) Successful in 1m9s
Deploy / build-ios (push) Successful in 6s
Deploy / release-deploy (push) Successful in 1m50s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:10:58 +08:00
9 changed files with 648 additions and 167 deletions
+18
View File
@@ -5,6 +5,24 @@ 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.35] - 2026-06-12
### 改进
- Debug 版本窗口标题增加 [DEBUG] 后缀,便于与正式安装版区分
## [1.0.34] - 2026-06-12
### 修复
- 修复 macOS 客户端打印时应用卡死、只能强制退出的问题
### 改进
- 「关于我们」中「打开文档」「更新日志」等链接指向新域名 jiu.51yanmei.com
## [1.0.33] - 2026-06-12
### 改进
- macOS 客户端插件集成从 CocoaPods 迁移至 Swift Package Manager,优化 macOS 构建流程
## [1.0.32] - 2026-06-11
### 修复
+4 -4
View File
@@ -1,10 +1,10 @@
{
"provider": "岩美技术有限公司",
"website": "https://jiu.yanmei.com",
"website": "https://jiu.51yanmei.com",
"email": "yammy2023@163.com",
"phone": "",
"wechat": "",
"terms_url": "https://jiu.yanmei.com/terms/",
"privacy_url": "https://jiu.yanmei.com/privacy/",
"docs_url": "https://jiu.yanmei.com/docs/"
"terms_url": "https://jiu.51yanmei.com/terms/",
"privacy_url": "https://jiu.51yanmei.com/privacy/",
"docs_url": "https://jiu.51yanmei.com/docs/"
}
+19 -3
View File
@@ -1,3 +1,5 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart';
import '../errors/error_reporter.dart';
import 'package:pdf/pdf.dart';
@@ -198,8 +200,14 @@ Future<void> printProductLabelImpl({
),
));
// 预生成 PDF 字节,避免 macOS NSPrintPanel 打开后再调 doc.save() 导致主线程卡住。
final bytes = await doc.save();
if (!kIsWeb && Platform.isMacOS) {
// 让当前帧完成,再打开 macOS 原生打印面板,防止 sheet 动画与 Flutter 渲染死锁。
await Future<void>.delayed(Duration.zero);
}
await Printing.layoutPdf(
name: '标签_${_fileTs()}', onLayout: (_) async => doc.save());
name: '标签_${_fileTs()}', onLayout: (_) async => bytes);
} catch (e, st) {
reportError(e, st);
throw Exception('打印失败,请检查打印机连接和驱动是否正常。\n详情:$e');
@@ -439,8 +447,12 @@ Future<void> printStockInOrderImpl(StockInOrder order) async {
),
],
));
final bytes = await doc.save();
if (!kIsWeb && Platform.isMacOS) {
await Future<void>.delayed(Duration.zero);
}
await Printing.layoutPdf(
name: '入库单_${_fileTs()}', onLayout: (_) async => doc.save());
name: '入库单_${_fileTs()}', onLayout: (_) async => bytes);
} catch (e, st) {
reportError(e, st);
throw Exception('打印失败,请检查打印机连接和驱动是否正常。\n详情:$e');
@@ -495,8 +507,12 @@ Future<void> printStockOutOrderImpl(StockOutOrder order) async {
],
));
try {
final bytes = await doc.save();
if (!kIsWeb && Platform.isMacOS) {
await Future<void>.delayed(Duration.zero);
}
await Printing.layoutPdf(
name: '出库单_${_fileTs()}', onLayout: (_) async => doc.save());
name: '出库单_${_fileTs()}', onLayout: (_) async => bytes);
} catch (e, st) {
reportError(e, st);
throw Exception('打印失败,请检查打印机连接和驱动是否正常。\n详情:$e');
+2 -1
View File
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -107,7 +108,7 @@ class _JiuAppState extends ConsumerState<JiuApp> {
Widget build(BuildContext context) {
final router = ref.watch(appRouterProvider);
return MaterialApp.router(
title: '岩美',
title: kDebugMode ? '岩美 [DEBUG]' : '岩美',
theme: AppTheme.light(),
routerConfig: router,
debugShowCheckedModeBanner: false,
-25
View File
@@ -1,46 +1,21 @@
PODS:
- file_picker (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- package_info_plus (0.0.1):
- FlutterMacOS
- printing (1.0.0):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
DEPENDENCIES:
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- printing (from `Flutter/ephemeral/.symlinks/plugins/printing/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
EXTERNAL SOURCES:
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
FlutterMacOS:
:path: Flutter/ephemeral
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
printing:
:path: Flutter/ephemeral/.symlinks/plugins/printing/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
printing: c4cf83c78fd684f9bc318e6aadc18972aa48f617
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
@@ -29,6 +29,7 @@
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
7CC0DF083710AA9C43B56A42 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35335B030AEE727449F09D5 /* Pods_Runner.framework */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -88,6 +89,7 @@
B35335B030AEE727449F09D5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B6FABD6521DBD25CA93F2ABB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
D768E6B98F41FC79E2D5D343 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -103,6 +105,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
7CC0DF083710AA9C43B56A42 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -164,6 +167,7 @@
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
@@ -231,6 +235,9 @@
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
@@ -256,6 +263,9 @@
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
@@ -796,6 +806,18 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}
@@ -5,6 +5,24 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "&quot;$FLUTTER_ROOT&quot;/packages/flutter_tools/bin/macos_assemble.sh prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "jiu_client.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
+406 -126
View File
@@ -223,10 +223,10 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<h1>酒库管理系统 — 项目 TODO</h1>
<div class="header-meta">生成于 2026-06-11 · 真相源 todo/todo.json</div>
<div class="stats">
<div class="stat-pill"><strong>36</strong>全部</div>
<div class="stat-pill"><strong>5</strong>待开始</div>
<div class="stat-pill"><strong>46</strong>全部</div>
<div class="stat-pill"><strong>1</strong>待开始</div>
<div class="stat-pill"><strong>0</strong>开发中</div>
<div class="stat-pill"><strong>17</strong>待验收</div>
<div class="stat-pill"><strong>31</strong>待验收</div>
<div class="stat-pill"><strong>14</strong>已验收</div>
</div>
</div>
@@ -260,7 +260,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="filter-sep"></div>
<div class="filter-group">
<span class="filter-label">平台 / 标签</span>
<button class="filter-chip" data-filter-tag="Android">Android</button><button class="filter-chip" data-filter-tag="Web">Web</button><button class="filter-chip" data-filter-tag="Windows">Windows</button><button class="filter-chip" data-filter-tag="iOS">iOS</button><button class="filter-chip" data-filter-tag="mac">mac</button><button class="filter-chip" data-filter-tag="前端">前端</button><button class="filter-chip" data-filter-tag="后端">后端</button><button class="filter-chip" data-filter-tag="安全">安全</button><button class="filter-chip" data-filter-tag="数据库">数据库</button><button class="filter-chip" data-filter-tag="文档">文档</button>
<button class="filter-chip" data-filter-tag="Android">Android</button><button class="filter-chip" data-filter-tag="CI/CD">CI/CD</button><button class="filter-chip" data-filter-tag="Web">Web</button><button class="filter-chip" data-filter-tag="Windows">Windows</button><button class="filter-chip" data-filter-tag="iOS">iOS</button><button class="filter-chip" data-filter-tag="mac">mac</button><button class="filter-chip" data-filter-tag="前端">前端</button><button class="filter-chip" data-filter-tag="后端">后端</button><button class="filter-chip" data-filter-tag="安全">安全</button><button class="filter-chip" data-filter-tag="数据库">数据库</button><button class="filter-chip" data-filter-tag="文档">文档</button>
</div>
</div>
@@ -269,147 +269,35 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="section-block" id="section-open">
<div class="section-title st-open" data-toggle="open">
📋 待开始 <span class="s-count">5</span>
📋 待开始 <span class="s-count">1</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-open">
<ul class="todo-list" id="list-open">
<li class="todo-card s-open"
data-id="2"
data-level="high"
data-status="open"
data-tier=""
data-tags="Web">
<div class="card-header">
<span class="item-title">备案号为占位假值</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-block">高优 · 紧急</span>
</div>
</div>
<div class="item-desc">现为沪ICP备2026000000号(全0),上线前必须替换真实备案号。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="Web">Web</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-08</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="23"
data-level="low"
data-status="open"
data-tier=""
data-tags="前端,Web">
<div class="card-header">
<span class="item-title">注册/联系入口「发邮件」升级为「加客服微信」并优化文案</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-low">一般 / 优化</span>
</div>
</div>
<div class="item-desc">当前注册成功页和网站联系支持入口引导用户「发邮件」,体验较弱。升级为扫码加客服微信,同时梳理相关文案措辞,使表达更专业、有亲和力。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-08</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="38"
data-level="low"
data-id="50"
data-level="mid"
data-status="open"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">将拼音回填从启动逻辑改为一次性迁移脚本</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
</div>
</div>
<div class="item-desc">main.go 每次启动执行 backfillPinyin 全表扫描,数据量大后拖慢启动。改为 go run ./cmd/migrate-pinyin 一次性迁移,后续新数据靠写入时自动生成</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="39"
data-level="low"
data-status="open"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">DB 连接池参数(MaxIdleConns/MaxOpenConns)改为可配置项</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
</div>
</div>
<div class="item-desc">main.go 中硬编码 MaxIdleConns=10, MaxOpenConns=100,无法运维调优。改为读取 config(viper),并在文档中说明推荐值</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="40"
data-level="low"
data-status="open"
data-tier="2"
data-tags="后端,数据库">
<div class="card-header">
<span class="item-title">明确 Inventories 反范式字段语义:是当前值还是入库快照</span>
<span class="item-title">License model 的 license_key 索引超 InnoDB 3072 字节限制需改用前缀索引</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-2"></span>
<span class="tag t-high">重要</span>
<span class="tag tier-3"></span>
</div>
</div>
<div class="item-desc">inventories 表存了 product_name/warehouse_name/supplier_name 等,既可能是冗余(与 products JOIN 重复),也可能是历史快照(入库时锁定)。需明确语义并在 model 注释中说明;若是快照,应只在写入时固定,不随主表变化</div>
<div class="item-desc">license.go 的 LicenseKey 是 size:2048+uniqueIndexutf8mb4 下 8192 字节超 MySQL InnoDB 索引上限,全新环境 AutoMigrate 必失败(线上已手动建 license_key(768) 前缀索引绕过)。需在 model 层固化:缩短列或 GORM 指定索引前缀长度,并同步 schema.sql。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
@@ -430,7 +318,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
<div class="section-block" id="section-done">
<div class="section-title st-done" data-toggle="done">
🔍 待验收 <span class="s-count">17</span>
🔍 待验收 <span class="s-count">31</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-done">
@@ -548,6 +436,62 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
</li>
<li class="todo-card s-done"
data-id="41"
data-level="high"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">CheckInventoryAvailability Scan错误未检查导致所有商品误报库存不足</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-block">高优 · 紧急</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="41" data-title="CheckInventoryAvailability Scan错误未检查导致所有商品误报库存不足">拒绝验收</button>
</div>
</div>
<div class="item-desc">service/stock.go:312 DB查询失败时sums为空,所有商品have=0,合法出库审核被全部拒绝</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="49"
data-level="high"
data-status="done"
data-tier="2"
data-tags="CI/CD">
<div class="card-header">
<span class="item-title">修复 act_runner→Forgejo 任务状态上报丢失导致构建成功却标失败</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-block">高优 · 紧急</span>
<span class="tag tier-2">二级</span>
<button class="reject-btn" data-id="49" data-title="修复 act_runner→Forgejo 任务状态上报丢失导致构建成功却标失败">拒绝验收</button>
</div>
</div>
<div class="item-desc">v1.0.27~v1.0.32 连续 6 个版本 CI 全部'失败',但日志显示所有 job 实际构建成功(🏁 Job succeeded),是 act_runner 最终状态上报丢失(Failed to write EOT),被 Forgejo 僵尸任务清理标为 failure。生产环境因此停留在 v1.0.266/8)。需排查 relay/GOAWAY race 或升级 act_runner/Forgejo。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="CI/CD">CI/CD</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="20"
data-level="mid"
@@ -772,6 +716,118 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
</li>
<li class="todo-card s-done"
data-id="42"
data-level="mid"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">finance ListRecords 和 inventory Logs handler 缺 pageSize 上限</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="42" data-title="finance ListRecords 和 inventory Logs handler 缺 pageSize 上限">拒绝验收</button>
</div>
</div>
<div class="item-desc">finance.go:36 和 inventory.go:130 漏加 ValidatePageSize#37 未覆盖这两个 handler,可被任意用户触发全表扫描</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="43"
data-level="mid"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">ReconcileInventory 两条 Scan 错误未检查导致对账误判</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="43" data-title="ReconcileInventory 两条 Scan 错误未检查导致对账误判">拒绝验收</button>
</div>
</div>
<div class="item-desc">admin.go:122-123 invQuery/logQuery Scan错误被丢弃;invQuery失败→差异消失显示全部正常;logQuery失败→所有库存飘红为差异</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="44"
data-level="mid"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">license Info 端点 device_count 统计错误且 ListDevices 错误被吞</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="44" data-title="license Info 端点 device_count 统计错误且 ListDevices 错误被吞">拒绝验收</button>
</div>
</div>
<div class="item-desc">handler/license.go:67-70 两个bug1.devs,_ 错误被_吞掉DB故障时deviceCount=02.ListDevices(shopID)包含该店所有license设备而非当前license</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="2"
data-level="low"
data-status="done"
data-tier=""
data-tags="Web">
<div class="card-header">
<span class="item-title">备案号为占位假值</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<button class="reject-btn" data-id="2" data-title="备案号为占位假值">拒绝验收</button>
</div>
</div>
<div class="item-desc">现为沪ICP备2026000000号(全0),上线前必须替换真实备案号。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="Web">Web</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-08</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="21"
data-level="low"
@@ -882,6 +938,34 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
</li>
<li class="todo-card s-done"
data-id="23"
data-level="low"
data-status="done"
data-tier=""
data-tags="前端,Web">
<div class="card-header">
<span class="item-title">注册/联系入口「发邮件」升级为「加客服微信」并优化文案</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<button class="reject-btn" data-id="23" data-title="注册/联系入口「发邮件」升级为「加客服微信」并优化文案">拒绝验收</button>
</div>
</div>
<div class="item-desc">当前注册成功页和网站联系支持入口引导用户「发邮件」,体验较弱。升级为扫码加客服微信,同时梳理相关文案措辞,使表达更专业、有亲和力。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-08</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="24"
data-level="low"
@@ -962,6 +1046,202 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="item-meta">
<span class="meta-date">🕐 2026-06-08</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="38"
data-level="low"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">将拼音回填从启动逻辑改为一次性迁移脚本</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="38" data-title="将拼音回填从启动逻辑改为一次性迁移脚本">拒绝验收</button>
</div>
</div>
<div class="item-desc">main.go 每次启动执行 backfillPinyin 全表扫描,数据量大后拖慢启动。改为 go run ./cmd/migrate-pinyin 一次性迁移,后续新数据靠写入时自动生成</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="39"
data-level="low"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">DB 连接池参数(MaxIdleConns/MaxOpenConns)改为可配置项</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="39" data-title="DB 连接池参数(MaxIdleConns/MaxOpenConns)改为可配置项">拒绝验收</button>
</div>
</div>
<div class="item-desc">main.go 中硬编码 MaxIdleConns=10, MaxOpenConns=100,无法运维调优。改为读取 config(viper),并在文档中说明推荐值</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="40"
data-level="low"
data-status="done"
data-tier="2"
data-tags="后端,数据库">
<div class="card-header">
<span class="item-title">明确 Inventories 反范式字段语义:是当前值还是入库快照</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-2">二级</span>
<button class="reject-btn" data-id="40" data-title="明确 Inventories 反范式字段语义:是当前值还是入库快照">拒绝验收</button>
</div>
</div>
<div class="item-desc">inventories 表存了 product_name/warehouse_name/supplier_name 等,既可能是冗余(与 products JOIN 重复),也可能是历史快照(入库时锁定)。需明确语义并在 model 注释中说明;若是快照,应只在写入时固定,不随主表变化</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-10</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="45"
data-level="low"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">license Activate 幂等路径 Update 错误未检查</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="45" data-title="license Activate 幂等路径 Update 错误未检查">拒绝验收</button>
</div>
</div>
<div class="item-desc">service/license.go:70 s.db.Model(&amp;existing).Update()失败时仍返回nil error,设备名未更新但调用方不知道</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="46"
data-level="low"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">license Verify 加载 license 缺 shop_id 过滤(防御纵深)</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="46" data-title="license Verify 加载 license 缺 shop_id 过滤(防御纵深)">拒绝验收</button>
</div>
</div>
<div class="item-desc">service/license.go:102 WHERE id=? AND is_active=1 应加 AND shop_id=? 防御数据异常时跨租户泄露</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="47"
data-level="low"
data-status="done"
data-tier="2"
data-tags="后端">
<div class="card-header">
<span class="item-title">util/response.go 的 RespondError 等辅助函数从未被调用(#34 实际未完成)</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-2">二级</span>
<button class="reject-btn" data-id="47" data-title="util/response.go 的 RespondError 等辅助函数从未被调用(#34 实际未完成)">拒绝验收</button>
</div>
</div>
<div class="item-desc">#34 只创建了util/response.go,没有将任何handler迁移过去,实际错误格式仍不统一,需完成迁移</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="48"
data-level="low"
data-status="done"
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-title">清理 ErrDeviceMismatch 死代码(多设备迁移后残留)</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
<button class="reject-btn" data-id="48" data-title="清理 ErrDeviceMismatch 死代码(多设备迁移后残留)">拒绝验收</button>
</div>
</div>
<div class="item-desc">service/license.go:24 ErrDeviceMismatch定义后从未返回,多设备迁移(#32)后已无单设备约束</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-11</span>
</div>
</div>
</li>
@@ -1521,7 +1801,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
return;
}
const escaped = reason.replace(/\/g, '\\').replace(/"/g, '\"');
const cmd = 'node todo/todo.mjs reject ' + rejectId + ' --reason "' + escaped + '"';
const cmd = '/todo reject ' + rejectId + ' ' + escaped;
cmdText.textContent = cmd;
cmdWrap.style.display = 'block';
document.getElementById('reject-confirm-btn').style.display = 'none';
+159 -8
View File
@@ -1,9 +1,9 @@
{
"meta": {
"title": "酒库管理系统 — 项目 TODO",
"updated_at": "2026-06-10T16:38:37.763Z"
"updated_at": "2026-06-11T15:52:40.354Z"
},
"seq": 40,
"seq": 50,
"items": [
{
"id": 1,
@@ -24,7 +24,7 @@
"id": 2,
"title": "备案号为占位假值",
"desc": "现为沪ICP备2026000000号(全0),上线前必须替换真实备案号。",
"level": "high",
"level": "low",
"tags": [
"Web"
],
@@ -32,7 +32,7 @@
"done": false,
"completed_at": null,
"version": null,
"status": "open"
"status": "done"
},
{
"id": 3,
@@ -404,7 +404,7 @@
"done": false,
"completed_at": null,
"version": null,
"status": "open"
"status": "done"
},
{
"id": 24,
@@ -613,7 +613,7 @@
"tags": [
"后端"
],
"status": "open",
"status": "done",
"created_at": "2026-06-10T15:31:57.979Z",
"done": false,
"completed_at": null,
@@ -628,7 +628,7 @@
"tags": [
"后端"
],
"status": "open",
"status": "done",
"created_at": "2026-06-10T15:32:00.233Z",
"done": false,
"completed_at": null,
@@ -644,11 +644,162 @@
"后端",
"数据库"
],
"status": "open",
"status": "done",
"created_at": "2026-06-10T15:32:03.641Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 41,
"title": "CheckInventoryAvailability Scan错误未检查导致所有商品误报库存不足",
"desc": "service/stock.go:312 DB查询失败时sums为空,所有商品have=0,合法出库审核被全部拒绝",
"level": "high",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:34.919Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 42,
"title": "finance ListRecords 和 inventory Logs handler 缺 pageSize 上限",
"desc": "finance.go:36 和 inventory.go:130 漏加 ValidatePageSize#37 未覆盖这两个 handler,可被任意用户触发全表扫描",
"level": "mid",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:46.152Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 43,
"title": "ReconcileInventory 两条 Scan 错误未检查导致对账误判",
"desc": "admin.go:122-123 invQuery/logQuery Scan错误被丢弃;invQuery失败→差异消失显示全部正常;logQuery失败→所有库存飘红为差异",
"level": "mid",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:46.205Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 44,
"title": "license Info 端点 device_count 统计错误且 ListDevices 错误被吞",
"desc": "handler/license.go:67-70 两个bug1.devs,_ 错误被_吞掉DB故障时deviceCount=02.ListDevices(shopID)包含该店所有license设备而非当前license",
"level": "mid",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:46.257Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 45,
"title": "license Activate 幂等路径 Update 错误未检查",
"desc": "service/license.go:70 s.db.Model(&existing).Update()失败时仍返回nil error,设备名未更新但调用方不知道",
"level": "low",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:59.016Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 46,
"title": "license Verify 加载 license 缺 shop_id 过滤(防御纵深)",
"desc": "service/license.go:102 WHERE id=? AND is_active=1 应加 AND shop_id=? 防御数据异常时跨租户泄露",
"level": "low",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:59.066Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 47,
"title": "util/response.go 的 RespondError 等辅助函数从未被调用(#34 实际未完成)",
"desc": "#34 只创建了util/response.go,没有将任何handler迁移过去,实际错误格式仍不统一,需完成迁移",
"level": "low",
"tier": 2,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:59.118Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 48,
"title": "清理 ErrDeviceMismatch 死代码(多设备迁移后残留)",
"desc": "service/license.go:24 ErrDeviceMismatch定义后从未返回,多设备迁移(#32)后已无单设备约束",
"level": "low",
"tier": 3,
"tags": [
"后端"
],
"status": "done",
"created_at": "2026-06-10T23:59:59.168Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 49,
"title": "修复 act_runner→Forgejo 任务状态上报丢失导致构建成功却标失败",
"desc": "v1.0.27~v1.0.32 连续 6 个版本 CI 全部'失败',但日志显示所有 job 实际构建成功(🏁 Job succeeded),是 act_runner 最终状态上报丢失(Failed to write EOT),被 Forgejo 僵尸任务清理标为 failure。生产环境因此停留在 v1.0.266/8)。需排查 relay/GOAWAY race 或升级 act_runner/Forgejo。",
"level": "high",
"tier": 2,
"tags": [
"CI/CD"
],
"status": "done",
"created_at": "2026-06-11T13:55:07.388Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 50,
"title": "License model 的 license_key 索引超 InnoDB 3072 字节限制需改用前缀索引",
"desc": "license.go 的 LicenseKey 是 size:2048+uniqueIndexutf8mb4 下 8192 字节超 MySQL InnoDB 索引上限,全新环境 AutoMigrate 必失败(线上已手动建 license_key(768) 前缀索引绕过)。需在 model 层固化:缩短列或 GORM 指定索引前缀长度,并同步 schema.sql。",
"level": "mid",
"tier": 3,
"tags": [
"后端",
"数据库"
],
"status": "open",
"created_at": "2026-06-11T15:52:40.353Z",
"done": false,
"completed_at": null,
"version": null
}
]
}