重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
223 lines
6.4 KiB
Swift
223 lines
6.4 KiB
Swift
//
|
|
// ScenicQueueModels.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 当前排队列表中的叫号状态。
|
|
enum ScenicQueueCallStatus: Hashable {
|
|
case waiting
|
|
case called
|
|
}
|
|
|
|
/// 当前排队卡片在指定拍摄会话与设置下的操作展示状态。
|
|
struct ScenicQueueTicketActionState: Equatable {
|
|
let skipEnabled: Bool
|
|
let primaryVisible: Bool
|
|
let primaryEnabled: Bool
|
|
let primaryTitle: String
|
|
let recallVisible: Bool
|
|
let completeVisible: Bool
|
|
|
|
/// 按 Android 排队卡片规则生成操作状态。
|
|
static func make(
|
|
ticket: ScenicQueueTicket,
|
|
shootingQueueNo: String?,
|
|
remainSeconds: Int,
|
|
showStartShootingButton: Bool
|
|
) -> ScenicQueueTicketActionState {
|
|
let isShooting = shootingQueueNo == ticket.queueNo
|
|
if isShooting {
|
|
return ScenicQueueTicketActionState(
|
|
skipEnabled: false,
|
|
primaryVisible: true,
|
|
primaryEnabled: false,
|
|
primaryTitle: "剩余 \(max(0, remainSeconds)) 秒",
|
|
recallVisible: false,
|
|
completeVisible: ticket.showCompleteAction
|
|
)
|
|
}
|
|
let primaryVisible = ticket.status == .waiting || showStartShootingButton
|
|
return ScenicQueueTicketActionState(
|
|
skipEnabled: true,
|
|
primaryVisible: primaryVisible,
|
|
primaryEnabled: true,
|
|
primaryTitle: ticket.status == .waiting ? "叫号" : "开始拍摄",
|
|
recallVisible: ticket.status == .called,
|
|
completeVisible: ticket.showCompleteAction
|
|
)
|
|
}
|
|
}
|
|
|
|
/// 排队首页 Diffable Data Source 的稳定列表项。
|
|
enum ScenicQueueListItem: Hashable {
|
|
case current(ScenicQueueTicket)
|
|
case skipped(ScenicQueueSkippedTicket)
|
|
}
|
|
|
|
/// 当前排队列表展示实体。
|
|
struct ScenicQueueTicket: Hashable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
let phoneMasked: String
|
|
let dialPhoneDigits: String
|
|
let status: ScenicQueueCallStatus
|
|
let statusText: String
|
|
let waitMinutes: Int
|
|
let peopleAhead: Int
|
|
let showCompleteAction: Bool
|
|
let queueBanLabel: String
|
|
let identityTag: String
|
|
let queueTimeDisplay: String
|
|
let queueCountToday: Int
|
|
let uid: Int64
|
|
let markAsPhotog: Int
|
|
let markAsFreelancePhotog: Int
|
|
let isMissRequeue: Bool
|
|
let missRequeueText: String
|
|
|
|
/// 是否展示身份角标,普通用户不展示。
|
|
var shouldShowIdentityTag: Bool {
|
|
let text = identityTag.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return !text.isEmpty && text != "普通用户"
|
|
}
|
|
|
|
/// 返回叫号后本地更新后的副本。
|
|
func markedCalled(statusText newStatusText: String) -> ScenicQueueTicket {
|
|
ScenicQueueTicket(
|
|
recordId: recordId,
|
|
queueNo: queueNo,
|
|
phoneMasked: phoneMasked,
|
|
dialPhoneDigits: dialPhoneDigits,
|
|
status: .called,
|
|
statusText: newStatusText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? statusText : newStatusText,
|
|
waitMinutes: waitMinutes,
|
|
peopleAhead: peopleAhead,
|
|
showCompleteAction: true,
|
|
queueBanLabel: queueBanLabel,
|
|
identityTag: identityTag,
|
|
queueTimeDisplay: queueTimeDisplay,
|
|
queueCountToday: queueCountToday,
|
|
uid: uid,
|
|
markAsPhotog: markAsPhotog,
|
|
markAsFreelancePhotog: markAsFreelancePhotog,
|
|
isMissRequeue: isMissRequeue,
|
|
missRequeueText: missRequeueText
|
|
)
|
|
}
|
|
}
|
|
|
|
/// 过号列表展示实体。
|
|
struct ScenicQueueSkippedTicket: Hashable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
let phoneMasked: String
|
|
let dialPhoneDigits: String
|
|
let statusText: String
|
|
let skippedTimeDisplay: String
|
|
let queueBanLabel: String
|
|
let isMissRequeue: Bool
|
|
let missRequeueText: String
|
|
}
|
|
|
|
/// 排队页顶部统计。
|
|
struct ScenicQueueSummary: Equatable {
|
|
let peopleInQueue: Int
|
|
let avgWaitMinutes: Int
|
|
|
|
static let empty = ScenicQueueSummary(peopleInQueue: 0, avgWaitMinutes: 0)
|
|
}
|
|
|
|
/// 打卡点下拉选项。
|
|
struct ScenicQueuePunchSpotOption: Hashable {
|
|
let id: String
|
|
let label: String
|
|
}
|
|
|
|
/// 排队设置本地快照,用于快速恢复 UI 与叫号配置。
|
|
struct ScenicQueueSettingsSnapshot: Codable, Equatable {
|
|
var shootMinute: Int = 0
|
|
var shootSecond: Int = 0
|
|
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"
|
|
}
|
|
|
|
/// 待确认的过号操作。
|
|
struct ScenicQueuePendingSkip: Equatable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
}
|
|
|
|
/// 待确认的重新排队操作。
|
|
struct ScenicQueuePendingRequeue: Equatable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
}
|
|
|
|
/// 待确认的完成操作。
|
|
struct ScenicQueuePendingFinish: Equatable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
}
|
|
|
|
/// 待确认的叫号操作。
|
|
struct ScenicQueuePendingCall: Equatable {
|
|
let recordId: Int64
|
|
let queueNo: String
|
|
let isRecall: Bool
|
|
}
|
|
|
|
/// 待确认的用户标记操作。
|
|
struct ScenicQueuePendingMark: Equatable {
|
|
let uid: Int64
|
|
let queueNo: String
|
|
let phoneMasked: String
|
|
let markAsFreelancePhotog: Int
|
|
}
|
|
|
|
/// 排队模块数字字段键,用于校验定位。
|
|
enum ScenicQueueNumericFieldKey: String {
|
|
case queueDistanceMeter
|
|
case queueTakeLimit
|
|
case missCallRequeueOffset
|
|
case autoCallAheadCount
|
|
case shootMinute
|
|
case shootSecond
|
|
case firstNoticeAhead
|
|
case secondNoticeAhead
|
|
case broadcastIntervalSec
|
|
case countdownReadableThresholdSec
|
|
}
|
|
|
|
/// 排队设置页 Tab。
|
|
enum ScenicQueueSettingsTab: Int, CaseIterable {
|
|
case basic
|
|
case notify
|
|
case voice
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .basic: "基础配置"
|
|
case .notify: "通知配置"
|
|
case .voice: "语音播报"
|
|
}
|
|
}
|
|
}
|