【通用表格组件】vue3 + element-ui + template 实现通用表格组件

介绍: 这是基于 vue3 + el-table 封装的通用表格组件 的 模板写法,想要参考tsx写法的可以到我另一篇博客喔~

【通用表格组件】vue3 + element-ui + tsx 实现通用表格组件

 这里通用表格,和上一篇通用表单一样的(表格组件都在我博客里),配置完全可控,然后每个el-table-column 都是通过传入的数组来循环便利渲染,大部分常用实现也写在了下面,无法具体实现或需要你自己自定义开发的,都可以通过render来开发。

1、父组件调用方式

    <CommonTable
      show-index
      show-check-box
      key-id="id"
      :loading="loading"
      :max-height="390"
      :table-label="tableHeaderData"
      :row-class-name="tableSortRowClassName"
      :data="tableData"
      :option="tableOptionsData"
      @operation="operationHandler"
      @handle-selection-change="handleSelectionChange"
    />
export const tableHeaderData = [
  {
    label: '部门名称/用户名称',
    prop: 'name',
  },
  {
    label: '部门负责人',
    prop: 'contactPerson',
  },
]

export const tableOptionsData = {
  label: '操作',
  width: '300',
  fixed: 'right',
  children: [
    {
       label: '查看制作详情',
       icon: 'el-icon-view',
       methods: 'view',
       permission: 'xxx',
       render(row) {
         return row.status !== 0
       }
    }
  ]
}

2、组件源码 

