拆分宣传页视图并扩充 Mock 数据,合作订单支持获客员备注名并修复线索图片网格尺寸。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,234 @@
|
||||
//
|
||||
// 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
|
||||
Reference in New Issue
Block a user