新增景区权限模块,集成 AMap CocoaPods 并支持模拟器
将景区选择与权限申请流程接入首页路由,集成高德 SDK 用于真机构建,并通过 Podfile post_install 钩子保证 arm64 模拟器可编译。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,189 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user