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.
285 lines
7.3 KiB
285 lines
7.3 KiB
import SwiftUI
|
|
|
|
// MARK: - 通用表单组件
|
|
struct FormView<Content: View>: View {
|
|
let title: String?
|
|
let content: Content
|
|
let spacing: CGFloat
|
|
let padding: EdgeInsets
|
|
|
|
init(
|
|
title: String? = nil,
|
|
spacing: CGFloat = 16,
|
|
padding: EdgeInsets = EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20),
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.title = title
|
|
self.spacing = spacing
|
|
self.padding = padding
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: spacing) {
|
|
if let title = title {
|
|
HStack {
|
|
Text(title)
|
|
.font(.title2)
|
|
.fontWeight(.semibold)
|
|
.foregroundColor(.primary)
|
|
|
|
Spacer()
|
|
}
|
|
}
|
|
|
|
content
|
|
}
|
|
.padding(padding)
|
|
}
|
|
.background(Color(.systemGroupedBackground))
|
|
}
|
|
}
|
|
|
|
// MARK: - 表单分组组件
|
|
struct FormGroup<Content: View>: View {
|
|
let title: String?
|
|
let content: Content
|
|
let spacing: CGFloat
|
|
|
|
init(
|
|
title: String? = nil,
|
|
spacing: CGFloat = 12,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.title = title
|
|
self.spacing = spacing
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: spacing) {
|
|
if let title = title {
|
|
HStack {
|
|
Text(title)
|
|
.font(.headline)
|
|
.foregroundColor(.primary)
|
|
|
|
Spacer()
|
|
}
|
|
}
|
|
|
|
content
|
|
}
|
|
.padding()
|
|
.background(Color(.systemBackground))
|
|
.cornerRadius(12)
|
|
.shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
|
|
}
|
|
}
|
|
|
|
// MARK: - 表单行组件
|
|
struct FormRow<Content: View>: View {
|
|
let title: String
|
|
let isRequired: Bool
|
|
let content: Content
|
|
let icon: String?
|
|
|
|
init(
|
|
title: String,
|
|
isRequired: Bool = false,
|
|
icon: String? = nil,
|
|
@ViewBuilder content: () -> Content
|
|
) {
|
|
self.title = title
|
|
self.isRequired = isRequired
|
|
self.icon = icon
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
HStack {
|
|
if let icon = icon {
|
|
Image(systemName: icon)
|
|
.font(.subheadline)
|
|
.foregroundColor(.blue)
|
|
}
|
|
|
|
Text(title)
|
|
.font(.subheadline)
|
|
.foregroundColor(.primary)
|
|
|
|
if isRequired {
|
|
Text("*")
|
|
.foregroundColor(.red)
|
|
.font(.subheadline)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
|
|
content
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 表单操作按钮组件
|
|
struct FormActionButton: View {
|
|
let title: String
|
|
let action: () -> Void
|
|
let isEnabled: Bool
|
|
let style: ButtonStyle
|
|
let icon: String?
|
|
|
|
enum ButtonStyle {
|
|
case primary
|
|
case secondary
|
|
case destructive
|
|
case success
|
|
}
|
|
|
|
init(
|
|
title: String,
|
|
action: @escaping () -> Void,
|
|
isEnabled: Bool = true,
|
|
style: ButtonStyle = .primary,
|
|
icon: String? = nil
|
|
) {
|
|
self.title = title
|
|
self.action = action
|
|
self.isEnabled = isEnabled
|
|
self.style = style
|
|
self.icon = icon
|
|
}
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: 8) {
|
|
if let icon = icon {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 16, weight: .medium))
|
|
}
|
|
|
|
Text(title)
|
|
.font(.system(size: 16, weight: .semibold))
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 16)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.fill(backgroundColor)
|
|
)
|
|
.foregroundColor(foregroundColor)
|
|
}
|
|
.disabled(!isEnabled)
|
|
.opacity(isEnabled ? 1.0 : 0.6)
|
|
}
|
|
|
|
private var backgroundColor: Color {
|
|
switch style {
|
|
case .primary:
|
|
return .blue
|
|
case .secondary:
|
|
return Color(.systemGray5)
|
|
case .destructive:
|
|
return .red
|
|
case .success:
|
|
return .green
|
|
}
|
|
}
|
|
|
|
private var foregroundColor: Color {
|
|
switch style {
|
|
case .primary, .destructive, .success:
|
|
return .white
|
|
case .secondary:
|
|
return .primary
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 预定义的表单操作按钮
|
|
extension FormActionButton {
|
|
static func primary(
|
|
title: String,
|
|
action: @escaping () -> Void,
|
|
isEnabled: Bool = true,
|
|
icon: String? = nil
|
|
) -> FormActionButton {
|
|
FormActionButton(
|
|
title: title,
|
|
action: action,
|
|
isEnabled: isEnabled,
|
|
style: .primary,
|
|
icon: icon
|
|
)
|
|
}
|
|
|
|
static func secondary(
|
|
title: String,
|
|
action: @escaping () -> Void,
|
|
isEnabled: Bool = true,
|
|
icon: String? = nil
|
|
) -> FormActionButton {
|
|
FormActionButton(
|
|
title: title,
|
|
action: action,
|
|
isEnabled: isEnabled,
|
|
style: .secondary,
|
|
icon: icon
|
|
)
|
|
}
|
|
|
|
static func destructive(
|
|
title: String,
|
|
action: @escaping () -> Void,
|
|
isEnabled: Bool = true,
|
|
icon: String? = nil
|
|
) -> FormActionButton {
|
|
FormActionButton(
|
|
title: title,
|
|
action: action,
|
|
isEnabled: isEnabled,
|
|
style: .destructive,
|
|
icon: icon
|
|
)
|
|
}
|
|
|
|
static func success(
|
|
title: String,
|
|
action: @escaping () -> Void,
|
|
isEnabled: Bool = true,
|
|
icon: String? = nil
|
|
) -> FormActionButton {
|
|
FormActionButton(
|
|
title: title,
|
|
action: action,
|
|
isEnabled: isEnabled,
|
|
style: .success,
|
|
icon: icon
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
FormView(title: "sample_form".localized) {
|
|
FormGroup(title: "basic_info".localized) {
|
|
FormRow(title: "username".localized, isRequired: true, icon: "person") {
|
|
TextField("enter_username".localized, text: .constant(""))
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
}
|
|
|
|
FormRow(title: "email".localized, isRequired: true, icon: "envelope") {
|
|
TextField("enter_email".localized, text: .constant(""))
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
.keyboardType(.emailAddress)
|
|
}
|
|
}
|
|
|
|
FormGroup(title: "actions".localized) {
|
|
FormActionButton.primary(title: "save".localized, action: {})
|
|
FormActionButton.secondary(title: "cancel".localized, action: {})
|
|
}
|
|
}
|
|
} |