// // HomeMoreFunctionsView.swift // suixinkan // // Created by Codex on 2026/6/22. // import SwiftUI /// 首页全部功能视图,展示常用应用和当前角色拥有的更多功能。 struct HomeMoreFunctionsView: View { @Environment(PermissionContext.self) private var permissionContext @Environment(AppRouter.self) private var appRouter @Environment(RouterPath.self) private var router @Environment(\.dismiss) private var dismiss @State private var viewModel = HomeViewModel() @State private var commonUris: [String] = [] private let commonMenuStore = HomeCommonMenuStore() private var commonItems: [HomeMenuItem] { commonUris.compactMap(menuItem(for:)) } private var moreItems: [HomeMenuItem] { viewModel.menuItems.filter { item in !isCommonURI(item.uri) } } var body: some View { VStack(spacing: 0) { topBar ScrollView { VStack(alignment: .leading, spacing: 26) { section(title: "常用应用", items: commonItems, isCommon: true) section(title: "更多功能", items: moreItems, isCommon: false) } .padding(.horizontal, AppMetrics.Spacing.mediumLarge) .padding(.top, AppMetrics.Spacing.xxLarge + 1) .padding(.bottom, AppMetrics.Spacing.xxLarge) } .background(Color(hex: 0xF5F5F5)) } .background(Color(hex: 0xF5F5F5).ignoresSafeArea()) .toolbar(.hidden, for: .navigationBar) .task { rebuildMenus() } .onChange(of: permissionContext.currentRole?.id) { _, _ in rebuildMenus() } .onChange(of: permissionContext.rolePermissions.count) { _, _ in rebuildMenus() } } private var topBar: some View { ZStack { Text("全部功能") .font(.system(size: AppMetrics.FontSize.title2 + 1, weight: .semibold)) .foregroundStyle(Color(hex: 0x2C2C2C)) .frame(maxWidth: .infinity) HStack { Button { dismiss() } label: { Image(systemName: "chevron.left") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(Color(hex: 0x111827)) .frame(width: AppMetrics.ControlSize.iconTapArea, height: AppMetrics.ControlSize.iconTapArea) } .buttonStyle(.plain) Spacer() } .padding(.horizontal, AppMetrics.Spacing.pageHorizontal) } .frame(height: 62) .background(.white) .overlay(alignment: .bottom) { Rectangle() .fill(Color.black.opacity(0.06)) .frame(height: 0.5) } } private func section(title: String, items: [HomeMenuItem], isCommon: Bool) -> some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.medium) { Text(title) .font(.system(size: AppMetrics.FontSize.title, weight: .bold)) .foregroundStyle(Color(hex: 0x2C2C2C)) appGrid(items: items, isCommon: isCommon) } } private func appGrid(items: [HomeMenuItem], isCommon: Bool) -> some View { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 3), spacing: AppMetrics.Spacing.mediumLarge) { ForEach(items, id: \.uri) { item in ZStack(alignment: .topTrailing) { Button { openMenu(item) } label: { VStack(spacing: AppMetrics.Spacing.mediumLarge + 1) { menuIconView(for: item) .frame(width: 34, height: 34) Text(item.title) .font(.system(size: AppMetrics.FontSize.callout)) .foregroundStyle(Color(hex: 0x252525)) .lineLimit(1) .minimumScaleFactor(0.78) } .frame(maxWidth: .infinity) .frame(height: 112) .background(.white, in: RoundedRectangle(cornerRadius: 8)) } .buttonStyle(.plain) Button { if isCommon { commonUris = commonMenuStore.remove(item.uri, current: commonUris) } else { commonUris = commonMenuStore.add(item.uri, current: commonUris, menuItems: viewModel.menuItems) } } label: { Image(systemName: isCommon ? "minus.circle.fill" : "plus.circle.fill") .font(.system(size: 24, weight: .bold)) .foregroundStyle(isCommon ? Color(hex: 0xFF1111) : AppDesign.primary) .background(Color.white, in: Circle()) } .buttonStyle(.plain) .offset(x: 9, y: -9) } .frame(maxWidth: .infinity) .frame(height: 112) } } } @ViewBuilder private func menuIconView(for item: HomeMenuItem) -> some View { RemoteImage(urlString: item.iconSrc, contentMode: .fit) { fallbackMenuIcon(for: item.uri) } } private func fallbackMenuIcon(for uri: String) -> some View { Image(systemName: HomeIconCatalog.iconName(for: uri)) .resizable() .scaledToFit() .foregroundStyle(AppDesign.primary) .frame(width: 34, height: 34) } private func menuItem(for uri: String) -> HomeMenuItem? { let availableURIs = Set(viewModel.menuItems.map(\.uri)) let resolvedUri = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs) guard let existing = viewModel.menuItems.first(where: { $0.uri == resolvedUri }) else { return nil } return HomeMenuItem( title: HomeMenuRouter.displayTitle(for: resolvedUri, fallback: existing.title), uri: resolvedUri, iconSrc: existing.iconSrc ) } private func isCommonURI(_ uri: String) -> Bool { let aliasKey = HomeMenuRouter.menuAliasKey(for: uri) return commonUris.contains { HomeMenuRouter.menuAliasKey(for: $0) == aliasKey } } private func openMenu(_ item: HomeMenuItem) { switch HomeMenuRouter.resolve(uri: item.uri, title: item.title) { case .tab(let tab): appRouter.select(tab) case .orders(let entry): appRouter.selectOrders(entry: entry) case .destination(let route): if route == .moreFunctions { return } router.navigate(to: .home(route)) case .unsupported(let uri, let title, _): router.navigate(to: .home(.modulePlaceholder(uri: uri, title: title))) case .placeholder(let uri, let title): HomeRouteDiagnostics.recordUnknown(uri: uri, title: title) router.navigate(to: .home(.modulePlaceholder(uri: uri, title: title))) } } private func rebuildMenus() { viewModel.buildMenus( from: permissionContext.rolePermissions, currentRoleId: permissionContext.currentRole?.id ) commonUris = commonMenuStore.load(menuItems: viewModel.menuItems) } }