🤖 正在生成内容...

iziToast.js v1.5.0 使用教程

📦 基础引入

CDN引入

<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/izitoast@1.5.0/dist/css/iziToast.min.css">
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/izitoast@1.5.0/dist/js/iziToast.min.js"></script>

NPM安装

npm install izitoast@1.5.0
// ES6导入
import iziToast from 'izitoast';
import 'izitoast/dist/css/iziToast.min.css';

// 或CommonJS
const iziToast = require('izitoast');
require('izitoast/dist/css/iziToast.min.css');

🚀 基础使用

1. 简单提示

// 成功提示
iziToast.success({
    title: '成功',
    message: '操作已成功完成',
    position: 'topRight'
});

// 错误提示
iziToast.error({
    title: '错误',
    message: '发生了一个错误',
    position: 'topRight'
});

// 警告提示
iziToast.warning({
    title: '警告',
    message: '请注意以下事项',
    position: 'topRight'
});

// 信息提示
iziToast.info({
    title: '信息',
    message: '这是一条普通信息',
    position: 'topRight'
});

2. 自定义Toast

iziToast.show({
    title: '自定义标题',
    message: '自定义消息内容',
    backgroundColor: '#ff4081',
    theme: 'dark',
    color: '#fff',
    icon: 'icon-star',
    image: 'https://via.placeholder.com/50',
    imageWidth: 50,
    layout: 2,
    balloon: true,
    close: false,
    progressBar: false,
    timeout: false,
    rtl: false,
    animateInside: true
});

🎯 实际应用场景

场景1:按钮点击显示弹窗

<button onclick="showSuccess()">成功操作</button>
<button onclick="showError()">错误操作</button>
<button onclick="showCustom()">自定义弹窗</button>
<button onclick="showQuestion()">确认对话框</button>

<script>
// 成功提示
function showSuccess() {
    iziToast.success({
        title: '操作成功',
        message: '您的数据已保存',
        position: 'topRight',
        timeout: 3000,
        onOpening: function() {
            console.log('弹窗开始显示');
        },
        onOpened: function() {
            console.log('弹窗完全显示');
        },
        onClosing: function() {
            console.log('弹窗开始关闭');
        },
        onClosed: function() {
            console.log('弹窗完全关闭');
        }
    });
}

// 错误提示
function showError() {
    iziToast.error({
        title: '操作失败',
        message: '请检查网络连接',
        position: 'topCenter',
        timeout: 5000,
        progressBarColor: '#ff4444',
        transitionIn: 'bounceInDown',
        transitionOut: 'fadeOutUp'
    });
}

// 自定义弹窗
function showCustom() {
    iziToast.show({
        class: 'custom-toast',
        title: '系统提示',
        message: '您有一条新消息',
        backgroundColor: '#17a2b8',
        theme: 'dark',
        color: '#fff',
        icon: 'fas fa-bell',
        imageWidth: 70,
        maxWidth: 400,
        zindex: 999999,
        layout: 2,
        balloon: true,
        close: true,
        closeOnEscape: true,
        closeOnClick: false,
        position: 'bottomRight',
        targetFirst: false,
        timeout: 8000,
        drag: true,
        pauseOnHover: true,
        resetOnHover: false,
        progressBar: true,
        progressBarColor: '#ffffff',
        progressBarEasing: 'linear',
        overlay: false,
        overlayClose: false,
        overlayColor: 'rgba(0, 0, 0, 0.6)',
        transitionIn: 'fadeInLeft',
        transitionOut: 'fadeOutRight',
        transitionInMobile: 'fadeInUp',
        transitionOutMobile: 'fadeOutDown',
        buttons: [
            ['<button>查看详情</button>', function(instance, toast) {
                alert('查看详情');
                instance.hide({}, toast);
            }],
            ['<button>忽略</button>', function(instance, toast) {
                instance.hide({}, toast);
            }]
        ]
    });
}

