
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>优化版文本快捷操作工具 © 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>