Vue实战技巧Element Table二次封装__Vue.js
发布于 17 天前 作者 banyungong 149 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

theme: cyanosis highlight: a11y-dark

前言

由于重构后台管理项目中有好多表格页面, 举个栗子

k-table-demo.png

这表格看着还挺好看,写起来叫人直呼XX,多动脑子少掉发,少走弯路多省鞋。

写了一个后感觉太麻烦了,于是我奋笔疾书,利用Vue+Element Table重新封装出了一套表格组件

3分钟就可以实现一个表格页面的骚操作,你值得拥有

思考

看了表格咱们简单的把它们划分了下功能区

  • 最先入场的选手是最上方一排整整齐齐有点严肃的搜索功能区

  • 其次入场的是略带风骚的表格功能区

  • 然后入场的是全能综合性型选手表格内容区域

  • 最后入场的是有着长腿带着长队的分页组件区域

诶,让我们思考下最复杂的地方在哪里?

下面我用个图来标注下咱们接下来需要拿下的高地

总的来说表格内容这一块最不可控,他可能有多选、序号、图片、状态、时间、操作列…

k-table.png

实践

咱们把搜索层写成一个组件filterPane.vue

把表格分成一个组件tablePane.vue

表格组件tablePane.vue包括功能区、表格内容区、分页

filterPane.vue

明确目标

搜索层一般包括日期选择器、输入框、select下拉选择器等+搜索功能、重置功能

传入数据结构整理

// 搜索栏组件
 filterData:{
   timeSelect:true,    //是否显示日期控件
   elinput:[          
     {
       name:'姓名',    //提示语
       key:'userName',  //字段名
       width:100        //宽度
     }
   ],
   elselect:[
     {
       name:'部门',
       key:'department',
       width:100
       option:[{
         key:1,
         value:'技术部'
       }]
     }
   ]
 }

timeSelect

  • 类型 Boolean 是否显示时间选择器

elinput

  • 类型 Array 输入框选项,子对象内

name为输入框的placeholder

key为字段名

elselect

  • 类型 Array select下拉框选项,子对象内

name为输入框的placeholder

key为字段名

option为select下拉选项

开始封装

<template>
  <div>
    <div class="filter-container">
      <el-date-picker
        v-if="filterData.timeSelect"
        v-model="dateRange"
        style="width: 300px"
        type="daterange"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        :default-time="['', '']"
        :picker-options="pickerOptions"
        class="filter-item"
      />
      <template v-if="filterData.elinput">
        <el-input
          v-for="(item,index) in filterData.elinput"
          :key="index"
          v-model="listQuery[item.key]"
          :placeholder="item.name"
          :style="{'width':item.width?item.width+'px':'200px'}"
          class="filter-item"
        />
      </template>
      <template v-if="filterData.elselect">
        <el-select
          v-for="(item,index) in filterData.elselect"
          :key="index"
          v-model="listQuery[item.key]"
          :placeholder="item.name"
          clearable
          :style="{'width':item.width?item.width+'px':'90px'}"
          class="filter-item"
        >
          <el-option
            v-for="i in item.option"
            :key="i.key"
            :label="i.value"
            :value="i.key"
          />
        </el-select>
      </template>
      <div class="btn">
        <el-button class="filter-item" type="primary" @click="handleSearch">
          搜索
        </el-button>
        <el-button class="filter-item" type="warning" @click="handleRest">
          重置
        </el-button>
      </div>
    </div>
  </div>
