@qin-ui/vant-pro

基于 vant 和 @qin-ui/pro-components-core 封装的高级表单和列表组件库
3,916 5
Install
npm install @qin-ui/vant-pro
Latest Version:1.3.4
Vue:3
License:MIT
Last Updated:Sep 9, 2026
Links: GitHub  ·  npm
Maintainer: dufan1994

@qin-ui/vant-pro

基于 Vant 4 和 Vue 3.x 二次封装的移动端高级表单组件库,专门针对移动端 H5 精细化打造,助您极速构建交互流畅、配置驱动的移动端表单应用。

Vant 4.x Vue 3.5+ TypeScript MIT

📝 使用示例和文档详细内容


✨ 特性

  • 🛡️ 基于 Vant 4+ 封装,支持 Vue 3.5+
  • 🔥 提供 ProForm 高级表单组件,通过配置即可快速生成移动端表单
  • 📱 移动端优先,内置 Popup 弹窗表单支持(Picker、DatePicker、Area 等)
  • ⚙️ 通过 JSON 配置动态生成表单字段,支持字段联动与嵌套分组
  • 🧩 支持自定义组件扩展,灵活应对复杂业务场景
  • 🎨 提供 ProComponentProvider 统一管理组件全局默认属性
  • 📐 完善的 TypeScript 类型推导,提供极致开发体验

本包是移动端表单库,仅提供 ProForm + ProComponentProvider,不含 ProTable。


📦 安装

确保已安装 vant (v4+) 和 vue (v3.5+):

npm i @qin-ui/vant-pro
# 或
pnpm add @qin-ui/vant-pro

注意:@qin-ui/vant-pro 将 vant 和 vue 作为 peerDependencies,需自行安装。


🤖 AI 辅助开发

本项目内置 AI 上下文初始化工具。安装后在项目根目录执行:

npx @qin-ui/vant-pro init-ai

将在项目中生成双载体 AI 上下文,使 Cursor / Claude Code / Codex 等 AI 工具深度理解本库的 Schema 驱动用法与属性透传规则:

  • AGENTS.md - 核心使用规则(跨工具开放约定,会话时自动注入,AI 默认加载)
  • .agents/skills/vant-pro/SKILL.md — skills 入口(速查 + 参考导航)
  • .agents/skills/vant-pro/references/ — 完整使用文档与结构化 API 元数据

生成的 AGENTS.md 使用标记区块写入,重复执行 init-ai 可安全更新,不影响项目内其他库的指令。建议将生成的文件提交到 Git 仓库,团队共享 AI 上下文。


🚀 快速开始

全局注册

import { createApp } from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
import VantPro from '@qin-ui/vant-pro';
import App from './App.vue';

const app = createApp(App);
app.use(Vant);
app.use(VantPro); // 全局注册 ProForm、ProComponentProvider
app.mount('#app');

按需引入(推荐)

<script setup lang="ts">
import { useForm, ProForm } from '@qin-ui/vant-pro';

type FormData = { name: string; age: number };

const form = useForm<FormData>({ name: '', age: 18 }, [
  {
    path: 'name',
    label: '姓名',
    component: 'field',
    placeholder: '请输入姓名',
    clearable: true,
  },
  {
    path: 'age',
    label: '年龄',
    component: 'stepper',
  },
]);
</script>

<template>
  <ProForm :form="form" />
</template>

🧱 属性分层透传(核心规则)

ProForm 是渲染引擎,所有真实 UI 来自 vant。vant 的结构是「van-field 容器 + 内部控件」两层(移动端没有桌面端的 Grid Col 层)。Field 上的属性会被"剥离到这两层",写错层就失效:

<van-field label="城市" placeholder="选择">   ← van-field 容器层
  <van-picker :columns="[...]" />            ← 内部控件层
</van-field>
属性 落到哪一层 说明
label / rules / required / clearable / inputAlign / errorMessageAlign / colon / readonly / clickable / isLink / labelWidth / labelAlign / labelClass / error / center / border / autosize / type / maxlength / placeholder / rows + 所有 on* 事件 <van-field> 容器层 表单项相关属性
columns / options / activeValue / min / max / step / count / size / direction / disabled + 该控件其余 vant 原生属性 内部输入控件本身(van-picker 等) 其余全部
component / hidden / modelProp / valueFormatter / displayFormatter / fields / slots / popup / fieldContainer / componentContainer / extraProps ProForm 逻辑消费,不绑到 DOM 框架级属性

