🦄 refactor: 重构整个项目 优化打包 修改后台服务为本地运行 添加更新版本检测功能

This commit is contained in:
alger
2025-01-01 02:25:18 +08:00
parent f8d421c9b1
commit 17d20fa299
260 changed files with 78557 additions and 1693 deletions
+187
View File
@@ -0,0 +1,187 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ref } from 'vue';
// 创建一个使用 IndexedDB 的组合函数
const useIndexedDB = () => {
const db = ref<IDBDatabase | null>(null); // 数据库引用
// 打开数据库并创建表
const initDB = (
dbName: string,
version: number,
stores: { name: string; keyPath?: string }[]
) => {
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open(dbName, version); // 打开数据库请求
request.onupgradeneeded = (event: any) => {
const db = event.target.result; // 获取数据库实例
stores.forEach((store) => {
if (!db.objectStoreNames.contains(store.name)) {
// 确保对象存储(表)创建
db.createObjectStore(store.name, {
keyPath: store.keyPath || 'id',
autoIncrement: true
});
}
});
};
request.onsuccess = (event: any) => {
db.value = event.target.result; // 保存数据库实例
resolve(); // 成功时解析 Promise
};
request.onerror = (event: any) => {
reject(event.target.error); // 失败时拒绝 Promise
};
});
};
// 通用新增数据
const addData = (storeName: string, value: any) => {
return new Promise<void>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化'); // 检查数据库是否已初始化
const tx = db.value.transaction(storeName, 'readwrite'); // 创建事务
const store = tx.objectStore(storeName); // 获取对象存储
const request = store.add(value); // 添加数据请求
request.onsuccess = () => {
console.log('成功'); // 成功时输出
resolve(); // 解析 Promise
};
request.onerror = (event) => {
console.error('新增失败:', (event.target as IDBRequest).error); // 输出错误
reject((event.target as IDBRequest).error); // 拒绝 Promise
};
});
};
// 通用保存数据(新增或更新)
const saveData = (storeName: string, value: any) => {
return new Promise<void>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化');
const tx = db.value.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
const request = store.put(value);
request.onsuccess = () => {
console.log('成功');
resolve();
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
};
// 通用获取数据
const getData = (storeName: string, key: string | number) => {
return new Promise<any>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化');
const tx = db.value.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const request = store.get(key);
request.onsuccess = (event) => {
if (event.target) {
resolve((event.target as IDBRequest).result);
} else {
reject('事件目标为空');
}
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
};
// 删除数据
const deleteData = (storeName: string, key: string | number) => {
return new Promise<void>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化');
const tx = db.value.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = () => {
console.log('删除成功');
resolve();
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
};
// 查询所有数据
const getAllData = (storeName: string) => {
return new Promise<any[]>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化');
const tx = db.value.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const request = store.getAll();
request.onsuccess = (event) => {
if (event.target) {
resolve((event.target as IDBRequest).result);
} else {
reject('事件目标为空');
}
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
};
// 分页查询数据
const getDataWithPagination = (storeName: string, page: number, pageSize: number) => {
return new Promise<any[]>((resolve, reject) => {
if (!db.value) return reject('数据库未初始化');
const tx = db.value.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const request = store.openCursor(); // 打开游标请求
const results: any[] = []; // 存储结果的数组
let index = 0; // 当前索引
const skip = (page - 1) * pageSize; // 计算跳过的数量
request.onsuccess = (event: any) => {
const cursor = event.target.result; // 获取游标
if (!cursor) {
resolve(results); // 如果没有更多数据,解析结果
return;
}
if (index >= skip && results.length < pageSize) {
results.push(cursor.value); // 添加当前游标值到结果
}
index++; // 增加索引
cursor.continue(); // 继续游标
};
request.onerror = (event: any) => {
reject(event.target.error);
};
});
};
return {
initDB,
addData,
saveData,
getData,
deleteData,
getAllData,
getDataWithPagination
};
};
export default useIndexedDB;
+39
View File
@@ -0,0 +1,39 @@
// musicHistoryHooks
import { useLocalStorage } from '@vueuse/core';
import type { SongResult } from '@/type/music';
export const useMusicHistory = () => {
const musicHistory = useLocalStorage<SongResult[]>('musicHistory', []);
const addMusic = (music: SongResult) => {
const index = musicHistory.value.findIndex((item) => item.id === music.id);
if (index !== -1) {
musicHistory.value[index].count = (musicHistory.value[index].count || 0) + 1;
musicHistory.value.unshift(musicHistory.value.splice(index, 1)[0]);
} else {
musicHistory.value.unshift({ ...music, count: 1 });
}
};
const delMusic = (music: SongResult) => {
const index = musicHistory.value.findIndex((item) => item.id === music.id);
if (index !== -1) {
musicHistory.value.splice(index, 1);
}
};
const musicList = ref(musicHistory.value);
watch(
() => musicHistory.value,
() => {
musicList.value = musicHistory.value;
}
);
return {
musicHistory,
musicList,
addMusic,
delMusic
};
};
+359
View File
@@ -0,0 +1,359 @@
import { computed, ref } from 'vue';
import { audioService } from '@/services/audioService';
import store from '@/store';
import type { ILyricText, SongResult } from '@/type/music';
import { getTextColors } from '@/utils/linearColor';
import { isElectron } from '@/utils';
const windowData = window as any;
export const lrcArray = ref<ILyricText[]>([]); // 歌词数组
export const lrcTimeArray = ref<number[]>([]); // 歌词时间数组
export const nowTime = ref(0); // 当前播放时间
export const allTime = ref(0); // 总播放时间
export const nowIndex = ref(0); // 当前播放歌词
export const correctionTime = ref(0.4); // 歌词矫正时间Correction time
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
export const sound = ref<Howl | null>(audioService.getCurrentSound());
export const isLyricWindowOpen = ref(false); // 新增状态
export const textColors = ref(getTextColors());
document.onkeyup = (e) => {
// 检查事件目标是否是输入框元素
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
return;
}
switch (e.code) {
case 'Space':
if (store.state.play) {
store.commit('setPlayMusic', false);
audioService.getCurrentSound()?.pause();
} else {
store.commit('setPlayMusic', true);
audioService.getCurrentSound()?.play();
}
break;
default:
}
};
watch(
() => store.state.playMusicUrl,
(newVal) => {
if (newVal) {
audioService.play(newVal);
sound.value = audioService.getCurrentSound();
audioServiceOn(audioService);
}
}
);
watch(
() => store.state.playMusic,
() => {
nextTick(async () => {
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
if (isElectron && isLyricWindowOpen.value && lrcArray.value.length > 0) {
sendLyricToWin();
}
});
},
{
deep: true,
immediate: true
}
);
export const audioServiceOn = (audio: typeof audioService) => {
let interval: any = null;
// 监听播放
audio.onPlay(() => {
store.commit('setPlayMusic', true);
interval = setInterval(() => {
nowTime.value = sound.value?.seek() as number;
allTime.value = sound.value?.duration() as number;
const newIndex = getLrcIndex(nowTime.value);
if (newIndex !== nowIndex.value) {
nowIndex.value = newIndex;
currentLrcProgress.value = 0;
// 当歌词索引更新时,发送歌词数据
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
// 定期发送歌词数据更新
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}, 50);
});
// 监听暂停
audio.onPause(() => {
store.commit('setPlayMusic', false);
clearInterval(interval);
// 暂停时也发送一次状态更新
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
});
// 监听结束
audio.onEnd(() => {
if (store.state.playMode === 1) {
// 单曲循环模式
audio.getCurrentSound()?.play();
} else if (store.state.playMode === 2) {
// 随机播放模式
const { playList } = store.state;
if (playList.length <= 1) {
// 如果播放列表只有一首歌或为空,则重新播放当前歌曲
audio.getCurrentSound()?.play();
} else {
// 随机选择一首不同的歌
let randomIndex;
do {
randomIndex = Math.floor(Math.random() * playList.length);
} while (randomIndex === store.state.playListIndex && playList.length > 1);
store.state.playListIndex = randomIndex;
store.commit('setPlay', playList[randomIndex]);
}
} else {
// 列表循环模式
store.commit('nextPlay');
}
});
};
export const play = () => {
audioService.getCurrentSound()?.play();
};
export const pause = () => {
audioService.getCurrentSound()?.pause();
};
const isPlaying = computed(() => store.state.play as boolean);
// 增加矫正时间
export const addCorrectionTime = (time: number) => (correctionTime.value += time);
// 减少矫正时间
export const reduceCorrectionTime = (time: number) => (correctionTime.value -= time);
// 获取当前播放歌词
export const isCurrentLrc = (index: number, time: number): boolean => {
const currentTime = lrcTimeArray.value[index];
const nextTime = lrcTimeArray.value[index + 1];
const nowTime = time + correctionTime.value;
const isTrue = nowTime > currentTime && nowTime < nextTime;
return isTrue;
};
// 获取当前播放歌词INDEX
export const getLrcIndex = (time: number): number => {
for (let i = 0; i < lrcTimeArray.value.length; i++) {
if (isCurrentLrc(i, time)) {
nowIndex.value = i;
return i;
}
}
return nowIndex.value;
};
// 获取当前播放歌词进度
const currentLrcTiming = computed(() => {
const start = lrcTimeArray.value[nowIndex.value] || 0;
const end = lrcTimeArray.value[nowIndex.value + 1] || start + 1;
return { start, end };
});
// 获取歌词样式
export const getLrcStyle = (index: number) => {
if (index === nowIndex.value) {
return {
backgroundImage: `linear-gradient(to right, #ffffff ${currentLrcProgress.value}%, #ffffff8a ${currentLrcProgress.value}%)`,
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
color: 'transparent',
transition: 'background-image 0.1s linear'
};
}
return {};
};
// 播放进度
export const useLyricProgress = () => {
let animationFrameId: number | null = null;
const updateProgress = () => {
if (!isPlaying.value) return;
const currentSound = sound.value;
if (!currentSound) return;
const { start, end } = currentLrcTiming.value;
const duration = end - start;
const elapsed = (currentSound.seek() as number) - start;
currentLrcProgress.value = Math.min(Math.max((elapsed / duration) * 100, 0), 100);
animationFrameId = requestAnimationFrame(updateProgress);
};
const startProgressAnimation = () => {
if (!animationFrameId && isPlaying.value) {
updateProgress();
}
};
const stopProgressAnimation = () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
};
watch(isPlaying, (newIsPlaying) => {
if (newIsPlaying) {
startProgressAnimation();
} else {
stopProgressAnimation();
}
});
onMounted(() => {
if (isPlaying.value) {
startProgressAnimation();
}
});
onUnmounted(() => {
stopProgressAnimation();
});
return {
currentLrcProgress,
getLrcStyle
};
};
// 设置当前播放时间
export const setAudioTime = (index: number) => {
const currentSound = sound.value;
if (!currentSound) return;
currentSound.seek(lrcTimeArray.value[index]);
currentSound.play();
};
// 获取当前播放的歌词
export const getCurrentLrc = () => {
const index = getLrcIndex(nowTime.value);
return {
currentLrc: lrcArray.value[index],
nextLrc: lrcArray.value[index + 1]
};
};
// 获取一句歌词播放时间几秒到几秒
export const getLrcTimeRange = (index: number) => ({
currentTime: lrcTimeArray.value[index],
nextTime: lrcTimeArray.value[index + 1]
});
// 监听歌词数组变化,当切换歌曲时重新初始化歌词窗口
watch(
() => lrcArray.value,
(newLrcArray) => {
if (newLrcArray.length > 0 && isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
);
// 发送歌词更新数据
export const sendLyricToWin = () => {
if (!isElectron || !isLyricWindowOpen.value) {
console.log('Cannot send lyric: electron or lyric window not available');
return;
}
try {
if (lrcArray.value.length > 0) {
const nowIndex = getLrcIndex(nowTime.value);
const updateData = {
type: 'full',
nowIndex,
nowTime: nowTime.value,
startCurrentTime: lrcTimeArray.value[nowIndex],
nextTime: lrcTimeArray.value[nowIndex + 1],
isPlay: isPlaying.value,
lrcArray: lrcArray.value,
lrcTimeArray: lrcTimeArray.value,
allTime: allTime.value,
playMusic: playMusic.value
};
window.api.sendLyric(JSON.stringify(updateData));
}
} catch (error) {
console.error('Error sending lyric update:', error);
}
};
export const openLyric = () => {
if (!isElectron) return;
console.log('Opening lyric window with current song:', playMusic.value?.name);
isLyricWindowOpen.value = !isLyricWindowOpen.value;
if (isLyricWindowOpen.value) {
setTimeout(() => {
window.api.openLyric();
sendLyricToWin();
}, 500);
sendLyricToWin();
} else {
closeLyric();
}
};
// 添加关闭歌词窗口的方法
export const closeLyric = () => {
if (!isElectron) return;
windowData.electron.ipcRenderer.send('close-lyric');
};
// 添加播放控制命令监听
if (isElectron) {
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
switch (command) {
case 'playpause':
if (store.state.play) {
store.commit('setPlayMusic', false);
audioService.getCurrentSound()?.pause();
} else {
store.commit('setPlayMusic', true);
audioService.getCurrentSound()?.play();
}
break;
case 'prev':
store.commit('prevPlay');
break;
case 'next':
store.commit('nextPlay');
break;
case 'close':
closeLyric();
break;
default:
console.log('Unknown command:', command);
break;
}
});
}
+195
View File
@@ -0,0 +1,195 @@
import { Howl } from 'howler';
import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music';
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
import { audioService } from '@/services/audioService';
import type { ILyric, ILyricText, SongResult } from '@/type/music';
import { getImgUrl, getMusicProxyUrl } from '@/utils';
import { getImageLinearBackground } from '@/utils/linearColor';
const musicHistory = useMusicHistory();
// 获取歌曲url
const getSongUrl = async (id: number) => {
const { data } = await getMusicUrl(id);
let url = '';
try {
if (data.data[0].freeTrialInfo || !data.data[0].url) {
const res = await getParsingMusicUrl(id);
console.log('res', res);
url = res.data.data.url;
}
} catch (error) {
console.error('error', error);
}
url = url || data.data[0].url;
return getMusicProxyUrl(url);
};
const getSongDetail = async (playMusic: SongResult) => {
playMusic.playLoading = true;
const playMusicUrl = await getSongUrl(playMusic.id);
const { backgroundColor, primaryColor } =
playMusic.backgroundColor && playMusic.primaryColor
? playMusic
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
playMusic.playLoading = false;
return { ...playMusic, playMusicUrl, backgroundColor, primaryColor };
};
// 加载 当前歌曲 歌曲列表数据 下一首mp3预加载 歌词数据
export const useMusicListHook = () => {
const handlePlayMusic = async (state: any, playMusic: SongResult) => {
const updatedPlayMusic = await getSongDetail(playMusic);
state.playMusic = updatedPlayMusic;
state.playMusicUrl = updatedPlayMusic.playMusicUrl;
state.play = true;
// 设置网页标题
document.title = `${updatedPlayMusic.name} - ${updatedPlayMusic?.song?.artists?.reduce((prev, curr) => `${prev}${curr.name}/`, '')}`;
loadLrcAsync(state, updatedPlayMusic.id);
musicHistory.addMusic(state.playMusic);
const playListIndex = state.playList.findIndex((item: SongResult) => item.id === playMusic.id);
state.playListIndex = playListIndex;
// 请求后续五首歌曲的详情
fetchSongs(state, playListIndex + 1, playListIndex + 6);
};
// 用于预加载下一首歌曲的 MP3 数据
const preloadNextSong = (nextSongUrl: string) => {
const sound = new Howl({
src: [nextSongUrl],
html5: true,
preload: true,
autoplay: false
});
return sound;
};
const fetchSongs = async (state: any, startIndex: number, endIndex: number) => {
const songs = state.playList.slice(
Math.max(0, startIndex),
Math.min(endIndex, state.playList.length)
);
const detailedSongs = await Promise.all(
songs.map(async (song: SongResult) => {
// 如果歌曲详情已经存在,就不重复请求
if (!song.playMusicUrl) {
return await getSongDetail(song);
}
return song;
})
);
// 加载下一首的歌词
const nextSong = detailedSongs[0];
if (!(nextSong.lyric && nextSong.lyric.lrcTimeArray.length > 0)) {
nextSong.lyric = await loadLrc(nextSong.id);
}
// 更新播放列表中的歌曲详情
detailedSongs.forEach((song, index) => {
state.playList[startIndex + index] = song;
});
preloadNextSong(nextSong.playMusicUrl);
};
const nextPlay = async (state: any) => {
if (state.playList.length === 0) {
state.play = true;
return;
}
const playListIndex = (state.playListIndex + 1) % state.playList.length;
await handlePlayMusic(state, state.playList[playListIndex]);
};
const prevPlay = async (state: any) => {
if (state.playList.length === 0) {
state.play = true;
return;
}
const playListIndex = (state.playListIndex - 1 + state.playList.length) % state.playList.length;
await handlePlayMusic(state, state.playList[playListIndex]);
await fetchSongs(state, playListIndex - 5, playListIndex);
};
const parseTime = (timeString: string): number => {
const [minutes, seconds] = timeString.split(':');
return Number(minutes) * 60 + Number(seconds);
};
const parseLyricLine = (lyricLine: string): { time: number; text: string } => {
const TIME_REGEX = /(\d{2}:\d{2}(\.\d*)?)/g;
const LRC_REGEX = /(\[(\d{2}):(\d{2})(\.(\d*))?\])/g;
const timeText = lyricLine.match(TIME_REGEX)?.[0] || '';
const time = parseTime(timeText);
const text = lyricLine.replace(LRC_REGEX, '').trim();
return { time, text };
};
const parseLyrics = (lyricsString: string): { lyrics: ILyricText[]; times: number[] } => {
const lines = lyricsString.split('\n');
const lyrics: ILyricText[] = [];
const times: number[] = [];
lines.forEach((line) => {
const { time, text } = parseLyricLine(line);
times.push(time);
lyrics.push({ text, trText: '' });
});
return { lyrics, times };
};
const loadLrc = async (playMusicId: number): Promise<ILyric> => {
try {
const { data } = await getMusicLrc(playMusicId);
const { lyrics, times } = parseLyrics(data.lrc.lyric);
const tlyric: Record<string, string> = {};
if (data.tlyric.lyric) {
const { lyrics: tLyrics, times: tTimes } = parseLyrics(data.tlyric.lyric);
tLyrics.forEach((lyric, index) => {
tlyric[tTimes[index].toString()] = lyric.text;
});
}
lyrics.forEach((item, index) => {
item.trText = item.text ? tlyric[times[index].toString()] || '' : '';
});
return {
lrcTimeArray: times,
lrcArray: lyrics
};
} catch (err) {
console.error('Error loading lyrics:', err);
return {
lrcTimeArray: [],
lrcArray: []
};
}
};
// 异步加载歌词的方法
const loadLrcAsync = async (state: any, playMusicId: number) => {
if (state.playMusic.lyric && state.playMusic.lyric.lrcTimeArray.length > 0) {
return;
}
const lyrics = await loadLrc(playMusicId);
state.playMusic.lyric = lyrics;
};
const play = () => {
audioService.getCurrentSound()?.play();
};
const pause = () => {
audioService.getCurrentSound()?.pause();
};
return {
handlePlayMusic,
nextPlay,
prevPlay,
play,
pause
};
};