Basics

验证

简介

Nylo Website v7 提供了围绕 FormValidator 类构建的验证系统。您可以在页面内使用 check() 方法验证数据,也可以直接使用 FormValidator 进行独立和字段级验证。

// Validate data in a NyPage/NyState using check()
check((validate) {
  validate.that('user@example.com').email();
  validate.that('Anthony')
              .capitalized()
              .maxLength(50);
}, onSuccess: () {
  print("All validations passed!");
});

// FormValidator with form fields
Field.text("Email", validator: FormValidator.email())

使用 check() 验证数据

在任何 NyPage 内,使用 check() 方法验证数据。它接受一个回调函数,该函数接收一个验证器列表。使用 .that() 添加数据并链式调用规则:

class _RegisterPageState extends NyPage<RegisterPage> {

  void handleForm() {
    check((validate) {
      validate.that(_emailController.text, label: "Email").email();
      validate.that(_passwordController.text, label: "Password")
          .notEmpty()
          .password(strength: 2);
    }, onSuccess: () {
      // All validations passed
      _submitForm();
    }, onValidationError: (FormValidationResponseBag bag) {
      // Validation failed
      print(bag.firstErrorMessage);
    });
  }
}

check() 的工作原理

  1. check() 创建一个空的 List<FormValidator>
  2. 您的回调使用 .that(data) 向列表添加带有数据的新 FormValidator
  3. 每个 .that() 返回一个可以链式调用规则的 FormValidator
  4. 回调执行后,列表中的每个验证器都会被检查
  5. 结果被收集到 FormValidationResponseBag

验证多个字段

check((validate) {
  validate.that(_nameController.text, label: "Name").notEmpty().capitalized();
  validate.that(_emailController.text, label: "Email").email();
  validate.that(_phoneController.text, label: "Phone").phoneNumberUs();
  validate.that(_ageController.text, label: "Age").numeric().minValue(18);
}, onSuccess: () {
  _submitForm();
});

可选的 label 参数设置在错误消息中使用的可读名称(例如"Email 必须是有效的电子邮件地址。")。

验证结果

check() 方法返回一个 FormValidationResponseBag(即 List<FormValidationResult>),您也可以直接检查它:

FormValidationResponseBag bag = check((validate) {
  validate.that(_emailController.text, label: "Email").email();
  validate.that(_passwordController.text, label: "Password")
      .password(strength: 1);
});

if (bag.isValid) {
  print("All valid!");
} else {
  // Get the first error message
  String? error = bag.firstErrorMessage;
  print(error);

  // Get all failed results
  List<FormValidationResult> errors = bag.validationErrors;

  // Get all successful results
  List<FormValidationResult> successes = bag.validationSuccess;
}

FormValidationResult

每个 FormValidationResult 代表验证单个值的结果:

FormValidator validator = FormValidator(data: "test@email.com")
    .email()
    .maxLength(50);

FormValidationResult result = validator.check();

if (result.isValid) {
  print("Valid!");
} else {
  // First error message
  String? message = result.getFirstErrorMessage();

  // All error messages
  List<String> messages = result.errorMessages();

  // Error responses
  List<FormValidationError> errors = result.errorResponses;
}

使用 FormValidator

FormValidator 可以独立使用或与表单字段一起使用。

独立使用

// Using a named constructor
FormValidator validator = FormValidator.email();
FormValidationResult result = validator.check("user@example.com");

if (result.isValid) {
  print("Email is valid");
} else {
  String? errorMessage = result.getFirstErrorMessage();
  print("Error: $errorMessage");
}

在构造函数中传入数据

FormValidator validator = FormValidator(data: "user@example.com", attribute: "Email")
    .email()
    .maxLength(50);

FormValidationResult result = validator.check();
print(result.isValid); // true

FormValidator 命名构造函数

Nylo Website v7 提供了常用验证的命名构造函数:

// Email validation
FormValidator.email(message: "Please enter a valid email")

// Password validation (strength 1 or 2)
FormValidator.password(strength: 1)
FormValidator.password(strength: 2, message: "Password too weak")