规则:label/rules/placeholder 等表单项属性给 van-field,控件特有属性(如 picker 的 columns)给内部控件。fieldClass/fieldStyle 绑到 van-field,componentClass/componentStyle 绑到内部控件。 输入控件的具体属性名/类型请以 vant 官方文档 为准(vant 用 clearable 不是 allowClear)。


📚 组件文档

1. ProComponentProvider

用于全局配置 Vant Pro 各组件的默认属性,通过 componentVars prop 传递,内部通过 provide / inject 下发配置给所有子组件。

使用示例
<script setup>
import { ProComponentProvider } from '@qin-ui/vant-pro';
</script>

<template>
  <ProComponentProvider
    :componentVars="{
      field: { clearable: true, placeholder: '请选择' },
      'pro-form': { inputAlign: 'center', required: 'auto' },
    }"
  >
    <ProForm :form="form" />
  </ProComponentProvider>
</template>
Props
参数名 说明 类型 默认值
componentVars 需要 provide 到子组件的配置 ComponentVars -
内置默认预设(INJECT_CONFIG)

ProComponentProvider 内置了一套默认属性预设,即使不配置 componentVars 也会生效。Field 配置优先级最高,会覆盖这些预设。vant 版预设极简,仅以下两项有默认值,其余内置组件均无预设;表单右对齐与校验滚动定位属移动端体验默认,可在 Field 或 componentVars 中覆盖:

component 默认预设
pro-form { inputAlign: 'right', errorMessageAlign: 'right', required: 'auto', scrollToError: true, scrollToErrorPosition: 'nearest' } -- 表单项右对齐、校验失败自动滚动
field { clearable: true, placeholder: '请输入' }

modelProp 默认 'modelValue'(vant 统一约定,即 v-model:modelValue)。 placeholder 不在 INJECT_CONFIG 预设内,由 locale 体系兜底自动生成。

ComponentVars 支持为以下组件类型配置默认属性:pro-form / field / switch / stepper / rate / slider / uploader / checkbox-group / radio-group / picker / date-picker / time-picker / cascader / area / signature / button。


2. ProForm

ProForm 是基于 Vant Form 高级封装的移动端表单组件,通过配置式 fields 字段描述即可快速生成表单,无需编写大量模板代码。

何时使用
  • 需要通过 JSON 配置生成表单,而非编写大量模板代码
  • 需要表单字段的嵌套分组与动态联动
  • 需要移动端 Popup 弹窗式的字段选择(Picker、DatePicker、Area 等)
  • 需要统一表单布局和样式
useForm Hook

useForm 是 ProForm 的核心 Hook,用于创建表单实例,管理表单数据与字段配置。

import { useForm } from '@qin-ui/vant-pro';

const form = useForm<FormDataType>(initialData, fields, root?)
参数 说明 类型 默认值
initData 表单初始数据 DeepPartial<D> -
initFields 表单字段配置 Field<ComponentName, D>[] -
root 是否为根表单实例 boolean true

返回值 (Form 实例)(formData 是 reactive 对象,不用 .value):

属性/方法 说明 类型
formData 响应式表单数据对象,可直接读写,支持深层路径 Reactive<D>
fields 响应式表单字段配置数组引用 Ref<Field[]>
formRef 底层 Vant Form 组件实例引用 Ref<FormInstance | undefined>
getFormData(path) 安全获取指定字段路径(支持深层 'a.b')的数据值 (path?) => any
setFormData(path, val) 安全更新指定路径的值;(path, val) 单值、(path, prev=>new) 函数式更新、({...}) 批量覆盖整个表单(先清空再写入)、(prev=>({...prev, name:'新'})) 函数式批量更新 (path?, val?) => void
getField(path, opts?) 获取字段配置;支持路径字符串或查找函数 getField(f => f.label==='姓名');{ all: true } 选项返回所有匹配数组 (path, opts) => Field|Field[]
setField(path, patch, opts?) 更新字段配置(默认合并);{ updateType: 'rewrite' } 覆盖;支持函数式 setField(path, prev=>({...}));{ all: true } 批量更新所有匹配 (path, patch, opts) => void
deleteField(path, opts?) 删除字段配置;{ all: true } 删除所有匹配字段 (path, opts?) => void
appendField(path, field, opts?) 在指定字段后追加新字段(入参可为数组);undefined 在末尾追加;{all:true} 所有匹配后追加 (path, field, opts?) => void
prependField(path, field, opts?) 在指定字段前插入新字段(入参可为数组);undefined 在开头插入;{all:true} 所有匹配前插入 (path, field, opts?) => void
getParentField(path, opts?) 查找父级字段配置;{all:true} 返回所有匹配的父级字段 (path, opts?) => Field
formPopup vant 独有:弹窗管理对象,open(path, initialValue?, customProps?, fieldId?) / close() / resetProps() / updateProps() / visible / props FormPopup

