preface

Because there are many table pages in the refactoring backend management project, for example

This form looks very good, write up to call XX, more brain less hair, less detources more shoes.

After writing one, I felt too much trouble, so I went ahead and repackaged a set of Table components using Vue+Element Table

You can do a table page in 3 minutes, and you deserve it

thinking

Look at the table and let’s simply divide them into functional areas

  • The first contestant to enter was a neat, somewhat serious search section at the top

  • The second entrance is slightly coquettish table functional area

  • Then enter the content area of the table for the all-rounder

  • Last to enter is the paging component area with long legs and long lines

So let’s think about what’s the most complicated part?

Let me draw a picture of the heights we need to take next

In general, the content of the table is the least controllable, it may have multiple choices, number, image, status, time, action column……

practice

Let’s write the search layer as a component, FilterPane.vue

Divide the table into one component, TablePane.vue

The table component TablePane.vue includes the functional area, the table content area, and paging

filterPane.vue

Clear goals

The search layer generally includes date selector, input box, select drop-down selector and other + search function, reset function

Incoming data structure sorting

// Search bar component
 filterData: {timeSelect:true.// Whether to display the date control
   elinput:[          
     {
       name:'name'./ / the clues
       key:'userName'./ / the field name
       width:100        / / width}].elselect:[
     {
       name:'department'.key:'department'.width:100
       option: [{key:1.value:'Technical Department'}}}]]Copy the code

timeSelect

  • typeBoolean

Whether to display the time selector

elinput

  • typeArray

Input box options, child objects inside

Name is the placeholder of the input field

Key is the field name

elselect

  • typeArray

Select dropdown option, subobject inside

Name is the placeholder of the input field

Key is the field name

Option is the select drop-down option

Began to encapsulate

<template>
  <div>
    <div class="filter-container">
      <el-date-picker
        v-if="filterData.timeSelect"
        v-model="dateRange"
        style="width: 300px"
        type="daterange"
        start-placeholder="Start Date"
        end-placeholder="End date"
        :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">search</el-button>
        <el-button class="filter-item" type="warning" @click="handleRest">reset</el-button>
      </div>
    </div>
  </div>
