ant-design-vue Table

封装表格主要功能:
1、表格加载(源数据支持数组和接口方法传递)
2、表格分页
3、表格伸缩列
4、支持单击选中行
5、表格支持列显示和隐藏(同时也可以查看AVue,具有相同的功能,AVue 组件已经封装,可直接使用;此处仅供需要情景使用)

第一步

安装支持vue的可拖动控件

npm install vue-draggable-resizable -S
第二步

利用ant和vue-draggable-resizeable封装自己的表格
Table.js如下

import { Table } from 'ant-design-vue'
import Vue from 'vue'
// 引入vue-draggable-resizable,用于表格列伸缩
import VueDraggableResizable from 'vue-draggable-resizable'
// TableOption用于表格列显示或隐藏
import TableOption from './TableOption'

// 注册组件
Vue.component('vue-draggable-resizable', VueDraggableResizable)
Vue.component('table-option', TableOption)

const componentName = 'drag-table'

const DragTable = {
 name: componentName,
 props: Object.assign({}, Table.props, {
   // 返回 Promise<{ currPage, totalCount, list: any[] }> 的获取数据的函数,用于内部管理数据加载
   data: { type: Function },
   // 是否开启:单击行则选中行
   selectOnClick: { type: Boolean, default: true },
   // 默认翻到第 1 页
   pageNum: { type: Number, default: 1 },
   // 默认分页大小 10 行
   pageSize: { type: Number, default: 10 },
   // 是否显示分页大小切换下拉框
   showSizeChanger: { type: Boolean, default: true },
   // 是否显示分页器
   showPagination: { type: [String, Boolean], default: 'auto' },
   // 指定表格当前页数的url 例如:/users/2
   pageURI: { type: Boolean, default: false },
   // 是否展示序号列
   showIndex: { type: Boolean, default: true },
   customCell: { type: Function }
 }),
 data() {
   return {
     localLoading: false, // 加载标识
     localDataSource: [], // 表格源数据
     localPagination: Object.assign({}, this.pagination), // 分页对象,合并ant默认分页数据
     localScroll: {},
     // 表格列显隐
     filterValue: [],
     originColumns: []
   }
 },
 computed: {
   localKeys() {
     return [...Object.keys(this.$data), ...Object.keys(this._computedWatchers), ...Object.keys(this).filter(k => k.startsWith('local'))]
   },
   // 处理最大显示长度后的列
   localColumns(){
     return this.originColumns.filter(col => !this.filterValue.includes(col.dataIndex || col.key || col.title))
   },
   // 表格伸缩列(该属性是ant表格中覆盖默认的 table 元素的属性components)
   localComponents(){
     const headerComponent = {}
     headerComponent.header ={}
     headerComponent.header.cell = (h, props, children) => {
       const { key, ...restProps } = props
       const col = this.columns.find(col => {
         const k = col.dataIndex || col.key
         return k === key
       })
       if (!col) {
         return h('th', { ...restProps }, [...children])
       }
       const dragProps = {
         key: col.dataIndex || col.key,
         class: 'table-draggable-handle',
         attrs: {
           w: 8,
           x: parseFloat(col.width),
           z: 1,
           axis: 'x',
           draggable: true,
           resizable: false,
           onDragStart: (e) => {
             e.stopPropagation()
           }
         },
         on: {
           dragging: (x) => {
             col.width = Math.max(x, 35)
             this.computeWidth()
           }
         }
       }
       const drag = h('vue-draggable-resizable', { ...dragProps })
       return <th {...restProps} title={col.title} width={col.width} class="resize-table-th">
         {children}
         { drag }
       </th>
     }
     return headerComponent
   }
 },
 watch: {
   loading(val) {
     this.localLoading = val
   },
   // 表格源数据
   dataSource: {
     handler(val) {
       this.localDataSource = val
     },
     immediate: true
   },
   'localPagination.current'(val) {
     this.pageURI && this.$router.push({
       ...this.$route,
       params: Object.assign({}, this.$route.params, { pageNo: val })
     })
   },
   pageNum(val) {
     Object.assign(this.localPagination, { current: val })
   },
   pageSize(val) {
     Object.assign(this.localPagination, { pageSize: val })
   },
   showSizeChanger(val) {
     Object.assign(this.localPagination, { showSizeChanger: val })
   },
   scroll() {
     this.calcLocalScroll()
   },
   columns: {
     handler(val) {
       const data = []
       // 表格添加序号列
       if (this.showIndex) {
         data.push({
           title: '序号',
           dataIndex: 'sort',
           width: 50,
           customRender: 
             (text, record, index) => record.total 
               ? record.total 
               :  `${(this.localPagination.current - 1) * (this.localPagination.pageSize) + (index + 1) || (index + 1)}`
         })
       }
       this.originColumns = data.concat(val)
       // 超出后显示省略号 不支持操作列、和排序一并使用
       this.originColumns.forEach((col)=>{
         if(col.dataIndex || col.key) {
           col.ellipsis = true
         }
       })
     },
     immediate: true
   }
 },
 created() {
   // 判断格是传进数据源还是远程数据接口方法
   if (this.data) {
     // 合并分页数据
     const { pageNo } = this.$route.params
     const localPageNum = this.pageURI ? (pageNo && parseInt(pageNo)) : this.pageNum
     this.localPagination = ['auto', true].includes(this.showPagination)
       ? Object.assign({}, this.localPagination, {
         showQuickJumper: true,
         current: localPageNum,
         pageSize: this.pageSize,
         showSizeChanger: this.showSizeChanger,
         pageSizeOptions: ['10', '20', '40', '80', '120']
       })
       : false
     // 调用接口获得数据
     this.loadData()
   } else {
     // 源数据传入不支持分页
     this.localPagination = false
   }
   window.addEventListener('resize', this.calcLocalScroll)
 },
 mounted() {
   setTimeout(() => {
     this.calcLocalScroll()
     this.resetColumns()
   })
 },
 destroyed() {
   window.removeEventListener('resize', this.calcLocalScroll)
 },
 methods: {
   /**
    * 表格限制最大宽度/高度计算,用于滾动显示
    */
   calcLocalScroll() {
     const localScroll = { ...(this.scroll || {}) }
     // 根据自己的页面计算除表格外其他组件占据的高度,从而得出表格最大高度,也可自适应显示表格
     const extraDis = (this.$store.getters.multiTab ? 40 : 0 ) + 56 + 104 + (this.pagination ? 6 : 0) + (this.$scopedSlots.footer ? 33 : 0)
     localScroll.x = localScroll.x || this.$el.offsetWidth - 20
     localScroll.y = localScroll.y || document.body.clientHeight - ((this.$el || {}).offsetTop || 128) - extraDis 
     this.localScroll = localScroll
     // 计算表格列宽度
     this.computeWidth()
   },
   /**
    * 表格重新加载方法
    *@param {object} option 对象属性: {boolean} isBackToFirstPage 如果参数为 true, 则强制刷新到第一页
    *                        对象属性: {boolean} isResetOption 如果参数为 true, 则重置显隐配置项
    *                        对象属性: {boolean} layoutTag 如果参数为 true, 则重新计算表格限制最大宽度/高度
    */
   refresh({ isBackToFirstPage = false, isResetOption = false, layoutTag = false } = { isBackToFirstPage: false, isResetOption: false, layoutTag: false }) {
     if(layoutTag) {
       this.calcLocalScroll()
     } else {
       isResetOption && this.resetColumns()
       isBackToFirstPage && (this.localPagination = Object.assign({}, {
         current: 1,
         pageSize: this.pageSize
       }))
       this.loadData()
     }
   },
   /**
    * 加载数据方法
    * @param {{ page: number, limit: number }} pagination 分页选项器
    * @param {{ [field: string]: string }} filters 过滤条件
    * @param {{ field: string, order: 'asc' | 'desc' }} sorter 排序条件
    */
   loadData(pagination, filters, sorter = {}) {
     this.localLoading = true

     const result = this.data({
       page: (pagination && pagination.current) ||
         this.showPagination && this.localPagination.current || this.pageNum,
       limit: (pagination && pagination.pageSize) ||
         this.showPagination && this.localPagination.pageSize || this.pageSize,

       sidx: sorter.field,
       order: sorter.order && sorter.order.slice(0, sorter.order.length - 3),

       ...filters
     })

     // 对接自己的通用数据接口需要修改下方代码中的 r.currPage, r.totalCount, r.list
     if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
       result.then(r => {
         r = r || { currPage: 1, totalCount: 0, list: [] }
         this.localPagination = this.showPagination
           ? Object.assign({}, this.localPagination, {
             showQuickJumper: true,
             current: r.currPage, // 返回结果中的当前分页数
             total: r.totalCount, // 返回结果中的总记录数
             showSizeChanger: this.showSizeChanger,
             pageSize: (pagination && pagination.pageSize) || this.localPagination.pageSize
           })
           : false
         // 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页
         if (r.list.length === 0 && this.showPagination && this.localPagination.current > 1) {
           this.localPagination.current--
           this.loadData()
           return
         }

         // 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 pageNo 和 pageSize 存在 且 totalCount 小于等于 pageNo * pageSize 的大小
         // 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能
         try {
           if ((['auto', true].includes(this.showPagination) && r.totalCount <= (r.pageNo * this.localPagination.pageSize))) {
             this.localPagination.hideOnSinglePage = true
           }
         } catch (e) {
           this.localPagination = false
         }
         this.localDataSource = r.list // 返回结果中的数组数据
         this.localLoading = false
       })
     }
   },
   /**
    * 自定义行。可以配置表格行的相关事件,此处主要定义表格单击选中行,没有复选框或者单选框得表格可以屏蔽该功能
    * @param {*} record 
    */
   localCustomRow(record) {
     const rowCustomer = this.customRow ? this.customRow(record) : {}

     if (!this.selectOnClick || !this.rowSelection) {
       return rowCustomer
     }

     if (!rowCustomer.on) {
       rowCustomer.on = {}
     }
     // 单击选中行需要判断是单选框还是多选框,表格多选或单选框得使用会在后续发文章补充。
     const selectOnClickHandler = () => {
       const { type, selectedRowKeys } = this.rowSelection
       if (selectedRowKeys.includes(record[this.rowKey]) && !type) {
         this.rowSelection.selections.splice(this.rowSelection.selections.findIndex(r => r === record), 1)
         selectedRowKeys.splice(selectedRowKeys.findIndex(r => r === record[this.rowKey]), 1)
       } else if(!type) {
         this.rowSelection.selections.push(record)
         selectedRowKeys.push(record[this.rowKey])
       } else {
         this.rowSelection.selectedRow = record
         selectedRowKeys.splice(0, 1, record[this.rowKey])  
       }
     }
     if (rowCustomer.on.click) {
       const originalClickHandler = rowCustomer.on.click
       rowCustomer.on.click = e => {
         originalClickHandler(e)
         selectOnClickHandler(e, record)
       }
     } else {
       rowCustomer.on.click = selectOnClickHandler
     }
     return rowCustomer
   },
 
   /**
    * 对表格设置width(来避免表头和内容的错位)
    */
   computeWidth() {
     const fullWidth = (this.localScroll.x || this.$el?.offsetWidth) - (this.rowSelection ? 60 : 0)
     if(!isNaN(fullWidth) && fullWidth >= 0) {
       const remain = this.originColumns.reduce((obj, col) => {
         if(!this.filterValue.includes(col.dataIndex || col.key)) {
           if(col.width) {
             obj.colWidthSum =obj.colWidthSum - (typeof width === 'string' && col.width.endsWith('%')
               ? parseFloat(col.width) * fullWidth / 100
               : parseFloat(col.width))
           } else {
             obj.noWidthColCount += 1
           }
         }
         return obj
       }, {colWidthSum: fullWidth, noWidthColCount: 0})
       // 平均宽度
       const averageWidth = remain.colWidthSum / remain.noWidthColCount
       
       const lastIndex = this.originColumns.length - ( this.originColumns[this.originColumns.length-1].fixed ? 2 : 1) 
       // 设置默认列宽,最少显示为7个字符
       // 最后一列默认不设置宽度,避免列宽改变时影响其他列
       this.originColumns.forEach((col,index) => {
         if(index !== lastIndex  && averageWidth !== Infinity && !col.width) {
           Vue.set(col, 'width', averageWidth > 150 ? averageWidth : 150)
           remain.colWidthSum = remain.colWidthSum - (averageWidth > 150 ? averageWidth : 150)
         } else if(index === lastIndex) {
           const minWidth = col.width || 150
           remain.colWidthSum = (remain.colWidthSum + (col.width || 0)).toFixed()
           Vue.set(col, 'width', remain.colWidthSum < minWidth ? minWidth : undefined)
         }
       })
     }
   },
   /**
    * 表格列重置,主要使用在数据使用数据数组的表格
    */
   resetColumns() {
     this.filterValue = []
     this.filterShow = !this.filterShow
   }
 },

