新增宣传页 Mock 模块并接入首页路由。

从 xh_test 同步 ScenicPromotion 演示页,补齐 MVVM 结构、本地资源、单元测试与模块文档,注册 promotion_page 首页入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-02 15:29:25 +08:00
parent a528d9b0d7
commit 1b1e93b9ca
30 changed files with 2661 additions and 4 deletions

View File

@ -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
}