mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-18 11:37:31 +08:00
✨ feat: 增强语言切换功能和用户播放列表显示
This commit is contained in:
@@ -16,6 +16,7 @@ dist.zip
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
bun.lock
|
||||||
|
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
|
|||||||
@@ -87,5 +87,8 @@ export default {
|
|||||||
exitApp: 'Exit App',
|
exitApp: 'Exit App',
|
||||||
rememberChoice: 'Remember my choice',
|
rememberChoice: 'Remember my choice',
|
||||||
closeApp: 'Close App'
|
closeApp: 'Close App'
|
||||||
|
},
|
||||||
|
userPlayList: {
|
||||||
|
title: "{name}'s Playlist"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,5 +86,8 @@ export default {
|
|||||||
exitApp: '退出应用',
|
exitApp: '退出应用',
|
||||||
rememberChoice: '记住我的选择',
|
rememberChoice: '记住我的选择',
|
||||||
closeApp: '关闭应用'
|
closeApp: '关闭应用'
|
||||||
|
},
|
||||||
|
userPlayList: {
|
||||||
|
title: '{name}的常听'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ if (!isSingleInstance) {
|
|||||||
|
|
||||||
// 监听语言切换
|
// 监听语言切换
|
||||||
ipcMain.on('change-language', (_, locale: Language) => {
|
ipcMain.on('change-language', (_, locale: Language) => {
|
||||||
|
console.log('locale',locale)
|
||||||
// 更新主进程的语言设置
|
// 更新主进程的语言设置
|
||||||
i18n.global.locale = locale;
|
i18n.global.locale = locale;
|
||||||
// 更新托盘菜单
|
// 更新托盘菜单
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { app, BrowserWindow, Menu, nativeImage, Tray } from 'electron';
|
import { app, BrowserWindow, Menu, nativeImage, Tray, ipcMain } from 'electron';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import type { Language } from '../../i18n/main';
|
import type { Language } from '../../i18n/main';
|
||||||
@@ -35,9 +35,12 @@ export function updateTrayMenu() {
|
|||||||
i18n.global.locale = value;
|
i18n.global.locale = value;
|
||||||
// 更新托盘菜单
|
// 更新托盘菜单
|
||||||
updateTrayMenu();
|
updateTrayMenu();
|
||||||
// 通知渲染进程
|
// 直接通知主窗口
|
||||||
const win = BrowserWindow.getAllWindows()[0];
|
const windows = BrowserWindow.getAllWindows();
|
||||||
win?.webContents.send('set-language', value);
|
for (const win of windows) {
|
||||||
|
win.webContents.send('language-changed', value);
|
||||||
|
console.log('向窗口发送语言变更事件:', value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+1
@@ -18,6 +18,7 @@ declare global {
|
|||||||
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
||||||
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
||||||
removeDownloadListeners: () => void;
|
removeDownloadListeners: () => void;
|
||||||
|
onLanguageChanged: (callback: (locale: string) => void) => void;
|
||||||
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
||||||
};
|
};
|
||||||
$message: any;
|
$message: any;
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ const api = {
|
|||||||
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => {
|
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => {
|
||||||
ipcRenderer.on('download-complete', (_event, success, filePath) => callback(success, filePath));
|
ipcRenderer.on('download-complete', (_event, success, filePath) => callback(success, filePath));
|
||||||
},
|
},
|
||||||
|
// 语言相关
|
||||||
|
onLanguageChanged: (callback: (locale: string) => void) => {
|
||||||
|
console.log('注册语言变更监听器');
|
||||||
|
ipcRenderer.on('language-changed', (_event, locale) => {
|
||||||
|
console.log('收到语言变更事件:', locale);
|
||||||
|
callback(locale);
|
||||||
|
});
|
||||||
|
},
|
||||||
removeDownloadListeners: () => {
|
removeDownloadListeners: () => {
|
||||||
ipcRenderer.removeAllListeners('download-progress');
|
ipcRenderer.removeAllListeners('download-progress');
|
||||||
ipcRenderer.removeAllListeners('download-complete');
|
ipcRenderer.removeAllListeners('download-complete');
|
||||||
|
|||||||
@@ -57,13 +57,15 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSetLanguage = (_: any, value: string) => {
|
const handleSetLanguage = (value: string) => {
|
||||||
locale.value = value;
|
console.log('应用语言变更:', value);
|
||||||
// settingsStore.setLanguage(value);
|
if (value) {
|
||||||
|
locale.value = value;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
settingsStore.initializeSettings();
|
settingsStore.initializeSettings();
|
||||||
handleSetLanguage(null, settingsStore.setData.language);
|
handleSetLanguage(settingsStore.setData.language);
|
||||||
settingsStore.initializeTheme();
|
settingsStore.initializeTheme();
|
||||||
settingsStore.initializeSystemFonts();
|
settingsStore.initializeSystemFonts();
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
@@ -71,13 +73,12 @@ if (isMobile.value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
window.electron.ipcRenderer.on('language-changed', handleSetLanguage);
|
window.api.onLanguageChanged(handleSetLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 先初始化播放状态
|
// 先初始化播放状态
|
||||||
await playerStore.initializePlayState();
|
await playerStore.initializePlayState();
|
||||||
|
|
||||||
// 如果有正在播放的音乐,则初始化音频监听器
|
// 如果有正在播放的音乐,则初始化音频监听器
|
||||||
if (playerStore.playMusic && playerStore.playMusic.id) {
|
if (playerStore.playMusic && playerStore.playMusic.id) {
|
||||||
// 使用 nextTick 确保 DOM 更新后再初始化
|
// 使用 nextTick 确保 DOM 更新后再初始化
|
||||||
|
|||||||
@@ -144,11 +144,14 @@ watch(modelValue, (newVal) => {
|
|||||||
});
|
});
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
// 加载歌手信息
|
// 加载歌手信息
|
||||||
|
|
||||||
|
const previousArtistId = ref<number>();
|
||||||
const loadArtistInfo = async (id: number) => {
|
const loadArtistInfo = async (id: number) => {
|
||||||
if (currentArtistId.value === id) return;
|
// if (currentArtistId.value === id) return;
|
||||||
|
if (previousArtistId.value === id) return;
|
||||||
activeTab.value = 'songs';
|
activeTab.value = 'songs';
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
currentArtistId.value = id;
|
previousArtistId.value = id;
|
||||||
try {
|
try {
|
||||||
const info = await getArtistDetail(id);
|
const info = await getArtistDetail(id);
|
||||||
if (info.data?.data?.artist) {
|
if (info.data?.data?.artist) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { computed } from 'vue';
|
|||||||
import { usePlayerStore } from '@/store/modules/player';
|
import { usePlayerStore } from '@/store/modules/player';
|
||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const isPlay = computed(() => playerStore.isPlay);
|
const isPlay = computed(() => playerStore.playMusicUrl);
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
height: {
|
height: {
|
||||||
|
|||||||
@@ -392,7 +392,8 @@ const toggleSelect = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleArtistClick = (id: number) => {
|
const handleArtistClick = (id: number) => {
|
||||||
settingsStore.currentArtistId = id;
|
settingsStore.setCurrentArtistId(id);
|
||||||
|
settingsStore.setShowArtistDrawer(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取歌手列表(最多显示5个)
|
// 获取歌手列表(最多显示5个)
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
:space-between="20"
|
:space-between="20"
|
||||||
draggable
|
draggable
|
||||||
show-arrow
|
show-arrow
|
||||||
autoplay
|
:autoplay="false"
|
||||||
>
|
>
|
||||||
<n-carousel-item
|
<n-carousel-item
|
||||||
:class="setAnimationClass('animate__backInRight')"
|
:class="setAnimationClass('animate__backInRight')"
|
||||||
:style="setAnimationDelay(0, 100)"
|
:style="getCarouselItemStyle(0, 100, 6 )"
|
||||||
style="width: calc((100% / 5) - 16px)"
|
|
||||||
>
|
>
|
||||||
<div v-if="dayRecommendData" class="recommend-singer-item relative">
|
<div v-if="dayRecommendData" class="recommend-singer-item relative">
|
||||||
<div
|
<div
|
||||||
@@ -46,14 +45,13 @@
|
|||||||
<n-carousel-item
|
<n-carousel-item
|
||||||
v-if="userStore.user && userPlaylist.length"
|
v-if="userStore.user && userPlaylist.length"
|
||||||
:class="setAnimationClass('animate__backInRight')"
|
:class="setAnimationClass('animate__backInRight')"
|
||||||
:style="setAnimationDelay(1, 100) + '; width: calc(100% / 2); max-width: 460px;'"
|
:style="getCarouselItemStyleForPlaylist(userPlaylist.length)"
|
||||||
>
|
>
|
||||||
<div class="user-play">
|
<div class="user-play">
|
||||||
<div class="user-play-title flex items-center mb-3">
|
<div class="user-play-title mb-3">
|
||||||
<n-avatar size="small" round :src="userStore.user?.avatarUrl" class="mr-2" />
|
{{ t('comp.userPlayList.title', { name: userStore.user?.nickname }) }}
|
||||||
{{ userStore.user?.nickname }}的常听
|
|
||||||
</div>
|
</div>
|
||||||
<div class="user-play-list">
|
<div class="user-play-list" :class="getPlaylistGridClass(userPlaylist.length)">
|
||||||
<div
|
<div
|
||||||
v-for="item in userPlaylist"
|
v-for="item in userPlaylist"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -62,12 +60,16 @@
|
|||||||
>
|
>
|
||||||
<div class="user-play-item-img">
|
<div class="user-play-item-img">
|
||||||
<img :src="getImgUrl(item.coverImgUrl, '200y200')" alt="" />
|
<img :src="getImgUrl(item.coverImgUrl, '200y200')" alt="" />
|
||||||
<div class="user-play-item-overlay"></div>
|
<div class="user-play-item-overlay">
|
||||||
</div>
|
<div class="user-play-item-play-btn">
|
||||||
<div class="user-play-item-info">
|
<i class="iconfont icon-playfill text-3xl text-white"></i>
|
||||||
<div class="user-play-item-info-name">{{ item.name }}</div>
|
</div>
|
||||||
<div class="user-play-item-info-count text-xs opacity-70">
|
</div>
|
||||||
{{ t('common.songCount', { count: item.trackCount }) }}
|
<div class="user-play-item-title">
|
||||||
|
<div class="user-play-item-title-name">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-play-item-count">
|
||||||
|
<div class="user-play-item-count-tag">{{ t('common.songCount', { count: item.trackCount }) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,13 +80,13 @@
|
|||||||
v-for="(item, index) in hotSingerData?.artists"
|
v-for="(item, index) in hotSingerData?.artists"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="setAnimationClass('animate__backInRight')"
|
:class="setAnimationClass('animate__backInRight')"
|
||||||
:style="setAnimationDelay(index + 1, 100)"
|
:style="getCarouselItemStyle(index + 1, 100, 6)"
|
||||||
style="width: calc((100% / 5) - 16px)"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="recommend-singer-item relative"
|
class="recommend-singer-item relative"
|
||||||
:class="setAnimationClass('animate__backInRight')"
|
:class="setAnimationClass('animate__backInRight')"
|
||||||
:style="setAnimationDelay(index + 2, 100)"
|
:style="setAnimationDelay(index + 2, 100)"
|
||||||
|
@click="handleOpenSinger(item.id)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="setBackgroundImg(getImgUrl(item.picUrl, '500y500'))"
|
:style="setBackgroundImg(getImgUrl(item.picUrl, '500y500'))"
|
||||||
@@ -94,11 +96,12 @@
|
|||||||
{{ t('common.songCount', { count: item.musicSize }) }}
|
{{ t('common.songCount', { count: item.musicSize }) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="recommend-singer-item-info z-10">
|
<div class="recommend-singer-item-info z-10">
|
||||||
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
|
<div class="recommend-singer-item-info-name text-el text-right line-clamp-1">{{ item.name }}</div>
|
||||||
<i class="iconfont icon-playfill text-xl"></i>
|
</div>
|
||||||
</div>
|
<!-- 播放按钮(hover时显示) -->
|
||||||
<div class="ml-4">
|
<div class="recommend-singer-item-play-overlay" @click.stop="handleOpenSinger(item.id)">
|
||||||
<div class="recommend-singer-item-info-name text-el">{{ item.name }}</div>
|
<div class="recommend-singer-item-play-btn">
|
||||||
|
<i class="iconfont icon-playfill text-4xl"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,8 +136,7 @@ import { getDayRecommend, getHotSinger } from '@/api/home';
|
|||||||
import { getListDetail } from '@/api/list';
|
import { getListDetail } from '@/api/list';
|
||||||
import { getUserPlaylist } from '@/api/user';
|
import { getUserPlaylist } from '@/api/user';
|
||||||
import MusicList from '@/components/MusicList.vue';
|
import MusicList from '@/components/MusicList.vue';
|
||||||
import router from '@/router';
|
import { useSettingsStore, useUserStore } from '@/store';
|
||||||
import { useUserStore } from '@/store';
|
|
||||||
import { IDayRecommend } from '@/type/day_recommend';
|
import { IDayRecommend } from '@/type/day_recommend';
|
||||||
import { Playlist } from '@/type/list';
|
import { Playlist } from '@/type/list';
|
||||||
import type { IListDetail } from '@/type/listDetail';
|
import type { IListDetail } from '@/type/listDetail';
|
||||||
@@ -157,6 +159,58 @@ const playlistLoading = ref(false);
|
|||||||
const playlistItem = ref<Playlist | null>(null);
|
const playlistItem = ref<Playlist | null>(null);
|
||||||
const playlistDetail = ref<IListDetail | null>(null);
|
const playlistDetail = ref<IListDetail | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取轮播项的样式
|
||||||
|
* @param index 项目索引(用于动画延迟)
|
||||||
|
* @param delayStep 动画延迟的步长(毫秒)
|
||||||
|
* @param totalItems 总共分成几等分(默认为5)
|
||||||
|
* @param maxWidth 最大宽度(可选,单位为px)
|
||||||
|
* @returns 样式字符串
|
||||||
|
*/
|
||||||
|
const getCarouselItemStyle = (
|
||||||
|
index: number,
|
||||||
|
delayStep: number,
|
||||||
|
totalItems: number = 5,
|
||||||
|
maxWidth?: number
|
||||||
|
) => {
|
||||||
|
const animationDelay = setAnimationDelay(index, delayStep);
|
||||||
|
const width = `calc((100% / ${totalItems}) - 16px)`;
|
||||||
|
const maxWidthStyle = maxWidth ? `max-width: ${maxWidth}px;` : '';
|
||||||
|
|
||||||
|
return `${animationDelay}; width: ${width}; ${maxWidthStyle}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据歌单数量获取轮播项的样式
|
||||||
|
* @param playlistCount 歌单数量
|
||||||
|
* @returns 样式字符串
|
||||||
|
*/
|
||||||
|
const getCarouselItemStyleForPlaylist = (playlistCount: number) => {
|
||||||
|
const animationDelay = setAnimationDelay(1, 100);
|
||||||
|
let width = '';
|
||||||
|
let maxWidth = '';
|
||||||
|
|
||||||
|
switch(playlistCount) {
|
||||||
|
case 1:
|
||||||
|
width = 'calc(100% / 4 - 16px)';
|
||||||
|
maxWidth = 'max-width: 180px;';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
width = 'calc(100% / 3 - 16px)';
|
||||||
|
maxWidth = 'max-width: 380px;';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
width = 'calc(100% / 2 - 16px)';
|
||||||
|
maxWidth = 'max-width: 520px;';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
width = 'calc(100% / 1 - 16px)';
|
||||||
|
maxWidth = 'max-width: 656px;';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${animationDelay}; width: ${width}; ${maxWidth}`;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadData();
|
await loadData();
|
||||||
});
|
});
|
||||||
@@ -179,22 +233,21 @@ const loadData = async () => {
|
|||||||
hotSingerData.value = singerData;
|
hotSingerData.value = singerData;
|
||||||
if (userStore.user) {
|
if (userStore.user) {
|
||||||
const { data: playlistData } = await getUserPlaylist(userStore.user?.userId);
|
const { data: playlistData } = await getUserPlaylist(userStore.user?.userId);
|
||||||
|
// 确保最多只显示4个歌单,并按播放次数排序
|
||||||
userPlaylist.value = (playlistData.playlist as Playlist[])
|
userPlaylist.value = (playlistData.playlist as Playlist[])
|
||||||
.sort((a, b) => b.playCount - a.playCount)
|
.sort((a, b) => b.playCount - a.playCount)
|
||||||
.slice(0, 3);
|
.slice(0, 4);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toSearchSinger = (keyword: string) => {
|
const settingsStore = useSettingsStore();
|
||||||
router.push({
|
|
||||||
path: '/search',
|
const handleOpenSinger = (id: number) => {
|
||||||
query: {
|
settingsStore.setCurrentArtistId(id);
|
||||||
keyword
|
settingsStore.setShowArtistDrawer(true);
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toPlaylist = async (id: number) => {
|
const toPlaylist = async (id: number) => {
|
||||||
@@ -226,63 +279,127 @@ watchEffect(() => {
|
|||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getPlaylistGridClass = (length: number) => {
|
||||||
|
switch(length) {
|
||||||
|
case 1:
|
||||||
|
return 'one-column';
|
||||||
|
case 2:
|
||||||
|
return 'two-columns';
|
||||||
|
case 3:
|
||||||
|
return 'three-columns';
|
||||||
|
default:
|
||||||
|
return 'four-columns';
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.recommend-singer {
|
.recommend-singer {
|
||||||
&-list {
|
&-list {
|
||||||
@apply flex;
|
@apply flex;
|
||||||
height: 280px;
|
height: 220px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
&-item {
|
&-item {
|
||||||
@apply flex-1 h-full rounded-3xl p-5 flex flex-col justify-between overflow-hidden;
|
@apply flex-1 h-full rounded-3xl p-5 flex flex-col justify-between overflow-hidden relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
&-bg {
|
&-bg {
|
||||||
@apply bg-gray-900 dark:bg-gray-800 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
@apply bg-gray-900 dark:bg-gray-800 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||||
filter: brightness(60%);
|
filter: brightness(60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-info {
|
&-info {
|
||||||
@apply flex items-center p-2;
|
@apply flex flex-col p-2;
|
||||||
&-play {
|
|
||||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer text-white;
|
|
||||||
}
|
|
||||||
&-name {
|
&-name {
|
||||||
@apply text-gray-100 dark:text-gray-100;
|
@apply text-gray-100 dark:text-gray-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-count {
|
&-count {
|
||||||
@apply text-gray-100 dark:text-gray-100;
|
@apply text-gray-100 dark:text-gray-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-play {
|
||||||
|
&-overlay {
|
||||||
|
@apply absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-black/20 z-20 opacity-0 transition-all duration-300 flex items-center justify-center;
|
||||||
|
backdrop-filter: blur(1px);
|
||||||
|
|
||||||
|
.recommend-singer-item:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-btn {
|
||||||
|
@apply w-20 h-20 bg-transparent flex justify-center items-center text-white;
|
||||||
|
transform: translateY(50px) scale(0.8);
|
||||||
|
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
|
||||||
|
.recommend-singer-item:hover & {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-play {
|
.user-play {
|
||||||
@apply bg-light-200 dark:bg-dark-100 rounded-3xl p-5 h-full flex flex-col;
|
@apply bg-light-300 dark:bg-dark-300 rounded-3xl px-4 py-3 h-full;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
&-title {
|
&-title {
|
||||||
@apply text-gray-900 dark:text-gray-100 font-bold text-lg;
|
@apply text-gray-900 dark:text-gray-100 font-bold text-lg line-clamp-1;
|
||||||
}
|
}
|
||||||
&-list {
|
&-list {
|
||||||
@apply grid grid-cols-3 gap-5 h-full;
|
@apply grid gap-3 h-full;
|
||||||
|
&.one-column {
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
.user-play-item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.two-columns {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
.user-play-item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.three-columns {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
.user-play-item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.four-columns {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
.user-play-item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&-item {
|
&-item {
|
||||||
@apply bg-light dark:bg-dark-200 rounded-2xl overflow-hidden flex flex-col cursor-pointer transition-all duration-300;
|
@apply rounded-2xl overflow-hidden flex flex-col;
|
||||||
height: 190px;
|
height: 176px;
|
||||||
&:hover {
|
|
||||||
|
&-img {
|
||||||
|
@apply relative cursor-pointer transition-all duration-300;
|
||||||
|
height: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%; /* 确保宽高比为1:1,即正方形 */
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||||
.user-play-item-overlay {
|
.user-play-item-overlay {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&-img {
|
|
||||||
@apply relative;
|
|
||||||
height: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 100%; /* 确保宽高比为1:1,即正方形 */
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@apply absolute inset-0 w-full h-full object-cover;
|
@apply absolute inset-0 w-full h-full object-cover;
|
||||||
@@ -291,13 +408,25 @@ watchEffect(() => {
|
|||||||
&-overlay {
|
&-overlay {
|
||||||
@apply absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center opacity-0 transition-opacity duration-300;
|
@apply absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center opacity-0 transition-opacity duration-300;
|
||||||
}
|
}
|
||||||
&-info {
|
&-title {
|
||||||
@apply px-1 py-1;
|
@apply absolute top-0 left-0 right-0 p-2 bg-gradient-to-b from-black/70 to-transparent z-10;
|
||||||
&-name {
|
&-name {
|
||||||
@apply text-gray-900 dark:text-gray-100 font-medium text-sm line-clamp-1;
|
@apply text-white font-medium text-sm line-clamp-3;
|
||||||
}
|
}
|
||||||
&-count {
|
}
|
||||||
@apply text-gray-700 dark:text-gray-300 mt-1;
|
&-count {
|
||||||
|
@apply absolute bottom-2 right-2 z-10;
|
||||||
|
&-tag {
|
||||||
|
@apply px-2 py-0.5 text-xs text-white bg-black/50 backdrop-blur-sm rounded-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-play-btn {
|
||||||
|
@apply flex items-center justify-center;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
.user-play-item:hover & {
|
||||||
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ watch(
|
|||||||
() => settingsStore.currentArtistId,
|
() => settingsStore.currentArtistId,
|
||||||
(newId) => {
|
(newId) => {
|
||||||
if (newId) {
|
if (newId) {
|
||||||
|
console.log('newId',newId)
|
||||||
artistDrawerShow.value = true;
|
artistDrawerShow.value = true;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
artistDrawerRef.value?.loadArtistInfo(newId);
|
artistDrawerRef.value?.loadArtistInfo(newId);
|
||||||
|
|||||||
Vendored
+1
@@ -8,6 +8,7 @@ export interface IElectronAPI {
|
|||||||
openLyric: () => void;
|
openLyric: () => void;
|
||||||
sendLyric: (data: string) => void;
|
sendLyric: (data: string) => void;
|
||||||
unblockMusic: (id: number) => Promise<string>;
|
unblockMusic: (id: number) => Promise<string>;
|
||||||
|
onLanguageChanged: (callback: (locale: string) => void) => void;
|
||||||
store: {
|
store: {
|
||||||
get: (key: string) => Promise<any>;
|
get: (key: string) => Promise<any>;
|
||||||
set: (key: string, value: any) => Promise<boolean>;
|
set: (key: string, value: any) => Promise<boolean>;
|
||||||
|
|||||||
Reference in New Issue
Block a user