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.
237 lines
6.1 KiB
237 lines
6.1 KiB
import SwiftUI
|
|
|
|
// MARK: - 通用验证组件
|
|
struct ValidationView: View {
|
|
let validationState: ValidationState
|
|
let message: String?
|
|
let showIcon: Bool
|
|
|
|
init(
|
|
validationState: ValidationState,
|
|
message: String? = nil,
|
|
showIcon: Bool = true
|
|
) {
|
|
self.validationState = validationState
|
|
self.message = message
|
|
self.showIcon = showIcon
|
|
}
|
|
|
|
var body: some View {
|
|
if validationState != .none {
|
|
HStack(spacing: 6) {
|
|
if showIcon {
|
|
Image(systemName: iconName)
|
|
.font(.caption)
|
|
.foregroundColor(iconColor)
|
|
}
|
|
|
|
if let message = message {
|
|
Text(message)
|
|
.font(.caption)
|
|
.foregroundColor(textColor)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 6)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 6)
|
|
.fill(backgroundColor)
|
|
)
|
|
}
|
|
}
|
|
|
|
private var iconName: String {
|
|
switch validationState {
|
|
case .none:
|
|
return ""
|
|
case .valid:
|
|
return "checkmark.circle"
|
|
case .warning:
|
|
return "exclamationmark.triangle"
|
|
case .error:
|
|
return "xmark.circle"
|
|
case .info:
|
|
return "info.circle"
|
|
}
|
|
}
|
|
|
|
private var iconColor: Color {
|
|
switch validationState {
|
|
case .none:
|
|
return .clear
|
|
case .valid:
|
|
return .green
|
|
case .warning:
|
|
return .orange
|
|
case .error:
|
|
return .red
|
|
case .info:
|
|
return .blue
|
|
}
|
|
}
|
|
|
|
private var textColor: Color {
|
|
switch validationState {
|
|
case .none:
|
|
return .clear
|
|
case .valid:
|
|
return .green
|
|
case .warning:
|
|
return .orange
|
|
case .error:
|
|
return .red
|
|
case .info:
|
|
return .blue
|
|
}
|
|
}
|
|
|
|
private var backgroundColor: Color {
|
|
switch validationState {
|
|
case .none:
|
|
return .clear
|
|
case .valid:
|
|
return .green.opacity(0.1)
|
|
case .warning:
|
|
return .orange.opacity(0.1)
|
|
case .error:
|
|
return .red.opacity(0.1)
|
|
case .info:
|
|
return .blue.opacity(0.1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 验证状态枚举
|
|
enum ValidationState {
|
|
case none
|
|
case valid
|
|
case warning
|
|
case error
|
|
case info
|
|
}
|
|
|
|
// MARK: - 预定义的验证样式
|
|
extension ValidationView {
|
|
static func success(message: String, showIcon: Bool = true) -> ValidationView {
|
|
ValidationView(
|
|
validationState: .valid,
|
|
message: message,
|
|
showIcon: showIcon
|
|
)
|
|
}
|
|
|
|
static func warning(message: String, showIcon: Bool = true) -> ValidationView {
|
|
ValidationView(
|
|
validationState: .warning,
|
|
message: message,
|
|
showIcon: showIcon
|
|
)
|
|
}
|
|
|
|
static func error(message: String, showIcon: Bool = true) -> ValidationView {
|
|
ValidationView(
|
|
validationState: .error,
|
|
message: message,
|
|
showIcon: showIcon
|
|
)
|
|
}
|
|
|
|
static func info(message: String, showIcon: Bool = true) -> ValidationView {
|
|
ValidationView(
|
|
validationState: .info,
|
|
message: message,
|
|
showIcon: showIcon
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - 字符计数验证
|
|
struct CharacterCountValidation: View {
|
|
let currentCount: Int
|
|
let maxCount: Int
|
|
let warningThreshold: Double
|
|
|
|
init(
|
|
currentCount: Int,
|
|
maxCount: Int,
|
|
warningThreshold: Double = 0.9
|
|
) {
|
|
self.currentCount = currentCount
|
|
self.maxCount = maxCount
|
|
self.warningThreshold = warningThreshold
|
|
}
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Spacer()
|
|
|
|
if currentCount >= maxCount {
|
|
ValidationView.error(message: "max_characters_reached".localized)
|
|
} else if currentCount >= Int(Double(maxCount) * warningThreshold) {
|
|
ValidationView.warning(message: "near_character_limit".localized)
|
|
} else {
|
|
Text("\(currentCount)/\(maxCount)")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 必填字段验证
|
|
struct RequiredFieldValidation: View {
|
|
let fieldName: String
|
|
let isEmpty: Bool
|
|
|
|
var body: some View {
|
|
if isEmpty {
|
|
ValidationView.error(message: String(format: "field_required".localized, fieldName))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 格式验证
|
|
struct FormatValidation: View {
|
|
let fieldName: String
|
|
let isValid: Bool
|
|
let errorMessage: String?
|
|
|
|
init(
|
|
fieldName: String,
|
|
isValid: Bool,
|
|
errorMessage: String? = nil
|
|
) {
|
|
self.fieldName = fieldName
|
|
self.isValid = isValid
|
|
self.errorMessage = errorMessage
|
|
}
|
|
|
|
var body: some View {
|
|
if !isValid {
|
|
ValidationView.error(
|
|
message: errorMessage ?? String(format: "field_format_incorrect".localized, fieldName)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
VStack(spacing: 16) {
|
|
ValidationView.success(message: "验证成功!")
|
|
ValidationView.warning(message: "这是一个警告")
|
|
ValidationView.error(message: "这是一个错误")
|
|
ValidationView.info(message: "这是一个提示")
|
|
|
|
CharacterCountValidation(currentCount: 95, maxCount: 100)
|
|
CharacterCountValidation(currentCount: 100, maxCount: 100)
|
|
|
|
RequiredFieldValidation(fieldName: "用户名", isEmpty: true)
|
|
RequiredFieldValidation(fieldName: "邮箱", isEmpty: false)
|
|
|
|
FormatValidation(fieldName: "邮箱地址", isValid: false)
|
|
FormatValidation(fieldName: "电话号码", isValid: true)
|
|
}
|
|
.padding()
|
|
} |