校验与重置通过 formRef 调用 vant 原生方法:form.formRef.value?.validate()、form.formRef.value?.resetValidation()。

useFormPopup

useForm 内部已集成 useFormPopup(即 form.formPopup),用于控制 Popup 弹窗的开关与配置。也可独立使用:

import { useFormPopup } from '@qin-ui/vant-pro';

const formPopup = useFormPopup(true);
// 打开弹窗:可传初始值(原始数据)、本次 Popup 属性覆盖项与字段实例 ID
formPopup.open(
  'fieldName',
  ['110000', '110100'],
  { position: 'top' },
  'field-1'
);
formPopup.close(); // 关闭弹窗
formPopup.updateProps({ position: 'top' }); // 更新弹窗属性
属性/方法 说明
props Popup 的 props(reactive,默认 { position:'bottom', round:true, safeAreaInsetBottom:true })
visible 弹窗是否可见(computed)
popupFieldPath 当前打开的字段路径(ref)
popupFieldValue 当前弹窗字段的值(ref)
activeFieldId 当前激活的字段实例 ID,用于同 path 多实例隔离
contentId / teleportTo 弹层 Teleport 容器的唯一 DOM id 与选择器,多实例互不干扰
open(path, initialValue?, customProps?, fieldId?) 打开指定字段的弹层;自动重置属性为默认值后合并 customProps;initialValue 传原始数据
close() 关闭弹窗
resetProps() 重置弹层属性为全局默认值
updateProps(props) 合并更新当前弹层属性
Props
参数名 说明 类型 默认值
form useForm 返回的表单实例 Form -

同时继承 Vant Form Props 的所有属性。不传递 form prop 时,ProForm 内部会自行创建一个表单实例。

字段配置(Field)

每个字段支持的配置项如下:

属性名 说明 类型 默认值
path 字段路径,对应表单数据中的属性名 Path<D> -
label 字段标签,支持字符串或插槽组件 SlotComponentType -
component 字段组件类型,见下方支持列表 ComponentName 'field'
hidden 是否隐藏字段,支持响应式 MaybeRef<boolean> false
disabled 是否禁用字段,支持响应式 MaybeRef<boolean> false
required 是否必填 boolean -
placeholder 占位提示文字 string -
clearable 是否可清空 boolean -
fields 嵌套子字段(用于分组) Field[] -
valueFormatter 值格式化器(输入转换) ValueFormatter -
displayFormatter 展示格式化器(Popup 字段回显文字) DisplayFormatter -
componentContainer 字段组件外层包裹容器 ContainerComponent -
fieldContainer 嵌套字段的外层包裹容器 ContainerComponent -
componentStyle 字段组件样式 CSSProperties -
componentClass 字段组件类名 any -
fieldStyle 字段外层样式 CSSProperties -
fieldClass 字段外层类名 any -
modelProp 自定义组件 v-model 绑定的属性名 string -
popup 是否以 Popup 弹窗形式展示该字段 boolean | Partial<PopupProps> false
slots 字段插槽配置 Slots -
valueFormatter 字段值转换

控制表单值与组件值之间的转换(如 Picker 的数组与字符串互转)。在表单数据写入前(computed setter 中,于 onUpdate:modelValue 写回前)执行,支持两种形态。函数形态仅 set(写入)方向生效,读取时不转换;对象形态 get/set 双向生效:

// 函数形态:(新值, 旧值) => 转换后的值,写回 formData
{ path: 'name', component: 'field', valueFormatter: (val, oldVal) => val?.trim() }

// 对象形态:get 读出时转换,set 写入时转换
{
  path: 'tags',
  component: 'picker',
  valueFormatter: {
    get: (val) => (val ? val.split(',') : []),       // formData -> 组件显示
    set: (val) => (val as string[])?.join(','),       // 组件 -> formData
  },
}

📌 旧值(oldVal)注入契约:仅当转换函数显式声明两个形参 (val, oldVal) => ... 时,底层才读取并传入写入前的旧值快照;声明单形参则不读取旧值(零克隆开销)。请勿使用默认参数或剩余参数声明(否则 fn.length 无法识别)。oldVal 为只读快照副本,不得依赖原地修改写回。

