新增宣传页 Mock 模块并接入首页路由。
从 xh_test 同步 ScenicPromotion 演示页,补齐 MVVM 结构、本地资源、单元测试与模块文档,注册 promotion_page 首页入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,215 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user