🎈 perf: 优化加载 升级vue3.5 electron32等多个包 添加v-loading指令

This commit is contained in:
alger
2024-09-04 15:20:43 +08:00
parent a4eda61a86
commit b6a5461a1d
14 changed files with 268 additions and 86 deletions

3
components.d.ts vendored
View File

@@ -1,10 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
MPop: typeof import('./src/components/common/MPop.vue')['default']
@@ -23,7 +23,6 @@ declare module 'vue' {
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch']
NTooltip: typeof import('naive-ui')['NTooltip']
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']

View File

@@ -1,6 +1,6 @@
{
"name": "alger-music",
"version": "1.5.1",
"version": "1.6.0",
"description": "这是一个用于音乐播放的应用程序。",
"author": "Alger <algerkc@qq.com>",
"main": "app.js",
@@ -21,16 +21,16 @@
"@tailwindcss/postcss7-compat": "^2.2.4",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/compiler-sfc": "^3.3.4",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-core": "^3.3.4",
"@vueuse/core": "^10.7.1",
"@vueuse/electron": "^10.9.0",
"autoprefixer": "^9.8.6",
"axios": "^0.21.1",
"electron": "^30.0.0",
"electron-builder": "^24.13.0",
"@vitejs/plugin-vue": "^5.1.3",
"@vue/compiler-sfc": "^3.5.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/runtime-core": "^3.5.0",
"@vueuse/core": "^11.0.3",
"@vueuse/electron": "^11.0.3",
"autoprefixer": "^10.4.20",
"axios": "^1.7.7",
"electron": "^32.0.1",
"electron-builder": "^25.0.5",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.0.0",
@@ -40,22 +40,22 @@
"eslint-plugin-vue": "^9.21.1",
"eslint-plugin-vue-scoped-css": "^2.7.2",
"lodash": "^4.17.21",
"naive-ui": "^2.38.2",
"postcss": "^7.0.36",
"prettier": "^3.2.5",
"naive-ui": "^2.39.0",
"postcss": "^8.4.44",
"prettier": "^3.3.3",
"remixicon": "^4.2.0",
"sass": "^1.35.2",
"sass": "^1.78.0",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
"typescript": "^4.3.2",
"unplugin-auto-import": "^0.17.2",
"unplugin-vue-components": "^0.26.0",
"typescript": "^5.5.4",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.4",
"vfonts": "^0.1.0",
"vite": "^4.4.7",
"vite": "^5.4.3",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "1.0.0-beta.5",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vue-tsc": "^0.0.24",
"vite-plugin-vue-devtools": "7.4.0",
"vue": "^3.5.0",
"vue-router": "^4.4.3",
"vue-tsc": "^2.1.4",
"vuex": "^4.1.0"
}
}

View File

