271 lines
7.3 KiB
TypeScript
271 lines
7.3 KiB
TypeScript
import * as vscode from 'vscode';
|
||
import * as path from 'path';
|
||
|
||
// 尝试加载 node-notifier,如果失败则使用 null
|
||
let notifier: any = null;
|
||
try {
|
||
notifier = require('node-notifier');
|
||
console.log('[NotificationService] node-notifier 加载成功');
|
||
} catch (error) {
|
||
console.log('[NotificationService] node-notifier 加载失败,将只使用 VS Code 内置通知');
|
||
}
|
||
|
||
/**
|
||
* 通知类型枚举
|
||
*/
|
||
export enum NotificationType {
|
||
INFO = 'info',
|
||
SUCCESS = 'success',
|
||
WARNING = 'warning',
|
||
ERROR = 'error'
|
||
}
|
||
|
||
/**
|
||
* 通知选项接口
|
||
*/
|
||
export interface NotificationOptions {
|
||
/** 通知标题 */
|
||
title: string;
|
||
/** 通知消息 */
|
||
message: string;
|
||
/** 通知类型 */
|
||
type?: NotificationType;
|
||
/** 是否播放声音 */
|
||
sound?: boolean;
|
||
/** 超时时间(秒),0 表示不自动消失 */
|
||
timeout?: number;
|
||
/** 自定义图标路径 */
|
||
icon?: string;
|
||
/** 点击通知时的回调 */
|
||
onClick?: () => void;
|
||
}
|
||
|
||
/**
|
||
* 系统通知服务类
|
||
*/
|
||
export class NotificationService {
|
||
private static instance: NotificationService;
|
||
private readonly extensionPath: string;
|
||
private readonly iconPath: string;
|
||
private lastNotificationTime: Map<string, number> = new Map();
|
||
private readonly DEBOUNCE_INTERVAL = 3000; // 3 秒防抖
|
||
|
||
private constructor(context: vscode.ExtensionContext) {
|
||
this.extensionPath = context.extensionPath;
|
||
this.iconPath = path.join(this.extensionPath, 'media', 'icon.png');
|
||
console.log('[NotificationService] 初始化通知服务');
|
||
console.log('[NotificationService] 扩展路径:', this.extensionPath);
|
||
console.log('[NotificationService] 图标路径:', this.iconPath);
|
||
}
|
||
|
||
/**
|
||
* 获取单例实例
|
||
*/
|
||
public static getInstance(context?: vscode.ExtensionContext): NotificationService {
|
||
if (!NotificationService.instance && context) {
|
||
NotificationService.instance = new NotificationService(context);
|
||
}
|
||
return NotificationService.instance;
|
||
}
|
||
|
||
/**
|
||
* 检查是否启用系统通知
|
||
*/
|
||
private isSystemNotificationEnabled(): boolean {
|
||
const config = vscode.workspace.getConfiguration('ic-coder');
|
||
return config.get<boolean>('enableSystemNotification', true);
|
||
}
|
||
|
||
/**
|
||
* 检查是否应该发送通知(防抖)
|
||
*/
|
||
private shouldSendNotification(key: string): boolean {
|
||
const now = Date.now();
|
||
const lastTime = this.lastNotificationTime.get(key) || 0;
|
||
|
||
if (now - lastTime < this.DEBOUNCE_INTERVAL) {
|
||
return false;
|
||
}
|
||
|
||
this.lastNotificationTime.set(key, now);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 发送系统通知
|
||
*/
|
||
public sendNotification(options: NotificationOptions): void {
|
||
console.log('[NotificationService] ========== 开始发送通知 ==========');
|
||
console.log('[NotificationService] 通知选项:', options);
|
||
|
||
// 检查用户配置
|
||
if (!this.isSystemNotificationEnabled()) {
|
||
console.log('[NotificationService] 系统通知已禁用');
|
||
return;
|
||
}
|
||
console.log('[NotificationService] 系统通知已启用');
|
||
|
||
const {
|
||
title,
|
||
message,
|
||
type = NotificationType.INFO,
|
||
onClick
|
||
} = options;
|
||
|
||
// 防抖检查
|
||
const notificationKey = `${title}-${message}`;
|
||
if (!this.shouldSendNotification(notificationKey)) {
|
||
console.log('[NotificationService] 通知被防抖机制拦截');
|
||
return;
|
||
}
|
||
console.log('[NotificationService] 通过防抖检查');
|
||
|
||
// 如果 node-notifier 不可用,直接使用 VS Code 内置通知
|
||
if (!notifier) {
|
||
console.log('[NotificationService] node-notifier 不可用,使用 VS Code 内置通知');
|
||
this.showVSCodeNotification(title, message, type, onClick);
|
||
return;
|
||
}
|
||
|
||
// 使用 node-notifier 发送系统通知
|
||
console.log('[NotificationService] 使用 node-notifier 发送系统通知');
|
||
|
||
try {
|
||
const notificationConfig: any = {
|
||
title: title,
|
||
message: message,
|
||
sound: true,
|
||
wait: false,
|
||
timeout: 10,
|
||
appID: 'IC Coder'
|
||
};
|
||
|
||
// Windows 特定配置
|
||
if (process.platform === 'win32') {
|
||
notificationConfig.icon = this.iconPath;
|
||
console.log('[NotificationService] Windows 平台,图标路径:', this.iconPath);
|
||
}
|
||
|
||
console.log('[NotificationService] 通知配置:', notificationConfig);
|
||
|
||
notifier.notify(notificationConfig, (err: any, response: any, metadata: any) => {
|
||
if (err) {
|
||
console.error('[NotificationService] ❌ node-notifier 失败:', err);
|
||
} else {
|
||
console.log('[NotificationService] ✅ node-notifier 成功');
|
||
console.log('[NotificationService] 响应:', response);
|
||
console.log('[NotificationService] 元数据:', metadata);
|
||
}
|
||
});
|
||
|
||
if (onClick) {
|
||
notifier.on('click', () => {
|
||
console.log('[NotificationService] 用户点击了系统通知');
|
||
onClick();
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('[NotificationService] ❌ node-notifier 异常:', error);
|
||
// 如果系统通知失败,显示 VS Code 内置通知作为备用
|
||
console.log('[NotificationService] 系统通知失败,显示 VS Code 内置通知');
|
||
this.showVSCodeNotification(title, message, type, onClick);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示 VS Code 内置通知
|
||
*/
|
||
private showVSCodeNotification(
|
||
title: string,
|
||
message: string,
|
||
type: NotificationType,
|
||
onClick?: () => void
|
||
): void {
|
||
const fullMessage = `${title}: ${message}`;
|
||
console.log('[NotificationService] 显示 VS Code 通知:', fullMessage);
|
||
|
||
let notificationPromise: Thenable<string | undefined>;
|
||
|
||
switch (type) {
|
||
case NotificationType.ERROR:
|
||
notificationPromise = vscode.window.showErrorMessage(fullMessage, '查看详情');
|
||
break;
|
||
case NotificationType.WARNING:
|
||
notificationPromise = vscode.window.showWarningMessage(fullMessage, '查看详情');
|
||
break;
|
||
case NotificationType.SUCCESS:
|
||
case NotificationType.INFO:
|
||
default:
|
||
notificationPromise = vscode.window.showInformationMessage(fullMessage, '查看详情');
|
||
break;
|
||
}
|
||
|
||
// 处理点击事件
|
||
if (onClick) {
|
||
notificationPromise.then((selection) => {
|
||
if (selection === '查看详情') {
|
||
console.log('[NotificationService] 用户点击了通知');
|
||
onClick();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送成功通知
|
||
*/
|
||
public success(title: string, message: string, onClick?: () => void): void {
|
||
this.sendNotification({
|
||
title,
|
||
message,
|
||
type: NotificationType.SUCCESS,
|
||
sound: true,
|
||
timeout: 10,
|
||
onClick
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送错误通知
|
||
*/
|
||
public error(title: string, message: string, onClick?: () => void): void {
|
||
this.sendNotification({
|
||
title,
|
||
message,
|
||
type: NotificationType.ERROR,
|
||
sound: true,
|
||
timeout: 15,
|
||
onClick
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送警告通知
|
||
*/
|
||
public warning(title: string, message: string, onClick?: () => void): void {
|
||
this.sendNotification({
|
||
title,
|
||
message,
|
||
type: NotificationType.WARNING,
|
||
sound: true,
|
||
timeout: 10,
|
||
onClick
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发送信息通知
|
||
*/
|
||
public info(title: string, message: string, onClick?: () => void): void {
|
||
this.sendNotification({
|
||
title,
|
||
message,
|
||
type: NotificationType.INFO,
|
||
sound: false,
|
||
timeout: 8,
|
||
onClick
|
||
});
|
||
}
|
||
|
||
}
|