具有网格布局的可重用Vue JS搜索过滤器

时间:2018-05-18 18:30:34

标签: javascript vue.js vuejs2

因此,经过大约2-3周的阅读,尝试简单的例子,并在VueJs中采取婴儿步骤,尝试更复杂的用例,我被卡住了。

创建一个基本搜索方法,按标题过滤数据,并以3列网格布局迭代返回结果。

现在我需要让这个东西可以重复使用。

  1. 有多个部分(如section2,section3,...),每个部分都有相应的内容数据(contents2,contents3,...),每个部分都响应过滤。例如,如果我的第2节有歌曲标题而不是Evernote和Pinterest,那么仍然会以相同的方式工作,并在3列中列出内容。
  2. 鉴于每个部分的过滤机制基本相同,想要将其用作计算属性而不是方法(+性能增益),但不确定如何识别每个部分的内容(例如第3部分的content3) ,并在3列网格中返回已过滤的内容,而不会影响其他部分。
  3. 当然,'暴力'方式是为每个内容数组编写一种过滤方法。但实际上,我只是一个VueJs noob谁知道(2)是可能的,不能编码。

    提前感谢任何指示。

    HTML

    <div id="app">
      <h2>Search</h2>
      <div><input type="text" v-model="searchString" placeholder="Search" /></div>
    
      <section id="section1">
        <div class="row" v-for="i in rowCount" :key="i.id">
          <div v-for="content in filtered1(i)" :key="content.id" class="one-third">
            <img :src="content.image" class="center-block" />
            <h3>{{content.title}}</h3>
            <p class="m-t-content tablet">{{content.description}}</p>
            <p class="m-t-content mobile-center tablet"><a :href="content.url">Read more</a></p>
          </div>
        </div>
      </section>
    
      <section id="section2">
      </section>  
    </div>
    

    JS

    new Vue({
      el: "#app",
      data () {
        return {
          searchString: '',
          itemsPerRow: 3,
          contents1: [
            {
              'title': 'Android',
              'url': '/',
              'description': 'Lorem ipsum dolor sit amet.',
              'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/android-icon.png'
            },
            {
              'title': 'Pinterest',
              'url': '/',
              'description': 'Consectetur adipiscing elit.',
              'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/pinterest-icon.png'
            },
            {
              'title': 'Behance',
              'url': '/',
              'description': 'Pellentesque pulvinar nisi.',
              'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/behance-icon.png'
            },
            {
              'title': 'Evernote',
              'url': '/',
              'description': 'Id tincidunt orci elementum.',
              'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/evernote-icon.png'
            },
            {
              'title': 'Github',
              'url': '/',
              'description': 'Lorem ipsum dolor sit amet.',
              'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/github-icon.png'
            }
          ]
        }
      },
      computed: {
        rowCount: function () {
          return Math.ceil(this.contents1.length / this.itemsPerRow)
        }
      },
      methods: {
        filtered1: function (index) {
          var contentsArray = this.contents1
          var searchString = this.searchString
    
          if (!searchString) {
            return contentsArray.slice((index - 1) * this.itemsPerRow, index * this.itemsPerRow)
          }
    
          searchString = searchString.trim().toLowerCase()
    
          contentsArray = contentsArray.filter(function (item) {
            if (item.title.toLowerCase().indexOf(searchString) !== -1) {
              return item
            }
          })
    
          return contentsArray.slice((index - 1) * this.itemsPerRow, index * this.itemsPerRow)
        }
      }
    })
    

    CSS

    body {
      background: #fff;
      padding: 20px;
      font-family: Helvetica;
    }
    
    #app {
      background: #fff;
      border-radius: 4px;
      padding: 20px;
      transition: all 0.2s;
    }
    
    li {
      margin: 8px 0;
    }
    
    h2 {
      font-weight: bold;
      margin-bottom: 15px;
    }
    
    h3 {
      font-weight: bold;
      margin-bottom: 5px;
    }
    
    del {
      color: rgba(0, 0, 0, 0.3);
    }
    
    .one-third {
      box-sizing: border-box;
      float: left;
      margin: 0 1%;
      width: 31.33%;  
    }
    
    .row {
      margin: 10px 0;
    }
    
    .row:after {
      content: "";
      display: table;
      clear: both;
    }
    

    Fiddle here

2 个答案:

答案 0 :(得分:2)

一个非常简单的实现方法是将您的Vue转换为一个组件,并将每个渲染元素的内容暴露给一个插槽,以便父级可以设置自定义内容。

这是我的意思的一个例子。

&#13;
&#13;
console.clear()

Vue.component("filtered", {
	template: `
  <div>
    <h2>Search</h2>
    <div><input type="text" v-model="searchString" placeholder="Search" /></div>

    <section>
    <div class="row" v-for="i in rowCount" :key="i">
      <div v-for="content in filtered.slice(itemsPerRow * (i - 1), itemsPerRow * i)" :key="content[keyColumn]" class="one-third">
				<slot :content="content" />
      </div>
    </div>
    </section>  
  </div>
	`,
  props: ["contents", "itemsPerRow", "filterColumns", "keyColumn"],
  data(){
  	return {
    	searchString: ''
    }
  },
  computed: {
    rowCount: function () {
      return Math.ceil(this.filtered.length / this.itemsPerRow)
    },
    filtered(){
      let results = this.contents
      if (!this.searchString)
        return results
        
      let searchString = this.searchString.toLowerCase()
        
      return results.filter(item => {
        for (let column of this.filterColumns)
          if (item[column].toLowerCase().includes(searchString))
            return true
        
        return false
      })
      
    }
  },
})

