feat: 优化歌词体验

This commit is contained in:
alger
2024-05-20 19:54:00 +08:00
parent 50e594b91d
commit 7c8598ffa5
8 changed files with 173 additions and 41 deletions
+1
View File
@@ -39,6 +39,7 @@ function createWindow() {
label: '退出', label: '退出',
click: () => { click: () => {
win.destroy(); win.destroy();
app.quit();
}, },
}, },
]); ]);
+35 -3
View File
@@ -3,7 +3,7 @@ const path = require('path');
let lyricWindow = null; let lyricWindow = null;
const loadLyricWindow = (ipcMain) => { const createWin = () => {
lyricWindow = new BrowserWindow({ lyricWindow = new BrowserWindow({
width: 800, width: 800,
height: 300, height: 300,
@@ -16,8 +16,16 @@ const loadLyricWindow = (ipcMain) => {
contextIsolation: false, contextIsolation: false,
}, },
}); });
};
const loadLyricWindow = (ipcMain) => {
ipcMain.on('open-lyric', () => { ipcMain.on('open-lyric', () => {
if (lyricWindow) {
if (lyricWindow.isMinimized()) lyricWindow.restore();
lyricWindow.focus();
lyricWindow.show();
return;
}
createWin();
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
lyricWindow.webContents.openDevTools({ mode: 'detach' }); lyricWindow.webContents.openDevTools({ mode: 'detach' });
lyricWindow.loadURL('http://localhost:4678/#/lyric'); lyricWindow.loadURL('http://localhost:4678/#/lyric');
@@ -26,11 +34,35 @@ const loadLyricWindow = (ipcMain) => {
lyricWindow.loadURL(`file://${distPath}/index.html#/lyric`); lyricWindow.loadURL(`file://${distPath}/index.html#/lyric`);
} }
lyricWindow.setMinimumSize(600, 200);
// 隐藏任务栏
lyricWindow.setSkipTaskbar(true);
lyricWindow.show(); lyricWindow.show();
}); });
ipcMain.on('send-lyric', (e, data) => { ipcMain.on('send-lyric', (e, data) => {
lyricWindow.webContents.send('receive-lyric', data); if (lyricWindow) {
lyricWindow.webContents.send('receive-lyric', data);
}
});
ipcMain.on('top-lyric', (e, data) => {
lyricWindow.setAlwaysOnTop(data);
});
ipcMain.on('close-lyric', () => {
lyricWindow.close();
lyricWindow = null;
});
ipcMain.on('mouseenter-lyric', () => {
lyricWindow.setIgnoreMouseEvents(true);
});
ipcMain.on('mouseleave-lyric', () => {
lyricWindow.setIgnoreMouseEvents(false);
}); });
}; };
+2 -2
View File
@@ -39,8 +39,8 @@
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.2.3",
"@vue/compiler-sfc": "^3.3.4", "@vue/compiler-sfc": "^3.3.4",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"electron": "^28.0.0", "electron": "^30.0.6",
"electron-builder": "^24.9.1", "electron-builder": "^24.13.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
+10 -4
View File
@@ -18,8 +18,8 @@
<i class="iconfont icon-likefill"></i> <i class="iconfont icon-likefill"></i>
</div> </div>
<div <div
class="song-item-operating-play bg-black" class="song-item-operating-play bg-black animate__animated"
:class="isPlaying ? 'bg-green-600' : ''" :class="{ 'bg-green-600': isPlaying, animate__flipInY: playLoading }"
@click="playMusicEvent(item)" @click="playMusicEvent(item)"
> >
<i v-if="isPlaying && play" class="iconfont icon-stop"></i> <i v-if="isPlaying && play" class="iconfont icon-stop"></i>
@@ -51,6 +51,8 @@ const play = computed(() => store.state.play as boolean);
const playMusic = computed(() => store.state.playMusic); const playMusic = computed(() => store.state.playMusic);
const playLoading = computed(() => playMusic.value.id === props.item.id && playMusic.value.playLoading);
// 判断是否为正在播放的音乐 // 判断是否为正在播放的音乐
const isPlaying = computed(() => { const isPlaying = computed(() => {
return playMusic.value.id === props.item.id; return playMusic.value.id === props.item.id;
@@ -59,8 +61,11 @@ const isPlaying = computed(() => {
const emits = defineEmits(['play']); const emits = defineEmits(['play']);
// 播放音乐 设置音乐详情 打开音乐底栏 // 播放音乐 设置音乐详情 打开音乐底栏
const playMusicEvent = (item: any) => { const playMusicEvent = async (item: any) => {
store.commit('setPlay', item); if (playMusic.value.id === item.id) {
return;
}
await store.commit('setPlay', item);
store.commit('setIsPlay', true); store.commit('setIsPlay', true);
emits('play', item); emits('play', item);
}; };
@@ -105,6 +110,7 @@ const playMusicEvent = (item: any) => {
} }
&-play { &-play {
@apply cursor-pointer border border-gray-500 rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition; @apply cursor-pointer border border-gray-500 rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition;
animation-iteration-count: infinite;
} }
} }
} }
+2 -1
View File
@@ -133,7 +133,7 @@ export const getLrcTimeRange = (index: any) => {
return { currentTime, nextTime }; return { currentTime, nextTime };
}; };
export const sendLyricToWin = (isPlay: boolean) => { export const sendLyricToWin = (isPlay: boolean = true) => {
try { try {
// 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间 // 设置lyricWinData 获取 当前播放的两句歌词 和歌词时间
let lyricWinData = null; let lyricWinData = null;
@@ -166,4 +166,5 @@ export const sendLyricToWin = (isPlay: boolean) => {
export const openLyric = () => { export const openLyric = () => {
const windowData = window as any; const windowData = window as any;
windowData.electronAPI.openLyric(); windowData.electronAPI.openLyric();
sendLyricToWin();
}; };
+4 -1
View File
@@ -16,6 +16,7 @@ interface State {
playList: SongResult[]; playList: SongResult[];
playListIndex: number; playListIndex: number;
setData: any; setData: any;
lyric: any;
} }
const state: State = { const state: State = {
@@ -28,6 +29,7 @@ const state: State = {
playList: [], playList: [],
playListIndex: 0, playListIndex: 0,
setData: null, setData: null,
lyric: {},
}; };
const windowData = window as any; const windowData = window as any;
@@ -39,10 +41,11 @@ const mutations = {
state.menus = menus; state.menus = menus;
}, },
async setPlay(state: State, playMusic: SongResult) { async setPlay(state: State, playMusic: SongResult) {
state.playMusic = playMusic; state.playMusic = { ...playMusic, playLoading: true };
state.playMusicUrl = await getSongUrl(playMusic.id); state.playMusicUrl = await getSongUrl(playMusic.id);
state.play = true; state.play = true;
musicHistory.addMusic(playMusic); musicHistory.addMusic(playMusic);
state.playMusic.playLoading = false;
}, },
setIsPlay(state: State, isPlay: boolean) { setIsPlay(state: State, isPlay: boolean) {
state.isPlay = isPlay; state.isPlay = isPlay;
+1
View File
@@ -15,6 +15,7 @@ export interface SongResult {
song: Song; song: Song;
alg: string; alg: string;
count?: number; count?: number;
playLoading?: boolean;
} }
interface Song { interface Song {
+118 -30
View File
@@ -1,5 +1,6 @@
<template> <template>
<div class="lyric-window" :class="theme"> <div class="lyric-window" :class="[lyricSetting.theme, { lyric_lock: lyricSetting.isLock }]">
<div class="drag-bar"></div>
<div class="lyric-bar" :class="{ 'lyric-bar-hover': isDrag }"> <div class="lyric-bar" :class="{ 'lyric-bar-hover': isDrag }">
<div class="buttons"> <div class="buttons">
<!-- <div class="music-buttons"> <!-- <div class="music-buttons">
@@ -13,23 +14,32 @@
<i class="iconfont icon-next"></i> <i class="iconfont icon-next"></i>
</div> </div>
</div> --> </div> -->
<div class="check-theme" @click="checkTheme"> <div class="button check-theme" @click="checkTheme">
<i v-if="theme === 'light'" class="icon ri-sun-line"></i> <i v-if="lyricSetting.theme === 'light'" class="icon ri-sun-line"></i>
<i v-else class="icon ri-moon-line"></i> <i v-else class="icon ri-moon-line"></i>
</div> </div>
<div class="button-move"> <div class="button">
<i class="icon ri-drag-move-2-line"></i> <i class="icon ri-share-2-line" :class="{ checked: lyricSetting.isTop }" @click="handleTop"></i>
</div>
<div class="button button-lock" @click="handleLock">
<i v-if="lyricSetting.isLock" class="icon ri-lock-line"></i>
<i v-else class="icon ri-lock-unlock-line"></i>
</div>
<div class="button">
<i class="icon ri-close-circle-line" @click="handleClose"></i>
</div> </div>
</div> </div>
</div> </div>
<div v-if="lyricData.lrcArray[lyricData.nowIndex]" class="lyric-box"> <div id="clickThroughElement" class="lyric-box">
<h2 class="lyric lyric-current">{{ lyricData.lrcArray[lyricData.nowIndex].text }}</h2> <template v-if="lyricData.lrcArray[lyricData.nowIndex]">
<p class="lyric-current">{{ lyricData.currentLrc.trText }}</p> <h2 class="lyric lyric-current">{{ lyricData.lrcArray[lyricData.nowIndex].text }}</h2>
<template v-if="lyricData.lrcArray[lyricData.nowIndex + 1]"> <p class="lyric-current">{{ lyricData.currentLrc.trText }}</p>
<h2 class="lyric lyric-next"> <template v-if="lyricData.lrcArray[lyricData.nowIndex + 1]">
{{ lyricData.lrcArray[lyricData.nowIndex + 1].text }} <h2 class="lyric lyric-next">
</h2> {{ lyricData.lrcArray[lyricData.nowIndex + 1].text }}
<p class="lyric-next">{{ lyricData.nextLrc.trText }}</p> </h2>
<p class="lyric-next">{{ lyricData.nextLrc.trText }}</p>
</template>
</template> </template>
</div> </div>
</div> </div>
@@ -38,7 +48,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useIpcRenderer } from '@vueuse/electron'; import { useIpcRenderer } from '@vueuse/electron';
const windowData = window as any;
const ipcRenderer = useIpcRenderer(); const ipcRenderer = useIpcRenderer();
const lyricData = ref({ const lyricData = ref({
@@ -60,27 +69,64 @@ const lyricData = ref({
nowIndex: 0, nowIndex: 0,
}); });
const lyricSetting = ref({
...(localStorage.getItem('lyricData')
? JSON.parse(localStorage.getItem('lyricData') || '')
: {
isTop: false,
theme: 'light',
isLock: false,
}),
});
onMounted(() => { onMounted(() => {
ipcRenderer.on('receive-lyric', (event, data) => { ipcRenderer.on('receive-lyric', (event, data) => {
try { try {
lyricData.value = JSON.parse(data); lyricData.value = JSON.parse(data);
console.log('lyricData.value', lyricData.value);
} catch (error) { } catch (error) {
console.error('error', error); console.error('error', error);
} }
}); });
}); });
const theme = ref('dark');
const checkTheme = () => { const checkTheme = () => {
if (theme.value === 'light') { if (lyricSetting.value.theme === 'light') {
theme.value = 'dark'; lyricSetting.value.theme = 'dark';
} else { } else {
theme.value = 'light'; lyricSetting.value.theme = 'light';
} }
}; };
// const drag = (event: MouseEvent) => {
// windowData.electronAPI.dragStart(event); const handleTop = () => {
// }; lyricSetting.value.isTop = !lyricSetting.value.isTop;
ipcRenderer.send('top-lyric', lyricSetting.value.isTop);
};
const handleLock = () => {
lyricSetting.value.isLock = !lyricSetting.value.isLock;
};
const handleClose = () => {
ipcRenderer.send('close-lyric');
};
watch(
() => lyricSetting.value,
(newValue) => {
localStorage.setItem('lyricData', JSON.stringify(newValue));
},
{ deep: true },
);
// onMounted(() => {
// const el = document.getElementById('clickThroughElement') as HTMLElement;
// el.addEventListener('mouseenter', () => {
// if (lyricSetting.value.isLock) ipcRenderer.send('mouseenter-lyric');
// });
// el.addEventListener('mouseleave', () => {
// if (lyricSetting.value.isLock) ipcRenderer.send('mouseleave-lyric');
// });
// });
</script> </script>
<style> <style>
@@ -93,10 +139,36 @@ body {
.lyric-window { .lyric-window {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
@apply overflow-hidden text-gray-600 hover:bg-gray-400 hover:bg-opacity-75; @apply overflow-hidden text-gray-600 rounded-xl box-border;
// border: 4px solid transparent;
&:hover .lyric-bar { &:hover .lyric-bar {
opacity: 1; opacity: 1;
} }
&:hover .drag-bar {
opacity: 1;
}
&:hover {
box-shadow: inset 0 0 10px 0 rgba(255, 255, 255, 0.5);
}
}
.lyric_lock {
&:hover {
box-shadow: none;
}
&:hover .lyric-bar {
background-color: transparent;
.button {
opacity: 0;
}
.button-lock {
opacity: 1;
color: #d6d6d6;
}
}
&:hover .drag-bar {
opacity: 0;
}
} }
.icon { .icon {
@@ -107,7 +179,7 @@ body {
background-color: #b1b1b1; background-color: #b1b1b1;
@apply flex flex-col justify-center items-center; @apply flex flex-col justify-center items-center;
width: 100vw; width: 100vw;
height: 100px; height: 40px;
opacity: 0; opacity: 0;
&:hover { &:hover {
opacity: 1; opacity: 1;
@@ -116,10 +188,24 @@ body {
.lyric-bar-hover { .lyric-bar-hover {
opacity: 1; opacity: 1;
} }
.drag-bar {
-webkit-app-region: drag;
height: 20px;
cursor: move;
background-color: #383838;
opacity: 0;
}
.buttons { .buttons {
width: 100vw; width: 100vw;
height: 100px; height: 100px;
@apply flex justify-center items-center; @apply flex justify-center items-center gap-4;
}
.button {
@apply cursor-pointer text-center;
}
.checked {
color: #fff !important;
} }
.button-move { .button-move {
-webkit-app-region: drag; -webkit-app-region: drag;
@@ -151,9 +237,9 @@ body {
} }
.lyric { .lyric {
text-shadow: 0 0 10px #fff; text-shadow: 0 0 1vw #2c2c2c;
font-size: 4vw; font-size: 4vw;
@apply font-bold m-0 p-0 whitespace-nowrap select-none pointer-events-none; @apply font-bold m-0 p-0 select-none pointer-events-none;
} }
.lyric-current { .lyric-current {
@@ -168,6 +254,7 @@ body {
.lyric-window.dark { .lyric-window.dark {
.lyric { .lyric {
text-shadow: none; text-shadow: none;
text-shadow: 0 0 1vw #000000;
} }
.lyric-current { .lyric-current {
color: #fff; color: #fff;
@@ -176,7 +263,8 @@ body {
color: #cecece; color: #cecece;
} }
} }
// .lyric-box { .lyric-box {
// writing-mode: vertical-rl; // writing-mode: vertical-rl;
// } padding: 10px;
}
</style> </style>