</template>
<script>
// 搜索栏组件
// filterData:{
//   timeSelect:true,
//   elinput:[
//     {
//       name:'姓名',
//       key:'userName'
//     }
//   ],
//   elselect:[
//     {
//       name:'部门',
//       key:'department'
//       option:[{
//         key:1,
//         value:'技术部'
//       }]
//     }
//   ]
// }
export default {
  props: {
    // eslint-disable-next-line vue/require-default-prop
    filterData: {
      type: Object
    }
  },
  data() {
    return {
      pickerOptions: {
        disabledDate(time) {
          return time.getTime() > Date.now()
        }
      },
      dateRange: ['', ''],
      listQuery: {}
    }
  },
  watch: {
    'filterData'(val) {
      console.log(val)
      if (val.elinput.length > 0) {
        val.elinput.map(item => {
          this.listQuery[item.key] = ''
        })
      }
      if (val.elselect.length > 0) {
        val.elinput.map(item => {
          this.listQuery[item.key] = ''
        })
      }
    },
    //缓存进页面想清空可用
    'filterData.rest': {
      handler: function(val) {
        if (val) {
          this.handleRest()
        }
      },
      deep: true
    }
  },
  methods: {
    handleSearch() {
      console.log('搜索成功', this.listQuery)
      const data = this.$global.deepClone(this.listQuery)
      if (this.dateRange && this.dateRange[0] !== '') {
        const startTime = this.$moment(this.dateRange[0]).format('YYYY-MM-DD') + ' 00:00:00'
        const endTime = this.$moment(this.dateRange[1]).format('YYYY-MM-DD') + ' 23:59:59'
        data.beginDate = startTime
        data.endDate = endTime
      }
      Object.keys(data).forEach(function(key) {
        if (data[key] === '') {
          delete data[key]
        }
      })
      this.$emit('filterMsg', data)
    },
    handleRest() {
      const data = this.$global.deepClone(this.listQuery)
      Object.keys(data).forEach(function(key) {
        data[key] = ''
      })
      this.listQuery = data
      this.dateRange = ['', '']
      console.log('重置成功', this.listQuery)
    }
  }
}
</script>

<style  scoped lang='scss'>
.filter-item{
  margin-left: 10px;
  display: inline-block;
}
.filter-container .filter-item:nth-of-type(1){
  margin-left: 0px;
}
.btn{
  display: inline-block;
  margin-left: 10px;
}
</style>

tablePane.vue

明确目标

实现表格功能行、实现表格基本功能、实现分页功能

传入数据结构整理

  dataSource: {
          tool:[
            {
              name: '新增用户', //按钮名称
              key: 1,  // 唯一标识符
              permission: 2010106, // 权限点
              type: '',  // 使用element自带按钮类型
              bgColor: '#67c23a', // 自定义背景色
              handleClick: this.handleAdd //自定义事件
            },
          ]
         data: [], // 表格数据
         cols: [], // 表格的列数据
         isSelection: false, // 表格有多选时设置
         selectable: function(val) {//禁用部分行多选
          if (val.isVideoStatus === 1) {
            return false
          } else {
            return true
          }
        },
         handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
         isOperation: true, // 表格有操作列时设置
         isIndex: true, // 列表序号
         loading: true, // loading
         pageData: {
          total: 0, // 总条数
          pageSize: 10, // 每页数量
          pageNum: 1 // 页码
         }
         operation: {
           // 表格有操作列时设置
           label: '操作', // 列名
           width: '350', // 根据实际情况给宽度
           data: [
             {
               label: '冻结', // 操作名称
               permission:'' //权限点
               type: 'info', //按钮类型
               handleRow: function(){} // 自定义事件
             },
           ]
         }
       },

tool

  • 类型 Array
  • 默认值 [ ]

配置表格工具列

 dataSource: {
         tool:[
           {
             name: '新增用户', //按钮名称
             key: 1,  // 唯一标识符
             permission: 2010106, // 权限点
             type: '',  // 使用element自带按钮类型
             bgColor: '#67c23a', // 自定义背景色
             handleClick: this.handleAdd //自定义事件
           },
         ]
  }

cols

  • 类型 Array
  • 默认值 [ ] 配置表头
 dataSource: {
         cols:[
            {
               label: '标题',                       //列名
               prop: 'belongUserId',                //字段名称
               width: 100                           //列宽度
            },
            {
               label: '副标题(季)',
               prop: 'subtitle',
               isCodeTableFormatter: function(val) {//过滤器
                 if (val.subtitle === 0) {
                   return '无'
                 } else {
                   return val.subtitle
                 }
               },
               width: 100
            },
             {
               label: '创建时间',
               prop: 'createTime',
               isCodeTableFormatter: function(val) {//时间过滤器
                 return timeFormat(val.createTime)
               },
               width: 150
             }
         ]
  }

pageData

  • 类型 Object
  • 默认值 { } 配置分页
 dataSource: {
        pageData: {
         total: 0, // 总条数
         pageSize: 10, // 每页数量
         pageNum: 1, // 页码
         pageSize:[5,10,15,20]// 每页数量
        }
 }

operation

  • 类型 Object
  • 默认值 { } 配置操作列
