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

ly-eui

v1.4.9

Published

基于 Element Plus 的 Vue 3 组件库,提供表格、表单、对话框等常用业务组件。

Downloads

54

Readme

LyEui - Vue 3 组件库

基于 Element Plus 的 Vue 3 组件库,提供表格、表单、对话框等常用业务组件。

特性

  • 🚀 基于 Vue 3 + Element Plus
  • 📦 支持按需加载和 Tree-shaking
  • 🎨 完整的 TypeScript 支持
  • ⚡ 兼容 Vite、Webpack 等构建工具
  • 🎯 专注于企业级中后台应用

在线资源

安装与使用

安装

npm install ly-eui element-plus
# 或
yarn add ly-eui element-plus

全局注册

import { createApp } from 'vue'
import App from './App.vue'
import lyEui from 'ly-eui'
import 'ly-eui/style.css'

const app = createApp(App)
app.use(lyEui, {
      DpTable: {
            methods: {
                  $requestAPI({ url, headers, formParams, ...options }) {
                        console.log('接口注入依赖===', { url, headers, formParams })
                        return useRequest(url, formParams, { method: 'post', ...headers, ...options })
                  },
                  // 配置自动查询字段输出
                  configQueryfieldOutput() {
                        return {
                              total: 'records',
                              pageSize: 'pages',
                              current: 'pageindex',
                              tableData: 'rows'
                        }
                  }
            }
      },
      LyForm: {
            methods: {
                  $requestAPI({ url, headers, formParams, ...options }) {
                        console.log('接口注入依赖===', { url, headers, formParams, ...options })
                        return useRequest(url, formParams, { method: 'post', ...headers, ...options })
                  },
                  // 配置自动查询字段输出
                  fieldFieldOutput() {
                        return {
                              total: 'records',
                              pageSize: 'pages',
                              current: 'pageindex',
                              listData: 'rows'
                        }
                  }
            }
      }
})
app.mount('#app')

按需引入

import { DpTable, LyForm, useDialog } from 'ly-eui'
import 'ly-eui/dist/style.css'

export default {
  components: {
    DpTable,
    LyForm
  },
  setup() {
    const dialog = useDialog()
    return { dialog }
  }
}

1. dp-table 表格组件

dp-table 是一个功能强大的表格组件,支持分页、排序、选择、固定列、复合表头、自定义渲染等功能,并内置了数据请求和分页自动处理能力。

基础用法

<template>
  <dp-table
    :options="tableOptions"
    :columns="tableColumns"
    v-model:pagination="pagination"
    v-model:dataSource="dataSource"
    ref="tableRef"
  />
</template>

<script setup>
import { ref, reactive } from 'vue'

const tableRef = ref()
const dataSource = ref([])
const pagination = reactive({
  total: 0,
  pageSize: 10,
  current: 1
})

const tableOptions = reactive({
  size: 'small',
  border: true,
  heightType: 'height',
  tabHeight: 400
})

const tableColumns = reactive([
  {
    type: 'index',
    label: '序号',
    width: 80,
    align: 'center'
  },
  {
    label: '姓名',
    key: 'name',
    emptyPlaceholder: '-暂无-'
  },
  {
    label: '年龄',
    key: 'age',
    render: (text, row) => {
      return h('el-button', {
        type: 'primary',
        link: true,
        onClick: () => alert(`年龄: ${row.age}`)
      }, row.age)
    }
  },
  {
    label: '操作',
    key: 'operate',
    btns: [
      {
        name: '查看详情',
        clickEvent: (row) => {
          console.log('查看详情', row)
        }
      },
      {
        name: '删除',
        type: 'danger',
        clickEvent: (row) => {
          console.log('删除', row)
        }
      }
    ]
  }
])
</script>

参数配置

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | options | Object | 是 | {} | 表格全局配置,包含分页、接口请求等信息 | | columns | Array<Object> | 是 | [] | 表格列配置数组,定义表格的列内容及样式 | | v-model:pagination | Object | 是 | {} | 分页数据绑定,包含总条数、当前页、每页条数等信息 | | v-model:dataSource | Array | 是 | [] | 表格数据源绑定,存储表格展示的数据 | | ref | String | 否 | - | 表格实例引用,用于调用表格方法或访问表格状态 | | initTabHeight | Number | 否 | 400 | 表格初始高度 | | headBarClass | String/Object | 否 | '' | 表格头部栏样式类 | | showHeaderBar | Boolean | 否 | true | 是否显示头部栏 |

options 配置项详解

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | size | String | 否 | 'small' | 表格尺寸,可选值为 'large''default''small' | | border | Boolean | 否 | false | 是否显示表格边框 | | heightType | String | 否 | 'auto' | 表格高度类型,可选值为 'auto''height' | | tabHeight | Number | 否 | 400 | 表格容器高度(当 heightType 为 'height' 时生效) | | isInitRun | Boolean | 否 | true | 初始化时是否自动请求接口数据 | | cachePageSelection | Boolean | 否 | true | 是否缓存分页选择状态 | | rowkey | String | 否 | - | 行数据的唯一标识字段,用于缓存选择状态 | | apiParams | Object | 否 | - | 接口请求参数配置 | | configTableOut | Object | 否 | - | 接口返回数据字段映射配置 | | listenToCallBack | Object | 否 | - | 请求前后的回调函数配置 | | httpRequest | Function | 否 | - | 自定义请求方法 | | actionUrl | String | 否 | - | 接口请求地址(已废弃,推荐使用 apiParams) |

apiParams 配置

apiParams: {
  url: '/api/data/list', // 接口地址
  headers: { // 请求头
    'Content-Type': 'application/json'
  },
  model: { // 请求参数
    status: 1
  },
  options: { // 其他请求选项
    method: 'POST'
  }
}

configTableOut 配置

