@qynpm/form

基于 Vue 3 和 Element Plus 的 schema 表单工具包。
1,128
Install
npm install @qynpm/form
Latest Version:1.1.0
Vue:3
License:MIT
Last Updated:Oct 26, 2026
Links: npm
Maintainer: a516056892

@qynpm/form

基于 Vue 3 + Element Plus 的 schema 驱动表单工具包。

当前提供:

  • useFormItem
  • QyQueryForm
  • defineFormModel
  • useSave
  • useSyncQueryParams
  • configureQyForm

安装

pnpm add @qynpm/form

Peer Dependencies:

  • vue >= 3.4
  • element-plus >= 2.0
  • @element-plus/icons-vue >= 2.0
  • pinia >= 2.0
  • vue-router >= 4.0
  • @qynpm/ui

引入

这是一个以 hooks 为主的工具包,通常按需引入即可:

import {
  QyQueryForm,
  defineFormModel,
  useFormItem,
  useSave,
  useSyncQueryParams,
  configureQyForm
} from '@qynpm/form'

当前稳定导出

构建产物将已有依赖 vuedraggable 外部化,ESM/CJS 消费由宿主 bundler 或 Node 解析包依赖;直接通过 <script> 使用 UMD 时,需先加载它并提供全局变量 vuedraggable。

当前建议作为公共包稳定使用的导出面如下:

导出项 类型 说明
useFormItem hook schema 表单核心能力
QyQueryForm component 查询表单外壳,兼容旧查询表单布局、保存筛选和拖拽排序
defineFormModel helper 兼容旧 qy-query-form 的 model 定义方式
useSave hook 保存 / 应用筛选条件
useSyncQueryParams hook query 参数同步
configureQyForm function 宿主全局注册运行时配置

说明:

  • QyQueryForm、defineFormModel 可以作为旧查询表单迁移入口使用。
  • configureQyForm 目前只承载筛选条件存储扩展,不建议继续塞入其他业务能力。
  • README 中未列出的内部文件、临时兼容实现,不应作为稳定 API 依赖。

宿主接入

@qynpm/form 默认使用 localStorage 保存筛选条件。

如果宿主项目需要复用旧 useUserCache 一类的“用户级缓存”能力,不建议把接口逻辑直接写死进包内,而是由宿主在启动阶段全局注册一次:

import { configureQyForm, type FormPresetOption } from '@qynpm/form'

configureQyForm({
  presetStorage: {
    async load(id) {
      // 这里接宿主自己的读取逻辑
      return [] as FormPresetOption[]
    },
    async save(id, presets) {
      // 这里接宿主自己的保存逻辑
    }
  }
})

建议接入位置:

  • main.ts
  • 项目公共初始化文件,例如 setupQyForm.ts
  • UI / 表单体系统一入口

这样页面层不需要逐个传 adapter,后续所有 QyQueryForm / useSave 都会走这套全局实现。

迁移页面前,建议先阅读:

设计说明

useFormItem 采用 schema 驱动模式:

  • 第一参数 model 用来描述字段结构、默认值、控件类型和渲染方式
  • 第二参数 options 用来描述 query 同步、缓存、紧凑布局、保存能力等
  • 内部维护响应式 modelForm
  • 页面侧通过 formItem / formItems 输出表单项
  • 提交时通过 modelForm 或 getModelForm() 获取当前值

补充说明:

  • QyDialog 现在通过 @qynpm/ui 提供,createInstance 仍属于包内运行时实现
  • 当前版本更适合作为体系内拆包使用,还不是完全脱离宿主约定的通用表单库
  • 图标能力仍然默认复用宿主现有方案
  • imageInput 继续作为旧 schema 兼容入口,内部由 @qynpm/ui/QyRemarkImage 承接

快速上手

<template>
  <el-form v-bind="form.getFormProps()">
    <component :is="form.formItem" prop="shopName" />
    <component :is="form.formItem" prop="shopType" />

    <el-form-item>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
      <el-button @click="form.reset">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { useFormItem } from '@qynpm/form'

