封裝element-ui表格,我是這樣作的

日日加班至夜半,環視周圍無人走;

明晚八點準時走,誰不打卡誰是狗。javascript

使用過element-ui的表格的同窗應該都有這樣的體會,作一個簡單的表格還比較容易,但若是這個表格包含了頂部的按鈕,還有分頁,甚至再包含了行編輯,那開發工做量就成倍的增長,特別是在開發管理系統的時候,表格一個接一個的去開發, 即浪費時間,還對我的沒有什麼提高。今天小編帶來了本身封裝的一個表格,讓你用JSON就能夠簡單的生成表格。html

本文主要集中於使用說明與核心代碼說明,完整代碼請訪問 https://github.com/snowzijun/vue-element-table,若是以爲有用,麻煩給小編一個 star,你的每個 star都是對小編的支持,當前功能比較簡陋,本倉庫將持續更新。同時您也能夠微信搜索【前端有的玩】公衆號,小編拉你進前端技術交流羣。

表格需求

通常管理系統對錶格會有如下需求前端

  1. 能夠分頁(須要有分頁條)
  2. 能夠多選(表格帶複選框)
  3. 頂部須要加一些操做按鈕(新增,刪除等等)
  4. 表格每行行尾有操做按鈕
  5. 表格行能夠編輯

以下圖爲一個示例表格vue

若是咱們直接使用element-ui提供的組件的話,那麼開發一個這樣的表格就須要使用到如下內容java

  1. 須要使用表格的插槽功能,開發每一行的按鈕
  2. 須要經過樣式調整頂部按鈕,表格,分頁條的佈局樣式
  3. 須要監聽分頁的事件而後去刷新表格數據
  4. 頂部按鈕或操做按鈕若是須要獲取表格數據,須要調用表格提供的api
  5. 對於有行編輯的需求,還須要經過插槽去渲染行編輯的內容,同時要控制行編輯的開關

不只僅開發表格比較麻煩,並且還要考慮團隊協做,若是每一個人實現表格的方式存在差異,那麼可能後期的維護成本也會變得很高。那怎麼辦呢?git

項目安裝

安裝插件

在使用element-ui的項目中,能夠經過如下命令進行安裝github

npm install vue-elementui-table -S

在項目中使用

main.js中添加如下代碼shell

import ZjTable from 'vue-element-table'

Vue.use(ZjTable)

而後便可像下文中的使用方式進行使用npm

表格配置

爲了知足團隊快速開發的須要,小編對上面提出來的需求進行了封裝,而後使用的時候,開發人員只須要配置一些JSON即可以完成以上功能的開發。element-ui

基礎配置

一個基礎的表格包含了數據和列信息,那麼如何用封裝的表格去配置呢?

<template>
  <zj-table
    :columns="columns"
    :data="data"
    :pagination="false"
  />
</template>
<script>
export default {
  data() {
    return {
      // 表格的列信息, 數組每一項表明一個字段,可使用element 列屬性的全部屬性,如下僅爲示例
      columns: Object.freeze([
        {
          // 表頭顯示的文字
          label: '姓名',
          // 對應數據裏面的字段
          prop: 'name'
        },
        {
          label: '性別',
          prop: 'sex',
          // 格式化表格,與element-ui 的表格屬性相同
          formatter(row, column, cellValue) {
            return cellValue === 1 ? '男' : '女'
          }
        },
        {
          label: '年齡',
          prop: 'age'
        }
      ]),
      data: [
        {
          name: '子君',
          sex: 1,
          age: 18
        }
      ]
    }
  }
}
</script>

經過上面的配置,就能夠完成一個基礎表格的開發,完整代碼見 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/base.vue,效果以下圖所示

表格默認會顯示覆選框,也能夠經過配置selectable屬性來關閉掉

添加分頁

簡單的表格用封裝以後的或未封裝的開發工做量區別並不大,咱們繼續爲表格添加上分頁

<template>
    <!--
    current-page.sync 表示頁碼, 添加上 .sync 在頁碼發生變化時自動同步頁碼
    page-size.sync 每頁條數
    total  總條數
    height="auto" 配置height:auto, 表格高度會根據內容自動調整,若是不指定,表格將保持充滿父容器,同時表頭會固定,不跟隨滾動條滾動
    @page-change 不管pageSize currentPage 哪個變化,都會觸發這個事件
  -->
  <zj-table
    v-loading="loading"
    :columns="columns"
    :data="data"
    :current-page.sync="currentPage"
    :page-size.sync="pageSize"
    :total="total"
    height="auto"
    @page-change="$_handlePageChange"
  />
