Add ScenicPermission module and AMap CocoaPods with simulator support.
Integrate scenic selection and permission flows into home routing, add AMap SDK for device builds while keeping arm64 simulators compilable via Podfile post_install hooks. 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