ant-design-vue Table 封装表格主要功能: 1、表格加载(数据支持数据数组和接口访问方法) 2、表格分页 3、表格伸缩列 4、支持单击选中行 5、表格支持列显示和隐藏(同时也可以查看AVue,具有相同的功能,AVue 组件已经封装,可直接使用;此处仅供需要情景使用)
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 = 'my-table' const MyTable = { 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' }, pageURI: { type: Boolean, default: false }, }, bordered: { type:Boolean, default: true} }), data() { return { localLoading: false, localDataSource: [], localPagination: Object.assign({}, this.pagination), localScroll: {}, // 表格列显示隐藏 filterValue:[], filterShow: false } }, computed: { // 判断是否在弹出框里,用于定义表格是否可以滚动 isInDialog() { let parent = this.$parent while (parent) { if (parent.$options._componentTag === 'a-modal') { return true } parent = parent.$parent } return false }, localKeys() { return [...Object.keys(this.$data), ...Object.keys(this._computedWatchers), ...Object.keys(this).filter(k => k.startsWith('local'))] }, // 处理最大显示长度后的列 allColumns() { let results = [] // 设置 scroll 属性后,需要设置所有列的 width 来避免表头和内容的错位。 // 对未设置 width 的 col 设置平均宽度 const fullWidth = this.localScroll.x || (this.$el || {}).offsetWidth - 85 const remainWidth = this.columns.reduce( (remain, { width }) => width ? typeof width === 'string' && width.endsWith('%') ? remain - parseFloat(width) * fullWidth / 100 : remain - parseFloat(width) : remain, fullWidth ) const noWidthColCount = this.columns.reduce((count, { width }) => width ? count : count + 1, 0) const averageWidth = remainWidth / noWidthColCount this.columns.forEach(col => !col.width && Vue.set(col, 'width', averageWidth)) results = this.columns.reduce((results,col)=>{ // 超出后显示省略号 if(this.isDrag || col.key !== 'action') { // 组件不支持排序省略一起使用,使用排序后,表头省略会失效 col.ellipsis = true } const result = Object.assign({}, col) results.push(result) return results },results) return results }, localColumns(){ return this.allColumns.filter(col => this.filterValue.includes(col.dataIndex || col.key || col.title)) }, // 表格伸缩列 // 不支持表格中有子表格,需要屏蔽 localComponents(){ const headerComponent = {} if(!this.isDrag) return 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: col.width, z: 1, axis: 'x', draggable: true, resizable: false }, on: { dragging: (x) => { col.width = Math.max(x, 35) } } } 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() }, }, 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 || {}) } if (!this.isInTable && !this.isInDialog) { localScroll.x = localScroll.x || this.$el.offsetWidth - 100 localScroll.y = localScroll.y || document.body.clientHeight - ((this.$el || {}).offsetTop || 128) - 236 } else { localScroll.x = localScroll.x || this.$el.offsetWidth - 80 } this.localScroll = localScroll }, /** * 表格重新加载方法 * 如果参数为 true, 则强制刷新到第一页 * @param {boolean} bool */ refresh(bool = false, isSearch = true) { !isSearch && this.resetColumns() bool && (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 // eslint-disable-next-line 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 }, /** * 表格列动态显示隐藏方法 */ filter(checkedValues){ this.filterValue = checkedValues }, /** * 表格列重置,主要使用在数据使用数据数组的表格 */ resetColumns() { this.filterValue = this.allColumns.map(col => col.dataIndex || col.key || col.title) 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;">{[ h('a-table', { props, on, scopedSlots: { ...this.$scopedSlots } }, Object.keys(this.$slots).map(name => ( <template slot={name}>{this.$slots[name]}</template> )) ), props.showHeader && h('table-option', { style: { position: 'absolute', right: 0, top:'-35px', }, props: { columns: this.allColumns, visible: this.filterShow }, on: { filter: this.filter } })]} </div> ) } } export default EbigTableTableOption.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="tableSelectVisible" class="table-select"> <a-checkbox :checked="colOptions.length === checkedValues.length" @change="onCheckAllChange" > 全选/反选 </a-checkbox> <a-checkbox-group :options="colOptions" :value="checkedValues" @change="selectChange" /> </div> </div> </template> <script> const componentName = 'my-table-option' const MyTableOption= { name: componentName, props: { columns: { type: Array, default: ()=>([])}, visible: { type: Boolean } }, data() { return { checkedValues: [], colOptions: [], tableSelectVisible: false } }, watch: { visible: { handler() { this.checkedValues = this.colOptions.map(col => col.value) this.tableSelectVisible = false }, immediate: true } }, mounted(){ setTimeout(()=>{ this.colOptions = this.columns.reduce((options,column)=>{ options.push({label: column.title || column.slots.title, value: column.dataIndex || column.key || column.title }) return options },[]) this.checkedValues = this.colOptions.map(col => col.value) }) }, methods: { handleClick() { this.tableSelectVisible = !this.tableSelectVisible }, onCheckAllChange(e) { this.checkedValues = e.target.checked ? this.colOptions.map(option => option.value):[] this.$emit('filter', this.checkedValues) }, selectChange(checkedValues) { this.checkedValues = checkedValues this.$emit('filter', checkedValues) } } } export default MyTableOption </script> <style lang="less"> .ant-checkbox-group-item + .ant-checkbox-group-item { display: block } .table-select { position: absolute; background:#fff; border:1px solid #ecedef; top: 30px; right: 0; z-index: 100; padding: 10px 0 10px 10px; width: 180px; } </style>表格使用案例
<!-- 表格 --> <my-table ref="table" row-key="id" :columns="columns" :data="loadData" /> <script> export default { name: 'example', data(){ return { // 表格列 columns: [ { title: '姓名', dataIndex: 'name' }, { title: '性别', dataIndex: 'sex'}, { title: '年龄', dataIndex: 'age' }, { title: '学历', dataIndex: 'education' }, { title: '邮箱', dataIndex: 'email' }, { title: '手机号码', dataIndex: 'phone' } ] } } methods: { query() { this.refresh(true) }, refresh(isBackToFirstPage = false) { this.form.validateFields(err => err || this.$refs.table.refresh(isBackToFirstPage)) }, // 加载数据 loadData(pagination) { // 接口 return user.getAll({ ...pagination}) }, } } </script>