// 确认对话框
function showQuestion() {
    iziToast.question({
        timeout: false,
        close: false,
        overlay: true,
        displayMode: 'once',
        zindex: 999,
        title: '确认操作',
        message: '您确定要删除此项吗?',
        position: 'center',
        buttons: [
            ['<button><b>确定</b></button>', function(instance, toast) {
                instance.hide({}, toast);
                
                // 执行删除操作
                iziToast.success({
                    title: '已删除',
                    message: '项目已成功删除',
                    position: 'topRight'
                });
            }, true],
            ['<button>取消</button>', function(instance, toast) {
                instance.hide({}, toast);
            }]
        ],
        onClosing: function(instance, toast, closedBy) {
            console.log('关闭原因:', closedBy);
        }
    });
}
</script>

场景2:页面加载完成后显示

// DOM加载完成后显示
document.addEventListener('DOMContentLoaded', function() {
    iziToast.info({
        title: '欢迎',
        message: '页面加载完成',
        position: 'topRight',
        timeout: 2000
    });
});

// 或使用jQuery
$(document).ready(function() {
    iziToast.show({
        title: '系统通知',
        message: '欢迎访问我们的网站',
        backgroundColor: '#4caf50',
        position: 'topCenter',
        timeout: 3000
    });
});

// 页面完全加载后(包括图片等资源)
window.onload = function() {
    iziToast.success({
        title: '加载完成',
        message: '所有资源已加载完毕',
        position: 'bottomRight',
        timeout: 1500
    });
};

场景3:AJAX请求相关

// AJAX请求开始
function startAjaxRequest() {
    const toastId = iziToast.show({
        title: '处理中',
        message: '正在发送请求...',
        icon: 'fas fa-spinner fa-spin',
        backgroundColor: '#007bff',
        timeout: false,
        close: false,
        progressBar: false,
        position: 'topRight',
        id: 'ajax-toast'
    });
    return toastId;
}

// AJAX请求成功
function ajaxSuccess(response) {
    // 关闭加载提示
    const instance = iziToast;
    instance.hide({}, document.querySelector('.iziToast#ajax-toast'));
    
    iziToast.success({
        title: '请求成功',
        message: response.message || '操作成功',
        position: 'topRight',
        timeout: 3000
    });
}

// AJAX请求失败
function ajaxError(error) {
    // 关闭加载提示
    const instance = iziToast;
    instance.hide({}, document.querySelector('.iziToast#ajax-toast'));
    
    iziToast.error({
        title: '请求失败',
        message: error.message || '网络错误,请重试',
        position: 'topRight',
        timeout: 5000
    });
}

// 完整AJAX示例
function sendData(data) {
    // 显示加载中
    iziToast.show({
        id: 'loading-toast',
        title: '处理中',
        message: '正在提交数据...',
        backgroundColor: '#17a2b8',
        icon: 'fas fa-circle-notch fa-spin',
        timeout: false,
        close: false,
        position: 'topRight'
    });
    
    // 模拟AJAX请求
    fetch('/api/submit', {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
            'Content-Type': 'application/json'
        }
    })
    .then(response => {
        // 关闭加载提示
        iziToast.hide({}, document.querySelector('.iziToast#loading-toast'));
        
        if (!response.ok) {
            throw new Error('网络响应错误');
        }
        return response.json();
    })
    .then(data => {
        iziToast.success({
            title: '提交成功',
            message: '数据已成功保存',
            position: 'topRight',
            timeout: 3000
        });
    })
    .catch(error => {
        // 关闭加载提示
        iziToast.hide({}, document.querySelector('.iziToast#loading-toast'));
        
        iziToast.error({
            title: '提交失败',
            message: error.message,
            position: 'topRight',
            timeout: 5000,
            buttons: [
                ['<button>重试</button>', function(instance, toast) {
                    instance.hide({}, toast);
                    sendData(data); // 重新发送
                }]
            ]
        });
    });
}

场景4:表单验证