</template>
<script>
export default {
  data() {
    return {
      columns: Object.freeze([
        // 列字段與上例同樣,此處省略
      ]),
      data: [],
      // 當前頁碼
      currentPage: 1,
      // 每頁條數
      pageSize: 10,
      // 總條數
      total: 0,
      // 是否顯示loading
      loading: false
    }
  },
  created() {
    this.loadData()
  },
  methods: {
    // 加載表格數據
    loadData() {
      this.loading = true
      setTimeout(() => {
        // 假設總條數是40條
        this.total = 40
        const { currentPage, pageSize } = this
        // 模擬數據請求獲取數據
        this.data = new Array(pageSize).fill({}).map((item, index) => {
          return {
            name: `子君${currentPage + (index + 1) * 10}`,
            sex: Math.random() > 0.5 ? 1 : 0,
            age: Math.floor(Math.random() * 100)
          }
        })
        this.loading = false
      }, 1000)
    },
    $_handlePageChange() {
      // 由於上面設置屬性指定了.sync,因此這兩個屬性會自動變化
      console.log(this.pageSize, this.currentPage)
      // 分頁發生變化,從新請求數據
      this.loadData()
    }
  }
}
</script>

完整代碼請參考 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/pagination.vue

經過封裝,表格將自帶分頁功能,經過上面代碼,實現效果以下所示,是否是變得簡單了一些。接下來咱們繼續給表格添加按鈕

添加頂部按鈕

表格上面可能會有新增,刪除等等按鈕,怎麼辦呢,接下來咱們繼續經過配置去添加按鈕

<template>
  <zj-table
    :buttons="buttons"
  />
</template>
<script>
export default {
  data() {
    return {
      buttons: Object.freeze([
        {
          // id 必須有並且是在當前按鈕數組裏面是惟一的
          id: 'add',
          text: '新增',
          type: 'primary',
          icon: 'el-icon-circle-plus',
          click: this.$_handleAdd
        },
        {
          id: 'delete',
          text: '刪除',
          // rows 是表格選中的行,若是沒有選中行,則禁用刪除按鈕, disabled能夠是一個boolean值或者函數
          disabled: rows => !rows.length,
          click: this.$_handleRemove
        },
        {
          id: 'auth',
          text: '這個按鈕根據權限顯示',
          // 能夠經過返回 true/false來控制按鈕是否顯示
          before: (/** rows */) => {
            return true
          }
        },
        // 能夠配置下拉按鈕哦
        {
          id: 'dropdown',
          text: '下拉按鈕',
          children: [
            {
              id: 'moveUp',
              text: '上移',
              icon: 'el-icon-arrow-up',
              click: () => {
                console.log('上移')
              }
            },
            {
              id: 'moveDown',
              text: '下移',
              icon: 'el-icon-arrow-down',
              disabled: rows => !rows.length,
              click: () => {
                console.log('下移')
              }
            }
          ]
        }
      ])
    }
  },
  created() {},
  methods: {
    // 新增
    $_handleAdd() {
      this.$alert('點擊了新增按鈕')
    },
    // 頂部按鈕會自動將表格所選的行傳出來
    $_handleRemove(rows) {
      const ids = rows.map(({ id }) => id)
      this.$alert(`要刪除的行id爲${ids.join(',')}`)
    },
    // 關注做者公衆號
    $_handleFollowAuthor() {}
  }
}
</script>

表格頂部能夠添加普通的按鈕,也能夠添加下拉按鈕,同時還能夠經過before來配置按鈕是否顯示,disabled來配置按鈕是否禁用,上面完整代碼見 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/button.vue

經過上面的代碼就能夠配置出下面的表格,是否是很簡單呢?

表格頂部能夠有按鈕,行尾也是能夠添加按鈕的,一塊兒來看看

行操做按鈕

通常咱們會將一些單行操做的按鈕放在行尾,好比編輯,下載等按鈕,那如何給行尾配置按鈕呢?

<template>
  <zj-table
    :columns="columns"
  />
