Vue技巧Element Table二次封裝實戰示例

前言

由於重構後臺管理項目中有好多表格頁面, 舉個栗子

這表格看著還挺好看,寫起來叫人直呼XX,多動腦子少掉發,少走彎路多省鞋。

寫瞭一個後感覺太麻煩瞭,於是我奮筆疾書,利用Vue+Element Table重新封裝出瞭一套表格組件

3分鐘就可以實現一個表格頁面的騷操作,你值得擁有

思考

看瞭表格咱們簡單的把它們劃分瞭下功能區

  • 最先入場的選手是最上方一排整整齊齊有點嚴肅的搜索功能區
  • 其次入場的是略帶個性的表格功能區
  • 然後入場的是全能綜合性型選手表格內容區域
  • 最後入場的是有著長腿帶著長隊的分頁組件區域

誒,讓我們思考下最復雜的地方在哪裡?

下面我用個圖來標註下咱們接下來需要拿下的高地

總的來說表格內容這一塊最不可控,他可能有多選、序號、圖片、狀態、時間、操作列……

實踐

咱們把搜索層寫成一個組件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', //按鈕類型icon為圖表類型
             handleRow: function(){} // 自定義事件
           },
           {
             label: '修改', // 操作名稱
             permission:'1001' //權限點
             type: 'icon', //按鈕類型icon為圖表類型
             icon:'el-icon-plus'
             handleRow: function(){} // 自定義事件
           }
         ]
       }
}

tablePane.vue配置項Cols詳解

  • 普通列
cols:[
   {
       label: '標題',
       prop: 'title',
       width: 200
    }
]
  • 普通列字體顏色改變
cols:[
  {
    label: '狀態',
    prop: 'status',
    isTemplate: function(val) {
      if (val === 1) {
        return '禁言中'
      } else {
        return '已解禁'
      }
    },
    isTemplateClass: function(val) {
      if (val === 1) {
        return 'color-red'
      } else {
        return 'color-green'
      }
    }
  }
]
  • 帶filter過濾器列
cols:[
   {
      label: '推送時間',
      prop: 'pushTime',
      isCodeTableFormatter: function(val) {
        return timeFormat(val.pushTime)
      }
    },
    {
      label: '狀態',
      prop: 'status',
      isCodeTableFormatter: function(val) {
        if(val.status===1){
          return '成功'
        }else{
          return '失敗'
        }
      }
    }
]
  • 帶圖標列
cols:[
  {
     label: '目標類型',
     prop: 'targetType',
     isIcon: true,
     filter: function(val) {
       if (val === 4) {
         return '特定用戶'
       } else if (val === 3) {
         return '新註冊用戶'
       } else if (val === 2) {
         return '標簽用戶'
       } else if (val === 1) {
         return '全部用戶'
       }
     },
     icon: function(val) {
       if (val === 4) {
         return 'el-icon-mobile'
       } else {
         return false
       }
     },
     handlerClick: this.handlerClick
   }
]

開始封裝

<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>
      <!-- 需要特殊顏色顯示字體-->
       <el-table-column
         v-if="item.isSpecial"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <span :class="item.isSpecialClass(scope.row[scope.column.property])">{{ item.isSpecial(scope.row[scope.column.property]) }}</span>
         </template>
       </el-table-column>
       <!-- 需要帶圖標的某列,帶回調事件-->
       <el-table-column
         v-if="item.isIcon"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <span>
             <span>{{ item.filter(scope.row[scope.column.property]) }}</span>
             <i v-if="item.icon" :class="[item.icon(scope.row[scope.column.property]),'icon-normal']" @click="item.handlerClick(scope.row)" />
           </span>
           <!-- 比如要輸入框 顯示圖片等等 自己定義 -->
           <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.isSpecial&&!item.isIcon"
         :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,item.icon]" 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>

實戰

配置某頁面,咱先看配置圖片是不是省事多瞭,而且條理清楚

<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-table以k開頭代表快速的意思

以上就是Vue技巧Element Table二次封裝實戰示例的詳細內容,更多關於Vue Element Table二次封裝的資料請關註WalkonNet其它相關文章!

推薦閱讀: