Skip to content

Validator 验证器

轻量级内联验证包装器,用于单个表单元素的验证。与需要完整表单模型的 Form + FormItem 不同,PxValidator 可以包裹任意输入控件,当 Zod 验证失败时显示像素风格的提示文本。灵感来自 DaisyUI 的 Validator 模式。

名称分类说明类型
rules? 行为验证规则数组(支持 Zod schema 或 required 简写)ValidatorRule[]
modelValue? 内容要验证的值unknown
validate? 事件验证完成后触发(isValid: boolean, message: string) => void
default? 插槽表单控件内容
hint? 插槽自定义错误提示内容{ message: string }
validate? 暴露手动触发验证(trigger?: string) => Promise<boolean>
reset? 暴露重置验证状态() => void
validateStatus? 暴露当前验证状态Ref<'init' | 'error' | 'success'>
validateMessage? 暴露当前错误消息Ref<string>

基础用法

PxValidator 包裹表单元素,绑定 v-model 并提供 rules。使用 required: true 作为非空字符串验证的简写。

<template>
  <div class="demo-validator">
    <px-validator v-model="name" :rules="rules">
      <px-input v-model="name" placeholder="Enter your name" />
    </px-validator>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const name = ref('');
const rules = [{ required: true, trigger: 'change' }];
</script>

<style scoped>
.demo-validator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 320px;
}
</style>

Zod Schema 验证

使用 Zod schema 进行丰富的验证 —— 邮箱格式、最小长度、正则匹配等。

<template>
  <div class="demo-validator">
    <px-validator v-model="email" :rules="emailRules">
      <px-input v-model="email" placeholder="Email address" />
    </px-validator>
    <px-validator v-model="password" :rules="passwordRules">
      <px-input v-model="password" type="password" placeholder="Password (min 6 chars)" />
    </px-validator>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { z } from 'zod';

const email = ref('');
const password = ref('');

const emailRules = [{ schema: z.string().email('Please enter a valid email'), trigger: 'change' }];
const passwordRules = [
  { schema: z.string().min(6, 'Password must be at least 6 characters'), trigger: 'change' },
];
</script>

<style scoped>
.demo-validator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 320px;
}
</style>

自定义错误消息

在规则中使用自定义 message 覆盖 Zod 的默认错误提示。

<template>
  <div class="demo-validator">
    <px-validator v-model="age" :rules="rules">
      <px-input v-model="age" placeholder="Enter your age" />
    </px-validator>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { z } from 'zod';

const age = ref('');
const rules = [
  {
    schema: z.string().regex(/^\d+$/).pipe(z.coerce.number().min(1).max(150)),
    message: 'Please enter a valid age (1-150)',
    trigger: 'change',
  },
];
</script>

<style scoped>
.demo-validator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 320px;
}
</style>

触发模式

使用 trigger 控制验证时机:'change''blur''input'

<template>
  <div class="demo-validator">
    <p class="demo-validator__label">Validates on change:</p>
    <px-validator v-model="val1" :rules="[{ required: true, trigger: 'change' }]">
      <px-input v-model="val1" placeholder="Type and clear..." />
    </px-validator>

    <p class="demo-validator__label">Validates on blur:</p>
    <px-validator v-model="val2" :rules="[{ required: true, trigger: 'blur' }]">
      <px-input v-model="val2" placeholder="Focus and leave empty..." />
    </px-validator>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const val1 = ref('');
const val2 = ref('');
</script>

<style scoped>
.demo-validator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 320px;
}
.demo-validator__label {
  font-family: var(--px-font-family);
  font-size: 14px;
  color: var(--px-text-color-secondary);
  margin: 0;
}
</style>

编程式验证

通过模板 ref 访问 validate()reset() 进行手动控制。

<template>
  <div class="demo-validator">
    <px-validator ref="validatorRef" v-model="username" :rules="rules">
      <px-input v-model="username" placeholder="Username" />
    </px-validator>
    <div class="demo-validator__actions">
      <px-button type="primary" @click="handleValidate">Validate</px-button>
      <px-button @click="handleReset">Reset</px-button>
    </div>
    <p class="demo-validator__result">
      Status: {{ validatorRef?.validateStatus ?? 'init' }}
    </p>
  </div>
</template>

<script setup lang="ts">
import type { ValidatorInstance } from 'sakana-element';
import { ref } from 'vue';
import { z } from 'zod';

const username = ref('');
const validatorRef = ref<ValidatorInstance>();

const rules = [{ schema: z.string().min(3, 'Username must be at least 3 characters') }];

const handleValidate = async () => {
  try {
    await validatorRef.value?.validate();
  } catch {
    // validation failed
  }
};

const handleReset = () => {
  validatorRef.value?.reset();
};
</script>

<style scoped>
.demo-validator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 320px;
}
.demo-validator__actions {
  display: flex;
  gap: 8px;
}
.demo-validator__result {
  font-family: var(--px-font-family);
  font-size: 14px;
  color: var(--px-text-color-secondary);
  margin: 0;
}
</style>