<template>
  <div v-loading="loading">
    <el-table
      ref="table"
      v-bind="{ ...props }"
      :data="data"
      style="width: 100%"
      :row-class-name="tabRowClassName"
      @select="handleSelectionChange"
      :row-style="rowStyle"
      @select-all="handleSelectionChange"
      :cell-class-name="cellClassName"
      header-row-class-name="custom-table-header"
      :default-expand-all="expendAll"
      row-key="id"
      :max-height="maxHeight"
    >
      <!-- 单选框 -->
      <el-table-column
        v-if="showCheckBoX"
        width="55"
        type="selection"
        :reserve-selection="keep"
        :class-name="turnRadio ? `checkBoxRadio` : ``"
        align="center"
      ></el-table-column>
      <!-- 序号 -->
      <el-table-column v-if="showTypeIndex" align="center" label="序号" width="50">
        <template #default="{ $index }">{{ $index + 1 }}</template>
      </el-table-column>
      <!-- 表格 -->
      <el-table-column
        v-for="item in tableLabel.filter((item) => item.label)"
        :width="item.width ? item.width : ''"
        :key="item[keyId]"
        :align="!!item.align ? item.align : 'center'"
        :label="item.label"
        :show-overflow-tooltip="overflowText"
        :fixed="item.fixed"
        :prop="item.prop"
      >
        <template #default="{ row }">
          <template v-if="item.Image">
            <div>
              <el-image class="table-img" :src="row.attachment" :preview-src-list="[row.attachment]">
                <template #error>
                  <span>无</span>
                </template>
              </el-image>
            </div>
          </template>
          <template v-else-if="item.render">
            <div style="cursor: pointer" @click="!!item.methods && handleClickon(item.methods, row)" v-html="item.render(row)"></div>
          </template>
          <template v-else-if="item.format">
            <div v-if="typeof item.format === 'function'">{{ item.format(row[item.prop], row, item) }}</div>
          </template>

          <template v-else>
            <div class="text-no-wrap" @click="!!item.methods && handleClickon(item.methods, row)">
              {{ Object.prototype.toString.call(item.prop) == '[object Array]' ? propFilter(item.prop, row) : row[item.prop] }}
            </div>
          </template>
        </template>
      </el-table-column>
      <el-table-column v-if="!!option" :width="option.width" :label="option.label" :fixed="option.fixed" align="center">
        <template #default="scope" v-if="!!option.children">
          <!-- 常规正常情况 -->
              <el-button
                type="text"
                v-for="(item, index) in option.children"
                :key="index"
                v-show="!buttonHidden(item, scope.row)"
                v-bind="{ ...item.props }"
                :disabled="buttonDisabled(item, scope.row)"
                @click="handleTableButton(item.methods, scope.row, index, scope.$index)"
                :class="['btn-' + item.methods, 'btn-right']"
                size="mini"
              >
                <template v-if="item.render">
                  <div v-html="item.render(scope.row)"></div>
                </template>
                {{ !!item.label ? item.label : '' }}
              </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script lang="ts">
  import { defineComponent, computed, PropType, ref, onMounted, nextTick, reactive, watch } from 'vue'
  import { useStore } from 'store/index'
  import { Utils } from '@/utils'
  import { isNumber } from '@/utils/is'

  interface PageInfo {
    pageIndex: number
    totalCount: number
    pageSize: number
    pageArr?: []
    [ket: string]: any
  }

  interface ColumnI {
    label: string
    prop: string
    width?: string | number
  }

  interface TableOption {
    label: string
    methods: string
    props: object
    style: object
    render: Function
  }

  interface TableOptions {
    label: string
    width: string | number
    fixed: boolean
    popoverWidth?: number | string
    children: Array<TableOption>
  }

  interface Props {
    maxHeight: string | number
    stateArr: Array<any>
    tableLabel: Array<ColumnI>
    option: TableOption
    showCheckBoX: boolean
    showTypeIndex: boolean
    turnRadio: boolean
    selectedIdArr: Array<any>
    pageInfo: PageInfo
    showPagination: boolean
    overflowText: boolean
    loading: boolean
    keep: boolean
    keyId: string
    data: Array<any>
    expendAll: boolean
  }

  export default defineComponent({
    name: 'CommonTable',
    props: {
      maxHeight: {
        type: [String, Number],
        default: 600
      },
      stateArr: {
        type: Array,
        default: () => {
          return []
        }
      },
      tableLabel: {
        // 表格展示
        type: Array as PropType<Array<ColumnI>>,
        default: () => {
          return []
        }
      },
      data: {
        // 数据源
        type: Array as PropType<Array<any>>,
        default: () => {
          return []
        }
      },
      option: {
        // 配置需要显示的操作菜单
        type: Object as PropType<TableOption>
      },
      showCheckBoX: {
        // 配置是否显示全选(复选框)
        type: Boolean,
        default: false
      },
      showTypeIndex: {
        type: Boolean,
        default: false
      },
      turnRadio: {
        type: Boolean,
        default: false
      },
      selectedIdArr: {
        type: Array,
        default: []
      },
      pageInfo: {
        // 配置分页
        type: Object,
        default: () => {
          return {
            pageIndex: 1,
            totalCount: 0,
            pageSize: 10,
            pageArr: []
          }
        }
      },
      showPagination: {
        // 是否隐藏 分页显示
        type: Boolean,
        default: true
      },
      overflowText: {
        // 是否 隐藏文字过长
        type: Boolean,
        default: false
      },
      loading: {
        // loading 配置
        type: Boolean,
        default: false
      },
      keep: {
        type: Boolean,
        default: false
      },
      keyId: {
        // 动态绑定 key 值
        type: String,
        default: 'id'
      },
      props: {
        // 表格参数配置
        type: Object,
        default: function () {
          return {
            'show-header': true, // 显示表头e
            'highlight-current-row': false, // 是否要高亮当前行
            'tooltip-effect': 'dark', //
            'max-height': 'auto', // Table 的最大高度。合法的值为数字或者单位为 px 的高度。
            'empty-text': '没有数据', // 空数据显示状态
            'element-loading-text': '加载中', // loading 加载
            'header-cell-style': {
              background: '#F2F4F7',
              color: '#333',
              fontSize: '13px'
            }, // 表头样式
            border: false, // 是否带有纵向边框
            fit: true, // 列的宽度是否自撑开
            stripe: false // 是否显示斑马纹
          }
        }
      },
      rowStyle: {
        type: Object,
        default: () => {
          return {
            height: '40px'
          }
        }
      },
      expendAll: {
        type: Boolean,
        default: false
      },
      /**
         * 行内自定义class
         */
        rowClassName: {
          type: Function,
          default: () => {
            return () => {}
          }
    }
    },
    setup(props: any, { emit }) {
      watch(
        () => props.data,
        () => {
          if (props.showCheckBoX || props.turnRadio) {
            nextTick(() => {
              table.value.clearSelection()
              curPageCheck.value = []
              if (props.showCheckBoX && props.turnRadio) {
                props.data.filter((item: any) => {
                  if (item.id == props.selectedIdArr[0]) {
                    table.value.toggleRowSelection(item, true)
                  }
                })
              } else if (props.showCheckBoX) {
                props.data.filter((item: any) => {
                  if (props.selectedIdArr.includes(item.id)) {
                    table.value.toggleRowSelection(item, true)
                    curPageCheck.value.push(item.id)
                  }
                })
              }
            })
          }
        },
        {
          deep: true,
          immediate: true
        }
      )

      watch(
        () => props.selectedIdArr,
        (val) => {
          if (props.showCheckBoX || props.turnRadio) {
            nextTick(() => {
              table.value.clearSelection()
              curPageCheck.value = []
              if (props.showCheckBoX && props.turnRadio) {
                props.data.filter((item: any) => {
                  if (item.id == val[0]) {
                    table.value.toggleRowSelection(item, true)
                  }
                })
              } else if (props.showCheckBoX) {
                props.data.filter((item: any) => {
                  if (val.includes(item.id)) {
                    table.value.toggleRowSelection(item, true)
                    curPageCheck.value.push(item.id)
                  }
                })
              }
            })
          }
        },
        {
          deep: true
        }
      )
      /**
       * prop 单值 或者 数组过滤(此处为针对时间组,不作为通用处理)
       */
      const propFilter = (prop: [Array<any> | object], row: any) => {
        let res = prop.reduce((total: string, cur: any) => {
          if (row[cur]) {
            return (total += row[cur] + '~')
          } else {
            return ''
          }
        }, '')
        // console.log(res)
        return res ? res.replace(/~$/, '') : ''
      }
      const handleTableButton = (methods: any, row: object, index: number, rowIndex: number) => {
        // 按钮事件
        emit('handleTableButton', { methods, row, index, rowIndex })
      }
      const handleClickon = (methods: any, row: object) => {
        if (typeof methods !== 'string') throw '方法名错误'
        // 数据操作
        emit(methods, { methods, row })
      }
      const curPageCheck = ref<Array<any>>([])
      const handleSelectionChange = (val: Array<any>) => {
        let arr = val.map((item) => parseInt(item.id))
        let compare = Utils.compareArray(curPageCheck.value, arr)
        if (props.showCheckBoX && props.turnRadio) {
          // 选择项大于1时
          if (val.length > 1) {
            let del_row = val.shift()
            table.value.toggleRowSelection(del_row, false)
          }
        }
        // 全选
        if (props.showCheckBoX && props.selectedIdArr) {
          if (props.turnRadio) {
            emit('handleSelectionChange', val)
          } else {
            emit('handleSelectionChange', val, compare)
          }
        } else {
          emit('handleSelectionChange', val)
        }
      }
      const getList = (pages: any) => {
        let { page, limit } = pages
        if (!isNumber(page)) {
          page = page.value
        }
        if (!isNumber(limit)) {
          limit = limit.value
        }
        const pageInfo = {
          page,
          limit
        }
        emit('handleGetList', pageInfo)
      }
      const getRowKeys = (row: any) => {
        return row.id
      }
      const table = ref<any>(null)

      const selectAll = (val: any) => {
        if (props.showCheckBoX && props.turnRadio) {
          // 选择项大于1时
          if (val.length > 1) {
            val.length = 1
          }
        }
        emit('handleSelectionChange', val)
      }

      //斑马纹表格背景色
      const tabRowClassName = ({ row, rowIndex }: any) => {
        let index = rowIndex + 1
        if (index % 2 == 0) {
          return 'even-row'
        } else {
          return 'odd-row'
        }
        return ''
      }

      const cellClassName = ({ row, column, rowIndex, columnIndex }: any) => {
        if (row.confirmTag === 2 && columnIndex < (props as any).tableLabel.length) {
          return 'height_light_cell'
        } else {
          return ''
        }
      }

      const buttonHidden = (item: any, row?: any) => {
        if (typeof item.hidden === 'function') return item.hidden(row) || false
        if (!item.hidden) return item.hidden
      }

      const buttonDisabled = (item: any, row?: any) => {
        if (typeof item.disabled === 'function') return item.disabled(row) || false
        if (!item.disabled) return item.disabled
      }

      const store = useStore()
      const buttonType = computed(() => store.state.app.buttonType)
      const showVertical = ref<boolean>(false)
      /**
       * 单选框选中事件
       */
      const rowClick = (row: any): void => {
        emit('rowClick', row)
      }
      const radioId = ref<number | string>(-1)
      return {
        propFilter,
        handleTableButton,
        handleClickon,
        handleSelectionChange,
        getList,
        getRowKeys,
        tabRowClassName,
        cellClassName,
        buttonHidden,
        buttonDisabled,
        buttonType,
        showVertical,
        table,
        radioId,
        rowClick,
        selectAll
      }
    }
  })
