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

本文介绍了如何使用Vue3和ElementUI结合tsx编写一个通用表格组件,包括组件的样式文件、逻辑文件和渲染文件的详细内容,以及如何在项目中调用和使用该组件。组件支持全选、单选、复选、操作列等功能,并提供了自定义样式和扩展方法。
摘要由CSDN通过智能技术生成

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

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

话不多说,本组件分为以下四部分,并且在同一层级:

1、CommonTable.module.scss 文件为组件样式文件

:global {
  .common-table {

    .el-table__header,
    .el-table__body {
      margin: 0;
    }

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

    .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;
      }
    }

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

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

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

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

    .el-table .odd-row {
      --el-table-tr-background-color: #ffffff;
    }

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

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

    .custom-table-header {
      th {
        background-color: #fff4d9 !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 {
      cursor: pointer;
      display: inline;
    }

    .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;
      }
    }

    .el-table--small .el-table__cell {
      padding: 0;
    }

    .el-dropdown-menu__item {
      padding: 5px 10px !important;

      .el-button {
        width: 100%;
        text-align: center;
        padding: 0 8px;
        margin: 0;
      }
    }

    .flex-box {
      display: flex;
      flex-flow: row nowrap;
      justify-content: flex-start;

      .item {
        margin: 0 10px;
      }
    }
  }
    .el-table{
      .el-table__expand-icon{
        .el-icon-arrow-right{
          display: none;
        }
      }
    }
}

2、CommonTable.module.ts为组件逻辑文件

import {
  ref,
  watch,
  nextTick
} from 'vue';

export default function(props, emit, commonTable) {
  const curPageCheck = ref([])
  const selectId = ref()

  watch(() => props.selectRadioId, () => {
    selectId.value = props.selectRadioId
  }, { immediate: true })

  watch(() => props.data, () => {
    if (props.showCheckBox || props.turnRadio) {
      nextTick(() => {
        commonTable.value.clearSelection()
        curPageCheck.value = []
        if (props.showCheckBox && props.turnRadio) {
          props.data.filter((item) => {
            if (item.id === props.selectedIdArr[0]) {
              commonTable.value.toggleRowSelection(item, true)
            }
          })
        } else if (props.showCheckBox) {
          props.data.filter((item) => {
            if (props.selectedIdArr.includes(item.id)) {
              commonTable.value.toggleRowSelection(item, true)
              curPageCheck.value.push(item.id)
            }
          })
        }
      })
    }
  }, {
    immediate: true
  })
  watch(() => props.selectedIdArr, (val) => {
    if (props.showCheckBox || props.turnRadio) {
      nextTick(() => {
        commonTable.value.clearSelection()
        curPageCheck.value = []
        if (props.showCheckBox && props.turnRadio) {
          props.data.filter((item) => {
            if (item.id === val[0]) {
              commonTable.value.toggleRowSelection(item, true)
            }
          })
        } else if (props.showCheckBox) {
          props.data.filter((item) => {
            if (val.includes(item.id)) {
              commonTable.value.toggleRowSelection(item, true)
              curPageCheck.value.push(item.id)
            }
          })
        }
      })
    }
  }, {
    immediate: true
  })
  const methods = {
    /**
     * prop 单值 或者 数组过滤(此处为针对时间组,不作为通用处理)
     */
    propFilter(prop, row) {
      const res = prop.reduce((total, cur) => {
        if (row[cur]) {
          return (total += row[cur] + '~')
        } else {
          return ''
        }
      }, '')
      return res ? res.replace(/~$/, '') : ''
    },
    handleTableButton(row, type) {
      emit('operation', row, type);
    },
    /**
     * 后续扩展位
     * @param {*} methods
     * @param {*} row
     */
    handleClickon(methods, row) {
      emit(methods, { methods, row })
    },
    handleSelectionChange(val) {
      if (props.showCheckBox && props.turnRadio) {
        // 选择项大于1时
        if (val.length > 1) {
          const del_row = val.shift()
          commonTable.value.toggleRowSelection(del_row, false)
        }
      }
      // 全选
      if (props.showCheckBox && props.selectedIdArr) {
        if (props.turnRadio) {
          emit('handle-selection-change', val)
        } else {
          // 一般复选框都是走到这一步
          emit('handle-selection-change', val)
        }
      } else {
        emit('handle-selection-change', val)
      }
    },
    getRowKeys(row) {
      return row.id
    },
    selectAll(val) {
      if (props.showCheckBox && props.turnRadio) {
        // 选择项大于1时
        if (val.length > 1) {
          val.length = 1
        }
      }
      emit('handle-selection-change', val)
    },
    // 斑马纹表格背景色
    tabRowClassName({ rowIndex }) {
      const index = rowIndex + 1
      if (index % 2 === 0) {
        return 'even-row'
      } else {
        return 'odd-row'
      }
    },
    cellClassName({ row, columnIndex }) {
      if (row.confirmTag === 2 && columnIndex < props.tableLabel.length) {
        return 'height_light_cell'
      } else {
        return ''
      }
    },
    buttonDisabled(item, row) {
      if (typeof item.disabled === 'function') return item.disabled(row) || false
      if (!item.disabled) return item.disabled
    },
    /**
     * 单选框选中事件
     */
    rowClick(row) {
      if (row) {
        selectId.value = row.id;
        emit('rowClick', row)
      }
    }
  };
  return {
    selectId,
    methods
  }
}

