在 Vue 3 项目中安装并使用 Piying-View 渲染第一个表单。
🚀 想直接看效果? 直接拉模板仓库:https://github.com/piying-org/piying-view-vue-template
git clone https://github.com/piying-org/piying-view-vue-template
cd piying-view-vue-template
npm install && npm run dev
💡 先分清两种模式:本文演示的是自动模式(<PiyingView> + Schema 全自动渲染)。另一种手动模式(convertToField + PiyingFieldTemplate / PiyingField 手动绑定)见 两种使用模式。
pnpm add valibot @piying/view-core @piying/view-vue
Vue 2 项目请使用 @piying/view-vue2-legacy,API 结构一致,差异见 Vue 包 API 参考。
Vue 的字段控件通过 useControlValueAccessor() 拿到 cva,再用 defineExpose({ cva }) 暴露给 Piying-View 注册。
<!-- src/components/piying/input-text.vue -->
<script setup lang="ts">
import { useControlValueAccessor } from '@piying/view-vue';
// 解构出 cvaa 的成员,模板里才能自动解包 ref
const {
cva,
cvaa: { value, disabled, valueChange, touchedChange },
} = useControlValueAccessor();
// 关键:把 CVA 暴露出去
defineExpose({ cva });
</script>
<template>
<input
class="input"
type="text"
:value="value"
:disabled="disabled"
@input="valueChange($event.target.value)"
@blur="touchedChange"
/>
</template>
cvaa 提供:
| 成员 |
说明 |
value |
当前值(ShallowRef) |
disabled |
禁用状态(Ref) |
valueChange(v) |
更新值并触发变更 |
touchedChange() |
标记为已触碰 |
照这个模式再写两个控件:
<!-- src/components/piying/input-number.vue -->
<script setup lang="ts">
import { useControlValueAccessor } from '@piying/view-vue';
const {
cva,
cvaa: { value, disabled, valueChange, touchedChange },
} = useControlValueAccessor();
defineExpose({ cva });
</script>
<template>
<input
class="input"
type="number"
:value="value"
:disabled="disabled"
@input="valueChange($event.target.value === '' ? undefined : Number($event.target.value))"
@blur="touchedChange"
/>
</template>
<!-- src/components/piying/input-checkbox.vue -->
<script setup lang="ts">
import { useControlValueAccessor } from '@piying/view-vue';
const {
cva,
cvaa: { value, disabled, valueChange, touchedChange },
} = useControlValueAccessor();
defineExpose({ cva });
</script>
<template>
<input
class="checkbox"
type="checkbox"
:checked="value ?? false"
:disabled="disabled"
@change="valueChange($event.target.checked)"
@blur="touchedChange"
/>
</template>
包装器(Wrapper)负责在控件外面套一层标签。它通过 PI_VIEW_FIELD_TOKEN 注入当前字段,读取 props['title']。
<!-- src/components/piying/wrapper/label-wrapper.vue -->
<script setup lang="ts">
import { inject } from 'vue';
import { PI_VIEW_FIELD_TOKEN, signalToRef } from '@piying/view-vue';
const field = inject(PI_VIEW_FIELD_TOKEN)!;
const fieldProps = signalToRef(() => field.value.props());
</script>
<template>
<div class="flex items-center gap-2">
<span v-if="fieldProps['title']" class="label">{{ fieldProps['title'] }}</span>
<slot />
</div>
</template>
Wrappers 的完整写法见 Wrappers 包装器。
fieldConfig 是一张「类型名 → 组件」的注册表,Piying-View 按 Schema 推导出的类型名来这里查找渲染组件。
// src/components/piying/define.ts
import { markRaw } from 'vue';
import { actions, type PiViewConfig } from '@piying/view-vue';
import { PiyingViewGroup } from '@piying/view-vue';
import InputText from './input-text.vue';
import InputNumber from './input-number.vue';
import InputCheckbox from './input-checkbox.vue';
import LabelWrapper from './wrapper/label-wrapper.vue';
export const fieldConfig = {
types: {
string: {
type: markRaw(InputText),
actions: [actions.wrappers.set(['label'])],
},
number: {
type: markRaw(InputNumber),
actions: [actions.wrappers.set(['label'])],
},
boolean: {
type: markRaw(InputCheckbox),
actions: [actions.wrappers.set(['label'])],
},
// 对象 / 数组等容器类型,用内置的组容器
object: { type: markRaw(PiyingViewGroup) },
array: { type: markRaw(PiyingViewGroup) },
},
wrappers: {
label: { type: markRaw(LabelWrapper) },
},
} as PiViewConfig;
组件对象请用 markRaw 包一层,避免被 Vue 的响应式系统代理;也可以写成 () => import('./xxx.vue') 做懒加载。
<!-- src/views/PiyingDemo.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import * as v from 'valibot';
import { PiyingView } from '@piying/view-vue';
import { fieldConfig } from '@/components/piying/define';
const schema = v.object({
name: v.pipe(v.string(), v.minLength(2, '名称至少 2 个字符'), v.title('姓名')),
age: v.pipe(v.number(), v.minValue(18, '必须年满 18 岁'), v.title('年龄')),
email: v.pipe(v.optional(v.string()), v.title('邮箱')),
});
const options = {
fieldGlobalConfig: fieldConfig,
};
const model = ref<Record<string, any>>({});
</script>
<template>
<piying-view :schema="schema" :options="options" v-model="model" />
<pre>{{ JSON.stringify(model, null, 2) }}</pre>
</template>
<piying-view> 接收三个属性:
| 属性 |
说明 |
:schema |
Valibot Schema,定义字段和验证规则 |
v-model |
双向绑定,同步表单数据 |
:options |
Options 配置(fieldGlobalConfig 等) |
打开浏览器,你会看到 name / age / email 三个字段的表单。输入数据时 model 会同步更新;校验不通过时不会向 model 写出错误值。