submit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// 管理员API
|
||||
const ADMIN_BASE_URL = ''
|
||||
|
||||
function getAdminToken() {
|
||||
return localStorage.getItem('admin_token') || ''
|
||||
}
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const token = getAdminToken()
|
||||
|
||||
const response = await fetch(`${ADMIN_BASE_URL}${url}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||
...(options.headers || {}),
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// token无效,清除并跳转登录
|
||||
localStorage.removeItem('admin_token')
|
||||
if (window.location.pathname !== '/admin/login') {
|
||||
window.location.href = '/admin/login'
|
||||
}
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
export async function adminLogin(password) {
|
||||
return await request('/api/admin/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ password }),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放
|
||||
*/
|
||||
export async function adminPlay() {
|
||||
return await request('/api/admin/playback/play', {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
export async function adminPause() {
|
||||
return await request('/api/admin/playback/pause', {
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送公告
|
||||
*/
|
||||
export async function adminSendAnnouncement(announcement) {
|
||||
return await request('/api/admin/announcement', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(announcement),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置特定用户的音量
|
||||
*/
|
||||
export async function adminSetUserVolume(userId, volume, muted) {
|
||||
// 确保userId是字符串
|
||||
const userIdStr = String(userId)
|
||||
console.log('调用adminSetUserVolume:', userIdStr, volume, muted)
|
||||
return await request(`/api/admin/user/${encodeURIComponent(userIdStr)}/volume`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ volume, muted }),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置特定用户的静音状态
|
||||
*/
|
||||
export async function adminSetUserMute(userId, muted) {
|
||||
// 确保userId是字符串
|
||||
const userIdStr = String(userId)
|
||||
console.log('调用adminSetUserMute:', userIdStr, muted)
|
||||
return await request(`/api/admin/user/${encodeURIComponent(userIdStr)}/mute`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ muted }),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放状态
|
||||
*/
|
||||
export async function adminGetPlaybackState() {
|
||||
return await request('/api/admin/playback/state', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有在线用户
|
||||
*/
|
||||
export async function adminGetAllUsers() {
|
||||
return await request('/api/admin/users', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整播放进度
|
||||
*/
|
||||
export async function adminSeek(time) {
|
||||
return await request('/api/admin/playback/seek', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ time }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// Midway后端API
|
||||
// 使用相对路径,通过Vite代理转发到后端
|
||||
const BASE_URL = ''
|
||||
|
||||
async function request(path, options = {}) {
|
||||
// 使用相对路径,Vite会通过代理转发到后端
|
||||
const url = path.startsWith('/') ? path : `/${path}`
|
||||
const { method = 'GET', body, params = {} } = options
|
||||
|
||||
// 构建URL,添加查询参数
|
||||
let requestUrl = url
|
||||
if (method === 'GET' && params && Object.keys(params).length > 0) {
|
||||
const searchParams = new URLSearchParams()
|
||||
Object.entries(params).forEach(([k, v]) => {
|
||||
if (v !== undefined && v !== null && v !== '') {
|
||||
searchParams.append(k, v)
|
||||
}
|
||||
})
|
||||
const queryString = searchParams.toString()
|
||||
if (queryString) {
|
||||
requestUrl += `?${queryString}`
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
|
||||
if (body && method !== 'GET') {
|
||||
config.body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
const res = await fetch(requestUrl, config)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.message || '请求失败')
|
||||
}
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索歌曲
|
||||
*/
|
||||
export async function searchSongs(keywords, limit = 20) {
|
||||
return await request('/api/music/search', {
|
||||
method: 'GET',
|
||||
params: { keywords, limit }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌曲详情
|
||||
*/
|
||||
export async function getSongDetail(id) {
|
||||
return await request(`/api/music/song/${id}/detail`, {
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌曲播放URL
|
||||
*/
|
||||
export async function getSongUrlV1(id, level = 'standard') {
|
||||
const data = await request(`/api/music/song/${id}/url`, {
|
||||
method: 'GET',
|
||||
params: { level }
|
||||
})
|
||||
return data.url || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放队列
|
||||
*/
|
||||
export async function getQueue() {
|
||||
return await request('/api/music/queue', {
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到播放队列
|
||||
*/
|
||||
export async function addToQueue(song) {
|
||||
const result = await request('/api/music/queue/add', {
|
||||
method: 'POST',
|
||||
body: { song }
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放下一首
|
||||
*/
|
||||
export async function playNext() {
|
||||
const result = await request('/api/music/queue/play-next', {
|
||||
method: 'POST'
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列
|
||||
*/
|
||||
export async function clearQueue() {
|
||||
return await request('/api/music/queue/clear', {
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从队列移除歌曲
|
||||
*/
|
||||
export async function removeFromQueue(songId) {
|
||||
return await request('/api/music/queue/remove', {
|
||||
method: 'POST',
|
||||
body: { songId }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌词
|
||||
*/
|
||||
export async function getLyric(id) {
|
||||
const data = await request(`/api/music/song/${id}/lyric`, {
|
||||
method: 'GET'
|
||||
})
|
||||
return data || { raw: '', tlyric: '' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前播放歌曲
|
||||
*/
|
||||
export async function setPlaybackSong(songId) {
|
||||
return await request('/api/music/playback/set-song', {
|
||||
method: 'POST',
|
||||
body: { songId }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放控制 - 播放
|
||||
*/
|
||||
export async function play() {
|
||||
return await request('/api/music/playback/play', {
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放控制 - 暂停
|
||||
*/
|
||||
export async function pause() {
|
||||
return await request('/api/music/playback/pause', {
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放控制 - 跳转时间
|
||||
*/
|
||||
export async function seek(time) {
|
||||
return await request('/api/music/playback/seek', {
|
||||
method: 'POST',
|
||||
body: { time }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放状态
|
||||
*/
|
||||
export async function getPlaybackState() {
|
||||
return await request('/api/music/playback/state', {
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getBaseUrl() {
|
||||
// 返回空字符串,使用相对路径通过代理
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// WebSocket连接管理
|
||||
// 使用相对路径,通过Vite代理转发到后端
|
||||
// 在开发环境下,Vite会代理WebSocket连接
|
||||
const WS_BASE_URL = import.meta.env.DEV
|
||||
? `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}`
|
||||
: 'ws://localhost:7001'
|
||||
|
||||
let ws = null
|
||||
let reconnectTimer = null
|
||||
let reconnectAttempts = 0
|
||||
const MAX_RECONNECT_ATTEMPTS = 5
|
||||
const RECONNECT_DELAY = 3000
|
||||
|
||||
/**
|
||||
* 获取或创建客户端唯一标识
|
||||
*/
|
||||
function getClientId() {
|
||||
let clientId = localStorage.getItem('client_id')
|
||||
if (!clientId) {
|
||||
// 生成唯一客户端ID(基于时间戳和随机数)
|
||||
clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
||||
localStorage.setItem('client_id', clientId)
|
||||
}
|
||||
return clientId
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化WebSocket连接
|
||||
*/
|
||||
export function initWebSocket(onQueueUpdate, onPlaybackState, onOnlineCount, onAnnouncement, onVolumeControl, onUserId) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
console.log('WebSocket已经连接')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取客户端唯一标识
|
||||
const clientId = getClientId()
|
||||
const wsUrl = `${WS_BASE_URL}/ws?clientId=${encodeURIComponent(clientId)}`
|
||||
console.log('正在连接WebSocket:', wsUrl)
|
||||
|
||||
try {
|
||||
ws = new WebSocket(wsUrl)
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket连接已建立')
|
||||
reconnectAttempts = 0
|
||||
}
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
if (data.type === 'queue_update') {
|
||||
onQueueUpdate(data.data || [])
|
||||
} else if (data.type === 'playback_state') {
|
||||
if (onPlaybackState) {
|
||||
onPlaybackState(data.data)
|
||||
}
|
||||
} else if (data.type === 'online_count') {
|
||||
if (onOnlineCount) {
|
||||
onOnlineCount(data.data?.count || 0)
|
||||
}
|
||||
} else if (data.type === 'announcement') {
|
||||
if (onAnnouncement) {
|
||||
onAnnouncement(data.data)
|
||||
}
|
||||
} else if (data.type === 'volume_control') {
|
||||
if (onVolumeControl) {
|
||||
onVolumeControl(data.data)
|
||||
}
|
||||
} else if (data.type === 'user_id') {
|
||||
if (onUserId) {
|
||||
onUserId(data.data?.userId)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析WebSocket消息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket错误:', error)
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket连接已关闭')
|
||||
ws = null
|
||||
|
||||
// 尝试重连
|
||||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||
reconnectAttempts++
|
||||
console.log(`尝试重连 (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`)
|
||||
reconnectTimer = setTimeout(() => {
|
||||
initWebSocket(onQueueUpdate, onPlaybackState, onOnlineCount, onAnnouncement, onVolumeControl, onUserId)
|
||||
}, RECONNECT_DELAY)
|
||||
} else {
|
||||
console.error('WebSocket重连失败,已达到最大重试次数')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建WebSocket连接失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭WebSocket连接
|
||||
*/
|
||||
export function closeWebSocket() {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
ws.close()
|
||||
ws = null
|
||||
}
|
||||
|
||||
reconnectAttempts = MAX_RECONNECT_ATTEMPTS // 阻止自动重连
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebSocket连接状态
|
||||
*/
|
||||
export function getWebSocketStatus() {
|
||||
if (!ws) return 'disconnected'
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.CONNECTING:
|
||||
return 'connecting'
|
||||
case WebSocket.OPEN:
|
||||
return 'connected'
|
||||
case WebSocket.CLOSING:
|
||||
return 'closing'
|
||||
case WebSocket.CLOSED:
|
||||
return 'closed'
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user