当前位置: 首页 > news >正文

基于element-plus封装table组件

当前组件已经发布到npm库中,使用 pnpm add @dripadmin/drip-table 即可安装
表格本身是透明的, 实际使用时,需要自己设置背景色.

文章目录

    • 1. 需求分析
    • 2. 项目初始化与架构设计
      • 2.1 项目初始化
      • 2.2 目录结构设计
      • 2.3 基础配置文件
        • package.json
        • tsconfig.json
        • vite.lib.config.ts
    • 3. 组件设计与实现
      • 3.1 类型定义
      • 3.2 组件实现
        • 主组件实现
        • 行操作工具栏组件
      • 3.3 入口文件
    • 4. 组件功能与API文档
      • 4.1 DripTable 组件
        • 属性
        • 事件
        • 插槽
      • 4.2 行操作工具栏配置
    • 5. 打包与发布
      • 5.1 打包组件库
      • 5.2 发布到NPM
        • 5.2.1 准备发布
        • 5.2.2 登录NPM
        • 5.2.3 发布包
        • 5.2.4 版本更新
      • 5.3 使用发布的组件
    • 6. 总结与进阶

1. 需求分析

Element Plus提供了功能强大的el-table组件,但在实际业务系统中,存在大量重复的增删改查的操作,
所以针对现有的el-table进行封装后,只需要提供json类数据, 即可实现表单表格的操作, 提高我们的效率,
所需求大致如下:

  • 简化配置:通过JSON配置生成复杂表格
  • 工具栏:内置表格工具栏,支持自定义按钮和操作
  • 行操作:支持行级别的操作按钮
  • 分页控制:集成分页功能
  • 自定义渲染:支持自定义列渲染
  • 国际化:支持多语言
  • 主题定制:支持自定义主题
  • TypeScript支持:完整的类型定义

整体实现的效果如下图:
在这里插入图片描述

2. 项目初始化与架构设计

2.1 项目初始化

首先,我们需要创建一个基于Vue3和TypeScript的组件库项目:

# 创建项目目录
mkdir drip-table
cd drip-table# 初始化package.json
pnpm init# 安装核心依赖
pnpm add vue element-plus -P
pnpm add typescript vite @vitejs/plugin-vue @types/node -D

2.2 目录结构设计

组件库的目录结构如下:

drip-table/
├── packages/              # 组件源码
│   ├── components/        # 组件实现
│   │   ├── drip-table/    # 表格组件
│   │   │   ├── index.vue  # 主组件
│   │   │   ├── toolbar/   # 工具栏组件
│   │   │   └── row-toolbar/ # 行操作组件
│   │   └── drip-form/     # 表单组件(可选)
│   ├── types/             # 类型定义
│   └── index.ts           # 入口文件
├── playgrounds/           # 示例项目
│   └── drip-table-demo/   # 演示项目
├── package.json           # 包配置
├── tsconfig.json          # TypeScript配置
└── vite.lib.config.ts     # 打包配置

2.3 基础配置文件

package.json
{"name": "drip-table","version": "0.1.0","description": "基于Element Plus的表格组件封装","main": "dist/drip-table.umd.js","module": "dist/drip-table.es.js","types": "dist/types/index.d.ts","files": ["dist"],"scripts": {"dev": "cd playgrounds/drip-table-demo && pnpm run dev","build": "vite build --config vite.lib.config.ts","preview": "cd playgrounds/drip-table-demo && pnpm run preview"},"keywords": ["vue3","element-plus","table","component"],"author": "drip admin","license": "MIT","peerDependencies": {"vue": "^3.2.0","element-plus": "^2.2.0"}
}
tsconfig.json
{"compilerOptions": {"target": "ES2020","useDefineForClassFields": true,"module": "ESNext","lib": ["ES2020", "DOM", "DOM.Iterable"],"skipLibCheck": true,"moduleResolution": "bundler","allowImportingTsExtensions": true,"resolveJsonModule": true,"isolatedModules": true,"noEmit": true,"jsx": "preserve","strict": true,"noUnusedLocals": true,"noUnusedParameters": true,"noFallthroughCasesInSwitch": true,"paths": {"@/*": ["./packages/*"]}},"include": ["packages/**/*.ts", "packages/**/*.d.ts", "packages/**/*.tsx", "packages/**/*.vue"],"references": [{ "path": "./tsconfig.node.json" }]
}
vite.lib.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';export default defineConfig({plugins: [vue()],build: {lib: {entry: resolve(__dirname, 'packages/index.ts'),name: 'DripTable',fileName: (format) => `drip-table.${format}.js`},rollupOptions: {external: ['vue', 'element-plus'],output: {globals: {vue: 'Vue','element-plus': 'ElementPlus'}}}},resolve: {alias: {'@': resolve(__dirname, 'packages')}}
});