</template>
<script>
export default {
  data() {
    return {
      columns: Object.freeze([
        {
          // 能夠指定列的寬度,與element-ui原生用法一致
          width: 220,
          label: '姓名',
          prop: 'name'
        },
        // 行編輯按鈕,在表格末尾出現,自動鎖定右側
        {
          width: 180,
          label: '操做',
          // 經過 actions 指定行尾按鈕
          actions: [
            {
              id: 'follow',
              text: '關注做者',
              click: this.$_handleFollowAuthor
            },
            {
              id: 'edit',
              text: '編輯',
              // 能夠經過before控制按鈕是否顯示,好比下面年齡四十歲的纔會顯示編輯按鈕
              before(row) {
                return row.age < 40
              },
              click: this.$_handleEdit
            },
            {
              id: 'delete',
              text: '刪除',
              icon: 'el-icon-delete',
              disabled(row) {
                return row.sex === 0
              },
              // 爲了拿到this,這裏須要用箭頭函數
              click: () => {
                this.$alert('女生被禁止刪除了')
              }
            }
          ]
        }
      ])
    }
  },
  methods: {
    // 關注做者公衆號
    $_handleFollowAuthor() {
            console.log('微信搜索【前端有的玩】,這是對小編最大的支持')
    },
    /**
     * row 這一行的數據
     */
    $_handleEdit(row, column) {
      this.$alert(`點擊了姓名爲【${row.name}】的行上的按鈕`)
    }
  }
}
</script>

行操做按鈕會被凍結到表格最右側,不會跟隨滾動條滾動而滾動,上面完整代碼見, https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/button.vue

經過上面的代碼就能夠完成如下效果

最後再來一塊兒看看行編輯

行編輯

好比上例,我但願點擊行尾的編輯按鈕的時候,能夠直接在行上面編輯用戶的姓名與性別,如何配置呢?

<template>
  <zj-table
    ref="table"
    :columns="columns"
    :data="data"
  />
</template>
<script>
export default {
  data() {
    return {
      columns: Object.freeze([
        {
          label: '姓名',
          prop: 'name',
          editable: true,
          field: {
            componentType: 'input',
            rules: [
              {
                required: true,
                message: '請輸入姓名'
              }
            ]
          }
        },
        {
          label: '性別',
          prop: 'sex',
          // 格式化表格,與element-ui 的表格屬性相同
          formatter(row, column, cellValue) {
            return cellValue === '1' ? '男' : '女'
          },
          editable: true,
          field: {
            componentType: 'select',
            options: [
              {
                label: '男',
                value: '1'
              },
              {
                label: '女',
                value: '0'
              }
            ]
          }
        },
        {
          label: '年齡',
          prop: 'age',
          editable: true,
          field: {
            componentType: 'number'
          }
        },
        {
          label: '操做',
          actions: [
            {
              id: 'edit',
              text: '編輯',
              // 若是當前行啓用了編輯,則不顯示編輯按鈕
              before: row => {
                return !this.editIds.includes(row.id)
              },
              click: this.$_handleEdit
            },
            {
              id: 'save',
              text: '保存',
              // 若是當前行啓用了編輯,則顯示保存按鈕
              before: row => {
                return this.editIds.includes(row.id)
              },
              click: this.$_handleSave
            }
          ]
        }
      ]),
      data: [
        {
          // 行編輯必須指定rowKey字段,默認是id,若是修改成其餘字段,須要給表格指定row-key="字段名"
          id: '0',
          name: '子君',
          sex: '1',
          age: 18
        },
        {
          // 行編輯必須指定rowKey字段,默認是id,若是修改成其餘字段,須要給表格指定row-key="字段名"
          id: '1',
          name: '子君1',
          sex: '0',
          age: 18
        }
      ],
      editIds: []
    }
  },
  methods: {
    $_handleEdit(row) {
      // 經過調用 startEditRow 能夠開啓行編輯
      this.$refs.table.startEditRow(row.id)
      // 記錄開啓了行編輯的id
      this.editIds.push(row.id)
    },
    $_handleSave(row) {
      // 點擊保存的時候,經過endEditRow 結束行編輯
      this.$refs.table.endEditRow(row.id, (valid, result, oldRow) => {
        // 若是有表單驗證,則valid會返回是否驗證成功
        if (valid) {
          console.log('修改以後的數據', result)
          console.log('原始數據', oldRow)
          const index = this.editIds.findIndex(item => item === row.id)
          this.editIds.splice(index, 1)
        } else {
          // 若是校驗失敗,則返回校驗的第一個輸入框的異常信息
          console.log(result)
          this.$message.error(result.message)
        }
      })
    }
  }
}
</script>

不須要使用插槽就能夠完成行編輯,是否是很開心。上述完整代碼見 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/row-edit.vue

效果以下圖所示:

其餘功能

除了上面的功能以外,表格還能夠配置其餘許多功能,好比

  1. 能夠指定字段爲連接列,須要給列配置link屬性
  2. 能夠經過插槽自定義頂部按鈕,行操做按鈕,行字段等
  3. 能夠在按鈕區域右側經過插槽配置其餘內容
  4. 其餘等等

表格開發說明