const form = useFormItem(
  [
    {
      label: '店铺名称',
      key: 'shopName',
      type: 'string',
      rules: [{ required: true, message: '请输入店铺名称', trigger: 'blur' }]
    },
    {
      label: '店铺类型',
      key: 'shopType',
      type: 'select',
      value: 'erp',
      options: [
        { label: 'ERP', value: 'erp' },
        { label: 'Production', value: 'production' }
      ]
    }
  ],
  {
    id: 'shop-form'
  }
)

function handleSubmit() {
  console.log(form.getModelForm())
}
</script>

API

useFormItem(model, options?)

model

类型:FormItemSet<Key>[]

字段 类型 说明
label string 表单项标题
key string 字段名,对应 modelForm 的 key
asKey string 没有 key 时的备用唯一标识
type 'string' | 'number' | 'imageInput' | 'select' | 'date' | 'inputNumber' | 'radio' | 'qyMoneyInput' | 'qyRemoteSelect' | 'qyTreeSelect' | 'qyImageUpload' | 'qyDatePicker' | 'qyInputNumber' 控件类型
value any 默认值
props Record<string, any> 透传给控件的 props
rules FormItemRule[] Element Plus 校验规则
options Array | Ref select / radio 选项
render Function 自定义渲染函数

类型映射

@qynpm/form 保留旧 schema 类型,但内部基础组件已经切到 @qynpm/ui:

schema 类型 默认组件 说明
string QyInput 普通文本输入,type="number" 仍按输入框字符串语义处理
number QyInput 兼容旧 ElInput 用法,不等同于数字步进器
select QySelect 保留旧 options: { ref, label, value } 映射和数字值转换
radio QyRadioGroup 保留旧 options 映射,支持普通 radio 和 button radio
date QyDatePicker 保留 datetimerange 结束时间归一逻辑
inputNumber QyInputNumber 普通数字输入,默认值仍为 0
imageInput QyRemarkImage 备注文本 + 单图协议,走特殊适配路径

显式 qy* 类型只保留语义和旧基础类型明显不同的组件:

schema 类型 默认组件 使用场景
qyMoneyInput QyMoneyInput 金额、费用、税费
qyRemoteSelect QyRemoteSelect 远程筛选下拉
qyTreeSelect QyTreeSelect 分类、组织、品类等树形筛选;支持 data、label-key、value-key 等树选择参数
qyImageUpload QyImageUpload 单图字段;配置 multiple 后用于财审凭证、付款凭证等多图数组
qyDatePicker QyDatePicker 需要显式表达 UI 日期组件时使用
qyInputNumber QyInputNumber 需要显式表达 UI 数字输入时使用

qyTreeSelect 会直接渲染 QyTreeSelect,适合把旧 render + el-tree-select 收敛回 schema:

{
  label: '分类',
  key: 'cateList',
  type: 'qyTreeSelect',
  props: {
    data: catesList,
    'label-key': 'cateName',
    'value-key': 'cateProductCode',
    multiple: true,
    clearable: true,
    filterable: true,
    'render-after-expand': false
  }
}

不再额外暴露 qyInput、qySelect、qyRadioGroup。对应能力已经分别收敛到 string、select、radio,避免 schema API 重复。

imageInput 暂不改名为 remarkImage。当前项目内仍存在老组件 @qiyin/components/remarkImage,而 @qynpm/ui 中也有 QyRemarkImage。为了避免出现第三套命名,form 继续使用旧 schema 名 imageInput 承接备注图文协议。

imageInput 的值仍是旧协议字符串:普通备注文本和单张图片地址合并保存,图片部分通过 [url:xxx] 标记。useFormItem 渲染前会用 parseRemarkText 拆成 defaultValue/defaultImage 传给 QyRemarkImage,组件 change 后再把完整协议字符串写回 modelForm[key]。reset() 会重置 modelForm 并强制重新挂载 imageInput,避免备注组件内部未触发 blur 的临时文字或图片状态残留。