3. 组件设计与实现

3.1 类型定义

首先,我们需要定义组件的类型:

// packages/types/drip-table.ts
import type { CSSProperties } from "vue";export type Align = "left" | "center" | "right";export interface RowToolbarAction {label: string;type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default';disabled?: boolean;event: string;link?: boolean;
}export interface DripTableRowToolBar {label?: string;width?: number | string;align?: Align;fixed?: boolean | "left" | "right";size?: "small" | "default" | "large";actions: RowToolbarAction[];
}export interface DripTableColumn {label: string;prop?: string;type?: "selection" | "index" | "expand";width?: number | string;minWidth?: number | string;fixed?: boolean | "left" | "right";sortable?: boolean | "custom";align?: Align;headerAlign?: Align;showOverflowTooltip?: boolean;slot?: string;headerSlot?: string;children?: DripTableColumn[];
}export interface DripTablePagination {total: number;pageSize: number;currentPage: number;layout?: string;pageSizes?: number[];size?: "small" | "default" | "large";background?: boolean;align?: Align;
}export interface DripTableToolbarConfig {// 工具栏配置...
}export interface DripTableProps {data: any[];columns: DripTableColumn[];rowKey?: string;pagination?: DripTablePagination;toolbarLeft?: DripTableToolbarConfig;toolbarRight?: DripTableToolbarConfig;rowToolbar?: DripTableRowToolBar;height?: string | number;maxHeight?: string | number;stripe?: boolean;border?: boolean;size?: "large" | "default" | "small";fit?: boolean;showHeader?: boolean;highlightCurrentRow?: boolean;showOverflowTooltip?: boolean;emptyText?: string;defaultExpandAll?: boolean;expandRowKeys?: any[];defaultSort?: { prop: string; order: "ascending" | "descending" };tooltipEffect?: "dark" | "light";showSummary?: boolean;sumText?: string;summaryMethod?: (data: any) => any[];spanMethod?: (data: any) => any;selectOnIndeterminate?: boolean;indent?: number;lazy?: boolean;load?: (row: any, treeNode: any, resolve: (data: any[]) => void) => void;treeProps?: { children: string; hasChildren: string };tableLayout?: "fixed" | "auto";scrollbarAlwaysOn?: boolean;flexible?: boolean;
}export interface DripTableInstallOptions {locale?: string;i18n?: any;ssr?: boolean;
}

3.2 组件实现