</template>
<script>
// Search bar component
// filterData:{
// timeSelect:true,
// elinput:[
/ / {
// name:' name ',
// key:'userName'
/ /}
/ /,
// elselect:[
/ / {
// name:' department ',
// key:'department'
// option:[{
// key:1,
// value:' technical department '
/ /}]
/ /}
/ /]
// }
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] = ' '})}},// Cache into the page if you want to clear it
    'filterData.rest': {
      handler: function(val) {
        if (val) {
          this.handleRest()
        }
      },
      deep: true}},methods: {
    handleSearch() {
      console.log('Search successful'.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('Reset successful'.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>

Copy the code

tablePane.vue

Clear goals

Realize the table function row, realize the basic function of the table, realize the paging function

Incoming data structure sorting

  dataSource: {
          tool:[
            {
              name: 'New user'.// Button name
              key: 1.// Unique identifier
              permission: 2010106./ / access points
              type: ' '.// Use element's built-in button type
              bgColor: '#67c23a'.// Customize the background color
              handleClick: this.handleAdd // Customize events},]data: [].// Tabular data
         cols: [].// Table column data
         isSelection: false.// If the table has multiple options
         selectable: function(val) {// Disable partial row multiple selection
          if (val.isVideoStatus === 1) {
            return false
          } else {
            return true}},handleSelectionChange:(val) = >{} // Click the row and select multiple to return the selected array
         isOperation: true.// Set when the table has action columns
         isIndex: true.// Number of the list
         loading: true.// loading
         pageData: {
          total: 0./ / the total number of article
          pageSize: 10.// Number of pages
          pageNum: 1 / / page
         }
         operation: {
           // Set when the table has action columns
           label: 'operation'./ / column name
           width: '350'.// Give the width according to the actual situation
           data: [{label: 'freeze'.// The name of the operation
               permission:' ' / / access points
               type: 'info'.// Button type
               handleRow: function(){} // Customize events}}},],Copy the code

tool

  • typeArray
  • Default value []

Configure the table toolbar

 dataSource: {
         tool:[
           {
             name: 'New user'.// Button name
             key: 1.// Unique identifier
             permission: 2010106./ / access points
             type: ' '.// Use element's built-in button type
             bgColor: '#67c23a'.// Customize the background color
             handleClick: this.handleAdd // Customize events]}},Copy the code

cols

  • typeArray
  • Default value []

The configuration header

 dataSource: {
         cols:[
            {
               label: 'title'./ / column name
               prop: 'belongUserId'.// Field name
               width: 100                           / / column width
            },
            {
               label: 'Subtitle (season)'.prop: 'subtitle'.isCodeTableFormatter: function(val) {/ / filter
                 if (val.subtitle === 0) {
                   return 'no'
                 } else {
                   return val.subtitle
                 }
               },
               width: 100
            },
             {
               label: 'Creation time'.prop: 'createTime'.isCodeTableFormatter: function(val) {// Time filter
                 return timeFormat(val.createTime)
               },
               width: 150}}]Copy the code

pageData

  • typeObject
  • Default value {}

The configuration page

 dataSource: {
        pageData: {
         total: 0./ / the total number of article
         pageSize: 10.// Number of pages
         pageNum: 1./ / page
         pageSize: [5.10.15.20]// Number of pages}}Copy the code

operation

  • typeObject
  • Default value {}

Configure the Operation column

dataSource: {
       operation: {
         // Set when the table has action columns
         label: 'operation'./ / column name
         width: '350'.// Give the width according to the actual situation
         data: [{label: 'change'.// The name of the operation
             permission:'1001' / / access points
             type: 'info'.// The button type icon is the chart type
             handleRow: function(){} // Customize events
           },
           {
             label: 'change'.// The name of the operation
             permission:'1001' / / access points
             type: 'icon'.// The button type icon is the chart type
             icon:'el-icon-plus'
             handleRow: function(){} // Customize events}}}]Copy the code

Tablepane. vue Configuration item Cols Details

  • Common column
cols:[
   {
       label: 'title'.prop: 'title'.width: 200}]Copy the code
  • Normal column font color changed
cols:[
  {
    label: 'state'.prop: 'status'.isTemplate: function(val) {
      if (val === 1) {
        return 'Forbidden words'
      } else {
        return 'Unbanned'}},isTemplateClass: function(val) {
      if (val === 1) {
        return 'color-red'
      } else {
        return 'color-green'}}}]Copy the code
  • With filter filter column
cols:[
   {
      label: 'Push Time'.prop: 'pushTime'.isCodeTableFormatter: function(val) {
        return timeFormat(val.pushTime)
      }
    },
    {
      label: 'state'.prop: 'status'.isCodeTableFormatter: function(val) {
        if(val.status===1) {return 'success'
        }else{
          return 'failure'}}}]Copy the code
  • With icon column
cols:[
  {
     label: 'Target type'.prop: 'targetType'.isIcon: true.filter: function(val) {
       if (val === 4) {
         return 'Specific user'
       } else if (val === 3) {
         return 'New Registered User'
       } else if (val === 2) {
         return 'Tag user'
       } else if (val === 1) {
         return 'All users'}},icon: function(val) {
       if (val === 4) {
         return 'el-icon-mobile'
       } else {
         return false}},handlerClick: this.handlerClick
   }
]
Copy the code

Began to encapsulate

<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"
   >
     <! -- Whether there are multiple choices -->
     <el-table-column
       v-if="dataSource.isSelection"
       :selectable="dataSource.selectable"
       type="selection"
       :width="dataSource.selectionWidth || 50"
       align="center"
     />
     <! -- Do I need a serial number -->
     <el-table-column
       v-if="dataSource.isIndex"
       type="index"
       label="Serial number"
       width="55"
       align="center"
     />

     <template v-for="item in dataSource.cols">
       <! Table columns display special cases such as the input box to display pictures -->
       <el-table-column
         v-if="item.isTemplate"
         :key="item.prop"
         v-bind="item"
       >
         <template slot-scope="scope">
           <! For example, to input the box to display pictures and so on their own definition -->
           <slot :name="item.prop" :scope="scope" />
         </template>
       </el-table-column>
      <! -- Need special color to display font -->
       <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>
       <! -- need a column with an icon, bring back the event -->
       <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>
           <! For example, to input the box to display pictures and so on their own definition -->
           <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>
       <! -- Most applicable -->
       <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>
     <! -- Whether there is an operation column -->
     <! -- Not fixing columns when there is no data -->
     <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 with 3, more than 4 will appear -->
       <template slot-scope="scope">
         <! -- Three in a row, remove the hidden button after the length -->
         <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? The dataSource. PageData. PageSizes:,10,15,20 [5]."
       :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: 'add user ', // button name
// Key: 1, // Unique identifier
// permission: 2010106, // permission point
// Use element's built-in button type
// bgColor: '#67c23a', // custom background color
// handleClick: this.handleAdd // Custom event
/ /},
/ /]
// data: [], // table data
// cols: [], // table column data
// handleSelectionChange:(val)=>{} // click the row to select multiple selections to return the selected array
// isSelection: false, // set when the table has multiple selections
// isOperation: true, // Set this parameter when the table has operation columns
// isIndex: true, // List number
// loading: true, // loading
// pageData: {
// Total: 0, // Total number of entries
PageSize: 10, // Number of pages per page
// pageNum: 1, // page number
PageSize :[5,10,15,20]// number of pages per page
/ /}
// operation: {
// // Set this parameter when the table has operation columns
// label: 'operation ', // column name
// < span style = "box-sizing: border-box; line-height: 20px
// data: [
/ / {
// Label: 'freeze ', // Operation name
// permission: "// permission point
// type: 'info', // button type
// handleRow: function(){} // Custom event
/ /},
/ /]
/ /}
/ /},
export default {
 // Receive the value passed by the parent component
 props: {
   // Objects for table data and table part attributes
   // eslint-disable-next-line vue/require-default-prop
   dataSource: {
     type: Object}},data() {
   return{}},watch: {
   'dataSource.cols': { // Listen for table column changes
     deep: true.handler() {
       // Resolve the jitter problem of table column changes
       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(` per page${val}Article `)},handleCurrentChange(val) {
     this.$emit('changeNum', val)
     console.log('Current page:${val}`)},// Click the row to select
   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>

Copy the code

In actual combat

Configure a page, let’s first see the configuration picture is not more trouble, and clear

<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 {
      // Search bar configuration
      filterData: {
        timeSelect: false.elselect: [{name: 'state'.width: 120.key: 'platform'.option: [{key: 'all'.value: 'all'
              },
              {
                key: 1.value: 'IOS'
              },
              {
                key: 2.value: 'android'}]}]},// Table configuration
      dataSource: {
        tool: [{
          name: 'New Version'.key: 1.permission: 2010701.handleClick: this.handleAdd
        }],
        data: [].// Tabular data
        cols: [{label: 'Release Time'.prop: 'appIssueTime'.isCodeTableFormatter: function(val) {
              return timeFormat(val.appIssueTime)
            }
          },
          {
            label: 'the name of the APP.prop: 'appName'
          },
          {
            label: 'the APP version'.prop: 'appVersion'
          },
          {
            label: 'platform'.prop: 'appPlatform'.isCodeTableFormatter: function(val) {
              if (val.appPlatform === 1) {
                return 'IOS'
              } else {
                return 'Android'}}}, {label: 'Automatic update or not'.prop: 'appAutoUpdate'.isCodeTableFormatter: function(val) {
              if (val.appAutoUpdate === 1) {
                return 'is'
              } else {
                return 'no'}}}, {label: 'Update Description'.prop: 'appDesc'.width: 300
          },
          {
            label: 'Download address'.prop: 'downloadAddr'
          },
          {
            label: 'Publisher'.prop: 'userName'}].// Table column data
        handleSelectionChange: this.handleSelectionChange,
        isSelection: false.// If the table has multiple options
        isOperation: true.// Set when the table has action columns
        isIndex: true.// Number of the list
        loading: true.// loading
        pageData: {
          total: 0./ / the total number of article
          pageSize: 10.// Number of pages
          pageNum: 1 / / page
        },
        operation: {
          // Set when the table has action columns
          label: 'operation'./ / column name
          width: '100'.// Give the width according to the actual situation
          data: [{label: 'delete'.// The name of the operation
              type: 'danger'.permission: '2010702'.// The permissions of the later operation are used to control the permissions
              handleRow: this.handleRow
            }
          ]
        }
      },
      dialogAdd: false.msg: {},
      selected: []}},created() {
    this.getList()
  },
  methods: {
    // Get the tabular data
    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 === 'android') {
          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}}})},// Search layer events
    filterMsg(msg) {
      this.msg = msg
      if (Object.keys(msg).length > 0) {
        this.getList(msg)
      } else {
        this.getList()
      }
    },
    // Subcomponent communication
    childMsg(msg) {
      if (msg.dialogAdd === false) {
        this.dialogAdd = false
      } else if (msg.refreshList) {
        this.getList()
      }
    },
    // Change the number of pages
    changeSize(size) {
      this.dataSource.pageData.pageSize = size
      this.getList()
    },
    // Change page number
    changeNum(pageNum) {
      this.dataSource.pageData.pageNum = pageNum
      this.getList()
    },
    // Select multiple events
    handleSelectionChange(val) {
      this.selected = val
    },
    // The toolbar above the table callback
    handleAdd(index, row) {
      this.dialogAdd = true
    },
    // Table operation column callback
    handleRow(index, row, lable) {
      if (lable === 'delete') {
        this.$confirm('Are you sure to delete this version? '.'Warm tip', {
          confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'
        }).then(() = > {
          delVersion({ versionId: row.id }).then(res= > {
            if (res.succeed) {
              this.$message.success('Deletion succeeded')
              this.getList()
            }
          })
        }).catch(() = >{})}}}}</script>

<style  scoped lang='scss'>

</style>
Copy the code

At the end

Filterpane.vue, TablePane.vue have been completed, some special pages just need to copy down to the components of the current special page to change the next

Ok, it is still in continuous improvement, we have any questions can be put forward, or further optimization.

The full source file is available for download on gitHub and will be updated continuously

I gave it the name k-table and it starts with k for fast

K-table if it helps you, please light up your little star ⭐⭐⭐ oh ~ (crazy hint)

Super simple API, provide hand – in – hand teaching you to use examples!

Write in the last

I am Liangcheng A, a front end, who loves technology and life.

I’m glad to meet you.

If you want to learn more, please click here and look forward to your little ⭐⭐

  • Feel free to correct any mistakes in the comments section, and if this post helped you, feel free to like and follow 😊

  • This article was originally published in Nuggets and cannot be reproduced without permission at 💌