// Phone numbers
FormValidator.phoneNumberUk()
FormValidator.phoneNumberUs()

// URL validation
FormValidator.url()

// Length constraints
FormValidator.minLength(5, message: "Too short")
FormValidator.maxLength(100, message: "Too long")

// Value constraints
FormValidator.minValue(18, message: "Must be 18+")
FormValidator.maxValue(100)

// Size constraints (for lists/strings)
FormValidator.minSize(2, message: "Select at least 2")
FormValidator.maxSize(5)

// Not empty
FormValidator.notEmpty(message: "This field is required")

// Contains values
FormValidator.contains(['option1', 'option2'])

// Starts/ends with
FormValidator.beginsWith("https://")
FormValidator.endsWith(".com")

// Boolean checks
FormValidator.booleanTrue(message: "Must accept terms")
FormValidator.booleanFalse()

// Numeric
FormValidator.numeric()

// Date validations
FormValidator.date()
FormValidator.dateInPast()
FormValidator.dateInFuture()
FormValidator.dateAgeIsOlder(18, message: "Must be 18+")
FormValidator.dateAgeIsYounger(65)

// Text case
FormValidator.uppercase()
FormValidator.lowercase()
FormValidator.capitalized()

// Location formats
FormValidator.zipcodeUs()
FormValidator.postcodeUk()

// Regex pattern
FormValidator.regex(r'^[A-Z]{3}\d{4}$', message: "Invalid format")

// Custom validation
FormValidator.custom(
  message: "Invalid value",
  validate: (data) => data != null,
)

链式验证规则

使用实例方法流畅地链式调用多个规则。每个方法返回 FormValidator,允许您逐步构建规则:

FormValidator validator = FormValidator()
    .notEmpty()
    .email()
    .maxLength(50);

FormValidationResult result = validator.check("user@example.com");

if (!result.isValid) {
  List<String> errors = result.errorMessages();
  print(errors);
}

所有命名构造函数都有对应的链式实例方法:

FormValidator()
    .notEmpty(message: "Required")
    .email(message: "Invalid email")
    .minLength(5, message: "Too short")
    .maxLength(100, message: "Too long")
    .beginsWith("user", message: "Must start with 'user'")
    .lowercase(message: "Must be lowercase")

自定义验证

自定义规则(内联)

使用 .custom() 添加内联验证逻辑:

FormValidator.custom(
  message: "Username already taken",
  validate: (data) {
    // Return true if valid, false if invalid
    return !_takenUsernames.contains(data);
  },
)

或与其他规则链式调用:

FormValidator()
    .notEmpty()
    .custom(
      message: "Must start with a letter",
      validate: (data) => RegExp(r'^[a-zA-Z]').hasMatch(data.toString()),
    )

等值验证

检查值是否与另一个值匹配:

FormValidator()
    .notEmpty()
    .equals(
      _passwordController.text,
      message: "Passwords must match",
    )

将 FormValidator 与字段一起使用

FormValidator 与表单中的 Field 组件集成。将验证器传递给 validator 参数:

class RegisterForm extends NyFormData {
  RegisterForm({String? name}) : super(name ?? "register");

  @override
  fields() => [
        Field.text(
          "Name",
          autofocus: true,
          validator: FormValidator.notEmpty(),
        ),
        Field.email("Email", validator: FormValidator.email()),
        Field.password(
          "Password",
          validator: FormValidator.password(strength: 1),
        ),
      ];
}

您也可以在字段中使用链式验证器:

Field.text(
  "Username",
  validator: FormValidator()
      .notEmpty(message: "Username is required")
      .minLength(3, message: "At least 3 characters")
      .maxLength(20, message: "At most 20 characters"),
)

Field.slider(
  "Rating",
  validator: FormValidator.minValue(4, message: "Rating must be at least 4"),
)

可用验证规则

FormValidator 的所有可用规则,包括命名构造函数和链式方法:

规则 命名构造函数 链式方法 描述
电子邮件 FormValidator.email() .email() 验证电子邮件格式
密码 FormValidator.password(strength: 1) .password(strength: 1) 强度 1:8+ 字符,1 个大写字母,1 个数字。强度 2:+ 1 个特殊字符
非空 FormValidator.notEmpty() .notEmpty() 不能为空
最小长度 FormValidator.minLength(5) .minLength(5) 最小字符串长度
最大长度 FormValidator.maxLength(100) .maxLength(100) 最大字符串长度
最小值 FormValidator.minValue(18) .minValue(18) 最小数值(也适用于字符串长度、列表长度、映射长度)
最大值 FormValidator.maxValue(100) .maxValue(100) 最大数值
最小尺寸 FormValidator.minSize(2) .minSize(2) 列表/字符串的最小尺寸
最大尺寸 FormValidator.maxSize(5) .maxSize(5) 列表/字符串的最大尺寸
包含 FormValidator.contains(['a', 'b']) .contains(['a', 'b']) 值必须包含给定值之一
以...开头 FormValidator.beginsWith("https://") .beginsWith("https://") 字符串必须以前缀开头
以...结尾 FormValidator.endsWith(".com") .endsWith(".com") 字符串必须以后缀结尾
URL FormValidator.url() .url() 验证 URL 格式
数字 FormValidator.numeric() .numeric() 必须是数字
布尔真 FormValidator.booleanTrue() .booleanTrue() 必须为 true
布尔假 FormValidator.booleanFalse() .booleanFalse() 必须为 false
日期 FormValidator.date() .date() 必须是有效日期
过去日期 FormValidator.dateInPast() .dateInPast() 日期必须在过去
未来日期 FormValidator.dateInFuture() .dateInFuture() 日期必须在未来
年龄大于 FormValidator.dateAgeIsOlder(18) .dateAgeIsOlder(18) 年龄必须大于 N
年龄小于 FormValidator.dateAgeIsYounger(65) .dateAgeIsYounger(65) 年龄必须小于 N
首字母大写 FormValidator.capitalized() .capitalized() 首字母必须大写
全小写 FormValidator.lowercase() .lowercase() 所有字符必须为小写
全大写 FormValidator.uppercase() .uppercase() 所有字符必须为大写
美国电话 FormValidator.phoneNumberUs() .phoneNumberUs() 美国电话号码格式
英国电话 FormValidator.phoneNumberUk() .phoneNumberUk() 英国电话号码格式
美国邮编 FormValidator.zipcodeUs() .zipcodeUs() 美国邮编格式
英国邮编 FormValidator.postcodeUk() .postcodeUk() 英国邮编格式
正则表达式 FormValidator.regex(r'pattern') .regex(r'pattern') 必须匹配正则模式
等值 .equals(otherValue) 必须等于另一个值
自定义 FormValidator.custom(validate: fn) .custom(validate: fn) 自定义验证函数

所有规则都接受可选的 message 参数来自定义错误消息。

创建自定义验证规则

要创建可重用的验证规则,请扩展 FormRule 类:

class FormRuleUsername extends FormRule {
  @override
  String? rule = "username";

  @override
  String? message = "The {{attribute}} must be a valid username.";

  FormRuleUsername({String? message}) {
    if (message != null) {
      this.message = message;
    }
  }

  @override
  bool validate(data) {
    if (data is! String) return false;
    // Username: alphanumeric, underscores, 3-20 chars
    return RegExp(r'^[a-zA-Z0-9_]{3,20}$').hasMatch(data);
  }
}

message 中使用 {{attribute}} 作为占位符——它将在运行时被字段的标签替换。

使用自定义 FormRule

使用 FormValidator.rule() 将自定义规则添加到 FormValidator

FormValidator validator = FormValidator.rule([
  FormRuleNotEmpty(),
  FormRuleUsername(),
]);

FormValidationResult result = validator.check("my_username");

或使用 .custom() 方法进行一次性规则而无需创建类:

FormValidator()
    .notEmpty()
    .custom(
      message: "Username must start with a letter",
      validate: (data) => RegExp(r'^[a-zA-Z]').hasMatch(data.toString()),
    )