彻底抽离music组件和路由

This commit is contained in:
mtvpls
2026-05-27 00:04:58 +08:00
parent e60f8cc46f
commit 4992b9fffb
13 changed files with 245 additions and 934 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import type { Song } from '@/app/music/MusicClient';
import type { Song } from '@/lib/music/types';
export function playMusicSong(song: Song, index = -1) {
window.dispatchEvent(new CustomEvent('music:play-song', { detail: { song, index } }));
+8
View File
@@ -0,0 +1,8 @@
export function getApiErrorMessage(error: unknown, fallback: string): string {
if (typeof error === 'string') return error;
if (error && typeof error === 'object' && 'message' in error) {
const message = (error as { message?: unknown }).message;
if (typeof message === 'string' && message.trim()) return message;
}
return fallback;
}
+39 -7
View File
@@ -1,5 +1,5 @@
import React from 'react';
import type { MusicSource, Song } from '@/app/music/MusicClient';
import type { MusicSource, Song } from '@/lib/music/types';
export const musicSources: Array<{ key: MusicSource; label: string }> = [
{ key: 'wy', label: '网易云' },
@@ -10,12 +10,15 @@ export const musicSources: Array<{ key: MusicSource; label: string }> = [
];
export function normalizeSource(source: string | undefined | null): MusicSource {
if (source === 'netease') return 'wy';
if (source === 'qq') return 'tx';
if (source === 'kuwo') return 'kw';
if (source === 'wy' || source === 'tx' || source === 'kw' || source === 'kg' || source === 'mg') return source;
return 'wy';
}
export function mapSong(song: any): Song {
const rawSource = song.platform || song.source || song.vendor || song.origin;
const rawSource = song.source || song.platform || song.vendor || song.origin;
return {
id: String(song.id ?? song.songId ?? song.rid ?? song.mid ?? ''),
name: song.name || song.title || '未知歌曲',
@@ -23,19 +26,48 @@ export function mapSong(song: any): Song {
album: song.album || song.albumName,
pic: song.pic || song.cover || song.img,
platform: normalizeSource(rawSource),
duration: song.duration,
durationText: song.durationText,
duration: song.durationSec || song.duration,
durationText: song.durationText || song.interval,
songmid: song.songmid || song.mid,
};
}
export function SourcePill({ source, className = '' }: { source?: MusicSource | string; className?: string }) {
export function getSourceDisplayLabel(source?: MusicSource | string, compact = true): string {
switch (normalizeSource(source)) {
case 'wy':
return compact ? '网易' : '网易云';
case 'tx':
return compact ? 'QQ' : 'QQ音乐';
case 'kw':
return '酷我';
case 'kg':
return '酷狗';
case 'mg':
return '咪咕';
}
}
export function SourcePill({
source,
className = '',
variant = 'subtle',
}: {
source?: MusicSource | string;
className?: string;
variant?: 'subtle' | 'accent';
}) {
const normalized = normalizeSource(source);
const label = musicSources.find((item) => item.key === normalized)?.label || source || '未知';
const label = variant === 'accent'
? getSourceDisplayLabel(normalized)
: musicSources.find((item) => item.key === normalized)?.label || source || '未知';
const variantClass = variant === 'accent'
? 'rounded-lg border-red-500/50 bg-red-500/20 leading-none text-red-400'
: 'rounded-full border-white/10 bg-white/5 text-zinc-400';
return React.createElement(
'span',
{
className: `inline-flex shrink-0 items-center rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-[10px] font-medium text-zinc-400 ${className}`,
className: `inline-flex shrink-0 items-center border px-2 py-0.5 text-[10px] font-medium ${variantClass} ${className}`,
},
label
);
+23
View File
@@ -0,0 +1,23 @@
export type MusicSource = 'wy' | 'tx' | 'kw' | 'kg' | 'mg';
export type MusicQuality = '128k' | '320k' | 'flac' | 'flac24bit';
export interface Song {
id: string;
name: string;
artist: string;
album?: string;
pic?: string;
platform: MusicSource;
duration?: number;
durationText?: string;
songmid?: string;
}
export interface Playlist {
id: string;
name: string;
pic?: string;
source?: MusicSource;
updateFrequency?: string;
}