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: "已达到最大字符数") } else if currentCount >= Int(Double(maxCount) * warningThreshold) { ValidationView.warning(message: "接近字符限制") } 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: "\(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 ?? "\(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() }