主组件实现
<!-- packages/components/drip-table/index.vue -->
<template><ElConfigProvider :locale="elementLocale"><divclass="drip-table-wrapper":style="mergedWrapperStyle":id="String(tableKey)":class="[wrapperClass, { 'is-maximized': isMaximized, 'hide-ui': hideUIOnMaximize }]"><!-- 工具栏 --><div v-if="showAnyToolbar" class="drip-table__toolbars"><div class="drip-table__toolbar--left"><Toolbarv-if="toolbarLeftCfg":config="toolbarLeftCfg":columns="columns":data="data":table-key="tableKey"@refresh="emit('refresh')"@size-change="onSizeChange"@columns-visibility-change="onColumnsVisibilityChange"@columns-order-change="onColumnsOrderChange"@primary-action="emit('primary-action')"@maximize-toggle="onToggleMaximize"/> </div><div class="drip-table__toolbar--right"><Toolbarv-if="toolbarRightCfg":config="toolbarRightCfg":columns="columns":data="data":table-key="tableKey"@refresh="emit('refresh')"@size-change="onSizeChange"@columns-visibility-change="onColumnsVisibilityChange"@columns-order-change="onColumnsOrderChange"@primary-action="emit('primary-action')"@maximize-toggle="onToggleMaximize"/></div></div><!-- 表格 --><ElTableref="tableRef"v-bind="tableProps":data="data":height="tableHeight":max-height="tableMaxHeight":size="tableSize"@selection-change="emit('selection-change', $event)"@sort-change="emit('sort-change', $event)"@cell-click="emit('cell-click', $event)"@row-click="emit('row-click', $event)"><template v-for="(column, index) in visibleColumns" :key="index"><ElTableColumnv-if="!column.children || column.children.length === 0":prop="column.prop":label="column.label":type="column.type":width="column.width":min-width="column.minWidth":fixed="column.fixed":sortable="column.sortable":align="column.align":header-align="column.headerAlign":show-overflow-tooltip="column.showOverflowTooltip ?? showOverflowTooltip"><template #header="headerScope"><slot v-if="column.headerSlot" :name="column.headerSlot" :column="column" :scope="headerScope" /><span v-else>{{ column.label }}</span></template><template #default="scope"><slot v-if="column.slot" :name="column.slot" :row="scope.row" :column="column" :scope="scope" /><span v-else>{{ column.prop ? scope.row[column.prop] : '' }}</span></template></ElTableColumn><ElTableColumn v-else :label="column.label"><!-- 嵌套列 --><template v-for="(child, childIndex) in column.children" :key="`${index}-${childIndex}`"><!-- 嵌套列实现 --></template></ElTableColumn></template><!-- 行操作工具栏 --><ElTableColumn v-if="rowToolbar" :label="rowToolbar.label || '操作'" :width="rowToolbar.width || 100":align="rowToolbar.align || 'center'":fixed="rowToolbar.fixed || 'right'"><template #default="scope"><RowToolbar :actions="rowToolbar.actions || []" :row="scope.row":size="rowToolbar.size || 'small'"@action="(eventName, row) => emit('row-action', eventName, row)"/></template></ElTableColumn></ElTable><!-- 分页 --><div v-if="hasPagination" class="drip-table__pagination" :class="paginationClass" :style="paginationMerged?.style"><ElPaginationv-model:current-page="paginationState.currentPage"v-model:page-size="paginationState.pageSize":page-sizes="paginationMerged?.pageSizes":layout="paginationMerged?.layout":total="paginationMerged?.total ?? 0":background="paginationMerged?.background":size="paginationMerged?.size"@size-change="onPageSizeChange"@current-change="onCurrentPageChange"/></div></div></ElConfigProvider>
</template><script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { ElTable, ElTableColumn, ElPagination, ElConfigProvider } from 'element-plus';
import type { DripTableProps, DripTableColumn, DripTablePagination } from '@/types/drip-table';
import Toolbar from './toolbar/index.vue';
import RowToolbar from './row-toolbar/index.vue';// 组件逻辑实现...
</script><style scoped>
.drip-table-wrapper {width: 100%;position: relative;
}.drip-table__toolbars {display: flex;justify-content: space-between;margin-bottom: 16px;
}.drip-table__pagination {margin-top: 16px;display: flex;justify-content: flex-end;
}/* 更多样式... */
</style>
行操作工具栏组件
<!-- packages/components/drip-table/row-toolbar/index.vue -->
<template><div class="drip-table-row-toolbar"><el-button-group v-if="group"><el-buttonv-for="action in props.actions":key="action.event":type="action.type":size="props.size || 'small'":link="action.link || false"@click="handleAction(action.event)">{{ action.label }}</el-button></el-button-group><template v-else><el-buttonv-for="action in props.actions":key="action.event":type="action.type":size="props.size || 'small'":link="action.link || false"@click="handleAction(action.event)">{{ action.label }}</el-button></template></div>
</template><script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import type { RowToolbarAction } from '@/types/drip-table';const props = defineProps({actions: {type: Array as () => RowToolbarAction[],default: () => [],},row: {type: Object,default: () => ({}),},size: {type: String,default: 'small',},group: {type: Boolean,default: false,},
});const emit = defineEmits(['action']);const handleAction = (eventName: string) => {emit("action", eventName, props.row);
};
</script><style scoped>
.drip-table-row-toolbar {display: flex;gap: 8px;
}
</style>