configTableOut: {
  total: 'records',    // 总条数字段名
  pageSize: 'pageSize', // 每页条数字段名
  current: 'pageNo',   // 当前页字段名
  tableData: 'rows'    // 数据列表字段名
}

listenToCallBack 配置

listenToCallBack: {
  brcb: (params) => {
    // 请求前参数处理
    return { ...params, timestamp: Date.now() }
  },
  qrcb: (response) => {
    // 请求后响应处理
    console.log('响应数据:', response)
  }
}

columns 配置项详解

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | type | String | 否 | - | 列类型,如 'selection'(复选框)、'index'(序号列)、'expand'(展开行) | | label | String | 是 | - | 列标题 | | key/prop | String | 是 | - | 数据字段名,对应数据源中的字段 | | width | Number/String | 否 | - | 列宽度,可以是数字或字符串(如 '200px') | | align | String | 否 | 'left' | 内容对齐方式,可选值为 'left''center''right' | | fixed | String/Boolean | 否 | false | 是否固定列,可选值为 'left''right'true/false | | render | Function | 否 | - | 自定义渲染函数 | | btns | Array | 否 | - | 操作按钮配置(仅在 key/prop 为 'operate' 时生效) | | groups | Array | 否 | - | 复合表头配置 | | hidCol | Boolean | 否 | false | 是否隐藏该列 | | emptyPlaceholder | String | 否 | '-' | 空值占位符 | | scopedSlots | Object | 否 | - | 作用域插槽配置 |

复合表头配置示例

{
  label: '任务奖励',
  key: 'reward',
  groups: [
    {
      label: '奖励成长值',
      key: 'growthValue',
      width: 200,
      render: (text, record) => 20
    },
    {
      label: '奖励积分',
      key: 'points',
      width: 200,
      render: (text, record) => 50
    }
  ]
}

表格实例方法

通过 ref 获取表格实例后,可以调用以下方法:

// 获取选中的行数据
const selectedRows = tableRef.value.multipleSelection

// 清空选择
tableRef.value.clearAllSelection()

// 手动触发表格布局重计算
tableRef.value.tableRef?.doLayout()

// 获取表格内部实例
const tableInstance = tableRef.value.getTableInstantce()

事件

| 事件名 | 参数 | 描述 | |--------|------|------| | update:dataSource | dataSource | 数据源更新时触发 | | update:pagination | pagination | 分页信息更新时触发 | | pagination-current-change | pagination | 当前页码改变时触发 | | size-change | pagination, size | 每页条数改变时触发 | | change-table-sort | column | 表格排序改变时触发 |

高级用法

1. 表格与表单组合使用

<template>
  <dp-table
    :options="tableOptions"
    :columns="tableColumns"
    v-model:pagination="pagination"
    v-model:dataSource="dataSource"
  >
    <ly-form
      ref="formRef"
      :model="formModel"
      :layout-form-props="formLayout"
      :form-item-list="formItems"
      :config-btn="formButtons"
    />
  </dp-table>
</template>

2. 自动化分页处理

当表格嵌套表单时,表格会自动处理分页请求:

const tableOptions = {
  isInitRun: true,
  apiParams: {
    url: '/api/orders',
    model: { status: 1 }
  },
  configTableOut: {
    total: 'total',
    pageSize: 'size',
    current: 'page',
    tableData: 'list'
  }
}

3. 自定义请求方法

const tableOptions = {
  httpRequest: async (params) => {
    const { url, formParams, ...rest } = params
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formParams)
    })
    return await response.json()
  }
}

2. ly-form 表单组件

ly-form 是一个高度灵活的表单组件,支持动态表单项生成、多种内置表单类型、自定义渲染、表单验证、条件显示等功能。它采用声明式配置的方式,通过 JSON 配置快速构建复杂的表单界面。

基础用法

<template>
  <ly-form
    ref="formRef"
    :model="formModel"
    :layout-form-props="formLayout"
    :form-item-list="formItems"
    :config-btn="formButtons"
  />
</template>

<script setup>
import { ref, reactive } from 'vue'

const formRef = ref()

const formModel = reactive({
  sourceChannel: '',
  orderNo: '',
  customerName: '',
  customerPhone: '',
  productName: '',
  performanceMode: '',
  orderStatus: '',
  time: [],
  time2: [],
  batchNo: ''
})

const formLayout = reactive({
  fromLayProps: {
    labelWidth: '95px',
    size: 'small',
    minfoldRows: 2, // 最多展示2行,超出部分可折叠
    gutter: 16
  }
})

const formItems = reactive([
  {
    label: '来源渠道',
    span: 6,
    prop: 'sourceChannel',
    formtype: 'Select',
    dataOptions: [
      { label: '全部', value: '' },
      { label: '领克APP', value: '16' },
      { label: '几何APP', value: '15' },
      { label: '吉利APP', value: '14' },
      { label: '运营后台', value: '17' }
    ],
    attrs: {
      clearable: true,
      placeholder: '请选择'
    }
  },
  {
    label: '订单编号',
    span: 6,
    prop: 'orderNo',
    formtype: 'Input',
    formItemOptions: {
      rules: {
        required: true,
        trigger: 'blur'
      }
    }
  },
  {
    label: '客户姓名',
    span: 6,
    prop: 'customerName',
    formtype: 'Input',
    isHiden: (formData) => formData.sourceChannel === '16'
  }
])

const formButtons = reactive([
  {
    name: '查询',
    icon: 'Search',
    key: 'query',
    type: 'primary',
    click: (model, formInstance) => {
      formInstance.validate((valid) => {
        if (valid) {
          console.log('表单验证通过', model)
        }
      })
    }
  },
  {
    name: '重置',
    icon: 'RefreshLeft',
    key: 'reset',
    click: (model, formInstance) => {
      formInstance.resetFields()
    }
  }
])
</script>