dataSource: {
       operation: {
         // 表格有操作列时设置
         label: '操作', // 列名
         width: '350', // 根据实际情况给宽度
         data: [
           {
             label: '修改', // 操作名称
             permission:'1001' //权限点
             type: 'info', //按钮类型
             handleRow: function(){} // 自定义事件
           },
         ]
       }
}

开始封装

<template>
 <div>
   <div v-if="dataSource.tool" class="tool">
     <el-button
       v-for="(item) in dataSource.tool"
       :key="item.key"
       v-permission="item.permission"
       class="filter-item"
       :style="{'background':item.bgColor,borderColor:item.bgColor}"
       :type="item.type || 'primary'"
       @click="item.handleClick(item.name,$event)"
     >
       {{ item.name }}
     </el-button>
   </div>
   <el-table
     ref="table"
     v-loading="dataSource.loading"
     style="width: 100%;"
     :class="{ 'no-data': !dataSource.data || !dataSource.data.length }"
     :data="dataSource.data"
     @row-click="getRowData"
     @selection-change="dataSource.handleSelectionChange"
   >
     <!-- 是否有多选 -->
     <el-table-column
       v-if="dataSource.isSelection"
       :selectable="dataSource.selectable"
       type="selection"
       :width="dataSource.selectionWidth || 50"
       align="center"
     />
     <!-- 是否需要序号 -->
     <el-table-column
       v-if="dataSource.isIndex"
       type="index"
       label="序号"
       width="55"
       align="center"
     />

     <template v-for="item in dataSource.cols">
       <!-- 表格的列展示 特殊情况处理 比如要输入框 显示图片 -->
       <el-table-column
         v-if="item.isTemplate"
         :key="item.prop"
         v-bind="item"
       >
         <template slot-scope="scope">
           <!-- 比如要输入框 显示图片等等 自己定义 -->
           <slot :name="item.prop" :scope="scope" />
         </template>
       </el-table-column>
       <!-- 图片带tooltip -->
       <el-table-column
         v-if="item.isImagePopover"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <el-popover
             placement="right"
             title=""
             trigger="hover"
           >
             <img class="image-popover" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_60'" alt="">
             <img slot="reference" class="reference-img" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_10'" alt="">
           </el-popover>
         </template>
       </el-table-column>
       <!-- 大部分适用 -->
       <el-table-column
         v-if="!item.isImagePopover && !item.isTemplate && !item.invisible&&!item.timeFormat"
         :key="item.prop"
         v-bind="item.isCodeTableFormatter ? Object.assign({ formatter: item.isCodeTableFormatter }, item) : item"
         align="center"
         show-overflow-tooltip
       />
     </template>
     <!-- 是否有操作列 -->
     <!-- 没有数据时候不固定列 -->
     <el-table-column
       v-if="dataSource.isOperation"
       :show-overflow-tooltip="dataSource.operation.overflowTooltip"
       v-bind="dataSource.data && dataSource.data.length ? { fixed: 'right' } : null"
       style="margin-right:20px"
       class-name="handle-td"
       label-class-name="tc"
       :width="dataSource.operation.width"
       :label="dataSource.operation.label"
       align="center"
     >
       <!-- UI统一一排放3个,4个以上出现更多 -->
       <template slot-scope="scope">
         <!-- 三个一排的情况,去掉隐藏的按钮后的长度 -->
         <template v-if="dataSource.operation.data.length > 0">
           <div class="btn">
             <div v-for="(item) in dataSource.operation.data" :key="item.label">
               <template v-if="item.type!=='icon'">
                 <el-button
                   v-permission="item.permission"
                   v-bind="item"
                   :type="item.type?item.type:''"
                   size="mini"
                   @click.native.prevent="item.handleRow(scope.$index, scope.row, item.label)"
                 >
                   {{ item.label }}
                 </el-button>
               </template>
               <template v-else>
                 <i class="icon el-icon-plus" v-bind="item" @click="item.handleRow(scope.$index, scope.row, item.label)" />
               </template>
             </div>
           </div>
         </template>
       </template>
     </el-table-column>
   </el-table>
   <div class="page">
     <el-pagination
       v-if="dataSource.pageData.total>0"
       :current-page="dataSource.pageData.pageNum"
       :page-sizes="dataSource.pageData.pageSizes?dataSource.pageData.pageSizes:[5,10,15,20]"
       :page-size="dataSource.pageData.pageSize"
       layout="total, sizes, prev, pager, next, jumper"
       :total="dataSource.pageData.total"
       @size-change="handleSizeChange"
       @current-change="handleCurrentChange"
     />
   </div>
 </div>