// 表单提交验证
function validateForm(form) {
    const errors = [];
    
    // 检查用户名
    if (!form.username.value.trim()) {
        errors.push('用户名不能为空');
    }
    
    // 检查邮箱
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(form.email.value)) {
        errors.push('邮箱格式不正确');
    }
    
    // 检查密码
    if (form.password.value.length < 6) {
        errors.push('密码至少6位');
    }
    
    // 如果有错误,显示错误提示
    if (errors.length > 0) {
        errors.forEach((error, index) => {
            setTimeout(() => {
                iziToast.warning({
                    title: '验证失败',
                    message: error,
                    position: 'topRight',
                    timeout: 4000 + (index * 1000),
                    progressBarColor: '#ff9800',
                    transitionIn: 'fadeInDown'
                });
            }, index * 500);
        });
        return false;
    }
    
    // 验证通过
    iziToast.success({
        title: '验证通过',
        message: '表单验证成功,正在提交...',
        position: 'topRight',
        timeout: 2000
    });
    
    return true;
}

// 实时验证
document.querySelectorAll('input[data-validate]').forEach(input => {
    input.addEventListener('blur', function() {
        const value = this.value.trim();
        const validation = this.dataset.validate;
        
        if (!value && this.hasAttribute('required')) {
            iziToast.error({
                title: '必填项',
                message: `${this.placeholder || '此项'}不能为空`,
                position: 'topRight',
                timeout: 3000
            });
            this.classList.add('invalid');
        }
    });
    
    input.addEventListener('input', function() {
        this.classList.remove('invalid');
    });
});

场景5:事件监听相关

// 键盘事件
document.addEventListener('keydown', function(event) {
    // Ctrl + S 保存
    if (event.ctrlKey && event.key === 's') {
        event.preventDefault();
        iziToast.info({
            title: '保存中',
            message: '正在保存您的更改...',
            position: 'bottomLeft',
            timeout: 1500
        });
    }
    
    // Esc 关闭所有提示
    if (event.key === 'Escape') {
        const toasts = document.querySelectorAll('.iziToast');
        toasts.forEach(toast => {
            toast.style.transform = 'translateY(-100px)';
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        });
    }
});

// 网络状态变化
window.addEventListener('online', function() {
    iziToast.success({
        title: '网络恢复',
        message: '网络连接已恢复',
        position: 'topCenter',
        timeout: 3000,
        backgroundColor: '#4caf50'
    });
});

window.addEventListener('offline', function() {
    iziToast.error({
        title: '网络断开',
        message: '网络连接已断开,部分功能可能受限',
        position: 'topCenter',
        timeout: false, // 不自动关闭
        close: false,
        progressBar: false,
        backgroundColor: '#f44336',
        buttons: [
            ['<button>知道了</button>', function(instance, toast) {
                instance.hide({}, toast);
            }]
        ]
    });
});

// 复制事件
document.addEventListener('copy', function() {
    iziToast.show({
        title: '已复制',
        message: '内容已复制到剪贴板',
        backgroundColor: '#9c27b0',
        position: 'bottomRight',
        timeout: 2000
    });
});

场景6:通知队列管理

class ToastManager {
    constructor() {
        this.queue = [];
        this.isShowing = false;
    }
    
    // 添加通知到队列
    addToast(options) {
        this.queue.push(options);
        if (!this.isShowing) {
            this.showNext();
        }
    }
    
    // 显示下一个通知
    showNext() {
        if (this.queue.length === 0) {
            this.isShowing = false;
            return;
        }
        
        this.isShowing = true;
        const options = this.queue.shift();
        
        // 添加关闭回调,显示下一个
        const originalOnClosed = options.onClosed;
        options.onClosed = (instance, toast, closedBy) => {
            if (originalOnClosed) {
                originalOnClosed(instance, toast, closedBy);
            }
            // 延迟显示下一个
            setTimeout(() => this.showNext(), 500);
        };
        
        iziToast.show(options);
    }
    
    // 清空队列
    clearQueue() {
        this.queue = [];
        this.isShowing = false;
        // 关闭所有当前显示的Toast
        document.querySelectorAll('.iziToast').forEach(toast => {
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        });
    }
}

// 使用示例
const toastManager = new ToastManager();

// 批量添加通知
toastManager.addToast({
    title: '通知1',
    message: '第一条消息',
    position: 'topRight',
    timeout: 3000
});

toastManager.addToast({
    title: '通知2',
    message: '第二条消息',
    position: 'topRight',
    timeout: 3000
});

toastManager.addToast({
    title: '通知3',
    message: '第三条消息',
    position: 'topRight',
    timeout: 3000
});

