Compare commits

...

2 Commits

Author SHA1 Message Date
wangjia 3d1402bec8 ci(deploy-client): 改手动触发(去 tag 自动) + Windows 合并为单节点
Design Source Checks / design-source (push) Successful in 4m35s
- 去掉 push tags 自动触发,只留 workflow_dispatch(打 tag 只作标记,发版手动 Run workflow)
  ——弱网+gitea tag 事件飘忽下反复出重复 run/卡死,改手动可控
- build-windows + build-windows-ci 合并为单个 build-windows(runs-on 表达式选 runner +
  step if 决定家里现编/mac 拉 github),DAG 只显示一个节点

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-18 19:53:17 +08:00
wangjia f9ba81ae43 fix(golden): 冻结应用时钟消除时间依赖非确定性(product_detail/license/inventory_check)
golden 失败根因:商品详情屏用实时 DateTime.now() 算「近30天出库」,随真实时间推移
6月流水掉出30天窗口→渲染变化→对不上。license/inventory_check 同理(剩余天数/盘点日期)。
修复:
- product_detail_screen: DateTime.now() → appNow()(可注入时钟)
- golden_harness: 所有 golden 渲染前冻结 appClock=2026-07-01 + tearDown 复位(ds-flow 钉死动态值)
- 重录 license_mobile / m_inventory_check 6 张 golden;product_detail 冻结后自动匹配无需改图
本地 flutter test test/golden/ 全 120 通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-18 19:53:17 +08:00
9 changed files with 25 additions and 40 deletions
+17 -39
View File
@@ -1,11 +1,9 @@
name: Deploy Client
# 发版改为**手动触发**:打 tag 不再自动发版(tag 只作版本标记)。
# 发版时在 gitea「Run workflow」选该版本的 tag 作为 ref 运行 + 选 windows_source。
# (之前 push tags 自动触发在弱网/tag 事件飘忽下反复出重复 run、卡死,故改手动可控。)
on:
push:
tags:
- 'client-v[0-9]*.[0-9]*.[0-9]*'
# 手动运行(在 tag 上 Run workflow)时可指定 Windows 构建来源,无需改仓库变量。
# 走 github 模式:选该版本的 tag 作为 ref 运行 + windows_source=github。
workflow_dispatch:
inputs:
windows_source:
@@ -121,12 +119,12 @@ jobs:
APPSTORE_API_KEY_P8_BASE64: ${{ secrets.APPSTORE_API_KEY_P8_BASE64 }}
run: sh scripts/ci/compile-ios.sh "${{ gitea.ref_name }}"
# 默认:家里那台 Windows runner 构建(回家后照常用)。
# 仅当 gitea 仓库变量 WINDOWS_SOURCE=github 时跳过,改由 build-windows-ci 从 NAS 拉云构建。
# Windows 单一 job(只显示一个节点)。按 windows_source 决定来源:
# home(默认) → 在家里 Windows runner 现编(provision + compile
# github → 在 mac 上从 GitHub Release 拉云构建的安装器(免 frps;家里 Windows 关机时用)
# 来源判定:运行参数 windows_source 优先,其次仓库变量 WINDOWS_SOURCE,默认 home。
build-windows:
# 来源判定:运行参数 windows_source 优先,其次仓库变量 WINDOWS_SOURCE,默认 home。
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') != 'github' }}
runs-on: windows
runs-on: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' && 'mac' || 'windows' }}
env:
GOPROXY: https://goproxy.cn,direct
PUB_HOSTED_URL: https://pub.flutter-io.cn
@@ -135,30 +133,19 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Provision (idempotent — auto-runs if host not initialized)
# —— home 模式:家里 Windows runner 现编 ——
- name: Provision (home windows, idempotent)
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') != 'github' }}
shell: bash
run: sh scripts/ci/provision-windows.sh
- name: Compile (Flutter Windows)
- name: Compile Flutter Windows (home)
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') != 'github' }}
shell: bash
run: sh scripts/ci/compile-windows.sh "${{ gitea.ref_name }}"
- name: Upload windows artifacts
uses: actions/upload-artifact@v3
with:
name: windows
path: dist/
# 特殊:家里 Windows 关机/连不上时,从 GitHub Release 拉云构建的安装器(版本强匹配,免 frps)。
# 启用:gitea 仓库设变量 WINDOWS_SOURCE=github,并先在 GitHub 跑 windows.yml 构建该版本。
# 产出同名 windows artifact,下游 release-deploy-client 无需改动。
build-windows-ci:
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' }}
runs-on: mac
steps:
- uses: actions/checkout@v4
- name: Fetch staged Windows installer from GitHub (免 frps)
# —— github 模式:mac 上从 GitHub Release 拉(免 frps)——
- name: Fetch installer from GitHub (免 frps)
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' }}
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_REPO: bj-wangjia/jiu
@@ -171,16 +158,7 @@ jobs:
path: dist/
release-deploy-client:
needs: [build-client-web, build-macos, build-android, build-ios, build-windows, build-windows-ci]
# 家里/云 两条 Windows 腿只会跑一条(另一条 skipped)。用 always() + 显式成功判定,
# 只要其中一条 windows 成功、且其余端全绿就继续发版。
if: |
always() &&
needs.build-client-web.result == 'success' &&
needs.build-macos.result == 'success' &&
needs.build-android.result == 'success' &&
needs.build-ios.result == 'success' &&
(needs.build-windows.result == 'success' || needs.build-windows-ci.result == 'success')
needs: [build-client-web, build-macos, build-android, build-ios, build-windows]
runs-on: mac
env:
GOPROXY: https://goproxy.cn,direct
@@ -1,5 +1,6 @@
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../core/utils/dialog_util.dart';
import '../../core/utils/clock.dart';
import 'package:file_picker/file_picker.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/foundation.dart';
@@ -395,7 +396,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
final currentStock = _invRows.fold<num>(0, (s, i) => s + i.quantity);
final cost = p.purchasePrice ?? 0;
final stockValue = currentStock * cost;
final now = DateTime.now();
final now = appNow();
final out30 = _logs.where((l) {
if (l.direction != 'out') return false;
final d = DateTime.tryParse(l.createdAt ?? '');
Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

+6
View File
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart' show rootBundle, FontLoader;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jiu_client/core/theme/themes.dart';
import 'package:jiu_client/core/utils/clock.dart';
// 加载真实中文字体(消除 golden 豆腐块),使 golden 能与原型截图做保真 diff。
// 原型截图侧同样注入 Noto Sans SC(见 tools/fidelity.mjs),两边字体一致。
@@ -67,6 +68,11 @@ Future<void> pumpGolden(
double dpr = 2.0,
}) async {
await ensureGoldenFonts();
// 冻结应用时钟:凡走 appNow() 的展示计算(如商品详情「近30天出库」)不再依赖真实时间,
// 保证 golden 确定性(ds-flowgolden 钉死一切动态值)。选 2026-07-01,使 fake 6 月流水
// 稳定落在「近30天」窗口内。tearDown 复位,不影响其它测试。
appClock = () => DateTime(2026, 7, 1, 12, 0);
addTearDown(() => appClock = () => DateTime.now());
tester.view.physicalSize = Size(logical.width * dpr, logical.height * dpr);
tester.view.devicePixelRatio = dpr;
addTearDown(tester.view.resetPhysicalSize);