Skip to content
鼓励作者:欢迎打赏犒劳

vue开发油猴插件

vue开发油猴插件

https://github.com/ruanyf/weekly/issues/3273

vite-plugin-monkey : https://github.com/lisonge/vite-plugin-monkey

vue油猴插件脚手架:https://gitee.com/lzh1995/vue-monkey-demo

shell
pnpm create monkey

很简单,创建项目之后,启动项目,访问url,会跳转到油猴插件安装页面,点击安装即可调试。

非常的简单,支持热部署。

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化版文本快捷操作工具</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            padding: 20px;
            color: #333;
        }

        .container {
            max-width: 1200px;
            width: 100%;
            margin: 0 auto;
        }

        header {
            text-align: center;
            padding: 30px 0;
            margin-bottom: 40px;
        }

        h1 {
            font-size: 2.5rem;
            color: #2c3e50;
            margin-bottom: 15px;
            text-shadow: 1px 1px 3px rgba(0,0,0,0.1);
        }

        .subtitle {
            font-size: 1.1rem;
            color: #7f8c8d;
            max-width: 700px;
            margin: 0 auto;
            line-height: 1.6;
        }

        .instructions {
            background: rgba(255, 255, 255, 0.9);
            border-radius: 12px;
            padding: 25px;
            margin-bottom: 40px;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
            border-left: 5px solid #3498db;
        }

        .instructions h2 {
            color: #3498db;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 1.4rem;
        }

        .instructions ul {
            padding-left: 25px;
            margin: 15px 0;
        }

        .instructions li {
            margin-bottom: 10px;
            line-height: 1.5;
            font-size: 0.95rem;
        }

        .instructions .highlight {
            background-color: #f1c40f;
            padding: 2px 8px;
            border-radius: 4px;
            font-weight: bold;
            color: #2c3e50;
        }

        .content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 25px;
        }

        @media (max-width: 768px) {
            .content {
                grid-template-columns: 1fr;
            }
        }

        .demo-area, .sample-text {
            background: white;
            border-radius: 12px;
            padding: 25px;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
        }

        h2 {
            color: #2c3e50;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #ecf0f1;
            font-size: 1.3rem;
        }

        .sample-text p {
            line-height: 1.7;
            margin-bottom: 18px;
            font-size: 1.05rem;
            color: #34495e;
        }

        .text-block {
            padding: 18px;
            border-radius: 8px;
            margin-bottom: 18px;
            transition: all 0.3s ease;
            cursor: default;
            background-color: #f8f9fa;
            border: 1px solid #e9ecef;
        }

        .text-block:hover {
            background-color: #edf2f7;
        }

        /* 选中元素高亮样式 */
        .highlighted {
            background-color: #e3f2fd !important;
            border: 2px solid #3498db !important;
            box-shadow: 0 0 15px rgba(52, 152, 219, 0.4);
            transform: scale(1.01);
            animation: pulse 1.5s infinite;
        }

        @keyframes pulse {
            0% { box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.4); }
            70% { box-shadow: 0 0 0 10px rgba(52, 152, 219, 0); }
            100% { box-shadow: 0 0 0 0 rgba(52, 152, 219, 0); }
        }

        .text-block h3 {
            color: #3498db;
            margin-bottom: 8px;
            font-size: 1.15rem;
        }

        .notification {
            position: fixed;
            top: 30px;
            right: 30px;
            background: #2ecc71;
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            display: none;
            z-index: 2000;
            animation: slideIn 0.3s ease, fadeOut 0.5s ease 2s forwards;
            font-size: 0.9rem;
        }

        @keyframes slideIn {
            from {
                transform: translateX(100%);
                opacity: 0;
            }
            to {
                transform: translateX(0);
                opacity: 1;
            }
        }

        @keyframes fadeOut {
            from {
                opacity: 1;
            }
            to {
                opacity: 0;
            }
        }

        footer {
            text-align: center;
            margin-top: 40px;
            padding: 15px;
            color: #7f8c8d;
            font-size: 0.85rem;
        }

        .keyboard-shortcut {
            display: inline-flex;
            align-items: center;
            gap: 5px;
            margin: 0 5px;
        }

        kbd {
            background: linear-gradient(to bottom, #f8f8f8, #e7e7e7);
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 0 1px 0 rgba(0,0,0,0.2), 0 0 0 2px #fff inset;
            color: #333;
            display: inline-block;
            font-family: monospace;
            font-size: 0.85em;
            line-height: 1.4;
            margin: 0 0.1em;
            padding: 0.1em 0.6em;
            text-shadow: 0 1px 0 #fff;
        }

        .status-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: #e74c3c;
            margin-right: 8px;
        }


        .position-indicator {
            position: fixed;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: #e74c3c;
            z-index: 3000;
            pointer-events: none;
            transform: translate(-50%, -50%);
            display: none;
        }


        /* 右键菜单样式 */
        .tooltip {
            display: none;
            position: absolute;
            width: 240px;
            background-color: #f9f9f9;
            border: 1px solid #c0c0c0;
            box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
            z-index: 1000;
            padding: 2px;
            font-size: 13px;
        }

        .tooltip-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 5px 10px;
            background-color: #f0f0f0;
            border-bottom: 1px solid #e0e0e0;
            font-weight: bold;
            color: #333;
        }

        .close-btn {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 12px;
            color: #666;
            padding: 2px 5px;
        }

        .close-btn:hover {
            background-color: #e0e0e0;
            color: #000;
        }

        .tooltip-options {
            padding: 4px 0;
        }

        .tooltip-option {
            display: flex;
            align-items: center;
            padding: 5px 10px;
            cursor: default;
            position: relative;
        }

        .tooltip-option:hover {
            background-color: #1e90ff;
            color: white;
        }

        .tooltip-option i {
            width: 20px;
            margin-right: 8px;
            font-size: 12px;
            text-align: center;
        }

        .option-text h4 {
            font-weight: normal;
            font-size: 13px;
        }

        .tooltip-divider {
            height: 1px;
            background-color: #e0e0e0;
            margin: 4px 0;
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1><i class="fas fa-mouse-pointer"></i> 优化版文本快捷操作工具</h1>
        <p class="subtitle">悬停在文本上并按下Alt+C组合键,在鼠标右侧显示固定操作面板</p>
    </header>

    <div class="instructions">
        <h2><i class="fas fa-info-circle"></i> 使用说明</h2>
        <ul>
            <li>将鼠标悬停在页面上的任意文本内容上</li>
            <li>按下键盘组合键 <span class="highlight">Alt + C</span></li>
            <li>在鼠标右侧将会显示一个<span class="highlight">固定位置</span>的操作面板</li>
            <li>选择 <span class="highlight">复制当前文本</span> 复制鼠标所在位置的文本</li>
            <li>选择 <span class="highlight">复制同级文本</span> 复制同一段落或区块中的所有文本</li>
            <li>操作成功后会显示通知提示</li>
            <li>当前选中元素会有<span class="highlight">醒目标识</span></li>
        </ul>
        <p>提示:此工具非常适合快速收集和整理网页中的文本信息!</p>
    </div>

    <div class="content">
        <div class="demo-area">
            <h2><i class="fas fa-laptop-code"></i> 功能演示区</h2>
            <p>悬停在文本上并按Alt+C:</p>

            <div class="text-block">
                <h3>前端开发技术</h3>
                <p>前端开发是创建Web页面或app等前端界面呈现给用户的过程,通过HTML,CSS及JavaScript以及衍生出来的各种技术、框架、解决方案,来实现互联网产品的用户界面交互。</p>
            </div>

            <div class="text-block">
                <h3>JavaScript的重要性</h3>
                <p>JavaScript是一种轻量级的解释型编程语言,它基于原型编程、多范式的动态脚本语言,支持面向对象、命令式和声明式风格。</p>
            </div>

            <div class="text-block">
                <h3>响应式设计</h3>
                <p>响应式网页设计是一种网页设计的技术做法,该设计可使网站在多种浏览设备上阅读和导航,同时减少缩放、平移和滚动。</p>
            </div>
        </div>

        <div class="sample-text">
            <h2><i class="fas fa-align-left"></i> 示例文本</h2>

            <div class="text-block">
                <h3>用户体验设计</h3>
                <p>用户体验设计是以用户为中心的一种设计手段,以用户需求为目标而进行的设计。设计过程注重以用户为中心,用户体验的概念从开发的最早期就开始进入整个流程,并贯穿始终。</p>
            </div>

            <div class="text-block">
                <h3>CSS框架</h3>
                <p>CSS框架是一系列CSS文件的集合,包含了基本的元素重置,页面排版、网格布局、表单样式、通用规则等代码块。使用CSS框架可以简化前端开发工作,提高工作效率。</p>
            </div>

            <div class="text-block">
                <h3>现代Web开发</h3>
                <p>现代Web开发已经从前端、后端的简单划分发展为全栈开发。开发者需要掌握多种技术,包括HTML5、CSS3、JavaScript、Node.js、数据库、API设计等,以构建完整的Web应用。</p>
            </div>
        </div>
    </div>

    <footer>
        <p>优化版文本快捷操作工具 &copy; 2023 | 使用Alt+C组合键提升您的文本处理效率</p>
    </footer>
</div>

<!-- 右键菜单 -->
<div class="tooltip" id="textTooltip">
    <div class="tooltip-header">
        操作菜单
        <button class="close-btn" id="closeTooltip">
            <i class="fas fa-times"></i>
        </button>
    </div>
    <div class="tooltip-options">
        <div class="tooltip-option" id="copyCurrentSelectText">
            <i class="fas fa-copy"></i>
            <div class="option-text">
                <h4>复制当前选中文本</h4>
            </div>
        </div>
        <div class="tooltip-option" id="copyCurrentEle">
            <i class="fas fa-copy"></i>
            <div class="option-text">
                <h4>复制当前元素文本</h4>
            </div>
        </div>
        <div class="tooltip-divider"></div>
        <div class="tooltip-option" id="copySibling">
            <i class="fas fa-copy"></i>
            <div class="option-text">
                <h4>复制当前元素同级文本</h4>
            </div>
        </div>
        <div class="tooltip-divider"></div>
    </div>
</div>
<div class="notification" id="notification">
    <i class="fas fa-check-circle"></i> 文本已成功复制到剪贴板!
</div>

<div class="position-indicator" id="positionIndicator"></div>

<script>
    // 获取工具提示元素
    const tooltip = document.getElementById('textTooltip');
    const positionIndicator = document.getElementById('positionIndicator');
    let currentSelectText = '';
    //记录当前悬停的元素
    let currentElement = null;
    let currentPos = {}
    let tooltipPosition = { x: 0, y: 0 };

    //点击空白处隐藏菜单
    document.addEventListener('click', (e) => {
        let curEle = e.target
        if (tooltip.contains(curEle)) {
            //忽略
        }else{
            tooltip.style.display = 'none';
            removeHighlights();
        }
    })

    // 监听鼠标移动事件
    document.addEventListener('mousemove', (e) => {
        // 记录当前悬停的元素

        let curEle = e.target
        if (tooltip.contains(curEle)) {
            //忽略
        }else{
            if (tooltip.style.display == 'none' || tooltip.style.display == ''){
                currentElement = curEle;
            }
        }
        // 更新位置指示器(调试用)
        positionIndicator.style.left = e.clientX + 'px';
        positionIndicator.style.top = e.clientY + 'px';
        currentPos = {
            x : e.clientX ,
            y : e.clientY ,
        }
        // console.log('更新位置指示器:' + e.pageX + 'px',e.pageY + 'px')
    });

    // 监听键盘事件
    document.addEventListener('keydown', (e) => {
        // 检查是否按下了 Alt + C
        if (e.altKey && e.key === 'c') {
            // 阻止默认行为
            e.preventDefault();

            //当前鼠标选中的文本
            currentSelectText = window.getSelection().toString();
            // 确保当前元素有效
            if (!currentElement) return;

            // 记录当前位置
            tooltipPosition = {
                x: currentPos.x + 15, // 鼠标右侧15px
                y: currentPos.y - 30   // 鼠标上方30px
            };
            // 边界检测 - 防止弹窗超出屏幕右侧
            const windowWidth = window.innerWidth;
            const tooltipWidth = tooltip.offsetWidth;
            if (tooltipPosition.x + tooltipWidth > windowWidth) {
                tooltipPosition.x = windowWidth - tooltipWidth - 10;
            }

            // 边界检测 - 防止弹窗超出屏幕顶部
            if (tooltipPosition.y < 10) {
                tooltipPosition.y = 10;
            }

            // 设置工具提示位置
            tooltip.style.left = tooltipPosition.x + 'px';
            tooltip.style.top = tooltipPosition.y + 'px';

            // 显示工具提示
            tooltip.style.display = 'block';

            // console.log('设置工具提示位置:' + tooltipPosition.x + 'px',tooltipPosition.y + 'px')

            // 添加高亮效果
            removeHighlights();
            highlightCurrentElement();
        }
    });

    // 关闭工具提示
    document.getElementById('closeTooltip').addEventListener('click', () => {
        tooltip.style.display = 'none';
        removeHighlights();
    });

    // 高亮当前元素
    function highlightCurrentElement() {
        // 向上查找最近的文本块容器
        let targetElement = currentElement;
        targetElement.classList.add('highlighted');
    }

    // 移除高亮效果
    function removeHighlights() {
        document.querySelectorAll('.highlighted').forEach(el => {
            el.classList.remove('highlighted');
        });
    }

    // 复制当前选中文本
    document.getElementById('copyCurrentSelectText').addEventListener('click', () => {
        const text = currentSelectText;
        copyToClipboard(text);
        tooltip.style.display = 'none';
        removeHighlights();
    });
    // 复制当前元素文本
    document.getElementById('copyCurrentEle').addEventListener('click', () => {
        const text = currentElement.innerText || currentElement.textContent;
        copyToClipboard(text);
        tooltip.style.display = 'none';
        removeHighlights();
    });
    // 复制当前元素同级文本
    document.getElementById('copySibling').addEventListener('click', () => {
        let xpath = getXPath(currentElement)
        if (xpath){
            let xpath2 = xpath.replace(/\[\d+\]/g, '');
            let nodes = xpathQuery(xpath2)
            let text = ''
            nodes.forEach(node => {
                text+=node.innerText + "\r\n"
            })
            copyToClipboard(text);
            tooltip.style.display = 'none';
            removeHighlights();
        }
    });
    // 复制文本到剪贴板
    function copyToClipboard(text = '') {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);

        showNotification('当前文本已复制到剪贴板;长度:' + text.length);
    }
    // 显示通知
    function showNotification(message) {
        notification.innerHTML = `<i class="fas fa-check-circle"></i> ${message}`;
        notification.style.display = 'block';
        notification.style.backgroundColor = '#2ecc71';

        // 3秒后隐藏通知
        setTimeout(() => {
            notification.style.display = 'none';
        }, 3000);
    }

    // 获取元素的 XPath 路径
    function getXPath(element) {
        if (element.id !== "") { // 如果元素具有 ID 属性
            return '//*[@id="' + element.id + '"]'; // 返回格式为 '//*[@id="elementId"]' 的 XPath 路径
        }
        if (element === document.body) { // 如果当前元素是 document.body
            return "/html/body"; // 返回 '/html/body' 的 XPath 路径
        }

        var index = 1;
        const childNodes = element.parentNode ? element.parentNode.childNodes : []; // 获取当前元素的父节点的子节点列表
        var siblings = childNodes;

        for (var i = 0; i < siblings.length; i++) {
            var sibling = siblings[i];
            if (sibling === element) { // 遍历到当前元素
                // 递归调用,获取父节点的 XPath 路径,然后拼接当前元素的标签名和索引
                return (
                    getXPath(element.parentNode) +
                    "/" +
                    element.tagName.toLowerCase() +
                    "[" + index + "]"
                );
            }
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { // 遍历到具有相同标签名的元素
                index++; // 增加索引值
            }
        }
    }

    /**
     * 执行XPath查询
     * @param {string} xpath - XPath表达式
     * @param {Node} [context=document] - 查询上下文节点
     * @param {boolean} [singleNode=false] - 是否只返回单个节点
     * @returns {Array|Node|null} 匹配的节点数组/单个节点/null
     */
    function xpathQuery(xpath, context = document, singleNode = false) {
        const resultType = singleNode
            ? XPathResult.FIRST_ORDERED_NODE_TYPE
            : XPathResult.ORDERED_NODE_ITERATOR_TYPE;

        try {
            const result = document.evaluate(
                xpath,
                context,
                null,
                resultType,
                null
            );

            if (singleNode) {
                return result.singleNodeValue;
            }

            const nodes = [];
            let node;
            while ((node = result.iterateNext())) {
                nodes.push(node);
            }
            return nodes;
        } catch (e) {
            console.error("XPath查询错误:", e);
            return singleNode ? null : [];
        }
    }
</script>
</body>
</html>

如有转载或 CV 的请标注本站原文地址