</script>

<style lang="scss" scoped>
  ::v-deep .el-table__header,
  ::v-deep .el-table__body {
    margin: 0;
  }

  .scrollBar {
    @include scrollBar;
  }

  ::v-deep .el-table::before {
    height: 0;
  }

  ::v-deep .el-button {
    padding: 0;
    border: none;
    margin: 0 4px;
    padding: 0 4px 0 8px;
    border-left: 1px solid #e2e2e2;
    font-size: 14px;
    min-height: 14px;

    &:first-child {
      border-left: none;
    }
  }

  ::v-deep .el-button + .el-button {
    margin-left: 0;
  }

  .btn-see {
    color: #39a6ff;
  }

  .btn-handel {
    color: #41a4bd;
  }

  .btn-edit {
    color: #fcb0fb;
  }

  .btn-delete,
  .btn-del {
    color: #fd9090;
  }

  .btn-revoke {
    color: #ffa913;
  }

  ::v-deep .btn-right div {
    margin-right: 5px;
  }

  .btn-right div:empty {
    margin-right: 0px;
  }

  //斑马纹表格背景色
  ::v-deep(.el-table) .even-row {
    --el-table-tr-background-color: #f5fafb;
  }

  ::v-deep(.el-table) .odd-row {
    --el-table-tr-background-color: #ffffff;
  }

  .el-table--border::after,
  .el-table--group::after {
    width: 0;
  }

  ::v-deep .el-table td,
  th.is-leaf {
    border: none;
  }

  ::v-deep .el-table__fixed-right::before,
  .el-table__fixed::before {
    background-color: transparent;
  }

  ::v-deep .custom-table-header {
    th {
      background-color: #62c4ee !important;
      color: #fff !important;
    }
  }

  .progress-line {
    .el-progress-bar__outer {
      height: 16px !important;
    }

    .el-progress-bar__outer,
    .el-progress-bar__inner {
      border-radius: 0 !important;
    }
  }

  .text-no-wrap {
    @include text-no-wrap;
    cursor: pointer;
    display: inline;
  }

  ::v-deep(.el-table) {
    td.el-table__cell div,
    th.el-table__cell > .cell {
      font-size: 14px;
    }

    th.el-table__cell > .cell {
      font-weight: normal;
    }

    .cell {
      padding: 0 10px;
      line-height: 39px;
    }

    .el-table__header-wrapper .checkBoxRadio .el-checkbox {
      display: none;
    }

    .el-checkbox {
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .table-img {
      width: 60px;
      height: 60px;
      object-fit: cover;
      padding: 6px 0;
      display: flex;
      align-items: center;
      margin: 0 auto;
      justify-content: center;
    }
  }

  ::v-deep(.el-table--small .el-table__cell) {
    padding: 0;
  }

  ::v-deep(.el-dropdown-menu__item) {
    padding: 0 !important;
    .el-button {
      width: 100%;
      text-align: center;
      padding: 0 8px;
      margin: 0;
    }
  }
</style>

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vue2中,对于element-ui组件的二次封装,可以按照以下步骤进行: 1. 需求分析:明确需要封装的element-ui组件,以及需要添加的功能和配置项。 2. 创建父组件:编写父组件template和script代码,其中template中调用封装组件,script中定义需要传递给封装组件的props属性。 3. 创建封装组件:编写封装组件template和script代码。在template中使用element-ui组件,并根据需要进行样式和布局的调整。在script中定义props属性,接收父组件传递的值,并监听element-ui组件的事件,触发update事件给父组件。 4. 通过临时变量传递值:由于父组件传递给封装组件的props不能直接作为封装组件的v-model属性传递给element-ui组件,所以需要在封装组件中定义一个临时变量来存储值,并将该变量与element-ui组件进行绑定。 5. 完成打通:在封装组件中监听中间件,接收到element-ui组件的update事件后,再将该事件传递给父组件。 总结来说,Vue2中对于element-ui组件的二次封装,需要创建父组件和封装组件,通过props属性传递值,并在封装组件中监听element-ui组件的事件并触发update事件给父组件。同时,需要使用临时变量来传递值给element-ui组件。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Vue3+ts+element-plus 组件的二次封装-- 页脚分页el-pagination的二次封装](https://blog.csdn.net/cs492934056/article/details/128096257)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值