// 渲染表格方法
 render(h) {
   const props = {}
   // 表格属性
   Object.keys(Table.props).forEach(k => {
     const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
     // if(k === 'columns'){}
     if (this.localKeys.includes(localKey)) {
       props[k] = this[localKey]
     } else if (this[k] != null) {
       props[k] = this[k]
     }
   })
   const on = { ...this.$listeners }
   this.data && (on.change = this.loadData)
   return (
     <div class={`${componentName}-wrapper`} style="position: relative;">{[
       props.showHeader && h('table-option', {
         ref: 'tableOption',
         style: {
           float: 'right',
           marginTop:'-25px',
           marginRight: '5px'
         },
         props: {
           columns: this.originColumns,
           noCheckedValues: this.filterValue
         },
         on: {
           filter: (noCheckedValues) => this.filterValue = noCheckedValues
         }
       }),
       h('a-table', {
         props,
         on,
         scopedSlots: { ...this.$scopedSlots }
       },
         Object.keys(this.$slots).map(name => (
           <template slot={name}>{this.$slots[name]}</template>
         ))
       )
       ]}
     </div>
   )
 }
}

export default DragTable

TableOption.vue组件

<template>
  <div>
    <a-tooltip placement="leftTop" title="表格列显示配置">
      <a-button @click="handleClick" class="optionBtn"><a-icon type="table" /></a-button>
    </a-tooltip>
    <div v-if="visible" class="table-select">
      <a-checkbox
        :checked="options.length === checkedValues.length"
        @change="onCheckAllChange"
      >
        全选/反选
      </a-checkbox>
      <a-checkbox-group
        :options="options"
        v-model="checkedValues"
        @change="selectChange"
      />
    </div>
  </div>