3、CommonTable.tsx为组件渲染文件

import {
  defineComponent,
  ref,
  PropType
} from 'vue';
import {
  OptionLabel,
  TableLabel
} from './types';
import useModule from './commonTable.module';
import './commonTable.module.scss';

export default defineComponent({
  name: 'CommonTable',
  props: {
    /**
     * 表格最高高度
     */
    maxHeight: {
      type: [String, Number],
      default: ''
    },
    /**
     * 表格自定义属性展示
     */
    tableLabel: {
      type: Array as PropType<TableLabel[]>,
      default: () => []
    },
    /**
     * 表格数据源
     */
    data: {
      type: Array,
      default: () => []
    },
    /**
     * 配置需要显示的操作菜单
     */
    option: {
      type: Object as PropType<OptionLabel>,
      default: () => {}
    },
    showCheckBox: {
      // 配置是否显示全选(复选框)
      type: Boolean,
      default: false
    },
    /** 是否复选框禁用 */
    isEnable: {
      type: Boolean,
      default: false
    },
    /** 是否复选框禁用的方法 */
    enableCheckBox: {
      type: Function as PropType<(row: any) => boolean>,
      default: null
    },
    /**
     * 是否显示索引
     */
    showIndex: {
      type: Boolean,
      default: false
    },
    turnRadio: {
      type: Boolean,
      default: false
    },
    selectedIdArr: {
      type: Array as PropType<number[] | string[]>,
      default: () => []
    },
    /**
     * 是否 隐藏文字过长
     */
    overflowText: {
      type: Boolean,
      default: false
    },
    /**
     * 加载提示
     */
    loading: {
      type: Boolean,
      default: false
    },
    /**
     * 是否保持之前复选框的数据
     */
    keep: {
      type: Boolean,
      default: false
    },
    /**
     * 动态绑定 key 值
     */
    keyId: {
      type: String,
      default: 'id'
    },
    /**
     * 行内自定义样式配置
     */
    rowStyle: {
      type: Object,
      default: () => {
        return {
          height: '40px'
        }
      }
    },
    /**
     * 是否展示展开按钮
     */
    showExpand: {
      type: Boolean,
      default: false
    },
    /**
     * 单选模式
     */
    showRadio: {
      type: Boolean,
      default: false
    },
    selectRadioId: {
      type: String,
      default: ''
    }
  },
  setup(props, { emit, slots }) {
    const commonTable = ref(null)
    const {
      selectId,
      methods
    } = useModule(props, emit, commonTable)
    return () => (
      <div v-loading={props.loading}>
        <el-table
          class='common-table'
          ref={commonTable}
          data={props.data}
          border
          highlight-current-row={props.showRadio}
          max-height={props.maxHeight}
          row-class-name={methods.tabRowClassName}
          row-style={props.rowStyle}
          cell-class-name={methods.cellClassName}
          header-row-class-name='custom-table-header'
          row-key={props.keyId}
          on-select={methods.handleSelectionChange}
          on-select-all={methods.handleSelectionChange}
          on-current-change={methods.rowClick}
        >
          {
            props.showRadio && <el-table-column
              label='选择'
              align='center'
              width='55'
              scopedSlots={{
                default: scope => (
                  <el-radio
                    label={scope.row.url}
                    vModel={selectId.value}
                    onChange={() => methods.rowClick(scope.row)}
                  >
                    &nbsp;
                  </el-radio>
                )
              }}
            />
          }
          { props.isEnable ? (props.showCheckBox && <el-table-column
              key='showCheckBox'
              width='55'
              type='selection'
              reserve-selection={props.keep}
              selectable={props.enableCheckBox}
              class-name={props.turnRadio ? 'checkBoxRadio' : ''}
              align='center'
            />) : (props.showCheckBox && <el-table-column
            key='showCheckBox'
            width='55'
            type='selection'
            reserve-selection={props.keep}
            class-name={props.turnRadio ? 'checkBoxRadio' : ''}
            align='center'
          />)
          }
          {
            props.showExpand && <el-table-column
              key='showExpand'
              type='expand'
              scopedSlots={{
                default: scope => {
                  return <fragment row={scope.row}>
                    {
                      slots.expand?.()
                    }
                  </fragment>
                }
              }}
            >
            </el-table-column>
          }
          {
            props.showIndex && <el-table-column
              align='center'
              label='序号'
              width='50'
              scopedSlots={{
                default: scope => {
                  return scope.$index + 1
                }
              }}
            >
            </el-table-column>
          }
          {
            props.tableLabel.map((item: TableLabel) => {
              return <el-table-column
                key={item[props.keyId]}
                width={item.width ?? ''}
                align={item.align ?? 'center'}
                label={item.label}
                show-overflow-tooltip={props.overflowText}
                fixed={item.fixed}
                prop={item.prop}
                scopedSlots={{
                  default: (scope) => {
                    if (item.render) {
                      return <div
                        style='cursor: pointer'
                        onClick={() => item.methods && methods.handleClickon(item.methods, scope.row)}
                        domPropsInnerHTML={item.render(scope.row)}
                      >
                      </div>
                    } else {
                      return <div
                        class='text-no-wrap'
                        onClick={() => item.methods && methods.handleClickon(item.methods, scope.row)}>
                        {
                          Object.prototype.toString.call(item.prop) === '[object Array]' ? methods.propFilter(item.prop, scope.row) : (scope.row[item.prop] ?? '--')
                        }
                    </div>
                    }
                  }
                }}
              >
              </el-table-column>
            })
          }
          {
            props.option && <el-table-column
              width={props.option.width}
              label={props.option.label}
              fixed={props.option.fixed}
              align={props.option.align ?? 'center'}
              scopedSlots={{
                default: scope => {
                  return props.option.children.length && <div
                    class='flex-box'
                  >
                    {
                      props.option.children.map(item => {
                        return (typeof item.hidden === 'function' ? !item.hidden(scope.row) : !item.hidden) && (
                          <el-tooltip
                            class='item'
                            effect='light'
                            popper-class='common-tooltip-primary'
                            content={typeof item.label === 'function' ? item.label(scope.row) : item.label}
                            placement='top'
                          >
                            <i
                              class={['common-tooltip-icon', typeof item.icon === 'function' ? item.icon(scope.row) : item.icon]}
                              plain='true'
                              v-permission={item.permission ?? ''}
                              onClick={() => methods.handleTableButton(scope.row, item.methods)}
                            />
                          </el-tooltip>
                        )
                      })
                    }
                  </div>
                }
              }}
              >
            </el-table-column>
          }
        </el-table>
      </div>
    )
  }
})