</template>

<script>
//  dataSource: {
//          tool:[
//            {
//              name: '新增用户', //按钮名称
//              key: 1,  // 唯一标识符
//              permission: 2010106, // 权限点
//              type: '',  // 使用element自带按钮类型
//              bgColor: '#67c23a', // 自定义背景色
//              handleClick: this.handleAdd //自定义事件
//            },
//          ]
//         data: [], // 表格数据
//         cols: [], // 表格的列数据
//         handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
//         isSelection: false, // 表格有多选时设置
//         isOperation: true, // 表格有操作列时设置
//         isIndex: true, // 列表序号
//         loading: true, // loading
//         pageData: {
//          total: 0, // 总条数
//          pageSize: 10, // 每页数量
//          pageNum: 1, // 页码
//          pageSize:[5,10,15,20]// 每页数量
//         }
//         operation: {
//           // 表格有操作列时设置
//           label: '操作', // 列名
//           width: '350', // 根据实际情况给宽度
//           data: [
//             {
//               label: '冻结', // 操作名称
//               permission:'' //权限点
//               type: 'info', //按钮类型
//               handleRow: function(){} // 自定义事件
//             },
//           ]
//         }
//       },
export default {
 // 接收父组件传递过来的值
 props: {
   //  表格数据和表格部分属性的对象
   // eslint-disable-next-line vue/require-default-prop
   dataSource: {
     type: Object
   }
 },
 data() {
   return {

   }
 },
 watch: {
   'dataSource.cols': { // 监听表格列变化
     deep: true,
     handler() {
       // 解决表格列变动的抖动问题
       this.$nextTick(this.$refs.table.doLayout)
     }
   }
 },
 methods: {
   handleAdd(name) {
     console.log(name)
     this.$emit('toolMsg', name)
   },
   handleRow(index, row, lable) {
     console.log(index, row, lable)
   },
   handleSizeChange(val) {
     this.$emit('changeSize', val)
     console.log(`每页 ${val} 条`)
   },
   handleCurrentChange(val) {
     this.$emit('changeNum', val)
     console.log(`当前页: ${val}`)
   },
   // 点击行即可选中
   getRowData(row) {
     this.$refs.table.toggleRowSelection(row)
   }
 }
}
</script>
<style lang="scss" scoped>
.page{
 margin-top: 20px;
}
.btn{
 display: flex;
 justify-content: center;
}
.btn div{
 margin-left: 5px;
}
.reference-img{
 width: 40px;
 height: 40px;
 background-size:100% 100%;
 border-radius: 4px;
}
.image-popover{
 width: 200px;
 height: 200px;
 background-size:100% 100%;
}
.icon {
 width: 25px;
 font-size: 20px;
 font-weight: bold;
}
</style>

实战

配置某页面,咱先看配置图片是不是省事多了,而且条理清楚

1.png

2.png

<template>
  <div class="app-container">
    <filter-pane :filter-data="filterData" @filterMsg="filterMsg" />
    <table-pane
      :data-source="dataSource"
      @changeSize="changeSize"
      @changeNum="changeNum"
    />
    <add :dialog-add="dialogAdd" @childMsg="childMsg" />
  </div>
