chore: release v1.0.18
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s

移动端响应式适配(抽屉导航/列表卡片/弹窗自适应)、Android 正式签名与 APK 发布、
iOS(TestFlight) 工程与 CI、多平台构建流水线、相关文档同步。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-07 07:55:33 +08:00
parent 94f0fb2095
commit 480ce836bb
81 changed files with 3096 additions and 366 deletions
+14
View File
@@ -0,0 +1,14 @@
# TODO(待办 / 后续迭代)
本文件收集已明确「本期不做、后续再做」的事项,避免遗漏。
## 移动端适配
- [ ] **iOS 上架与分发(待 Apple 账号)**iOS 工程与 TestFlight 流水线已就绪(见 `docs/ios-signing.md`),待注册 Apple Developer 账号、配置证书/Profile/API Key 等 Forgejo secrets 后即可自动上传 TestFlight;后续可考虑 App Store 正式上架。
- [ ] **Android 应用内 APK 静默安装**:当前 Android「立即更新」仅打开下载链接;后续做应用内下载 APK + `REQUEST_INSTALL_PACKAGES` 触发安装。
- [ ] **平板专属三栏布局**:当前仅手机窄屏 + 桌面两档;平板(600–1200px)后续可做更优的三栏/双栏布局。
## 意见反馈(v1.0.17 已上线)
- [ ] **管理端反馈查看 UI**:当前仅有 admin API`GET /api/v1/admin/feedback``PATCH /api/v1/admin/feedback/:id`),app 内尚无超管查看/处理反馈的界面。
- [ ] **新反馈通知**:提交反馈后暂无 Telegram/邮件通知,后续可加。
+51
View File
@@ -0,0 +1,51 @@
# Android 正式签名 — 一次性配置
CI 用正式 release keystore 给 APK 签名。keystore **不入库**,通过 Forgejo Secrets 注入。
本文档是一次性操作,配置完成后每次打 tag 自动签名打包。
> 本地构建无需做这些:缺少 `key.properties` 时 `flutter build apk` 自动回退 debug 签名。
## 1. 生成 keystore(本地执行一次)
```bash
keytool -genkeypair -v \
-keystore jiu-release.jks \
-alias jiu \
-keyalg RSA -keysize 2048 -validity 10000 \
-storepass '<你的store密码>' \
-keypass '<你的key密码>' \
-dname "CN=Yanmei, OU=Jiu, O=Yanmei, L=, ST=, C=CN"
```
**务必妥善保管 `jiu-release.jks` 与两个密码**:一旦丢失,已安装用户将无法收到覆盖升级(签名不匹配)。
建议把 `jiu-release.jks` 备份到 `~/.env` 同级的安全位置(勿提交到仓库)。
## 2. 生成 base64(供 CI 注入)
```bash
base64 -i jiu-release.jks | pbcopy # macOS:已复制到剪贴板
```
## 3. 在 Forgejo 仓库配置 Secrets
仓库 → Settings → Actions → Secrets,新增 4 项:
| Secret 名 | 值 |
|-----------|-----|
| `ANDROID_KEYSTORE_BASE64` | 第 2 步的 base64 字符串 |
| `ANDROID_KEYSTORE_PASSWORD` | store 密码 |
| `ANDROID_KEY_ALIAS` | `jiu` |
| `ANDROID_KEY_PASSWORD` | key 密码 |
## 4. 验证
打一个 tag 触发流水线,确认:
- `build-android` job 绿,产出 `jiu-android.apk`
- 日志出现 `configuring release signing`(而非 `falling back to debug signing`);
- 下载安装后,下次升级能覆盖安装(签名一致)。
## 工作原理
- `client/android/app/build.gradle.kts`:存在 `key.properties` 则用正式签名,否则回退 debug。
- `scripts/ci/compile-android.sh`:从 `ANDROID_KEYSTORE_BASE64` 还原 keystore,写出 `key.properties`,构建后清理。
- `key.properties``*.jks``*.keystore` 已在 `client/android/.gitignore` 中忽略。
+68 -3
View File
@@ -40,7 +40,8 @@ jiu/
│ ├── main.go # 入口,启动时执行 AutoMigrate
│ ├── config/
│ │ ├── config.go # 配置结构体(含 mapstructure 标签)
│ │ ── config.yaml # 本地开发配置(不提交 git)
│ │ ── config.yaml # 本地开发配置(不提交 git)
│ │ └── version.yaml # 版本清单(version + download_urls,发版时由 release.sh 更新)
│ ├── internal/
│ │ ├── handler/ # HTTP 处理器(每模块一文件)
│ │ │ └── *_test.go # Handler 集成测试(SQLite in-memory
@@ -59,10 +60,13 @@ jiu/
│ │ └── S001.sql # 门店 S001 测试种子数据(SQL 命令形式)
│ └── schema/schema.sql # 完整建表 SQLMySQL
├── client/ # Flutter 跨端客户端
│ └── android/ ios/ macos/ web/ windows/ # 五个平台目录(均已初始化)
├── deploy/
│ └── docker-compose.yml # 本地开发:MySQL(3306) + Adminer(8888)
├── .gitea/workflows/ # Forgejo Actionsdeploy.yml=tag 发版,及备份/重置等)
├── scripts/
│ ├── dev.sh # 一站式开发脚本(见下方用法)
│ ├── ci/ # CI 编译/发布脚本(compile-*.sh / release.sh / deploy.sh / provision-*.sh
│ └── .logs/ # 脚本运行日志(已加入 .gitignore,不提交)
└── docs/
├── context/project.md # 本文件(项目全貌)
@@ -164,6 +168,7 @@ sh scripts/dev.sh seed S001
版本(无需 JWT:
GET /version # 返回最新版本信息(读取 version.yaml,缺失返回 500
GET /api/v1/public/release # 版本 + download_urls(驱动客户端更新提示与下载页)
商品:
GET/POST /api/v1/products
@@ -204,6 +209,12 @@ sh scripts/dev.sh seed S001
数据导入:
POST /api/v1/import/products (Excel/CSV)
POST /api/v1/import/partners (Excel/CSV)
意见反馈:
POST /api/v1/feedback # 提交反馈(bug/suggestion,文字+图片,认证)
POST /api/v1/feedback/images # 上传反馈附图(认证)
GET /api/v1/admin/feedback # 反馈列表(仅 superadmin
PATCH /api/v1/admin/feedback/:id # 标记处理状态(仅 superadmin
```
## Flutter 客户端结构
@@ -218,6 +229,9 @@ client/lib/
│ ├── config/app_config.dart # 全局 URL 配置(通过 --dart-define=BASE_URL=... 注入)
│ ├── router/app_router.dart # go_router_RouterNotifier + refreshListenable
│ ├── storage/login_history.dart # 登录历史(shared_preferences,支持候选词自动填充)
│ ├── responsive/responsive.dart # 响应式:context.isMobile<600)、context.dialogWidth(X)
│ ├── update/app_updater*.dart # 应用内更新(_io: Win/macOS 装;_web: 刷新;移动端/兜底开下载链接)
│ ├── errors/error_reporter.dart # 异常上报(reportError
│ ├── models/page_result.dart # 通用分页结果模型
│ └── exceptions.dart # 自定义异常类型
├── models/ # 数据模型(与后端 JSON 对应)
@@ -239,19 +253,24 @@ client/lib/
│ └── number_rule_provider.dart # 编号规则
├── screens/
│ ├── auth/login_screen.dart # 登录页(含候选词下拉,150ms 延迟防止覆盖 onTap
│ ├── shell/app_shell.dart # 主框架(顶栏 + 侧边栏 + 状态栏 + 更新提示 banner
│ ├── shell/app_shell.dart # 主框架(顶栏 + 侧边栏/窄屏 Drawer 抽屉 + 状态栏 + 更新 banner
│ ├── stock_in/ # 入库管理
│ ├── stock_out/ # 出库管理
│ ├── inventory/ # 库存管理(含批次追踪 batch_tracking_screen.dart
│ ├── partners/ # 往来单位
│ ├── finance/ # 财务管理
│ ├── products/ # 商品管理(含分类)
── settings/ # 系统设置(用户/仓库/编号规则/关于
── about/ # 关于我们(含意见反馈:文字+附图直接提交后台
│ ├── public/ # 商品扫码公开展示页(无需登录)
│ └── settings/ # 系统设置(用户/仓库/编号规则)
└── widgets/
├── page_scaffold.dart # Tab 页面封装
├── data_table_card.dart # 含工具栏+分页的数据表格
│ # StatefulWidget,用 Table + IntrinsicColumnWidth 实现
│ # ValueNotifier<int> _hoveredRow 驱动行 hover 高亮
│ # mobileCards 入参:窄屏渲染卡片流,宽屏仍表格
├── mobile_list_card.dart # 移动端列表卡片:MobileListCard / MobileCardField
│ # 标题 + 右上角徽章 + 字段竖排 + 底部操作,替代窄屏表格行
├── multi_select_dropdown.dart # 多选筛选组件(多个导出):
│ # MultiSelectDropdown — 独立多选按钮(toolbar 使用)
│ # FilterableColumnHeader — 列头内嵌筛选图标
@@ -327,6 +346,48 @@ final warehouseOptions = _records
- `required: true` — 不可隐藏
- `minWidth: 1000` — 屏幕宽度不足时自动隐藏
## 移动端 / 响应式 UI 规范
客户端同一套代码跑桌面/Web/平板/手机。**窄屏(手机)自动切换为移动布局,宽屏保持桌面布局不变**。
### 断点判定
统一用 `client/lib/core/responsive/responsive.dart` 的扩展,不要散落魔法数:
```dart
context.isMobile // 宽度 < 600kMobileBreakpoint
context.dialogWidth(720) // 固定宽度弹窗的安全宽度,≤ 屏宽 92%
```
### 列表 → 卡片
列表屏用 `DataTableCard` 时同时传 `mobileCards`,窄屏渲染卡片流、宽屏仍表格:
```dart
DataTableCard(
columns: [...], rows: [...], // 宽屏表格
mobileCards: items.map(_buildCard).toList(), // 窄屏卡片(MobileListCard
)
```
卡片复用 `MobileListCard`(标题/徽章/字段/操作)+ `MobileCardField``widgets/mobile_list_card.dart`)。
顶部并排汇总卡片在窄屏改为横向滚动。
### 弹窗宽度
固定宽度弹窗一律 `width: context.dialogWidth(X)`**禁止**裸写 `width: <固定值>`(窄屏会溢出)。
表单字段(`_FormField`)窄屏占满整行。
### 导航
窄屏用 Drawer 抽屉(`app_shell.dart``_buildDrawer`,汉堡按钮打开),隐藏底部状态栏;宽屏保持侧边栏。
新增页面挂在 shell 下即可,自动适配,无需单独处理。
## 跨端分发
| 平台 | 构建产物 | 分发方式 |
|------|----------|----------|
| Web | `flutter build web` | 部署到 `jiu.51yanmei.com/app` |
| Windows | Inno Setup `setup.exe` | 下载页 `/downloads/` + 应用内更新 |
| macOS | `.app` zip | 下载页 `/downloads/` + 应用内更新 |
| Android | 签名 `jiu-android.apk` | 下载页 `/downloads/`(见 docs/android-signing.md |
| iOS | 签名 IPA | TestFlight(见 docs/ios-signing.md |
版本清单:`backend/config/version.yaml``download_urls`(含各平台下载地址);客户端 `GET /api/v1/public/release` 读取,驱动更新提示与下载页。
## 文档索引
| 文档 | 路径 | 说明 |
@@ -337,3 +398,7 @@ final warehouseOptions = _records
| S001 种子 | backend/seeds/S001.sql | 门店 S001 测试数据(含完整入库/出库/库存历史) |
| S002 种子 | backend/seeds/S002.sql | 门店 S002 测试数据(基础数据相同,无库存/单据,模拟新门店) |
| 用户手册 | docs/user-manual.md | 酒行员工操作手册(登录/入库/出库/库存/财务/设置) |
| Android 签名 | docs/android-signing.md | 生成 keystore + 配置 Forgejo secretsCI 给 APK 正式签名 |
| iOS 分发 | docs/ios-signing.md | 证书/Profile/API Key + secretsCI 构建并上传 TestFlight |
| 部署(NAS/Gitea | docs/deployment-nas-gitea.md | 自建 Forgejo + runner 部署说明 |
| 待办 | docs/TODO.md | 后续迭代事项(iOS 上架、应用内 APK 安装、平板布局等) |
+13
View File
@@ -152,6 +152,19 @@ sh scripts/dev.sh --backend-only
| KeyDownEvent 断言失败 | Flutter 3.41 + macOS 26 已知 bug | 改用 Chrome 运行 |
| mouse_tracker 断言失败(debug| Flutter debug 断言 + hover 事件 | 用 `--profile` 模式运行 |
### 移动端构建
```bash
# Android(本地):无 client/android/key.properties 时自动回退 debug 签名
cd client && flutter build apk --release
# iOS(本地验证编译,不签名)
cd client && flutter build ios --release --no-codesign
```
- 正式签名/发布由 CI 完成:Android 见 `docs/android-signing.md`iOSTestFlight)见 `docs/ios-signing.md`
- 移动端 UI 自动适配:窄屏走移动布局(Drawer 抽屉 + 列表卡片),规范见 `docs/context/project.md` 的「移动端 / 响应式 UI 规范」。
---
## 五、后端测试架构
+73
View File
@@ -0,0 +1,73 @@
# iOS 分发(TestFlight)— 一次性配置
iOS 不能像安卓那样把安装包挂网页直接侧载。本项目通过 **TestFlight** 分发:CI 自动构建签名 IPA 并上传 App Store Connect,用户装 TestFlight app 后用公开链接安装。
所有凭证通过 Forgejo Secrets 注入,**不入库**。配置完成前 `build-ios` job 会自动跳过(exit 0),不阻塞发版。
## 前置:Apple Developer 账号
- 注册 Apple Developer Program$99/年)。
- 在 [App Store Connect](https://appstoreconnect.apple.com) 新建 AppBundle ID = `com.yanmei.jiu`(与工程一致)。
## 1. 发行证书(Apple Distribution
在钥匙串/开发者后台创建 **Apple Distribution** 证书,导出为 `.p12`(设一个导出密码):
```bash
# 已在 Xcode/钥匙串里有 Distribution 证书时,从「钥匙串访问」导出 .p12
base64 -i dist.p12 | pbcopy # → ANDROID 同理,复制到剪贴板
```
## 2. Provisioning ProfileApp Store 类型)
开发者后台 → Profiles → 新建 **App Store** 类型,关联 App ID `com.yanmei.jiu` 与上面的发行证书,下载 `.mobileprovision`
```bash
base64 -i jiu_appstore.mobileprovision | pbcopy
```
## 3. App Store Connect API Key
App Store Connect → Users and Access → Integrations → App Store Connect API → 新建密钥(角色 App Manager 即可),下载 `AuthKey_XXXX.p8`,记下 **Key ID****Issuer ID**
```bash
base64 -i AuthKey_XXXX.p8 | pbcopy
```
## 4. 在 Forgejo 仓库配置 Secrets
仓库 → Settings → Actions → Secrets,新增 7 项:
| Secret 名 | 值 |
|-----------|-----|
| `IOS_DIST_CERT_P12_BASE64` | 第 1 步 .p12 的 base64 |
| `IOS_DIST_CERT_PASSWORD` | .p12 导出密码 |
| `IOS_PROVISIONING_PROFILE_BASE64` | 第 2 步 .mobileprovision 的 base64 |
| `IOS_TEAM_ID` | Apple Developer Team ID10 位) |
| `APPSTORE_API_KEY_ID` | 第 3 步 Key ID |
| `APPSTORE_API_ISSUER_ID` | 第 3 步 Issuer ID |
| `APPSTORE_API_KEY_P8_BASE64` | 第 3 步 .p8 的 base64 |
## 5. 配置 TestFlight 公开链接(下载页)
TestFlight 设置「公开链接」后,把链接填到 `backend/config/version.yaml`
```yaml
download_urls:
ios: "https://testflight.apple.com/join/XXXXXXXX"
```
填上后下载页的 iOS 卡片自动激活为「通过 TestFlight 安装」;客户端「立即更新」在 iOS 上也会跳到该链接。release.sh 不会覆盖此行(仅改 macos/windows/android)。
## 6. 验证
打 tag 触发流水线,确认 `build-ios` job
- 日志非 `SKIP`,出现 `uploading to TestFlight`
- App Store Connect → TestFlight 出现新 build(处理需几分钟);
- 在 TestFlight 里设置测试组/公开链接后即可分发。
## 工作原理
- `scripts/ci/compile-ios.sh`:建临时 keychain 导入证书、安装 profile、生成 ExportOptionsmanual / app-store)、`flutter build ipa``xcrun altool --upload-app` 上传;CFBundleVersion 由版本号推导单调递增;缺 secrets 时跳过。
- `.gitea/workflows/deploy.yml``build-ios` jobmac runner,串行于 build-android。
- 工程:`client/ios`bundle id `com.yanmei.jiu`,应用名「岩美酒库」)。