feat: 完善剩余功能
This commit is contained in:
@@ -9,7 +9,6 @@ export const config = {
|
||||
|
||||
// 歌词配置
|
||||
lyrics: {
|
||||
cacheDir: "./lyrics",
|
||||
apiUrl: "http://123.57.93.143:28883",
|
||||
apiKey: "fzt_tom",
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { MusicStatus } from "./apple_music.ts";
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
|
||||
export interface LyricsData {
|
||||
current_lyric_time: number | null;
|
||||
@@ -37,14 +38,23 @@ let lyricsCache: LyricsCache = {
|
||||
};
|
||||
|
||||
const DB_PATH = "./lyrics.db";
|
||||
const db = new DatabaseSync(DB_PATH);
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*/
|
||||
export async function initDB(): Promise<void> {
|
||||
try {
|
||||
// 确保歌词目录存在
|
||||
await Deno.mkdir("./lyrics", { recursive: true });
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS lyrics (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
track_name TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
lyrics_text TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT 0
|
||||
)
|
||||
`);
|
||||
console.log(`数据库初始化完成: ${DB_PATH}`);
|
||||
} catch (error) {
|
||||
console.error(`数据库初始化失败: ${error}`);
|
||||
@@ -72,11 +82,8 @@ async function loadLyricsFromDB(
|
||||
artist: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const filePath = `./lyrics/${encodeURIComponent(
|
||||
trackName
|
||||
)}_${encodeURIComponent(artist)}.lrc`;
|
||||
const content = await Deno.readTextFile(filePath);
|
||||
return content;
|
||||
const content = db.prepare(`SELECT lyrics_text FROM lyrics WHERE track_name = ? AND artist = ?`).get(trackName, artist);
|
||||
return content.lyrics_text;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
@@ -85,25 +92,29 @@ async function loadLyricsFromDB(
|
||||
/**
|
||||
* 保存歌词到数据库
|
||||
*/
|
||||
async function saveLyrics(
|
||||
export async function saveLyrics(
|
||||
trackName: string,
|
||||
artist: string,
|
||||
lyricsText: string
|
||||
lyricsText: string,
|
||||
source: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const filePath = `./lyrics/${encodeURIComponent(
|
||||
trackName
|
||||
)}_${encodeURIComponent(artist)}.lrc`;
|
||||
await Deno.writeTextFile(filePath, lyricsText);
|
||||
db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
|
||||
db.prepare(`INSERT INTO lyrics (track_name, artist, lyrics_text, source) VALUES (?, ?, ?, ?)`).run(trackName, artist, lyricsText, source);
|
||||
lyricsCache.track_name = trackName;
|
||||
lyricsCache.artist = artist;
|
||||
lyricsCache.lyrics_text = lyricsText;
|
||||
lyricsCache.source = source;
|
||||
console.log(`保存歌词成功: ${trackName} - ${artist}`);
|
||||
} catch (error) {
|
||||
console.error(`保存歌词失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网易云音乐 API 搜索歌词
|
||||
* 从服务器 API 搜索歌词
|
||||
*/
|
||||
async function searchLyrics(
|
||||
export async function searchLyrics(
|
||||
trackName: string,
|
||||
artistName: string
|
||||
): Promise<string | null> {
|
||||
@@ -234,6 +245,18 @@ function formatLrcLyrics(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除歌词
|
||||
*/
|
||||
export async function deleteLyrics(trackName: string, artist: string): Promise<void> {
|
||||
try {
|
||||
db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
|
||||
console.log(`删除歌词成功: ${trackName} - ${artist}`);
|
||||
} catch (error) {
|
||||
console.error(`删除歌词失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前歌词数据
|
||||
*/
|
||||
@@ -276,9 +299,9 @@ export async function getLyricsData(status: MusicStatus): Promise<LyricsData> {
|
||||
const lyricsText = await searchLyrics(trackName || "", artist || "");
|
||||
if (lyricsText) {
|
||||
// 保存到数据库
|
||||
await saveLyrics(trackName || "", artist || "", lyricsText);
|
||||
await saveLyrics(trackName || "", artist || "", lyricsText, "server");
|
||||
lyricsCache.lyrics_text = lyricsText;
|
||||
lyricsCache.source = "netease";
|
||||
lyricsCache.source = "server";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "cors";
|
||||
import { logger } from "logger";
|
||||
import { ConnectionManager } from "./websocket_manager.ts";
|
||||
import { getMusicStatus, controlMusic } from "./apple_music.ts";
|
||||
import { getLyricsData } from "./lyrics.ts";
|
||||
import { getLyricsData, searchLyrics, saveLyrics, deleteLyrics } from "./lyrics.ts";
|
||||
|
||||
export interface ControlRequest {
|
||||
action: "playpause" | "previous" | "next" | "seek";
|
||||
@@ -16,7 +15,7 @@ export interface ControlRequest {
|
||||
}
|
||||
|
||||
export interface SearchLyricsRequest {
|
||||
title: string;
|
||||
track_name: string;
|
||||
artist: string;
|
||||
}
|
||||
|
||||
@@ -25,6 +24,17 @@ export interface GetLyricsFromIdRequest {
|
||||
artist: string;
|
||||
}
|
||||
|
||||
export interface SaveLyricsRequest {
|
||||
track_name: string;
|
||||
artist: string;
|
||||
lyrics: string;
|
||||
}
|
||||
|
||||
export interface DeleteLyricsRequest {
|
||||
track_name: string;
|
||||
artist: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册所有路由
|
||||
*/
|
||||
@@ -89,27 +99,50 @@ export function registerRoutes(app: Hono, manager: ConnectionManager): void {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/lyrics", async (c) => {
|
||||
try {
|
||||
const body = (await c.req.json()) as DeleteLyricsRequest;
|
||||
const { track_name, artist } = body;
|
||||
await deleteLyrics(track_name, artist);
|
||||
return c.json({ status: "success", message: "歌词删除成功" });
|
||||
} catch (error) {
|
||||
return c.json({ status: "error", error: String(error) }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索歌词
|
||||
app.post("/lyrics/search", async (c) => {
|
||||
try {
|
||||
const body = (await c.req.json()) as SearchLyricsRequest;
|
||||
|
||||
if (!body.title) {
|
||||
return c.json({ status: "error", message: "缺少 title 参数" }, 400);
|
||||
}
|
||||
|
||||
// 这里可以实现歌词搜索逻辑
|
||||
// 暂时返回空结果
|
||||
const { track_name, artist } = body;
|
||||
const lyricsData = await searchLyrics(track_name, artist);
|
||||
return c.json({
|
||||
status: "success",
|
||||
lyrics: null,
|
||||
message: "歌词搜索功能待实现",
|
||||
songs: [{
|
||||
name: track_name,
|
||||
artist: artist,
|
||||
lyrics: lyricsData,
|
||||
}],
|
||||
message: "歌词搜索成功",
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json({ status: "error", error: String(error) }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 保存歌词
|
||||
app.post("/lyrics/save", async (c) => {
|
||||
try {
|
||||
const body = (await c.req.json()) as SaveLyricsRequest;
|
||||
const { track_name, artist, lyrics } = body;
|
||||
await saveLyrics(track_name, artist, lyrics, "search");
|
||||
return c.json({ status: "success", message: "歌词保存成功" });
|
||||
} catch (error) {
|
||||
return c.json({ status: "error", error: String(error) }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 根据歌曲信息获取歌词
|
||||
app.post("/lyrics/get", async (c) => {
|
||||
try {
|
||||
|
||||
3
electron-app/.gitignore
vendored
3
electron-app/.gitignore
vendored
@@ -102,3 +102,6 @@ ENV/
|
||||
|
||||
# Local dev configurations
|
||||
config.local.js
|
||||
|
||||
# SQLite database
|
||||
lyrics.db
|
||||
|
||||
@@ -199,12 +199,6 @@ app.on("activate", function () {
|
||||
}
|
||||
});
|
||||
|
||||
// 应用退出前
|
||||
app.on("before-quit", () => {
|
||||
// 清理Python进程
|
||||
backendService.stopPythonBackend();
|
||||
});
|
||||
|
||||
// 监听第二实例启动事件
|
||||
app.on("second-instance", (event, commandLine, workingDirectory) => {
|
||||
// 当尝试启动第二个实例时,让主窗口获得焦点
|
||||
|
||||
@@ -267,7 +267,7 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
|
||||
* @returns {Array} 菜单模板数组
|
||||
*/
|
||||
function getMenuTemplate() {
|
||||
return [
|
||||
const res = [
|
||||
{
|
||||
label: i18next.t("menu.lockWindow"),
|
||||
click: () => {
|
||||
@@ -286,12 +286,6 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
|
||||
mainWindow.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18next.t("menu.openDevTools"),
|
||||
click: () => {
|
||||
mainWindow.webContents.openDevTools();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18next.t("menu.language"),
|
||||
submenu: [
|
||||
@@ -339,6 +333,16 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
|
||||
},
|
||||
},
|
||||
];
|
||||
if (isDev) {
|
||||
res.push({
|
||||
label: i18next.t("menu.openDevTools"),
|
||||
click: () => {
|
||||
mainWindow.webContents.openDevTools();
|
||||
searchWindow && searchWindow.webContents.openDevTools();
|
||||
},
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,19 +25,13 @@ async function sendControlCommand(action, position = null) {
|
||||
data: payload
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`控制请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 检查结果状态,如果Apple Music未运行,提供明确的错误信息
|
||||
if (result.status === 'error' && result.message === 'notrunning') {
|
||||
if (response.status === 'error' && response.message === 'notrunning') {
|
||||
console.warn('Apple Music未运行,无法执行控制命令');
|
||||
throw new Error('Apple Music未运行,请先启动Apple Music应用');
|
||||
}
|
||||
|
||||
return result;
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`${action}控制请求错误:`, error);
|
||||
throw error;
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
>
|
||||
<div class="result-info">
|
||||
<div class="track-name">{{ result.name }}</div>
|
||||
<div class="artist-name">{{ getArtistName(result.artists) }}</div>
|
||||
<div class="artist-name">{{ result.artist }}</div>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<button
|
||||
@@ -88,10 +88,6 @@ const hasSearched = ref(false);
|
||||
const selectedIndex = ref(-1);
|
||||
const searchResults = ref([]);
|
||||
|
||||
const getArtistName = (artists) => {
|
||||
return artists.map(artist => artist.name).join(', ');
|
||||
};
|
||||
|
||||
import request from '../utils/request';
|
||||
import { useLyricsStore } from '../store';
|
||||
const lyricsStore = useLyricsStore();
|
||||
@@ -139,12 +135,12 @@ const returnToSearch = () => {
|
||||
|
||||
const selectResult = (index) => {
|
||||
request({
|
||||
url: '/lyrics/getLyricsFromId',
|
||||
url: '/lyrics/save',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: searchResults.value[index].id,
|
||||
track_name: lyricsStore.trackName,
|
||||
artist: lyricsStore.artist
|
||||
artist: lyricsStore.artist,
|
||||
lyrics: searchResults.value[index].lyrics
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -312,7 +308,7 @@ h2 {
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.results-list {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user