options
字段 类型 默认值 说明
form Record<string, any> {} 初始化表单值,会覆盖 schema 默认值
syncQuery boolean false 是否和 URL query 同步
compact boolean false 是否启用紧凑布局 class
cache { key: string } undefined 启用 localStorage 缓存
customSetterGetter Record<string, CustomSetterGetter> {} query 同步时的自定义序列化 / 反序列化
showFilter boolean true useSave 是否展示快捷筛选项
id string undefined useSave 使用的唯一标识
返回值
字段 类型 说明
model FormItemSet[] 原始 schema
modelForm Reactive<Record<string, any>> 响应式表单值对象
formProps ComputedRef 表单 props 计算结果
getModelForm() () => Record<string, any> 获取当前表单值快照;文本字段去除首尾空白(date、qyDatePicker、imageInput 除外),不修改 modelForm
formItem() ({ prop | itemKey }) => VNode 渲染单个表单项
formItems() (props) => VNode[] 批量渲染表单项,支持 includes / excludes
reset() () => void 重置到默认值,并清理缓存
getFormProps() () => { model, rules } 返回可直接绑定到 el-form 的 props
save() (buttonProps?) => VNode | null 保存按钮
apply() (buttonProps?) => VNode | null 应用按钮
SaveComponent() () => JSX.Element 保存 / 应用按钮组
getFormItemVNode() (item) => VNode 获取单个表单项 VNode

useSave(options)

用于保存和恢复表单条件:

import { useSave } from '@qynpm/form'

const { save, apply, SaveComponent } = useSave({
  id: 'query-form',
  modelForm,
  showFilter: true
})

默认行为:

  • 未注册 presetStorage 时,使用 localStorage
  • 已注册 presetStorage 时,统一走宿主提供的读写实现

QyQueryForm

用于承接旧 qy-query-form 的查询表单外壳能力。

<template>
  <QyQueryForm
    v-model="queryForm"
    :model="model"
    :options="{
      syncQuery: true,
      compact: true,
      id: 'query-form'
    }"
    :query-loading="loading"
    label-position="top"
    size="small"
    item-draggable
    @query="handleQuery"
  />
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { QyQueryForm, defineFormModel } from '@qynpm/form'

const loading = ref(false)
const queryForm = ref({})
const model = defineFormModel(
  computed(() => [
    { label: '店铺名称', key: 'shopName', type: 'string' },
    { label: '店铺类型', key: 'shopType', type: 'select' }
  ])
)

function handleQuery() {}
</script>

当前承接的能力包括:

  • 查询 / 重置按钮
  • SaveComponent 保存筛选
  • itemDraggable 拖拽排序
  • spare / _spare 备用字段区域
  • options.id 对应的 localStorage 排序缓存

适用场景:

  • 页面原来已经使用旧 <qy-query-form>
  • 页面依赖保存筛选、拖拽排序、备用字段
  • 页面迁移时希望尽量少改模板结构

如果页面只需要普通 schema 表单,不需要查询外壳,优先直接使用 useFormItem。

Props
字段 类型 默认值 说明
model FormItemSet[] - 查询表单 schema,支持 spare 和运行时 _spare
options Options {} 透传给内部 useFormItem,其中 options.id 同时用于排序缓存和保存筛选
itemDraggable boolean false 是否开启拖拽排序和备用字段区域;开启后必须提供 options.id
queryLoading boolean false 查询按钮 loading 状态
modelValue Record<string, any> {} 查询表单当前值,和内部 modelForm 双向同步。初始化优先级:URL query(syncQuery)> 初始 modelValue > options.form > 缓存 > schema 默认值,值为 undefined 的键视为未传;整体替换为新对象时,新对象缺少的 schema 字段回到 schema 默认值
Events
事件 参数 说明
update:modelValue (value: Record<string, any>) 内部表单值变化时触发
query () 点击查询按钮或保存筛选快捷项回填后触发
Slots
插槽 说明
default 追加到查询按钮区域后的自定义内容
行为约束
  • reset 只重置表单值,不会额外触发 query
  • 点击“重置排序”会回到 schema 中 spare 定义的默认分组
  • 已保存排序时,新增字段会优先根据 _spare 恢复到 active / spare 区域
  • itemDraggable 开启时,如果缺少 options.id,组件会报错提示
  • 排序缓存使用 localStorage,key 格式为 qyQueryForm_${options.id}

