综合示例:完整业务表单
本文通过一个注册表单的综合示例,整合前面所学的所有知识点。
complete-example.definition.ts
import * as v from 'valibot';
import { actions, formConfig, hideWhen, setComponent } from '@piying/view-angular-core';
import { map } from 'rxjs';
export const schema = v.object({
username: v.pipe(
v.string(),
actions.attributes.set({ placeholder: '用户名(必填)' }),
formConfig({ required: true }),
),
age: v.pipe(
v.number(),
actions.attributes.set({ placeholder: '年龄' }),
formConfig({ required: true }),
),
password: v.pipe(
v.string(),
actions.attributes.set({ placeholder: '密码(至少 8 位)' }),
formConfig({
validators: [
(control) => {
if (control.value.includes('123')) {
return [
{
kind: 'weakPassword',
message: '密码不能包含连续数字',
},
];
}
if (control.value.length < 8) {
return [
{
kind: 'tooShort',
message: '密码至少 8 个字符',
},
];
}
return undefined;
},
],
}),
),
confirmPassword: v.pipe(
v.string(),
actions.attributes.set({ placeholder: '确认密码' }),
formConfig({
validators: [
(control) => {
const password = control.root.get('password')?.value;
if (control.value && password !== control.value) {
return [
{
kind: 'passwordsNotMatch',
message: '两次密码不一致',
},
];
}
return undefined;
},
],
}),
),
showEmail: v.boolean(),
email: v.pipe(
v.string(),
actions.attributes.set({ placeholder: '勾选上方开关后显示邮箱' }),
hideWhen({
listen: (fn) =>
fn({ list: [['..', 'showEmail']] }).pipe(
map((item) => !item.list[0]),
),
}),
),
bio: v.pipe(
v.string(),
setComponent('textarea'),
actions.attributes.set({ placeholder: '自我介绍(可选)' }),
),
});
export const model = {
username: '',
age: 18,
password: '',
confirmPassword: '',
showEmail: false,
email: '',
bio: '',
};实现一个用户注册表单,包含以下功能:
- 基本信息:用户名(必填)、邮箱(可选)、年龄(必填)
- 密码设置:密码 + 确认密码(需一致),强度提示联动
- 隐私设置:开关控制是否显示邮箱,条件隐藏/显示补充字段
- 兴趣爱好:动态标签列表,可增删
- 自我介绍:可选的长文本
完整 Schema 定义
Section titled “完整 Schema 定义”import * as v from 'valibot';
import { formConfig, setComponent, hideWhen, disableWhen, actions } from '@piying/view-angular-core';
import { map } from 'rxjs';
// 主 Schema
export const registerSchema = v.object({
// === 基本信息 ===
username: v.pipe(v.string(), v.minLength(2, '用户名至少 2 个字符'), setComponent('input'), formConfig({ required: true })),
email: v.pipe(v.optional(v.pipe(v.string(), v.email('请输入有效邮箱'))), setComponent('input')),
age: v.pipe(v.number(), v.minValue(18, '必须年满 18 岁'), v.maxValue(120, '年龄输入不合理'), setComponent('number-input'), formConfig({ required: true })),
// === 密码设置 ===
password: v.pipe(
v.string(),
v.minLength(8, '密码至少 8 个字符'),
setComponent('password-input'),
formConfig({
validators: [
(control) => {
if (control.value.includes('123')) {
return { weakPassword: '密码不能包含连续数字' };
}
return null;
},
],
}),
),
confirmPassword: v.pipe(
v.string(),
v.minLength(8, '请再次输入密码'),
setComponent('password-input'),
formConfig({
validators: [
(control) => {
const parent = control.root;
if (parent.value?.password !== control.value) {
return { passwordsNotMatch: '两次密码不一致' };
}
return null;
},
],
}),
),
// === 隐私设置 ===
showEmailPublicly: v.boolean(),
emailPublic: v.pipe(
v.string(),
setComponent('input'),
hideWhen({
listen: (fn) =>
fn({ list: [['..', 'showEmailPublicly']] }).pipe(
map((item) => !item.list[0]), // 关闭公开显示时隐藏
),
}),
),
bio: v.pipe(v.nullable(v.string()), setComponent('textarea')),
// === 兴趣爱好 ===
hobbies: v.array(v.string()),
});Angular 组件实现
Section titled “Angular 组件实现”import { Component, signal } from '@angular/core';
import { PiyingView } from '@piying/view-angular';
import { registerSchema } from './register-schema';
@Component({
selector: 'app-register',
standalone: true,
imports: [PiyingView],
template: ` <piying-view [schema]="schema" [(model)]="model" [options]="options"></piying-view> `,
})
export class RegisterComponent {
// 初始模型(可选,表单会自动填充默认值)
model = signal({
username: '',
age: 18,
email: undefined,
password: '',
confirmPassword: '',
showEmailPublicly: false,
hobbies: [],
});
// Options 配置
options = {
fieldGlobalConfig: {
types: {
input: { type: InputComponent },
'number-input': { type: NumberInputComponent },
'password-input': { type: PasswordInputComponent },
textarea: { type: TextareaComponent },
object: { type: PiyingViewGroup }, // 嵌套对象容器
array: { type: PiyingViewGroup }, // 数组容器
},
},
};
// Schema
schema = registerSchema;
}组件类型注册
Section titled “组件类型注册”InputComponent(通用输入框)
Section titled “InputComponent(通用输入框)”import { Component, input, output } from '@angular/core';
@Component({
selector: 'input-field',
standalone: true,
template: `
<label>{{ label() }}</label>
<input [placeholder]="placeholder()" [disabled]="disabled()" (input)="onChange($event.target.value)" />
`,
})
export class InputComponent {
label = input('');
placeholder = input('');
disabled = input(false);
onChange = output<string>();
}PasswordInputComponent(密码输入框)
Section titled “PasswordInputComponent(密码输入框)”@Component({
selector: 'password-field',
standalone: true,
template: `
<label>{{ label() }}</label>
<input type="password" [disabled]="disabled()" (input)="onChange($event.target.value)" />
`,
})
export class PasswordInputComponent {
label = input('');
disabled = input(false);
onChange = output<string>();
}动态联动效果
Section titled “动态联动效果”密码强度提示(使用 hideWhen)
Section titled “密码强度提示(使用 hideWhen)”import { hideWhen } from '@piying/view-angular-core';
import { map } from 'rxjs';
// 在 Schema 中定义一个隐藏的密码强度字段
const schema = v.object({
password: v.string(),
passwordStrength: v.pipe(
v.string(),
hideWhen({
listen: (fn) =>
fn({ list: [['..', 'password']] }).pipe(
map((item) => !item.list[0]), // 没有密码时隐藏
),
}),
),
confirmPassword: v.pipe(
v.string(),
disableWhen({
listen: (fn) =>
fn({ list: [['..', 'password']] }).pipe(
map((item) => !item.list[0]), // 没有密码时禁用确认框
),
}),
),
});动态标签列表(Array 操作)
Section titled “动态标签列表(Array 操作)”在模板中实现增删标签:
import { Component, input, output } from '@angular/core';
@Component({
standalone: true,
templateUrl: './tags.component.html',
})
export class TagsComponent {
model = input.required<string[]>();
modelChange = output<string[]>();
addTag() {
const current = this.model();
this.modelChange.emit([...current, '']);
}
removeTag(index: number) {
const current = this.model();
this.modelChange.emit(current.filter((_, i) => i !== index));
}
}表单提交处理
Section titled “表单提交处理”export class RegisterComponent {
onSubmit() {
const model = this.model();
// 校验通过后才允许提交
if (model.password !== model.confirmPassword) {
alert('两次密码不一致');
return;
}
console.log('注册数据:', {
username: model.username,
email: model.email,
age: model.age,
hobbies: model.hobbies,
bio: model.bio,
preferences: {
showEmailPublicly: model.showEmailPublicly,
emailPublic: model.emailPublic ?? '未设置',
},
});
}
}flowchart LR
A[用户输入用户名] --> B[Valibot minLength 验证]
B --> C{验证通过?}
C -->|是| D[model 更新]
C -->|否| E[显示错误信息]
D --> F[showEmailPublicly 变化]
F --> G[hideWhen 计算 emailPublic 可见性]
G --> H[视图重新渲染]
D --> I[密码变化]
I --> J[disableWhen 检查 confirmPassword]
J --> K[禁用/启用确认密码框]
D --> L[confirmPassword 变化]
L --> M[自定义验证器对比密码]
M --> N{密码一致?}
N -->|是| O[清除错误]
N -->|否| P[显示不匹配错误]- API: setComponent — 组件设置详解
- API: inputs、API: outputs — 组件输入输出设置
- API: hideWhen/disableWhen/valueChange — 动态控制 API