当前位置:首页 » 《我的小黑屋》 » 正文

Vue3 + Element Plus 封装公共表格组件(带源码)

1 人参与  2024年03月30日 13:25  分类 : 《我的小黑屋》  评论

点击全文阅读


文章目录

1 前言2 功能3 实现步骤3.1 复制基本表格3.2 支持自动获取表格数据3.3 支持数据列配置及插槽3.3.1 自动生成列3.2.2 支持表头自定义及插槽3.2.3 支持单元格自定义及插槽 3.3 支持操作列配置及插槽3.4 支持多选框配置3.5 支持表尾配置及插槽3.6 支持分页显示 4 使用方法5 源码

1 前言

由于项目中有很多菜单都是列表数据的展示,为避免太多重复代码,故将 Element Plus 的 Table 表格进行封装,实现通过配置展示列表数据

2 功能

在这里插入图片描述

支持自动获取表格数据支持数据列配置及插槽支持操作列配置及插槽支持多选框配置支持表尾配置及插槽支持分页显示

3 实现步骤

3.1 复制基本表格

到 Element Plus官网复制一份最简单的 Table 代码,并删除多余代码

<template>  <el-table :data="tableData">    <el-table-column prop="date" label="Date" />    <el-table-column prop="name" label="Name" />    <el-table-column prop="state" label="State" />    <el-table-column prop="city" label="City" />    <el-table-column prop="address" label="Address" />    <el-table-column prop="zip" label="Zip" />    <el-table-column fixed="right" label="Operations">      <template #default>        <el-button link type="primary" size="small" @click="handleClick">Detail</el-button>      </template>    </el-table-column>  </el-table></template><script lang="ts" setup>const tableData = [  {    date: '2016-05-03',    name: 'Tom',    state: 'California',    city: 'Los Angeles',    address: 'No. 189, Grove St, Los Angeles',    zip: 'CA 90036',    tag: 'Home'  },  {    date: '2016-05-02',    name: 'Tom',    state: 'California',    city: 'Los Angeles',    address: 'No. 189, Grove St, Los Angeles',    zip: 'CA 90036',    tag: 'Office'  },  {    date: '2016-05-04',    name: 'Tom',    state: 'California',    city: 'Los Angeles',    address: 'No. 189, Grove St, Los Angeles',    zip: 'CA 90036',    tag: 'Home'  },  {    date: '2016-05-01',    name: 'Tom',    state: 'California',    city: 'Los Angeles',    address: 'No. 189, Grove St, Los Angeles',    zip: 'CA 90036',    tag: 'Office'  }]const handleClick = () => {  console.log('click')}</script>

3.2 支持自动获取表格数据

tableData 数据改为从 props.api 接口获取

在这里插入图片描述

3.3 支持数据列配置及插槽

3.3.1 自动生成列

数据列(除多选框与操作列外)通过 props.columns 自动生成

在这里插入图片描述