經過上面的代碼示例,咱們已經知道了封裝以後的表格能夠完成哪些事情,接下來一塊兒來看看錶格是如何實現的。完整代碼見 https://github.com/snowzijun/vue-element-table/tree/master/src/components/zj-table

表格佈局

整個表格是經過JSX來封裝的,由於JSX使用起來更加靈活。對於咱們封裝的表格,咱們從豎向能夠分爲三部分,分別是頂部按鈕區,中間表格區,底部分頁區,如何去實現三個區域的佈局呢,核心代碼以下

render(h) {
    // 按鈕區域
    const toolbar = this.$_renderToolbar(h)
    // 表格區域
    const table = this.$_renderTable(h)
    // 分頁區域
    const page = this.$_renderPage(h)

    return (
      <div class="zj-table" style={{ height: this.tableContainerHeight }}>
        {toolbar}
        {table}
        {page}
      </div>
    )
  }

經過三個render函數分別渲染對應區域,而後將三個區域組合在一塊兒。

渲染表格列

經過前文的講解,咱們能夠將表格的列分爲如下幾種

  1. 常規列
  2. 行編輯列
  3. 操做按鈕列
  4. 插槽列
  5. 連接列(文檔後續完善)
  6. 嵌套列(文檔後續完善)
$_renderColumns(h, columns) {
      // 總體是否排序
      let sortable = this.sortable ? 'custom' : false
      return columns
        .filter(column => {
          const { hidden } = column
          if (hidden !== undefined) {
            if (typeof hidden === 'function') {
              return hidden({
                columns,
                column
              })
            }
            return hidden
          }
          return true
        })
        .map(column => {
          const {
            useSlot = false,
            // 若是存在操做按鈕,則actions爲非空數組
            actions = [],
            // 是否可編輯列, 對於可編輯列須要動態啓用編輯
            editable = false,
            // 是否有嵌套列
            nests,
            // 是否可點擊
            link = false
          } = column
          let newSortable = sortable
          if (column.sortable !== undefined) {
            newSortable = column.sortable ? 'custom' : false
          }
          column = {
            ...column,
            sortable: newSortable
          }
          if (nests && nests.length) {
            // 使用嵌套列
            return this.$_renderNestColumn(h, column)
          } else if (editable) {
            // 使用編輯列
            return this.$_renderEditColumn(h, column)
          } else if (useSlot) {
            // 使用插槽列
            return this.$_renderSlotColumn(h, column)
          } else if (actions && actions.length > 0) {
            // 使用操做列
            column.sortable = false
            return this.$_renderActionColumn(h, column)
          } else if (link) {
            // 使用連接列
            return this.$_renderLinkColumn(h, column)
          } else {
            // 使用默認列
            return this.$_renderDefaultColumn(h, column)
          }
        })
    },

行編輯列

當前表格行編輯支持input,select,datepicker,TimeSelect,InputNumber等組件,具體渲染代碼以下所示

// 編輯單元格
    $_renderEditCell(h, field) {
      const components = {
        input: Input,
        select: ZjSelect,
        date: DatePicker,
        time: TimeSelect,
        number: InputNumber
      }
      const componentType = field.componentType
      const component = components[componentType]
      if (component) {
        return this.$_renderField(h, field, component)
      } else if (componentType === 'custom') {
        // 若是自定義,能夠經過component指定組件
        return this.$_renderField(h, field, field.component)
      }
      return this.$_renderField(h, field, Input)
    },
    $_renderField(h, field, Component) {
      // 編輯行的id字段
      const { rowId, events = {}, nativeEvents = {} } = field

      const getEvents = events => {
        const newEvents = {}
        Object.keys(events).forEach(key => {
          const event = events[key]
          newEvents[key] = (...rest) => {
            const args = [
              ...rest,
              {
                rowId,
                row: this.editRowsData[rowId],
                value: this.editRowsData[rowId][field.prop]
              }
            ]
            return event(...args)
          }
        })
        return newEvents
      }
      // 事件改寫
      const newEvents = getEvents(events)
      const newNativeEvents = getEvents(nativeEvents)
      return (
        <Component
          size="small"
          on={newEvents}
          nativeOn={newNativeEvents}
          v-model={this.editRowsData[rowId][field.prop]}
          {...{
            attrs: field,
            props: field
          }}
        />
      )
    }

總結

這個表格包含了許多功能,文章長度優先,若是以爲有用,能夠經過訪問 https://github.com/snowzijun/vue-element-table 查看完整代碼,本倉庫代碼及文檔小編將持續完善,歡迎star。

結語

不要吹滅你的靈感和你的想象力; 不要成爲你的模型的奴隸。 ——文森特・梵高
相關文章
相關標籤/搜索