⚠️ valueFormatter 不支持响应式(不能包 ref/computed),与 component / fields / slots / modelProp / displayFormatter / popup 等同属不支持响应式的属性。

🔄 响应式联动

控制类属性(disabled / hidden / rules 等)支持三种响应式模式,按场景选最简的:

// A. computed() 声明式 -- 初始化时已知的联动(首选)
disabled: computed(() => !form.formData.enabled),

// B. ref() -- 外部共享状态
const isDisabled = ref(false);
disabled: isDisabled,

// C. setField() 命令式 -- 事件驱动的运行时变更
form.setField('limitCount', { disabled: true });

规则:初始化时已知的联动用 computed(),运行时事件触发的用 setField(),外部共享状态用 ref()。能用 computed() 解决就不要用 setField()。

不支持响应式的属性

以下属性不能包 ref/computed(会失效或引发渲染异常),只能传静态值:

属性 原因
component 组件对象/函数,响应式代理会破坏渲染(需 markRaw)
valueFormatter / displayFormatter 纯转换函数,无需响应式
fields 嵌套字段数组结构,响应式代理会干扰内部解析
slots 插槽内容,响应式代理会破坏渲染
modelProp / popup 字符串/布尔标识,无需响应式
componentContainer / fieldContainer 容器组件,需 markRaw

动态切换 component 或 slots 时,用 setField() 命令式更新,而非包 computed。


内置组件映射(component 可选值)

内置键名 对应 Vant 组件 说明
'field' Field 文本/密码/数字输入(特殊:无内部控件包裹,直接渲染 van-field)
'switch' Switch 开关
'stepper' Stepper 步进器
'rate' Rate 评分
'slider' Slider 滑块
'uploader' Uploader 文件上传
'checkbox-group' CheckboxGroup 复选框组
'radio-group' RadioGroup 单选按钮组
'picker' Picker 选择器(常配 popup: true)
'date-picker' DatePicker 日期选择(常配 popup: true)
'time-picker' TimePicker 时间选择(常配 popup: true)
'cascader' Cascader 级联选择(常配 popup: true)
'area' Area 省市区选择(常配 popup: true)
'signature' Signature 签名板(常配 popup: true)
'button' Button 按钮
'custom' 自定义组件 完全渲染自定义组件

🧩 自定义组件(4 种方式)

Field.component 的解析优先级(高 -> 低):

teleport 插槽注入 > ProComponentProvider.componentMap > 内置 componentMap > 原始 component 值

方式 1:传入 SFC 单文件组件对象(推荐,单字段最常用)

component 直接传组件对象,必须用 markRaw 包裹:

import { markRaw } from 'vue';
import MyInput from './MyInput.vue';

const fields = [{ path: 'code', label: '验证码', component: markRaw(MyInput) }];

⚠️ 必须 markRaw:不包会被 Vue 当成响应式对象深度代理,触发性能警告甚至渲染异常。类型上 component 接受 RenderComponentType | Raw<RenderComponentType>,Raw<T> 即 markRaw 的类型标记。此字段不支持响应式(不能包 ref/computed)。

方式 2:传入 render 函数(单字段,需动态拼装 props 时)

component 传 (props, ctx) => VNode。props 含 v-model 绑定值与 path,ctx.attrs 含透传属性:

import { h } from 'vue';
import MyInput from './MyInput.vue';

const fields = [
  {
    path: 'code',
    label: '验证码',
    component: (p, ctx) => h(MyInput, { ...p, ...ctx.attrs }),
  },
];
方式 3:ProComponentProvider 注入 componentMap(全局复用,推荐多字段场景)

在根用 componentMap 注册,Field 里用字符串引用。可覆盖内置组件。

<script setup lang="ts">
import { ProComponentProvider } from '@qin-ui/vant-pro';
import MyIdCardScanner from './components/MyIdCardScanner.vue';

const componentMap = {
  'id-scanner': MyIdCardScanner,
};
</script>

<template>
  <ProComponentProvider :component-map="componentMap">
    <AppLayout />
  </ProComponentProvider>
</template>

追加 TypeScript 全局声明获取强类型补全(项目任意 .d.ts):

declare module '@qin-ui/vant-pro' {
  interface ComponentMap {
    'id-scanner': typeof MyIdCardScanner;
  }
}

之后 component: 'id-scanner' 即获精准属性类型联想与校验。

方式 4:模板 scoped slot(声明式,简单替换)

插槽名 = 字段 path,绑定参数通过 v-bind="scoped" 转发(teleport 机制,优先级最高):

