feat: 迁移架构&实现功能

- 持久化音量设置
- 搜索页支持自定义每页显示数量(持久化)
- vite-electron-plugin迁移electron-vite
This commit is contained in:
lqtmcstudio
2026-02-06 14:57:32 +08:00
parent d6e1d11a52
commit 5a3ab02ee1
8 changed files with 60 additions and 17 deletions

44
src/preload/index.ts Normal file
View File

@@ -0,0 +1,44 @@
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('electronAPI', {
// 窗口控制
minimizeWindow: () => ipcRenderer.send('window-minimize'),
maximizeWindow: () => ipcRenderer.send('window-maximize'),
closeWindow: () => ipcRenderer.send('window-close'),
isMaximized: () => ipcRenderer.invoke('window-is-maximized'),
// qzplayer Control
qzplayer: {
load: (url: string) => ipcRenderer.invoke('qzplayer-load', url),
play: () => ipcRenderer.invoke('qzplayer-play'),
pause: () => ipcRenderer.invoke('qzplayer-pause'),
togglePause: () => ipcRenderer.invoke('qzplayer-toggle-pause'),
stop: () => ipcRenderer.invoke('qzplayer-stop'),
setVolume: (vol: number) => ipcRenderer.invoke('qzplayer-set-volume', vol),
seek: (time: number) => ipcRenderer.invoke('qzplayer-seek', time),
onEvent: (callback: (event: any, data: any) => void) => ipcRenderer.on('qzplayer-event', callback)
},
// Plugin System
plugin: {
call: (pluginId: string, method: string, args: any[]) => ipcRenderer.invoke('plugin:call', pluginId, method, args),
search: (pluginId: string, query: string, page: number, limit: number) => ipcRenderer.invoke('plugin:call', pluginId, 'search', [query, page, limit]),
getLyric: (pluginId: string, id: string) => ipcRenderer.invoke('plugin:call', pluginId, 'getLyric', [id]),
},
// Cache Control
getCacheInfo: () => ipcRenderer.invoke('cache:getInfo'),
setCachePersist: (persist: boolean) => ipcRenderer.invoke('cache:setPersist', persist),
openCacheFolder: () => ipcRenderer.invoke('cache:openFolder'),
clearCache: () => ipcRenderer.invoke('cache:clear'),
// Settings
settings: {
getAll: () => ipcRenderer.invoke('settings:getAll'),
set: (settings: any) => ipcRenderer.invoke('settings:set', settings),
getTheme: () => ipcRenderer.invoke('settings:getTheme'),
setTheme: (theme: 'dark' | 'light') => ipcRenderer.invoke('settings:setTheme', theme),
getAccentColor: () => ipcRenderer.invoke('settings:getAccentColor'),
setAccentColor: (color: string) => ipcRenderer.invoke('settings:setAccentColor', color)
}
})

View File