<el-table-column  v-for="item in props.columns"  :key="item.prop"  :prop="item.prop"  :label="item.label"  :sortable="item.sortable ? 'custom' : false"  :width="item.width"></el-table-column>
interface TableConfigInterface {  api: string // 表格数据获取接口  columns: {    // 显示列    prop: string // 键名    label?: string // 表头显示名称    formatter?: (row: unknown) => string // 自定义单元格格式化方法,参数为当前行数据    tooltip?: string // 表头 tooltip    sortable?: boolean // 是否可以排序    width?: number | string // 宽度    style?: string // 单元格样式    labelStyle?: string // 表头样式  }[]}
3.2.2 支持表头自定义及插槽
el-table-column 使用 <template #header> 自定义表头<slot :name="item.prop + 'Header'"> 支持插槽labelStyle 支持样式自定义支持 tooltip
<el-table-column  v-for="item in props.columns"  :key="item.prop"  :prop="item.prop"  :label="item.label"  :sortable="item.sortable ? 'custom' : false"  :width="item.width">  <template #header>    <slot :name="item.prop + 'Header'">      <div class="inline-flex" :style="item.labelStyle">        <span>{{ item.label }}</span>        <el-tooltip          popper-class="table-tooltip"          effect="dark"          placement="top-start"          :content="item.tooltip"          v-if="item.tooltip"        >          <el-icon><i-ep-Warning /></el-icon>        </el-tooltip>      </div>    </slot>  </template></el-table-column>
3.2.3 支持单元格自定义及插槽
el-table-column 使用 <template #default="scope"> 自定义单元格<slot :name="item.prop" :row="scope.row"> 支持插槽style 属性支持自定义样式formatter 方法支持自定义显示内容
<el-table-column  v-for="item in props.columns"  :key="item.prop"  :prop="item.prop"  :label="item.label"  :sortable="item.sortable ? 'custom' : false"  :width="item.width">  <template #header>    <slot :name="item.prop + 'Header'">      <div class="inline-flex" :style="item.labelStyle">        <span>{{ item.label }}</span>        <el-tooltip          popper-class="table-tooltip"          effect="dark"          placement="top-start"          :content="item.tooltip"          v-if="item.tooltip"        >          <el-icon><i-ep-Warning /></el-icon>        </el-tooltip>      </div>    </slot>  </template>  <template #default="scope">    <slot :name="item.prop" :row="scope.row">      <div :style="item.style">        <span v-if="item.formatter">{{ item.formatter(scope.row) }}</span>        <span v-else>{{ scope.row[item.prop] }}</span>      </div>    </slot>  </template></el-table-column>

3.3 支持操作列配置及插槽

el-table-column 使用 <template #default="scope"> 自定义操作列<slot :name="item.prop" :row="scope.row"> 支持插槽visible 方法支持自定义按钮显示逻辑
<el-table-column  fixed="right"  label="操作"  :width="props.operation?.width"  v-if="props.operation?.columns">  <template #default="scope">    <slot name="operations" :row="scope.row">      <span v-for="item in props.operation?.columns" :key="item.text || item.icon">        <el-button          v-if="setVisible(scope.row, item.visible)"          :type="item.type"          :link="item.link"          :plain="item.plain"          @click="item.click(scope.row)"          size="small"          class="margin-right: 4px"        >          <el-icon v-if="item.icon" :class="item.icon"></el-icon>          {{ item.text }}        </el-button>      </span>    </slot>  </template></el-table-column>
// 操作框逻辑const setVisible = (row: unknown, visible?: (row: unknown) => boolean) => {  if (!visible || visible(row)) {    return true  }  return false}

3.4 支持多选框配置

<el-table>增加 selection 列

<el-table-column fixed :selectable="setSelectable" type="selection" v-if="showSelectBox" />

TableConfigInterface 增加 rowKey、selectable

interface TableConfigInterface {  // ......  rowKey?: string // 行数据的 Key  selectable?: boolean | ((row: unknown) => boolean) // 当前行多选框是否可以勾选,参数为当前行数据,默认为 false} const props = withDefaults(defineProps<TableConfigInterface>(), {  rowKey: 'id',})// 多选框逻辑const disabledList = reactive<string[]>([]) // 禁止勾选的数据const showSelectBox = computed(() => props.selectable && disabledList.length < tableData.length)const setSelectable = (row: unknown) => {  const selectable =    typeof props.selectable === 'boolean' ? props.selectable : props.selectable?.(row)  if (!selectable && !disabledList.includes(row?.[props.rowKey])) {    disabledList.push(row?.[props.rowKey])  }  return selectable}

3.5 支持表尾配置及插槽

<el-table>增加 @selection-change 及 ref 配置<slot name="footer"> 支持插槽visible 方法支持自定义按钮显示逻辑
<el-table  v-loading="loading"  :data="tableData"  @selection-change="handleSelectionChange"  table-layout="auto"  ref="tableRef">  <!-- ...... --></el-table><div v-if="showSelectBox" class="p-14">  <el-checkbox    v-model="isSelected"    @click="tableRef?.toggleAllSelection()"    :indeterminate="indeterminate"    label="全选"    style="vertical-align: middle; margin-right: 10px"  />  <slot name="footer" :rows="selectionRows">    <span v-for="item in props.footer?.operations" :key="item.text || item.icon">      <el-button        v-if="item.visible ? item.visible() : true"        :type="item.type || 'primary'"        :link="item.link"        :plain="item.plain"        :disabled="!selectionRows.length"        @click="item.click(selectionRows)"        style="margin-left: 10px"      >        <el-icon v-if="item.icon" :class="item.icon"></el-icon>        {{ item.text }}      </el-button>    </span>  </slot></div>
const tableRef = ref()const isSelected = ref(false) // 是否有选中数据const selectionRows = ref<unknown[]>([]) // 当前选中的数据const handleSelectionChange = (rows: unknown[]) => {  selectionRows.value = rows  isSelected.value = rows.length > 0}const indeterminate = computed(  () =>    selectionRows.value.length > 0 &&    selectionRows.value.length < tableData.length - disabledList.length)

3.6 支持分页显示

最底部增加<el-pagination></el-pagination>

<el-pagination  background  :total="tableData.length"  :layout="props.layout"  v-model:current-page="pagination.currentPage"  v-model:page-size="pagination.pageSize"  @current-change="getTableData"  @size-change="getTableData"  class="p-y-20"></el-pagination>
interface TableConfigInterface {  // ......  layout?: string // 组件布局} const pagination = ref({  currentPage: 1,  pageSize: 10})

4 使用方法

<template>  <TableComponent v-bind="tableConfig">    <template #nameHeader>      <div>姓名</div>    </template>    <template #name>      <div>Yana</div>    </template>  </TableComponent></template><script setup lang="ts">const tableConfig: TableConfigInterface = {  api: 'getTableData',  columns: [    {      prop: 'date',      label: 'Date',      tooltip: 'This is Date'    },    {      prop: 'name',      label: 'Name'    },    {      prop: 'state',      label: 'State'    },    {      prop: 'city',      label: 'City'    },    {      prop: 'address',      label: 'Address'    },    {      prop: 'zip',      label: 'Zip',      style: 'color: red',      labelStyle: 'color: red',      sortable: true    }  ],  operation: {    columns: [      {        text: '编辑',        click: () => {},        visible: (row) => row.date === '2016-05-07'      }    ]  },  rowKey: 'date',  selectable: (row) => row.date === '2016-05-07',  footer: {    operations: [      {        text: '删除',        click: () => {}      }    ]  }}</script>

5 源码

<template>  <div v-loading="loading" class="table-wrapper">    <el-table      :data="tableData"      :max-height="props.maxHeight"      :default-sort="props.defaultSort"      @selection-change="handleSelectionChange"      @sort-change="handleSortChange"      @row-click="goDetail"      :row-style="{ cursor: 'pointer' }"      table-layout="auto"      ref="tableRef"    >      <el-table-column fixed :selectable="setSelectable" type="selection" v-if="showSelectBox" />      <el-table-column        v-for="item in props.columns"        :key="item.prop"        :prop="item.prop"        :label="item.label"        :sortable="item.sortable ? 'custom' : false"        :width="item.width"      >        <template #header>          <slot name="header">            <div class="inline-flex" :style="item.labelStyle">              <span>{{ item.label }}</span>              <el-tooltip                popper-class="table-tooltip"                effect="dark"                placement="top-start"                :content="item.tooltip"                v-if="item.tooltip"              >                <el-icon><i-ep-Warning /></el-icon>              </el-tooltip>            </div>          </slot>        </template>        <template #default="scope">          <slot :name="item.prop" :row="scope.row">            <div :style="item.style">              <span v-if="item.formatter">{{ item.formatter(scope.row) }}</span>              <span v-else>{{ scope.row[item.prop] }}</span>            </div>          </slot>        </template>      </el-table-column>      <el-table-column        fixed="right"        label="操作"        :width="props.operation?.width"        v-if="props.operation?.columns"      >        <template #default="scope">          <slot name="operations" :row="scope.row">            <span v-for="item in props.operation?.columns" :key="item.text || item.icon">              <el-button                v-if="setVisible(scope.row, item.visible)"                :type="item.type"                :link="item.link"                :plain="item.plain"                @click="item.click(scope.row)"                size="small"                style="margin-right: 4px"              >                <el-icon v-if="item.icon" :class="item.icon"></el-icon>                {{ item.text }}              </el-button>            </span>          </slot>        </template>      </el-table-column>    </el-table>    <div v-if="showSelectBox" class="p-14">      <el-checkbox        v-model="isSelected"        @click="tableRef?.toggleAllSelection()"        :indeterminate="indeterminate"        label="全选"        style="vertical-align: middle; margin-right: 10px"      />      <slot name="footer" :rows="selectionRows">        <span v-for="item in props.footer?.operations" :key="item.text || item.icon">          <el-button            v-if="item.visible ? item.visible() : true"            :type="item.type || 'primary'"            :link="item.link"            :plain="item.plain"            :disabled="!selectionRows.length"            @click="item.click(selectionRows)"            style="margin-left: 10px"          >            <el-icon v-if="item.icon" :class="item.icon"></el-icon>            {{ item.text }}          </el-button>        </span>      </slot>    </div>  </div>  <el-pagination    background    :total="tableData.length"    :layout="props.layout"    v-model:current-page="pagination.currentPage"    v-model:page-size="pagination.pageSize"    @current-change="getTableData"    @size-change="getTableData"    class="p-y-20"  /></template><script lang="ts" setup>interface OperationInterface {  click: (row: unknown) => void // 按钮点击方法,参数为当前行数据  text?: string // 按钮显示文字  icon?: string // 按钮 icon  visible?: (row?: unknown) => boolean // 设置按钮是否可见,参数为当前行数据,默认为 true  type?: string // 按钮类型['primary'| 'success'| 'warning'| 'danger'| 'info']  link?: boolean // 是否为链接按钮  plain?: boolean // 是否为朴素按钮}interface TableConfigInterface {  api: string // 表格数据获取接口  rowKey?: string // 行数据的 Key  columns: {    // 显示列    prop: string // 键名    label?: string // 表头显示名称    formatter?: (row: unknown) => string // 自定义单元格格式化方法,参数为当前行数据    tooltip?: string // 表头 tooltip    sortable?: boolean // 是否可以排序    width?: number | string // 宽度    style?: string // 单元格样式    labelStyle?: string // 表头样式  }[]  selectable?: boolean | ((row: unknown) => boolean) // 当前行多选框是否可以勾选,参数为当前行数据,默认为 false  operation?: {    // 操作列    columns: OperationInterface[]    width?: number | string // 宽度  }  footer?: {    // 操作列    operations: OperationInterface[]  }  defaultSort?: {    // 默认排序    prop: string // 默认排序的列    order?: string // ['ascending'| 'descending'], 没有指定 order, 则默认顺序是 ascending  }  maxHeight?: number | string // 表格最大高度  layout?: string}const props = withDefaults(defineProps<TableConfigInterface>(), {  rowKey: 'id',  layout: 'prev, pager, next, total'})const pagination = ref({  currentPage: 1,  pageSize: 10})const tableRef = ref()let tableData = reactive<unknown[]>([])// 多选框逻辑const isSelected = ref(false) // 是否有选中数据const selectionRows = ref<unknown[]>([]) // 当前选中的数据const handleSelectionChange = (rows: unknown[]) => {  selectionRows.value = rows  isSelected.value = rows.length > 0}const disabledList = reactive<string[]>([]) // 禁止勾选的数据const setSelectable = (row: unknown) => {  const selectable =    typeof props.selectable === 'boolean' ? props.selectable : props.selectable?.(row)  if (!selectable && !disabledList.includes(row?.[props.rowKey])) {    disabledList.push(row?.[props.rowKey])  }  return selectable}const indeterminate = computed(  () =>    selectionRows.value.length > 0 &&    selectionRows.value.length < tableData.length - disabledList.length)const showSelectBox = computed(() => props.selectable && disabledList.length < tableData.length)// 操作框逻辑const showOperation = ref(false)const setVisible = (row: unknown, visible?: (row: unknown) => boolean) => {  if (!visible || visible(row)) {    showOperation.value = true    return true  }  return false}// 排序const handleSortChange = (data: { prop: string; order: string | null }) => {  const { prop, order } = data  console.log(prop, order)  // getTableData}// 跳转详情页const goDetail = (row: unknown) => {  console.log(row)}// 发送接口const loading = ref(true)const getTableData = () => {  loading.value = true  showOperation.value = false  tableData = [    {      date: '2016-05-02',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-03',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-04',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-05',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-06',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-07',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-08',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-09',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-10',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    },    {      date: '2016-05-11',      name: 'Tom',      state: 'California',      city: 'Los Angeles',      address: 'No. 189, Grove St, Los Angeles',      zip: 'CA 90036'    }  ]  loading.value = false}getTableData()</script><style lang="scss" scoped>.table-wrapper {  border-top: 1px solid #eaeaea;  border-left: 1px solid #eaeaea;  border-right: 1px solid #eaeaea;}.inline-flex {  display: inline-flex;  align-items: center;}.p-14 {  border-bottom: 1px solid #eaeaea;  padding: 14px;}.p-y-20 {  padding-top: 20px;  padding-bottom: 20px;  justify-content: center;}</style><style lang="scss">.table-tooltip {  max-width: 220px;}</style>

点击全文阅读


本文链接:http://zhangshiyu.com/post/88104.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1