You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
9.0 KiB
326 lines
9.0 KiB
import SwiftUI
|
|
|
|
// MARK: - 简化的卡片组件
|
|
struct SimpleCardView: View {
|
|
let content: AnyView
|
|
let padding: EdgeInsets
|
|
let cornerRadius: CGFloat
|
|
let shadowColor: Color
|
|
let shadowRadius: CGFloat
|
|
let shadowOffset: CGSize
|
|
let backgroundColor: Color
|
|
|
|
init(
|
|
padding: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16),
|
|
cornerRadius: CGFloat = 12,
|
|
shadowColor: Color = .black.opacity(0.1),
|
|
shadowRadius: CGFloat = 8,
|
|
shadowOffset: CGSize = CGSize(width: 0, height: 4),
|
|
backgroundColor: Color = Color(.systemBackground),
|
|
@ViewBuilder content: () -> some View
|
|
) {
|
|
self.padding = padding
|
|
self.cornerRadius = cornerRadius
|
|
self.shadowColor = shadowColor
|
|
self.shadowRadius = shadowRadius
|
|
self.shadowOffset = shadowOffset
|
|
self.backgroundColor = backgroundColor
|
|
self.content = AnyView(content())
|
|
}
|
|
|
|
var body: some View {
|
|
content
|
|
.padding(padding)
|
|
.background(backgroundColor)
|
|
.cornerRadius(cornerRadius)
|
|
.shadow(
|
|
color: shadowColor,
|
|
radius: shadowRadius,
|
|
x: shadowOffset.width,
|
|
y: shadowOffset.height
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - 预定义的卡片样式
|
|
extension SimpleCardView {
|
|
static func standard<Content: View>(
|
|
@ViewBuilder content: () -> Content
|
|
) -> SimpleCardView {
|
|
SimpleCardView(content: content)
|
|
}
|
|
|
|
static func elevated<Content: View>(
|
|
@ViewBuilder content: () -> Content
|
|
) -> SimpleCardView {
|
|
SimpleCardView(
|
|
shadowColor: .black.opacity(0.15),
|
|
shadowRadius: 12,
|
|
shadowOffset: CGSize(width: 0, height: 6),
|
|
content: content
|
|
)
|
|
}
|
|
|
|
static func subtle<Content: View>(
|
|
@ViewBuilder content: () -> Content
|
|
) -> SimpleCardView {
|
|
SimpleCardView(
|
|
shadowColor: .black.opacity(0.05),
|
|
shadowRadius: 4,
|
|
shadowOffset: CGSize(width: 0, height: 2),
|
|
content: content
|
|
)
|
|
}
|
|
|
|
static func compact<Content: View>(
|
|
@ViewBuilder content: () -> Content
|
|
) -> SimpleCardView {
|
|
SimpleCardView(
|
|
padding: EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12),
|
|
cornerRadius: 8,
|
|
content: content
|
|
)
|
|
}
|
|
|
|
static func large<Content: View>(
|
|
@ViewBuilder content: () -> Content
|
|
) -> SimpleCardView {
|
|
SimpleCardView(
|
|
padding: EdgeInsets(top: 24, leading: 24, bottom: 24, trailing: 24),
|
|
cornerRadius: 16,
|
|
content: content
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - 信息卡片组件
|
|
struct InfoCard: View {
|
|
let title: String
|
|
let subtitle: String?
|
|
let icon: String?
|
|
let iconColor: Color
|
|
let content: String
|
|
let actionTitle: String?
|
|
let action: (() -> Void)?
|
|
|
|
init(
|
|
title: String,
|
|
subtitle: String? = nil,
|
|
icon: String? = nil,
|
|
iconColor: Color = .blue,
|
|
content: String,
|
|
actionTitle: String? = nil,
|
|
action: (() -> Void)? = nil
|
|
) {
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
self.icon = icon
|
|
self.iconColor = iconColor
|
|
self.content = content
|
|
self.actionTitle = actionTitle
|
|
self.action = action
|
|
}
|
|
|
|
var body: some View {
|
|
SimpleCardView.standard {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
// 标题行
|
|
HStack {
|
|
if let icon = icon {
|
|
Image(systemName: icon)
|
|
.font(.title2)
|
|
.foregroundColor(iconColor)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(title)
|
|
.font(.headline)
|
|
.foregroundColor(.primary)
|
|
|
|
if let subtitle = subtitle {
|
|
Text(subtitle)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
|
|
// 内容
|
|
Text(content)
|
|
.font(.body)
|
|
.foregroundColor(.primary)
|
|
.lineLimit(nil)
|
|
|
|
// 操作按钮
|
|
if let actionTitle = actionTitle, let action = action {
|
|
Button(action: action) {
|
|
Text(actionTitle)
|
|
.font(.subheadline)
|
|
.fontWeight(.medium)
|
|
.foregroundColor(iconColor)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 统计卡片组件
|
|
struct StatCard: View {
|
|
let title: String
|
|
let value: String
|
|
let subtitle: String?
|
|
let icon: String?
|
|
let iconColor: Color
|
|
let trend: Trend?
|
|
|
|
enum Trend {
|
|
case up(String)
|
|
case down(String)
|
|
case neutral(String)
|
|
}
|
|
|
|
init(
|
|
title: String,
|
|
value: String,
|
|
subtitle: String? = nil,
|
|
icon: String? = nil,
|
|
iconColor: Color = .blue,
|
|
trend: Trend? = nil
|
|
) {
|
|
self.title = title
|
|
self.value = value
|
|
self.subtitle = subtitle
|
|
self.icon = icon
|
|
self.iconColor = iconColor
|
|
self.trend = trend
|
|
}
|
|
|
|
var body: some View {
|
|
SimpleCardView.standard {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
// 标题和图标
|
|
HStack {
|
|
if let icon = icon {
|
|
Image(systemName: icon)
|
|
.font(.title2)
|
|
.foregroundColor(iconColor)
|
|
}
|
|
|
|
Text(title)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
}
|
|
|
|
// 数值
|
|
Text(value)
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(.primary)
|
|
|
|
// 副标题和趋势
|
|
HStack {
|
|
if let subtitle = subtitle {
|
|
Text(subtitle)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if trend != nil {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: trendIcon)
|
|
.font(.caption)
|
|
.foregroundColor(trendColor)
|
|
|
|
Text(trendValue)
|
|
.font(.caption)
|
|
.foregroundColor(trendColor)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var trendIcon: String {
|
|
switch trend {
|
|
case .up:
|
|
return "arrow.up"
|
|
case .down:
|
|
return "arrow.down"
|
|
case .neutral:
|
|
return "minus"
|
|
case .none:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
private var trendColor: Color {
|
|
switch trend {
|
|
case .up:
|
|
return .green
|
|
case .down:
|
|
return .red
|
|
case .neutral:
|
|
return .orange
|
|
case .none:
|
|
return .clear
|
|
}
|
|
}
|
|
|
|
private var trendValue: String {
|
|
switch trend {
|
|
case .up(let value), .down(let value), .neutral(let value):
|
|
return value
|
|
case .none:
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
VStack(spacing: 20) {
|
|
// 标准卡片
|
|
SimpleCardView.standard {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("标准卡片")
|
|
.font(.headline)
|
|
Text("这是一个标准样式的卡片组件")
|
|
.font(.body)
|
|
}
|
|
}
|
|
|
|
// 信息卡片
|
|
InfoCard(
|
|
title: "提示信息",
|
|
subtitle: "重要提醒",
|
|
icon: "info.circle",
|
|
content: "这是一个信息卡片,用于显示重要的提示信息。",
|
|
actionTitle: "了解更多",
|
|
action: {}
|
|
)
|
|
|
|
// 统计卡片
|
|
StatCard(
|
|
title: "总用户数",
|
|
value: "1,234",
|
|
subtitle: "本月新增 123",
|
|
icon: "person.3",
|
|
trend: .up("+12%")
|
|
)
|
|
|
|
// 紧凑卡片
|
|
SimpleCardView.compact {
|
|
Text("紧凑卡片")
|
|
.font(.headline)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|