Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cac84b4388 | |||
| 3d1402bec8 | |||
| f9ba81ae43 |
@@ -1,45 +0,0 @@
|
|||||||
name: Design Source Checks
|
|
||||||
|
|
||||||
# 设计真相源检查(治理规则见 CLAUDE.md「设计真相源分层」):
|
|
||||||
# PR 与合并 main 时自动跑:四道静态闸(node + dart format)+ flutter test
|
|
||||||
# (单测 + golden ×3 主题回归自比,2026-07-07 纳入——此前 DoD 写了但无闸)。
|
|
||||||
# fidelity 重型像素闸维持「定期体检」定位,本地手动跑。
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: checks-${{ gitea.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
design-source:
|
|
||||||
runs-on: mac
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Provision (idempotent — auto-runs if host not initialized)
|
|
||||||
run: sh scripts/ci/provision-mac.sh
|
|
||||||
|
|
||||||
- name: 原型设计系统闸(check-ds 12 道)
|
|
||||||
run: node design/prototype/tools/check-ds.mjs
|
|
||||||
|
|
||||||
- name: L1 真相源同源闸(tokens 快照 / icons / 官网 token 值 / web hex)
|
|
||||||
run: node tools/check-l1-sync.mjs
|
|
||||||
|
|
||||||
- name: Flutter 颜色单源闸(check_ds_code)
|
|
||||||
run: node client/tool/check_ds_code.mjs
|
|
||||||
|
|
||||||
- name: token codegen 新鲜度(regen → format → 与入库产物零 diff)
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
node tool/gen_tokens.mjs
|
|
||||||
dart format lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart
|
|
||||||
git diff --exit-code -- lib/core/theme/app_tokens.g.dart lib/core/theme/app_dims.g.dart lib/core/theme/app_chrome.g.dart
|
|
||||||
|
|
||||||
- name: Flutter 测试(单测 + golden ×3 主题回归自比)
|
|
||||||
run: |
|
|
||||||
cd client
|
|
||||||
flutter test
|
|
||||||
@@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'client-v[0-9]*.[0-9]*.[0-9]*'
|
- 'client-v[0-9]*.[0-9]*.[0-9]*'
|
||||||
# 手动运行(在 tag 上 Run workflow)时可指定 Windows 构建来源,无需改仓库变量。
|
|
||||||
# 走 github 模式:选该版本的 tag 作为 ref 运行 + windows_source=github。
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
windows_source:
|
windows_source:
|
||||||
@@ -121,12 +119,12 @@ jobs:
|
|||||||
APPSTORE_API_KEY_P8_BASE64: ${{ secrets.APPSTORE_API_KEY_P8_BASE64 }}
|
APPSTORE_API_KEY_P8_BASE64: ${{ secrets.APPSTORE_API_KEY_P8_BASE64 }}
|
||||||
run: sh scripts/ci/compile-ios.sh "${{ gitea.ref_name }}"
|
run: sh scripts/ci/compile-ios.sh "${{ gitea.ref_name }}"
|
||||||
|
|
||||||
# 默认:家里那台 Windows runner 构建(回家后照常用)。
|
# Windows 单一 job(只显示一个节点)。按 windows_source 决定来源:
|
||||||
# 仅当 gitea 仓库变量 WINDOWS_SOURCE=github 时跳过,改由 build-windows-ci 从 NAS 拉云构建。
|
# home(默认) → 在家里 Windows runner 现编(provision + compile)
|
||||||
|
# github → 在 mac 上从 GitHub Release 拉云构建的安装器(免 frps;家里 Windows 关机时用)
|
||||||
|
# 来源判定:运行参数 windows_source 优先,其次仓库变量 WINDOWS_SOURCE,默认 home。
|
||||||
build-windows:
|
build-windows:
|
||||||
# 来源判定:运行参数 windows_source 优先,其次仓库变量 WINDOWS_SOURCE,默认 home。
|
runs-on: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' && 'mac' || 'windows' }}
|
||||||
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') != 'github' }}
|
|
||||||
runs-on: windows
|
|
||||||
env:
|
env:
|
||||||
GOPROXY: https://goproxy.cn,direct
|
GOPROXY: https://goproxy.cn,direct
|
||||||
PUB_HOSTED_URL: https://pub.flutter-io.cn
|
PUB_HOSTED_URL: https://pub.flutter-io.cn
|
||||||
@@ -135,30 +133,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
shell: bash
|
||||||
run: sh scripts/ci/provision-windows.sh
|
run: sh scripts/ci/provision-windows.sh
|
||||||
|
- name: Compile Flutter Windows (home)
|
||||||
- name: Compile (Flutter Windows)
|
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') != 'github' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: sh scripts/ci/compile-windows.sh "${{ gitea.ref_name }}"
|
run: sh scripts/ci/compile-windows.sh "${{ gitea.ref_name }}"
|
||||||
|
|
||||||
- name: Upload windows artifacts
|
# —— github 模式:mac 上从 GitHub Release 拉(免 frps)——
|
||||||
uses: actions/upload-artifact@v3
|
- name: Fetch installer from GitHub (免 frps)
|
||||||
with:
|
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' }}
|
||||||
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)
|
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
GH_REPO: bj-wangjia/jiu
|
GH_REPO: bj-wangjia/jiu
|
||||||
@@ -171,16 +158,7 @@ jobs:
|
|||||||
path: dist/
|
path: dist/
|
||||||
|
|
||||||
release-deploy-client:
|
release-deploy-client:
|
||||||
needs: [build-client-web, build-macos, build-android, build-ios, build-windows, build-windows-ci]
|
needs: [build-client-web, build-macos, build-android, build-ios, build-windows]
|
||||||
# 家里/云 两条 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')
|
|
||||||
runs-on: mac
|
runs-on: mac
|
||||||
env:
|
env:
|
||||||
GOPROXY: https://goproxy.cn,direct
|
GOPROXY: https://goproxy.cn,direct
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
import '../../core/utils/dialog_util.dart';
|
import '../../core/utils/dialog_util.dart';
|
||||||
|
import '../../core/utils/clock.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:flutter/foundation.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 currentStock = _invRows.fold<num>(0, (s, i) => s + i.quantity);
|
||||||
final cost = p.purchasePrice ?? 0;
|
final cost = p.purchasePrice ?? 0;
|
||||||
final stockValue = currentStock * cost;
|
final stockValue = currentStock * cost;
|
||||||
final now = DateTime.now();
|
final now = appNow();
|
||||||
final out30 = _logs.where((l) {
|
final out30 = _logs.where((l) {
|
||||||
if (l.direction != 'out') return false;
|
if (l.direction != 'out') return false;
|
||||||
final d = DateTime.tryParse(l.createdAt ?? '');
|
final d = DateTime.tryParse(l.createdAt ?? '');
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart' show rootBundle, FontLoader;
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:jiu_client/core/theme/themes.dart';
|
import 'package:jiu_client/core/theme/themes.dart';
|
||||||
|
import 'package:jiu_client/core/utils/clock.dart';
|
||||||
|
|
||||||
// 加载真实中文字体(消除 golden 豆腐块),使 golden 能与原型截图做保真 diff。
|
// 加载真实中文字体(消除 golden 豆腐块),使 golden 能与原型截图做保真 diff。
|
||||||
// 原型截图侧同样注入 Noto Sans SC(见 tools/fidelity.mjs),两边字体一致。
|
// 原型截图侧同样注入 Noto Sans SC(见 tools/fidelity.mjs),两边字体一致。
|
||||||
@@ -67,6 +68,11 @@ Future<void> pumpGolden(
|
|||||||
double dpr = 2.0,
|
double dpr = 2.0,
|
||||||
}) async {
|
}) async {
|
||||||
await ensureGoldenFonts();
|
await ensureGoldenFonts();
|
||||||
|
// 冻结应用时钟:凡走 appNow() 的展示计算(如商品详情「近30天出库」)不再依赖真实时间,
|
||||||
|
// 保证 golden 确定性(ds-flow:golden 钉死一切动态值)。选 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.physicalSize = Size(logical.width * dpr, logical.height * dpr);
|
||||||
tester.view.devicePixelRatio = dpr;
|
tester.view.devicePixelRatio = dpr;
|
||||||
addTearDown(tester.view.resetPhysicalSize);
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
|
|||||||