212 lines
7.2 KiB
Markdown
212 lines
7.2 KiB
Markdown
# 「全部功能」页业务逻辑
|
||
|
||
Android 工程对应文档:[`zhiflyfollow/docs/home/AllFunctions.md`](../../../zhiflyfollow/docs/home/AllFunctions.md)
|
||
|
||
## 模块职责
|
||
|
||
「全部功能」页(`HomeMoreFunctionsView`)展示当前角色可用的功能入口,分为两个分区:
|
||
|
||
- **常用应用**:已加入首页常用的入口,支持移除(`-`)
|
||
- **更多功能**:其余可用入口,支持添加为常用(`+`)
|
||
|
||
常用应用的增删会同步写入本地持久化,并与首页「常用应用」网格共用同一套配置(`HomeCommonMenuStore`)。
|
||
|
||
本页数据构建规则与 Android `AllFunctionsViewModel` 对齐,**不**复用首页 `HomeViewModel` 的 flatten + `preferredOrder` 逻辑。
|
||
|
||
---
|
||
|
||
## 涉及文件
|
||
|
||
| 文件 | 职责 |
|
||
| --- | --- |
|
||
| `Views/HomeMoreFunctionsView.swift` | 页面 UI、增删常用、触发重建 |
|
||
| `Services/HomeAllFunctionsBuilder.swift` | 菜单列表构建与常用/更多拆分 |
|
||
| `Services/HomeCommonMenuStore.swift` | 常用 URI 持久化与默认生成 |
|
||
| `Services/HomeAllFunctionsDiagnostics.swift` | Debug 诊断日志 |
|
||
| `App/State/PermissionContext.swift` | 提供当前角色顶层权限 |
|
||
| `Routing/HomeMenuRouter.swift` | 点击入口后的路由解析 |
|
||
|
||
---
|
||
|
||
## 数据流
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
API[role-permission 接口] --> PC[PermissionContext]
|
||
PC --> TLP[topLevelPermissions 顶层节点]
|
||
TLP --> BUILDER[HomeAllFunctionsBuilder.build]
|
||
CMS[HomeCommonMenuStore.load] --> COMMON[commonURIs]
|
||
COMMON --> BUILDER
|
||
BUILDER --> ALL[allFunctions]
|
||
BUILDER --> CF[commonFunctions]
|
||
BUILDER --> MF[moreFunctions]
|
||
CF --> UI1[常用应用网格]
|
||
MF --> UI2[更多功能网格]
|
||
```
|
||
|
||
### 触发重建的时机
|
||
|
||
`HomeMoreFunctionsView.rebuildMenus()` 在以下情况执行:
|
||
|
||
1. 页面首次进入(`.task`)
|
||
2. 当前角色 `roleCode` 变化
|
||
3. `rolePermissions` 数量变化
|
||
|
||
用户点击 `+` / `-` 时只更新 `commonUris` 并调用 `applySnapshot`,不重新走持久化读取(除非权限上下文同时变化)。
|
||
|
||
---
|
||
|
||
## 第一步:读取顶层权限
|
||
|
||
数据来源:`PermissionContext.topLevelPermissions(for: roleCode)`
|
||
|
||
- 仅取当前匹配角色的 `role.permission` **顶层数组**
|
||
- **不**递归展开 `children` 子节点
|
||
- 顺序与 API 返回一致,不做本地重排
|
||
- 过滤掉 URI 为空的节点
|
||
|
||
对应 Android:`appStore.getPermission()` 中保存的顶层权限列表(MMKV 快照)。
|
||
|
||
---
|
||
|
||
## 第二步:白名单过滤(可用入口)
|
||
|
||
由 `HomeAllFunctionsBuilder.allMenuItems(from:)` 执行:
|
||
|
||
```
|
||
allFunctions = 顶层权限
|
||
.按 API 顺序遍历
|
||
.保留 uri ∈ HomeCommonMenuStore.androidHomeMenuURIs 的项
|
||
.映射为 HomeMenuItem
|
||
```
|
||
|
||
`androidHomeMenuURIs` 与 Android `Constants.menuList` 登记 URI 一致。已登记且会出现在「全部功能」页的 URI 包括 `location_report`、`photographer_report`(举报摄影师)、`wallet`、`message_center` 等。
|
||
|
||
以下典型 URI **不会**出现在「全部功能」页,即使接口返回了顶层权限:
|
||
|
||
- `basic_info`、`photographer_stats`、`photographer_orders`
|
||
- `location_info`、`scan_qr`、`payment_qr`
|
||
- `album_list`、`material_upload` 等未登记 URI
|
||
|
||
子权限 URI 也不会出现(未 flatten)。
|
||
|
||
---
|
||
|
||
## 第三步:菜单项字段映射
|
||
|
||
每个保留的 `PermissionItem` 转为 `HomeMenuItem`:
|
||
|
||
| 字段 | 规则 |
|
||
| --- | --- |
|
||
| `uri` | 权限节点原始 URI,精确字符串 |
|
||
| `title` | API `name` 非空时用 `name`;否则 `HomeMenuRouter.title(for:)`;再经 `HomeMenuRouter.displayTitle` 统一部分同义入口文案 |
|
||
| `iconSrc` | API `icon_src`;为空时 UI 层用 `HomeIconCatalog` SF Symbol 兜底 |
|
||
|
||
Android 端标题/icon 来自本地 `Constants.menuList` drawable;iOS 优先接口字段 + 本地图标兜底。
|
||
|
||
---
|
||
|
||
## 第四步:读取常用 URI
|
||
|
||
由 `HomeCommonMenuStore.load` 提供 `commonUris`,规则详见 [`Home.md`](Home.md)「常用应用」章节,核心要点:
|
||
|
||
- 存储 key:`home.common.menu.uris.account.{accountScope}.role.{roleCode}`(无 accountScope 时用 `home.common.menu.uris.role.{roleCode}`)
|
||
- 首次无 saved:按顶层前 4 个 URI 与 menuList 求交生成默认,并落库
|
||
- 已有 saved:精确 URI 匹配当前可用顶层权限;全部失效时返回空,不回退默认
|
||
- 增删常用:精确 URI 匹配(不使用 `menuAliasKey`)
|
||
|
||
---
|
||
|
||
## 第五步:拆分为常用 / 更多
|
||
|
||
`HomeAllFunctionsBuilder.build(topLevelPermissions:commonURIs:)`:
|
||
|
||
```text
|
||
commonSet = Set(commonURIs)
|
||
|
||
commonFunctions = allFunctions.filter { commonSet.contains($0.uri) }
|
||
moreFunctions = allFunctions.filter { !commonSet.contains($0.uri) }
|
||
```
|
||
|
||
要点:
|
||
|
||
- 匹配方式:**精确 URI**,不做别名归一
|
||
- **常用区顺序**:跟随 `allFunctions` 列表顺序(API 顺序 ∩ 白名单后的顺序),**不是** `commonUris` 数组的存储顺序
|
||
- **更多区顺序**:同样跟随 `allFunctions` 剩余项顺序
|
||
|
||
与 Android 一致:
|
||
|
||
```kotlin
|
||
val common = functions.filter { it.uri in commonUris }
|
||
val more = functions.filter { it.uri !in commonUris }
|
||
```
|
||
|
||
---
|
||
|
||
## 展示层
|
||
|
||
### 布局
|
||
|
||
- 两节标题:「常用应用」「更多功能」
|
||
- 每节 3 列 `LazyVGrid`,卡片高度 112pt
|
||
- 卡片右上角:`常用` 显示红色 `-`,`更多` 显示蓝色 `+`
|
||
|
||
### 点击行为
|
||
|
||
- 点击卡片主体:`HomeMenuRouter.resolve(uri:title:)` 解析路由
|
||
- Tab 切换、订单 Tab、Home 子路由、占位页等
|
||
- `more_functions` URI 在页内忽略(防循环)
|
||
- 点击 `-`:`HomeCommonMenuStore.remove` → `applySnapshot`
|
||
- 点击 `+`:`HomeCommonMenuStore.add`(校验 URI 在顶层可用白名单内)→ `applySnapshot`
|
||
|
||
### 与首页的关系
|
||
|
||
| 维度 | 首页常用应用网格 | 全部功能页 |
|
||
| --- | --- | --- |
|
||
| 数据源 | `HomeCommonMenuStore` + `HomeViewModel`(展示标题/icon) | `HomeAllFunctionsBuilder` |
|
||
| 列表范围 | 仅 common URIs + 「更多功能」入口 | 全部可用入口分 common / more |
|
||
| 排序 | common 按 store 顺序映射 | common/more 均按 `allFunctions` API 顺序 |
|
||
| 持久化 | 共用 `HomeCommonMenuStore` | 共用 `HomeCommonMenuStore` |
|
||
|
||
---
|
||
|
||
## 与 HomeViewModel 的区别
|
||
|
||
| | `HomeViewModel`(首页等) | `HomeAllFunctionsBuilder`(全部功能) |
|
||
| --- | --- | --- |
|
||
| 权限范围 | 递归 flatten 整棵权限树 | 仅顶层 |
|
||
| 白名单 | 无 | `androidHomeMenuURIs` |
|
||
| 排序 | `preferredOrder` 硬编码权重 | API 顶层顺序 |
|
||
| 去重 | `menuAliasKey` 别名去重 | 无(顶层 URI 精确保留) |
|
||
|
||
「全部功能」页**不应**调用 `HomeViewModel.buildMenus()`。
|
||
|
||
---
|
||
|
||
## 诊断日志
|
||
|
||
Debug 构建下可用 Xcode Console 过滤 `HomeAllFunctions`:
|
||
|
||
| step | 含义 |
|
||
| --- | --- |
|
||
| `allFunctions` | 白名单过滤后的完整 URI 列表 |
|
||
| `commonFunctions` | 常用分区 URI |
|
||
| `moreFunctions` | 更多分区 URI |
|
||
|
||
常用应用持久化链路仍使用 `HomeCommonMenu` tag,见 `HomeCommonMenuStore` / 首页 `HomeView` 日志。
|
||
|
||
---
|
||
|
||
## 测试
|
||
|
||
单元测试:`suixinkanTests/HomeAllFunctionsBuilderTests.swift`
|
||
|
||
覆盖场景:
|
||
|
||
- menuList 白名单过滤
|
||
- API 顶层顺序保留(非 preferredOrder)
|
||
- 不展开子权限
|
||
- 精确 URI 拆分 common / more
|
||
- 常用区顺序跟随 `allFunctions`
|
||
- 与 Android 样例账号(27 顶层 → 21 可用 → 3 常用 + 18 更多)一致
|