🎨 高级定制

1. 全局配置

// 设置全局默认配置
iziToast.settings({
    timeout: 5000,
    resetOnHover: true,
    icon: 'material-icons',
    transitionIn: 'flipInX',
    transitionOut: 'flipOutX',
    position: 'topCenter',
    displayMode: 'replace', // 替换上一个相同位置的Toast
    onOpening: function() {
        console.log('toast is opening');
    },
    onClosing: function() {
        console.log('toast is closing');
    }
});

// 不同位置设置不同配置
const positionSettings = {
    topRight: {
        timeout: 3000,
        close: true,
        progressBar: true
    },
    bottomLeft: {
        timeout: 5000,
        close: false,
        progressBar: false
    },
    center: {
        timeout: false,
        overlay: true,
        closeOnEscape: true
    }
};

2. 主题定制

/* 自定义主题 */
.iziToast-custom-theme {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 15px;
    box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}

.iziToast-custom-theme .iziToast-title {
    font-family: 'Arial', sans-serif;
    font-weight: bold;
    font-size: 18px;
}

.iziToast-custom-theme .iziToast-message {
    font-size: 14px;
    line-height: 1.5;
}

/* 使用自定义主题 */
iziToast.show({
    class: 'iziToast-custom-theme',
    title: '自定义主题',
    message: '使用自定义CSS主题',
    position: 'topRight'
});

3. 响应式配置

// 根据屏幕大小调整配置
function responsiveToast(config) {
    const isMobile = window.innerWidth <= 768;
    
    const mobileConfig = {
        position: 'bottomCenter',
        maxWidth: '90%',
        timeout: 2000,
        transitionInMobile: 'fadeInUp',
        transitionOutMobile: 'fadeOutDown',
        closeOnClick: true,
        displayMode: 1
    };
    
    const desktopConfig = {
        position: 'topRight',
        maxWidth: 350,
        timeout: 5000,
        transitionIn: 'fadeInRight',
        transitionOut: 'fadeOutRight',
        closeOnClick: false,
        displayMode: 2
    };
    
    return {
        ...config,
        ...(isMobile ? mobileConfig : desktopConfig)
    };
}

// 使用响应式配置
iziToast.success(responsiveToast({
    title: '操作成功',
    message: '数据已保存'
}));

🔧 实用工具函数

1. 显示加载指示器

function showLoader(message = '加载中...') {
    return iziToast.show({
        id: 'loader',
        title: '',
        message: `<div class="loader-content">${message}</div>`,
        backgroundColor: 'rgba(0,0,0,0.8)',
        theme: 'dark',
        icon: 'fas fa-spinner fa-spin',
        timeout: false,
        close: false,
        progressBar: false,
        position: 'center',
        maxWidth: 200,
        animateInside: false,
        onClosing: function() {
            // 清理操作
        }
    });
}

function hideLoader() {
    iziToast.hide({}, document.querySelector('.iziToast#loader'));
}

2. 倒计时提示

function countdownToast(title, message, seconds = 5) {
    let count = seconds;
    const toast = iziToast.show({
        id: 'countdown',
        title: title,
        message: `${message} (${count}s)`,
        backgroundColor: '#ff9800',
        timeout: false,
        close: false,
        position: 'topRight',
        progressBar: true,
        progressBarColor: '#ff5722'
    });
    
    const interval = setInterval(() => {
        count--;
        if (count <= 0) {
            clearInterval(interval);
            iziToast.hide({}, document.querySelector('.iziToast#countdown'));
        } else {
            const toastElement = document.querySelector('.iziToast#countdown .iziToast-message');
            if (toastElement) {
                toastElement.textContent = `${message} (${count}s)`;
            }
        }
    }, 1000);
    
    return interval;
}

3. 确认删除对话框

