Files
wangjia 05203a9b5b
Design Source Checks / design-source (push) Failing after 14m23s
refactor: 原型迁移 .superpowers/prototype → design/prototype(与 CONTRACT 同目录)
- git mv 全目录(历史保留);.gitignore 收敛为整个 .superpowers/ 忽略
- 全仓 16 处引用同步(CI checks.yml / l1-sync / screens.mjs / hooks /
  CLAUDE.md / CONTRACT / SSR 模板注释 / docs / web 注释)
- 修 pre-commit 路径正则残留;5180 评审服务已切新路径
- 验证:check-ds 12 道 / l1-sync 6 道 / fidelity 抽查全过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-07 18:47:31 +08:00

409 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 酒库管理系统 — 项目上下文
> 所有 Agent 在开始工作前必须读取此文件,了解项目全貌。
## 项目简介
面向酒水门店的仓库管理系统。核心特点:
- **多租户**:每个账号对应一个门店,数据通过 `shop_id` 完全隔离
- **付费授权**:许可证绑定设备 ID,支持试用/年付/买断
- **跨端客户端**:Flutter 单一代码库,支持 Windows / Web / macOS / iOS / Android
## 技术栈
```yaml
后端:
语言: Go 1.26
框架: GinHTTP+ GORMORM
数据库: MySQL 8.0Docker 容器 jiu_mysql
认证: JWTHS256),Access Token 60分钟,Refresh Token 7天
模块路径: github.com/wangjia/jiu/backend
前端:
框架: Flutter 3.x
状态管理: Riverpod
HTTP客户端: Dio(含 401 自动刷新 token 拦截器)
路由: go_router(含登录重定向)
目录: client/
数据库管理:
Schema: backend/schema/schema.sql(完整建表 SQL,容器启动时自动执行)
ORM 迁移: GORM AutoMigrate(后端启动时自动同步)
种子数据: backend/seeds/<shop>.sql(通过 dev.sh seed 命令执行)
```
## 项目目录结构
```
jiu/
├── backend/
│ ├── main.go # 入口,启动时执行 AutoMigrate
│ ├── config/
│ │ ├── config.go # 配置结构体(含 mapstructure 标签)
│ │ ├── config.yaml # 本地开发配置(不提交 git)
│ │ └── version.yaml # 版本清单(version + download_urls,发版时由 release.sh 更新)
│ ├── internal/
│ │ ├── handler/ # HTTP 处理器(每模块一文件)
│ │ │ └── *_test.go # Handler 集成测试(SQLite in-memory
│ │ ├── service/ # 业务逻辑层
│ │ │ └── *_test.go # Service 单元测试
│ │ ├── model/ # GORM 数据模型
│ │ │ ├── base.go # Base/TenantBase/Date/JSON 公共类型
│ │ │ └── *.go # 各业务模型
│ │ ├── middleware/auth.go # JWT 验证 + shop_id 注入(GetShopID/GetUserID
│ │ │ # ReadOnly():只读用户禁止写操作(全局挂载)
│ │ │ # AdminOnly():仅管理员可访问(挂载在 /users 路由组)
│ │ └── router/router.go # 路由注册
│ ├── testutil/setup.go # 测试工具包(SQLite DB、CreateTestXxx、GetAuthToken
│ ├── cmd/seed/main.go # 数据库工具(--reset 删表重建,--clear 清空数据)
│ ├── seeds/
│ │ └── 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 # 本文件(项目全貌)
├── dev-setup.md # 环境搭建指南
├── requirements/ # 需求文档
├── architecture/ # 架构设计
├── api/ # API 接口规范
├── review/ # 代码审查报告 + bug 报告
├── security/ # 安全审计报告
└── runbooks/ # 运维操作手册
```
## 核心数据模型
### 关键语义:product = 「特有产品 / 序列号」(非 SKU)
**`products` 表的每一行 = 一个特有产品,以序列号(= 商品编码 `code`,如 ZXZ###### / P####)唯一标识,每个产品独一份、不复用**(像手机序列号,哪怕同款也各不相同)。一个产品的全部信息以 product 为**单一来源**
- `code` 序列号(同店唯一,`UNIQUE KEY uk_shop_code (shop_id, code)` 防并发/重复)
- `name`=**品牌**、`series`=**型号**、`spec`=**版本/规格**、`unit` 单位
- `production_date` 生产日期、`batch_no` 批次、`purchase_price` 进价、`sale_price` 售价
- `public_id`(公开页/二维码)、`product_images`(图片)、`name_pinyin`/`name_initials`(拼音搜索索引,写入时由 `util.ToPinyin` 自动生成)
**入库每录一行 = 新建一个独立 product(发新序列号)**,不按名称复用(旧 `findOrCreate` 复用逻辑已废弃);同名同型号录两次 = 两个独立序列号。`handler/product.go``nextProductCode`max+1 自增)+ `createIndependentProduct` 负责。
### 基础数据字典(「基础数据」页 `products_screen.dart` 管理)
入库选择商品时从字典选取文本,再据此建 product:
- `product_name_options`(商品名=**品牌**)、`product_series_options`(系列=**型号**)、`product_spec_options`(规格=**版本**
- 商品属性字典:`product_origin_options`(产地)、`product_shelf_life_options`(保质期)、`product_storage_options`(储存方式)、`product_description_docs`(介绍文档)——公开页展示用,product 以可空外键引用
### 库存与单据(库存/明细 = 指向 product 的引用 + 历史快照)
```yaml
inventories: # 库存批次:每行 = 某 product 在某仓库的一批(product_id + warehouse_id + quantity
# + stock_in_item_id(来源入库明细,盘盈则 inventory_check_id
# 含「快照列」product_code/product_name/series/spec/unit/unit_price/
# production_date/batch_no/supplier_name/warehouse_name —— 导入/审核时
# 从源/product 拷贝,作历史保真 + 显示兜底
stock_in_orders: # 入库单(status: draft→pending→approved/rejected;可由本人/管理员 withdraw 回 draft
stock_in_items: # 入库明细(product_id + 快照列 + quantity + cost_price 进价·单瓶 + cost_amount 总进价 + batch_no/production_date
stock_out_orders: # 出库单(同状态机;sale_total 应收合计 + profit_total 总利润,建单落库、确认售价/进价联动重算)
stock_out_items: # 出库明细(cost_price 成本快照 + sale_price 售价 + cost_amount/sale_amount 两侧小计;成本/利润仅管理员可见,operator 响应被服务端抹零)
# ※ 2026-07 定价消歧:旧列 unit_price/total_price/total_amount 弃用(详见 CLAUDE.md「定价字段口径」)
inventory_logs: # 库存流水(每次变动自动记录 in/out + qty_before/after
inventory_checks / inventory_check_items: # 盘点单 / 盘点明细(FIFO 盘盈盘亏)
```
> **显示策略**:库存列表 `inventory.go` 用 `COALESCE(NULLIF(p.code,''), 快照列)`——**优先 productproduct 为空/被删才回退快照**。前端 `lineOrProduct` 同理。历史导入的出入库明细 `product_id` 多指向一个占位 product`HIST-PLACEHOLDER`),真实信息存快照列。
### 其它主表
```yaml
shops: # 门店(租户根节点)
users: # 用户(含 shop_idrole: superadmin/admin/operator/readonly
licenses / license_codes / license_devices: # 授权(时长兑换券短码 + 设备绑定)
product_categories: # 商品分类(可空)
warehouses: # 仓库
partners: # 往来单位(type: supplier/customer
finance_records: # 财务流水(receivable/payable/receipt/payment
number_rules: # 单号生成规则(前缀+日期+6位序号)
feedbacks: # 意见反馈(bug/suggestion + 图片)
```
## 关键业务规则
1. **多租户隔离**`shop_id` 只从 JWT 提取(`middleware.GetShopID(c)`),绝不从请求参数读取
2. **product = 序列号**:入库每行新建独立 product(不按名称复用);product 是商品信息单一来源,库存/明细只是指向它的引用 + 历史快照(见上节)
3. **库存变更事务**:入库/出库审核时,同一事务内更新 `inventories` + 写 `inventory_logs`
4. **出库前校验**:审核出库时校验库存充足,不足返回错误并回滚
5. **单据状态机与撤回**`draft→submit→pending→approve/reject`;审核中(pending)可 **withdraw 回 draft** 再改再提交——管理员/超管撤回任意单,操作员限本人单(`OperatorID==本人`handler 内判权);已审核(approved)只读
6. **单号生成**:通过 `number_rules` 表事务安全生成,格式 `{前缀}{YYYYMMDD}{6位序号}`
7. **权限角色**`superadmin`(超管,可清空数据/看反馈)> `admin`(管理员,管用户/店铺信息)> `operator`(操作员)> `readonly`(只读,所有写操作 403)。`middleware.ReadOnly()` 全局挂载、`AdminOnly()`(admin+superadmin) / `SuperAdminOnly()` 按需挂载
8. **并发安全**
- `GenerateOrderNo` 在事务内用 `FOR UPDATE` 锁住 `number_rules` 行,防并发重复单号
- `ApproveStockOut`/`updateInventory` 在事务内用 `FOR UPDATE` 锁库存行,防超卖竞态
- `products.code``(shop_id,code)` UNIQUE 约束,建 product 按最大序号+1、ErrDuplicatedKey 重试最多 5 次(不复用软删号)
## 开发脚本(scripts/dev.sh
所有常用操作都通过 `sh scripts/dev.sh <命令>` 执行:
```bash
# 启动
sh scripts/dev.sh run # 启动前后端(后端代码未变则跳过重启)
sh scripts/dev.sh run --force # 强制重启后端
sh scripts/dev.sh --backend-only # 仅启动后端
sh scripts/dev.sh --frontend-only # 仅启动前端
sh scripts/dev.sh stop # 停止所有服务
# 数据库
sh scripts/dev.sh seed S001 # 清空并写入 S001 测试数据(执行 seeds/S001.sql
sh scripts/dev.sh reset # 删表重建(AutoMigrate
sh scripts/dev.sh clear # 清空所有业务数据(保留表结构)
```
## 本地开发快速启动
```bash
# 1. 启动 MySQL 容器(首次或容器未运行时)
docker compose -f deploy/docker-compose.yml up -d
# 2. 一键启动前后端
sh scripts/dev.sh run
# 3. 写入测试数据(另开终端)
sh scripts/dev.sh seed S001
# 写入后可用以下账号登录(门店编号 S001):
# admin / password123 管理员
# operator / password123 操作员
# test / password123 只读
# 4. 数据库管理界面(可选)
# 浏览器打开 http://localhost:8888
# 服务器: mysql,用户: root,密码: password,数据库: jiu_db
```
## 已实现的 API 接口(以 `router/router.go` 为准)
```yaml
无需 JWT:
GET /health /version
POST /api/v1/auth/login /api/v1/auth/refresh /api/v1/public/register
GET /api/v1/public/products/:public_id # 公开商品详情(扫码页)
GET /api/v1/public/shops/:shop_code/products # 店铺公开商品列表(仅有库存 + 数量 + code)
GET /api/v1/public/release # 版本 + download_urls
GET /product/:public_id # 注入 OG 标签的分享页
POST /api/v1/public/errors # 客户端异常上报
会话/许可证:
POST /api/v1/auth/logout /api/v1/auth/ping
GET /api/v1/sessions ; DELETE /api/v1/sessions/:id (AdminOnly)
GET /api/v1/license/info|verify|devices ; POST /api/v1/license/activate|deactivate
商品(product=序列号):
GET/POST /api/v1/products ; PUT/DELETE /api/v1/products/:id
POST /api/v1/products/find-or-create
GET /api/v1/products/:id/detail /:id/qrcode
POST /api/v1/products/:id/images ; DELETE /api/v1/products/:id/images/:image_id
基础数据字典 /api/v1/product-options:
names | series | specs | origins | shelf-lives | storages | description-docs
每组 GET(支持 ?keyword 服务端搜索)/ POST / PUT/:id / DELETE/:id
仓库 /warehouses · 往来单位 /partners?type&keyword: GET/POST/PUT/DELETE
入库 /stock-in/orders · 出库 /stock-out/orders:
GET(列表,?status&keyword) /POST ; GET/PUT/DELETE/:id
PUT /:id/submit | approve | reject | withdraw # withdraw=审核中撤回回草稿
库存 /inventory:
GET ""(?warehouse_id&keyword&series&spec&in_stock) ; GET /logs
PUT /:id/remark ; POST /checks ; GET /checks/:id ; PUT /checks/:id/complete
财务 /finance: GET /records /summary ; POST /records ; PUT /records/:id/close · /close-by-ref
用户 /users(AdminOnly): GET/POST ; PUT/:id ; PUT /:id/reset-password
店铺 /shop: GET /info ; PUT /info、POST /logo (AdminOnly)
编号规则 /number-rules: GET ""; PUT /:id
意见反馈 /feedback: POST ""、/images
数据导入 /import: products | partners | product-names|series|specs|codes | stock-in | stock-out | inventory (Excel)
超管 /admin(SuperAdminOnly): POST /clear-data ; GET /reconcile /errors /feedback ; PATCH /feedback/:id
```
## Flutter 客户端结构
```
client/lib/
├── main.dart # 入口:ProviderScope + _AppBootstrapauth restore
├── core/
│ ├── theme/app_theme.dart # 颜色常量 + ThemeData
│ ├── auth/auth_state.dart # AuthUser, AuthState, AuthNotifiershared_preferences 持久化)
│ ├── api/api_client.dart # Dio 封装(401 自动刷新,_disposed 防悬空回调)
│ ├── 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 对应)
├── repositories/ # 数据访问层(封装 API 调用)
│ └── auth_repository.dart # 登录/刷新 token(含历史记录保存)
├── providers/ # Riverpod 状态管理
│ ├── license_provider.dart # 许可证状态(licenseInfoProvider
│ ├── connectivity_provider.dart # 网络连通性探测(connectivityProvider
│ ├── update_provider.dart # 版本更新检查(updateCheckProvider
│ │ # 平台判断用 kIsWeb,避免 Web 上调用 dart:io
│ ├── product_provider.dart # 商品列表(AsyncNotifierProvider,含缓存)
│ ├── stock_in_provider.dart # 入库单列表
│ ├── stock_out_provider.dart # 出库单列表
│ ├── inventory_provider.dart # 库存列表
│ ├── finance_provider.dart # 财务记录
│ ├── partner_provider.dart # 往来单位
│ ├── warehouse_provider.dart # 仓库列表
│ ├── user_provider.dart # 用户管理
│ └── number_rule_provider.dart # 编号规则
├── screens/
│ ├── auth/login_screen.dart # 登录页(含候选词下拉,150ms 延迟防止覆盖 onTap
│ ├── shell/app_shell.dart # 主框架(顶栏 + 侧边栏/窄屏 Drawer 抽屉 + 状态栏 + 更新 banner
│ ├── stock_in/ # 入库管理
│ ├── stock_out/ # 出库管理
│ ├── inventory/ # 库存管理(含批次追踪 batch_tracking_screen.dart
│ ├── partners/ # 往来单位
│ ├── finance/ # 财务管理
│ ├── products/ # 商品管理(含分类)
│ ├── 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 — 列头内嵌筛选图标
│ # hover 显示,active 常显;图标 16px 靠右对齐
│ # ColDef — 列定义(key, label, required, minWidth
│ # ColumnToggleButton — 列显示/隐藏切换按钮
├── status_badge.dart # 状态标签(草稿/待审/已审/拒绝)
└── form_dialog.dart # 弹窗表单封装
```
## 前端配置管理
后端 URL 通过 `AppConfig` 统一管理,支持构建时注入:
```dart
// client/lib/core/config/app_config.dart
class AppConfig {
static const _baseUrl = String.fromEnvironment(
'BASE_URL',
defaultValue: 'http://localhost:8080',
);
static String get baseUrl => _baseUrl;
static String get apiBaseUrl => '$_baseUrl/api/v1';
static String get healthUrl => '$_baseUrl/health';
static String get versionUrl => '$_baseUrl/version';
}
```
**构建/运行时注入**
```bash
flutter run --dart-define=BASE_URL=http://192.168.1.100:8080
flutter build web --dart-define=BASE_URL=https://api.example.com
```
**规则**:所有硬编码 `localhost:8080` 的 URL 必须改用 `AppConfig` 对应属性,不得直接拼接字符串。
## 前端表格 UI 规范(ds 真相源,2026-07 起)
列表屏统一 `DsTable``client/lib/widgets/ds/ds_table.dart`):toolbar + 表格 + pager 连成一卡,
镜像原型 `.toolbar/.table/.pager`。列定义 `DsColumn`;可筛选列走列头漏斗(filtered 态主色高亮);
可隐藏列走列设置菜单;分页 `total` 带控件 / `pagerInfoText` 仅文案。行 hover、表头底色等
全部来自 `context.tokens`(禁硬编码色,闸:`node client/tool/check_ds_code.mjs`)。
其余原子(按钮/输入/徽章/chip/分段/KPI/菜单/toast/柱状图)见 `client/lib/widgets/ds/`
一对一镜像 `design/prototype/atoms.css`。旧 `DataTableCard`/`FilterableColumnHeader`
体系已废弃(2026-07-03),勿在新屏使用。
## 移动端 / 响应式 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 |
版本清单 `version.yaml`(归 **client**,写 version/build_number/release_notes/下载链接/changelog);后端 `GET /version``/api/v1/public/release` **每请求实时读取**,client 部署后立即生效,无需重启后端、无需重建官网。
### 三条独立发版流水线(互不影响,各有 tag 前缀 / CHANGELOG / workflow
| part | 范围 | tag 前缀 | CHANGELOG | workflow |
|------|------|---------|-----------|----------|
| **client** | `client/` Flutter 全平台 + `version.yaml` | `client-v*` | `CHANGELOG-client.md` | `deploy-client.yml` |
| **site** | `web/` Eleventy 营销宣传站(不含 Web 版 app) | `site-v*` | `CHANGELOG-site.md` | `deploy-site.yml` |
| **server** | `backend/` Go 服务 + 共享基建(nginx/systemd) | `server-v*` | `CHANGELOG-server.md` | `deploy-server.yml` |
`/release <part> [version]` slash command:本地 build→test→更新 CHANGELOG→commit→tag→pushCI(Forgejo) 按 tag 前缀触发对应 workflow 自动编译/测试/发 Release/部署 ali/Telegram 通知。**测试未过禁止发版**。
### 生产环境与运维
- 生产:阿里云 ECS`ssh ali`,北京;2026-07-02 从 EC2 割接)。入口 `https://jiu.51yanmei.com`nginx 443 ssl+http2certbot webroot 自动续期),反代 `127.0.0.1:8081` 的 systemd `jiu.service`MySQL 在容器 `jiu_mysql`127.0.0.1:3306)。配置 `/opt/jiu/config/production.env`DATABASE_DSN、SERVER_PORT=8081、STORAGE_PUBLIC_URL)。每日 DB 备份 → 开发机 `~/jiu-db-backups`
- CI runnermac runner=开发者本机(launchd + relay 绕 Shadowrocket,有看门狗自愈)、windows runner=另一台机(nssm 服务 `forgejo-runner`)、ubuntu=NAS docker。Forgejo 在 NAS`git.51yanmei.com`)。
- 一次性数据工具:`cmd/import-history`(旧系统进销存批量导入)、`cmd/fix-inventory-products`(修复历史库存 product_id 错指)。
## 文档索引
| 文档 | 路径 | 说明 |
|------|------|------|
| 项目上下文 | docs/context/project.md | 本文件,所有 Agent 必读 |
| 环境搭建 | docs/dev-setup.md | 工具安装 + 开发脚本详解 + 测试架构 |
| Schema | backend/schema/schema.sql | 完整数据库建表 SQL |
| S001 种子 | backend/seeds/S001.sql | 门店 S001 测试数据(含完整入库/出库/库存历史) |
| S002 种子 | backend/seeds/S002.sql | 门店 S002 测试数据(基础数据相同,无库存/单据,模拟新门店) |
| 文档索引 | docs/index.html | 全部文档单一入口 |
| 用户手册 | docs/manual/user-manual.html | 酒行员工操作手册(HTML 主版本;官网帮助页源=web/content/docs.md |
| 开发手册 | docs/manual/dev-manual.html | 架构/后端/前端/数据库/API/运维/发版 全景 |
| DB 列级文档 | docs/db-schema.html | 数据库 Schema 可视化 |
| 用户手册(旧) | docs/user-manual.md | 已迁移至 HTML 版,停止更新 |
| 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 安装、平板布局等) |