feat(devtools): 添加生产环境调试工具系统

新增调试工具目录 src/devtools/,提供完整的生产环境调试能力:

- apiDebugger: 拦截所有 API 请求/响应,记录日志
- notificationDebugger: 测试浏览器通知,检查权限
- socketDebugger: 监听所有 Socket 事件,诊断连接状态
- 全局 API: window.__DEBUG__ 提供便捷的控制台调试命令

功能特性:
- 环境变量控制:REACT_APP_ENABLE_DEBUG=true 开启
- 动态导入:不影响生产环境性能
- 完整诊断:diagnose()、performance()、exportAll()
- 易于移除:所有代码集中在 src/devtools/ 目录

Webpack 配置:
- 添加 'debug' alias 强制解析到 node_modules/debug
- 添加 @devtools alias 简化导入路径
- 避免与 npm debug 包的命名冲突

🔧 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-11-10 17:26:14 +08:00
parent e555d22499
commit c593582006
7 changed files with 935 additions and 0 deletions

View File

@@ -16,6 +16,15 @@ NODE_ENV=production
# Mock 配置(生产环境禁用 Mock
REACT_APP_ENABLE_MOCK=false
# 🔧 调试模式(生产环境临时调试用)
# 开启后会在全局暴露 window.__DEBUG__ 调试 API
# ⚠️ 警告: 调试模式会记录所有 API 请求/响应,调试完成后请立即关闭!
# 使用方法:
# 1. 设置为 true 并重新构建
# 2. 在浏览器控制台使用 window.__DEBUG__.help() 查看命令
# 3. 调试完成后设置为 false 并重新构建
REACT_APP_ENABLE_DEBUG=false
# 后端 API 地址(生产环境)
REACT_APP_API_URL=http://49.232.185.254:5001

View File

@@ -110,6 +110,9 @@ module.exports = {
...webpackConfig.resolve,
alias: {
...webpackConfig.resolve.alias,
// 强制 'debug' 模块解析到 node_modules避免与 src/devtools/ 冲突)
'debug': path.resolve(__dirname, 'node_modules/debug'),
// 根目录别名
'@': path.resolve(__dirname, 'src'),
@@ -119,6 +122,7 @@ module.exports = {
'@constants': path.resolve(__dirname, 'src/constants'),
'@contexts': path.resolve(__dirname, 'src/contexts'),
'@data': path.resolve(__dirname, 'src/data'),
'@devtools': path.resolve(__dirname, 'src/devtools'),
'@hooks': path.resolve(__dirname, 'src/hooks'),
'@layouts': path.resolve(__dirname, 'src/layouts'),
'@lib': path.resolve(__dirname, 'src/lib'),
@@ -263,6 +267,34 @@ module.exports = {
logLevel: 'debug',
pathRewrite: { '^/concept-api': '' },
},
'/bytedesk-api': {
target: 'http://43.143.189.195',
changeOrigin: true,
secure: false,
logLevel: 'debug',
pathRewrite: { '^/bytedesk-api': '' },
},
'/chat': {
target: 'http://43.143.189.195',
changeOrigin: true,
secure: false,
logLevel: 'debug',
// 不需要pathRewrite保留/chat路径
},
'/config': {
target: 'http://43.143.189.195',
changeOrigin: true,
secure: false,
logLevel: 'debug',
// 不需要pathRewrite保留/config路径
},
'/visitor': {
target: 'http://43.143.189.195',
changeOrigin: true,
secure: false,
logLevel: 'debug',
// 不需要pathRewrite保留/visitor路径
},
},
}),
},

253
src/devtools/apiDebugger.js Normal file
View File