参数配置

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | model | Object | 是 | {} | 表单数据绑定对象,存储表单字段值 | | layoutFormProps | Object | 是 | {} | 表单布局属性配置 | | formItemList | Array<Object> | 是 | [] | 表单项配置数组 | | configBtn | Array<Object> | 否 | [] | 操作按钮配置数组 | | renderBtn | Function/Boolean | 否 | false | 自定义按钮渲染函数 | | btnColSpanRow | Boolean | 否 | true | 按钮是否独占一行 | | ref | String | 否 | - | 表单实例引用 |

layoutFormProps 配置项详解

fromLayProps 配置

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | isBtnHiden | Boolean | 否 | false | 是否隐藏操作按钮 | | labelWidth | String | 否 | - | 标签宽度(如 '95px') | | labelBtnWidth | String | 否 | - | 按钮标签宽度 | | btnColSpan | Number | 否 | 0 | 按钮栅格占据列数 | | minfoldRows | Number | 否 | 0 | 最小折叠行数,超过此行数会显示展开/收起按钮 | | size | String | 否 | - | 表单尺寸('large''default''small') | | labelPosition | String | 否 | 'left' | 标签位置('left''right''top') | | showMessage | Boolean | 否 | true | 是否显示校验错误信息 |

rowLayProps 配置

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | gutter | Number | 否 | 0 | 栅格间隔 | | justify | String | 否 | 'start' | 水平排列方式 | | align | String | 否 | 'top' | 垂直对齐方式 |

setOptions 配置

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | setOptions | Boolean | 否 | false | 是否启用表单和表格自定义选项开关(需要与 dp-table 组合使用) |

formItemList 配置项详解

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | label | String | 是 | - | 表单项标签 | | span | Number | 是 | 6 | 栅格占据列数(1-24) | | prop | String | 是 | - | 数据绑定字段名 | | formtype | String | 否 | - | 内置表单类型 | | render | Function | 否 | - | 自定义渲染函数 | | formItemOptions | Object | 否 | - | 表单项配置(校验规则等) | | isHiden | Function | 否 | - | 动态隐藏条件函数 | | dataOptions | Array | 否 | [] | 数据选项(用于 Select、Radio 等) | | components | Object | 否 | - | 自定义组件 | | apiParams | Object | 否 | - | 接口参数(用于异步加载数据) | | callOptionListFormat | Function | 否 | - | 数据格式化函数 | | attrs | Object | 否 | - | 表单项额外属性 | | on | Object | 否 |

内置表单类型 (formtype)

  • 'Input': 输入框
  • 'Select': 选择器
  • 'datePicker': 日期选择器
  • 'timePicker': 时间选择器
  • 'Cascader':级联选择器
  • 'Radio': 单选框
  • 'Checkbox': 复选框
  • 'Switch': 开关
  • 'Rate': 评分
  • 'Upload': 上传

自定义渲染示例

{
  label: '履约方式',
  span: 6,
  prop: 'performanceMode',
  render: (formData, row) => {
    return h('el-select', {
      modelValue: formData.performanceMode,
      'onUpdate:modelValue': (value) => {
        formData.performanceMode = value
      },
      clearable: true,
      style: 'width: 100%'
    }, row.dataOptions.map(item =>
      h('el-option', {
        key: item.value,
        value: item.value,
        label: item.label
      })
    ))
  }
}

异步数据加载

{
  label: '商品分类',
  span: 6,
  prop: 'categoryId',
  formtype: 'Select',
  apiParams: {
    url: '/api/categories',
    model: { status: 1 }
  },
  callOptionListFormat: (data) => {
    return data.map(item => ({
      value: item.id,
      label: item.name
    }))
  }
}

configBtn 配置项详解

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | name | String | 是 | - | 按钮名称 | | icon | String | 否 | - | 按钮图标名称 | | key | String | 否 | - | 按钮标识 | | type | String | 否 | 'default' | 按钮类型 | | disabled | Boolean/Function | 否 | false | 是否禁用按钮 | | click | Function | 是 | - | 按钮点击事件处理函数 | | direction | String | 否 | 'left' | 按钮位置('left''right') | | triggerEvent | String | 否 | - | 触发事件类型('query''rest') |

按钮点击事件参数

click: (model, formInstance, tableInstance) => {
  // model: 表单数据对象
  // formInstance: 表单实例(包含 validate、resetFields 等方法)
  // tableInstance: 表格实例(当与 dp-table 组合使用时)
}

表单实例方法

通过 ref 获取表单实例后,可以调用以下方法:

// 表单验证
formRef.value.formInstance.validate((valid, fields) => {
  if (valid) {
    console.log('验证通过')
  } else {
    console.log('验证失败', fields)
  }
})

// 重置表单
formRef.value.formInstance.resetFields()

// 清空验证
formRef.value.formInstance.clearValidate()

高级用法

1. 复杂表单验证

{
  label: '客户手机号',
  span: 6,
  prop: 'customerPhone',
  formtype: 'Input',
  formItemOptions: {
    rules: [
      {
        required: true,
        message: '请输入手机号',
        trigger: 'blur'
      },
      {
        validator: (rule, value, callback) => {
          if (value && !/^[1][3-9][0-9]{9}$/.test(value)) {
            callback(new Error('请输入正确的手机号'))
          } else {
            callback()
          }
        },
        trigger: 'blur'
      }
    ]
  }
}

2. 条件显示表单项

{
  label: '客户姓名',
  span: 6,
  prop: 'customerName',
  formtype: 'Input',
  isHiden: (formData, item, fromProps) => {
    // 根据表单数据动态隐藏表单项
    return formData.sourceChannel === '16'
  }
}

3. 表单与表格组合

ly-form 作为 dp-table 的子组件时,表单会自动与表格联动:

<dp-table :options="tableOptions" :columns="tableColumns">
  <ly-form
    :model="formModel"
    :layout-form-props="formLayout"
    :form-item-list="formItems"
  />