@@ -9,20 +9,18 @@
<i class="iconfont icon-icon_error music-close" @click="close"></i>
<div class="music-title text-el">{{ name }}</div>
<!-- 歌单歌曲列表 -->
<div :show="loading" class="music-list">
<div class="music-list">
<n-scrollbar>
<n-spin :show="loading">
<div class="music-list-content">
<div
v-for="(item, index) in songList"
:key="item.id"
:class="setAnimationClass('animate__bounceInUp')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="formatDetail(item)" @play="handlePlay" />
</div>
<div v-loading="loading || !songList.length" class="music-list-content">
<div
v-for="(item, index) in songList"
:key="item.id"
:class="setAnimationClass('animate__bounceInLeft')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="formatDetail(item)" @play="handlePlay" />
</div>
</n-spin>
</div>
<play-bottom />
</n-scrollbar>
</div>
@@ -38,24 +36,15 @@ import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
import PlayBottom from './common/PlayBottom.vue';
const loading = ref(true);
const store = useStore();
const props = defineProps<{
const { songList, loading = false } = defineProps<{
show: boolean;
name: string;
songList: any[];
loading?: boolean;
}>();
const emit = defineEmits(['update:show']);
watch(
() => props.songList,
(val) => {
loading.value = !(val && val.length);
},
{ immediate: true },
);
const emit = defineEmits(['update:show', 'update:loading']);
const formatDetail = computed(() => (detail: any) => {
const song = {
@@ -70,7 +59,7 @@ const formatDetail = computed(() => (detail: any) => {
});
const handlePlay = () => {
const tracks = props.songList || [];
const tracks = songList || [];
store.commit('setPlayList', tracks);
};

View File

@@ -4,6 +4,7 @@
<div class="recommend-singer">
<div class="recommend-singer-list">
<div
v-if="dayRecommendData"
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(0, 100)"
@@ -27,7 +28,7 @@
</div>
</div>
<div
v-for="(item, index) in hotSingerData?.artists.slice(0, 4)"
v-for="(item, index) in hotSingerData?.artists"
:key="item.id"
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
@@ -59,6 +60,7 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { getDayRecommend, getHotSinger } from '@/api/home';
import router from '@/router';
@@ -66,6 +68,8 @@ import { IDayRecommend } from '@/type/day_recommend';
import type { IHotSinger } from '@/type/singer';
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
const store = useStore();
// 歌手信息
const hotSingerData = ref<IHotSinger>();
const dayRecommendData = ref<IDayRecommend>();
@@ -82,18 +86,24 @@ const showMusic = ref(false);
// };
// 页面初始化
onMounted(async () => {
await loadData();
});
const loadData = async () => {
try {
const [{ data: singerData }, { data: dayRecommend }] = await Promise.all([
getHotSinger({ offset: 0, limit: 5 }),
getDayRecommend(),
]);
if (dayRecommend.data) {
singerData.artists = singerData.artists.slice(0, 4);
}
hotSingerData.value = singerData;
dayRecommendData.value = dayRecommend.data;
} catch (error) {
console.error('error', error);
}
});
};
const toSearchSinger = (keyword: string) => {
router.push({
@@ -103,6 +113,13 @@ const toSearchSinger = (keyword: string) => {
},
});
};
// 监听登录状态
watchEffect(() => {
if (store.state.user) {
loadData();
}
});
</script>
<style lang="scss" scoped>

View File

@@ -1,7 +1,12 @@
<template>
<div class="recommend-music">
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">本周最热音乐</div>
<div v-show="recommendMusic?.result" class="recommend-music-list" :class="setAnimationClass('animate__bounceInUp')">
<div
v-show="recommendMusic?.result"
v-loading="loading"
class="recommend-music-list"
:class="setAnimationClass('animate__bounceInUp')"
>
<!-- 推荐音乐列表 -->
<template v-for="(item, index) in recommendMusic?.result" :key="item.id">
<div :class="setAnimationClass('animate__bounceInUp')" :style="setAnimationDelay(index, 100)">
@@ -24,11 +29,14 @@ import SongItem from './common/SongItem.vue';
const store = useStore();
// 推荐歌曲
const recommendMusic = ref<IRecommendMusic>();
const loading = ref(false);
// 加载推荐歌曲
const loadRecommendMusic = async () => {
loading.value = true;
const { data } = await getRecommendMusic({ limit: 10 });
recommendMusic.value = data;
loading.value = false;
};
// 页面初始化

7
src/directive/index.ts Normal file
View File

@@ -0,0 +1,7 @@
import { vLoading } from './loading/index';
const directives = {
loading: vLoading,
};
export default directives;

View File

@@ -0,0 +1,40 @@
import { createVNode, render, VNode } from 'vue';
import Loading from './index.vue';
const vnode: VNode = createVNode(Loading) as VNode;
export const vLoading = {
// 在绑定元素的父组件 及他自己的所有子节点都挂载完成后调用
mounted: (el: HTMLElement, binding: any) => {
render(vnode, el);
},
// 在绑定元素的父组件 及他自己的所有子节点都更新后调用
updated: (el: HTMLElement, binding: any) => {
if (binding.value) {
vnode?.component?.exposed.show();
} else {
vnode?.component?.exposed.hide();
}
// 动态添加删除自定义class: loading-parent
formatterClass(el, binding);
},
// 绑定元素的父组件卸载后调用
unmounted: () => {
vnode?.component?.exposed.hide();
},
};
function formatterClass(el: HTMLElement, binding: any) {
const classStr = el.getAttribute('class');
const tagetClass: number = classStr?.indexOf('loading-parent') as number;
if (binding.value) {
if (tagetClass === -1) {
el.setAttribute('class', `${classStr} loading-parent`);
}
} else if (tagetClass > -1) {
const classArray: Array<string> = classStr?.split('') as string[];
classArray.splice(tagetClass - 1, tagetClass + 15);
el.setAttribute('class', classArray?.join(''));
}
}

View File

@@ -0,0 +1,92 @@
<!-- -->
<template>
<div v-if="isShow" class="loading-box">
<div class="mask" :style="{ background: maskBackground }"></div>
<div class="loading-content-box">
<n-spin size="small" />
<div :style="{ color: textColor }" class="tip">{{ tip }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { NSpin } from 'naive-ui';
import { ref } from 'vue';
defineProps({
tip: {
type: String,
default() {
return '加载中...';
},
},
maskBackground: {
type: String,
default() {
return 'rgba(0, 0, 0, 0.8)';
},
},
loadingColor: {
type: String,
default() {
return 'rgba(255, 255, 255, 1)';
},
},
textColor: {
type: String,
default() {
return 'rgba(255, 255, 255, 1)';
},
},
});
const isShow = ref(false);
const show = () => {
isShow.value = true;
};
const hide = () => {
isShow.value = false;
};
defineExpose({
show,
hide,
isShow,
});
</script>
<style lang="scss" scoped>
.loading-box {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 9999;
.n-spin {
// color: #ccc;
}
.mask {
width: 100%;
height: 100%;
}
.loading-content-box {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.tip {
font-size: 14px;
margin-top: 8px;
}
}
</style>

View File

@@ -10,8 +10,13 @@ import router from '@/router';
import store from '@/store';
import App from './App.vue';
import directives from './directive';
const app = createApp(App);
Object.keys(directives).forEach((key: string) => {
app.directive(key, directives[key]);
});
app.use(router);
app.use(store);
app.mount('#app');

View File

@@ -16,19 +16,24 @@ const showMusic = ref(false);
const recommendItem = ref<IRecommendItem | null>();
const listDetail = ref<IListDetail | null>();
const listLoading = ref(true);
const selectRecommendItem = async (item: IRecommendItem) => {
listLoading.value = true;
recommendItem.value = null;
listDetail.value = null;
showMusic.value = true;
recommendItem.value = item;
const { data } = await getListDetail(item.id);
listDetail.value = data;
listLoading.value = false;
};
const route = useRoute();
const listTitle = ref(route.query.type || '歌单列表');
const loading = ref(false);
const loadList = async (type: string) => {
loading.value = true;
const params = {
cat: type || '',
limit: 30,
@@ -36,6 +41,7 @@ const loadList = async (type: string) => {
};
const { data } = await getListByCat(params);
recommendList.value = data.playlists;
loading.value = false;
};
if (route.query.type) {
@@ -51,6 +57,7 @@ watch(
async (newParams) => {
if (newParams.type) {
recommendList.value = null;
listTitle.value = newParams.type || '歌单列表';
loadList(newParams.type as string);
}
},
@@ -62,7 +69,7 @@ watch(
<div class="recommend-title" :class="setAnimationClass('animate__bounceInLeft')">{{ listTitle }}</div>
<!-- 歌单列表 -->
<n-scrollbar class="recommend" :size="100" @click="showMusic = false">
<div v-if="recommendList" class="recommend-list">
<div v-loading="loading" class="recommend-list">
<div
v-for="(item, index) in recommendList"
:key="item.id"
@@ -91,6 +98,7 @@ watch(
</n-scrollbar>
<music-list
v-model:show="showMusic"
v-model:loading="listLoading"
:name="recommendItem?.name || ''"
:song-list="listDetail?.playlist.tracks || []"
/>

View File

@@ -70,6 +70,14 @@ const timerIsQr = (key: string) => {
return timer;
};
// 离开页面时
onBeforeUnmount(() => {
if (timerRef.value) {
clearInterval(timerRef.value);
timerRef.value = null;
}
});
// 是否扫码登陆
const isQr = ref(!isMobile.value);
const chooseQr = () => {

View File

@@ -4,7 +4,7 @@
<h2>推荐MV</h2>
</div>
<n-scrollbar :size="100">
<div class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
<div v-loading="loading" class="mv-list-content" :class="setAnimationClass('animate__bounceInLeft')">
<div
v-for="(item, index) in mvList"
:key="item.id"
@@ -32,7 +32,7 @@
</n-scrollbar>
<n-drawer :show="showMv" height="100vh" placement="bottom" :z-index="999999999">
<div class="mv-detail">
<div v-loading="mvLoading" class="mv-detail">
<video :src="playMvUrl" controls autoplay></video>
<div class="mv-detail-title">
<div class="title">{{ playMvItem?.name }}</div>
@@ -62,19 +62,25 @@ const mvList = ref<Array<IMvItem>>([]);
const playMvItem = ref<IMvItem>();
const playMvUrl = ref<string>();
const store = useStore();
const loading = ref(false);
onMounted(async () => {
loading.value = true;
const res = await getTopMv(30);
mvList.value = res.data.data;
loading.value = false;
});
const mvLoading = ref(false);
const handleShowMv = async (item: IMvItem) => {
mvLoading.value = true;
store.commit('setIsPlay', false);
store.commit('setPlayMusic', false);
showMv.value = true;
const res = await getMvUrl(item.id);
playMvItem.value = item;
playMvUrl.value = res.data.data.url;
mvLoading.value = false;
};
const close = () => {

View File

@@ -29,32 +29,30 @@
:native-scrollbar="false"
>
<div class="title">{{ hotKeyword }}</div>
<n-spin :show="searchDetailLoading">
<div class="search-list-box">
<template v-if="searchDetail">
<div
v-for="(item, index) in searchDetail?.songs"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="item" @play="handlePlay" />
</div>
<template v-for="(list, key) in searchDetail">
<template v-if="key.toString() !== 'songs'">
<div
v-for="(item, index) in list"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<SearchItem :item="item" />
</div>
</template>
<div v-loading="searchDetailLoading" class="search-list-box">
<template v-if="searchDetail">
<div
v-for="(item, index) in searchDetail?.songs"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="item" @play="handlePlay" />
</div>
<template v-for="(list, key) in searchDetail">
<template v-if="key.toString() !== 'songs'">
<div
v-for="(item, index) in list"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<SearchItem :item="item" />
</div>
</template>
</template>
</div>
</n-spin>
</template>
</div>
</n-layout>
</div>
</template>

View File

@@ -21,6 +21,7 @@ const router = useRouter();
const userDetail = ref<IUserDetail>();
const playList = ref<any[]>([]);
const recordList = ref();
const infoLoading = ref(false);
const user = computed(() => store.state.user);
@@ -29,6 +30,7 @@ const loadPage = async () => {
router.push('/login');
return;
}
infoLoading.value = true;
const { data: userData } = await getUserDetail(user.value.userId);
userDetail.value = userData;
@@ -36,9 +38,12 @@ const loadPage = async () => {
const { data: playlistData } = await getUserPlaylist(user.value.userId);
playList.value = playlistData.playlist;
getUserRecord(user.value.userId).then(({ data: recordData }) => {
recordList.value = recordData.allData;
});
// getUserRecord(user.value.userId).then(({ data: recordData }) => {
// recordList.value = recordData.allData;
// });
const { data: recordData } = await getUserRecord(user.value.userId);
recordList.value = recordData.allData;
infoLoading.value = false;
};
onActivated(() => {
@@ -109,7 +114,7 @@ const handlePlay = () => {
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
<div class=" ">创建的歌单</div>
<n-scrollbar>
<n-scrollbar v-loading="infoLoading">
<div v-for="(item, index) in playList" :key="index" class="play-list-item" @click="showPlaylist(item.id)">
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled />
<div class="play-list-item-info">
@@ -125,7 +130,7 @@ const handlePlay = () => {
<div v-if="!isMobile" class="right" :class="setAnimationClass('animate__fadeInRight')">
<div class="title">听歌排行</div>
<div class="record-list">
<n-scrollbar>
<n-scrollbar v-loading="infoLoading">
<div
v-for="(item, index) in recordList"
:key="item.song.id"