从 xh_test 同步 ScenicPromotion 演示页,补齐 MVVM 结构、本地资源、单元测试与模块文档,注册 promotion_page 首页入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
216 lines
5.1 KiB
Swift
216 lines
5.1 KiB
Swift
//
|
||
// ScenicPromotionModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/7/2.
|
||
//
|
||
|
||
import Foundation
|
||
import SwiftUI
|
||
|
||
// MARK: - 宣传页页面状态
|
||
|
||
/// 宣传页主页面加载与展示状态。
|
||
enum ScenicPromotionPageState: Equatable {
|
||
case idle
|
||
case loading
|
||
case normal
|
||
case empty
|
||
case error(String)
|
||
}
|
||
|
||
// MARK: - 宣传页演示场景
|
||
|
||
/// 宣传页产品演示场景,用于验收 Loading / Empty / Error 等状态。
|
||
enum ScenicPromotionDemoScenario: String, CaseIterable, Identifiable {
|
||
case normal
|
||
case noMedia
|
||
case noQueue
|
||
case loading
|
||
case error
|
||
case empty
|
||
|
||
var id: String { rawValue }
|
||
|
||
var title: String {
|
||
switch self {
|
||
case .normal: return "正常"
|
||
case .noMedia: return "无媒体"
|
||
case .noQueue: return "无排队"
|
||
case .loading: return "加载"
|
||
case .error: return "异常"
|
||
case .empty: return "空数据"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 宣传页素材类型
|
||
|
||
/// 宣传页横向素材与主视觉区支持的视频 / 照片模式。
|
||
enum ScenicPromotionMediaMode: String, CaseIterable, Identifiable {
|
||
case video
|
||
case photo
|
||
|
||
var id: String { rawValue }
|
||
|
||
var title: String {
|
||
switch self {
|
||
case .video: return "视频"
|
||
case .photo: return "照片"
|
||
}
|
||
}
|
||
|
||
var iconName: String {
|
||
switch self {
|
||
case .video: return "play.circle.fill"
|
||
case .photo: return "photo.fill"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 宣传页设置页状态
|
||
|
||
/// 宣传页设置页加载与展示状态。
|
||
enum ScenicPromotionSettingsPageState: Equatable {
|
||
case idle
|
||
case loading
|
||
case normal
|
||
case empty
|
||
case error(String)
|
||
}
|
||
|
||
// MARK: - 宣传页设置页演示场景
|
||
|
||
/// 设置页产品演示场景。
|
||
enum ScenicPromotionSettingsDemoScenario: String, CaseIterable, Identifiable {
|
||
case normal
|
||
case loading
|
||
case empty
|
||
case error
|
||
|
||
var id: String { rawValue }
|
||
|
||
var title: String {
|
||
switch self {
|
||
case .normal: return "正常"
|
||
case .loading: return "加载"
|
||
case .empty: return "空数据"
|
||
case .error: return "异常"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 设置页上传素材类型
|
||
|
||
/// 设置页中视频宣传素材与图片素材的上传类型。
|
||
enum ScenicPromotionUploadKind: String, CaseIterable, Identifiable {
|
||
case video
|
||
case image
|
||
|
||
var id: String { rawValue }
|
||
|
||
var title: String {
|
||
switch self {
|
||
case .video: return "视频宣传素材"
|
||
case .image: return "图片素材"
|
||
}
|
||
}
|
||
|
||
var shortTitle: String {
|
||
switch self {
|
||
case .video: return "视频"
|
||
case .image: return "图片"
|
||
}
|
||
}
|
||
|
||
var uploadTitle: String {
|
||
switch self {
|
||
case .video: return "上传视频宣传素材"
|
||
case .image: return "上传图片素材"
|
||
}
|
||
}
|
||
|
||
var emptyTitle: String {
|
||
switch self {
|
||
case .video: return "暂无视频宣传素材"
|
||
case .image: return "暂无图片素材"
|
||
}
|
||
}
|
||
|
||
var iconName: String {
|
||
switch self {
|
||
case .video: return "video.fill"
|
||
case .image: return "photo.on.rectangle.angled"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 宣传页数据模型
|
||
|
||
/// 宣传页整块看板数据,后续接接口时可直接替换 Mock 来源。
|
||
struct ScenicPromotionBoard: Equatable {
|
||
let scenicName: String
|
||
let subtitle: String
|
||
let qrCodeTitle: String
|
||
let spots: [ScenicPromotionSpot]
|
||
}
|
||
|
||
/// 单个打卡点,包含排队数据和该点位的视频 / 图片素材。
|
||
struct ScenicPromotionSpot: Identifiable, Equatable {
|
||
let id: String
|
||
let name: String
|
||
let iconName: String
|
||
let queue: ScenicPromotionQueueInfo?
|
||
let videos: [ScenicPromotionMedia]
|
||
let photos: [ScenicPromotionMedia]
|
||
}
|
||
|
||
/// 当前点位的排队和叫号信息。
|
||
struct ScenicPromotionQueueInfo: Equatable {
|
||
let waitingPeople: Int
|
||
let estimatedMinutes: Int
|
||
let averageMinutes: Int
|
||
let nextNumber: String
|
||
let currentNumber: String
|
||
let shootingDuration: String
|
||
}
|
||
|
||
/// 宣传页横向素材和底部主视觉共用的数据结构。
|
||
struct ScenicPromotionMedia: Identifiable, Hashable {
|
||
let id: String
|
||
let mode: ScenicPromotionMediaMode
|
||
let title: String
|
||
let subtitle: String
|
||
let duration: String?
|
||
let palette: [UInt]
|
||
let assetName: String?
|
||
let heroAssetName: String?
|
||
let previewWidth: CGFloat
|
||
}
|
||
|
||
/// 设置页中的排队打卡点,多选绑定使用。
|
||
struct ScenicPromotionQueuePoint: Identifiable, Hashable {
|
||
let id: String
|
||
let name: String
|
||
let areaName: String
|
||
}
|
||
|
||
/// 设置页中的素材打卡点,上传视频 / 图片前必须先选择。
|
||
struct ScenicPromotionMaterialPoint: Identifiable, Hashable {
|
||
let id: String
|
||
let name: String
|
||
let areaName: String
|
||
}
|
||
|
||
/// 设置页中已上传的 Mock 素材,按素材打卡点分组管理。
|
||
struct ScenicPromotionManagedMaterial: Identifiable, Hashable {
|
||
let id: String
|
||
let pointID: String
|
||
let kind: ScenicPromotionUploadKind
|
||
let title: String
|
||
let subtitle: String
|
||
let assetName: String
|
||
let duration: String?
|
||
let createdAt: String
|
||
}
|