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

@flowertip/search-form

v0.2.2

Published

基于 Vue 3 + Element Plus 的可配置搜索表单组件,支持十三种字段类型、响应式布局、远程数据、字段联动、展开折叠、自定义插槽

Readme

@flowertip/search-form

基于 Vue 3 + Element Plus 的可配置搜索表单组件。通过 fields 配置数组动态生成查询表单,支持十三种字段类型、响应式栅格布局、远程数据源、字段联动、展开/折叠等功能。CSS 样式自动注入,无需额外引入样式文件。

安装

npm install @flowertip/search-form

本组件依赖以下 peerDependencies,请确保项目中已安装:

npm install vue@^3.4 element-plus@^2.14 @element-plus/icons-vue@^2.3

快速开始

组件 CSS 已内联注入,import { SearchForm } 即可获得完整样式,无需手动引入 CSS 文件。

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { SearchForm } from '@flowertip/search-form'

const app = createApp(App)
app.use(ElementPlus)
app.component('SearchForm', SearchForm)
app.mount('#app')
<template>
  <div class="search-form-demo">
    <SearchForm
      ref="searchFormRef"
      :fields="formFields"
      :label-width="'110px'"
      label-suffix=":"
      :cols="3"
      :gutter="20"
      submit-text="查询"
      reset-text="清空"
      @submit="onSubmit"
      @reset="onReset"
      @toggle-collapse="onToggleCollapse"
      @field-change="onFieldChange"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { SearchForm } from '@flowertip/search-form'
import { InfoFilled } from '@element-plus/icons-vue'

const searchFormRef = ref()
const result = ref(null)

/** 表单字段配置 */
const formFields = ref([
  // ========== 1. 基础输入框 ==========
  {
    type: 'input',
    label: '用户名',
    field: 'username',
    placeholder: '请输入用户名',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    labelTip: '请输入系统注册用户名,支持中英文及数字组合',
  },

  // ========== 2. 本地数据源下拉框 ==========
  {
    type: 'select',
    label: '本地状态',
    field: 'localStatus',
    placeholder: '请选择状态(本地数据)',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    options: [
      { label: '草稿', value: 'draft' },
      { label: '已发布', value: 'published' },
      { label: '已归档', value: 'archived' }
    ]
  },

  // ========== 3. 远程数据源下拉框(GitHub API 演示)==========
  {
    type: 'select',
    label: '远程状态',
    field: 'remoteStatus',
    placeholder: '请选择(远程数据)',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    remoteOptions: {
      url: 'https://api.github.com/repos/vuejs/core/issues',
      filterable: true,
      queryKey: 'page',
      transform: (response) => {
        return response.slice(0, 5).map((item, index) => ({
          label: `#${index + 1} ${(item.title || '').substring(0, 20)}`,
          value: item.number || index
        }))
      }
    }
  },

  // ========== 4. 日期选择器 ==========
  {
    type: 'date',
    label: '开始日期',
    field: 'startDate',
    placeholder: '请选择日期',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    dateFormat: 'YYYY-MM-DD',
    dateValueFormat: 'YYYY-MM-DD'
  },

  // ========== 5. 日期时间选择器 ==========
  {
    type: 'datetime',
    label: '创建时间',
    field: 'createTime',
    placeholder: '请选择日期时间',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    dateFormat: 'YYYY-MM-DD HH:mm:ss',
    dateValueFormat: 'YYYY-MM-DD HH:mm:ss'
  },

  // ========== 6. 级联选择器 ==========
  {
    type: 'cascader',
    label: '所属地区',
    field: 'region',
    placeholder: '请选择地区',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    options: [
      {
        label: '广东',
        value: 'guangdong',
        children: [
          { label: '深圳', value: 'shenzhen' },
          { label: '广州', value: 'guangzhou' }
        ]
      },
      {
        label: '浙江',
        value: 'zhejiang',
        children: [
          { label: '杭州', value: 'hangzhou' },
          { label: '宁波', value: 'ningbo' }
        ]
      }
    ],
    cascaderProps: { expandTrigger: 'hover' }
  },

  // ========== 7. 数值输入框 ==========
  {
    type: 'input-number',
    label: '年龄',
    field: 'age',
    placeholder: '请输入年龄',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    labelTip: '请输入有效年龄,范围 0-120 岁',
    labelIcon: InfoFilled,
    attrs: { min: 0, max: 120, step: 1 }
  },

  // ========== 8. 复合型输入框:select 嵌套在 el-input 的 #prepend 中 ==========
  {
    type: 'composite',
    label: '金额',
    field: 'amount',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    labelTip: '选择币种并输入金额数值',
    composite: {
      prefix: {
        field: 'amountUnit',
        defaultValue: 'CNY',
        placeholder: '币种',
        options: [
          { label: 'CNY', value: 'CNY' },
          { label: 'USD', value: 'USD' },
          { label: 'EUR', value: 'EUR' }
        ]
      },
      suffix: {
        field: 'amountValue',
        placeholder: '请输入金额'
      }
    }
  },

  // ========== 9. 额外字段:演示折叠 — 默认隐藏,展开后可见 ==========
  {
    type: 'input',
    label: '邮箱',
    field: 'email',
    placeholder: '请输入邮箱',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    rules: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }]
  },
  {
    type: 'select',
    label: '主类别',
    field: 'mainCategory',
    placeholder: '请选择主类别',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    labelTip: '选择主类别后,子类别选项自动联动更新',
    linkage: {
      targets: ['subCategory'],
      handler: (newVal, targets, formModel, helpers) => {
        // 根据主类别动态设置子类别选项
        const subOptionsMap = {
          electronics: [
            { label: '手机', value: 'phone' },
            { label: '电脑', value: 'computer' },
            { label: '耳机', value: 'headphone' }
          ],
          clothing: [
            { label: '上衣', value: 'top' },
            { label: '裤子', value: 'pants' },
            { label: '鞋子', value: 'shoes' }
          ],
          food: [
            { label: '零食', value: 'snack' },
            { label: '饮料', value: 'drink' },
            { label: '生鲜', value: 'fresh' }
          ]
        }
        // 更新子类别字段的 options(通过查找字段并直接修改)
        const subField = formFields.value.find(f => f.field === 'subCategory')
        if (subField) {
          subField.options = subOptionsMap[newVal] || []
        }
      }
    },
    options: [
      { label: '电子产品', value: 'electronics' },
      { label: '服装', value: 'clothing' },
      { label: '食品', value: 'food' }
    ]
  },
  {
    type: 'select',
    label: '子类别',
    field: 'subCategory',
    placeholder: '先选择主类别',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    labelTip: '根据主类别动态变化',
    dependsOn: 'mainCategory',
    disabled: false,
    options: []  // 由 linkage handler 动态填充
  },
  {
    type: 'input-number',
    label: '排序权重',
    field: 'weight',
    placeholder: '请输入权重',
    span: { xs: 24, sm: 12, md: 8, lg: 6 },
    attrs: { min: 0, max: 100 }
  }
])

