步骤
1、UI文件夹下新建BaseSpinner.vue作为载入动画使用,并在main.js中导入供全局使用
<template>
<div class="spinner">
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</template>
<style scoped>
.spinner {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: #3d008d;
margin: -4px 0 0 -4px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router.js';
import store from './stores';
import BaseCard from './components/ui/BaseCard.vue';
import BaseButton from './components/ui/BaseButton.vue';
import BaseBadge from './components/ui/BaseBadge.vue';
import BaseSpinner from './components/ui/BaseSpinner.vue';
const app = createApp(App);
app.use(router);
app.use(store);
app.component('base-card', BaseCard);
app.component('base-button', BaseButton);
app.component('base-badge', BaseBadge);
app.component('base-spinner', BaseSpinner);
app.mount('#app');
2、pages/coaches/CoachesList.vue
添加isLoading: false,
更新loadCoaches方法
添加base-spinner组件
<template>
<section>
<coach-filter @change-filter="setFilters"></coach-filter>
</section>
<section>
<base-card>
<div class="controls">
<base-button mode="outline" @click="loadCoaches">Refresh</base-button>
<base-button v-if="!isCoach && !isLoading" :link="true" to="/register">Register as Coach</base-button>
</div>
<div v-if="isLoading">
<base-spinner></base-spinner>
</div>
<ul v-else-if="hasCoaches">
<!-- <li v-for="coach in filteredCoaches" :key="coach.id">{{ coach.firstName }}</li> -->
<coach-item v-for="coach in filteredCoaches" :key="coach.id" :id="coach.id" :firstName="coach.firstName"
:lastName="coach.lastName" :rate="coach.hourlyRate" :areas="coach.areas"></coach-item>
</ul>
<h3 v-else>No coaches found.</h3>
</base-card>
</section>
</template>
<script>
import CoachItem from '@/components/coaches/CoachItem.vue'
import CoachFilter from '@/components/coaches/CoachFilter.vue'
export default {
components: {
CoachItem,
CoachFilter
},
data() {
return {
isLoading: false,
activeFilters: {
frontend: true,
backend: true,
career: true
},
};
},
computed: {
filteredCoaches() {
const coaches = this.$store.getters['coaches/coaches'];
return coaches.filter(coach => {
if (this.activeFilters.frontend && coach.areas.includes('frontend')) return true;
if (this.activeFilters.backend && coach.areas.includes('backend')) return true;
if (this.activeFilters.career && coach.areas.includes('career')) return true;
return false;
});
},
hasCoaches() {
return !this.isLoading && this.$store.getters['coaches/hasCoaches'];
},
isCoach() {
return this.$store.getters['coaches/isCoach'];
},
},
methods: {
setFilters(updatedFilters) {
this.activeFilters = updatedFilters;
},
async loadCoaches() {
this.isLoading = true;
await this.$store.dispatch('coaches/loadCoaches');
this.isLoading = false;
},
},
created() {
this.loadCoaches();
},
};
</script>
<style scoped>
ul {
list-style: none;
margin: 0;
padding: 0;
}
.controls {
display: flex;
justify-content: space-between;
}
</style>