持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
193 lines
7.5 KiB
Swift
193 lines
7.5 KiB
Swift
//
|
||
// HomeMoreFunctionsView.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 首页全部功能视图,展示常用应用和当前角色拥有的更多功能。
|
||
struct HomeMoreFunctionsView: View {
|
||
@EnvironmentObject private var accountContext: AccountContext
|
||
@EnvironmentObject private var permissionContext: PermissionContext
|
||
@EnvironmentObject private var appRouter: AppRouter
|
||
@EnvironmentObject private var router: RouterPath
|
||
|
||
@StateObject 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 {
|
||
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.medium)
|
||
.padding(.bottom, AppMetrics.Spacing.xxLarge)
|
||
}
|
||
.background(Color(hex: 0xF5F5F5))
|
||
.navigationTitle("全部功能")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.task {
|
||
rebuildMenus()
|
||
}
|
||
.onChange(of: permissionContext.currentRole?.roleCode) { _ in
|
||
rebuildMenus()
|
||
}
|
||
.onChange(of: permissionContext.rolePermissions.count) { _ in
|
||
rebuildMenus()
|
||
}
|
||
}
|
||
|
||
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 {
|
||
let roleCode = permissionContext.currentAppRole?.rawValue
|
||
if isCommon {
|
||
commonUris = commonMenuStore.remove(
|
||
item.uri,
|
||
current: commonUris,
|
||
roleCode: roleCode,
|
||
accountScope: accountContext.accountCachePrefix
|
||
)
|
||
} else {
|
||
commonUris = commonMenuStore.add(
|
||
item.uri,
|
||
current: commonUris,
|
||
menuItems: viewModel.menuItems,
|
||
roleCode: roleCode,
|
||
accountScope: accountContext.accountCachePrefix
|
||
)
|
||
}
|
||
} 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 .orderPush(let route):
|
||
appRouter.select(.orders)
|
||
appRouter.router(for: .orders).navigate(to: .orders(route))
|
||
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() {
|
||
let roleCode = permissionContext.currentAppRole?.rawValue
|
||
let legacyRoleId = permissionContext.currentRole?.id
|
||
viewModel.buildMenus(
|
||
from: permissionContext.rolePermissions,
|
||
currentRoleCode: roleCode
|
||
)
|
||
commonUris = commonMenuStore.load(
|
||
menuItems: viewModel.menuItems,
|
||
roleCode: roleCode,
|
||
accountScope: accountContext.accountCachePrefix,
|
||
legacyRoleId: legacyRoleId,
|
||
topLevelPermissionURIs: permissionContext.topLevelPermissionURIs(for: roleCode)
|
||
)
|
||
}
|
||
}
|