3.3 入口文件

// packages/index.ts
import type { App } from 'vue';
import DripTableVue from './components/drip-table/index.vue';
import DripFormVue from './components/drip-form/index.vue';
import RowToolbarVue from './components/drip-table/row-toolbar/index.vue';
import type {DripTableProps,DripTableColumn,DripTablePagination,DripTableToolbarConfig,DripTableRowToolBar,DripTableInstallOptions,
} from './types/drip-table';
import type { DripFormConfig, DripFormItem } from './types/drip-form';export const DripTable = Object.assign(DripTableVue, {install(app: App, options?: DripTableInstallOptions) {app.component((DripTableVue as any).name || 'DripTable', DripTableVue);app.component('DripTableRowToolbar', RowToolbarVue);app.provide('locale', options ?? { locale: null, i18n: null, ssr: false });},
});export const DripForm = Object.assign(DripFormVue, {install(app: App) {app.component((DripFormVue as any).name || 'DripForm', DripFormVue);},
});export const DripTableRowToolbar = RowToolbarVue;export type {DripTableProps,DripTableColumn,DripTablePagination,DripTableToolbarConfig,DripTableRowToolBar,
};export type { DripFormConfig, DripFormItem };

4. 组件功能与API文档

4.1 DripTable 组件

属性
属性名类型默认值说明
dataArray[]表格数据
columnsArray[]表格列配置
rowKeyString‘id’行数据的唯一标识
paginationObject-分页配置
toolbarLeftObject-左侧工具栏配置
toolbarRightObject-右侧工具栏配置
rowToolbarObject-行操作工具栏配置
heightString/Number-表格高度
maxHeightString/Number-表格最大高度
stripeBooleanfalse是否为斑马纹表格
borderBooleanfalse是否带有边框
sizeString‘default’表格大小
更多属性参考Element Plus的Table组件
事件
事件名说明参数
selection-change当选择项发生变化时触发selection
sort-change当表格的排序条件发生变化时触发{ column, prop, order }
row-click当某一行被点击时触发row, column, event
row-action当行操作按钮被点击时触发eventName, row
refresh当刷新按钮被点击时触发-
page-change当页码改变时触发currentPage
page-size-change当每页显示条数改变时触发pageSize
primary-action当主操作按钮被点击时触发-
插槽
插槽名说明作用域参数
[column.slot]自定义列内容{ row, column, scope }
[column.headerSlot]自定义列头内容{ column, scope }

4.2 行操作工具栏配置

// 行操作工具栏配置示例
const rowToolbar = {label: '操作',width: 220,align: 'center',fixed: 'right',size: 'small',actions: [{ label: '新增', type: 'primary', event: 'add' },{ label: '修改', type: 'warning', event: 'edit' },{ label: '删除', type: 'danger', event: 'delete' }]
};

5. 打包与发布

5.1 打包组件库

# 执行打包命令
pnpm run build

打包后的文件将输出到dist目录,包含以下文件:

  • drip-table.es.js - ES模块格式
  • drip-table.umd.js - UMD格式
  • types/ - TypeScript类型定义

5.2 发布到NPM