</dp-table>

在这种情况下,表单的查询按钮会自动触发表格的数据请求。


3. useDialog 弹窗组件

useDialog 是一个函数式弹窗组件,提供简单直观的 API 来创建和管理弹窗。它支持拖拽、全屏、自定义按钮、内容渲染等功能,并提供两种模式:单例模式和多实例模式。

基础用法

import { useDialog } from 'ly-eui'

// 创建弹窗实例
const dialog = useDialog()

// 打开弹窗
const openDialog = () => {
  dialog({
    width: '40%',
    title: '提醒',
    isDraggable: true,
    closeOnClickModal: true,
    configBtn: [
      {
        name: '确定',
        icon: 'CircleCheck',
        type: 'primary',
        click: (renderBodyRefs, lyFormInstance, dialogInstance) => {
          // 关闭弹窗
          dialogInstance.exposed.closed()
        }
      },
      {
        name: '取消',
        icon: 'CircleClose',
        click: () => {
          // 关闭弹窗
          dialog.close()
        }
      }
    ],
    render: (renderBodyRefs, h, { DpTable, LyForm }) => {
      return h('div', '这是一个弹窗内容')
    },
    onSubmit: (closeFn) => {
      // 提交回调
      console.log('弹窗提交')
      closeFn() // 关闭弹窗
    },
    onClosed: () => {
      // 关闭回调
      console.log('弹窗已关闭')
    }
  })
}

参数配置

useDialog 函数参数

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | Component | Component | 否 | DpDialog | 自定义弹窗组件 | | opt | Object | 否 | { onlyInstance: false } | 选项配置 |

DialogComponent 函数参数 (options 对象)

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | visible | Boolean | 否 | true | 是否可见 | | width | String/Number | 否 | '50%' | 弹窗宽度(百分比或像素值) | | title | String | 否 | - | 弹窗标题 | | isDraggable | Boolean | 否 | false | 是否允许拖动弹窗 | | closeOnClickModal | Boolean | 否 | true | 点击蒙层是否关闭弹窗 | | hiddenFullBtn | Boolean | 否 | false | 是否隐藏全屏按钮 | | fullscreen | Boolean | 否 | false | 是否以全屏模式打开弹窗 | | destroyOnClose | Boolean | 否 | true | 关闭时是否销毁组件 | | configBtn | Array<Object> | 否 | [] | 弹窗底部操作按钮配置 | | render | Function | 是 | - | 自定义渲染函数 | | onSubmit | Function | 否 | - | 弹窗提交事件处理函数 | | onClosed | Function | 否 | - | 弹窗关闭事件处理函数 | | appendTo | String/HTMLElement | 否 | 'body' | 弹窗挂载的 DOM 元素 |

configBtn 配置项详解

| 参数名 | 类型 | 是否必填 | 默认值 | 描述 | |--------|------|----------|--------|------| | name | String | 是 | - | 按钮显示的文本 | | icon | String | 否 | - | 按钮图标名称 | | key | String | 否 | - | 按钮的唯一标识 | | type | String | 否 | - | 按钮类型('primary''success''warning''danger''info') | | disabled | Boolean/Function | 否 | false | 是否禁用按钮,可以是返回布尔值的函数 | | click | Function | 是 | - | 按钮点击事件处理函数 | | direction | String | 否 | - | 按钮方向('left''right') |

按钮点击事件参数

click: (renderBodyRefs, lyFormInstance, dialogInstance) => {
  // renderBodyRefs: 渲染内容的 refs
  // lyFormInstance: ly-form 实例(如果渲染内容包含 ly-form)
  // dialogInstance: 弹窗实例
}

弹窗实例方法

单例模式 (onlyInstance: false,默认)

const dialog = useDialog()

// 关闭弹窗
dialog.close()

// 销毁弹窗
dialog.destroy()

多实例模式 (onlyInstance: true)

const dialog = useDialog(null, { onlyInstance: true })

// 关闭当前弹窗实例
dialog.close()

高级用法

1. 嵌套弹窗

const openNestedDialog = () => {
  const dialog1 = useDialog()
  const dialog2 = useDialog()
  
  dialog1({
    width: '30%',
    title: '嵌套对话框',
    configBtn: [
      {
        name: '打开子弹窗',
        click: () => {
          dialog2({
            width: '40%',
            title: '子弹窗',
            render: () => h('div', '这是子弹窗')
          })
        }
      }
    ],
    render: () => h('div', '这是父弹窗')
  })
}

2. 弹窗与表单组合

const openFormDialog = () => {
  const dialog = useDialog()
  
  dialog({
    width: '65%',
    title: '新增',
    isDraggable: true,
    closeOnClickModal: false,
    configBtn: [
      {
        name: '保存',
        type: 'primary',
        click: (renderBodyRefs, lyFormInstance) => {
          lyFormInstance.validate((valid) => {
            if (valid) {
              console.log('表单验证通过')
              // 保存逻辑
            }
          })
        }
      }
    ],
    render: (renderBodyRefs, h, { LyForm }) => {
      return h(LyForm, {
        ref: 'formRef',
        model: formModel,
        layoutFormProps: formLayout,
        formItemList: formItems
      })
    }
  })
}

3. 弹窗与表格组合

const openTableDialog = () => {
  const dialog = useDialog()
  
  dialog({
    width: '80%',
    title: '表格弹窗',
    render: (renderBodyRefs, h, { DpTable }) => {
      return h(DpTable, {
        options: tableOptions,
        columns: tableColumns,
        pagination: pagination,
        dataSource: dataSource
      })
    }
  })
}

4. 自定义弹窗组件