</template>
<script>
const componentName = 'ebig-table-option'

const TableOption= {
  name: componentName,
  props: {
    columns: { type: Array, default: ()=>([])},
    noCheckedValues: { type:Array, default: ()=>([]) }
  },
  data() {
    return {
      checkedValues: [],
      visible: false
    }
  },
  watch: {
    noCheckedValues: {
      handler(val) {
        this.checkedValues = this.options.filter(col => !val.includes(col.value)).map(c => c.value)
      },
      immediate: true
    }
  },
  computed: {
    options() {
      return this.columns.map(col => {
        const key = col.dataIndex || col.key
        return { label: col.title || col.slots.title, value: key + '' }
      })
    },
    allKeys: vm => vm.options.map(o => o.value)
  },
  methods: {
    handleClick() {
      this.visible = !this.visible
    },
    onCheckAllChange(e) {
      this.checkedValues = e.target.checked ? this.allKeys : []
      const noCheckedValues = e.target.checked ? [] : this.allKeys
      this.$emit('filter', noCheckedValues)
    },
    selectChange(checkedValues) {
      const noCheckedValues = this.allKeys.filter(key => !checkedValues.includes(key))
      this.$emit('filter', noCheckedValues)
    }
  }
}

export default TableOption
</script>

<style lang="less">
.optionBtn {
  padding: 0 4px !important;
  height: auto !important;
  opacity: 0.4;
  &:hover {
    opacity: 1;
  }
}
.ant-checkbox-group-item + .ant-checkbox-group-item {
  display: block
}
.table-select {
  position: absolute;
  background:#fff;
  border:1px solid #ecedef;
  top: 5px;
  right: 0;
  z-index: 100000;
  padding: 10px 0 10px 10px;
  width: 180px;
  max-height: 100%;
  overflow: auto;
  &::-webkit-scrollbar {
    width: 5px;
  }
  &::-webkit-scrollbar-thumb {
    border-radius: 8px;
    background-color: rgb(177, 175, 175);
  }
  &::-webkit-scrollbar-thumb:hover {
    border-radius: 10px;
    background-color: #22212177;
  }
}
</style>
使用

