How to add a loading icon while searching? (Vue.js 2)

北战南征 提交于 2019-12-19 04:10:04

问题


My component is like this :

<template>
    ...
        <input type="text" class="form-control" v-model="rawFilter" placeholder="Search" @keyup="getPlayers">
    ...
</template>

<script>
    import _ from 'lodash'
    ...
    export default {
        ...
        data() {
            return{
                msg:'hello vue',
                rawFilter:'',
                loading:false
            }
        },
        ...
        methods: {
            getPlayers: _.debounce(function(e) {
                const text = e.target.value.trim()
                this.$store.dispatch('getPlayers', {
                  q: text
                })
            },1000),
            ...
        }
    }
</script>

When I searching, before display the data, I want add a loading icon

How can I do it in vue.js 2?


回答1:


For usability's sake, I'll suggest using a loader which has its own vuex state.

  1. This will allow you to have control over it from any component.
  2. You can use it easily by means of simple function calls.
  3. Naturally avoid props and events.

First define where you would need this particular loader:

  1. Is it to be used for all api calls?
  2. Some browser intensive task (like processing an loaded file).
  3. Or something more specific in nature (maybe show the loader only when the user is trying to login)

If your loader is not tightly coupled to any component like in case 1. Then it would make more sens to keep your loader in your main vue file (if you are using vue-cli then App.vue)

Something like so:

<template>
  <div id="app">
    <loader></loader>
    <router-view></router-view>
  </div>
</template>

<script>
import Loader from './components/shared/loader/Loader'

export default {
  name: 'app',
  components: {
    Loader
  }
}
</script>

With this, you don't have to add loader.vue in every other component file. But first, I'll show you the loader component and store I am using.

<template>
  <div class='loader-container' :class='{"show": show, "hidden": !show}'>
    <div class="curved-div">
      <div class="colour-magic">
        <i class='fa fa-circle-o-notch rotate'></i>
      </div>
      <div class="loading">
        {{ loading }}
      </div>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import * as NameSpace from '../../../store/NameSpace'

export default {
  data () {
    return {
      loading: 'Loading...'
    }
  },
  computed: {
    ...mapGetters({
      show: NameSpace.GET_LOADER_STATE
    })
  }
}
</script>

<style scoped>
.loader-container {
  position: fixed;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.8);
}

.curved-div {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateX(-50%);
  border-radius: .3rem;
  width: 20rem;
  padding:1rem;
  background: white;
  box-shadow: 0 0 .1rem #fefefe;
}

.curved-div > * {
  display: inline-block;
}

.rotate {
  border-radius: 50%;
  padding: .5rem;
  animation-name: rotate;
  animation-duration: .7s;
  animation-iteration-count: infinite;
  animation-delay: 0s;
}

.loading {
  text-align: center;
  width: 12rem;
  font-size: 1.8rem;
}

.show {
  visibility: visible;
  opacity: 1;
  z-index: 1;
  transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}

.hidden {
  opacity: 0;
  visibility: hidden;
  z-index: 0;
  transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}

@keyframes rotate {
  0% {
    transform: rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg);
  }
}

.colour-magic {
  animation-name: colorMagic;
  animation-duration: 20s;
  animation-iteration-count: infinite;
  animation-delay: 0s;
}

@keyframes colorMagic {
  0% { color: rgb(179,10,10); }
  10% { color: rgb(227,132,22); }
  20% { color: rgb(164,153,7); }
  30% { color: rgb(26,171,19); }
  40% { color: rgb(19,144,177); }
  50% { color: rgb(14,16,221); }
  60% { color: rgb(27,9,98); }
  70% { color: rgb(58,11,111); }
  80% { color: rgb(126,14,129); }
  90% { color: rgb(208,19,121); }
  100% { color: rgb(198,18,18); }
}
</style>

Please note that I am using font-awesome for the loader.

and here is the store:

import * as NameSpace from '../NameSpace'
// you can also use the namespace: true in your store and eliminate the need of NameSpace.js    

const state = {
  [NameSpace.LOADER_STATE]: false
}

const getters = {
  [NameSpace.GET_LOADER_STATE]: state => {
    return state[NameSpace.LOADER_STATE]
  }
}

const mutations = {
  [NameSpace.MUTATE_LOADER_STATE]: (state, payload) => {
    state[NameSpace.LOADER_STATE] = payload
  }
}

const actions = {
  [NameSpace.LOADER_SHOW_ACTION]: ({ commit }, payload) => {
    commit(NameSpace.MUTATE_LOADER_STATE, payload)
  }
}

export default {
  state,
  getters,
  mutations,
  actions
}

A usage example:

// This is not a .vue file it is a .js file, therefore a different way of using the store.
import Vue from 'vue'
import * as NameSpace from 'src/store/NameSpace'
import loaderState from 'src/store/modules/loader'

/**
* Pass the mutation function to reduce the text length
* This function can now be used in the api calls to start/stop the loader
* as the api starts and finishes.
*/
let loaderSwitch = loaderState.mutations[NameSpace.MUTATE_LOADER_STATE].bind(null, loaderState.state)

login (username, password) {
  loaderSwitch(true)
  return new Promise((resolve, reject) => {
    SomeEndpoint.logIn(username, password, {
      success (user) {
        loaderSwitch(false)
        resolve(user.attributes)
      },
      error (user, error) {
        loaderSwitch(false)
        reject(errorHelper(error.code))
      }
    })
  })

Now, irrespective of the component where login is used, the loader component need not be kept there.




回答2:


You simply need to attach it to a flag and use v-if, if you're using vue resource, you can set the loading flag to true in the before callback and set it back to false after you receive the response:

Vue Instance

  methods: {
    loadData() {
      this.$http.get('/search', {
        before: () => {
          this.loading = true;
        }
      }).then(response => {
        // Deal with response
      }).then(() => {
          //set loading flag to false
          this.loading = false;
      })
    }
  },
  data: {
    loading: false
  }

HTML

<div id="app">
  <button @click="loadData">
    Get Data
  </button>

  <!-- Only show if loading is true -->
  <div v-if="loading" v-cloak>
    <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
    <span>Loading...</span>
  </div>
</div>

Here's the JSFiddle: https://jsfiddle.net/hyeycoan/




回答3:


You could add a loading div on your template, and toggle it's display based on loading flag. Something like this

new Vue({
  el: '#app',
  data: {
    show: true,
	isLoading: false,  
  },
	methods:{
		loadData: function(){
			this.isLoading = true;
			setTimeout(function(){
				this.isLoading = false;
			}.bind(this),1000);
		}
	}
})
.loading{
	display: none;
	position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    background: rgba(128, 128, 128, 0.5);
}
.loading.show{
	display: initial;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<div id="app">
	<div class="loading" v-bind:class="{ show: isLoading }">
		<span>Loading</span>
	</div>
	<button @click="loadData">Load</button>
</div>	
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
</html>

You could make the loading indicator pretty with css, or you can use already available one, for example you could use http://tobiasahlin.com/spinkit/, http://loading.io/



来源:https://stackoverflow.com/questions/42454027/how-to-add-a-loading-icon-while-searching-vue-js-2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!