初始提交
This commit is contained in:
355
suixinkan/Features/Account/Models/AccountContextModels.swift
Normal file
355
suixinkan/Features/Account/Models/AccountContextModels.swift
Normal file
@ -0,0 +1,355 @@
|
||||
//
|
||||
// AccountContextModels.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 角色权限响应实体,表示一个角色、该角色权限树以及可访问景区。
|
||||
struct RolePermissionResponse: Decodable, Equatable {
|
||||
let role: RoleInfo
|
||||
let scenic: [ScenicInfo]
|
||||
}
|
||||
|
||||
/// 角色实体,表示用户可切换的业务角色及其权限树。
|
||||
struct RoleInfo: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let notes: String?
|
||||
let permission: [PermissionItem]
|
||||
|
||||
/// 角色响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case notes
|
||||
case permission
|
||||
}
|
||||
|
||||
/// 创建角色实体,主要用于测试和本地状态恢复。
|
||||
init(id: Int, name: String, notes: String? = nil, permission: [PermissionItem] = []) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.notes = notes
|
||||
self.permission = permission
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容 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)
|
||||
notes = try container.decodeIfPresent(String.self, forKey: .notes)
|
||||
permission = try container.decodeIfPresent([PermissionItem].self, forKey: .permission) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 权限节点实体,表示一个菜单或功能权限,并可递归包含子权限。
|
||||
struct PermissionItem: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let pid: Int
|
||||
let name: String
|
||||
let uri: String
|
||||
let iconSrc: String?
|
||||
let children: [PermissionItem]
|
||||
|
||||
/// 权限节点响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case pid
|
||||
case name
|
||||
case uri
|
||||
case iconSrc = "icon_src"
|
||||
case children
|
||||
}
|
||||
|
||||
/// 创建权限节点,主要用于测试和本地构造权限树。
|
||||
init(id: Int, pid: Int = 0, name: String, uri: String = "", iconSrc: String? = nil, children: [PermissionItem] = []) {
|
||||
self.id = id
|
||||
self.pid = pid
|
||||
self.name = name
|
||||
self.uri = uri
|
||||
self.iconSrc = iconSrc
|
||||
self.children = children
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容 ID 类型不稳定和 children 缺失。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
pid = try container.decodeLossyInt(forKey: .pid) ?? 0
|
||||
name = try container.decodeLossyString(forKey: .name)
|
||||
uri = try container.decodeLossyString(forKey: .uri)
|
||||
iconSrc = try container.decodeIfPresent(String.self, forKey: .iconSrc)
|
||||
children = try container.decodeIfPresent([PermissionItem].self, forKey: .children) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 景区权限实体,表示某个角色可访问的景区。
|
||||
struct ScenicInfo: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let status: Int?
|
||||
let location: ScenicLocationInfo?
|
||||
let coverImg: String?
|
||||
|
||||
/// 景区权限响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case status
|
||||
case location
|
||||
case coverImg = "cover_img"
|
||||
}
|
||||
|
||||
/// 创建景区权限实体,主要用于测试和兜底列表生成。
|
||||
init(id: Int, name: String, status: Int? = nil, location: ScenicLocationInfo? = nil, coverImg: String? = nil) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.status = status
|
||||
self.location = location
|
||||
self.coverImg = coverImg
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容 location 是对象或 JSON 字符串。
|
||||
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)
|
||||
status = try container.decodeLossyInt(forKey: .status)
|
||||
coverImg = try container.decodeIfPresent(String.self, forKey: .coverImg)
|
||||
if let value = try? container.decodeIfPresent(ScenicLocationInfo.self, forKey: .location) {
|
||||
location = value
|
||||
} else if let rawValue = try? container.decodeIfPresent(String.self, forKey: .location),
|
||||
let data = rawValue.data(using: .utf8) {
|
||||
location = try? JSONDecoder().decode(ScenicLocationInfo.self, from: data)
|
||||
} else {
|
||||
location = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 景区位置实体,表示景区经纬度和地址。
|
||||
struct ScenicLocationInfo: Decodable, Equatable {
|
||||
let lng: Double
|
||||
let lat: Double
|
||||
let address: String
|
||||
|
||||
/// 景区位置响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case lng
|
||||
case lat
|
||||
case address
|
||||
}
|
||||
|
||||
/// 创建景区位置实体,主要用于测试和本地构造。
|
||||
init(lng: Double, lat: Double, address: String) {
|
||||
self.lng = lng
|
||||
self.lat = lat
|
||||
self.address = address
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容经纬度是字符串或数字。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
lng = try container.decodeLossyDouble(forKey: .lng) ?? 0
|
||||
lat = try container.decodeLossyDouble(forKey: .lat) ?? 0
|
||||
address = try container.decodeLossyString(forKey: .address)
|
||||
}
|
||||
}
|
||||
|
||||
/// 景区列表响应实体,表示用户可选择的景区列表。
|
||||
struct ScenicListAllResponse: Decodable, Equatable {
|
||||
let total: Int
|
||||
let list: [ScenicListItem]
|
||||
|
||||
/// 景区列表响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case total
|
||||
case list
|
||||
}
|
||||
|
||||
/// 创建景区列表响应,主要用于测试和兜底数据。
|
||||
init(total: Int, list: [ScenicListItem]) {
|
||||
self.total = total
|
||||
self.list = list
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容 total 类型不稳定。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||||
list = try container.decodeIfPresent([ScenicListItem].self, forKey: .list) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 景区列表项实体,表示一个可进入的景区。
|
||||
struct ScenicListItem: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let name: String
|
||||
|
||||
/// 景区列表项响应的 JSON 字段映射。
|
||||
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 StoreItem: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let scenicId: Int
|
||||
let name: String
|
||||
let address: String
|
||||
let status: Int
|
||||
let statusText: String
|
||||
let tel: String?
|
||||
let logo: String?
|
||||
|
||||
/// 门店响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case scenicId = "scenic_id"
|
||||
case name
|
||||
case address
|
||||
case status
|
||||
case statusText = "status_text"
|
||||
case tel
|
||||
case logo
|
||||
}
|
||||
|
||||
/// 创建门店实体,主要用于测试和本地构造。
|
||||
init(
|
||||
id: Int,
|
||||
scenicId: Int,
|
||||
name: String,
|
||||
address: String = "",
|
||||
status: Int = 0,
|
||||
statusText: String = "",
|
||||
tel: String? = nil,
|
||||
logo: String? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.scenicId = scenicId
|
||||
self.name = name
|
||||
self.address = address
|
||||
self.status = status
|
||||
self.statusText = statusText
|
||||
self.tel = tel
|
||||
self.logo = logo
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容字段类型不稳定和可选字段缺失。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
|
||||
name = try container.decodeLossyString(forKey: .name)
|
||||
address = try container.decodeLossyString(forKey: .address)
|
||||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||||
statusText = try container.decodeLossyString(forKey: .statusText)
|
||||
tel = try container.decodeIfPresent(String.self, forKey: .tel)
|
||||
logo = try container.decodeIfPresent(String.self, forKey: .logo)
|
||||
}
|
||||
}
|
||||
|
||||
/// 景点实体,表示某个景区下的景点或打卡点。
|
||||
struct ScenicSpotItem: Decodable, Equatable, Identifiable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let status: Int
|
||||
let statusLabel: String
|
||||
|
||||
/// 景点响应的 JSON 字段映射。
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case status
|
||||
case statusLabel = "status_label"
|
||||
}
|
||||
|
||||
/// 创建景点实体,主要用于测试和本地构造。
|
||||
init(id: Int, name: String, status: Int = 0, statusLabel: String = "") {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.status = status
|
||||
self.statusLabel = statusLabel
|
||||
}
|
||||
|
||||
/// 自定义解码,兼容字段类型不稳定。
|
||||
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)
|
||||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||||
statusLabel = try container.decodeLossyString(forKey: .statusLabel)
|
||||
}
|
||||
}
|
||||
|
||||
private extension KeyedDecodingContainer {
|
||||
/// 将 String、数字和 Bool 宽松解码为字符串。
|
||||
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 ? "true" : "false"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// 将 Int、Double 或数字字符串宽松解码为整数。
|
||||
func decodeLossyInt(forKey key: Key) throws -> Int? {
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let intValue = Int(text) {
|
||||
return intValue
|
||||
}
|
||||
if let doubleValue = Double(text) {
|
||||
return Int(doubleValue)
|
||||
}
|
||||
}
|
||||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||||
return Int(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// 将 Double、Int 或数字字符串宽松解码为浮点数。
|
||||
func decodeLossyDouble(forKey key: Key) throws -> Double? {
|
||||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||||
return Double(value)
|
||||
}
|
||||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||||
return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user