@qynpm/form
v1.0.6
Published
基于 Vue 3 和 Element Plus 的 schema 表单工具包。
Downloads
47
Readme
@qynpm/form
基于 Vue 3 + Element Plus 的 schema 驱动表单工具包。
当前提供:
useFormItemQyQueryFormdefineFormModeluseSaveuseSyncQueryParamsconfigureQyForm
安装
pnpm add @qynpm/formPeer Dependencies:
vue >= 3.4element-plus >= 2.0@element-plus/icons-vue >= 2.0pinia >= 2.0vue-router >= 4.0@qynpm/ui
引入
这是一个以 hooks 为主的工具包,通常按需引入即可:
import {
QyQueryForm,
defineFormModel,
useFormItem,
useSave,
useSyncQueryParams,
configureQyForm
} from '@qynpm/form'当前稳定导出
当前建议作为公共包稳定使用的导出面如下:
| 导出项 | 类型 | 说明 |
| --- | --- | --- |
| 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> | 获取当前表单值快照 |
| 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 双向同步 |
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 初始化回填
- 数组值默认序列化 / 反序列化
date时间区间转换- 尝试同步更新 tagsView 中的 query
使用示例
基础表单
<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 buildpnpm dev:启动 playground 预览页pnpm build:构建到dist/
Workspace 联调:
{
"dependencies": {
"@qynpm/form": "workspace:*"
}
}已知限制
useFormItem和useSave目前仍然是配套关系,后续可以再评估是否继续拆开。createInstance仍在包内,后续可根据拆包策略决定是否继续下沉。- 当前图标能力仍然偏体系内约定。
imageInput是备注图文协议,默认上传接口只适合当前起印体系;跨项目使用时需要确认api/tokenKey。
当前联调说明
当前不再通过手动修改 form / ui 源码 import 来切换联调模式,而是统一使用顶层 Vite 开关控制。
开关位置:
qy-workspace顶层vite.config.ts
当前配置:
const ENABLE_LOCAL_QIYIN_DEBUG = true规则如下:
- 当值为
true时
@qiyin/form/@qynpm/form映射到本地源码入口 index.ts@qynpm/ui映射到本地源码入口 ../UI/lib/index.js- 适合 workspace 本地联调
- 当值为
false时
- 走各自包的正常入口
- 更接近线上/发包后的使用方式
注意:
- 修改开关后需要重启对应项目的 dev 服务