// 自定义弹窗组件
const CustomDialog = {
  name: 'CustomDialog',
  props: ['visible', 'title', 'onSubmit', 'onClosed'],
  emits: ['update:visible'],
  setup(props, { emit }) {
    const handleClose = () => {
      emit('update:visible', false)
      props.onClosed?.()
    }
    
    const handleSubmit = () => {
      props.onSubmit?.(handleClose)
    }
    
    return () => h('div', {
      class: 'custom-dialog'
    }, [
      h('h3', props.title),
      h('div', '自定义内容'),
      h('button', { onClick: handleSubmit }, '确定'),
      h('button', { onClick: handleClose }, '取消')
    ])
  }
}

// 使用自定义弹窗
const dialog = useDialog(CustomDialog)
dialog({
  title: '自定义弹窗',
  onSubmit: (closeFn) => {
    console.log('自定义提交')
    closeFn()
  }
})

最佳实践

1. 性能优化

  • 表格性能: 对于大数据量表格,建议使用虚拟滚动或分页加载
  • 表单性能: 避免在 render 函数中进行复杂计算,可使用 computed 缓存
  • 弹窗性能: 对于频繁打开的弹窗,考虑使用单例模式 (onlyInstance: true)

2. 错误处理

  • 表格错误处理: 在 httpRequest 中添加错误处理逻辑
  • 表单验证: 使用完整的 async-validator 规则进行表单验证
  • 弹窗异常: 在弹窗的 onSubmitonClosed 回调中处理异常

3. 代码组织

  • 配置分离: 将表格列配置、表单项配置等提取到单独的文件中
  • 组件复用: 创建可复用的表单组件和弹窗组件
  • 状态管理: 对于复杂的状态,考虑使用 Vuex 或 Pinia 进行管理

4. 类型安全

// TypeScript 类型定义示例
interface Order {
  id: number
  orderNo: string
  customerName: string
  customerPhone: string
  orderStatus: 'WAIT_PAYMENT' | 'WAIT_INSTALL' | 'INSTALLED_WAIT_CONFIRM' | 'WAIT_DELIVER' | 'DEAL_CLOSED' | 'DEAL_FINISH'
}

interface TableColumn {
  label: string
  key: keyof Order
  width?: number | string
  render?: (text: any, row: Order) => VNode
}

interface FormItem {
  label: string
  prop: keyof Order
  span: number
  formtype: string
  formItemOptions?: {
    rules?: Rule[]
  }
}

常见问题解答

Q1: 如何自定义表格列的样式?

A: 可以通过 columns 配置中的 render 函数返回自定义的 JSX 元素,或者使用 scopedSlots 配置作用域插槽。

Q2: 表单如何与表格联动?

A: 当 ly-form 作为 dp-table 的子组件时,表单的查询按钮会自动触发表格的数据请求。也可以通过 ref 手动调用表格的 httpRequestInstance 方法。

Q3: 弹窗中的表单如何验证?

A: 在弹窗的按钮点击事件中,通过第二个参数 lyFormInstance 调用 validate 方法进行表单验证。

Q4: 如何处理异步数据加载?

A: 在 formItemList 中使用 apiParams 配置异步接口,组件会自动处理数据加载和选项渲染。

开发环境

# 启动开发环境
node start.js --imgname=vite-ly-eui --mode=dev1

ly-eui 组件库综合说明

1. 架构设计特点

优势:

  • 声明式编程范式: 采用配置驱动的开发模式,通过 JSON/对象配置快速构建复杂界面,极大提升开发效率
  • JSX/TSX 支持: 充分利用 Vue 3 的 Composition API 和 JSX 能力,提供灵活的渲染控制
  • 组件解耦设计: 三大核心组件(LyForm、DpTable、useDialog)职责清晰,可独立使用也可组合使用
  • 插件化架构: 支持全局插件配置和按需引入,便于项目集成和定制

劣势:

  • 学习曲线较陡: 需要开发者熟悉 JSX/TSX 语法和声明式编程思想
  • 过度依赖配置: 复杂的配置对象可能导致代码可读性下降

2. 核心功能评估

LyForm 表单组件:

  • ✅ 支持动态表单渲染,内置多种表单类型(Input、Select、DatePicker 等)
  • ✅ 提供灵活的布局控制(栅格系统、折叠展开、自定义渲染)
  • ✅ 支持异步数据加载和接口自动调用
  • ✅ 内置表单验证和条件显示逻辑

DpTable 表格组件:

  • ✅ 完整的表格功能(分页、排序、选择、固定列等)
  • ✅ 支持分组表头和复杂自定义渲染
  • ✅ 内置数据请求和分页自动处理
  • ✅ 提供缓存分页选择状态功能
  • ✅ 自适应高度计算和响应式布局

useDialog 弹窗组件:

  • ✅ 函数式调用,使用简单直观
  • ✅ 支持拖拽、全屏、自定义按钮等高级功能
  • ✅ 提供两种模式:单例模式和多实例模式
  • ✅ 完善的生命周期管理

3. 技术栈兼容性

兼容性良好:

  • ✅ 基于 Vue 3 + Element Plus 构建,充分利用现代 Vue 特性
  • ✅ TypeScript 支持完善,提供完整的类型定义
  • ✅ 支持现代浏览器和开发工具链

依赖约束:

  • ⚠️ 强依赖 Element Plus 2.0+,升级 Element Plus 可能带来兼容性风险
  • ⚠️ 需要 Vue 3.0+ 环境,

不兼容 Vue 2.x 项目

4. 性能表现

优势:

  • ✅ 虚拟滚动支持,大数据量表格性能良好
  • ✅ 按需加载机制,减少初始包体积
  • ✅ 响应式数据绑定,状态更新高效

待优化:

  • ⚠️ 复杂配置对象的深拷贝可能影响性能
  • ⚠️ 大量自定义渲染函数可能增加内存占用

5. 开发体验

开发友好:

  • ✅ 配置驱动,减少样板代码
  • ✅ 完善的 TypeScript 支持
  • ✅ 丰富的示例和文档
  • ✅ 组件组合灵活,扩展性强

