# 「全部功能」页业务逻辑 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`、`report_photographer`(举报摄影师)、`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 更多)一致