Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
380 lines
16 KiB
Swift
380 lines
16 KiB
Swift
//
|
||
// ScenicQueueSettingsModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/25.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 排队设置响应。
|
||
struct ScenicQueueSettingData: Decodable, Equatable {
|
||
let exists: Bool
|
||
let setting: ScenicQueueSettingItem?
|
||
let time: String?
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case exists
|
||
case setting
|
||
case time
|
||
}
|
||
|
||
/// 创建排队设置响应。
|
||
init(exists: Bool = false, setting: ScenicQueueSettingItem? = nil, time: String? = nil) {
|
||
self.exists = exists
|
||
self.setting = setting
|
||
self.time = time
|
||
}
|
||
|
||
/// 宽松解码排队设置响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
exists = try container.decodeLossyBool(forKey: .exists) ?? false
|
||
setting = try container.decodeIfPresent(ScenicQueueSettingItem.self, forKey: .setting)
|
||
time = try container.decodeLossyString(forKey: .time)
|
||
}
|
||
}
|
||
|
||
/// 排队设置实体。
|
||
struct ScenicQueueSettingItem: Decodable, Equatable {
|
||
let id: Int64?
|
||
let scenicId: Int?
|
||
let scenicSpotId: Int?
|
||
let scenicSpotName: String
|
||
let photoEstimateMin: Int
|
||
let photoEstimateSec: Int
|
||
let firstNoticeThresholdPos: Int
|
||
let firstNoticeSmsEnabled: Int
|
||
let firstNoticeCallEnabled: Int
|
||
let secondNoticeThresholdPos: Int
|
||
let secondNoticeSmsEnabled: Int
|
||
let secondNoticeCallEnabled: Int
|
||
let countdownBroadcastIntervalSec: Int
|
||
let countdownReadableThresholdSec: Int
|
||
let queueDistanceMeter: Int
|
||
let queueTakeLimit: Int
|
||
let missCallRequeueOffset: Int
|
||
let showStartShootButton: Int?
|
||
let autoCallNextCount: Int?
|
||
let businessStartTime: String
|
||
let businessEndTime: String
|
||
let status: Int
|
||
let remark: String
|
||
let voiceBroadcasts: [ScenicQueueVoiceBroadcastItem]
|
||
let createdAt: String
|
||
let updatedAt: String
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case scenicId = "scenic_id"
|
||
case scenicSpotId = "scenic_spot_id"
|
||
case scenicSpotName = "scenic_spot_name"
|
||
case photoEstimateMin = "photo_estimate_min"
|
||
case photoEstimateSec = "photo_estimate_sec"
|
||
case firstNoticeThresholdPos = "first_notice_threshold_pos"
|
||
case firstNoticeSmsEnabled = "first_notice_sms_enabled"
|
||
case firstNoticeCallEnabled = "first_notice_call_enabled"
|
||
case secondNoticeThresholdPos = "second_notice_threshold_pos"
|
||
case secondNoticeSmsEnabled = "second_notice_sms_enabled"
|
||
case secondNoticeCallEnabled = "second_notice_call_enabled"
|
||
case countdownBroadcastIntervalSec = "countdown_broadcast_interval_sec"
|
||
case countdownReadableThresholdSec = "countdown_readable_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootButton = "show_start_shoot_button"
|
||
case autoCallNextCount = "auto_call_next_count"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
case status
|
||
case remark
|
||
case voiceBroadcasts = "voice_broadcasts"
|
||
case createdAt = "created_at"
|
||
case updatedAt = "updated_at"
|
||
}
|
||
|
||
/// 宽松解码排队设置实体。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = (try container.decodeLossyInt(forKey: .id)).map(Int64.init)
|
||
scenicId = try container.decodeLossyInt(forKey: .scenicId)
|
||
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId)
|
||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||
photoEstimateMin = try container.decodeLossyInt(forKey: .photoEstimateMin) ?? 0
|
||
photoEstimateSec = try container.decodeLossyInt(forKey: .photoEstimateSec) ?? 0
|
||
firstNoticeThresholdPos = try container.decodeLossyInt(forKey: .firstNoticeThresholdPos) ?? 0
|
||
firstNoticeSmsEnabled = try container.decodeLossyInt(forKey: .firstNoticeSmsEnabled) ?? 0
|
||
firstNoticeCallEnabled = try container.decodeLossyInt(forKey: .firstNoticeCallEnabled) ?? 0
|
||
secondNoticeThresholdPos = try container.decodeLossyInt(forKey: .secondNoticeThresholdPos) ?? 0
|
||
secondNoticeSmsEnabled = try container.decodeLossyInt(forKey: .secondNoticeSmsEnabled) ?? 0
|
||
secondNoticeCallEnabled = try container.decodeLossyInt(forKey: .secondNoticeCallEnabled) ?? 0
|
||
countdownBroadcastIntervalSec = try container.decodeLossyInt(forKey: .countdownBroadcastIntervalSec) ?? 50
|
||
countdownReadableThresholdSec = try container.decodeLossyInt(forKey: .countdownReadableThresholdSec) ?? 15
|
||
queueDistanceMeter = try container.decodeLossyInt(forKey: .queueDistanceMeter) ?? 0
|
||
queueTakeLimit = try container.decodeLossyInt(forKey: .queueTakeLimit) ?? 0
|
||
missCallRequeueOffset = try container.decodeLossyInt(forKey: .missCallRequeueOffset) ?? 1
|
||
showStartShootButton = try container.decodeLossyInt(forKey: .showStartShootButton)
|
||
autoCallNextCount = try container.decodeLossyInt(forKey: .autoCallNextCount)
|
||
businessStartTime = try container.decodeLossyString(forKey: .businessStartTime)
|
||
businessEndTime = try container.decodeLossyString(forKey: .businessEndTime)
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 1
|
||
remark = try container.decodeLossyString(forKey: .remark)
|
||
voiceBroadcasts = (try? container.decodeIfPresent([ScenicQueueVoiceBroadcastItem].self, forKey: .voiceBroadcasts)) ?? []
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
updatedAt = try container.decodeLossyString(forKey: .updatedAt)
|
||
}
|
||
}
|
||
|
||
/// 排队语音播报预设。
|
||
struct ScenicQueueVoiceBroadcastItem: Codable, Identifiable, Equatable {
|
||
var id: String { "\(sortOrder)-\(content)" }
|
||
let content: String
|
||
let sortOrder: Int
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case content
|
||
case sortOrder = "sort_order"
|
||
}
|
||
|
||
/// 创建语音播报预设。
|
||
init(content: String, sortOrder: Int) {
|
||
self.content = content
|
||
self.sortOrder = sortOrder
|
||
}
|
||
|
||
/// 宽松解码语音播报预设。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
content = try container.decodeLossyString(forKey: .content).trimmingCharacters(in: .whitespacesAndNewlines)
|
||
sortOrder = try container.decodeLossyInt(forKey: .sortOrder) ?? 0
|
||
}
|
||
}
|
||
|
||
/// 保存排队设置请求体。
|
||
struct ScenicQueueSaveSettingRequest: Encodable, Equatable {
|
||
let scenicId: Int
|
||
let scenicSpotId: Int
|
||
let photoEstimateMin: Int
|
||
let photoEstimateSec: Int
|
||
let firstNoticeThresholdPos: Int
|
||
let firstNoticeSmsEnabled: Int
|
||
let firstNoticeCallEnabled: Int
|
||
let secondNoticeThresholdPos: Int
|
||
let secondNoticeSmsEnabled: Int
|
||
let secondNoticeCallEnabled: Int
|
||
let countdownBroadcastIntervalSec: Int
|
||
let countdownReadableThresholdSec: Int
|
||
let queueDistanceMeter: Int?
|
||
let queueTakeLimit: Int?
|
||
let missCallRequeueOffset: Int?
|
||
let showStartShootButton: Int?
|
||
let autoCallNextCount: Int?
|
||
let businessStartTime: String
|
||
let businessEndTime: String
|
||
let voiceBroadcasts: [ScenicQueueVoiceBroadcastItem]
|
||
let status: Int
|
||
let remark: String?
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case scenicId = "scenic_id"
|
||
case scenicSpotId = "scenic_spot_id"
|
||
case photoEstimateMin = "photo_estimate_min"
|
||
case photoEstimateSec = "photo_estimate_sec"
|
||
case firstNoticeThresholdPos = "first_notice_threshold_pos"
|
||
case firstNoticeSmsEnabled = "first_notice_sms_enabled"
|
||
case firstNoticeCallEnabled = "first_notice_call_enabled"
|
||
case secondNoticeThresholdPos = "second_notice_threshold_pos"
|
||
case secondNoticeSmsEnabled = "second_notice_sms_enabled"
|
||
case secondNoticeCallEnabled = "second_notice_call_enabled"
|
||
case countdownBroadcastIntervalSec = "countdown_broadcast_interval_sec"
|
||
case countdownReadableThresholdSec = "countdown_readable_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootButton = "show_start_shoot_button"
|
||
case autoCallNextCount = "auto_call_next_count"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
case voiceBroadcasts = "voice_broadcasts"
|
||
case status
|
||
case remark
|
||
}
|
||
}
|
||
|
||
/// 排队设置变更日志响应。
|
||
struct ScenicQueueSettingChangeLogData: Decodable, Equatable {
|
||
let total: Int
|
||
let page: Int
|
||
let pageSize: Int
|
||
let list: [ScenicQueueSettingChangeLogItem]
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case total
|
||
case page
|
||
case pageSize = "page_size"
|
||
case list
|
||
}
|
||
|
||
/// 创建变更日志响应。
|
||
init(total: Int = 0, page: Int = 1, pageSize: Int = 20, list: [ScenicQueueSettingChangeLogItem] = []) {
|
||
self.total = total
|
||
self.page = page
|
||
self.pageSize = pageSize
|
||
self.list = list
|
||
}
|
||
|
||
/// 宽松解码变更日志响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||
page = try container.decodeLossyInt(forKey: .page) ?? 1
|
||
pageSize = try container.decodeLossyInt(forKey: .pageSize) ?? 20
|
||
list = (try? container.decodeIfPresent([ScenicQueueSettingChangeLogItem].self, forKey: .list)) ?? []
|
||
}
|
||
}
|
||
|
||
/// 排队设置变更日志项。
|
||
struct ScenicQueueSettingChangeLogItem: Decodable, Equatable, Identifiable {
|
||
let id: Int64
|
||
let scenicSpotName: String
|
||
let operatorName: String
|
||
let operatorPhoneTail: String
|
||
let summary: String
|
||
let displayText: String
|
||
let createdAt: String
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case scenicSpotName = "scenic_spot_name"
|
||
case operatorName = "operator_name"
|
||
case operatorPhoneTail = "operator_phone_tail"
|
||
case summary
|
||
case displayText = "display_text"
|
||
case createdAt = "created_at"
|
||
}
|
||
|
||
/// 宽松解码变更日志项。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
|
||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||
operatorName = try container.decodeLossyString(forKey: .operatorName)
|
||
operatorPhoneTail = try container.decodeLossyString(forKey: .operatorPhoneTail)
|
||
summary = try container.decodeLossyString(forKey: .summary)
|
||
displayText = try container.decodeLossyString(forKey: .displayText)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
}
|
||
}
|
||
|
||
/// 排队取号二维码响应。
|
||
struct ScenicQueueShootQueueQRCodeData: Decodable, Equatable {
|
||
let qrcodeUrl: String
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case qrcodeUrl = "qrcode_url"
|
||
}
|
||
|
||
/// 创建二维码响应。
|
||
init(qrcodeUrl: String = "") {
|
||
self.qrcodeUrl = qrcodeUrl
|
||
}
|
||
|
||
/// 宽松解码二维码响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
qrcodeUrl = try container.decodeLossyString(forKey: .qrcodeUrl)
|
||
}
|
||
}
|
||
|
||
/// 本地排队设置快照,用于服务器无配置或离线时保留关键配置。
|
||
struct ScenicQueueSettingsSnapshot: Codable, Equatable {
|
||
var shootMinute: Int = 0
|
||
var shootSecond: Int = 30
|
||
var firstAheadCount: Int = 0
|
||
var firstSms: Bool = false
|
||
var firstPhone: Bool = false
|
||
var secondAheadCount: Int = 0
|
||
var secondSms: Bool = false
|
||
var secondPhone: Bool = false
|
||
var broadcastIntervalSec: Int = 50
|
||
var countdownThresholdSec: Int = 15
|
||
var queueDistanceMeter: Int = 0
|
||
var queueTakeLimit: Int = 0
|
||
var missCallRequeueOffset: Int = 1
|
||
var showStartShootingButton: Bool = true
|
||
var autoCallAheadCount: Int = 0
|
||
var quickCallButtonEnabled: Bool = false
|
||
var prepareCallButtonEnabled: Bool = false
|
||
var businessOpen: Bool = true
|
||
var businessStartTime: String = "10:00"
|
||
var businessEndTime: String = "20:00"
|
||
|
||
/// 枚举,定义相关常量或状态。
|
||
enum CodingKeys: String, CodingKey {
|
||
case shootMinute = "shoot_minute"
|
||
case shootSecond = "shoot_second"
|
||
case firstAheadCount = "first_ahead_count"
|
||
case firstSms = "first_sms"
|
||
case firstPhone = "first_phone"
|
||
case secondAheadCount = "second_ahead_count"
|
||
case secondSms = "second_sms"
|
||
case secondPhone = "second_phone"
|
||
case broadcastIntervalSec = "broadcast_interval_sec"
|
||
case countdownThresholdSec = "countdown_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootingButton = "show_start_shooting_button"
|
||
case autoCallAheadCount = "auto_call_ahead_count"
|
||
case quickCallButtonEnabled = "quick_call_button_enabled"
|
||
case prepareCallButtonEnabled = "prepare_call_button_enabled"
|
||
case businessOpen = "business_open"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
}
|
||
}
|
||
|
||
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),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines),
|
||
!text.isEmpty {
|
||
return Int(text) ?? Int(Double(text) ?? 0)
|
||
}
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value ? 1 : 0 }
|
||
return nil
|
||
}
|
||
|
||
/// 解码宽松布尔。
|
||
func decodeLossyBool(forKey key: Key) throws -> Bool? {
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value }
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) { return value != 0 }
|
||
if let value = try? decodeIfPresent(String.self, forKey: key),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
|
||
!text.isEmpty {
|
||
if ["1", "true", "yes"].contains(text) { return true }
|
||
if ["0", "false", "no"].contains(text) { return false }
|
||
}
|
||
return nil
|
||
}
|
||
}
|