学习成本:

  • ⚠️ 需要理解 JSX/TSX 语法
  • ⚠️ 配置对象结构较复杂,需要熟悉 API

6. 适用场景

强烈推荐:

  • 中后台管理系统快速开发
  • 数据密集型应用(表格、表单为主)
  • 需要快速原型开发的场景
  • 团队技术栈统一为 Vue 3 + Element Plus

谨慎使用:

  • 对性能要求极高的应用
  • 需要大量自定义 UI 的项目
  • 团队不熟悉 JSX/TSX 的情况

7. 总结

ly-eui 是一个功能强大、设计现代的 Vue 3 组件库,特别适合中后台管理系统的快速开发。它通过声明式配置大大简化了复杂界面的开发过程,提供了完整的表格、表单、弹窗解决方案。虽然在学习曲线和性能方面有一定挑战,但其带来的开发效率提升和代码质量改善是显著的。对于采用 Vue 3 + Element Plus 技术栈的团队,ly-eui 是一个值得考虑的选择。


完整CRUD示例

下面是一个完整的增删改查示例,展示了如何使用 ly-eui 的三大核心组件协同工作:

<template>
  <div class="crud-demo">
    <!-- 表格区域 -->
    <dp-table
      ref="tableRef"
      :options="tableOptions"
      :columns="tableColumns"
      :pagination="pagination"
      :http-request="httpRequest"
    >
      <!-- 查询表单 -->
      <ly-form
        ref="searchFormRef"
        :model="searchForm"
        :layout-form-props="searchLayout"
        :form-item-list="searchItems"
      />
    </dp-table>
  </div>
</template>

<script setup>
import { ref, reactive, h } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useDialog } from 'ly-eui'

// 表格引用
const tableRef = ref()
const searchFormRef = ref()

// 搜索表单数据
const searchForm = reactive({
  orderNo: '',
  customerName: '',
  orderStatus: '',
  dateRange: []
})

// 搜索表单布局
const searchLayout = reactive({
  inline: true,
  configBtn: [
    {
      name: '查询',
      icon: 'Search',
      type: 'primary',
      triggerEvent: 'query',
      click: () => {
        tableRef.value.httpRequestInstance()
      }
    },
    {
      name: '重置',
      icon: 'Refresh',
      triggerEvent: 'rest',
      click: () => {
        searchFormRef.value.formInstance.resetFields()
        tableRef.value.httpRequestInstance()
      }
    }
  ]
})

// 搜索表单项
const searchItems = reactive([
  {
    label: '订单编号',
    span: 6,
    prop: 'orderNo',
    formtype: 'Input',
    formItemOptions: {
      placeholder: '请输入订单编号'
    }
  },
  {
    label: '客户姓名',
    span: 6,
    prop: 'customerName',
    formtype: 'Input',
    formItemOptions: {
      placeholder: '请输入客户姓名'
    }
  },
  {
    label: '订单状态',
    span: 6,
    prop: 'orderStatus',
    formtype: 'Select',
    formItemOptions: {
      placeholder: '请选择订单状态',
      clearable: true
    },
    options: [
      { value: '', label: '全部' },
      { value: 'WAIT_PAYMENT', label: '待付款' },
      { value: 'WAIT_INSTALL', label: '待安装' },
      { value: 'INSTALLED_WAIT_CONFIRM', label: '已安装待确认' },
      { value: 'WAIT_DELIVER', label: '待发货' },
      { value: 'DEAL_CLOSED', label: '交易关闭' },
      { value: 'DEAL_FINISH', label: '交易完成' }
    ]
  },
  {
    label: '下单时间',
    span: 6,
    prop: 'dateRange',
    formtype: 'datePicker',
    formItemOptions: {
      type: 'daterange',
      rangeSeparator: '至',
      startPlaceholder: '开始日期',
      endPlaceholder: '结束日期',
      valueFormat: 'YYYY-MM-DD'
    }
  }
])

// 表格配置
const tableOptions = reactive({
  stripe: true,
  border: true,
  fit: true,
  showHeader: true,
  highlightCurrentRow: true,
  emptyText: '暂无数据',
  showSummary: false,
  selectOnIndeterminate: true,
  indent: 16,
  lazy: false,
  load: undefined,
  treeProps: { hasChildren: 'hasChildren', children: 'children' },
  defaultExpandAll: false,
  defaultExpandedKeys: undefined,
  expandRowKeys: undefined,
  defaultSort: undefined,
  tooltipEffect: 'dark',
  showOverflowTooltip: true,
  spanMethod: undefined,
  selectOnClick: true,
  reserveSelection: true,
  rowKey: 'id'
})

// 表格列配置
const tableColumns = reactive([
  {
    type: 'selection',
    width: 55,
    fixed: 'left'
  },
  {
    label: '订单编号',
    key: 'orderNo',
    width: 180,
    fixed: 'left',
    showOverflowTooltip: true
  },
  {
    label: '客户姓名',
    key: 'customerName',
    width: 120
  },
  {
    label: '客户手机号',
    key: 'customerPhone',
    width: 130
  },
  {
    label: '订单状态',
    key: 'orderStatus',
    width: 120,
    render: (text, row) => {
      const statusMap = {
        'WAIT_PAYMENT': { text: '待付款', type: 'warning' },
        'WAIT_INSTALL': { text: '待安装', type: 'info' },
        'INSTALLED_WAIT_CONFIRM': { text: '已安装待确认', type: 'primary' },
        'WAIT_DELIVER': { text: '待发货', type: 'info' },
        'DEAL_CLOSED': { text: '交易关闭', type: 'danger' },
        'DEAL_FINISH': { text: '交易完成', type: 'success' }
      }
      const status = statusMap[text] || { text: '未知', type: 'info' }
      return h('el-tag', { type: status.type }, status.text)
    }
  },
  {
    label: '订单金额',
    key: 'orderAmount',
    width: 120,
    align: 'right',
    render: (text) => `¥${Number(text).toFixed(2)}`
  },
  {
    label: '下单时间',
    key: 'createTime',
    width: 160,
    render: (text) => new Date(text).toLocaleString()
  },
  {
    label: '操作',
    key: 'action',
    width: 200,
    fixed: 'right',
    render: (text, row) => {
      return h('div', { class: 'action-buttons' }, [
        h('el-button', {
          type: 'primary',
          size: 'small',
          onClick: () => handleEdit(row)
        }, '编辑'),
        h('el-button', {
          type: 'info',
          size: 'small',
          onClick: () => handleView(row)
        }, '查看'),
        h('el-button', {
          type: 'danger',
          size: 'small',
          onClick: () => handleDelete(row)
        }, '删除')
      ])
    }
  }
])