@@ -0,0 +1,253 @@
// src/debug/apiDebugger.js
/**
* API 调试工具
* 生产环境临时调试使用,后期可整体删除 src/debug/ 目录
*/
import axios from 'axios';
import { getApiBase } from '@utils/apiConfig';
class ApiDebugger {
constructor() {
this.requestLog = [];
this.maxLogSize = 100;
this.isLogging = true;
}
/**
* 初始化 Axios 拦截器
*/
init() {
// 请求拦截器
axios.interceptors.request.use(
(config) => {
if (this.isLogging) {
const logEntry = {
type: 'request',
timestamp: new Date().toISOString(),
method: config.method.toUpperCase(),
url: config.url,
baseURL: config.baseURL,
fullURL: this._getFullURL(config),
headers: config.headers,
data: config.data,
params: config.params,
};
this._addLog(logEntry);
console.log(
`%c[API Request] ${logEntry.method} ${logEntry.fullURL}`,
'color: #2196F3; font-weight: bold;',
{
headers: config.headers,
data: config.data,
params: config.params,
}
);
}
return config;
},
(error) => {
console.error('[API Request Error]', error);
return Promise.reject(error);
}
);
// 响应拦截器
axios.interceptors.response.use(
(response) => {
if (this.isLogging) {
const logEntry = {
type: 'response',
timestamp: new Date().toISOString(),
method: response.config.method.toUpperCase(),
url: response.config.url,
fullURL: this._getFullURL(response.config),
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
};
this._addLog(logEntry);
console.log(
`%c[API Response] ${logEntry.method} ${logEntry.fullURL} - ${logEntry.status}`,
'color: #4CAF50; font-weight: bold;',
{
status: response.status,
data: response.data,
headers: response.headers,
}
);
}
return response;
},
(error) => {
if (this.isLogging) {
const logEntry = {
type: 'error',
timestamp: new Date().toISOString(),
method: error.config?.method?.toUpperCase() || 'UNKNOWN',
url: error.config?.url || 'UNKNOWN',
fullURL: error.config ? this._getFullURL(error.config) : 'UNKNOWN',
status: error.response?.status,
statusText: error.response?.statusText,
message: error.message,
data: error.response?.data,
};
this._addLog(logEntry);
console.error(
`%c[API Error] ${logEntry.method} ${logEntry.fullURL}`,
'color: #F44336; font-weight: bold;',
{
status: error.response?.status,
message: error.message,
data: error.response?.data,
}
);
}
return Promise.reject(error);
}
);
console.log('%c[API Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
}
/**
* 获取完整 URL
*/
_getFullURL(config) {
const baseURL = config.baseURL || '';
const url = config.url || '';
const fullURL = baseURL + url;
// 添加查询参数
if (config.params) {
const params = new URLSearchParams(config.params).toString();
return params ? `${fullURL}?${params}` : fullURL;
}
return fullURL;
}
/**
* 添加日志
*/
_addLog(entry) {
this.requestLog.unshift(entry);
if (this.requestLog.length > this.maxLogSize) {
this.requestLog = this.requestLog.slice(0, this.maxLogSize);
}
}
/**
* 获取所有日志
*/
getLogs(type = 'all') {
if (type === 'all') {
return this.requestLog;
}
return this.requestLog.filter((log) => log.type === type);
}
/**
* 清空日志
*/
clearLogs() {
this.requestLog = [];
console.log('[API Debugger] Logs cleared');
}
/**
* 导出日志为 JSON
*/
exportLogs() {
const blob = new Blob([JSON.stringify(this.requestLog, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `api-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
console.log('[API Debugger] Logs exported');
}
/**
* 打印日志统计
*/
printStats() {
const stats = {
total: this.requestLog.length,
requests: this.requestLog.filter((log) => log.type === 'request').length,
responses: this.requestLog.filter((log) => log.type === 'response').length,
errors: this.requestLog.filter((log) => log.type === 'error').length,
};
console.table(stats);
return stats;
}
/**
* 手动发送 API 请求(测试用)
*/
async testRequest(method, endpoint, data = null, config = {}) {
const apiBase = getApiBase();
const url = `${apiBase}${endpoint}`;
console.log(`[API Debugger] Testing ${method.toUpperCase()} ${url}`);
try {
const response = await axios({
method,
url,
data,
...config,
});
console.log('[API Debugger] Test succeeded:', response.data);
return response.data;
} catch (error) {
console.error('[API Debugger] Test failed:', error);
throw error;
}
}
/**
* 开启/关闭日志记录
*/
toggleLogging(enabled) {
this.isLogging = enabled;
console.log(`[API Debugger] Logging ${enabled ? 'enabled' : 'disabled'}`);
}
/**
* 获取最近的错误
*/
getRecentErrors(count = 10) {
return this.requestLog.filter((log) => log.type === 'error').slice(0, count);
}
/**
* 按 URL 过滤日志
*/
getLogsByURL(urlPattern) {
return this.requestLog.filter((log) => log.url && log.url.includes(urlPattern));
}
/**
* 按状态码过滤日志
*/
getLogsByStatus(status) {
return this.requestLog.filter((log) => log.status === status);
}
}
// 导出单例
export const apiDebugger = new ApiDebugger();
export default apiDebugger;

268
src/devtools/index.js Normal file
View File

@@ -0,0 +1,268 @@
// src/debug/index.js
/**
* 调试工具统一入口
*
* 使用方法:
* 1. 开启调试: 在 .env.production 中设置 REACT_APP_ENABLE_DEBUG=true
* 2. 使用控制台命令: window.__DEBUG__.api.getLogs()
* 3. 后期移除: 删除整个 src/debug/ 目录 + 从 src/index.js 移除导入
*
* 全局 API:
* - window.__DEBUG__ - 调试 API 主对象
* - window.__DEBUG__.api - API 调试工具
* - window.__DEBUG__.notification - 通知调试工具
* - window.__DEBUG__.socket - Socket 调试工具
* - window.__DEBUG__.help() - 显示帮助信息
* - window.__DEBUG__.exportAll() - 导出所有日志
*/
import { apiDebugger } from './apiDebugger';
import { notificationDebugger } from './notificationDebugger';
import { socketDebugger } from './socketDebugger';
class DebugToolkit {
constructor() {
this.api = apiDebugger;
this.notification = notificationDebugger;
this.socket = socketDebugger;
}
/**
* 初始化所有调试工具
*/
init() {
console.log(
'%c╔════════════════════════════════════════════════════════════════╗',
'color: #FF9800; font-weight: bold;'
);
console.log(
'%c║ 🔧 调试模式已启用 (Debug Mode Enabled) ║',
'color: #FF9800; font-weight: bold;'
);
console.log(
'%c╚════════════════════════════════════════════════════════════════╝',
'color: #FF9800; font-weight: bold;'
);
console.log('');
// 初始化各个调试工具
this.api.init();
this.notification.init();
this.socket.init();
// 暴露到全局
window.__DEBUG__ = this;
// 打印帮助信息
this._printWelcome();
}
/**
* 打印欢迎信息
*/
_printWelcome() {
console.log('%c📚 调试工具使用指南:', 'color: #2196F3; font-weight: bold; font-size: 14px;');
console.log('');
console.log('%c1⃣ API 调试:', 'color: #2196F3; font-weight: bold;');
console.log(' __DEBUG__.api.getLogs() - 获取所有 API 日志');
console.log(' __DEBUG__.api.getRecentErrors() - 获取最近的错误');
console.log(' __DEBUG__.api.exportLogs() - 导出 API 日志');
console.log(' __DEBUG__.api.testRequest(method, endpoint, data) - 测试 API 请求');
console.log('');
console.log('%c2⃣ 通知调试:', 'color: #9C27B0; font-weight: bold;');
console.log(' __DEBUG__.notification.getLogs() - 获取所有通知日志');
console.log(' __DEBUG__.notification.forceNotification() - 发送测试通知');
console.log(' __DEBUG__.notification.checkPermission() - 检查通知权限');
console.log(' __DEBUG__.notification.exportLogs() - 导出通知日志');
console.log('');
console.log('%c3⃣ Socket 调试:', 'color: #00BCD4; font-weight: bold;');
console.log(' __DEBUG__.socket.getLogs() - 获取所有 Socket 日志');
console.log(' __DEBUG__.socket.getStatus() - 获取连接状态');
console.log(' __DEBUG__.socket.reconnect() - 手动重连');
console.log(' __DEBUG__.socket.exportLogs() - 导出 Socket 日志');
console.log('');
console.log('%c4⃣ 通用命令:', 'color: #4CAF50; font-weight: bold;');
console.log(' __DEBUG__.help() - 显示帮助信息');
console.log(' __DEBUG__.exportAll() - 导出所有日志');
console.log(' __DEBUG__.printStats() - 打印所有统计信息');
console.log(' __DEBUG__.clearAll() - 清空所有日志');
console.log('');
console.log(
'%c⚠ 警告: 调试模式会记录所有 API 请求和响应,请勿在生产环境长期开启!',
'color: #F44336; font-weight: bold;'
);
console.log('');
}
/**
* 显示帮助信息
*/
help() {
this._printWelcome();
}
/**
* 导出所有日志
*/
exportAll() {
console.log('[Debug Toolkit] Exporting all logs...');
const allLogs = {
timestamp: new Date().toISOString(),
api: this.api.getLogs(),
notification: this.notification.getLogs(),
socket: this.socket.getLogs(),
};
const blob = new Blob([JSON.stringify(allLogs, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `debug-all-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
console.log('[Debug Toolkit] ✅ All logs exported');
}
/**
* 打印所有统计信息
*/
printStats() {
console.log('\n%c=== 📊 调试统计信息 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
console.log('\n%c[API 统计]', 'color: #2196F3; font-weight: bold;');
const apiStats = this.api.printStats();
console.log('\n%c[通知统计]', 'color: #9C27B0; font-weight: bold;');
const notificationStats = this.notification.printStats();
console.log('\n%c[Socket 统计]', 'color: #00BCD4; font-weight: bold;');
const socketStats = this.socket.printStats();
return {
api: apiStats,
notification: notificationStats,
socket: socketStats,
};
}
/**
* 清空所有日志
*/
clearAll() {
console.log('[Debug Toolkit] Clearing all logs...');
this.api.clearLogs();
this.notification.clearLogs();
this.socket.clearLogs();
console.log('[Debug Toolkit] ✅ All logs cleared');
}
/**
* 快速诊断(检查所有系统状态)
*/
diagnose() {
console.log('\n%c=== 🔍 系统诊断 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
// 1. Socket 状态
console.log('\n%c[1/3] Socket 状态', 'color: #00BCD4; font-weight: bold;');
const socketStatus = this.socket.getStatus();
// 2. 通知权限
console.log('\n%c[2/3] 通知权限', 'color: #9C27B0; font-weight: bold;');
const notificationStatus = this.notification.checkPermission();
// 3. API 错误
console.log('\n%c[3/3] 最近的 API 错误', 'color: #F44336; font-weight: bold;');
const recentErrors = this.api.getRecentErrors(5);
if (recentErrors.length > 0) {
console.table(
recentErrors.map((err) => ({
时间: err.timestamp,
方法: err.method,
URL: err.url,
状态码: err.status,
错误信息: err.message,
}))
);
} else {
console.log('✅ 没有 API 错误');
}
// 4. 汇总报告
const report = {
timestamp: new Date().toISOString(),
socket: socketStatus,
notification: notificationStatus,
apiErrors: recentErrors.length,
};
console.log('\n%c=== 诊断报告 ===', 'color: #4CAF50; font-weight: bold;');
console.table(report);
return report;
}
/**
* 性能监控
*/
performance() {
console.log('\n%c=== ⚡ 性能监控 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
// 计算 API 平均响应时间
const apiLogs = this.api.getLogs();
const responseTimes = [];
for (let i = 0; i < apiLogs.length - 1; i++) {
const log = apiLogs[i];
const prevLog = apiLogs[i + 1];
if (
log.type === 'response' &&
prevLog.type === 'request' &&
log.url === prevLog.url
) {
const responseTime =
new Date(log.timestamp).getTime() - new Date(prevLog.timestamp).getTime();
responseTimes.push({
url: log.url,
method: log.method,
time: responseTime,
});
}
}
if (responseTimes.length > 0) {
const avgTime =
responseTimes.reduce((sum, item) => sum + item.time, 0) / responseTimes.length;
const maxTime = Math.max(...responseTimes.map((item) => item.time));
const minTime = Math.min(...responseTimes.map((item) => item.time));
console.log('API 响应时间统计:');
console.table({
平均响应时间: `${avgTime.toFixed(2)}ms`,
最快响应: `${minTime}ms`,
最慢响应: `${maxTime}ms`,
请求总数: responseTimes.length,
});
// 显示最慢的 5 个请求
console.log('\n最慢的 5 个请求:');
const slowest = responseTimes.sort((a, b) => b.time - a.time).slice(0, 5);
console.table(
slowest.map((item) => ({
方法: item.method,
URL: item.url,
响应时间: `${item.time}ms`,
}))
);
} else {
console.log('暂无性能数据');
}
}
}
// 导出单例
export const debugToolkit = new DebugToolkit();
export default debugToolkit;

View File

@@ -0,0 +1,166 @@
// src/debug/notificationDebugger.js
/**
* 通知系统调试工具
* 扩展现有的 window.__NOTIFY_DEBUG__添加更多生产环境调试能力
*/
import { browserNotificationService } from '@services/browserNotificationService';
class NotificationDebugger {
constructor() {
this.eventLog = [];
this.maxLogSize = 100;
}
/**
* 初始化调试工具
*/
init() {
console.log('%c[Notification Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
}
/**
* 记录通知事件
*/
logEvent(eventType, data) {
const logEntry = {
type: eventType,
timestamp: new Date().toISOString(),
data,
};
this.eventLog.unshift(logEntry);
if (this.eventLog.length > this.maxLogSize) {
this.eventLog = this.eventLog.slice(0, this.maxLogSize);
}
console.log(
`%c[Notification Event] ${eventType}`,
'color: #9C27B0; font-weight: bold;',
data
);
}
/**
* 获取所有事件日志
*/
getLogs() {
return this.eventLog;
}
/**
* 清空日志
*/
clearLogs() {
this.eventLog = [];
console.log('[Notification Debugger] Logs cleared');
}
/**
* 导出日志
*/
exportLogs() {
const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `notification-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
console.log('[Notification Debugger] Logs exported');
}
/**
* 强制发送浏览器通知(测试用)
*/
forceNotification(options = {}) {
const defaultOptions = {
title: '🧪 测试通知',
body: `测试时间: ${new Date().toLocaleString()}`,
tag: `test_${Date.now()}`,
requireInteraction: false,
autoClose: 5000,
};
const finalOptions = { ...defaultOptions, ...options };
console.log('[Notification Debugger] Sending test notification:', finalOptions);
const notification = browserNotificationService.sendNotification(finalOptions);
if (notification) {
console.log('[Notification Debugger] ✅ Notification sent successfully');
} else {
console.error('[Notification Debugger] ❌ Failed to send notification');
}
return notification;
}
/**
* 检查通知权限状态
*/
checkPermission() {
const permission = browserNotificationService.getPermissionStatus();
const isSupported = browserNotificationService.isSupported();
const status = {
supported: isSupported,
permission,
canSend: isSupported && permission === 'granted',
};
console.table(status);
return status;
}
/**
* 请求通知权限
*/
async requestPermission() {
console.log('[Notification Debugger] Requesting notification permission...');
const result = await browserNotificationService.requestPermission();
console.log(`[Notification Debugger] Permission result: ${result}`);
return result;
}
/**
* 打印事件统计
*/
printStats() {
const stats = {
total: this.eventLog.length,
byType: {},
};
this.eventLog.forEach((log) => {
stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
});
console.log('=== Notification Stats ===');
console.table(stats.byType);
console.log(`Total events: ${stats.total}`);
return stats;
}
/**
* 按类型过滤日志
*/
getLogsByType(eventType) {
return this.eventLog.filter((log) => log.type === eventType);
}
/**
* 获取最近的事件
*/
getRecentEvents(count = 10) {
return this.eventLog.slice(0, count);
}
}
// 导出单例
export const notificationDebugger = new NotificationDebugger();
export default notificationDebugger;

View File

@@ -0,0 +1,194 @@
// src/debug/socketDebugger.js
/**
* Socket 调试工具
* 扩展现有的 window.__SOCKET_DEBUG__添加更多生产环境调试能力
*/
import { socket } from '@services/socket';
class SocketDebugger {
constructor() {
this.eventLog = [];
this.maxLogSize = 100;
}
/**
* 初始化调试工具
*/
init() {
// 监听所有 Socket 事件
this._attachEventListeners();
console.log('%c[Socket Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
}
/**
* 附加事件监听器
*/
_attachEventListeners() {
const events = [
'connect',
'disconnect',
'connect_error',
'reconnect',
'reconnect_failed',
'new_event',
'system_notification',
];
events.forEach((event) => {
socket.on(event, (data) => {
this.logEvent(event, data);
});
});
}
/**
* 记录 Socket 事件
*/
logEvent(eventType, data) {
const logEntry = {
type: eventType,
timestamp: new Date().toISOString(),
data,
};
this.eventLog.unshift(logEntry);
if (this.eventLog.length > this.maxLogSize) {
this.eventLog = this.eventLog.slice(0, this.maxLogSize);
}
console.log(
`%c[Socket Event] ${eventType}`,
'color: #00BCD4; font-weight: bold;',
data
);
}
/**
* 获取所有事件日志
*/
getLogs() {
return this.eventLog;
}
/**
* 清空日志
*/
clearLogs() {
this.eventLog = [];
console.log('[Socket Debugger] Logs cleared');
}
/**
* 导出日志
*/
exportLogs() {
const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `socket-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
console.log('[Socket Debugger] Logs exported');
}
/**
* 获取连接状态
*/
getStatus() {
const status = {
connected: socket.connected || false,
type: window.SOCKET_TYPE || 'UNKNOWN',
reconnectAttempts: socket.getReconnectAttempts?.() || 0,
maxReconnectAttempts: socket.getMaxReconnectAttempts?.() || Infinity,
};
console.table(status);
return status;
}
/**
* 手动触发连接
*/
connect() {
console.log('[Socket Debugger] Manually connecting...');
socket.connect();
}
/**
* 手动断开连接
*/
disconnect() {
console.log('[Socket Debugger] Manually disconnecting...');
socket.disconnect();
}
/**
* 手动重连
*/
reconnect() {
console.log('[Socket Debugger] Manually reconnecting...');
socket.disconnect();
setTimeout(() => {
socket.connect();
}, 1000);
}
/**
* 发送测试事件
*/
emitTest(eventName, data = {}) {
console.log(`[Socket Debugger] Emitting test event: ${eventName}`, data);
socket.emit(eventName, data);
}
/**
* 打印事件统计
*/
printStats() {
const stats = {
total: this.eventLog.length,
byType: {},
};
this.eventLog.forEach((log) => {
stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
});
console.log('=== Socket Stats ===');
console.table(stats.byType);
console.log(`Total events: ${stats.total}`);
return stats;
}
/**
* 按类型过滤日志
*/
getLogsByType(eventType) {
return this.eventLog.filter((log) => log.type === eventType);
}
/**
* 获取最近的事件
*/
getRecentEvents(count = 10) {
return this.eventLog.slice(0, count);
}
/**
* 获取错误事件
*/
getErrors() {
return this.eventLog.filter(
(log) => log.type === 'connect_error' || log.type === 'reconnect_failed'
);
}
}
// 导出单例
export const socketDebugger = new SocketDebugger();
export default socketDebugger;

View File

@@ -13,6 +13,19 @@ import App from './App';
import { browserNotificationService } from './services/browserNotificationService';
window.browserNotificationService = browserNotificationService;
// 🔧 条件导入调试工具(生产环境可选)
// 开启方式: 在 .env 文件中设置 REACT_APP_ENABLE_DEBUG=true
// 移除方式: 删除此段代码 + 删除 src/devtools/ 目录
if (process.env.REACT_APP_ENABLE_DEBUG === 'true') {
import('./devtools').then(({ debugToolkit }) => {
debugToolkit.init();
console.log(
'%c✅ 调试工具已加载!使用 window.__DEBUG__.help() 查看命令',
'color: #4CAF50; font-weight: bold; font-size: 14px;'
);
});
}
// 注册 Service Worker用于支持浏览器通知
function registerServiceWorker() {
// ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突)