function onSubmit(formData) {
  console.log('查询数据:', formData)
  result.value = formData
}

function onFieldChange({ field, value }) {
  console.log(`字段 [${field}] 变更为:`, value)
}

function onReset(formData) {
  console.log('表单已重置:', formData)
  result.value = null
}

function onToggleCollapse(collapsed) {
  console.log('切换折叠状态:', collapsed ? '已折叠' : '已展开')
}
</script>

<style scoped>
.search-form-demo {
  margin: 20px auto 0;
  width: 90%;
  padding: 10px;
  box-sizing: border-box;
  background-color: #fff;
  border-radius: 4px;
  border: 1px solid var(--el-border-color);
  box-shadow: var(--el-box-shadow-lighter);
}
</style>

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | fields | Array | — (必填) | 字段配置数组 | | initialValue | Object | {} | 初始表单值 | | labelWidth | String | '100px' | 标签宽度 | | labelSuffix | String | '' | 标签后缀 | | cols | Number | 4 | 默认每行字段数 | | gutter | Number | 20 | 栅格间距 | | size | String | 'default' | 表单尺寸:large / default / small | | disabled | Boolean | false | 是否全局禁用表单 | | showActions | Boolean | true | 是否显示操作按钮 | | submitText | String | '查询' | 提交按钮文本 | | resetText | String | '重置' | 重置按钮文本 | | submitIcon | Component | '' | 提交按钮图标 | | resetIcon | Component | '' | 重置按钮图标 | | containerWidth | Number | 0 | 容器宽度(用于计算折叠行数),不传则自动获取 |

FieldConfig(字段配置)

每个字段支持以下属性:

通用属性

| 属性 | 类型 | 说明 | |------|------|------| | type | String | 字段类型,见下方 | | label | String | 标签文本 | | field | String | 绑定字段名 | | placeholder | String | 占位文本 | | span | Number / Object | 栅格列宽。数字表示固定值;对象支持 { xs, sm, md, lg, xl } 响应式 | | visible | Boolean | 是否可见,默认 true | | disabled | Boolean | 是否禁用 | | defaultValue | Any | 字段默认值 | | rules | Array | el-form 校验规则 | | clearable | Boolean | 是否可清空,默认 true | | attrs | Object | 透传给控件的额外属性 | | labelIcon | Component | label 旁图标,默认 QuestionFilled | | labelTip | String | 图标 hover 提示文字 | | linkage | Array / Object | 联动配置 | | dependsOn | String / Array | 依赖字段名 |