// 分页配置
const pagination = reactive({
  pageSize: 10,
  currentPage: 1,
  pageSizes: [10, 20, 50, 100],
  layout: 'total, sizes, prev, pager, next, jumper',
  background: true
})

// 弹窗实例
const dialog = useDialog()

// 表单数据
const formModel = reactive({
  id: undefined,
  orderNo: '',
  customerName: '',
  customerPhone: '',
  orderStatus: 'WAIT_PAYMENT',
  orderAmount: 0,
  remark: ''
})

// 表单布局
const formLayout = reactive({
  labelWidth: '100px',
  configBtn: [
    {
      name: '保存',
      icon: 'Check',
      type: 'primary',
      click: (model, formInstance, dialogInstance) => {
        formInstance.validate((valid) => {
          if (valid) {
            saveOrder(model).then(() => {
              ElMessage.success('保存成功')
              dialogInstance.exposed.closed()
              tableRef.value.httpRequestInstance()
            })
          }
        })
      }
    },
    {
      name: '取消',
      icon: 'Close',
      click: (model, formInstance, dialogInstance) => {
        dialogInstance.exposed.closed()
      }
    }
  ]
})

// 表单项
const formItems = reactive([
  {
    label: '订单编号',
    span: 12,
    prop: 'orderNo',
    formtype: 'Input',
    formItemOptions: {
      disabled: true,
      placeholder: '系统自动生成'
    }
  },
  {
    label: '客户姓名',
    span: 12,
    prop: 'customerName',
    formtype: 'Input',
    formItemOptions: {
      placeholder: '请输入客户姓名'
    },
    rules: [
      { required: true, message: '请输入客户姓名', trigger: 'blur' },
      { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
    ]
  },
  {
    label: '客户手机号',
    span: 12,
    prop: 'customerPhone',
    formtype: 'Input',
    formItemOptions: {
      placeholder: '请输入客户手机号'
    },
    rules: [
      { required: true, message: '请输入客户手机号', trigger: 'blur' },
      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
    ]
  },
  {
    label: '订单状态',
    span: 12,
    prop: 'orderStatus',
    formtype: 'Select',
    formItemOptions: {
      placeholder: '请选择订单状态'
    },
    options: [
      { value: 'WAIT_PAYMENT', label: '待付款' },
      { value: 'WAIT_INSTALL', label: '待安装' },
      { value: 'INSTALLED_WAIT_CONFIRM', label: '已安装待确认' },
      { value: 'WAIT_DELIVER', label: '待发货' },
      { value: 'DEAL_CLOSED', label: '交易关闭' },
      { value: 'DEAL_FINISH', label: '交易完成' }
    ],
    rules: [{ required: true, message: '请选择订单状态', trigger: 'change' }]
  },
  {
    label: '订单金额',
    span: 12,
    prop: 'orderAmount',
    formtype: 'Input',
    formItemOptions: {
      type: 'number',
      placeholder: '请输入订单金额'
    },
    rules: [
      { required: true, message: '请输入订单金额', trigger: 'blur' },
      { type: 'number', min: 0, message: '订单金额不能小于0', trigger: 'blur' }
    ]
  },
  {
    label: '备注',
    span: 24,
    prop: 'remark',
    formtype: 'Input',
    formItemOptions: {
      type: 'textarea',
      rows: 3,
      placeholder: '请输入备注信息'
    }
  }
])

// 表格数据请求
const httpRequest = async (params) => {
  try {
    const queryParams = {
      ...params,
      ...searchForm,
      startDate: searchForm.dateRange?.[0],
      endDate: searchForm.dateRange?.[1]
    }
    
    const response = await fetch('/api/orders', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(queryParams)
    })
    
    const data = await response.json()
    return {
      data: data.list,
      total: data.total
    }
  } catch (error) {
    ElMessage.error('获取数据失败')
    return {
      data: [],
      total: 0
    }
  }
}

// 新增订单
const handleAdd = () => {
  // 重置表单数据
  Object.keys(formModel).forEach(key => {
    if (key === 'orderStatus') {
      formModel[key] = 'WAIT_PAYMENT'
    } else if (key === 'orderAmount') {
      formModel[key] = 0
    } else {
      formModel[key] = undefined
    }
  })
  
  dialog({
    width: '60%',
    title: '新增订单',
    isDraggable: true,
    closeOnClickModal: false,
    render: (renderBodyRefs, h, { LyForm }) => {
      return h(LyForm, {
        ref: 'formRef',
        model: formModel,
        layoutFormProps: formLayout,
        formItemList: formItems
      })
    }
  })
}

// 编辑订单
const handleEdit = (row) => {
  // 填充表单数据
  Object.keys(formModel).forEach(key => {
    formModel[key] = row[key]
  })
  
  dialog({
    width: '60%',
    title: '编辑订单',
    isDraggable: true,
    closeOnClickModal: false,
    render: (renderBodyRefs, h, { LyForm }) => {
      return h(LyForm, {
        ref: 'formRef',
        model: formModel,
        layoutFormProps: formLayout,
        formItemList: formItems
      })
    }
  })
}

