mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
✨ feat: 优化播放器样式 添加单曲循环 优化桌面歌词效果
This commit is contained in:
+42
-2
@@ -1,17 +1,19 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow, screen } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
let lyricWindow = null;
|
let lyricWindow = null;
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
const createWin = () => {
|
const createWin = () => {
|
||||||
lyricWindow = new BrowserWindow({
|
lyricWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 300,
|
height: 200,
|
||||||
frame: false,
|
frame: false,
|
||||||
show: false,
|
show: false,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
|
alwaysOnTop: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -68,6 +70,44 @@ const loadLyricWindow = (ipcMain) => {
|
|||||||
ipcMain.on('mouseleave-lyric', () => {
|
ipcMain.on('mouseleave-lyric', () => {
|
||||||
lyricWindow.setIgnoreMouseEvents(false);
|
lyricWindow.setIgnoreMouseEvents(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 开始拖动
|
||||||
|
ipcMain.on('lyric-drag-start', () => {
|
||||||
|
isDragging = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理拖动移动
|
||||||
|
ipcMain.on('lyric-drag-move', (e, { deltaX, deltaY }) => {
|
||||||
|
if (!lyricWindow) return;
|
||||||
|
|
||||||
|
const [currentX, currentY] = lyricWindow.getPosition();
|
||||||
|
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize;
|
||||||
|
const [windowWidth, windowHeight] = lyricWindow.getSize();
|
||||||
|
|
||||||
|
// 计算新位置,确保窗口不会移出屏幕
|
||||||
|
const newX = Math.max(0, Math.min(currentX + deltaX, screenWidth - windowWidth));
|
||||||
|
const newY = Math.max(0, Math.min(currentY + deltaY, screenHeight - windowHeight));
|
||||||
|
|
||||||
|
lyricWindow.setPosition(newX, newY);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 结束拖动
|
||||||
|
ipcMain.on('lyric-drag-end', () => {
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加鼠标穿透事件处理
|
||||||
|
ipcMain.on('set-ignore-mouse', (e, shouldIgnore) => {
|
||||||
|
if (!lyricWindow) return;
|
||||||
|
|
||||||
|
if (shouldIgnore) {
|
||||||
|
// 设置鼠标穿透,但保留拖动区域可交互
|
||||||
|
lyricWindow.setIgnoreMouseEvents(true, { forward: true });
|
||||||
|
} else {
|
||||||
|
// 取消鼠标穿透
|
||||||
|
lyricWindow.setIgnoreMouseEvents(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -92,7 +92,13 @@ export const audioServiceOn = (audio: typeof audioService) => {
|
|||||||
// 监听结束
|
// 监听结束
|
||||||
audio.onEnd(() => {
|
audio.onEnd(() => {
|
||||||
handleEnded();
|
handleEnded();
|
||||||
store.commit('nextPlay');
|
if (store.state.playMode === 1) {
|
||||||
|
// 单曲循环模式
|
||||||
|
audio.getCurrentSound()?.play();
|
||||||
|
} else {
|
||||||
|
// 列表循环模式
|
||||||
|
store.commit('nextPlay');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.n-slider-handle-indicator--top {
|
||||||
|
@apply bg-transparent text-[#ffffffdd] text-2xl px-2 py-1 shadow-none mb-0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.text-el {
|
.text-el {
|
||||||
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
|
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
<!-- 展开全屏 -->
|
<!-- 展开全屏 -->
|
||||||
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" />
|
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" />
|
||||||
<!-- 底部播放栏 -->
|
<!-- 底部播放栏 -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="music-play-bar"
|
class="music-play-bar"
|
||||||
:class="setAnimationClass('animate__bounceInUp') + ' ' + (musicFullVisible ? 'play-bar-opcity' : '')"
|
:class="setAnimationClass('animate__bounceInUp') + ' ' + (musicFullVisible ? 'play-bar-opcity' : '')"
|
||||||
>
|
>
|
||||||
|
<div class="music-time custom-slider">
|
||||||
|
<n-slider v-model:value="timeSlider" :step="1" :max="allTime" :min="0" :format-tooltip="formatTooltip"></n-slider>
|
||||||
|
</div>
|
||||||
<div class="play-bar-img-wrapper" @click="setMusicFull">
|
<div class="play-bar-img-wrapper" @click="setMusicFull">
|
||||||
<n-image :src="getImgUrl(playMusic?.picUrl, '300y300')" class="play-bar-img" lazy preview-disabled />
|
<n-image :src="getImgUrl(playMusic?.picUrl, '300y300')" class="play-bar-img" lazy preview-disabled />
|
||||||
<div class="hover-arrow">
|
<div class="hover-arrow">
|
||||||
@@ -38,23 +42,26 @@
|
|||||||
<div class="music-buttons-play" @click="playMusicEvent">
|
<div class="music-buttons-play" @click="playMusicEvent">
|
||||||
<i class="iconfont icon" :class="play ? 'icon-stop' : 'icon-play'"></i>
|
<i class="iconfont icon" :class="play ? 'icon-stop' : 'icon-play'"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="music-buttons-next" @click="handleEnded">
|
<div class="music-buttons-next" @click="handleNext">
|
||||||
<i class="iconfont icon-next"></i>
|
<i class="iconfont icon-next"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="music-time custom-slider">
|
|
||||||
<div class="time">{{ getNowTime }}</div>
|
|
||||||
<n-slider v-model:value="timeSlider" :step="0.05" :tooltip="false"></n-slider>
|
|
||||||
<div class="time">{{ getAllTime }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="audio-volume custom-slider">
|
|
||||||
<div>
|
|
||||||
<i class="iconfont icon-notificationfill"></i>
|
|
||||||
</div>
|
|
||||||
<n-slider v-model:value="volumeSlider" :step="0.01" :tooltip="false"></n-slider>
|
|
||||||
</div>
|
|
||||||
<div class="audio-button">
|
<div class="audio-button">
|
||||||
<n-tooltip trigger="hover" :z-index="9999999" @click="toggleFavorite">
|
<div class="audio-volume custom-slider">
|
||||||
|
<div class="volume-icon" @click="mute">
|
||||||
|
<i class="iconfont" :class="getVolumeIcon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="volume-slider">
|
||||||
|
<n-slider v-model:value="volumeSlider" :step="0.01" :tooltip="false" vertical></n-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-tooltip trigger="hover" :z-index="9999999">
|
||||||
|
<template #trigger>
|
||||||
|
<i class="iconfont" :class="playModeIcon" @click="togglePlayMode"></i>
|
||||||
|
</template>
|
||||||
|
{{ playModeText }}
|
||||||
|
</n-tooltip>
|
||||||
|
<n-tooltip trigger="hover" :z-index="9999999">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<i class="iconfont icon-likefill" :class="{ 'like-active': isFavorite }" @click="toggleFavorite"></i>
|
<i class="iconfont icon-likefill" :class="{ 'like-active': isFavorite }" @click="toggleFavorite"></i>
|
||||||
</template>
|
</template>
|
||||||
@@ -133,17 +140,33 @@ watch(
|
|||||||
// 使用 useThrottleFn 创建节流版本的 seek 函数
|
// 使用 useThrottleFn 创建节流版本的 seek 函数
|
||||||
const throttledSeek = useThrottleFn((value: number) => {
|
const throttledSeek = useThrottleFn((value: number) => {
|
||||||
if (!sound.value) return;
|
if (!sound.value) return;
|
||||||
sound.value.seek((value * allTime.value) / 100);
|
sound.value.seek(value);
|
||||||
|
nowTime.value = value;
|
||||||
}, 50); // 50ms 的节流延迟
|
}, 50); // 50ms 的节流延迟
|
||||||
|
|
||||||
// 修改 timeSlider 计算属性
|
// 修改 timeSlider 计算属性
|
||||||
const timeSlider = computed({
|
const timeSlider = computed({
|
||||||
get: () => (nowTime.value / allTime.value) * 100,
|
get: () => nowTime.value,
|
||||||
set: throttledSeek,
|
set: throttledSeek,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const formatTooltip = (value: number) => {
|
||||||
|
return `${secondToMinute(value)} / ${secondToMinute(allTime.value)}`;
|
||||||
|
};
|
||||||
|
|
||||||
// 音量条
|
// 音量条
|
||||||
const audioVolume = ref(localStorage.getItem('volume') ? parseFloat(localStorage.getItem('volume') as string) : 1);
|
const audioVolume = ref(localStorage.getItem('volume') ? parseFloat(localStorage.getItem('volume') as string) : 1);
|
||||||
|
const getVolumeIcon = computed(() => {
|
||||||
|
// 0 静音 ri-volume-mute-line 0.5 ri-volume-down-line 1 ri-volume-up-line
|
||||||
|
if (audioVolume.value === 0) {
|
||||||
|
return 'ri-volume-mute-line';
|
||||||
|
}
|
||||||
|
if (audioVolume.value <= 0.5) {
|
||||||
|
return 'ri-volume-down-line';
|
||||||
|
}
|
||||||
|
return 'ri-volume-up-line';
|
||||||
|
});
|
||||||
|
|
||||||
const volumeSlider = computed({
|
const volumeSlider = computed({
|
||||||
get: () => audioVolume.value * 100,
|
get: () => audioVolume.value * 100,
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
@@ -153,17 +176,31 @@ const volumeSlider = computed({
|
|||||||
audioVolume.value = value / 100;
|
audioVolume.value = value / 100;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 获取当前播放时间
|
|
||||||
const getNowTime = computed(() => {
|
// 静音
|
||||||
return secondToMinute(nowTime.value);
|
const mute = () => {
|
||||||
|
if (volumeSlider.value === 0) {
|
||||||
|
volumeSlider.value = 30;
|
||||||
|
} else {
|
||||||
|
volumeSlider.value = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 播放模式
|
||||||
|
const playMode = computed(() => store.state.playMode);
|
||||||
|
const playModeIcon = computed(() => {
|
||||||
|
return playMode.value === 0 ? 'ri-repeat-2-line' : 'ri-repeat-one-line';
|
||||||
|
});
|
||||||
|
const playModeText = computed(() => {
|
||||||
|
return playMode.value === 0 ? '列表循环' : '单曲循环';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取总时间
|
// 切换播放模式
|
||||||
const getAllTime = computed(() => {
|
const togglePlayMode = () => {
|
||||||
return secondToMinute(allTime.value);
|
store.commit('togglePlayMode');
|
||||||
});
|
};
|
||||||
|
|
||||||
function handleEnded() {
|
function handleNext() {
|
||||||
store.commit('nextPlay');
|
store.commit('nextPlay');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +261,13 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.music-play-bar {
|
.music-play-bar {
|
||||||
@apply h-20 w-full absolute bottom-0 left-0 flex items-center rounded-t-2xl overflow-hidden box-border px-6 py-2;
|
@apply h-20 w-full absolute bottom-0 left-0 flex items-center box-border px-6 py-2 pt-3;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
box-shadow: 0px 0px 10px 2px rgba(203, 203, 203, 0.034);
|
box-shadow: 0px 0px 10px 2px rgba(203, 203, 203, 0.034);
|
||||||
background-color: #212121;
|
background-color: #212121;
|
||||||
animation-duration: 0.5s !important;
|
animation-duration: 0.5s !important;
|
||||||
.music-content {
|
.music-content {
|
||||||
width: 140px;
|
width: 160px;
|
||||||
@apply ml-4;
|
@apply ml-4;
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
@@ -253,14 +290,14 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.music-buttons {
|
.music-buttons {
|
||||||
@apply mx-6;
|
@apply mx-6 flex-1 flex justify-center;
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@apply text-2xl hover:text-green-500 transition;
|
@apply text-2xl hover:text-green-500 transition;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@apply text-xl hover:text-white;
|
@apply text-3xl hover:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@apply flex items-center;
|
@apply flex items-center;
|
||||||
@@ -270,25 +307,28 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-play {
|
&-play {
|
||||||
background: #383838;
|
background-color: #ffffff20;
|
||||||
@apply flex justify-center items-center w-12 h-12 rounded-full mx-4 hover:bg-green-500 transition bg-opacity-40;
|
@apply flex justify-center items-center w-20 h-12 rounded-full mx-4 hover:bg-[#ffffff40] transition;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-time {
|
|
||||||
@apply flex flex-1 items-center;
|
|
||||||
|
|
||||||
.time {
|
|
||||||
@apply mx-4 mt-1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio-volume {
|
.audio-volume {
|
||||||
width: 140px;
|
@apply flex items-center relative;
|
||||||
@apply flex items-center mx-4;
|
&:hover {
|
||||||
|
.volume-slider {
|
||||||
|
@apply opacity-100 visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.volume-icon {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@apply text-2xl hover:text-green-500 transition cursor-pointer mr-4;
|
@apply text-2xl hover:text-green-500 transition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider {
|
||||||
|
@apply absolute opacity-0 invisible transition-all duration-300 bottom-[30px] left-1/2 -translate-x-1/2 h-[180px] px-2 py-4 bg-gray-800 bg-opacity-80 rounded-xl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,17 +396,31 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
--n-handle-size: 12px;
|
--n-handle-size: 12px;
|
||||||
--n-handle-color: var(--primary-color);
|
--n-handle-color: var(--primary-color);
|
||||||
|
|
||||||
&:hover {
|
&.n-slider--vertical {
|
||||||
--n-rail-height: 6px;
|
height: 100%;
|
||||||
--n-handle-size: 14px;
|
|
||||||
|
.n-slider-rail {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.n-slider-rail {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-slider-handle {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.n-slider-rail {
|
.n-slider-rail {
|
||||||
@apply overflow-hidden;
|
@apply overflow-hidden transition-all duration-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.n-slider-handle {
|
.n-slider-handle {
|
||||||
@apply transition-opacity duration-200;
|
@apply transition-all duration-200;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,4 +472,17 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
.like-active {
|
.like-active {
|
||||||
@apply text-red-600;
|
@apply text-red-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-loop,
|
||||||
|
.icon-single-loop {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-time .n-slider {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+13
-2
@@ -13,6 +13,11 @@ const defaultSettings = {
|
|||||||
authorUrl: 'https://github.com/algerkong',
|
authorUrl: 'https://github.com/algerkong',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getLocalStorageItem<T>(key: string, defaultValue: T): T {
|
||||||
|
const item = localStorage.getItem(key);
|
||||||
|
return item ? JSON.parse(item) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
menus: any[];
|
menus: any[];
|
||||||
play: boolean;
|
play: boolean;
|
||||||
@@ -28,6 +33,7 @@ interface State {
|
|||||||
searchValue: string;
|
searchValue: string;
|
||||||
searchType: number;
|
searchType: number;
|
||||||
favoriteList: number[];
|
favoriteList: number[];
|
||||||
|
playMode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: State = {
|
const state: State = {
|
||||||
@@ -36,7 +42,7 @@ const state: State = {
|
|||||||
isPlay: false,
|
isPlay: false,
|
||||||
playMusic: {} as SongResult,
|
playMusic: {} as SongResult,
|
||||||
playMusicUrl: '',
|
playMusicUrl: '',
|
||||||
user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') as string) : null,
|
user: getLocalStorageItem('user', null),
|
||||||
playList: [],
|
playList: [],
|
||||||
playListIndex: 0,
|
playListIndex: 0,
|
||||||
setData: defaultSettings,
|
setData: defaultSettings,
|
||||||
@@ -44,7 +50,8 @@ const state: State = {
|
|||||||
isMobile: false,
|
isMobile: false,
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
searchType: 1,
|
searchType: 1,
|
||||||
favoriteList: localStorage.getItem('favoriteList') ? JSON.parse(localStorage.getItem('favoriteList') || '[]') : [],
|
favoriteList: getLocalStorageItem('favoriteList', []),
|
||||||
|
playMode: getLocalStorageItem('playMode', 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
||||||
@@ -91,6 +98,10 @@ const mutations = {
|
|||||||
state.favoriteList = state.favoriteList.filter((id) => id !== songId);
|
state.favoriteList = state.favoriteList.filter((id) => id !== songId);
|
||||||
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
|
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
|
||||||
},
|
},
|
||||||
|
togglePlayMode(state: State) {
|
||||||
|
state.playMode = state.playMode === 0 ? 1 : 0;
|
||||||
|
localStorage.setItem('playMode', JSON.stringify(state.playMode));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
|||||||
+129
-33
@@ -2,9 +2,11 @@
|
|||||||
<div
|
<div
|
||||||
class="lyric-window"
|
class="lyric-window"
|
||||||
:class="[lyricSetting.theme, { lyric_lock: lyricSetting.isLock }]"
|
:class="[lyricSetting.theme, { lyric_lock: lyricSetting.isLock }]"
|
||||||
|
@mousedown="handleMouseDown"
|
||||||
@mouseenter="handleMouseEnter"
|
@mouseenter="handleMouseEnter"
|
||||||
@mouseleave="handleMouseLeave"
|
@mouseleave="handleMouseLeave"
|
||||||
>
|
>
|
||||||
|
<div class="drag-overlay"></div>
|
||||||
<!-- 顶部控制栏 -->
|
<!-- 顶部控制栏 -->
|
||||||
<div class="control-bar" :class="{ 'control-bar-show': showControls }">
|
<div class="control-bar" :class="{ 'control-bar-show': showControls }">
|
||||||
<div class="font-size-controls">
|
<div class="font-size-controls">
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
<div class="control-button" @click="handleTop">
|
<div class="control-button" @click="handleTop">
|
||||||
<i class="ri-pushpin-line" :class="{ active: lyricSetting.isTop }"></i>
|
<i class="ri-pushpin-line" :class="{ active: lyricSetting.isTop }"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-button" @click="handleLock">
|
<div id="lyric-lock" class="control-button" @click="handleLock">
|
||||||
<i v-if="lyricSetting.isLock" class="ri-lock-line"></i>
|
<i v-if="lyricSetting.isLock" class="ri-lock-line"></i>
|
||||||
<i v-else class="ri-lock-unlock-line"></i>
|
<i v-else class="ri-lock-unlock-line"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else class="lyric-empty">暂无歌词</div>
|
<div v-else class="lyric-empty">无歌词</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,6 +86,8 @@ const isInitialized = ref(false);
|
|||||||
// 字体大小控制
|
// 字体大小控制
|
||||||
const fontSize = ref(24); // 默认字体大小
|
const fontSize = ref(24); // 默认字体大小
|
||||||
const fontSizeStep = 2; // 每次整的步长
|
const fontSizeStep = 2; // 每次整的步长
|
||||||
|
const animationFrameId = ref<number | null>(null);
|
||||||
|
const lastUpdateTime = ref(performance.now());
|
||||||
|
|
||||||
// 静态数据
|
// 静态数据
|
||||||
const staticData = ref<{
|
const staticData = ref<{
|
||||||
@@ -136,14 +140,21 @@ const clearHideTimer = () => {
|
|||||||
|
|
||||||
// 处理鼠标进入窗口
|
// 处理鼠标进入窗口
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (!lyricSetting.value.isLock) return;
|
console.log('handleMouseEnter');
|
||||||
isHovering.value = true;
|
if (lyricSetting.value.isLock) {
|
||||||
|
isHovering.value = true;
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', true);
|
||||||
|
} else {
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理鼠标离开窗口
|
// 处理鼠标离开窗口
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
|
console.log('handleMouseLeave');
|
||||||
if (!lyricSetting.value.isLock) return;
|
if (!lyricSetting.value.isLock) return;
|
||||||
isHovering.value = false;
|
isHovering.value = false;
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听锁定状态变化
|
// 监听锁定状态变化
|
||||||
@@ -180,7 +191,7 @@ const wrapperStyle = computed(() => {
|
|||||||
const containerCenter = containerHeight.value / 2;
|
const containerCenter = containerHeight.value / 2;
|
||||||
|
|
||||||
// 计算当前行到顶部的距离(包含padding)
|
// 计算当前行到顶部的距离(包含padding)
|
||||||
const currentLineTop = currentIndex.value * lineHeight.value + containerHeight.value * 0.2; // 加上顶部padding
|
const currentLineTop = currentIndex.value * lineHeight.value + containerHeight.value * 0.2 + lineHeight.value; // 加上顶部padding
|
||||||
|
|
||||||
// 计算偏移量,使当前行居中
|
// 计算偏移量,使当前行居中
|
||||||
const targetOffset = containerCenter - currentLineTop;
|
const targetOffset = containerCenter - currentLineTop;
|
||||||
@@ -265,10 +276,6 @@ onMounted(() => {
|
|||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动画帧ID
|
|
||||||
const animationFrameId = ref<number | null>(null);
|
|
||||||
|
|
||||||
// 实际播放时间
|
// 实际播放时间
|
||||||
const actualTime = ref(0);
|
const actualTime = ref(0);
|
||||||
|
|
||||||
@@ -317,9 +324,8 @@ const updateProgress = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 记录上次更新时间
|
// 记录上次更新时间
|
||||||
const lastUpdateTime = ref(performance.now());
|
|
||||||
|
|
||||||
// 监听数据更新
|
// 监听据更新
|
||||||
watch(
|
watch(
|
||||||
() => dynamicData.value,
|
() => dynamicData.value,
|
||||||
(newData: any) => {
|
(newData: any) => {
|
||||||
@@ -351,7 +357,7 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 修改数据更新处理
|
// 修改数据更新处
|
||||||
const handleDataUpdate = (parsedData: {
|
const handleDataUpdate = (parsedData: {
|
||||||
nowTime: number;
|
nowTime: number;
|
||||||
startCurrentTime: number;
|
startCurrentTime: number;
|
||||||
@@ -405,7 +411,7 @@ onMounted(() => {
|
|||||||
animationFrameId.value = null;
|
animationFrameId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保据格式正确
|
// 确保数据格式正确
|
||||||
if (Array.isArray(parsedData.lrcArray)) {
|
if (Array.isArray(parsedData.lrcArray)) {
|
||||||
staticData.value = {
|
staticData.value = {
|
||||||
lrcArray: parsedData.lrcArray,
|
lrcArray: parsedData.lrcArray,
|
||||||
@@ -446,6 +452,7 @@ const handleTop = () => {
|
|||||||
|
|
||||||
const handleLock = () => {
|
const handleLock = () => {
|
||||||
lyricSetting.value.isLock = !lyricSetting.value.isLock;
|
lyricSetting.value.isLock = !lyricSetting.value.isLock;
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', lyricSetting.value.isLock);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -459,6 +466,74 @@ watch(
|
|||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 添加拖动相关变量
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const startPosition = ref({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
// 处理鼠标按下事件
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
// 如果点击的是控制按钮区域或窗口被锁定,不处理拖动
|
||||||
|
if (
|
||||||
|
lyricSetting.value.isLock ||
|
||||||
|
(e.target as HTMLElement).closest('.control-buttons') ||
|
||||||
|
(e.target as HTMLElement).closest('.font-size-controls')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只响应鼠标左键
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
|
isDragging.value = true;
|
||||||
|
startPosition.value = { x: e.screenX, y: e.screenY };
|
||||||
|
|
||||||
|
// 添加全局鼠标事件监听
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!isDragging.value) return;
|
||||||
|
|
||||||
|
const deltaX = e.screenX - startPosition.value.x;
|
||||||
|
const deltaY = e.screenY - startPosition.value.y;
|
||||||
|
|
||||||
|
// 发送移动事件到主进程
|
||||||
|
windowData.electron.ipcRenderer.send('lyric-drag-move', { deltaX, deltaY });
|
||||||
|
startPosition.value = { x: e.screenX, y: e.screenY };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
if (!isDragging.value) return;
|
||||||
|
isDragging.value = false;
|
||||||
|
|
||||||
|
// 移除事件监听
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加全局事件监听
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
isDragging.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const lyricLock = document.getElementById('lyric-lock');
|
||||||
|
if (lyricLock) {
|
||||||
|
lyricLock.onmouseenter = () => {
|
||||||
|
if (lyricSetting.value.isLock) {
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lyricLock.onmouseleave = () => {
|
||||||
|
if (lyricSetting.value.isLock) {
|
||||||
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -474,9 +549,25 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
user-select: none;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
.control-bar {
|
||||||
|
&-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
--bg-color: transparent;
|
|
||||||
--text-color: #ffffff;
|
--text-color: #ffffff;
|
||||||
--text-secondary: rgba(255, 255, 255, 0.6);
|
--text-secondary: rgba(255, 255, 255, 0.6);
|
||||||
--highlight-color: #1db954;
|
--highlight-color: #1db954;
|
||||||
@@ -484,22 +575,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
--bg-color: transparent;
|
|
||||||
--text-color: #333333;
|
--text-color: #333333;
|
||||||
--text-secondary: rgba(51, 51, 51, 0.6);
|
--text-secondary: rgba(51, 51, 51, 0.6);
|
||||||
--highlight-color: #1db954;
|
--highlight-color: #1db954;
|
||||||
--control-bg: rgba(255, 255, 255, 0.3);
|
--control-bg: rgba(255, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lyric_lock {
|
|
||||||
.control-bar {
|
|
||||||
background: var(--control-bg);
|
|
||||||
|
|
||||||
&-show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-bar {
|
.control-bar {
|
||||||
@@ -508,8 +588,6 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background: var(--control-bg);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -519,14 +597,8 @@ body {
|
|||||||
transition:
|
transition:
|
||||||
opacity 0.2s ease,
|
opacity 0.2s ease,
|
||||||
visibility 0.2s ease;
|
visibility 0.2s ease;
|
||||||
-webkit-app-region: drag;
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
&-show {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-size-controls {
|
.font-size-controls {
|
||||||
margin-right: auto; // 将字体控制放在侧
|
margin-right: auto; // 将字体控制放在侧
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
@@ -583,6 +655,7 @@ body {
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lyric-scroll {
|
.lyric-scroll {
|
||||||
@@ -663,4 +736,27 @@ body {
|
|||||||
.lyric-line-current {
|
.lyric-line-current {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control-bar {
|
||||||
|
.control-buttons {
|
||||||
|
.control-button {
|
||||||
|
&:not(:has(.ri-lock-line)):not(:has(.ri-lock-unlock-line)) {
|
||||||
|
.lyric_lock & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyric_lock & .font-size-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lyric_lock {
|
||||||
|
background: transparent;
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+8
-2
@@ -1,8 +1,14 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
colors: {
|
||||||
|
highlight: 'var(--highlight-color)',
|
||||||
|
text: 'var(--text-color)',
|
||||||
|
secondary: 'var(--text-secondary)',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user