赞
踩
vue3通用表格组件的由来,前面讲过后台管理页面中查询展示表格数据基本上是必备功能。前面我发布过一篇使用vue2+element-ui封装的通用页面组件。原文可访问这篇文章 还在写重复的增删改查的重复代码?还在重复写Ajax请求?Vue2+Element-ui实现常规后台查询展示页面来了
在vue3普及的现在。这篇文章将使用vue3和element-plus。在el-table组件的基础上二次封装表格的常用展示形式。简化我们的代码使用方式。减少重复代码量,提高一定的开发效率。下面我们来一起看看我们的实现思路。
初始化vue3的项目我们使用vite+vue3+ts,这里就不多叙述了,大家可以看这篇文章: 初始化
1.基础文本展示
2.枚举类型展示 如:(状态,分类,标签,等根据status或type等字段展示对应的内容)
3.文本过长超出省略号
4.鼠标悬浮展示
5.操作区域 如:(查看,编辑,删除等操作)
根据以上的功能我们来在el-table的基础上把他封装到一个通用组件中
这里我们使用tsx函数组件的方式来实现,这样避免在template模版中使用大量的if else 哦我
我们先来定义一个table组件 在components文件中定义一个mijiTable.tsx的组件
首先我们在组件中引入element-plus的el-table、el-table-column组件
在这个组件中我们使用defineComponent来定义一个组件,使用组合式api setup来实现内部逻辑
这个组件我们接受两个值,一个是用来渲染el-table-column的数据 columns,一个是表格要渲染的数据 dataSource
我们来看看columns字段都包含了什么:
其实columns的内容字段就是 el-table-column组件的属性我们在这个基础上扩展一个slot字段用来自定义单元格内容
- [{
- prop: '',
- label: '操作',
- type: '', //
- slot: 'options',
- width: 120,
- fixed: 'right'
- }]
-
在element-plus中 type属性默认值改成了default我们这里的type不用做el-table-column的type属性。而是用做区分表格列是什么格式。目前我们实现的有以下几种
常规 text、index序号、枚举、和操作栏
我们来看看如何动态渲染el-table-colunm
-
- import {
- ElTable,
- ElTableColumn
- } from 'element-plus'
-
- export default defineComponent({
- props: {
- columns: {
- type: Array,
- default: () => []
- },
- dataSource: {
- type: Array,
- default: () => []
- }
- },
- setup(props, { slots }: any) {
- const { columns, dataSource } = props
- console.log('渲染表格列的数据', columns)
- console.log('表格数据:', dataSource);
-
- const tableData = ref()
- watch(() => props.dataSource, (now) => {
- tableData.value = now
- })
-
- const initTable = () => {
- return <ElTable
- data={tableData.value}
- style={{ width: "100%" }}>
- </ElTable>
- }
-
- return () => initTable()
- }
- })
-
-
基础的文本渲染没有什么特殊的还是使用el-table-column组件就好
-
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>
-
-
这里我就不列举官方的这些属性了,大家可以去官方文档查看: el-table-column属性
下面我们看看如何动态渲染不同类型的table-column
我们在el-table中写如下代码:利用数组的map去生成el-table-column组件,在map中我们来判断当前的column是什么类型的 然后动态的返回就好。
-
- {
- columns.map((col: any) => {
- const colWidth = col.width || '';
- const minWidth = col.minWidth || '';
- const fixed = col.fixed || false;
- const showOverflowTooltip = col.showOverflowTooltip || false
- const length = col.options && col.options.length || null
- const headerSlot = genHeaderSlot(col); // 自定义表头
- const defaultSlot = genDefaultSlot(col); // 自定义表格单元格
- const tableCellSlot = {
- ...headerSlot,
- ...defaultSlot,
- }
- // 序号
- if (col.prop === 'index') {
- return (<ElTableColumn
- label={col.label}
- type="index"
- className={col.className}
- width={55}
- />);
- }
-
- switch (col.type) {
- // 操作栏
- case 'options':
- return (
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth || (length > 3 ? '180px' : '160px')}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row) => {
- let btns, moreBtn
- if (length > 3) {
- btns = col.options.slice(0, 2)
- moreBtn = col.options.slice(2, length)
- } else {
- btns = col.options
- }
- return length > 3 ? renderOptionsAndMore(btns, moreBtn, row) : renderOptions(btns, row)
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>
- );
- // 枚举类型的表格单元格
- case 'map':
- if (col.typeMap) { // 状态枚举
- return (<ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row): any => {
- return renderTableMapCell(row, col);
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>);
- } else { // 普通枚举
- return (<ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row) => {
- return col.map[row[col.prop]];
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>);
- }
- default:
- return (
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>
- );
- }
- })
- }
-
-
index类型就不过多解释了,来看看column中的options类型,也就是表格的操作栏,
操作栏动态计算高度或者通过传入的宽度来设置宽度,这里的按钮我们通过 options 类型中的options数组来渲染
-
- const buttonEvents = (v: any, row: any) => {
- if (v.clickEvent) {
- v.clickEvent(row)
- } else {
- alert('请传入clickEvent事件参数')
- }
- }
- const renderButton = (v: any, row: any) => {
- return <ElButton
- text
- type={v.type}
- disabled={v.disabled && v.disabled(row)}
- class={v.className}
- style={{ padding: '5px' }}
- onClick={() => buttonEvents(v, row)}
- >
- {v.text}
- </ElButton>
- }
- // 渲染按钮
- const renderOptions = (btns: any, row: any) => {
- return btns && btns.map((v: any) => {
- return renderButton(v, row)
- })
- }
- // 渲染更多按钮
- const renderOptionsAndMore = (btns: any, moreBtn: any, row: any) => {
- return (<div>
- {renderOptions(btns, row)}
- {
- <ElDropdown v-slots={{
- dropdown: () => <ElDropdownMenu>
- {moreBtn.map((v: any) => {
- return <ElDropdownItem>
- {renderButton(v, row)}
- </ElDropdownItem>
- })}
- </ElDropdownMenu>
- }}>
- <ElButton style={{ marginLeft: '10px', padding: '5px' }} type="primary" text>
- 更多<ElIcon><ArrowDownBold /></ElIcon>
- </ElButton>
- </ElDropdown>
- }
- </div>)
- }
-
-
除了options类型还有枚举和插槽这两种形式,插槽的就相对简单了。
-
- // 根据自定义表头的配置,动态生成需要的scopedSlot对象
- const genHeaderSlot = (col: any) => {
- if (col.headerSlot) {
- return {
- header: () => {
- return slots.headerSlot && slots.headerSlot();
- }
- };
- }
-
- return {};
- }
- // 自定义表格单元格的slot显示
- const genDefaultSlot = (col: any) => {
- if (col.slot) {
- return {
- // scope 是当前渲染单元格的数据
- default: (scope: any) => {
- return slots[col.slot] && slots[col.slot](scope);
- }
- }
- }
- }
-
-
- 在table-column 中我们这样使用的
-
- const headerSlot = genHeaderSlot(col); // 自定义表头
- const defaultSlot = genDefaultSlot(col); // 自定义表格单元格
- const tableCellSlot = {
- ...headerSlot,
- ...defaultSlot,
- }
-
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- >
- {{ ...tableCellSlot }} //这里展开插槽的对象默认渲染的 是 default: () => col.slot()
- </ElTableColumn>
-
-
枚举的形式我们以普通的和带徽标的为例,枚举的我们要额外接收两个参数,map和typeMap,map为当前数据所要映射的内容,typeMap为带徽标组件的类型'primary' | 'success' | 'warning' | 'danger' | 'info' 你们也可以自行拓展其他的组件,如tag等展示型组件
-
- // 渲染 type: map 的单元格
- const renderTableMapCell = (row: any, col: any) => {
- if (col.map[row[col.prop]]) {
- return <ElBadge
- is-dot={true}
- type={col.typeMap[row[col.prop]]}
- dot-position="left-middle"
- >
- {col.map[row[col.prop]]}
- </ElBadge>;
- }
-
- return null;
- }
-
-
-
到这里我们基础的使用列已经封装好了,下面我们来看看具体的使用方式。
可以全局引入注册全局组件,也可以在单独页面中引入。这里我们看下全局组件
-
- import { createApp } from 'vue'
- import './style.css'
- import App from './App.vue'
-
- import router from './router/index'
-
- import ElementPlus from 'element-plus'
- import 'element-plus/dist/index.css'
- // 引入表格组件
- import MiJiTable from './components/mijiTable'
-
- const app = createApp(App)
- // 这里通过app.component注册表格组件为全局组件
- app.component(MiJiTable.name, MiJiTable)
-
- app.use(router).use(ElementPlus).mount('#app')
-
-
这里MiJiTable.name就是组件中的name属性。miji-table
这样我们在页面中就可以直接使用<miji-table></miji-table>
-
- <template>
- <miji-table :columns="columns" :dataSource="dataSource"></miji-table>
- </template>
-
- <script setup lang="ts">
- import { onMounted, ref } from "vue";
- // 我们初始化的时候定义一个columns 定义好要渲染表格的列有哪些。
- const columns = ref([
- { prop: "index", label: "序号", width: "80px" },
- { prop: "date", label: "Date" },
- { prop: "name", label: "Name" },
- {
- prop: "state",
- label: "State",
- },
- { prop: "city", label: "City", minWidth: "120px" },
- { prop: "address", label: "Address", minWidth: "120px" },
- { prop: "zip", label: "Zip", minWidth: "120px" },
- { prop: "tag", label: "Tag", minWidth: "120px" },
- {
- prop: "option",
- label: "操作",
- fixed: "right",
- type: "options",
- options: [
- {
- type: "primary",
- text: "查看",
- clickEvent: (row) => optionsFunction(row),
- }
- ],
- },
- ]);
- // 定义 表格数据的变量
- const dataSource = ref([]);
-
- const optionsFunction = (row) => {
- console.log(row);
- };
-
- onMounted(() => {
- setTimeout(() => {
- dataSource.value = [] // 这里做请求数据填充
- }, 1000);
- });
- </script>
-
-
完整代码:如下
-
- import { watch, defineComponent, ref } from 'vue'
- import {
- ElTable,
- ElTableColumn,
- ElButton,
- ElDropdown,
- ElDropdownMenu,
- ElDropdownItem,
- ElBadge,
- ElIcon
- } from 'element-plus'
-
- import { ArrowDownBold } from '@element-plus/icons-vue'
-
- import { tableProps } from './props'
-
- export default defineComponent({
- props: tableProps,
- setup(props, { slots }: any) {
- const { columns, dataSource } = props
- console.log('表格数据:', dataSource);
- const tableData = ref()
- watch(() => props.dataSource, (now) => {
- tableData.value = now
- })
-
- const buttonEvents = (v: any, row: any) => {
- if (v.clickEvent) {
- v.clickEvent(row)
- } else {
- alert('请传入clickEvent事件参数')
- }
- }
- const renderButton = (v: any, row: any) => {
- return <ElButton
- text
- type={v.type}
- disabled={v.disabled && v.disabled(row)}
- class={v.className}
- style={{ padding: '5px' }}
- onClick={() => buttonEvents(v, row)}
- >
- {v.text}
- </ElButton>
- }
- // 渲染按钮
- const renderOptions = (btns: any, row: any) => {
- return btns && btns.map((v: any) => {
- return renderButton(v, row)
- })
- }
- // 渲染更多按钮
- const renderOptionsAndMore = (btns: any, moreBtn: any, row: any) => {
- return (<div>
- {renderOptions(btns, row)}
- {
- <ElDropdown v-slots={{
- dropdown: () => <ElDropdownMenu>
- {moreBtn.map((v: any) => {
- return <ElDropdownItem>
- {renderButton(v, row)}
- </ElDropdownItem>
- })}
- </ElDropdownMenu>
- }}>
- <ElButton style={{ marginLeft: '10px', padding: '5px' }} type="primary" text>
- 更多<ElIcon><ArrowDownBold /></ElIcon>
- </ElButton>
- </ElDropdown>
- }
- </div>)
- }
-
- // 渲染 type: map 的单元格
- const renderTableMapCell = (row: any, col: any) => {
- if (col.map[row[col.prop]]) {
- return <ElBadge
- is-dot={true}
- type={col.typeMap[row[col.prop]]}
- dot-position="left-middle"
- >
- {col.map[row[col.prop]]}
- </ElBadge>;
- }
-
- return null;
- }
-
- // 根据自定义表头的配置,动态生成需要的scopedSlot对象
- const genHeaderSlot = (col: any) => {
- if (col.headerSlot) {
- return {
- header: () => {
- return slots.headerSlot && slots.headerSlot();
- }
- };
- }
-
- return {};
- }
- // 自定义表格单元格的slot显示
- const genDefaultSlot = (col: any) => {
- if (col.slot) {
- return {
- // scope 是当前渲染单元格的数据
- default: (scope: any) => {
- return slots[col.slot] && slots[col.slot](scope);
- }
- }
- }
- }
-
- const initTable = () => {
- return <ElTable
- data={tableData.value}
- style={{ width: "100%" }}>
- {
- columns.map((col: any) => {
- const colWidth = col.width || '';
- const minWidth = col.minWidth || '';
- const fixed = col.fixed || false;
- const showOverflowTooltip = col.showOverflowTooltip || false
- const length = col.options && col.options.length || null
- const headerSlot = genHeaderSlot(col); // 自定义表头
- const defaultSlot = genDefaultSlot(col); // 自定义表格单元格
- const tableCellSlot = {
- ...headerSlot,
- ...defaultSlot,
- }
- // 序号
- if (col.prop === 'index') {
- return (<ElTableColumn
- label={col.label}
- type="index"
- className={col.className}
- width={55}
- />);
- }
-
- switch (col.type) {
- // 操作栏
- case 'options':
- return (
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth || (length > 3 ? '180px' : '160px')}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row) => {
- let btns, moreBtn
- if (length > 3) {
- btns = col.options.slice(0, 2)
- moreBtn = col.options.slice(2, length)
- } else {
- btns = col.options
- }
- return length > 3 ? renderOptionsAndMore(btns, moreBtn, row) : renderOptions(btns, row)
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>
- );
- // 枚举类型的表格单元格
- case 'map':
- if (col.typeMap) { // 状态枚举
- return (<ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row): any => {
- return renderTableMapCell(row, col);
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>);
- } else { // 普通枚举
- return (<ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- formatter={(row) => {
- return col.map[row[col.prop]];
- }}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>);
- }
- default:
- return (
- <ElTableColumn
- prop={col.prop}
- label={col.label}
- className={col.className}
- width={colWidth}
- min-width={minWidth}
- show-overflow-tooltip={showOverflowTooltip}
- fixed={fixed}
- >
- {{ ...tableCellSlot }}
- </ElTableColumn>
- );
- }
- })
- }
- </ElTable>
- }
- return () => initTable()
- }
- })
-
-
以上就是常规功能表格的二次封装,下篇文章我会在这个基础上封装一个常规后台页面级别的组件,
欢迎大家私信留言,多多点评
可以扫描下方二维码关注我的公众号或添加我的微信联系、沟通。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。