// // 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 roleCode: String let notes: String? let permission: [PermissionItem] /// 角色响应的 JSON 字段映射。 enum CodingKeys: String, CodingKey { case id case name case roleCode = "role_code" case notes case permission } /// 创建角色实体,主要用于测试和本地状态恢复。 init(id: Int, name: String, roleCode: String = "", notes: String? = nil, permission: [PermissionItem] = []) { self.id = id self.name = name self.roleCode = roleCode 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) roleCode = try container.decodeLossyString(forKey: .roleCode) 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 } }