vue.js - 關於 vue data-grid 思路與解法

查看:100
本文介绍了vue.js - 關於 vue data-grid 思路與解法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问 题

小弟試做了 data table 的 component,需求是希望可以具備排序, 搜尋, 過濾, 分頁 的功能下面是程式碼地webpackbin

如果無法連線最下面會附上程式碼:

今天的問題是因為這三個功能需求是累計的所以在做法上我選擇透過 filters

<div class="u-tr" v-for="entry in source | filterBy searchQuery | keep 'length' 'length' | orderBy sortKey sortOrders[sortKey] | limitBy size start">
  <div class="u-td" v-for="column in columns" :style="{ minWidth: column.width, flex: column.width ? 'none' : 1 }">
    <slot :name="entry.id + '_' + column.key">{{entry[column.key]}}</slot>
  </div>
</div>

entry in source | filterBy searchQuery | keep 'length' 'length' | orderBy sortKey sortOrders[sortKey] | limitBy size start

在沒有分頁的時候,vue 官方文件已提供一個優秀精簡的範例, 但為了完成 搜尋 + 分頁 複合條件堆疊的需求於是在這個範例我實作了一個 keep directive 我知道這樣做會產生 side effect 並不是一個合理的做法,但是卻是一個最簡單的做法,求更好的思路

Vue.filter('keep', function (val, ref, method) {
  this[ref] = val[method]
  return val
})

另外有一個關於 filterBy 的小問題就是遇到自己客製的搜尋會跟 filterBy 結果不同,關於這點有些不是很明白為什麼在單個字元的過濾上會不一樣

source = this.source.filter((entry) => {
   var result = Object.keys(entry).map((key) => {
     if (typeof entry[key] === 'string') {
       return entry[key].toLowerCase().indexOf(query.toLowerCase()) > -1
     } else {
       return false
     }
   })
   return result.some((r) => r)
 })

下面為完整程式碼

  • App.vue

<style lang="sass" scoped>
.u-table-container {
  overflow: hidden;
  zoom: 1;
}

.u-tr {
  display: flex;
  flex-direction: row;

  .u-td:first-child, .u-th:first-child {
    border-left: none;
  }
}

.u-th {
  font-weight: 700;
}

.u-th,
.u-td {
  min-width: 120px;
  padding: 8px;
  border-left: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
  word-wrap: break-word;
}

