feat: QZ Music for Windows(个人学习项目);Vue主界面+设置界面;Router路由;Pinia全局状态管理;封面取色;IPC通信示例

This commit is contained in:
lqtmcstudio
2026-01-02 14:14:59 +08:00
commit 1a397a7fb3
26 changed files with 9385 additions and 0 deletions

168
src/stores/playerStore.ts Normal file
View File

@@ -0,0 +1,168 @@
import { defineStore } from 'pinia';
import { ref, computed, watch } from 'vue';
export const usePlayerStore = defineStore('player', () => {
const currentSong = ref({
id: 1,
title: "Example Track",
artist: "AI Artist",
cover: "http://p2.music.126.net/W3VMsSEjTdvhz7h3a0oxTg==/17782401556325576.jpg?param=130y130",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
duration: 0
});
const isPlaying = ref(false);
const currentTime = ref(0);
const volume = ref(80);
const showPlaylist = ref(false);
const showSettings = ref(false);
const darkMode = ref(false);
const themeColors = ref({ primary: '#6366f1', secondary: '#a855f7' });
// 新增:用于顶部进度条的最深色
const progressColor = ref('#ffffff');
const audio = new Audio(currentSong.value.url);
audio.volume = volume.value / 100;
// 颜色提取函数(轻量版,不依赖外部库)
function extractColors() {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.src = currentSong.value.cover + '?t=' + Date.now(); // 避免缓存
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 缩小图片以提高性能
const scale = 0.1;
canvas.width = Math.floor(img.width * scale);
canvas.height = Math.floor(img.height * scale);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
// 收集所有像素颜色
const colors: { r: number; g: number; b: number; brightness: number }[] = [];
for (let i = 0; i < imageData.length; i += 4) {
const r = imageData[i];
const g = imageData[i + 1];
const b = imageData[i + 2];
const brightness = (r + g + b) / 3;
colors.push({ r, g, b, brightness });
}
// 计算颜色频率(简单实现)
const colorFrequency: Record<string, number> = {};
colors.forEach(color => {
// 将颜色量化为 16 位色,减少颜色数量
const key = `${Math.floor(color.r / 16)}${Math.floor(color.g / 16)}${Math.floor(color.b / 16)}`;
colorFrequency[key] = (colorFrequency[key] || 0) + 1;
});
// 转换回 RGB 并按频率排序
const sortedColors = Object.entries(colorFrequency)
.map(([key, count]) => {
const r = parseInt(key[0], 16) * 16;
const g = parseInt(key[1], 16) * 16;
const b = parseInt(key[2], 16) * 16;
return { r, g, b, count };
})
.sort((a, b) => b.count - a.count);
// 提取主色调和辅助色调
if (sortedColors.length > 0) {
// 主色调:频率最高的颜色
const primary = sortedColors[0];
const primaryColor = `rgb(${primary.r}, ${primary.g}, ${primary.b})`;
// 辅助色调:选择与主色调亮度差异较大的颜色
let secondary = sortedColors[1] || sortedColors[0];
let maxBrightnessDiff = Math.abs(primary.brightness - secondary.brightness);
// 在频率较高的颜色中寻找最合适的辅助色
for (let i = 1; i < Math.min(10, sortedColors.length); i++) {
const currentBrightness = (sortedColors[i].r + sortedColors[i].g + sortedColors[i].b) / 3;
const brightnessDiff = Math.abs(primary.brightness - currentBrightness);
if (brightnessDiff > maxBrightnessDiff) {
maxBrightnessDiff = brightnessDiff;
secondary = sortedColors[i];
}
}
const secondaryColor = `rgb(${secondary.r}, ${secondary.g}, ${secondary.b})`;
// 更新主题颜色
themeColors.value = {
primary: primaryColor,
secondary: secondaryColor
};
// 提取进度条颜色(使用主色调的深色版本)
const darkPrimary = {
r: Math.floor(primary.r * 0.7),
g: Math.floor(primary.g * 0.7),
b: Math.floor(primary.b * 0.7)
};
progressColor.value = `rgb(${darkPrimary.r}, ${darkPrimary.g}, ${darkPrimary.b})`;
} else {
// 默认颜色
themeColors.value = { primary: '#6366f1', secondary: '#a855f7' };
progressColor.value = '#ffffff';
}
};
}
// 监听歌曲切换时重新提取颜色
watch(() => currentSong.value.cover, () => {
extractColors();
console.log("!!")
}, { immediate: true });
audio.addEventListener('loadedmetadata', () => {
currentSong.value.duration = audio.duration;
});
audio.addEventListener('timeupdate', () => {
currentTime.value = audio.currentTime;
});
audio.addEventListener('ended', () => {
isPlaying.value = false;
currentTime.value = 0;
});
function togglePlay() {
if (isPlaying.value) {
audio.pause();
} else {
audio.play().catch(e => console.warn("播放失败,需用户交互:", e));
}
isPlaying.value = !isPlaying.value;
}
function seek(time: number) {
audio.currentTime = time;
}
function toggleSettings() { console.log("!!");showSettings.value = !showSettings.value; }
watch(volume, (newVol) => {
audio.volume = newVol / 100;
});
const progressPercentage = computed(() =>
currentSong.value.duration ? (currentTime.value / currentSong.value.duration) * 100 : 0
);
return {
currentSong, isPlaying, currentTime, volume, showPlaylist,
themeColors, progressPercentage, progressColor,
togglePlay, seek, extractColors,
showSettings, toggleSettings,
darkMode
};
});