new Vue({
  el: "#app",
  data () {
    return {
      records:[
        {
          name: "Dark Side of the Moon",
          artist: "Pink Floyd"
        },
        {
          name: "Wish You Were Here",
          artist: "Pink Floyd",
        },
        {
          name: "The Joshua Tree",
          artist: "U2"
        }
      ],
      contents1: [
        {
          'title': 'Android',
          'url': '/',
          'description': 'Lorem ipsum dolor sit amet.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/android-icon.png'
        },
        {
          'title': 'Pinterest',
          'url': '/',
          'description': 'Consectetur adipiscing elit.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/pinterest-icon.png'
        },
        {
          'title': 'Behance',
          'url': '/',
          'description': 'Pellentesque pulvinar nisi.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/behance-icon.png'
        },
        {
          'title': 'Evernote',
          'url': '/',
          'description': 'Id tincidunt orci elementum.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/evernote-icon.png'
        },
        {
          'title': 'Github',
          'url': '/',
          'description': 'Lorem ipsum dolor sit amet.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/github-icon.png'
        }
      ]
    }
  },
})
&#13;
body {
  background: #fff;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

h3 {
  font-weight: bold;
  margin-bottom: 5px;
}

del {
  color: rgba(0, 0, 0, 0.3);
}

.one-third {
  box-sizing: border-box;
  float: left;
  margin: 0 1%;
  width: 31.33%;  
}

.row {
  margin: 10px 0;
}

.row:after {
  content: "";
  display: table;
  clear: both;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <filtered :contents="records" :items-per-row="2" :filter-columns="['name']" :key-column="'name'">
    <template slot-scope="{content}">
      <h4>{{content.name}}</h4>
    </template>
  </filtered>
  <filtered :contents="contents1" :items-per-row="3" :filter-columns="['title', 'description']" :key-column="'title'">
    <template slot-scope="{content}">
      <img :src="content.image" class="center-block" />
        <h3>{{content.title}}</h3>
        <p class="m-t-content tablet">{{content.description}}</p>
        <p class="m-t-content mobile-center tablet"><a :href="content.url">Read more</a></p>
    </template>
  </filtered>
</div>
&#13;
&#13;
&#13;

根据评论中的进一步讨论,您可以使用以下公共搜索字符串:

&#13;
&#13;
console.clear()

Vue.component("filtered", {
  props: ["contents","itemsPerRow", "filterColumns", "keyColumn", "searchString"],
	template: `
    <section>
    <div class="row" v-for="i in rowCount" :key="i">
      <div v-for="content in filtered.slice(itemsPerRow * (i - 1), itemsPerRow * i)" :key="content[keyColumn]" class="one-third">
				<slot :content="content" />
      </div>
    </div>
    </section>  
	`,
  computed: {
    rowCount: function () {
      return Math.ceil(this.filtered.length / this.itemsPerRow)
    },
    filtered(){
      let results = this.contents
      if (!this.searchString)
        return results
        
      let searchString = this.searchString.toLowerCase()
        
      return results.filter(item => {
        for (let column of this.filterColumns)
          if (item[column].toLowerCase().includes(searchString))
            return true
        
        return false
      })
      
    }
  },
})

new Vue({
  el: "#app",
  data () {
    return {
      searchString: '',
      records:[
        {
          name: "Dark Side of the Moon",
          artist: "Pink Floyd"
        },
        {
          name: "Wish You Were Here",
          artist: "Pink Floyd",
        },
        {
          name: "The Joshua Tree",
          artist: "U2"
        }
      ],
      contents1: [
        {
          'title': 'Android',
          'url': '/',
          'description': 'Lorem ipsum dolor sit amet.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/android-icon.png'
        },
        {
          'title': 'Pinterest',
          'url': '/',
          'description': 'Consectetur adipiscing elit.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/pinterest-icon.png'
        },
        {
          'title': 'Behance',
          'url': '/',
          'description': 'Pellentesque pulvinar nisi.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/behance-icon.png'
        },
        {
          'title': 'Evernote',
          'url': '/',
          'description': 'Id tincidunt orci elementum.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/evernote-icon.png'
        },
        {
          'title': 'Github',
          'url': '/',
          'description': 'Lorem ipsum dolor sit amet.',
          'image': 'http://icons.iconarchive.com/icons/danleech/simple/64/github-icon.png'
        }
      ]
    }
  },
})
&#13;
body {
  background: #fff;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

h3 {
  font-weight: bold;
  margin-bottom: 5px;
}

del {
  color: rgba(0, 0, 0, 0.3);
}

.one-third {
  box-sizing: border-box;
  float: left;
  margin: 0 1%;
  width: 31.33%;  
}

.row {
  margin: 10px 0;
}

.row:after {
  content: "";
  display: table;
  clear: both;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <h2>Search</h2>
    <div><input type="text" v-model="searchString" placeholder="Search" /></div>

  <filtered :contents="records" 
            :items-per-row="2" 
            :filter-columns="['name']" 
            :key-column="'name'"
            :search-string="searchString">
    <template slot-scope="{content}">
      <h4>{{content.name}}</h4>
    </template>
  </filtered>
  
  <filtered :contents="contents1" 
            :items-per-row="3" 
            :filter-columns="['title', 'description']" 
            :key-column="'title'"
            :search-string="searchString">
    <template slot-scope="{content}">
      <img :src="content.image" class="center-block" />
      <h3>{{content.title}}</h3>
      <p class="m-t-content tablet">{{content.description}}</p>
      <p class="m-t-content mobile-center tablet"><a :href="content.url">Read more</a></p>
    </template>
  </filtered>
</div>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

检查一下!

一个基于VUEJS的非常简单而强大的网格,该示例与ASP.NET MVC相结合

https://github.com/Dariush-Tasdighi/LEARNING_VUE

Learn_1000操作和视图具有过滤器!