@@ -190,6 +190,7 @@ const formatTime = (seconds2: number) => {
<style scoped> <style scoped>
.player-bar { .player-bar {
box-sizing: border-box;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;

View File

@@ -93,6 +93,7 @@ const togglePlaylists = () => {
<style scoped> <style scoped>
.sidebar { .sidebar {
box-sizing: border-box;
width: var(--sidebar-width); width: var(--sidebar-width);
height: 100vh; height: 100vh;
background-color: var(--color-bg-secondary); background-color: var(--color-bg-secondary);

View File

@@ -20,7 +20,7 @@ import { computed } from 'vue';
import Sidebar from '../components/Sidebar.vue'; import Sidebar from '../components/Sidebar.vue';
import TopBar from '../components/TopBar.vue'; import TopBar from '../components/TopBar.vue';
import PlayerBar from '../components/PlayerBar.vue'; import PlayerBar from '../components/PlayerBar.vue';
import { usePlayerStore } from '../stores/player.ts'; import { usePlayerStore } from '../stores/player';
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
const hasSongs = computed(() => playerStore.playlist.length > 0); const hasSongs = computed(() => playerStore.playlist.length > 0);
@@ -29,7 +29,6 @@ const hasSongs = computed(() => playerStore.playlist.length > 0);
<style> <style>
/* 这确保 width: 100% + padding 不会撑破容器 */ /* 这确保 width: 100% + padding 不会撑破容器 */
*, *::before, *::after { *, *::before, *::after {
box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }

View File

@@ -2,7 +2,6 @@ import { defineStore } from 'pinia';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import type { Song } from '../types/song'; import type { Song } from '../types/song';
import { parseLyric } from '../utils/lyricParser';
export enum PlayMode { export enum PlayMode {
List = 'list', List = 'list',
@@ -147,13 +146,7 @@ export const usePlayerStore = defineStore('player', () => {
// Check if plugin API exists // Check if plugin API exists
if (window.electronAPI?.plugin?.getLyric) { if (window.electronAPI?.plugin?.getLyric) {
const rawLyric = await window.electronAPI.plugin.getLyric(song.source || 'kw', song.id.toString()); const rawLyric = await window.electronAPI.plugin.getLyric(song.source || 'kw', song.id.toString());
if (rawLyric) { console.log(rawLyric)
const parsedLines = parseLyric(rawLyric);
if (parsedLines) {
lyrics.value = { lines: parsedLines };
}
console.log(lyrics)
}
} else { } else {
MessagePlugin.warning("当前插件不支持歌词获取").then() MessagePlugin.warning("当前插件不支持歌词获取").then()
} }

View File

@@ -101,6 +101,7 @@ const currentDate = computed(() => {
} }
.content-wrapper { .content-wrapper {
box-sizing: border-box;
padding: 24px; padding: 24px;
max-width: 1400px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
@@ -116,6 +117,7 @@ const currentDate = computed(() => {
border-radius: var(--radius-2xl); border-radius: var(--radius-2xl);
-electron-corner-smoothing: 65%; -electron-corner-smoothing: 65%;
padding: 40px; padding: 40px;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 32px; gap: 32px;
@@ -362,6 +364,7 @@ const currentDate = computed(() => {
} }
.song-item { .song-item {
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 16px;

View File

@@ -74,10 +74,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, onMounted } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { usePlayerStore } from '../stores/player.ts'; import { usePlayerStore } from '../stores/player';
const route = useRoute(); const route = useRoute();
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
@@ -140,6 +140,7 @@ const handlePlaySong = (index: number) => {
} }
.content-wrapper { .content-wrapper {
box-sizing: border-box;
padding: 30px; padding: 30px;
max-width: 1400px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
@@ -155,6 +156,7 @@ const handlePlaySong = (index: number) => {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 40px; padding: 0 40px;
box-sizing: border-box;
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
} }
@@ -360,8 +362,7 @@ const handlePlaySong = (index: number) => {
} }
/* List Styles */ /* List Styles */
.song-list-container {
}
.list-header { .list-header {
display: flex; display: flex;
@@ -376,10 +377,10 @@ const handlePlaySong = (index: number) => {
.col-album { width: 200px; } .col-album { width: 200px; }
.col-time { width: 60px; text-align: right; } .col-time { width: 60px; text-align: right; }
.song-list {
}
.song-item { .song-item {
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px 16px; padding: 10px 16px;

View File

@@ -227,6 +227,7 @@ watch(limit, (newLimit) => {
} }
.content-wrapper { .content-wrapper {
box-sizing: border-box;
padding: 20px 30px; /* Reduced vertical padding, kept horizontal for spacing but flexible */ padding: 20px 30px; /* Reduced vertical padding, kept horizontal for spacing but flexible */
width: 100%; width: 100%;
/* Removed max-width to allow full width usage as requested */ /* Removed max-width to allow full width usage as requested */
@@ -403,12 +404,12 @@ watch(limit, (newLimit) => {
.col-time { text-align: right; } .col-time { text-align: right; }
.song-item { .song-item {
box-sizing: border-box;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
transition: background-color 0.2s; transition: background-color 0.2s;
cursor: pointer; cursor: pointer;
color: var(--color-text-secondary); color: var(--color-text-secondary);
width: 100%; /* Fill width */ width: 100%; /* Fill width */
box-sizing: border-box; /* Include padding in width */
} }
.song-item:hover { .song-item:hover {