// // PermissionApplyStatusView.swift // suixinkan // // Created by Codex on 2026/6/23. // import SwiftUI /// 权限申请状态页面,展示审核中、通过或驳回的角色景区权限申请。 struct PermissionApplyStatusView: View { @Environment(ScenicPermissionAPI.self) private var scenicPermissionAPI @State private var viewModel = PermissionApplyStatusViewModel() @State private var editingPending: RoleApplyPendingResponse? let applyCode: String? /// 初始化权限申请状态页面,可传入指定申请编号。 init(applyCode: String? = nil) { self.applyCode = applyCode } var body: some View { ScrollView { VStack(spacing: AppMetrics.Spacing.small) { if let pending = viewModel.pending { statusCard(pending) roleCard(pending) scenicCard(pending) } else if viewModel.loadFailed { ContentUnavailableView( "申请状态加载失败", systemImage: "exclamationmark.triangle", description: Text(viewModel.loadFailureReason ?? "请检查网络后重试") ) Button("重新加载") { Task { await viewModel.load(api: scenicPermissionAPI, applyCode: applyCode) } } .font(.system(size: AppMetrics.FontSize.footnote, weight: .semibold)) .buttonStyle(.plain) } else { ContentUnavailableView("暂无审核中的申请", systemImage: "doc.text.magnifyingglass") } } .padding(AppMetrics.Spacing.medium) } .background(Color(hex: 0xF5F7FA).ignoresSafeArea()) .navigationTitle("权限申请") .navigationBarTitleDisplayMode(.inline) .toolbar { if let pending = viewModel.pending, pending.status == 3 { ToolbarItem(placement: .topBarTrailing) { Button { editingPending = pending } label: { Label("去编辑", systemImage: "pencil") } } } } .navigationDestination(item: $editingPending) { pending in PermissionApplyView(initialPending: pending) } .task { await viewModel.load(api: scenicPermissionAPI, applyCode: applyCode) } } /// 构建审核状态卡片。 private func statusCard(_ pending: RoleApplyPendingResponse) -> some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.xSmall) { HStack { Label(statusLabel(pending), systemImage: statusIcon(pending.status)) .font(.system(size: AppMetrics.FontSize.subheadline, weight: .semibold)) .foregroundStyle(statusColor(pending.status)) .padding(.horizontal, AppMetrics.Spacing.small) .frame(height: 28) .background(statusColor(pending.status).opacity(0.12), in: Capsule()) Spacer() } row("申请编号", pending.code) row("提交时间", pending.createdAt) row("审核时间", pending.auditedAt ?? "--") row("审核人", pending.auditedBy ?? "--") row("审核备注", pending.auditNote ?? "--") } .padding(AppMetrics.Spacing.small) .background(statusColor(pending.status).opacity(0.08), in: RoundedRectangle(cornerRadius: 12)) .overlay { RoundedRectangle(cornerRadius: 12).stroke(statusColor(pending.status).opacity(0.35), lineWidth: 1) } } /// 构建角色卡片。 private func roleCard(_ pending: RoleApplyPendingResponse) -> some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.xSmall) { Text("新增角色") .font(.system(size: AppMetrics.FontSize.subheadline, weight: .semibold)) Text(pending.roleName) .font(.system(size: AppMetrics.FontSize.footnote, weight: .medium)) .foregroundStyle(AppDesign.primary) .padding(.horizontal, AppMetrics.Spacing.small) .padding(.vertical, AppMetrics.Spacing.xSmall) .background(AppDesign.primarySoft, in: Capsule()) } .frame(maxWidth: .infinity, alignment: .leading) .padding(AppMetrics.Spacing.small) .background(.white, in: RoundedRectangle(cornerRadius: 12)) } /// 构建景区卡片。 private func scenicCard(_ pending: RoleApplyPendingResponse) -> some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.xSmall) { HStack { Text("新增景区") .font(.system(size: AppMetrics.FontSize.subheadline, weight: .semibold)) Spacer() Text("\(pending.scenicList.count) 个") .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.textSecondary) } if pending.scenicList.isEmpty { Text("未选择景区") .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.textSecondary) } else { LazyVGrid(columns: [GridItem(.adaptive(minimum: 90), spacing: AppMetrics.Spacing.xSmall)], alignment: .leading, spacing: AppMetrics.Spacing.xSmall) { ForEach(pending.scenicList) { scenic in Text(scenic.name) .font(.system(size: AppMetrics.FontSize.footnote)) .foregroundStyle(AppDesign.primary) .padding(.horizontal, AppMetrics.Spacing.small) .padding(.vertical, AppMetrics.Spacing.xSmall) .background(AppDesign.primarySoft, in: Capsule()) } } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(AppMetrics.Spacing.small) .background(.white, in: RoundedRectangle(cornerRadius: 12)) } private func row(_ title: String, _ value: String?) -> some View { HStack(spacing: AppMetrics.Spacing.xSmall) { Text("\(title):") .foregroundStyle(AppDesign.textSecondary) Spacer() Text(nonEmpty(value) ?? "--") .foregroundStyle(AppDesign.textPrimary) .multilineTextAlignment(.trailing) } .font(.system(size: AppMetrics.FontSize.footnote)) } private func statusLabel(_ pending: RoleApplyPendingResponse) -> String { nonEmpty(pending.statusLabel) ?? { switch pending.status { case 2: return "已通过" case 3: return "已驳回" case 1: return "审核中" default: return "未知状态" } }() } private func statusIcon(_ status: Int) -> String { switch status { case 2: return "checkmark.circle.fill" case 3: return "xmark.octagon.fill" case 1: return "clock.badge.exclamationmark.fill" default: return "questionmark.circle.fill" } } private func statusColor(_ status: Int) -> Color { switch status { case 2: return AppDesign.success case 3: return Color(hex: 0xDC2626) case 1: return AppDesign.warning default: return AppDesign.textSecondary } } /// 去除空白字符后返回非空字符串。 private func nonEmpty(_ value: String?) -> String? { let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" return trimmed.isEmpty ? nil : trimmed } }