将景区选择与权限申请流程接入首页路由,集成高德 SDK 用于真机构建,并通过 Podfile post_install 钩子保证 arm64 模拟器可编译。 Co-authored-by: Cursor <cursoragent@cursor.com>
337 lines
12 KiB
Swift
337 lines
12 KiB
Swift
//
|
||
// ScenicPermissionModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 景区申请地区节点实体,表示省市区树中的一个可选节点。
|
||
struct ScenicAreaNode: Decodable, Equatable, Identifiable {
|
||
let id: String
|
||
let code: String
|
||
let name: String
|
||
let children: [ScenicAreaNode]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case code
|
||
case name
|
||
case children
|
||
}
|
||
|
||
/// 创建地区节点,主要用于单元测试和本地构造。
|
||
init(id: String, code: String = "", name: String, children: [ScenicAreaNode] = []) {
|
||
self.id = id
|
||
self.code = code
|
||
self.name = name
|
||
self.children = children
|
||
}
|
||
|
||
/// 自定义解码,兼容后端 id/code 为数字或字符串。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
code = try container.decodeLossyString(forKey: .code)
|
||
let rawId = try container.decodeLossyString(forKey: .id).trimmingCharacters(in: .whitespacesAndNewlines)
|
||
id = rawId.isEmpty ? code : rawId
|
||
name = try container.decodeLossyString(forKey: .name)
|
||
children = try container.decodeIfPresent([ScenicAreaNode].self, forKey: .children) ?? []
|
||
}
|
||
}
|
||
|
||
/// 景区申请待审核列表响应实体,表示当前用户提交过的景区入驻申请。
|
||
struct ScenicApplicationPendingsResponse: Decodable, Equatable {
|
||
let items: [ScenicApplicationPendingResponse]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case items
|
||
case list
|
||
case data
|
||
}
|
||
|
||
/// 创建待审核列表响应实体,主要用于测试替身。
|
||
init(items: [ScenicApplicationPendingResponse] = []) {
|
||
self.items = items
|
||
}
|
||
|
||
/// 自定义解码,兼容后端返回 items/list/data 三种列表字段。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
items = try container.decodeIfPresent([ScenicApplicationPendingResponse].self, forKey: .items)
|
||
?? container.decodeIfPresent([ScenicApplicationPendingResponse].self, forKey: .list)
|
||
?? container.decodeIfPresent([ScenicApplicationPendingResponse].self, forKey: .data)
|
||
?? []
|
||
}
|
||
}
|
||
|
||
/// 景区申请待审核实体,表示新增景区申请的审核状态和表单快照。
|
||
struct ScenicApplicationPendingResponse: Decodable, Equatable, Identifiable {
|
||
let id: Int
|
||
let code: String
|
||
let scenicId: Int
|
||
let scenicName: String
|
||
let scenicImages: String
|
||
let scenicProvince: String
|
||
let scenicCity: String
|
||
let coopType: Int
|
||
let remark: String?
|
||
let status: Int
|
||
let rejectReason: String?
|
||
let auditedBy: String?
|
||
let auditedAt: String?
|
||
let auditNote: String?
|
||
let createdAt: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case code
|
||
case scenicId = "scenic_id"
|
||
case scenicName = "scenic_name"
|
||
case scenicImages = "scenic_images"
|
||
case scenicProvince = "scenic_province"
|
||
case scenicCity = "scenic_city"
|
||
case coopType = "coop_type"
|
||
case remark
|
||
case status
|
||
case rejectReason = "reject_reason"
|
||
case auditedBy = "audited_by"
|
||
case auditedAt = "audited_at"
|
||
case auditNote = "audit_note"
|
||
case createdAt = "created_at"
|
||
}
|
||
|
||
/// 创建景区申请待审核实体,主要用于测试和本地回填。
|
||
init(
|
||
id: Int,
|
||
code: String = "",
|
||
scenicId: Int = 0,
|
||
scenicName: String,
|
||
scenicImages: String = "",
|
||
scenicProvince: String = "",
|
||
scenicCity: String = "",
|
||
coopType: Int = 1,
|
||
remark: String? = nil,
|
||
status: Int = 1,
|
||
rejectReason: String? = nil,
|
||
auditedBy: String? = nil,
|
||
auditedAt: String? = nil,
|
||
auditNote: String? = nil,
|
||
createdAt: String = ""
|
||
) {
|
||
self.id = id
|
||
self.code = code
|
||
self.scenicId = scenicId
|
||
self.scenicName = scenicName
|
||
self.scenicImages = scenicImages
|
||
self.scenicProvince = scenicProvince
|
||
self.scenicCity = scenicCity
|
||
self.coopType = coopType
|
||
self.remark = remark
|
||
self.status = status
|
||
self.rejectReason = rejectReason
|
||
self.auditedBy = auditedBy
|
||
self.auditedAt = auditedAt
|
||
self.auditNote = auditNote
|
||
self.createdAt = createdAt
|
||
}
|
||
|
||
/// 自定义解码,兼容后端数字和字符串混用。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||
code = try container.decodeLossyString(forKey: .code)
|
||
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
|
||
scenicName = try container.decodeLossyString(forKey: .scenicName)
|
||
if let images = try? container.decode([String].self, forKey: .scenicImages) {
|
||
scenicImages = images.joined(separator: ",")
|
||
} else {
|
||
scenicImages = try container.decodeLossyString(forKey: .scenicImages)
|
||
}
|
||
scenicProvince = try container.decodeLossyString(forKey: .scenicProvince)
|
||
scenicCity = try container.decodeLossyString(forKey: .scenicCity)
|
||
coopType = try container.decodeLossyInt(forKey: .coopType) ?? 1
|
||
remark = try? container.decodeIfPresent(String.self, forKey: .remark)
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||
rejectReason = try? container.decodeIfPresent(String.self, forKey: .rejectReason)
|
||
auditedBy = try? container.decodeIfPresent(String.self, forKey: .auditedBy)
|
||
auditedAt = try? container.decodeIfPresent(String.self, forKey: .auditedAt)
|
||
auditNote = try? container.decodeIfPresent(String.self, forKey: .auditNote)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
}
|
||
}
|
||
|
||
/// 景区申请提交请求实体,表示新增景区入驻申请表单。
|
||
struct ScenicApplicationSubmitRequest: Encodable, Equatable {
|
||
let scenicName: String?
|
||
let scenicImages: [String]?
|
||
let scenicProvince: String
|
||
let scenicCity: String
|
||
let coopType: Int
|
||
let remark: String
|
||
let scenicId: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case scenicName = "scenic_name"
|
||
case scenicImages = "scenic_images"
|
||
case scenicProvince = "scenic_province"
|
||
case scenicCity = "scenic_city"
|
||
case coopType = "coop_type"
|
||
case remark
|
||
case scenicId = "scenic_id"
|
||
}
|
||
}
|
||
|
||
/// 角色权限申请实体,表示一条角色/景区权限申请状态。
|
||
struct RoleApplyPendingResponse: Decodable, Equatable, Hashable, Identifiable {
|
||
let id: Int
|
||
let code: String
|
||
let roleId: Int
|
||
let roleName: String
|
||
let scenicList: [RoleApplyScenicItem]
|
||
let status: Int
|
||
let statusLabel: String
|
||
let createdAt: String
|
||
let auditedBy: String?
|
||
let auditedAt: String?
|
||
let auditNote: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case code
|
||
case roleId = "role_id"
|
||
case roleName = "role_name"
|
||
case scenicList = "scenic_list"
|
||
case status
|
||
case statusLabel = "status_label"
|
||
case createdAt = "created_at"
|
||
case auditedBy = "audited_by"
|
||
case auditedAt = "audited_at"
|
||
case auditNote = "audit_note"
|
||
}
|
||
|
||
/// 创建角色权限申请实体,主要用于测试和状态页回填。
|
||
init(
|
||
id: Int,
|
||
code: String = "",
|
||
roleId: Int,
|
||
roleName: String,
|
||
scenicList: [RoleApplyScenicItem] = [],
|
||
status: Int = 1,
|
||
statusLabel: String = "审核中",
|
||
createdAt: String = "",
|
||
auditedBy: String? = nil,
|
||
auditedAt: String? = nil,
|
||
auditNote: String? = nil
|
||
) {
|
||
self.id = id
|
||
self.code = code
|
||
self.roleId = roleId
|
||
self.roleName = roleName
|
||
self.scenicList = scenicList
|
||
self.status = status
|
||
self.statusLabel = statusLabel
|
||
self.createdAt = createdAt
|
||
self.auditedBy = auditedBy
|
||
self.auditedAt = auditedAt
|
||
self.auditNote = auditNote
|
||
}
|
||
|
||
/// 自定义解码,兼容后端字段类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||
code = try container.decodeLossyString(forKey: .code)
|
||
roleId = try container.decodeLossyInt(forKey: .roleId) ?? 0
|
||
roleName = try container.decodeLossyString(forKey: .roleName)
|
||
scenicList = try container.decodeIfPresent([RoleApplyScenicItem].self, forKey: .scenicList) ?? []
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||
statusLabel = try container.decodeLossyString(forKey: .statusLabel)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
auditedBy = try? container.decodeIfPresent(String.self, forKey: .auditedBy)
|
||
auditedAt = try? container.decodeIfPresent(String.self, forKey: .auditedAt)
|
||
auditNote = try? container.decodeIfPresent(String.self, forKey: .auditNote)
|
||
}
|
||
}
|
||
|
||
/// 角色权限申请景区实体,表示申请中勾选的一个景区。
|
||
struct RoleApplyScenicItem: Decodable, Equatable, Hashable, Identifiable {
|
||
let id: Int
|
||
let name: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case name
|
||
}
|
||
|
||
/// 创建角色权限申请景区实体,主要用于测试。
|
||
init(id: Int, name: String) {
|
||
self.id = id
|
||
self.name = name
|
||
}
|
||
|
||
/// 自定义解码,兼容景区 ID 类型不稳定。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||
name = try container.decodeLossyString(forKey: .name)
|
||
}
|
||
}
|
||
|
||
/// 角色权限提交请求实体,表示申请某角色在多个景区的权限。
|
||
struct RoleApplySubmitRequest: Encodable, Equatable {
|
||
let scenicId: [Int]
|
||
let roleId: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case scenicId = "scenic_id"
|
||
case roleId = "role_id"
|
||
}
|
||
}
|
||
|
||
/// 上传占位请求实体,表示景区申请已上传文件的轻量元信息。
|
||
struct ScenicApplicationUploadPlaceholder: Encodable, Equatable {
|
||
let fileName: String
|
||
let fileType: String
|
||
let fileSize: Int64
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case fileName = "file_name"
|
||
case fileType = "file_type"
|
||
case fileSize = "file_size"
|
||
}
|
||
}
|
||
|
||
private extension KeyedDecodingContainer {
|
||
/// 宽松解码字符串,兼容后端数字、布尔值和空值。
|
||
func decodeLossyString(forKey key: Key) throws -> String {
|
||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||
return value
|
||
}
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||
return String(value)
|
||
}
|
||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||
return String(value)
|
||
}
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
|
||
return value ? "1" : "0"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
/// 宽松解码整数,兼容后端字符串和浮点数。
|
||
func decodeLossyInt(forKey key: Key) throws -> Int? {
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||
return value
|
||
}
|
||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||
return Int(value)
|
||
}
|
||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||
return Int(value.trimmingCharacters(in: .whitespacesAndNewlines))
|
||
}
|
||
return nil
|
||
}
|
||
}
|