function confirmDelete(itemName, callback) {
    return iziToast.question({
        timeout: false,
        overlay: true,
        toastOnce: true,
        id: 'delete-confirm',
        title: '确认删除',
        message: `确定要删除"${itemName}"吗?此操作不可撤销。`,
        position: 'center',
        zindex: 99999,
        buttons: [
            ['<button style="background:#f44336;color:white;padding:8px 16px;border-radius:4px;"><b>删除</b></button>', 
            function(instance, toast) {
                instance.hide({}, toast);
                if (typeof callback === 'function') {
                    callback();
                }
            }, true],
            ['<button style="background:#ccc;padding:8px 16px;border-radius:4px;">取消</button>', 
            function(instance, toast) {
                instance.hide({}, toast);
            }]
        ],
        onOpening: function() {
            console.log('确认对话框打开');
        },
        onClosing: function(instance, toast, closedBy) {
            console.log('对话框关闭,操作:', closedBy);
        }
    });
}

// 使用示例
document.getElementById('delete-btn').addEventListener('click', function() {
    confirmDelete('重要文件', function() {
        iziToast.success({
            title: '已删除',
            message: '文件已成功删除',
            position: 'topRight'
        });
    });
});

📱 移动端优化

// 移动端专用Toast配置
function mobileToast(options) {
    const defaultOptions = {
        position: 'bottomCenter',
        maxWidth: '90vw',
        closeOnClick: true,
        timeout: 2000,
        animateInside: false,
        transitionInMobile: 'fadeInUp',
        transitionOutMobile: 'fadeOutDown',
        displayMode: 1
    };
    
    return iziToast.show({...defaultOptions, ...options});
}

// 触摸事件处理
let touchStartY = 0;
let touchEndY = 0;

document.addEventListener('touchstart', function(e) {
    touchStartY = e.changedTouches[0].screenY;
});

document.addEventListener('touchend', function(e) {
    touchEndY = e.changedTouches[0].screenY;
    const diff = touchStartY - touchEndY;
    
    // 向下滑动关闭Toast
    if (diff > 50) { // 滑动距离超过50px
        const toasts = document.querySelectorAll('.iziToast');
        if (toasts.length > 0) {
            iziToast.hide({}, toasts[toasts.length - 1]);
        }
    }
});

🎮 游戏化提示

// 成就系统提示
function showAchievement(title, description, icon = '🏆') {
    iziToast.show({
        class: 'achievement-toast',
        title: `${icon} ${title}`,
        message: description,
        backgroundColor: 'linear-gradient(135deg, #f6d365 0%, #fda085 100%)',
        theme: 'dark',
        color: '#fff',
        image: 'data:image/svg+xml;base64,...', // 徽章图标
        imageWidth: 60,
        position: 'topLeft',
        timeout: 5000,
        progressBarColor: '#ff9800',
        transitionIn: 'bounceInLeft',
        transitionOut: 'fadeOutLeft',
        onOpened: function() {
            // 播放音效
            const audio = new Audio('achievement.mp3');
            audio.play().catch(e => console.log('音效播放失败'));
        }
    });
}

// 等级提升提示
function levelUpToast(oldLevel, newLevel) {
    iziToast.success({
        title: '🎉 等级提升!',
        message: `从 ${oldLevel} 级升级到 ${newLevel} 级`,
        backgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
        position: 'topCenter',
        timeout: 4000,
        close: false,
        balloon: true,
        progressBar: false,
        transitionIn: 'flipInX',
        onClosed: function() {
            // 显示等级奖励
            showAchievement('等级达成', `恭喜达到 ${newLevel} 级!`);
        }
    });
}

🔍 调试技巧

// Toast调试工具
const toastDebugger = {
    // 显示所有活动Toast信息
    showActiveToasts() {
        const toasts = document.querySelectorAll('.iziToast');
        console.log(`当前活动Toast数量: ${toasts.length}`);
        
        toasts.forEach((toast, index) => {
            const title = toast.querySelector('.iziToast-title')?.textContent || '无标题';
            const message = toast.querySelector('.iziToast-message')?.textContent || '无内容';
            console.log(`Toast ${index + 1}: ${title} - ${message}`);
        });
    },
    
    // 清除所有Toast
    clearAll() {
        document.querySelectorAll('.iziToast').forEach(toast => {
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        });
        console.log('已清除所有Toast');
    },
    
    // 测试各种类型的Toast
    testAllTypes() {
        const types = ['success', 'error', 'warning', 'info', 'question'];
        types.forEach((type, index) => {
            setTimeout(() => {
                iziToast[type]({
                    title: `${type.toUpperCase()} 测试`,
                    message: `这是${type}类型的测试消息`,
                    position: 'topRight',
                    timeout: 2000
                });
            }, index * 500);
        });
    }
};