表格使用案例

<template>
  <a-card style="paddingTop: 50px">
    <drag-table
      ref="table"
      size="small"
      row-key="id"
      :columns="columns"
      :data="loadData"
    />
  </a-card>
</template>
<script>
import DragTable from './Table'

export default {
  name: 'drag-table-example',
  components: { DragTable },
  data() {
    return {
      columns: [
        { dataIndex: 'name', title: '姓名' },
        { dataIndex: 'sex', title: '性别' },
        { dataIndex: 'age', title: '年龄' },
        { dataIndex: 'school', title: '学校' }
      ],
      dataSource: []
    }
  },
  methods: {
    loadData() {
      return Promise.resolve({
        currPage: 1,
        pageSize: 10,
        totalCount: 9,
        totalPage: 1,
        list: [
          { id: 1, name: '张三', sex: '男', age: 18, school: '测试高级学校1'},
          { id: 2, name: '李四', sex: '女', age: 16, school: '测试高级学校2'},
          { id: 3, name: '王五', sex: '男', age: 15, school: '测试高级学校3'},
          { id: 4, name: '张红', sex: '女', age: 17, school: '测试高级学校4'},
          { id: 5, name: '陈平', sex: '男', age: 20, school: '测试高级学校5'},
        ]
      })
    }
  }
}
</script>

antdv-基础表格拖拽

Logo

前往低代码交流专区

更多推荐