<ProForm :form="form">
  <template #agreement="scoped">
    <van-checkbox v-bind="scoped">同意协议</van-checkbox>
  </template>
</ProForm>
选择建议
场景 推荐方式
单字段复用一个 SFC 方式 1(markRaw(SFC))
单字段、需动态拼装 props 方式 2(render 函数)
多处复用 / 替换内置组件 方式 3(componentMap + 声明扩充)
简单声明式替换、不想写 h() 方式 4(scoped slot)
自定义组件需遵守的约定
  • v-model:默认绑 modelValue(v-model:modelValue,vant 统一约定)。若组件用别的 prop,通过字段 modelProp 指定。
  • 接收 path:组件会收到 path prop(字段路径),可用于透传或标识。
  • 属性透传:Field 上除框架级属性外(component / hidden / modelProp / valueFormatter / displayFormatter / fields / slots / popup / fieldContainer / componentContainer / extraProps 等),其余作为 attrs 透传给自定义组件。

使用 Popup 弹窗

对于需要弹窗展示的字段(如 Picker、DatePicker 等),配置 popup: true 即可开启弹窗模式。表单项以只读 van-field 展示,点击后底部弹出 Popup,内部控件在 Popup 中渲染;确认则提交值,取消则丢弃修改。

<script setup lang="ts">
const form = useForm({ date: '', area: [] }, [
  {
    path: 'date',
    label: '日期',
    component: 'date-picker',
    popup: true,
    displayFormatter: (val: string) => val || '请选择日期',
  },
  {
    path: 'area',
    label: '地区',
    component: 'area',
    popup: true,
    title: '请选择所在地区',
    displayFormatter: (val: string[]) => val?.join(' ') || '请选择地区',
  },
]);
</script>

displayFormatter 是 vant 独有属性,控制弹窗字段在只读 van-field 上的回显文本。

嵌套分组字段

使用 fields 属性可以实现字段的嵌套分组(分组节点只配 label + fields,无 path/component):

<script setup lang="ts">
const form = useForm({}, [
  {
    label: '基本信息',
    fields: [
      { path: 'name', label: '姓名', component: 'field' },
      { path: 'age', label: '年龄', component: 'stepper' },
    ],
  },
  {
    label: '联系信息',
    fields: [
      { path: 'phone', label: '手机号', component: 'field' },
      { path: 'email', label: '邮箱', component: 'field' },
    ],
  },
]);
</script>
Slots
插槽名 说明
default 表单底部自定义内容
{path} 按字段 path 命名的插槽,可覆盖指定字段的渲染

3. useFields

独立使用字段管理 Hook,当需要单独管理字段配置时可以使用:

import { useFields } from '@qin-ui/vant-pro';

const { fields, handleFieldUpdate, handleFieldValues } = useFields([
  { path: 'name', label: '姓名', component: 'field' },
]);

4. useFormRef

获取 Vant Form 实例 ref:

import { useFormRef } from '@qin-ui/vant-pro';

const { formRef, setFormRef } = useFormRef();

// 可直接调用 Vant Form 实例方法
// formRef.value?.validate()

❌ 反模式

  • ❌ 在 <ProForm> 内手写 <van-field> -- ProForm 从 Field 配置自动渲染。
  • ❌ 猜透传属性名 -- 先查 vant-ui.com(vant 用 clearable 不是 allowClear,不用 mode:'multiple')。
  • ❌ 把 columns(picker 数据)当 van-field 属性 -- 它属于内部控件层。
  • ❌ 传 SFC 给 component 不用 markRaw。
  • ❌ 移动端选择类字段(picker/date-picker/area 等)忘了配 popup: true -- 弹窗模式是 vant 表单的核心用法。

Peer Dependencies

依赖包名称 推荐版本要求
vue ^3.5.0
vant ^4.0.0

🤝 贡献

欢迎通过 GitHub Issues 提交反馈或建议。

📄 许可证

基于 MIT 开源许可证。

Related Packages

zhytech-ui

基于Vue3 + ElementPlus + TypeScript封装的组件库

8,425
@daysnap/horn-ui

hron ui

3,143
@deppon/deppon-design

UI component library based on Vant, Vue 3 and TypeScript

17,411
vant-vtk

Vue3 + Vant4 扩展组件库

1,963

Version History

Version Released Vue License
1.3.4 ^3.5.0 MIT
1.3.3 ^3.5.0 MIT
1.3.2 ^3.5.0 MIT
1.3.1 ^3.5.0 MIT
1.3.0 ^3.5.0 MIT