目录
指令注册(加载动画多场景复用)(vue的自定义指令注册app.directive)
优化一:加载动画
在components下的base文件夹中创建Loading.vue(大致布局)
<template>
<div class="loading">
<div class="loading-content">
<img src="@/assets/images/loading.gif" width="24" height="24" alt="">
<p class="desc">loading...</p>
</div>
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
.loading-content {
text-align: center;
.desc {
line-height: 20px;
font-size: $font-size-small;
color: $color-text-l;
}
}
}
</style>
在main.js中全局注册的指令
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 全局挂载才能用
import directivePlugin from './plugins'
import './assets/scss/index.scss'
createApp(App).use(store).use(router).use(directivePlugin).mount('#app')
指令注册(加载动画多场景复用)(vue的自定义指令注册app.directive)
在components下创建指令文件夹plugins创建index.js
import loading from '@/components/base/Loading'
import { createApp } from 'vue'
export default {
install(app) {
// 加载动画
app.directive('loading', {
// 绑定指令DOM
mounted(el, binding) {
// console.log(el, binding);
// loading组件应用对象
const app = createApp(loading)
// loading组件的实例对象
const instance = app.mount(document.createElement('div'))
el.instance = instance
if (binding.value) {
appendEl(el)
}
},
// 更新时调用
updated(el, binding) {
if (binding.value !== binding.oldValue) {
binding.value ? appendEl(el) : removeEl(el)
}
}
})
}
}
function appendEl(el) {
const style = getComputedStyle(el)
let arr = ['absolute', 'fixed', 'relative']
if (arr.indexOf(style.position) === -1) {
// 绑定的DOM没有定位样式(就加上定位)
el.classList.add('g-relative')
}
el.appendChild(el.instance.$el)
}
function removeEl(el) {
el.classList.remove('g-relative')
el.removeChild(el.instance.$el)
}
在Recommend.vue中使用
<template>
<div class="recommend" v-loading="loading">
<my-scroll class="recommend-content">
<div>
<!-- 轮播 -->
<div class="slider-wrapper">
<div class="slider-content">
<my-slider v-if="sliders.length" :sliders="sliders"></my-slider>
</div>
</div>
<!-- 热门歌单推荐 -->
<div class="recommend-list">
<h2 class="list-title">热门歌单推荐</h2>
<ul>
<li
class="recommend-item"
v-for="(item, index) in albums"
:key="item.id"
>
<div class="icon">
<img
v-img-lazy="item.coverImgUrl"
width="60"
height="60"
alt=""
/>
</div>
<div class="text">
<p class="name">{{ item.name }}</p>
<p class="description">{{ item.description }}</p>
</div>
</li>
</ul>
</div>
</div>
</my-scroll>
</div>
</template>
<script setup>
import { getRecommend, getRecommendAlbum } from "@/service/recommend";
import { computed, onMounted, ref } from "vue";
import MySlider from "@/components/base/Slider";
import MyScroll from "@/components/base/Scroll";
const sliders = ref([]);
const albums = ref([]);
// 为true出现加载动画,false消失
const loading = computed(() => !sliders.value.length || !albums.value.length);
</script>
<style lang="scss" scoped>
</style>
优化二:图片懒加载
安装@vueuse/core工具(一个函数合集)
npm install @vueuse/core --save
在components下指令文件夹plugins的index.js
import loading from '@/components/base/Loading'
import { createApp } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
//默认占位图片
import defaultImg from '@/assets/images/lazy.png'
export default {
install(app) {
// 图片懒加载
app.directive('img-lazy', {
mounted(el, binding) {
// 全部设置为占为图片
el.src = defaultImg
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
//isIntersecting: true进入了视口区域,false未进入
if (isIntersecting) {
// 加载失败
el.onerror = function () {
el.src = defaultImg
}
el.src = binding.value
// 停止监听
stop()
}
})
}
})
}
}
使用图片懒加载指令
<img v-img-lazy="item.coverImgUrl" width="60" height="60" alt="" />
歌手页面
Singer.vue页面
<template>
<div class="singer">
<my-singerList :singerList="singerList"></my-singerList>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { getSingerList } from "@/service/singer";
import MySingerList from "@/components/SingerList"
const singerList = ref([]);
//创建26个大写字母的数组
const arrStrUppercase = computed(() => {
let arr = ["热"];
for (let i = 65; i < 91; i++) {
// console.log(i);
arr.push(String.fromCharCode(i));
}
return arr;
});
onMounted(async () => {
// console.log(arrStrUppercase);
const result = await getSingerList(arrStrUppercase.value);
// console.log(result);
singerList.value = result;
});
</script>
<style lang="scss" scoped>
</style>
使用String.fromCharCode()转码 (数字转字母)
在service文件夹下创建singer.js歌手数据的接口
import { get } from './base'
// 拿到26个字母的数组,遍历数组发起请求
export function getSingerList(arrStr) {
// console.log(arrStr);
let singerList = []//接收所有的数据
let promiseArr = []//
for (let i = 0; i < arrStr.length; i++) {
promiseArr.push(new Promise((resolve, reject) => {
handle(arrStr[i], singerList, resolve)
}))
}
// 所有promise请求成功才算成功
// console.log(promiseArr);
return Promise.all(promiseArr).then(() => {
singerList.sort((a,b) => {
return a.tag.charCodeAt() - b.tag.reject()
})
// console.log(singerList.pop());
singerList.unshift(singerList.pop())
return singerList
})
}
// 请求数据接口
function handle(item, singerList, resolve) {
if (item === '热') {
get('/top/artists', {
limit: 30
}).then((result) => {
// console.log(result)
singerList.push({
tag: item,
nameArr: result.artists
})
}).catch(err => {
throw err
})
} else {
// 通过首字母查询歌手
get('/artist/list', {
initial: item,
type: -1,
area: -1
}).then((result) => {
// console.log(result)
singerList.push({
tag: item,
nameArr: result.artists
})
}).catch(err => {
throw err
})
}
}
在components下新建SingerList.vue
<template>
<div class="singer-list">
<ul class="view-scroll">
<li class="group" v-for="group in singerList" :key="group.tag">
<h2 class="title">{{ group.tag }}</h2>
<ul>
<li class="item" v-for="item in group.nameArr" :key="item.id">
<div class="avatar">
<img v-img-lazy="item.picUrl" alt="" />
</div>
<span class="name">{{ item.name }}</span>
</li>
</ul>
</li>
</ul>
</div>
</template>
<script setup>
// 接收数据
const props = defineProps(["singerList"]);
</script>
<style lang="scss" scoped>
</style>