Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cac84b4388 | |||
| 3d1402bec8 | |||
| f9ba81ae43 | |||
| 067f21c273 | |||
| aa4a3951f7 | |||
| 5eec510f1e | |||
| 161dee17b1 | |||
| c8eabe0596 | |||
| 03772542d2 | |||
| 5cf8958313 | |||
| f6e326794e |
@@ -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:
|
||||
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,34 +133,22 @@ 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 关机/连不上时,从 NAS 暂存拉 GitHub 云构建的安装器(版本强匹配)。
|
||||
# 启用: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 NAS
|
||||
# —— github 模式:mac 上从 GitHub Release 拉(免 frps)——
|
||||
- name: Fetch installer from GitHub (免 frps)
|
||||
if: ${{ (github.event.inputs.windows_source || vars.WINDOWS_SOURCE || 'home') == 'github' }}
|
||||
env:
|
||||
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
|
||||
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPO: bj-wangjia/jiu
|
||||
run: sh scripts/ci/fetch-windows-staged.sh "${{ gitea.ref_name }}"
|
||||
|
||||
- name: Upload windows artifacts
|
||||
@@ -172,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
|
||||
|
||||
@@ -19,6 +19,9 @@ on:
|
||||
- 'scripts/ci/compile-windows.sh'
|
||||
- 'deploy/windows/**'
|
||||
|
||||
permissions:
|
||||
contents: write # 允许内置 GITHUB_TOKEN 创建 Release(winstage-v<版本>)
|
||||
|
||||
jobs:
|
||||
build-stage-windows:
|
||||
runs-on: windows-latest
|
||||
@@ -64,7 +67,23 @@ jobs:
|
||||
path: dist/jiu-windows-x64-setup.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stage installer → NAS gitea (winstage-v<版本>)
|
||||
# 发布到 GitHub Release winstage-v<版本> —— 发版时 mac-runner 从这里(github.com)拉,
|
||||
# 不走 ali frps(off-LAN 时 NAS 经 frps 太慢/易断)。用内置 GITHUB_TOKEN,免额外密钥。
|
||||
- name: Publish installer → GitHub Release (winstage-v<版本>,供 mac-runner 免 frps 拉取)
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
tag="winstage-v${{ steps.ver.outputs.ver }}"
|
||||
gh release delete "$tag" -R "${{ github.repository }}" --yes --cleanup-tag 2>/dev/null || true
|
||||
gh release create "$tag" -R "${{ github.repository }}" \
|
||||
--title "$tag" --notes "Windows 安装器暂存(供 deploy-client 免 frps 拉取)" \
|
||||
dist/jiu-windows-x64-setup.exe
|
||||
|
||||
# NAS 暂存仅作最佳努力的记录(发版实际从 GitHub Release 拉,见 fetch-windows-staged.sh)。
|
||||
# 经 ali frps 上传易失败,不让它 fail 整个构建。
|
||||
- name: Stage installer → NAS gitea (best-effort 记录)
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
env:
|
||||
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
||||
|
||||
@@ -5,6 +5,25 @@ 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.1.9] - 2026-07-18
|
||||
|
||||
### 新功能
|
||||
- 授权管理独立成一级页面,分「授权 / 购买续费 / 订单管理」三个标签。
|
||||
- 购买支持外跳支付页或内嵌二维码扫码付款,可查看/取消订单。
|
||||
- 财务应收应付汇总、财务筛选新增「往来单位」搜索。
|
||||
- 出入库单详情新增「生产日期 / 批次号」列;库存商品详情新增「供应商 / 批次号」。
|
||||
- 出入库列表状态筛选改为多选(默认草稿 + 待审核);新建单自动填充默认仓库。
|
||||
- 出入库列表卡片(手机端)显示品项数。
|
||||
|
||||
### 改进
|
||||
- 慢请求监控(后台记录 >500ms 请求,便于排查卡顿)。
|
||||
|
||||
### 修复
|
||||
- 切换账号后仍显示上一账号数据。
|
||||
- 订单时间按本地时区显示(原为 UTC)。
|
||||
- 「已在运行」提示框、出入库抽屉在特定情况下的闪退。
|
||||
- 支付宝下单因网关地址过长失败。
|
||||
|
||||
## [1.1.8] - 2026-07-10
|
||||
|
||||
### 新功能
|
||||
|
||||
@@ -75,7 +75,9 @@ func (h *StockInHandler) List(c *gin.Context) {
|
||||
query.Count(&total)
|
||||
|
||||
orders := make([]model.StockInOrder, 0)
|
||||
query.Preload("Warehouse", "shop_id = ?", shopID).
|
||||
// 列表卡片需要 items.length 显示品项数(手机端 MCard sub 行)
|
||||
query.Preload("Items").
|
||||
Preload("Warehouse", "shop_id = ?", shopID).
|
||||
Preload("Partner", "shop_id = ?", shopID).
|
||||
Preload("Operator", "shop_id = ?", shopID).
|
||||
Preload("Reviewer", "shop_id = ?", shopID).
|
||||
|
||||
@@ -87,7 +87,9 @@ func (h *StockOutHandler) List(c *gin.Context) {
|
||||
query.Count(&total)
|
||||
|
||||
orders := make([]model.StockOutOrder, 0)
|
||||
query.Preload("Warehouse", "shop_id = ?", shopID).
|
||||
// 列表卡片需要 items.length 显示品项数(手机端 MCard sub 行)
|
||||
query.Preload("Items").
|
||||
Preload("Warehouse", "shop_id = ?", shopID).
|
||||
Preload("Partner", "shop_id = ?", shopID).
|
||||
Preload("Operator", "shop_id = ?", shopID).
|
||||
Preload("Reviewer", "shop_id = ?", shopID).
|
||||
|
||||
@@ -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 ?? '');
|
||||
|
||||
|
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_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-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.devicePixelRatio = dpr;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
'在线':'i-wifi','离线':'i-wifi-off',
|
||||
'进行中':'i-ic04','已完成':'i-check',
|
||||
'未使用':'i-tag','已兑换':'i-check','作废':'i-close',
|
||||
'未结清':'i-ic04','已结清':'i-check','收款':'i-download','付款':'i-upload'
|
||||
'未结清':'i-ic04','已结清':'i-check','收款':'i-download','付款':'i-upload',
|
||||
'待支付':'i-ic04','已支付':'i-check','已关闭':'i-close'
|
||||
};
|
||||
window.BADGE_ICON = BADGE_ICON;
|
||||
function enhanceBadges(){
|
||||
|
||||
@@ -1,53 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# fetch-windows-staged.sh <tag> — 发版时从 NAS gitea 暂存 Release 拉 Windows 安装器。
|
||||
# fetch-windows-staged.sh <tag> — 发版时从 GitHub Release 拉 Windows 安装器。
|
||||
#
|
||||
# 用途:deploy-client.yml 的 build-windows-ci job(仅当 WINDOWS_SOURCE=github 时启用)
|
||||
# 调用此脚本,按发版 tag(client-vX.Y.Z) 的**同一版本号**去找 `winstage-vX.Y.Z`,
|
||||
# 调用此脚本,按发版 tag(client-vX.Y.Z) 的**同一版本号**去 GitHub 找 `winstage-vX.Y.Z`,
|
||||
# 把 jiu-windows-x64-setup.exe 下到 dist/,供后续 upload-artifact → release/deploy 使用。
|
||||
#
|
||||
# **版本强匹配**:找不到对应版本的暂存包 → 硬失败并提示先在 GitHub 构建该版本,
|
||||
# **为什么从 GitHub 而不是 NAS**:mac-runner 常在 off-LAN,NAS 只能经 ali frps(慢又易断);
|
||||
# Windows 包本就是 GitHub 云构建的,直接从 github.com 拉最稳、不过 frps。
|
||||
#
|
||||
# **版本强匹配**:找不到对应版本的 GitHub Release → 硬失败并提示先构建该版本,
|
||||
# 绝不静默发出旧版/错版 Windows 包。
|
||||
#
|
||||
# 需要 env:FORGEJO_URL、FORGEJO_TOKEN、GITEA_REPOSITORY。
|
||||
# 需要 env:GH_TOKEN(github PAT,私有库拉 release 需 auth);GH_REPO(默认 bj-wangjia/jiu)。
|
||||
set -euo pipefail
|
||||
|
||||
. "$(dirname "$0")/lib-forgejo.sh"
|
||||
|
||||
TAG="$1"
|
||||
VER="$(ver_from_tag "$TAG")"
|
||||
VER="${TAG#client-v}"; VER="${VER#v}"
|
||||
STAGE_TAG="winstage-v${VER}"
|
||||
NAME="jiu-windows-x64-setup.exe"
|
||||
GH_REPO="${GH_REPO:-bj-wangjia/jiu}"
|
||||
: "${GH_TOKEN:?需要 GH_TOKEN(github PAT)}"
|
||||
|
||||
echo "==> fetch-windows-staged: 发版版本=${VER},找暂存 ${STAGE_TAG}"
|
||||
base="${FORGEJO_URL}/api/v1/repos/${GITEA_REPOSITORY}"
|
||||
echo "==> fetch-windows-staged: 发版版本=${VER},从 GitHub 找 ${STAGE_TAG}(repo ${GH_REPO})"
|
||||
api="https://api.github.com/repos/${GH_REPO}"
|
||||
mkdir -p dist
|
||||
|
||||
if ! info=$(curl -kfsS -H "Authorization: token ${FORGEJO_TOKEN}" "${base}/releases/tags/${STAGE_TAG}" 2>/dev/null); then
|
||||
if ! info=$(curl -fsSL -H "Authorization: Bearer ${GH_TOKEN}" -H "Accept: application/vnd.github+json" "${api}/releases/tags/${STAGE_TAG}" 2>/dev/null); then
|
||||
cat >&2 <<MSG
|
||||
ERROR: NAS 上找不到暂存 Release ${STAGE_TAG}。
|
||||
ERROR: GitHub 上找不到 Release ${STAGE_TAG}。
|
||||
WINDOWS_SOURCE=github 需要先在 GitHub 构建**版本 ${VER}** 的 Windows 包:
|
||||
gh workflow run windows.yml -f ver=${VER} (仓库 bj-wangjia/jiu)
|
||||
构建完成会把安装器暂存到 NAS,再重跑本次发版即可。
|
||||
gh workflow run windows.yml -f ver=${VER} (仓库 ${GH_REPO})
|
||||
构建完成会发布 Windows Release,再重跑本次发版即可。
|
||||
MSG
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 取同名资产的 browser_download_url(版本已由 STAGE_TAG 精确锁定)。JSON 经 stdin 传入避免转义。
|
||||
# 注意:API 的 /assets/{id} 端点返回的是**元数据 JSON**(非二进制),必须用 browser_download_url。
|
||||
dl=$(printf '%s' "$info" | python3 -c "import sys,json; print(next((a['browser_download_url'] for a in json.load(sys.stdin).get('assets',[]) if a['name']=='${NAME}'), ''))")
|
||||
|
||||
if [ -z "$dl" ]; then
|
||||
# 取同名资产的 GitHub API asset url(用 Accept: octet-stream 拉二进制,302 跳 codeload,走 github.com 不过 frps)
|
||||
aid=$(printf '%s' "$info" | python3 -c "import sys,json; print(next((a['id'] for a in json.load(sys.stdin).get('assets',[]) if a['name']=='${NAME}'), ''))")
|
||||
if [ -z "$aid" ]; then
|
||||
echo "ERROR: ${STAGE_TAG} 存在但缺少 ${NAME} 资产(GitHub 构建可能失败)。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> fetch-windows-staged: 下载 ${dl} → dist/${NAME}"
|
||||
curl -kfsSL -H "Authorization: token ${FORGEJO_TOKEN}" "$dl" -o "dist/${NAME}"
|
||||
echo "==> fetch-windows-staged: 下载 asset id=${aid}(GitHub)→ dist/${NAME}"
|
||||
# 重试 + 断点续传(-C -)+ 低速/超时上限:断了自动续传重来,不会一次瞬断就整个失败。
|
||||
curl -fSL \
|
||||
--retry 30 --retry-delay 5 --retry-all-errors -C - \
|
||||
--connect-timeout 15 --speed-time 60 --speed-limit 1024 --max-time 900 \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" -H "Accept: application/octet-stream" \
|
||||
"${api}/releases/assets/${aid}" -o "dist/${NAME}"
|
||||
ls -lh "dist/${NAME}"
|
||||
|
||||
# 完整性校验:安装器至少应有 1MB,防重定向页/截断被当成有效包发出去。
|
||||
sz=$(wc -c < "dist/${NAME}")
|
||||
if [ "$sz" -lt 1000000 ]; then
|
||||
echo "ERROR: 下载的 ${NAME} 仅 ${sz} 字节,疑似重定向页/损坏,拒绝发版。" >&2
|
||||
echo "ERROR: 下载的 ${NAME} 仅 ${sz} 字节,疑似损坏,拒绝发版。" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> fetch-windows-staged: done(版本 ${VER} 匹配)"
|
||||
echo "==> fetch-windows-staged: done(版本 ${VER} 匹配,源=GitHub)"
|
||||
|
||||