🔧 feat: 更新依赖版本 修复类型错误 优化首页推荐样式

This commit is contained in:
alger
2025-03-20 01:07:39 +08:00
parent e355341596
commit 650e4ff786
17 changed files with 196 additions and 538 deletions

View File

@@ -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",

View File

@@ -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'
}
};

View File

@@ -84,6 +84,7 @@ export default {
closeTitle: '请选择关闭方式',
minimizeToTray: '最小化到托盘',
exitApp: '退出应用',
rememberChoice: '记住我的选择'
rememberChoice: '记住我的选择',
closeApp: '关闭应用'
}
};

View File

@@ -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 */

View File

@@ -544,10 +544,6 @@ watch(showControls, (newValue) => {
});
const isMobile = computed(() => false); // TODO: 从 settings store 获取
const handlePlay = () => {
playerStore.setIsPlay(true);
};
</script>
<style scoped lang="scss">

View File

@@ -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 () => {

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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;
}
}
}

View File

@@ -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) => {

View File

@@ -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();

View File

@@ -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">

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -84,10 +84,10 @@ onBeforeUnmount(() => {
});
// 是否扫码登陆
const chooseQr = () => {
isQr.value = !isQr.value;
loadLogin();
};
// const chooseQr = () => {
// isQr.value = !isQr.value;
// loadLogin();
// };
// 手机号登录
const phone = ref('');

View File

@@ -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);
};

View File

@@ -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();
}
}
);