235 lines
7.6 KiB
Swift
235 lines
7.6 KiB
Swift
//
|
|
// ScenicPromotionSettingsSharedViews.swift
|
|
// suixinkan
|
|
//
|
|
// Created by Codex on 2026/7/2.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// 设置页通用卡片容器。
|
|
struct ScenicPromotionSettingsCard<Content: View>: View {
|
|
let title: String
|
|
let subtitle: String
|
|
let icon: String
|
|
let tint: Color
|
|
@ViewBuilder let content: Content
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
HStack(alignment: .top, spacing: 12) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 18, weight: .bold))
|
|
.foregroundStyle(tint)
|
|
.frame(width: 40, height: 40)
|
|
.background(tint.opacity(0.12), in: RoundedRectangle(cornerRadius: 12))
|
|
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
Text(title)
|
|
.font(.system(size: 18, weight: .heavy))
|
|
.foregroundStyle(AppDesign.textPrimary)
|
|
Text(subtitle)
|
|
.font(.system(size: 13, weight: .medium))
|
|
.foregroundStyle(AppDesign.textSecondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
}
|
|
|
|
content
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(.white, in: RoundedRectangle(cornerRadius: 18))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 18)
|
|
.stroke(AppDesign.border, lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// 设置页下拉选择框样式。
|
|
struct ScenicPromotionSettingsSelectionField: View {
|
|
let title: String
|
|
let subtitle: String
|
|
let icon: String
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
Text(title)
|
|
.font(.system(size: 15, weight: .bold))
|
|
.foregroundStyle(AppDesign.textPrimary)
|
|
.lineLimit(2)
|
|
.multilineTextAlignment(.leading)
|
|
.minimumScaleFactor(0.82)
|
|
Text(subtitle)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.foregroundStyle(AppDesign.textSecondary)
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
Image(systemName: icon)
|
|
.font(.system(size: 13, weight: .bold))
|
|
.foregroundStyle(AppDesign.primary)
|
|
.frame(width: 30, height: 30)
|
|
.background(AppDesign.primarySoft, in: Circle())
|
|
}
|
|
.padding(14)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(AppDesign.primarySoft, in: RoundedRectangle(cornerRadius: 14))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.stroke(Color(hex: 0xCFE1FF), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// 设置页表单内联提示。
|
|
struct ScenicPromotionSettingsInlineNotice: View {
|
|
let text: String
|
|
let tint: Color
|
|
|
|
var body: some View {
|
|
Label(text, systemImage: "exclamationmark.circle.fill")
|
|
.font(.system(size: 13, weight: .semibold))
|
|
.foregroundStyle(tint)
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 9)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(tint.opacity(0.1), in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
}
|
|
|
|
/// 设置页某个素材点当前没有素材时的空状态。
|
|
struct ScenicPromotionSettingsMaterialEmptyView: View {
|
|
let kind: ScenicPromotionUploadKind
|
|
|
|
var body: some View {
|
|
VStack(spacing: 8) {
|
|
Image(systemName: kind.iconName)
|
|
.font(.system(size: 28, weight: .semibold))
|
|
.foregroundStyle(AppDesign.primary)
|
|
.frame(width: 58, height: 58)
|
|
.background(AppDesign.primarySoft, in: RoundedRectangle(cornerRadius: 16))
|
|
|
|
Text(kind.emptyTitle)
|
|
.font(.system(size: 15, weight: .bold))
|
|
.foregroundStyle(AppDesign.textPrimary)
|
|
Text("点击上传后会把素材保存到当前选择的打卡点")
|
|
.font(.system(size: 12, weight: .medium))
|
|
.foregroundStyle(AppDesign.textSecondary)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 20)
|
|
.background(Color(hex: 0xF8FAFD), in: RoundedRectangle(cornerRadius: 14))
|
|
}
|
|
}
|
|
|
|
/// 设置页素材行缩略图。
|
|
struct ScenicPromotionSettingsMaterialThumbnail: View {
|
|
let material: ScenicPromotionManagedMaterial
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Image(material.assetName)
|
|
.resizable()
|
|
.scaledToFill()
|
|
.frame(width: 64, height: 48)
|
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
|
|
if material.kind == .video {
|
|
Image(systemName: "play.fill")
|
|
.font(.system(size: 13, weight: .bold))
|
|
.foregroundStyle(.white)
|
|
.frame(width: 26, height: 26)
|
|
.background(.black.opacity(0.42), in: Circle())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 设置页 Loading 骨架屏。
|
|
struct ScenicPromotionSettingsLoadingView: View {
|
|
var body: some View {
|
|
ScrollView(showsIndicators: false) {
|
|
VStack(spacing: 16) {
|
|
RoundedRectangle(cornerRadius: 18)
|
|
.fill(.white)
|
|
.frame(height: 92)
|
|
ForEach(0..<4, id: \.self) { _ in
|
|
RoundedRectangle(cornerRadius: 18)
|
|
.fill(.white)
|
|
.frame(height: 132)
|
|
}
|
|
}
|
|
.redacted(reason: .placeholder)
|
|
.padding(16)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 设置页 Empty / Error 通用状态视图。
|
|
struct ScenicPromotionSettingsFullStateView: View {
|
|
let icon: String
|
|
let title: String
|
|
let message: String
|
|
let buttonTitle: String
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(spacing: 16) {
|
|
Spacer()
|
|
|
|
Image(systemName: icon)
|
|
.font(.system(size: 42, weight: .semibold))
|
|
.foregroundStyle(AppDesign.primary)
|
|
.frame(width: 82, height: 82)
|
|
.background(AppDesign.primarySoft, in: RoundedRectangle(cornerRadius: 22))
|
|
|
|
VStack(spacing: 7) {
|
|
Text(title)
|
|
.font(.system(size: 20, weight: .bold))
|
|
.foregroundStyle(AppDesign.textPrimary)
|
|
Text(message)
|
|
.font(.system(size: 14, weight: .medium))
|
|
.foregroundStyle(AppDesign.textSecondary)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
|
|
Button(action: action) {
|
|
Text(buttonTitle)
|
|
.font(.system(size: 15, weight: .bold))
|
|
.foregroundStyle(.white)
|
|
.frame(width: 132, height: 42)
|
|
.background(AppDesign.primary, in: Capsule())
|
|
}
|
|
.buttonStyle(.plain)
|
|
|
|
Spacer()
|
|
}
|
|
.padding(28)
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
#Preview("设置卡片") {
|
|
ScenicPromotionSettingsCard(
|
|
title: "排队打卡点绑定",
|
|
subtitle: "可多选,用于宣传页展示排队动态",
|
|
icon: "checklist.checked",
|
|
tint: AppDesign.primary
|
|
) {
|
|
ScenicPromotionSettingsSelectionField(
|
|
title: "网红桥排队点、汉地拔冲排队点",
|
|
subtitle: "2 个已选择",
|
|
icon: "chevron.down"
|
|
)
|
|
}
|
|
.padding()
|
|
.background(AppDesign.background)
|
|
}
|
|
#endif
|