5.2.1 准备发布
  1. 确保package.json中的信息正确:
{
{"name": "@dripadmin/drip-table","version": "0.2.5","description": "","license": "MIT","type": "module","main": "dist/index.cjs","module": "dist/index.mjs","types": "dist/index.d.ts","exports": {".": {"types": "./dist/index.d.ts","import": "./dist/index.mjs","require": "./dist/index.cjs"},"./style.css": "./dist/style.css"},"files": ["dist","readme.md"],// ...其他配置
}
  1. 创建.npmignore文件,排除不需要发布的文件:
# 源码和开发文件
packages/
playgrounds/
node_modules/
.vscode/
.idea/# 配置文件
.gitignore
.eslintrc.js
.prettierrc
tsconfig.json
vite.lib.config.ts# 其他文件
*.log
5.2.2 登录NPM
# 登录NPM
npm login

输入用户名、密码和邮箱,如果有双因素认证,还需要输入验证码。

5.2.3 发布包
# 发布包
npm publish

如果是第一次发布,可能需要添加--access=public参数:

npm publish --access=public
5.2.4 版本更新

当需要更新版本时,修改package.json中的版本号,然后重新打包和发布:

# 更新版本号
npm version patch  # 小版本更新
npm version minor  # 中版本更新
npm version major  # 大版本更新# 打包
pnpm run build# 发布
npm publish

5.3 使用发布的组件

在其他项目中安装和使用:

# 安装组件
pnpm add @dripadmin/drip-table

在Vue项目中注册和使用:

在main.ts 中引入全局的样式

// main.ts
import "@dripadmin/drip-table/style.css";

然后在业务组件中例如菜单管理中使用:

<template><DripForm:config="formConfig"@submit="onFormSubmit"@reset="onFormReset"@change="onFormChange"/><DripTable:columns="columns":data="rows":pagination="pagination":toolbar-right="toolbarRight":elTableProps="elTableProps":row-toolbar="tableRowToolbar"@page-size-change="onPageSizeChange"@page-current-change="onPageCurrentChange"@refresh="onRefresh"@row-click="onRowClick"><template #titleHeader><span>菜单名称</span></template><template #titleCell="{ row }"><span>{{ row.title }}</span></template></DripTable>
</template><script setup lang="ts">
import { onMounted, ref } from "vue";
import { DripTable,DripForm } from "@dripadmin/drip-table";
import type {DripTableColumn,DripTablePagination,DripTableToolbarConfig,DripTableRowToolBar,DripFormConfig,
} from "@dripadmin/drip-table";
import { getMenuListApi } from "@/api/menu_api";// 列定义
const columns = ref<DripTableColumn[]>([{ type: "index", label: "序", width: 60, align: "center" },{label: "菜单名称",prop: "title",slot: "titleCell",headerSlot: "titleHeader",minWidth: 160,},{ label: "路径", prop: "path", minWidth: 200 },{ label: "图标", prop: "icon", minWidth: 120 },{ label: "类型", prop: "type", minWidth: 100, align: "center" },{ label: "状态", prop: "status", minWidth: 100, align: "center" },{ label: "排序", prop: "order", minWidth: 80, align: "center" },
]);const tableRowToolbar = ref<DripTableRowToolBar>({actions: [{ label: "新增", type: "primary", event: "add" },{ label: "修改", type: "warning", event: "edit" },{ label: "删除", type: "danger", event: "delete" },],
});// 数据与分页
const rows = ref<any[]>([]);
const pagination = ref<DripTablePagination>({total: 0,pageSize: 10,currentPage: 1,
});// 工具条(右侧显示刷新/大小/列设置/最大化)
const toolbarRight = ref<DripTableToolbarConfig>({showRefresh: true,showSize: true,showColumnSetting: true,showFullscreen: true,
});// 透传 el-table 原生属性
const elTableProps = ref<Record<string, any>>({border: true,size: "default",
});// 加载菜单数据(兼容不同返回结构)
async function loadData() {const page = pagination.value.currentPage;const size = pagination.value.pageSize;const res: any = await getMenuListApi({ page, pageSize: size });const list =res?.list ?? res?.records ?? res?.rows ?? (Array.isArray(res) ? res : []);const total = res?.total ?? list.length ?? 0;rows.value = list || [];pagination.value.total = total || 0;
}function onPageSizeChange(size: number) {pagination.value.pageSize = size;pagination.value.currentPage = 1;loadData();
}function onPageCurrentChange(page: number) {pagination.value.currentPage = page;loadData();
}function onRefresh() {pagination.value.currentPage = 1;loadData();
}function onRowClick(eventName: string, row: any) {console.log("点击行操作:", eventName, row);
}onMounted(() => {loadData();
});const formConfig = ref<DripFormConfig>({items: [{type: "input",label: "名称",field: "name",placeholder: "输入名称",width: 220,},{type: "select",label: "类型",field: "type",options: [{ label: "目录", value: "0" },{ label: "页面", value: "1" },{ label: "按钮", value: "2" },{ label: "链接", value: "3" },],width: 140,},{type: "select",label: "状态",field: "status",options: [{ label: "启用", value: "启用" },{ label: "停用", value: "停用" },],width: 140,},],
});// 筛选条件
const filters = ref<{keyword: string;type: string | null;status: string | null;
}>({ keyword: "", type: null, status: null });
function onFormSubmit(values: Record<string, any>) {filters.value = { ...filters.value, ...values } as any;pagination.value.currentPage = 1;loadData();
}
function onFormReset(values: Record<string, any>) {filters.value = { keyword: "", type: null, status: null };pagination.value.currentPage = 1;loadData();
}
function onFormChange(field: string, value: any, values: Record<string, any>) {filters.value = { ...filters.value, ...values } as any;
}</script>

6. 总结与进阶

该组件部分代码使用AI自动生成,再进行加工优化,基本完成了基于Element Plus的表格组件二次封装, 已经发布在NPM库中。

这个组件库可以帮助我们在项目中快速实现简单的表格查询操作,提高开发效率, 后续会继续完善.

做为学习的一部分, 会逐步应用到dripadmin项目中.


http://www.dtcms.com/a/487745.html

相关文章:

  • 长沙市网站建设公司网做影视网站需要境外
  • 全国响应式网站建设宜宾网站建设网站
  • 终极AI开发组合:Warp + Cursor + Claude Code 实战技巧分享
  • 响应式企业营销型网站多少钱企业策划书模板word
  • MetaShape(PhotoScan)——Camera calibration相机标定详解
  • 青岛好的网站制作推广wordpress速度确实差些
  • 网站seo步骤专业的传媒行业网站开发
  • 大模型-高频考点-每日一更【篇二】
  • 卡文迪许实验室:百年物理圣地的辉煌发展史
  • 服务器建设网站伪类网站
  • 网站建设论文模板小说网站建设目的
  • 模板建站有什么优势高大上网站设计
  • Windows Server 2012/2016 开启远程桌面
  • 网页设计课程主要内容网站关键词怎样优化
  • 深圳建设网站哪家强wordpress获取站点副标题
  • C#中的依赖注入
  • 石家庄专业商城网站制作wordpress 主机安装教程
  • 外贸玩具网站无锡网站制作.
  • 国密 SM3 算法详解
  • 技术解析:Cryptomator如何实现云文件透明加密——从开发视角看开源安全工具
  • AI系统源码,人工智能,图像、视频、聊天、聊天机器人
  • 网站建设布为网wordpress禁止自适应
  • WINDOWS查看FTP端口号脚本
  • 镇江市建设工程造价信息期刊电子版官方
  • C#:可选参数
  • java数据结构--认识泛型
  • 常德做网站专业公司有关网站空间不正确的说法是
  • vmware workstation 25H2 开启3D加速失败问题修复
  • 建站网站关键词优化沧州网站建设哪家专业
  • JVM类的加载