Toast组件模块

可被其他油猴脚本引用的toast提示组件

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/548898/1657505/Toast%E7%BB%84%E4%BB%B6%E6%A8%A1%E5%9D%97.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Toast组件模块(深色系)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  深色系提示的toast组件,适合夜间模式或深色主题页面
// @author       You
// @match        *
// @grant        none
// @noframes
// ==/UserScript==

(function(window) {
    'use strict';

    // 防止重复加载
    if (window.MonkeyToast) {
        return;
    }

    const TOAST_CONFIG = {
        maxCount: 5,          // 最大同时显示数量
        baseOffset: 20,       // 基础偏移量(px)
        spacing: 10,          // 每个Toast之间的间距
        defaultDuration: 3000,// 默认显示时长(ms)
        animationDuration: 300// 动画过渡时间(ms)
    };

    // 深色系颜色配置
    const COLORS = {
        default: {
            background: 'rgba(45, 45, 45, 0.8)',   // 深灰黑色背景(主色调,沉稳不刺眼)
            text: '#f0f0f0',          // 浅灰文字(确保高对比度)
            border: '1px solid rgba(68, 68, 68, 0.9)',// 深灰边框(增强轮廓)
            hoverBackground: '#1a1a1a',// 悬停时更深的背景
            hoverText: '#ffffff',     // 悬停时文字更亮
            hoverOpacity: 1           // 完全不透明,确保可读性
        }
    };

    // 存储活跃的Toast (message -> {element, timer})
    const activeToasts = new Map();
    // 等待显示的Toast队列
    const toastQueue = [];

    /**
     * 显示Toast提示
     * @param {string} message - 提示内容
     * @param {number} duration - 显示时长(ms),可选
     * @param {Object} options - 额外选项,可选
     * @param {string} options.backgroundColor - 背景颜色
     * @param {string} options.color - 文字颜色
     * @param {string} options.border - 边框样式
     * @param {string} options.hoverBackground - 悬停背景色
     * @param {string} options.hoverText - 悬停文字颜色
     * @param {number} options.hoverOpacity - 悬停透明度
     */
    function showToast(message, duration = TOAST_CONFIG.defaultDuration, options = {}) {
        // 检查是否已达到最大显示数量
        if (activeToasts.size >= TOAST_CONFIG.maxCount) {
            // 加入队列等待
            toastQueue.push({ message, duration, options });
            return;
        }

        // 检查是否为重复消息
        if (activeToasts.has(message)) {
            return;
        }

        // 合并默认样式和自定义样式
        const bgColor = options.backgroundColor || COLORS.default.background;
        const textColor = options.color || COLORS.default.text;
        const border = options.border || COLORS.default.border;
        const hoverBgColor = options.hoverBackground || COLORS.default.hoverBackground;
        const hoverTextColor = options.hoverText || COLORS.default.hoverText;
        const hoverOpacity = options.hoverOpacity ?? COLORS.default.hoverOpacity;

        // 创建Toast元素
        const toast = document.createElement('div');
        toast.className = 'tm-toast';
        
        toast.style.cssText = `
            position: fixed;
            top: ${TOAST_CONFIG.baseOffset}px;
            left: 50%;
            transform: translateX(-50%);
            background: ${bgColor};
            color: ${textColor};
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 999999;
            opacity: 1;
            transition: all ${TOAST_CONFIG.animationDuration}ms ease;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            pointer-events: auto;
            max-width: 80%;
            word-wrap: break-word;
            font-size: 14px;
            line-height: 1.4;
        `;
        toast.textContent = message;

        // 添加到文档
        const container = document.body || document.documentElement;
        container.appendChild(toast);

        // 入场动画
        setTimeout(() => {
            toast.style.transform = 'translateX(-50%) translateY(0)';
        }, 10);

        // 记录到活跃列表
        const timer = setTimeout(() => {
            removeToast(message);
        }, duration);
        activeToasts.set(message, { 
            element: toast, 
            timer, 
            options,
            originalBg: bgColor,
            originalText: textColor,
            hoverBg: hoverBgColor,
            hoverText: hoverTextColor,
            hoverOpacity: hoverOpacity
        });

        // 更新所有Toast位置
        updateToastPositions();

        // 鼠标悬停暂停计时并改变样式
        toast.addEventListener('mouseenter', () => {
            const toastData = activeToasts.get(message);
            if (toastData && toastData.timer) {
                clearTimeout(toastData.timer);
                toastData.timer = null;
                // 应用hover样式
                toast.style.background = toastData.hoverBg;
                toast.style.color = toastData.hoverText;
                toast.style.opacity = toastData.hoverOpacity;
            }
        });

        // 鼠标离开恢复计时和样式
        toast.addEventListener('mouseleave', () => {
            const toastData = activeToasts.get(message);
            if (toastData && !toastData.timer) {
                toastData.timer = setTimeout(() => {
                    removeToast(message);
                }, duration);
                // 恢复原始样式
                toast.style.background = toastData.originalBg;
                toast.style.color = toastData.originalText;
                toast.style.opacity = 1;
            }
        });
    }

    /**
     * 移除指定Toast
     * @param {string} message - 要移除的提示内容
     */
    function removeToast(message) {
        const toastData = activeToasts.get(message);
        if (!toastData) return;

        const { element, timer } = toastData;
        if (timer) clearTimeout(timer);

        // 淡出动画
        element.style.opacity = 0;
        element.style.transform = 'translateX(-50%) translateY(-10px)';

        // 动画结束后移除元素
        setTimeout(() => {
            try {
                element.remove();
            } catch (e) { /* 忽略已移除的情况 */ }

            activeToasts.delete(message);

            // 更新位置
            updateToastPositions();

            // 检查队列并显示下一个
            if (toastQueue.length > 0) {
                const nextToast = toastQueue.shift();
                showToast(nextToast.message, nextToast.duration, nextToast.options);
            }
        }, TOAST_CONFIG.animationDuration);
    }

    /**
     * 更新所有活跃Toast的位置,实现自动堆叠
     */
    function updateToastPositions() {
        let currentOffset = TOAST_CONFIG.baseOffset;

        // 按添加顺序遍历并更新位置
        Array.from(activeToasts.values()).forEach(({ element }) => {
            // 设置新位置
            element.style.top = `${currentOffset}px`;

            // 计算下一个位置(当前元素高度 + 间距)
            currentOffset += element.offsetHeight + TOAST_CONFIG.spacing;
        });
    }

    /**
     * 清除所有toast
     */
    function clearAllToasts() {
        // 清除活跃的toast
        Array.from(activeToasts.keys()).forEach(message => {
            removeToast(message);
        });
        // 清空队列
        toastQueue.length = 0;
    }

    /**
     * 配置toast全局参数
     * @param {Object} config - 配置对象
     */
    function configToast(config) {
        Object.assign(TOAST_CONFIG, config);
    }

    /**
     * 配置全局颜色
     * @param {Object} colorConfig - 颜色配置对象
     */
    function configColors(colorConfig) {
        Object.assign(COLORS.default, colorConfig);
    }

    // 暴露公共API
    window.MonkeyToast = {
        show: showToast,
        remove: removeToast,
        clearAll: clearAllToasts,
        config: configToast,
        configColors: configColors
    };

})(window);