通知系统 #

一、通知概述 #

1.1 通知类型 #

类型 说明 使用场景
系统通知 操作系统原生通知 重要提醒
应用内通知 应用内部弹窗 即时反馈
徽章通知 图标角标数字 未读消息

1.2 安装插件 #

bash
pnpm add @tauri-apps/plugin-notification
rust
// src-tauri/src/lib.rs
tauri::Builder::default()
    .plugin(tauri_plugin_notification::init())
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

1.3 权限配置 #

json
// src-tauri/capabilities/default.json
{
    "permissions": [
        "notification:default",
        "notification:allow-is-permission-granted",
        "notification:allow-request-permission",
        "notification:allow-notify"
    ]
}

二、发送通知 #

2.1 基本通知 #

typescript
import { sendNotification } from '@tauri-apps/plugin-notification';

async function showNotification() {
    await sendNotification({
        title: '通知标题',
        body: '这是通知内容',
    });
}

2.2 带图标的通知 #

typescript
import { sendNotification } from '@tauri-apps/plugin-notification';

async function showNotificationWithIcon() {
    await sendNotification({
        title: '新消息',
        body: '您有一条新消息',
        icon: 'icons/message.png',
    });
}

2.3 通知选项 #

typescript
import { sendNotification, Options } from '@tauri-apps/plugin-notification';

const options: Options = {
    title: '下载完成',
    body: '文件已成功下载',
    icon: 'icons/download.png',
    sound: 'default',
};

await sendNotification(options);

三、通知权限 #

3.1 检查权限 #

typescript
import { isPermissionGranted } from '@tauri-apps/plugin-notification';

async function checkPermission() {
    const granted = await isPermissionGranted();
    console.log('Notification permission:', granted);
    return granted;
}

3.2 请求权限 #

typescript
import { requestPermission, isPermissionGranted } from '@tauri-apps/plugin-notification';

async function ensurePermission() {
    let granted = await isPermissionGranted();
    
    if (!granted) {
        const permission = await requestPermission();
        granted = permission === 'granted';
    }
    
    return granted;
}

3.3 权限处理示例 #

typescript
import {
    isPermissionGranted,
    requestPermission,
    sendNotification,
} from '@tauri-apps/plugin-notification';

async function notify(title: string, body: string) {
    let granted = await isPermissionGranted();
    
    if (!granted) {
        const permission = await requestPermission();
        granted = permission === 'granted';
    }
    
    if (granted) {
        await sendNotification({ title, body });
    } else {
        console.warn('Notification permission not granted');
    }
}

四、通知管理 #

4.1 创建通知对象 #

typescript
import { Notification } from '@tauri-apps/plugin-notification';

async function createNotification() {
    const notification = await Notification.create({
        title: '任务完成',
        body: '您的任务已完成',
    });
    
    // 显示通知
    await notification.show();
}

4.2 通知 ID #

typescript
import { Notification } from '@tauri-apps/plugin-notification';

const notification = await Notification.create({
    id: 'task-complete',
    title: '任务完成',
    body: '您的任务已完成',
});

await notification.show();

4.3 取消通知 #

typescript
import { Notification } from '@tauri-apps/plugin-notification';

// 通过 ID 取消
await Notification.cancel('task-complete');

// 取消所有通知
await Notification.cancelAll();

五、通知事件 #

5.1 监听通知点击 #

typescript
import { onAction } from '@tauri-apps/plugin-notification';

const unlisten = await onAction((notification) => {
    console.log('Notification clicked:', notification);
    // 处理点击事件
});

// 取消监听
unlisten();

5.2 通知关闭事件 #

typescript
import { onNotificationClosed } from '@tauri-apps/plugin-notification';

await onNotificationClosed((notification) => {
    console.log('Notification closed:', notification);
});

六、通知封装 #

6.1 通知管理器 #

typescript
// utils/notificationManager.ts
import {
    isPermissionGranted,
    requestPermission,
    sendNotification,
    Notification,
} from '@tauri-apps/plugin-notification';

export class NotificationManager {
    private static instance: NotificationManager;
    private permissionGranted: boolean = false;

    private constructor() {}

    static getInstance(): NotificationManager {
        if (!NotificationManager.instance) {
            NotificationManager.instance = new NotificationManager();
        }
        return NotificationManager.instance;
    }

    async init(): Promise<void> {
        this.permissionGranted = await isPermissionGranted();
        
        if (!this.permissionGranted) {
            const permission = await requestPermission();
            this.permissionGranted = permission === 'granted';
        }
    }

    async show(title: string, body: string, icon?: string): Promise<boolean> {
        if (!this.permissionGranted) {
            await this.init();
        }

        if (!this.permissionGranted) {
            console.warn('Notification permission not granted');
            return false;
        }

        await sendNotification({ title, body, icon });
        return true;
    }