</template>
<script>
import filterPane from '@/components/Table/filterPane'
import tablePane from '@/components/Table/tablePane'
import add from './components/add'
import { getVersionList, delVersion } from '@/api/user'
import { timeFormat } from '@/filters/index'
export default {
  name: 'Suggestion',
  components: { filterPane, tablePane, add },
  data() {
    return {
      // 搜索栏配置
      filterData: {
        timeSelect: false,
        elselect: [
          {
            name: '状态',
            width: 120,
            key: 'platform',
            option: [
              {
                key: '全部',
                value: '全部'
              },
              {
                key: 1,
                value: 'IOS'
              },
              {
                key: 2,
                value: '安卓'
              }
            ]
          }
        ]
      },
      // 表格配置
      dataSource: {
        tool: [{
          name: '新增版本',
          key: 1,
          permission: 2010701,
          handleClick: this.handleAdd
        }],
        data: [], // 表格数据
        cols: [
          {
            label: '发布时间',
            prop: 'appIssueTime',
            isCodeTableFormatter: function(val) {
              return timeFormat(val.appIssueTime)
            }
          },
          {
            label: 'APP名称',
            prop: 'appName'
          },
          {
            label: 'APP版本',
            prop: 'appVersion'
          },
          {
            label: '平台',
            prop: 'appPlatform',
            isCodeTableFormatter: function(val) {
              if (val.appPlatform === 1) {
                return 'IOS'
              } else {
                return 'Android'
              }
            }
          },
          {
            label: '是否自动更新',
            prop: 'appAutoUpdate',
            isCodeTableFormatter: function(val) {
              if (val.appAutoUpdate === 1) {
                return '是'
              } else {
                return '否'
              }
            }
          },
          {
            label: '更新描述',
            prop: 'appDesc',
            width: 300
          },
          {
            label: '下载地址',
            prop: 'downloadAddr'
          },
          {
            label: '发布人',
            prop: 'userName'
          }
        ], // 表格的列数据
        handleSelectionChange: this.handleSelectionChange,
        isSelection: false, // 表格有多选时设置
        isOperation: true, // 表格有操作列时设置
        isIndex: true, // 列表序号
        loading: true, // loading
        pageData: {
          total: 0, // 总条数
          pageSize: 10, // 每页数量
          pageNum: 1 // 页码
        },
        operation: {
          // 表格有操作列时设置
          label: '操作', // 列名
          width: '100', // 根据实际情况给宽度
          data: [
            {
              label: '删除', // 操作名称
              type: 'danger',
              permission: '2010702', // 后期这个操作的权限,用来控制权限
              handleRow: this.handleRow
            }
          ]
        }
      },
      dialogAdd: false,
      msg: {},
      selected: []
    }
  },
  created() {
    this.getList()
  },
  methods: {
    // 获取列表数据
    getList() {
      const data = {
        pageSize: this.dataSource.pageData.pageSize,
        pageNum: this.dataSource.pageData.pageNum
      }
      if (this.msg) {
        if (this.msg.platform === 'IOS') {
          data.platform = 1
        } else if (this.msg.platform === '安卓') {
          data.platform = 2
        }
      }
      this.dataSource.loading = true
      getVersionList(data).then(res => {
        this.dataSource.loading = false
        if (res.succeed) {
          if (res.data.total > 0) {
            this.dataSource.pageData.total = res.data.total
            this.dataSource.data = res.data.data
          } else {
            this.dataSource.data = []
            this.dataSource.pageData.total = 0
          }
        }
      })
    },
    // 搜索层事件
    filterMsg(msg) {
      this.msg = msg
      if (Object.keys(msg).length > 0) {
        this.getList(msg)
      } else {
        this.getList()
      }
    },
    // 子组件通信
    childMsg(msg) {
      if (msg.dialogAdd === false) {
        this.dialogAdd = false
      } else if (msg.refreshList) {
        this.getList()
      }
    },
    // 改变每页数量
    changeSize(size) {
      this.dataSource.pageData.pageSize = size
      this.getList()
    },
    // 改变页码
    changeNum(pageNum) {
      this.dataSource.pageData.pageNum = pageNum
      this.getList()
    },
    // 多选事件
    handleSelectionChange(val) {
      this.selected = val
    },
    // 表格上方工具栏回调
    handleAdd(index, row) {
      this.dialogAdd = true
    },
    // 表格操作列回调
    handleRow(index, row, lable) {
      if (lable === '删除') {
        this.$confirm('确认删除该版本?', '温馨提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          delVersion({ versionId: row.id }).then(res => {
            if (res.succeed) {
              this.$message.success('删除成功')
              this.getList()
            }
          })
        }).catch(() => {
        })
      }
    }
  }
}
</script>

<style  scoped lang='scss'>

</style>

结尾

filterPane.vuetablePane.vue已完成,有些特殊页面只需要复制下到当前特殊页面的components里改动下就

可以了,目前还在不断完善中,大家有什么问题可以提出来,也好进一步优化。

完整源文件在gitHub,可以下载直接使用,后续会持续更新

我给起了个名k-tablek开头代表快速的意思

k-table 如果对你有帮助,请点亮你的小星星⭐⭐⭐哦~(疯狂暗示)

超级简单的API,提供手把手教你使用的实例!

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 凉城a 原文链接:https://juejin.im/post/6951564460964347912

回到顶部