4、types.ts为类型定义文件

/** 表格基础类型配置  */
interface BasicLabel {
  label: string; // 标题
  width?: number | string; // 宽度
  fixed?: string; // 固定位置
  align?: string; // 行排列
}

/** 表格顶部类型配置  */
export interface TableLabel extends BasicLabel {
  prop: string;
  methods?: string;
  render?: render;
}

/** 表格顶部自定义函数类型 */
interface render {
  (row: any): string | any
}

/** 表格操作栏类型 */
export interface OptionLabel extends BasicLabel {
  children: OptionChild[]
}

/** 表格操作栏子选项类型 */
export interface OptionChild {
  label: string | ((row: any) => string); // 标题
  icon: string | ((row: any) => string); // icon图标
  methods: string; // 执行方法
  permission?: string | string[] | object; // 权限
  hidden?: boolean | ((row: any) => boolean);
}

5、组件调用方式 

组件调用:

        <CommonTable
              show-index
              show-check-box={true}
              loading={loading.value}
              max-height={550}
              table-label={tableHeaderData}
              data={tableData.value}
              option={tableOptionsData}
              on-operation={methods.operationHandler}
              on-handle-selection-change={methods.handleSelectionChange}
            />

 属性及方法使用说明:

注意:如果你在使用Sortable插件想要拖动排序表格时,tableOptionsData 下的fixed参数请不要写,不然会导致无法拖动!

/** 表格头部配置  */
const tableHeaderData = [
  {
    label: '文件大小',
    prop: 'size',
    width: '100',
    methods: 'fileEntry'
    render(row: TableDataItem) {
      return `<span>${row.size}</span>`
    }
  }
]

/** 表格操作栏配置  */
const tableOptionsData = {
  label: '操作',
  width: '150',
  fixed: 'right',
  children: [
    {
      hidden: false || (row: TableDataItem) { // 可为boolean 或者 函数
        return !row.status
      },
      label: '操作记录',
      icon: 'xxx',
      methods: 'record',
      permission: ''
    }
  ]
}

const methods = {
    /**
     * 操作栏分发逻辑
     * @param row 当行数据
     * @param type 分发函数名
     */
    operationHandler(row: TableDataItem, type: string) {
      if (type === 'record') { // 操作记录
      }
    },
    /**
     * 复选框处理回调
     * @param val 复选框选中的数据
     */
    handleSelectionChange(val: TableDataItem[]) {
      multipleSelection.value = val
    }
}

---觉得好用且实用的,那就点赞收藏吧---

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值