type 类型

支持以下十三种字段类型:

| type | 控件 | 额外属性 | |------|------|----------| | input | el-input | — | | select | el-select | options, remoteOptions | | date | el-date-picker | dateFormat, dateValueFormat | | datetime | el-date-picker | dateFormat, dateValueFormat | | week | el-date-picker | dateFormat, dateValueFormat | | month | el-date-picker | dateFormat, dateValueFormat | | year | el-date-picker | dateFormat, dateValueFormat | | daterange | el-date-picker | startPlaceholder, endPlaceholder, rangeSeparator, dateFormat, dateValueFormat | | datetimerange | el-date-picker | startPlaceholder, endPlaceholder, rangeSeparator, dateFormat, dateValueFormat | | cascader | el-cascader | options, cascaderProps | | input-number | el-input-number | attrs 传入 min/max/step | | composite | el-input(prepend) | composite: { prefix: {...}, suffix: {...} } | | custom | 作用域插槽 | 通过 #field-{field} 插槽自由渲染 |

remoteOptions(远程数据源)

{
  type: 'select',
  field: 'city',
  remoteOptions: {
    url: '/api/cities',                     // 或 (formModel) => `/api/cities/${formModel.province}`
    params: (formModel) => ({ pid: formModel.province }),  // 动态查询参数
    filterable: true,                        // 是否可搜索
    queryKey: 'keyword',                     // 搜索参数键名
    transform: (response) => response.map(...)  // 响应转换函数
  },
  dependsOn: 'province'
}

composite(复合输入框)

composite 类型将 el-select 嵌套在 el-input#prepend 插槽中,适合「前缀选择 + 后缀输入」的场景(如币种 + 金额):

{
  type: 'composite',
  label: '金额',
  field: 'amount',
  composite: {
    prefix: {
      field: 'amountUnit',      // 前缀绑定的字段名
      defaultValue: 'CNY',       // 前缀默认值
      placeholder: '币种',
      options: [
        { label: 'CNY', value: 'CNY' },
        { label: 'USD', value: 'USD' }
      ]
    },
    suffix: {
      field: 'amountValue',      // 后缀绑定的字段名
      placeholder: '请输入金额'
    }
  }
}

custom(自定义控件)

custom 类型通过作用域插槽 #field-{fieldName} 让使用者自由渲染任意 Element Plus 控件:

<template>
  <SearchForm :fields="fields">
    <!-- 插槽名必须为 field-{field} -->
    <template #field-themeColor="{ modelValue, update, disabled }">
      <el-color-picker
        :model-value="modelValue"
        :disabled="disabled"
        @update:model-value="update"
      />
    </template>
  </SearchForm>
</template>

<script setup>
const fields = [
  { type: 'custom', label: '主题颜色', field: 'themeColor', defaultValue: '#409EFF' }
]
</script>

作用域插槽参数:

| 参数 | 说明 | |------|------| | field | 字段配置对象 | | modelValue | 当前字段值 | | disabled | 是否禁用 | | update | 更新值的函数 (val) => void |

linkage(字段联动)

联动有两种写法:

简写(数组)

{ field: 'province', linkage: ['city', 'district'] }

完整写法(对象)

{
  field: 'category',
  linkage: {
    targets: ['subCategory'],
    handler: (newVal, targets, formModel, helpers) => {
      helpers.setFieldValue('otherField', 'defaultValue')
      helpers.reloadField('subCategory')
    }
  }
}

Events

| 事件 | 参数 | 说明 | |------|------|------| | submit | formData | 表单验证通过后触发 | | reset | formData | 重置后触发 | | toggle-collapse | collapsed | 折叠/展开切换 | | field-change | { field, value, linkedFields, formData } | 联动字段变化时触发 |

Expose(组件实例方法)

const formRef = ref()

formRef.value.validate()        // 返回 Promise,验证表单
formRef.value.resetFields()     // 重置表单
formRef.value.getFormData()     // 获取表单数据
formRef.value.setFormData({})   // 动态设置表单数据
formRef.value.toggleCollapse()  // 切换折叠
formRef.value.isCollapsed       // 当前折叠状态
formRef.value.remoteLoading     // { [field]: true/false } 远程加载状态

License

MIT