npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qynpm/form

v1.0.6

Published

基于 Vue 3 和 Element Plus 的 schema 表单工具包。

Downloads

47

Readme

@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'

当前稳定导出

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

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

说明:

  • QyQueryFormdefineFormModel 可以作为旧查询表单迁移入口使用。
  • 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 输出表单项
  • 提交时通过 modelFormgetModelForm() 获取当前值

补充说明:

  • 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 | 分类、组织、品类等树形筛选;支持 datalabel-keyvalue-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
  }
}

不再额外暴露 qyInputqySelectqyRadioGroup。对应能力已经分别收敛到 stringselectradio,避免 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,宿主项目需要接入 piniavue-router
  • 包内图标仍然默认复用宿主图标体系
  • imageInput 默认使用 /permission-api/common/uploadsAdmin-Token,如宿主上传接口不同,需要显式传入 api / tokenKey

本地开发

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

Workspace 联调:

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

已知限制

  1. useFormItemuseSave 目前仍然是配套关系,后续可以再评估是否继续拆开。
  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 服务