defineFormModel(formModel)

兼容旧查询表单的 model 定义方式,本身只做透传。

import { computed } from 'vue'
import { defineFormModel } from '@qynpm/form'

const model = defineFormModel(
  computed(() => [
    { label: '关键词', key: 'keyword', type: 'string' }
  ])
)

作用:

  • 让旧页面迁移时保持 defineFormModel(...) 写法不变
  • 方便后续统一替换旧 @qiyin/components/qyQueryForm 入口

configureQyForm(config)

用于给整个包注册运行时配置。当前主要用于接管“保存筛选条件”的存储实现。

import { configureQyForm } from '@qynpm/form'

configureQyForm({
  presetStorage: {
    async load(id) {
      return []
    },
    async save(id, presets) {}
  }
})
presetStorage

类型:

type FormPresetOption = {
  name: string
  data: string
}

type QyFormPresetStorage = {
  load: (cacheId: string) => Promise<FormPresetOption[]> | FormPresetOption[]
  save: (
    cacheId: string,
    presets: FormPresetOption[]
  ) => Promise<void> | void
}

说明:

  • load:读取某个筛选组 id 下的全部已保存条件
  • save:整体保存该 id 当前的全部条件,删除场景也通过这里落库
  • 包内不会要求宿主逐页传入;只需要全局注册一次

useSyncQueryParams(queryParams, options?)

用于查询表单与地址栏联动:

const form = useFormItem(schema, {
  syncQuery: true
})

当前保持的行为包括:

  • query 初始化回填
  • 数组值默认用逗号序列化 / 反序列化
  • 日期字段按类型序列化(见下表)
  • 尝试同步更新 tagsView 中的 query

URL 中的值无法按字段配置还原时(例如旧版本写出的错误日期链接、超出范围的时间戳),保留字段初始化值,不会覆盖成 undefined 或错误数组;无法序列化的值(例如不完整的日期区间)不写入 URL。

日期字段的 URL 格式

date、qyDatePicker 字段按实际生效的 type 与 valueFormat 选择格式:componentProps 覆盖 props(显式写 undefined 也算覆盖),value-format 与 valueFormat 两种写法均可。

字段类型 valueFormat URL 中的值 还原为
单值(date、datetime、week、month、year) 日期格式,如 YYYY-MM-DD 原值,如 2024-01-05 原字符串;必须能按该格式解析,否则视为无效
单值 x 毫秒时间戳 数字
单值 X 秒级时间戳 字符串
单值 未设置(控件值为 Date) 毫秒时间戳 Date
区间(daterange、datetimerange、monthrange) 任意 两个毫秒时间戳,逗号分隔 ['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss'],与 valueFormat 无关,沿用既有格式
多选(dates) 同单值 各项按单值规则,逗号分隔;任一项本身含逗号时改用 JSON 数组 数组,各项同单值
自定义转换

customSetterGetter 中配置的字段优先于以上默认规则。实际调用方式以此为准:get(value) 把表单值转成写入 URL 的字符串,set(text) 把 URL 中的字符串还原为表单值。也可以把字段配置为字符串 'date',表示按日期区间处理。

使用示例

基础表单

<component :is="form.formItem" prop="shopName" />
<component :is="form.formItem" prop="shopType" />

批量渲染

<component :is="form.formItems" />

includes / excludes

<component :is="form.formItems" includes="keyword,status,channel" />
<component :is="form.formItems" excludes="remark" />

query 表单

const queryForm = useFormItem(schema, {
  syncQuery: true,
  id: 'query-form'
})

