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

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

TS + vue3.2 + vite2 + element-plus 通用表格组件封装

话不多说,本组件分为四部分:

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

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

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

export default function(props, emit, CommonTable) {
  const curPageCheck = ref([])
  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) {
      emit('rowClick', row)
    }
  }
  return {
    methods
  }
}

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

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

export default defineComponent({
  name: 'CommonTable',
  props: {
    /**
     * 表格最高高度
     */
    maxHeight: {
      type: [String, Number],
      default: 'auto'
    },
    /**
     * 表格自定义属性展示
     */
    tableLabel: {
      type: Array as PropType<TableLabel[]>,
      default: () => []
    },
    /**
     * 表格数据源
     */
    data: {
      type: Array as PropType<TableDataItem[]>,
      default: () => []
    },
    /**
     * 配置需要显示的操作菜单
     */
    option: {
      type: Object,
      default: () => {}
    },
    showCheckBox: {
      // 配置是否显示全选(复选框)
      type: Boolean,
      default: false
    },
    /**
     * 是否显示索引
     */
    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
    }
  },
  setup(props, { emit, slots }) {
    const CommonTable = ref(null)
    const {
      methods
    } = useModule(props, emit, CommonTable)
    return () => (
      <div v-loading={props.loading}>
      <el-table
        class='common-table'
        ref={CommonTable}
        data={props.data}
        border
        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}
      >
        {
          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 <el-tooltip
                        class='item'
                        effect='light'
                        popper-class='yxp-tooltip-primary'
                        content={item.label}
                        placement='top'
                      >
                        <i
                          class={['yxp-tooltip-icon', item.icon]}
                          plain='true'
                          onClick={() => methods.handleTableButton(scope.row, item.methods)}
                        />
                      </el-tooltip>
                    })
                  }
                </div>
              }
            }}
            >
          </el-table-column>
        }
      </el-table>
    </div>
    )
  }
})

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

/** 表格行数据类型 */
export interface TableDataItem {
  xxx: number;
}

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

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

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

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

/** 表格操作栏子选项类型 */
export interface OptionChild {
  label: string; // 标题
  icon: string; // icon图标
  method: string; // 执行方法
  permission: string | string[]; // 权限
}

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}
            />

 属性及方法使用说明:

/** 表格头部配置  */
const tableHeaderData = [
  {
    label: '文件大小',
    prop: 'size',
    width: '100',
    render(row) {
      return xxx
    }
  }
]

/** 表格操作栏配置  */
const tableOptionsData = {
  label: '操作',
  width: '150',
  fixed: 'right',
  children: [
    {
      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
    }
}

有不懂的可以底下评论噢~

猜你喜欢

转载自blog.csdn.net/qq_39404437/article/details/129199954
今日推荐