    async showWithId(id: string, title: string, body: string): Promise<void> {
        if (!this.permissionGranted) {
            await this.init();
        }

        const notification = await Notification.create({
            id,
            title,
            body,
        });

        await notification.show();
    }

    async cancel(id: string): Promise<void> {
        await Notification.cancel(id);
    }

    async cancelAll(): Promise<void> {
        await Notification.cancelAll();
    }

    hasPermission(): boolean {
        return this.permissionGranted;
    }
}

export const notificationManager = NotificationManager.getInstance();

6.2 使用通知管理器 #

typescript
import { notificationManager } from './utils/notificationManager';

// 初始化
await notificationManager.init();

// 发送通知
await notificationManager.show('提示', '操作已完成');

// 发送带 ID 的通知
await notificationManager.showWithId('download-1', '下载', '文件下载中...');

// 取消通知
await notificationManager.cancel('download-1');

七、应用内通知 #

7.1 Toast 组件 #

tsx
import { useState, useEffect } from 'react';

interface ToastProps {
    message: string;
    type: 'info' | 'success' | 'error' | 'warning';
    duration?: number;
    onClose: () => void;
}

export function Toast({ message, type, duration = 3000, onClose }: ToastProps) {
    useEffect(() => {
        const timer = setTimeout(onClose, duration);
        return () => clearTimeout(timer);
    }, [duration, onClose]);

    return (
        <div className={`toast toast-${type}`}>
            <span>{message}</span>
            <button onClick={onClose}>×</button>
        </div>
    );
}

7.2 Toast 管理器 #

typescript
// hooks/useToast.ts
import { useState, useCallback } from 'react';

interface Toast {
    id: string;
    message: string;
    type: 'info' | 'success' | 'error' | 'warning';
}

export function useToast() {
    const [toasts, setToasts] = useState<Toast[]>([]);

    const addToast = useCallback((message: string, type: Toast['type'] = 'info') => {
        const id = crypto.randomUUID();
        setToasts((prev) => [...prev, { id, message, type }]);
    }, []);

    const removeToast = useCallback((id: string) => {
        setToasts((prev) => prev.filter((t) => t.id !== id));
    }, []);

    const info = useCallback((message: string) => addToast(message, 'info'), [addToast]);
    const success = useCallback((message: string) => addToast(message, 'success'), [addToast]);
    const error = useCallback((message: string) => addToast(message, 'error'), [addToast]);
    const warning = useCallback((message: string) => addToast(message, 'warning'), [addToast]);

    return { toasts, addToast, removeToast, info, success, error, warning };
}

7.3 使用 Toast #

tsx
import { useToast } from '../hooks/useToast';
import { Toast } from '../components/Toast';

function App() {
    const { toasts, removeToast, success, error } = useToast();

    const handleSave = async () => {
        try {
            await saveData();
            success('保存成功');
        } catch (err) {
            error('保存失败');
        }
    };

    return (
        <div>
            <button onClick={handleSave}>保存</button>
            
            <div className="toast-container">
                {toasts.map((toast) => (
                    <Toast
                        key={toast.id}
                        message={toast.message}
                        type={toast.type}
                        onClose={() => removeToast(toast.id)}
                    />
                ))}
            </div>
        </div>
    );
}

八、徽章通知 #

8.1 设置徽章 #

typescript
// macOS 和 Windows 支持
import { invoke } from '@tauri-apps/api/core';

async function setBadge(count: number) {
    await invoke('set_badge_count', { count });
}

8.2 Rust 实现 #

rust
#[tauri::command]
fn set_badge_count(app: tauri::AppHandle, count: u32) -> Result<(), String> {
    #[cfg(target_os = "macos")]
    {
        app.set_badge_count(count as i32)
            .map_err(|e| e.to_string())?;
    }
    
    Ok(())
}

九、最佳实践 #

9.1 通知策略 #

typescript
// 根据重要性选择通知方式
async function notifyUser(message: string, important: boolean = false) {
    if (important) {
        // 重要消息使用系统通知
        await notificationManager.show('重要通知', message);
    } else {
        // 普通消息使用应用内通知
        toast.info(message);
    }
}

9.2 通知频率控制 #

typescript
class RateLimitedNotifier {
    private lastNotification = 0;
    private minInterval = 5000; // 5秒

    async notify(title: string, body: string): Promise<void> {
        const now = Date.now();
        
        if (now - this.lastNotification < this.minInterval) {
            console.log('Notification rate limited');
            return;
        }
        
        this.lastNotification = now;
        await notificationManager.show(title, body);
    }
}

十、总结 #

10.1 核心要点 #

要点 说明
发送通知 sendNotification
权限管理 检查和请求权限
通知事件 点击和关闭事件
应用内通知 Toast 组件
徽章通知 角标数字

10.2 下一步 #

现在你已经掌握了通知系统,接下来让我们学习 安全架构,了解 Tauri 的安全模型!

最后更新:2026-03-28