mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
🔧 feat: 更新依赖版本 修复类型错误 优化首页推荐样式
This commit is contained in:
51
package.json
51
package.json
@@ -21,41 +21,40 @@
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@unblockneteasemusic/server": "^0.27.8-patch.1",
|
||||
"electron-store": "^8.1.1",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"font-list": "^1.5.1",
|
||||
"netease-cloud-music-api-alger": "^4.25.0",
|
||||
"vue-i18n": "11.1.2"
|
||||
"vue-i18n": "9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
"@electron-toolkit/eslint-config-ts": "^2.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@rushstack/eslint-patch": "^1.10.3",
|
||||
"@tailwindcss/postcss7-compat": "^2.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@types/howler": "^2.2.12",
|
||||
"@types/node": "^20.14.8",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/compiler-sfc": "^3.5.0",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/runtime-core": "^3.5.13",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/electron": "^13.0.0",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@vue/runtime-core": "^3.5.0",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"@vueuse/electron": "^11.0.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.8.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.7",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^34.0.0",
|
||||
"electron": "^35.0.2",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron-vite": "3.0.0",
|
||||
"electron-vite": "^3.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
@@ -66,27 +65,25 @@
|
||||
"eslint-plugin-vue-scoped-css": "^2.7.2",
|
||||
"howler": "^2.2.4",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^15.0.7",
|
||||
"marked": "^15.0.4",
|
||||
"naive-ui": "^2.41.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.2",
|
||||
"pinia": "^3.0.1",
|
||||
"remixicon": "^4.2.0",
|
||||
"sass": "^1.83.4",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.3.2",
|
||||
"remixicon": "^4.6.0",
|
||||
"sass": "^1.86.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tunajs": "^1.0.15",
|
||||
"typescript": "^5.5.2",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vfonts": "^0.1.0",
|
||||
"vite": "^5.3.1",
|
||||
"unplugin-auto-import": "^19.1.1",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"vite": "^6.2.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "7.7.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-tsc": "^2.0.22",
|
||||
"vuex": "^4.1.0"
|
||||
"vue-tsc": "^2.0.22"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.alger.music",
|
||||
|
||||
@@ -85,6 +85,7 @@ export default {
|
||||
closeTitle: 'Choose how to close',
|
||||
minimizeToTray: 'Minimize to Tray',
|
||||
exitApp: 'Exit App',
|
||||
rememberChoice: 'Remember my choice'
|
||||
rememberChoice: 'Remember my choice',
|
||||
closeApp: 'Close App'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ export default {
|
||||
closeTitle: '请选择关闭方式',
|
||||
minimizeToTray: '最小化到托盘',
|
||||
exitApp: '退出应用',
|
||||
rememberChoice: '记住我的选择'
|
||||
rememberChoice: '记住我的选择',
|
||||
closeApp: '关闭应用'
|
||||
}
|
||||
};
|
||||
|
||||
1
src/renderer/components.d.ts
vendored
1
src/renderer/components.d.ts
vendored
@@ -2,6 +2,7 @@
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
|
||||
@@ -544,10 +544,6 @@ watch(showControls, (newValue) => {
|
||||
});
|
||||
|
||||
const isMobile = computed(() => false); // TODO: 从 settings store 获取
|
||||
|
||||
const handlePlay = () => {
|
||||
playerStore.setIsPlay(true);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -181,10 +181,11 @@ import { computed, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { getMusicDetail } from '@/api/music';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
// import { usePlayerStore } from '@/store/modules/player';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
// import { audioService } from '@/services/audioService';
|
||||
import { getImgUrl } from '@/utils';
|
||||
// import { SongResult } from '@/type/music';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -209,7 +210,7 @@ interface DownloadedItem {
|
||||
}
|
||||
|
||||
const message = useMessage();
|
||||
const playerStore = usePlayerStore();
|
||||
// const playerStore = usePlayerStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const showDrawer = computed({
|
||||
@@ -343,11 +344,11 @@ const confirmDelete = async () => {
|
||||
};
|
||||
|
||||
// 播放音乐
|
||||
const handlePlay = async (musicInfo: SongResult) => {
|
||||
await playerStore.setPlay(musicInfo);
|
||||
playerStore.setPlayMusic(true);
|
||||
playerStore.setIsPlay(true);
|
||||
};
|
||||
// const handlePlay = async (musicInfo: SongResult) => {
|
||||
// await playerStore.setPlay(musicInfo);
|
||||
// playerStore.setPlayMusic(true);
|
||||
// playerStore.setIsPlay(true);
|
||||
// };
|
||||
|
||||
// 获取已下载音乐列表
|
||||
const refreshDownloadedList = async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="search-item" :class="[item.type, shape]" @click="handleClick">
|
||||
<div class="search-item" :class="[shape, item.type]" @click="handleClick">
|
||||
<div class="search-item-img">
|
||||
<n-image
|
||||
class="w-full h-full"
|
||||
@@ -107,16 +107,15 @@ const handleClick = async () => {
|
||||
}
|
||||
|
||||
if (props.item.type === 'mv') {
|
||||
handleShowMv(getCurrentMv());
|
||||
handleShowMv();
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowMv = async (item: IMvItem) => {
|
||||
const handleShowMv = async () => {
|
||||
playerStore.setIsPlay(false);
|
||||
playerStore.setPlayMusic(false);
|
||||
audioService.getCurrentSound()?.pause();
|
||||
showPop.value = true;
|
||||
currentMv.value = item;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -172,15 +171,15 @@ const handleShowMv = async (item: IMvItem) => {
|
||||
}
|
||||
}
|
||||
|
||||
.mv {
|
||||
.search-item.mv {
|
||||
&:hover {
|
||||
.play {
|
||||
@apply opacity-60;
|
||||
}
|
||||
}
|
||||
.search-item-img {
|
||||
width: 160px;
|
||||
height: 90px;
|
||||
width: 160px !important;
|
||||
height: 90px !important;
|
||||
@apply rounded-lg relative;
|
||||
}
|
||||
.play {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { marked } from 'marked';
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
:space-between="20"
|
||||
draggable
|
||||
show-arrow
|
||||
:autoplay="false"
|
||||
autoplay
|
||||
>
|
||||
<n-carousel-item :class="setAnimationClass('animate__backInRight')" :style="setAnimationDelay(0, 100)" style="width: calc((100% / 5) - 16px)">
|
||||
<div
|
||||
v-if="dayRecommendData"
|
||||
class="recommend-singer-item relative"
|
||||
>
|
||||
<n-carousel-item
|
||||
:class="setAnimationClass('animate__backInRight')"
|
||||
:style="setAnimationDelay(0, 100)"
|
||||
style="width: calc((100% / 5) - 16px)"
|
||||
>
|
||||
<div v-if="dayRecommendData" class="recommend-singer-item relative">
|
||||
<div
|
||||
:style="
|
||||
setBackgroundImg(getImgUrl(dayRecommendData?.dailySongs[0].al.picUrl, '500y500'))
|
||||
@@ -42,20 +43,33 @@
|
||||
</div>
|
||||
</n-carousel-item>
|
||||
|
||||
<n-carousel-item :class="setAnimationClass('animate__backInRight')"
|
||||
:style="setAnimationDelay(1, 100)"
|
||||
style="width: calc(((100% / 5) - 16px) * 3)">
|
||||
<n-carousel-item
|
||||
v-if="userStore.user && userPlaylist.length"
|
||||
:class="setAnimationClass('animate__backInRight')"
|
||||
:style="setAnimationDelay(1, 100) + '; width: calc(100% / 2); max-width: 460px;'"
|
||||
>
|
||||
<div class="user-play">
|
||||
<div class="user-play-title">
|
||||
{{ store.state.user?.nickname }}
|
||||
<div class="user-play-title flex items-center mb-3">
|
||||
<n-avatar size="small" round :src="userStore.user?.avatarUrl" class="mr-2" />
|
||||
{{ userStore.user?.nickname }}的常听
|
||||
</div>
|
||||
<div class="user-play-item" v-for="item in userPlaylist" :key="item.id">
|
||||
<div class="user-play-item-img">
|
||||
<img :src="getImgUrl(item.coverImgUrl, '200y200')" alt="">
|
||||
</div>
|
||||
<div class="user-play-item-info">
|
||||
<div class="user-play-item-info-name text- overflow-hidden">{{ item.name }}</div>
|
||||
<div class="user-play-item-info-count">{{ t('common.songCount', { count: item.trackCount }) }}</div>
|
||||
<div class="user-play-list">
|
||||
<div
|
||||
v-for="item in userPlaylist"
|
||||
:key="item.id"
|
||||
class="user-play-item"
|
||||
@click="toPlaylist(item.id)"
|
||||
>
|
||||
<div class="user-play-item-img">
|
||||
<img :src="getImgUrl(item.coverImgUrl, '200y200')" alt="" />
|
||||
<div class="user-play-item-overlay"></div>
|
||||
</div>
|
||||
<div class="user-play-item-info">
|
||||
<div class="user-play-item-info-name">{{ item.name }}</div>
|
||||
<div class="user-play-item-info-count text-xs opacity-70">
|
||||
{{ t('common.songCount', { count: item.trackCount }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,25 +113,36 @@
|
||||
:song-list="dayRecommendData?.dailySongs"
|
||||
:cover="false"
|
||||
/>
|
||||
|
||||
<!-- 添加用户歌单弹窗 -->
|
||||
<music-list
|
||||
v-model:show="showPlaylist"
|
||||
v-model:loading="playlistLoading"
|
||||
:name="playlistItem?.name || ''"
|
||||
:song-list="playlistDetail?.playlist?.tracks || []"
|
||||
:list-info="playlistDetail?.playlist"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, watchEffect } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { getDayRecommend, getHotSinger } from '@/api/home';
|
||||
import { getListDetail } from '@/api/list';
|
||||
import { getUserPlaylist } from '@/api/user';
|
||||
import MusicList from '@/components/MusicList.vue';
|
||||
import router from '@/router';
|
||||
import { useUserStore } from '@/store';
|
||||
import { IDayRecommend } from '@/type/day_recommend';
|
||||
import { Playlist } from '@/type/list';
|
||||
import type { IListDetail } from '@/type/listDetail';
|
||||
import type { IHotSinger } from '@/type/singer';
|
||||
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
|
||||
import { getUserPlaylist } from '@/api/user';
|
||||
import { Playlist } from '@/type/list';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 歌手信息
|
||||
@@ -126,6 +151,12 @@ const dayRecommendData = ref<IDayRecommend>();
|
||||
const showMusic = ref(false);
|
||||
const userPlaylist = ref<Playlist[]>([]);
|
||||
|
||||
// 为歌单弹窗添加的状态
|
||||
const showPlaylist = ref(false);
|
||||
const playlistLoading = ref(false);
|
||||
const playlistItem = ref<Playlist | null>(null);
|
||||
const playlistDetail = ref<IListDetail | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
@@ -146,9 +177,11 @@ const loadData = async () => {
|
||||
}
|
||||
|
||||
hotSingerData.value = singerData;
|
||||
if(store.state.user){
|
||||
const { data: playlistData } = await getUserPlaylist(store.state.user?.userId);
|
||||
userPlaylist.value = (playlistData.playlist as Playlist[]).sort((a, b) => b.playCount - a.playCount).slice(0, 3);
|
||||
if (userStore.user) {
|
||||
const { data: playlistData } = await getUserPlaylist(userStore.user?.userId);
|
||||
userPlaylist.value = (playlistData.playlist as Playlist[])
|
||||
.sort((a, b) => b.playCount - a.playCount)
|
||||
.slice(0, 3);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
@@ -164,9 +197,32 @@ const toSearchSinger = (keyword: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const toPlaylist = async (id: number) => {
|
||||
playlistLoading.value = true;
|
||||
playlistItem.value = null;
|
||||
playlistDetail.value = null;
|
||||
showPlaylist.value = true;
|
||||
|
||||
// 设置当前点击的歌单信息
|
||||
const selectedPlaylist = userPlaylist.value.find((item) => item.id === id);
|
||||
if (selectedPlaylist) {
|
||||
playlistItem.value = selectedPlaylist;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取歌单详情
|
||||
const { data } = await getListDetail(id);
|
||||
playlistDetail.value = data;
|
||||
} catch (error) {
|
||||
console.error('获取歌单详情失败:', error);
|
||||
} finally {
|
||||
playlistLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听登录状态
|
||||
watchEffect(() => {
|
||||
if (store.state.user) {
|
||||
if (userStore.user) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
@@ -200,23 +256,48 @@ watchEffect(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.user-play{
|
||||
@apply flex bg-light-100 dark:bg-dark rounded-3xl p-4 gap-4;
|
||||
&-item{
|
||||
@apply bg-light dark:bg-dark-100 rounded-3xl overflow-hidden w-28;
|
||||
&-img{
|
||||
@apply w-28 h-28 rounded-3xl overflow-hidden;
|
||||
img{
|
||||
@apply w-full h-full object-cover;
|
||||
.user-play {
|
||||
@apply bg-light-200 dark:bg-dark-100 rounded-3xl p-5 h-full flex flex-col;
|
||||
backdrop-filter: blur(20px);
|
||||
&-title {
|
||||
@apply text-gray-900 dark:text-gray-100 font-bold text-lg;
|
||||
}
|
||||
&-list {
|
||||
@apply grid grid-cols-3 gap-5 h-full;
|
||||
}
|
||||
&-item {
|
||||
@apply bg-light dark:bg-dark-200 rounded-2xl overflow-hidden flex flex-col cursor-pointer transition-all duration-300;
|
||||
height: 190px;
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
.user-play-item-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&-info{
|
||||
@apply flex-1;
|
||||
&-name{
|
||||
@apply text-gray-900 dark:text-gray-100 line-clamp-1;
|
||||
&-img {
|
||||
@apply relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 100%; /* 确保宽高比为1:1,即正方形 */
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
|
||||
img {
|
||||
@apply absolute inset-0 w-full h-full object-cover;
|
||||
}
|
||||
&-count{
|
||||
@apply text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
&-overlay {
|
||||
@apply absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center opacity-0 transition-opacity duration-300;
|
||||
}
|
||||
&-info {
|
||||
@apply px-1 py-1;
|
||||
&-name {
|
||||
@apply text-gray-900 dark:text-gray-100 font-medium text-sm line-clamp-1;
|
||||
}
|
||||
&-count {
|
||||
@apply text-gray-700 dark:text-gray-300 mt-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ import {
|
||||
textColors,
|
||||
useLyricProgress
|
||||
} from '@/hooks/MusicHook';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { getImgUrl, isMobile } from '@/utils';
|
||||
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
||||
@@ -373,7 +372,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const playerStore = usePlayerStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const handleArtistClick = (id: number) => {
|
||||
|
||||
@@ -112,7 +112,6 @@ import alipay from '@/assets/alipay.png';
|
||||
import wechat from '@/assets/wechat.png';
|
||||
import Coffee from '@/components/Coffee.vue';
|
||||
import { SEARCH_TYPES, USER_SET_OPTIONS } from '@/const/bar-const';
|
||||
import { usePlayerStore } from '@/store/modules/player';
|
||||
import { useSearchStore } from '@/store/modules/search';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -122,7 +121,6 @@ import { checkUpdate, UpdateResult } from '@/utils/update';
|
||||
import config from '../../../../package.json';
|
||||
|
||||
const router = useRouter();
|
||||
const playerStore = usePlayerStore();
|
||||
const searchStore = useSearchStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="button" @click="minimize">
|
||||
<i class="iconfont icon-minisize"></i>
|
||||
</div>
|
||||
<div class="button" @click="close">
|
||||
<div class="button" @click="handleClose">
|
||||
<i class="iconfont icon-close"></i>
|
||||
</div>
|
||||
</template>
|
||||
@@ -27,7 +27,7 @@
|
||||
<n-modal
|
||||
v-model:show="showCloseModal"
|
||||
preset="dialog"
|
||||
title="关闭应用"
|
||||
:title="t('comp.titleBar.closeApp')"
|
||||
:style="{ width: '400px' }"
|
||||
:mask-closable="true"
|
||||
>
|
||||
@@ -80,7 +80,7 @@ const minimize = () => {
|
||||
|
||||
const handleAction = (action: 'minimize' | 'close') => {
|
||||
if (rememberChoice.value) {
|
||||
settingsStore.setSettings({
|
||||
settingsStore.setSetData({
|
||||
...settingsStore.setData,
|
||||
closeAction: action
|
||||
});
|
||||
@@ -112,10 +112,6 @@ const drag = (event: MouseEvent) => {
|
||||
}
|
||||
window.api.dragStart(event as unknown as string);
|
||||
};
|
||||
|
||||
const handleThemeChange = () => {
|
||||
settingsStore.toggleTheme();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,415 +1,5 @@
|
||||
import { createPinia } from 'pinia';
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import setData from '@/../main/set.json';
|
||||
import { logout } from '@/api/login';
|
||||
import { getLikedList, likeSong } from '@/api/music';
|
||||
import { useMusicListHook } from '@/hooks/MusicListHook';
|
||||
import router from '@/router';
|
||||
import homeRouter from '@/router/home';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { isElectron } from '@/utils';
|
||||
import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
|
||||
|
||||
// 默认设置
|
||||
const defaultSettings = setData;
|
||||
|
||||
function isValidUrl(urlString: string): boolean {
|
||||
try {
|
||||
return Boolean(new URL(urlString));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getLocalStorageItem<T>(key: string, defaultValue: T): T {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
if (!item) return defaultValue;
|
||||
|
||||
// 尝试解析 JSON
|
||||
const parsedItem = JSON.parse(item);
|
||||
|
||||
// 对于音乐 URL,检查是否是有效的 URL 格式或本地文件路径
|
||||
if (key === 'currentPlayMusicUrl' && typeof parsedItem === 'string') {
|
||||
if (!parsedItem.startsWith('local://') && !isValidUrl(parsedItem)) {
|
||||
console.warn(`Invalid URL in localStorage for key ${key}, using default value`);
|
||||
localStorage.removeItem(key);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 对于播放列表,检查是否是数组且每个项都有必要的字段
|
||||
if (key === 'playList') {
|
||||
if (!Array.isArray(parsedItem)) {
|
||||
console.warn(`Invalid playList format in localStorage, using default value`);
|
||||
localStorage.removeItem(key);
|
||||
return defaultValue;
|
||||
}
|
||||
// 检查每个歌曲对象是否有必要的字段
|
||||
const isValid = parsedItem.every((item) => item && typeof item === 'object' && 'id' in item);
|
||||
if (!isValid) {
|
||||
console.warn(`Invalid song objects in playList, using default value`);
|
||||
localStorage.removeItem(key);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 对于当前播放音乐,检查是否是对象且包含必要的字段
|
||||
if (key === 'currentPlayMusic') {
|
||||
if (!parsedItem || typeof parsedItem !== 'object' || !('id' in parsedItem)) {
|
||||
console.warn(`Invalid currentPlayMusic format in localStorage, using default value`);
|
||||
localStorage.removeItem(key);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return parsedItem;
|
||||
} catch (error) {
|
||||
console.warn(`Error parsing localStorage item for key ${key}:`, error);
|
||||
// 如果解析失败,删除可能损坏的数据
|
||||
localStorage.removeItem(key);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export interface State {
|
||||
menus: any[];
|
||||
play: boolean;
|
||||
isPlay: boolean;
|
||||
playMusic: SongResult;
|
||||
playMusicUrl: string;
|
||||
user: any;
|
||||
playList: SongResult[];
|
||||
playListIndex: number;
|
||||
setData: typeof defaultSettings;
|
||||
lyric: any;
|
||||
isMobile: boolean;
|
||||
searchValue: string;
|
||||
searchType: number;
|
||||
favoriteList: number[];
|
||||
playMode: number;
|
||||
theme: ThemeType;
|
||||
musicFull: boolean;
|
||||
showUpdateModal: boolean;
|
||||
showArtistDrawer: boolean;
|
||||
currentArtistId: number | null;
|
||||
systemFonts: { label: string; value: string }[];
|
||||
showDownloadDrawer: boolean;
|
||||
savedPlayProgress?: number;
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
menus: homeRouter,
|
||||
play: false,
|
||||
isPlay: false,
|
||||
playMusic: getLocalStorageItem('currentPlayMusic', {} as SongResult),
|
||||
playMusicUrl: getLocalStorageItem('currentPlayMusicUrl', ''),
|
||||
user: getLocalStorageItem('user', null),
|
||||
playList: getLocalStorageItem('playList', []),
|
||||
playListIndex: getLocalStorageItem('playListIndex', 0),
|
||||
setData: defaultSettings,
|
||||
lyric: {},
|
||||
isMobile: false,
|
||||
searchValue: '',
|
||||
searchType: 1,
|
||||
favoriteList: getLocalStorageItem('favoriteList', []),
|
||||
playMode: getLocalStorageItem('playMode', 0),
|
||||
theme: getCurrentTheme(),
|
||||
musicFull: false,
|
||||
showUpdateModal: false,
|
||||
showArtistDrawer: false,
|
||||
currentArtistId: null,
|
||||
systemFonts: [{ label: '系统默认', value: 'system-ui' }],
|
||||
showDownloadDrawer: false
|
||||
};
|
||||
|
||||
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
||||
|
||||
const mutations = {
|
||||
setMenus(state: State, menus: any[]) {
|
||||
state.menus = menus;
|
||||
},
|
||||
async setPlay(state: State, playMusic: SongResult) {
|
||||
await handlePlayMusic(state, playMusic);
|
||||
localStorage.setItem('currentPlayMusic', JSON.stringify(state.playMusic));
|
||||
localStorage.setItem('currentPlayMusicUrl', state.playMusicUrl);
|
||||
},
|
||||
setIsPlay(state: State, isPlay: boolean) {
|
||||
state.isPlay = isPlay;
|
||||
localStorage.setItem('isPlaying', isPlay.toString());
|
||||
},
|
||||
async setPlayMusic(state: State, play: boolean) {
|
||||
state.play = play;
|
||||
localStorage.setItem('isPlaying', play.toString());
|
||||
|
||||
// 每次更改播放状态时,确保当前播放歌曲信息也被保存
|
||||
if (state.playMusic && Object.keys(state.playMusic).length > 0) {
|
||||
localStorage.setItem('currentPlayMusic', JSON.stringify(state.playMusic));
|
||||
if (state.playMusicUrl) {
|
||||
localStorage.setItem('currentPlayMusicUrl', state.playMusicUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
setMusicFull(state: State, musicFull: boolean) {
|
||||
state.musicFull = musicFull;
|
||||
},
|
||||
setPlayList(state: State, playList: SongResult[]) {
|
||||
state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id);
|
||||
state.playList = playList;
|
||||
localStorage.setItem('playList', JSON.stringify(playList));
|
||||
localStorage.setItem('playListIndex', state.playListIndex.toString());
|
||||
},
|
||||
async nextPlay(state: State) {
|
||||
await nextPlay(state);
|
||||
},
|
||||
async prevPlay(state: State) {
|
||||
await prevPlay(state);
|
||||
},
|
||||
// 添加到下一首播放
|
||||
addToNextPlay(state: State, song: SongResult) {
|
||||
const playList = [...state.playList];
|
||||
const currentIndex = state.playListIndex;
|
||||
|
||||
// 检查歌曲是否已经在播放列表中
|
||||
const existingIndex = playList.findIndex((item) => item.id === song.id);
|
||||
if (existingIndex !== -1) {
|
||||
// 如果歌曲已经在列表中,将其移动到当前播放歌曲的下一个位置
|
||||
playList.splice(existingIndex, 1);
|
||||
}
|
||||
|
||||
// 在当前播放歌曲后插入新歌曲
|
||||
playList.splice(currentIndex + 1, 0, song);
|
||||
|
||||
// 更新播放列表
|
||||
state.playList = playList;
|
||||
state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id);
|
||||
localStorage.setItem('playList', JSON.stringify(playList));
|
||||
localStorage.setItem('playListIndex', state.playListIndex.toString());
|
||||
},
|
||||
setSetData(state: State, setData: any) {
|
||||
state.setData = setData;
|
||||
if (isElectron) {
|
||||
// (window as any).electron.ipcRenderer.setStoreValue(
|
||||
// 'set',
|
||||
// JSON.parse(JSON.stringify(setData))
|
||||
// );
|
||||
window.electron.ipcRenderer.send(
|
||||
'set-store-value',
|
||||
'set',
|
||||
JSON.parse(JSON.stringify(setData))
|
||||
);
|
||||
} else {
|
||||
localStorage.setItem('appSettings', JSON.stringify(setData));
|
||||
}
|
||||
},
|
||||
async addToFavorite(state: State, songId: number) {
|
||||
// 先添加到本地
|
||||
if (!state.favoriteList.includes(songId)) {
|
||||
state.favoriteList = [songId, ...state.favoriteList];
|
||||
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
|
||||
}
|
||||
|
||||
// 如果用户已登录,尝试同步到服务器
|
||||
if (state.user && localStorage.getItem('token')) {
|
||||
try {
|
||||
await likeSong(songId, true);
|
||||
} catch (error) {
|
||||
console.error('同步收藏到服务器失败,但已保存在本地:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
async removeFromFavorite(state: State, songId: number) {
|
||||
// 先从本地移除
|
||||
state.favoriteList = state.favoriteList.filter((id) => id !== songId);
|
||||
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
|
||||
|
||||
// 如果用户已登录,尝试同步到服务器
|
||||
if (state.user && localStorage.getItem('token')) {
|
||||
try {
|
||||
await likeSong(songId, false);
|
||||
} catch (error) {
|
||||
console.error('同步取消收藏到服务器失败,但已在本地移除:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
togglePlayMode(state: State) {
|
||||
state.playMode = (state.playMode + 1) % 3;
|
||||
localStorage.setItem('playMode', JSON.stringify(state.playMode));
|
||||
},
|
||||
toggleTheme(state: State) {
|
||||
state.theme = state.theme === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(state.theme);
|
||||
},
|
||||
setShowUpdateModal(state, value) {
|
||||
state.showUpdateModal = value;
|
||||
},
|
||||
logout(state: State) {
|
||||
logout().then(() => {
|
||||
state.user = null;
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('token');
|
||||
});
|
||||
},
|
||||
setShowArtistDrawer(state, show: boolean) {
|
||||
state.showArtistDrawer = show;
|
||||
if (!show) {
|
||||
state.currentArtistId = null;
|
||||
}
|
||||
},
|
||||
setCurrentArtistId(state, id: number) {
|
||||
state.currentArtistId = id;
|
||||
},
|
||||
setSystemFonts(state, fonts: string[]) {
|
||||
state.systemFonts = [
|
||||
{ label: '系统默认', value: 'system-ui' },
|
||||
...fonts.map((font) => ({
|
||||
label: font,
|
||||
value: font
|
||||
}))
|
||||
];
|
||||
},
|
||||
setShowDownloadDrawer(state: State, show: boolean) {
|
||||
state.showDownloadDrawer = show;
|
||||
},
|
||||
setLanguage(state: State, language: string) {
|
||||
state.setData.language = language;
|
||||
if (isElectron) {
|
||||
window.electron.ipcRenderer.send('set-store-value', 'set.language', language);
|
||||
} else {
|
||||
localStorage.setItem('appSettings', JSON.stringify(state.setData));
|
||||
}
|
||||
},
|
||||
getLanguage(state: State) {
|
||||
return state.setData.language;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
initializeSettings({ commit }: { commit: any }) {
|
||||
if (isElectron) {
|
||||
const setData = window.electron.ipcRenderer.sendSync('get-store-value', 'set');
|
||||
commit('setSetData', {
|
||||
...defaultSettings,
|
||||
...setData
|
||||
});
|
||||
} else {
|
||||
const savedSettings = localStorage.getItem('appSettings');
|
||||
if (savedSettings) {
|
||||
commit('setSetData', {
|
||||
...defaultSettings,
|
||||
...JSON.parse(savedSettings)
|
||||
});
|
||||
} else {
|
||||
commit('setSetData', defaultSettings);
|
||||
}
|
||||
}
|
||||
},
|
||||
initializeTheme({ state }: { state: State }) {
|
||||
applyTheme(state.theme);
|
||||
},
|
||||
async initializeFavoriteList({ state }: { state: State }) {
|
||||
// 先获取本地收藏列表
|
||||
const localFavoriteList = localStorage.getItem('favoriteList');
|
||||
const localList: number[] = localFavoriteList ? JSON.parse(localFavoriteList) : [];
|
||||
|
||||
// 如果用户已登录,尝试获取服务器收藏列表并合并
|
||||
if (state.user && state.user.userId) {
|
||||
try {
|
||||
const res = await getLikedList(state.user.userId);
|
||||
if (res.data?.ids) {
|
||||
// 合并本地和服务器的收藏列表,去重
|
||||
const serverList = res.data.ids.reverse();
|
||||
const mergedList = Array.from(new Set([...localList, ...serverList]));
|
||||
state.favoriteList = mergedList;
|
||||
} else {
|
||||
state.favoriteList = localList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取服务器收藏列表失败,使用本地数据:', error);
|
||||
state.favoriteList = localList;
|
||||
}
|
||||
} else {
|
||||
state.favoriteList = localList;
|
||||
}
|
||||
|
||||
// 更新本地存储
|
||||
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
|
||||
},
|
||||
showArtist({ commit }, id: number) {
|
||||
commit('setCurrentArtistId', id);
|
||||
},
|
||||
async initializeSystemFonts({ commit, state }) {
|
||||
if (!isElectron) return;
|
||||
// 如果已经有字体列表(不只是默认字体),则不重复获取
|
||||
if (state.systemFonts.length > 1) return;
|
||||
|
||||
try {
|
||||
const fonts = await window.api.invoke('get-system-fonts');
|
||||
commit('setSystemFonts', fonts);
|
||||
} catch (error) {
|
||||
console.error('获取系统字体失败:', error);
|
||||
}
|
||||
},
|
||||
async initializePlayState({ state, commit }: { state: State; commit: any }) {
|
||||
const savedPlayList = getLocalStorageItem('playList', []);
|
||||
const savedPlayMusic = getLocalStorageItem('currentPlayMusic', null) as SongResult | null;
|
||||
const savedPlayProgress = localStorage.getItem('playProgress');
|
||||
|
||||
if (savedPlayList.length > 0) {
|
||||
commit('setPlayList', savedPlayList);
|
||||
}
|
||||
|
||||
if (savedPlayMusic && Object.keys(savedPlayMusic).length > 0) {
|
||||
// 不直接使用保存的 URL,而是重新获取
|
||||
try {
|
||||
// 使用 handlePlayMusic 来重新获取音乐 URL
|
||||
|
||||
// 根据自动播放设置决定是否恢复播放状态
|
||||
const shouldAutoPlay = state.setData.autoPlay;
|
||||
await handlePlayMusic(state, savedPlayMusic, shouldAutoPlay);
|
||||
state.play = shouldAutoPlay;
|
||||
state.isPlay = true;
|
||||
|
||||
// 如果有保存的播放进度,则提供给前端组件使用
|
||||
if (savedPlayProgress) {
|
||||
try {
|
||||
const progress = JSON.parse(savedPlayProgress);
|
||||
if (progress && progress.songId === savedPlayMusic.id) {
|
||||
// 在全局状态中添加播放进度
|
||||
state.savedPlayProgress = progress.progress;
|
||||
} else {
|
||||
// 如果歌曲ID不匹配,清除保存的进度
|
||||
localStorage.removeItem('playProgress');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析保存的播放进度失败', e);
|
||||
localStorage.removeItem('playProgress');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重新获取音乐链接失败:', error);
|
||||
// 清除无效的播放状态
|
||||
state.play = false;
|
||||
state.isPlay = false;
|
||||
state.playMusic = {} as SongResult;
|
||||
state.playMusicUrl = '';
|
||||
localStorage.removeItem('currentPlayMusic');
|
||||
localStorage.removeItem('currentPlayMusicUrl');
|
||||
localStorage.removeItem('isPlaying');
|
||||
localStorage.removeItem('playProgress');
|
||||
}
|
||||
}
|
||||
},
|
||||
initializeLanguage({ state }: { state: State }) {
|
||||
state.setData.language = getLocalStorageItem('appSettings', { language: 'zh-CN' }).language;
|
||||
if (isElectron) {
|
||||
window.electron.ipcRenderer.send('set-store-value', 'set.language', state.setData.language);
|
||||
} else {
|
||||
localStorage.setItem('appSettings', JSON.stringify(state.setData));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 创建 pinia 实例
|
||||
const pinia = createPinia();
|
||||
|
||||
@@ -29,12 +29,17 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
const showDownloadDrawer = ref(false);
|
||||
|
||||
const setSetData = (data: any) => {
|
||||
// 合并现有设置和新设置
|
||||
const mergedData = {
|
||||
...setData.value,
|
||||
...data
|
||||
};
|
||||
|
||||
if (isElectron) {
|
||||
console.log('data', data);
|
||||
window.electron.ipcRenderer.send('set-store-value', 'set', cloneDeep(data));
|
||||
setData.value = cloneDeep(data);
|
||||
window.electron.ipcRenderer.send('set-store-value', 'set', cloneDeep(mergedData));
|
||||
setData.value = cloneDeep(mergedData);
|
||||
} else {
|
||||
localStorage.setItem('appSettings', JSON.stringify(cloneDeep(data)));
|
||||
localStorage.setItem('appSettings', JSON.stringify(cloneDeep(mergedData)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,10 +78,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
};
|
||||
|
||||
const setLanguage = (language: string) => {
|
||||
setSetData({
|
||||
...setData.value,
|
||||
language
|
||||
});
|
||||
setSetData({ language });
|
||||
if (isElectron) {
|
||||
window.electron.ipcRenderer.send('change-language', language);
|
||||
}
|
||||
|
||||
@@ -84,10 +84,10 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
|
||||
// 是否扫码登陆
|
||||
const chooseQr = () => {
|
||||
isQr.value = !isQr.value;
|
||||
loadLogin();
|
||||
};
|
||||
// const chooseQr = () => {
|
||||
// isQr.value = !isQr.value;
|
||||
// loadLogin();
|
||||
// };
|
||||
|
||||
// 手机号登录
|
||||
const phone = ref('');
|
||||
|
||||
@@ -461,7 +461,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { FormRules } from 'naive-ui';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { computed, h, nextTick, onMounted, ref, watch } from 'vue';
|
||||
@@ -494,23 +493,19 @@ const updateInfo = ref<UpdateResult>({
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const setData = ref(settingsStore.setData);
|
||||
// 使用计算属性来管理设置数据
|
||||
const setData = computed({
|
||||
get: () => settingsStore.setData,
|
||||
set: (newData) => {
|
||||
settingsStore.setSetData(newData);
|
||||
}
|
||||
});
|
||||
|
||||
const isDarkTheme = computed({
|
||||
get: () => settingsStore.theme === 'dark',
|
||||
set: () => settingsStore.toggleTheme()
|
||||
});
|
||||
|
||||
// watch(
|
||||
// () => setData.value,
|
||||
// (newData) => {
|
||||
// console.log('newData', newData);
|
||||
// settingsStore.setSetData(newData);
|
||||
// },
|
||||
// {
|
||||
// deep: true
|
||||
// }
|
||||
// );
|
||||
|
||||
const openAuthor = () => {
|
||||
window.open(setData.value.authorUrl);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="user-page">
|
||||
<div
|
||||
v-if="userDetail"
|
||||
v-if="userDetail && user"
|
||||
class="left"
|
||||
:class="setAnimationClass('animate__fadeInLeft')"
|
||||
:style="{ backgroundImage: `url(${getImgUrl(user.backgroundUrl)})` }"
|
||||
@@ -167,6 +167,8 @@ const loadData = async () => {
|
||||
try {
|
||||
infoLoading.value = true;
|
||||
|
||||
if (!user.value) return;
|
||||
|
||||
const { data: userData } = await getUserDetail(user.value.userId);
|
||||
if (!mounted.value) return;
|
||||
userDetail.value = userData;
|
||||
@@ -186,7 +188,7 @@ const loadData = async () => {
|
||||
console.error('加载用户页面失败:', error);
|
||||
// 如果获取用户数据失败,可能是token过期
|
||||
if (error.response?.status === 401) {
|
||||
userStore.logout();
|
||||
userStore.handleLogout();
|
||||
router.push('/login');
|
||||
}
|
||||
} finally {
|
||||
@@ -214,7 +216,7 @@ watch(
|
||||
(newUser) => {
|
||||
if (!mounted.value) return;
|
||||
if (newUser) {
|
||||
loadUserData();
|
||||
loadPage();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user