启用缓存

const form = useFormItem(schema, {
  cache: {
    key: 'shop-form-cache'
  }
})

自定义渲染

{
  label: '自定义字段',
  key: 'custom',
  type: 'string',
  render: (h, { modelForm, Component }) =>
    h('div', { class: 'flex gap-2' }, [
      Component,
      h('span', modelForm.custom || '')
    ])
}

保存 / 应用按钮

<template>
  <component :is="form.SaveComponent" />
</template>

宿主全局注册示例

下面是适合在宿主项目里落地的最小示例:

import { configureQyForm, type FormPresetOption } from '@qynpm/form'

async function getUserPresets(cacheId: string): Promise<FormPresetOption[]> {
  // 示例:替换成宿主自己的接口
  return []
}

async function saveUserPresets(
  cacheId: string,
  presets: FormPresetOption[]
) {
  // 示例:替换成宿主自己的接口
}

configureQyForm({
  presetStorage: {
    load: getUserPresets,
    save: saveUserPresets
  }
})

如果要兼容旧 useUserCache 语义,可以在这个全局注册层里做:

  • load(id) 中调用旧缓存读取接口
  • save(id, presets) 中把整份筛选条件数组写回

这样替换旧 qyQueryForm 时,页面本身不需要改调用方式。

样式与运行时说明

  • 宿主项目需要引入 element-plus/dist/index.css
  • @qynpm/form 当前会自动注入包内样式
  • 宿主项目需要提供 @element-plus/icons-vue
  • 如果使用 syncQuery,宿主项目需要接入 pinia 和 vue-router
  • 包内图标仍然默认复用宿主图标体系
  • imageInput 默认使用 /permission-api/common/uploads 和 Admin-Token,如宿主上传接口不同,需要显式传入 api / tokenKey

本地开发

pnpm dev
pnpm build
  • pnpm dev:启动 playground 预览页
  • pnpm build:构建到 dist/

Workspace 联调:

{
  "dependencies": {
    "@qynpm/form": "workspace:*"
  }
}

已知限制

  1. useFormItem 和 useSave 目前仍然是配套关系,后续可以再评估是否继续拆开。
  2. createInstance 仍在包内,后续可根据拆包策略决定是否继续下沉。
  3. 当前图标能力仍然偏体系内约定。
  4. imageInput 是备注图文协议,默认上传接口只适合当前起印体系;跨项目使用时需要确认 api / tokenKey。

当前联调说明

当前不再通过手动修改 form / ui 源码 import 来切换联调模式,而是统一使用顶层 Vite 开关控制。

开关位置:

  • qy-workspace 顶层 vite.config.ts

当前配置:

const ENABLE_LOCAL_QIYIN_DEBUG = true

规则如下:

  1. 当值为 true 时
  • @qiyin/form / @qynpm/form 映射到本地源码入口 index.ts
  • @qynpm/ui 映射到本地源码入口 ../UI/lib/index.js
  • 适合 workspace 本地联调
  1. 当值为 false 时
  • 走各自包的正常入口
  • 更接近线上/发包后的使用方式

注意:

  1. 修改开关后需要重启对应项目的 dev 服务

Related Packages

@xunserver-jsf/element-plus

Element Plus UI adapter for @xunserver-jsf/vue.

586 0
d-render

基于 Element Plus 二次封装的数据驱动渲染组件库 - 表单、搜索表单、表格配置化渲染

8,750 5
@gooonzick/wizard-vue

Vue 3 integration for Wizard framework

3,248 3
czy-avue-form-design

Vue3.0版本,因Avue还存在一些bug,请慎重使用。

2,531 1,277
element-plus-generator

基于element-plus封装的表格、表单生成器

2,054

Version History

Version Released Vue License
1.1.0 ^3.4.21 MIT
1.0.7 ^3.4.21 MIT
1.0.6 ^3.4.21 MIT
1.0.5 ^3.4.21 MIT
1.0.4 ^3.4.21 MIT