.u-table {
  border: 1px solid darken(#ddd, 10%);
}

.u-head {
  .u-th {
    border-bottom-color: darken(#ddd, 20%);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;


    &:hover {
      color: #636363;
    }

    &.scrollbar {
      flex: none;
      border-left: none;
      padding: 0;
      border-bottom: 1px solid darken(#ddd, 10%);
    }
  }
}

.u-body {
  overflow: auto;
  // overflow-y: scroll;
  max-height: 75vh;

  .u-tr {
    &:hover {
      background: lighten(#f3f3f3, 2%);
    }
  }
}

.arrow {
  display: inline-block;
  vertical-align: middle;
  width: 0;
  height: 0;
  margin-left: 5px;
  opacity: 0.66;
}

.arrow.asc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 4px solid #333;
}

.arrow.dsc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #333;
}

.information {
  text-align: right;
  color: rgba(0, 0, 0, .57);
  font-size: .9em;
  padding-right: 5px;
}

.text-center {
  text-align: center;
}

.u-search-form {
  text-align: right;
  position: relative;

  .u-search {
    border-radius: 15px;
    border: 1px solid #ccc;
    margin-bottom: 5px;
    min-width: 180px;
    padding: 5px 20px;
  }

  .ion-ios-search {
    position: absolute;
    right: 10px;
    top: 5px;
    font-size: 1.2em;
  }

}

ul.pagination {
  display: inline-block;
  padding: 0;
  margin: 15px 0 0 0;
}

ul.pagination li {display: inline;}

ul.pagination li a {
  color: #999;
  float: left;
  padding: 8px 16px;
  text-decoration: none;
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
  border-right: 1px solid #ddd;
  
  &:first-child {
    border-left: 1px solid #ddd;
  }
}
ul.pagination li a.active {
    background-color: #4CAF50;
    color: white;
}

ul.pagination li a:hover:not(.active) {background-color: #ddd;}
</style>

<template>

<div class="u-table-container">
  <form id="search" class="u-search-form">
      <input name="query" v-model="searchQuery" class="u-search" placeholder="Search">
      <span class="icon ion-ios-search"></span>
  </form>

  <div class="u-table">
    <div class="u-head">
      <div class="u-tr" :style="{transform: `translate3d(${-this.left}px, 0, 0)`}">
        <div class="u-th"
          v-for="(index, column) in columns"
          :style="{ minWidth: column.width, flex: column.width ? 'none' : 1 }"
          @click.stop="sortBy($event, column.key)">

          <!-- slot head column -->
          <slot :name="'_' + index">{{ column.text }}</slot>

          <!-- sort button -->
          <span class="arrow" v-if="source[0][column.key]"
           :class="sortOrders[column.key] > 0 ? 'asc' : 'dsc'">

        </div>
        <div v-if="edge" class="u-th scrollbar" :style="{ minWidth: this.edge + 'px' }"></div>
      </div>
    </div>
    <div class="u-body" @scroll.stop.prevent="onHorizontalScroll">
      <div class="u-tr" v-for="entry in source | filterBy searchQuery | keep 'length' 'length' | orderBy sortKey sortOrders[sortKey] | limitBy size start">
        <div class="u-td" v-for="column in columns" :style="{ minWidth: column.width, flex: column.width ? 'none' : 1 }">
          <slot :name="entry.id + '_' + column.key">{{entry[column.key]}}</slot>
        </div>
      </div>
    </div>
  </div>

  <div class="information">Total {{source.length}} Records, Now {{ length }} Records, {{pages}} Pages</div>
  <div class="text-center">
    <ul class="pagination">
      <li @click.stop.prevent="jump('prev')">
        <a href="#" aria-label="Previous 10"><span aria-hidden="true" class="fa fa-angle-double-left"></span></a>
      </li>
      <li @click.stop.prevent="paginate(currentPage - 1)">
        <a href="#" aria-label="Previous"><span aria-hidden="true" class="fa fa-angle-left"></span></a>
      </li>
      <li v-for="n in scope" :class="{'active': n === this.currentPage}"><a href="#" @click.stop.prevent="paginate(n)">{{ n }}</a></li>
      <li @click.stop.prevent="paginate(currentPage + 1)">
        <a href="#" aria-label="Next"><span aria-hidden="true" class="fa fa-angle-right"></span></a>
      </li>
      <li @click.stop.prevent="jump('next')">
        <a href="#" aria-label="Next 10"><span aria-hidden="true" class="fa fa-angle-double-right"></span></a>
      </li>
    </ul>
  </div>
</div>

</template>

<script>

export default {
  props: {
    source: {
      type: Array
    },
    columns: {
      type: Array
    },
    // count per page
    size: {
      type: Number
    },
    paginatorSize: {
      type: Number,
      default: 10
    }
  },
  data () {
    var sortOrders = {}
    this.columns.forEach(function (column) {
      if (column.key)
        sortOrders[column.key] = 1
    })

    return {
      left: 0,        // header row offset position.
      edge: 0,        // offset of scrollbar width.
      sortKey: '',    // column name of sort
      sortOrders: sortOrders,
      currentPage: 1,
      searchQuery: null,
      length: this.source.length // total source count include filter result
    }
  },
  ready () {
    var clientWidth = document.querySelector('.u-body').clientWidth
    var offsetWidth = document.querySelector('.u-body').offsetWidth
    this.edge = offsetWidth - clientWidth;
  },
  methods: {
    onHorizontalScroll (e) {
      this.left = document.querySelector('.u-body').scrollLeft
    },
    sortBy (e, key) {
      this.sortKey = key
      this.sortOrders[key] = this.sortOrders[key] * -1
    },
    paginate (page) {
      if (page < 1) page = 1
      if (page > this.pages) page = this.pages
      this.currentPage = page
    },
    /**
     * jump to first page of N scope
     * @param  {String} dir [value is 'next' or 'prev', next or prev scope]
     */
    jump (dir = 'next') {
      var first = this.scope[0]
      var last = this.scope.slice(-1)[0]
      var page = this.currentPage // default

      switch (dir) {
        case 'next':
          page = (first + this.paginatorSize) > this.pages ? last : (first + this.paginatorSize)
          break;
        case 'prev':
          page = (first - this.paginatorSize) < 1 ? 1 : (first - this.paginatorSize)
          break;
        default:
          break;
      }
      this.currentPage = page
    }
  },
  computed: {
    start () {
      return (this.currentPage - 1) * this.size
    },
    // current pages e.g. current page is 1 then reutrn [1..10]
    scope () {
      var n = 1
      while (this.currentPage > this.paginatorSize * n) {
        n++
      }
      var startPage = this.paginatorSize * (n - 1) + 1
      var endPage = this.paginatorSize * n > this.pages ? this.pages : this.paginatorSize * n
      var arr = []
      for (var i = startPage; i <= endPage; i++) {
        arr.push(i)
      }
      return arr
    },
    pages () {
      var query = this.searchQuery
      var source = []
      var len = 0
      if (query) {
        // Using keep to save length by filter
        len = this.length
      } else {
        len = this.source.length
      }

      var pages = Math.ceil(len / this.size)
      if (this.currentPage > pages) {
        this.currentPage = 1
      }

      return pages
    }
  }
}
</script>

  • main.js

import Vue from 'vue'
import App from './App.vue'

Vue.filter('count', function (val) {
  if (!val) return 0
  var isArray = Array.isArray ? Array.isArray : (v) => {return v instanceof Array}
  return isArray(val) ? val.length :  Array.concat.call(null, val)
})

Vue.filter('keep', function (val, ref, method) {
  this[ref] = val[method]
  return val
})

new Vue({
  el: 'body',
  components: { App }
})

  • index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet">
  </head>
  <body>
    <app :source="[{name: 'A'}, {name: 'B'}, {name: 'C'}, {name: 'D'}, {name: 'B'}, {name: 'E'}, {name: 'F'}]" 
         :columns="[{key: 'name', text: '名稱'}]" 
         :paginator-size="10"
         :size="2" :></app>
    <script src="main.js"></script>
  </body>
</html>

另外一篇相同思維的文章但步驟比較詳細

解决方案

不要把filter条件都放到template里 这样不好控制是否使用某个filter
可以把筛选后得到的数组用computed的方法来实现

https://segmentfault.com/n/1330000005620028

这篇关于vue.js - 關於 vue data-grid 思路與解法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