// 添加到全局作用域便于调试
window.toastDebug = toastDebugger;

📊 性能优化建议

  1. 合理使用timeout:避免长时间显示Toast
  2. 使用displayMode:避免同时显示过多Toast
  3. 批量操作时使用队列:如文件上传进度
  4. 移动端减少动画:关闭animateInside提升性能
  5. 及时清理:使用hide()方法手动关闭不再需要的Toast
// 性能优化示例
function optimizedToast(config) {
    return {
        animateInside: window.innerWidth > 768, // 桌面端开启动画
        displayMode: 'replace', // 替换而不是堆积
        timeout: config.important ? 5000 : 2000, // 重要消息显示更久
        ...config
    };
}

相关参数说明

参数名默认值说明
class''应用于Toast的CSS类,可用作参考
title''Toast的标题文本
titleColor''标题文字颜色
titleSize''标题字体大小
titleLineHeight''标题行高
message''通知的消息内容
messageColor''消息文字颜色
messageSize''消息字体大小
messageLineHeight''消息行高
backgroundColor''Toast的背景颜色
theme''可以是lightdark或自定义CSS类,格式如:".iziToast-theme-name"
color''可以是十六进制颜色值、预定义主题颜色(blueredgreenyellow)或自定义CSS类,格式如:".iziToast-color-name"
icon''图标CSS类(可使用Icomoon、Fontawesome等字体图标库)
iconText''图标文字(适用于Material Icons等字体图标)
iconColor''图标颜色
iconUrlnull要加载的图标文件地址
image''封面图片
imageWidth50封面图片宽度,如100px
maxWidthnullToast的最大宽度,如500px
zindex99999Toast的CSS z-index属性值
layout1可以是12,或自定义布局类,格式如:".iziToast-layout3"
balloonFALSE应用气球样式Toast
closeTRUE显示"x"关闭按钮
closeOnEscapeFALSE允许使用Esc键关闭Toast
closeOnClickFALSE允许通过点击Toast关闭
rtlFALSE从右到左文本方向选项
position'bottomRight'Toast显示位置,可选值:bottomRightbottomLefttopRighttopLefttopCenterbottomCentercenter
target''显示Toast的固定位置
targetFirstTRUE将Toast添加到第一个位置显示
toastOnceFALSE在'onClosed'函数中等待另一个Toast关闭(需要设置ID)
displayMode0等待模式:1'once'等待Toast关闭后打开新Toast,2'replace'替换已打开的Toast
timeout5000自动关闭的毫秒数,设为false禁用自动关闭
dragTRUE启用拖拽关闭功能
pauseOnHoverTRUE鼠标悬停时暂停超时计时
resetOnHoverFALSE鼠标悬停时重置超时计时
progressBarTRUE启用超时进度条
progressBarColor''进度条颜色
progressBarEasing'linear'进度条动画缓动效果
overlayFALSE启用遮罩层
overlayCloseFALSE允许点击遮罩层关闭Toast
overlayColor'rgba(0, 0, 0, 0.6)'遮罩层背景颜色
animateInsideTRUE启用Toast内元素的动画效果
buttons{}按钮数组配置
inputs{}输入框数组配置
transitionIn'fadeInUp'默认打开动画,可选值:bounceInLeftbounceInRightbounceInUpbounceInDownfadeInfadeInDownfadeInUpfadeInLeftfadeInRightflipInX
transitionOut'fadeOut'默认关闭动画,可选值:fadeOutfadeOutUpfadeOutDownfadeOutLeftfadeOutRightflipOutX
transitionInMobile'fadeInUp'移动端默认打开动画
transitionOutMobile'fadeOutDown'移动端默认关闭动画
onOpeningfunction() {}Toast打开时触发的回调函数
onOpenedfunction() {}Toast完全打开后触发的回调函数
onClosingfunction() {}Toast关闭时触发的回调函数
onClosedfunction() {}Toast完全关闭后触发的回调函数