// 查看订单
const handleView = (row) => {
  dialog({
    width: '60%',
    title: '订单详情',
    isDraggable: true,
    configBtn: [
      {
        name: '关闭',
        icon: 'Close',
        click: (renderBodyRefs, dialogInstance) => {
          dialogInstance.exposed.closed()
        }
      }
    ],
    render: (renderBodyRefs, h) => {
      return h('div', { class: 'order-detail' }, [
        h('el-descriptions', {
          title: '订单信息',
          column: 2,
          border: true
        }, () => [
          h('el-descriptions-item', { label: '订单编号' }, row.orderNo),
          h('el-descriptions-item', { label: '客户姓名' }, row.customerName),
          h('el-descriptions-item', { label: '客户手机号' }, row.customerPhone),
          h('el-descriptions-item', { label: '订单状态' }, () => {
            const statusMap = {
              'WAIT_PAYMENT': { text: '待付款', type: 'warning' },
              'WAIT_INSTALL': { text: '待安装', type: 'info' },
              'INSTALLED_WAIT_CONFIRM': { text: '已安装待确认', type: 'primary' },
              'WAIT_DELIVER': { text: '待发货', type: 'info' },
              'DEAL_CLOSED': { text: '交易关闭', type: 'danger' },
              'DEAL_FINISH': { text: '交易完成', type: 'success' }
            }
            const status = statusMap[row.orderStatus] || { text: '未知', type: 'info' }
            return h('el-tag', { type: status.type }, status.text)
          }),
          h('el-descriptions-item', { label: '订单金额' }, `¥${Number(row.orderAmount).toFixed(2)}`),
          h('el-descriptions-item', { label: '下单时间' }, new Date(row.createTime).toLocaleString()),
          h('el-descriptions-item', { label: '备注', span: 2 }, row.remark || '无')
        ])
      ])
    }
  })
}

// 删除订单
const handleDelete = (row) => {
  ElMessageBox.confirm(
    `确定要删除订单 ${row.orderNo} 吗?`,
    '删除确认',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).then(() => {
    return deleteOrder(row.id)
  }).then(() => {
    ElMessage.success('删除成功')
    tableRef.value.httpRequestInstance()
  }).catch(() => {
    ElMessage.info('已取消删除')
  })
}

// 批量删除
const handleBatchDelete = () => {
  const selectedRows = tableRef.value.getSelectionRows()
  if (selectedRows.length === 0) {
    ElMessage.warning('请选择要删除的订单')
    return
  }
  
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.length} 个订单吗?`,
    '批量删除确认',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).then(() => {
    const ids = selectedRows.map(row => row.id)
    return batchDeleteOrders(ids)
  }).then(() => {
    ElMessage.success('批量删除成功')
    tableRef.value.clearSelection()
    tableRef.value.httpRequestInstance()
  }).catch(() => {
    ElMessage.info('已取消批量删除')
  })
}

// API 方法(模拟)
const saveOrder = async (data) => {
  // 模拟 API 调用
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('保存订单:', data)
      resolve({ success: true })
    }, 1000)
  })
}

const deleteOrder = async (id) => {
  // 模拟 API 调用
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('删除订单:', id)
      resolve({ success: true })
    }, 500)
 
})

const batchDeleteOrders = async (ids) => {
  // 模拟 API 调用
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('批量删除订单:', ids)
      resolve({ success: true })
    }, 500)
  })
}

// 添加新增按钮到表格工具栏
tableOptions.configBtn = [
  {
    name: '新增',
    icon: 'Plus',
    type: 'primary',
    click: handleAdd
  },
  {
    name: '批量删除',
    icon: 'Delete',
    type: 'danger',
    click: handleBatchDelete
  }
]
</script>

<style scoped>
.crud-demo {
  padding: 20px;
}

.action-buttons {
  display: flex;
  gap: 8px;
}

.order-detail {
  padding: 20px;
}
</style>

这个完整的CRUD示例展示了:

  1. 表格与表单联动: 搜索表单自动与表格数据请求联动
  2. 完整的增删改查: 包含新增、编辑、查看、删除、批量删除功能
  3. 弹窗与表单组合: 使用 useDialog 创建表单弹窗
  4. 数据验证: 表单内置完整的验证规则
  5. 状态管理: 使用响应式数据管理表单状态
  6. 用户体验: 包含加载状态、错误处理、确认提示等

总结

ly-eui 组件库通过声明式配置的方式,极大地简化了中后台管理系统的开发过程。三大核心组件各有特色:

  • dp-table: 功能强大的表格组件,支持复杂的数据展示和交互
  • ly-form: 灵活的表单组件,支持动态渲染和验证
  • useDialog: 简洁的弹窗组件,提供函数式调用方式

通过合理的组合使用这些组件,可以快速构建出功能完善、用户体验良好的管理后台系统。组件库的设计遵循现代前端开发的最佳实践,提供了良好的类型支持和扩展能力。

快速开始建议

  1. 从简单开始: 先掌握基础用法,再逐步学习高级功能
  2. 参考示例: 多参考官方示例和文档,理解配置结构
  3. 逐步集成: 先在项目中试用单个组件,再逐步替换现有代码
  4. 关注性能: 对于大数据量场景,注意使用分页和虚拟滚动
  5. 保持更新: 关注组件库的更新,及时获取新功能和修复

希望这份文档能够帮助你快速上手 ly-eui 组件库,构建出优秀的中后台管理系统!


相关资源


最后更新时间: 2025年12月1日
文档版本: v1.4.7
组件库版本: 基于最新版本 **更新开发文档和修复Select 组件中 el-option 不显示问题 **更新打包esbuild适配Liux平台


## 许可证

MIT License

## 联系方式

如有问题或建议,请提交 Issue 或 Pull Request。