feat: 初始化音乐项目后台管理系统- 新增后台管理相关页面和功能组件

- 实现管理员评论管理、数据统计等功能
- 添加后台布局和路由管理
-配置数据库和Redis连接
- 实现文件上传和访问路径配置
- 添加Sa-Token安全配置
This commit is contained in:
ikmkj
2025-04-26 19:32:45 +08:00
commit b275ffab5a
185 changed files with 32763 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
/**
* 清理本地存储
*/
export function clearStorage() {
localStorage.removeItem('token')
localStorage.removeItem('tokenName')
localStorage.removeItem('tokenPrefix')
localStorage.removeItem('user')
localStorage.removeItem('isAdmin')
console.log('本地存储已清理')
}

View File

@@ -0,0 +1,42 @@
/**
* 处理图片URL确保它是完整的URL
* @param {string} url 图片URL
* @returns {string} 完整的图片URL
*/
export function processImageUrl(url) {
if (!url) {
return 'https://via.placeholder.com/150?text=无图片';
}
// 如果是完整URL直接返回
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
// 如果是相对路径添加API基础URL
// 使用后端服务器地址
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8085';
// 确保路径以/开头
if (!url.startsWith('/')) {
url = '/' + url;
}
return `${baseUrl}${url}`;
}
/**
* 格式化播放次数
* @param {number} count 播放次数
* @returns {string} 格式化后的播放次数
*/
export function formatPlayCount(count) {
if (!count) return '0';
if (count < 10000) {
return count.toString();
} else if (count < 100000000) {
return (count / 10000).toFixed(1) + '万';
} else {
return (count / 100000000).toFixed(1) + '亿';
}
}

View File

@@ -0,0 +1,95 @@
import SockJS from 'sockjs-client/dist/sockjs.min.js'; // 确保导入正确的 SockJS 文件
import { Client } from '@stomp/stompjs';
let stompClient = null;
let subscriptions = {}; // 用于存储订阅,方便取消
const connect = (onConnectedCallback, onErrorCallback) => {
// 后端 WebSocket 端点 URL (根据实际部署情况可能需要修改)
// 注意:如果后端运行在不同端口,需要写完整 URL例如 'http://localhost:8080/ws'
// 如果前后端同源,可以直接用 '/ws'
const socket = new SockJS('http://localhost:8085/ws'); // 使用后端配置的端点 (修改为绝对路径)
stompClient = new Client({
webSocketFactory: () => socket,
reconnectDelay: 5000, // 自动重连延迟
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
onConnect: (frame) => {
console.log('WebSocket Connected: ' + frame);
if (onConnectedCallback) {
onConnectedCallback(frame);
}
},
onStompError: (frame) => {
console.error('Broker reported error: ' + frame.headers['message']);
console.error('Additional details: ' + frame.body);
if (onErrorCallback) {
onErrorCallback(frame);
}
},
onWebSocketError: (error) => {
console.error('WebSocket Error: ', error);
if (onErrorCallback) {
onErrorCallback(error);
}
},
onDisconnect: () => {
console.log('WebSocket Disconnected');
// 清理所有订阅
subscriptions = {};
}
});
stompClient.activate();
};
const subscribe = (destination, callback) => {
if (stompClient && stompClient.connected) {
console.log(`Subscribing to ${destination}`);
const subscription = stompClient.subscribe(destination, (message) => {
console.log(`Message received from ${destination}:`, message.body);
if (callback) {
try {
callback(JSON.parse(message.body));
} catch (e) {
console.error("Error parsing message body:", e);
callback(message.body); // 如果解析失败,返回原始 body
}
}
});
subscriptions[destination] = subscription; // 存储订阅
return subscription;
} else {
console.error('Cannot subscribe, Stomp client not connected.');
return null;
}
};
const unsubscribe = (destination) => {
if (subscriptions[destination]) {
console.log(`Unsubscribing from ${destination}`);
subscriptions[destination].unsubscribe();
delete subscriptions[destination]; // 从存储中移除
}
};
const disconnect = () => {
if (stompClient && stompClient.connected) {
stompClient.deactivate();
console.log('WebSocket connection deactivated.');
}
// 即使未连接,也尝试清理 stompClient 实例
stompClient = null;
subscriptions = {}; // 清空订阅记录
};
// 可选:发送消息的方法 (如果需要前端主动发消息)
const sendMessage = (destination, body) => {
if (stompClient && stompClient.connected) {
stompClient.publish({ destination: destination, body: JSON.stringify(body) });
} else {
console.error('Cannot send message, Stomp client not connected.');
}
};
export { connect, subscribe, unsubscribe, disconnect, sendMessage };