// ==UserScript==
// @name 移动端开发者工具
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 移动浏览器开发者调试工具
// @author Your name
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 样式定义
const styles = `
.devtools-container {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 50%;
background: #fff;
z-index: 10000;
border-bottom: 1px solid #ccc;
display: none;
flex-direction: column;
font-family: monospace;
transition: transform 0.3s ease;
transform: translateY(-100%);
}
.devtools-container.expanded {
transform: translateY(0);
}
.devtools-header {
display: flex;
padding: 5px;
border-bottom: 1px solid #ccc;
background: #f5f5f5;
}
.devtools-tab {
padding: 5px 10px;
margin-right: 5px;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 14px;
}
.devtools-tab.active {
background: #fff;
border-bottom: none;
}
.devtools-content {
flex: 1;
overflow: auto;
padding: 10px;
}
.devtools-panel {
display: none;
height: 100%;
}
.devtools-panel.active {
display: block;
}
.toggle-devtools {
position: fixed;
top: 10px;
right: 10px;
z-index: 10001;
padding: 5px 10px;
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.expand-collapse {
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
background: #4CAF50;
color: white;
border: none;
border-radius: 0 0 5px 5px;
padding: 2px 10px;
cursor: pointer;
z-index: 10001;
}
.element-highlight {
position: absolute;
background: rgba(137, 196, 244, 0.3);
border: 1px solid #89C4F4;
pointer-events: none;
z-index: 9999;
}
.console-input {
width: 100%;
padding: 5px;
border: 1px solid #ccc;
}
.console-output {
margin: 5px 0;
padding: 5px;
background: #f5f5f5;
border-radius: 3px;
}
.network-item {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
margin-bottom: 10px;
}
.network-details {
display: none;
padding: 10px;
background: #f9f9f9;
margin-top: 5px;
border-radius: 4px;
}
.network-item.expanded .network-details {
display: block;
}
.network-summary {
padding: 8px;
background: #f5f5f5;
border-radius: 4px;
margin-bottom: 5px;
}
.network-summary div {
margin: 2px 0;
}
.network-details pre {
white-space: pre-wrap;
word-wrap: break-word;
background: #fff;
padding: 8px;
border-radius: 4px;
border: 1px solid #eee;
margin: 5px 0;
max-height: 300px;
overflow-y: auto;
}
.network-details h4 {
margin: 10px 0 5px 0;
color: #333;
}
.network-item.error {
background: #ffe6e6;
}
.network-details-section {
margin: 10px 0;
}
.network-details-content {
max-height: 200px;
overflow-y: auto;
background: #fff;
border: 1px solid #eee;
border-radius: 4px;
}
`;
// 添加样式
function addStyle(css) {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
}
// 创建开发者工具UI
function createDevToolsUI() {
const container = document.createElement('div');
container.className = 'devtools-container';
container.innerHTML = `
<div class="devtools-header">
<div class="devtools-tab" data-panel="elements">元素</div>
<div class="devtools-tab" data-panel="console">控制台</div>
<div class="devtools-tab active" data-panel="network">网络</div>
<div class="devtools-tab" data-panel="styles">样式</div>
</div>
<div class="devtools-content">
<div class="devtools-panel elements-panel"></div>
<div class="devtools-panel console-panel">
<div class="console-output-container"></div>
<input type="text" class="console-input" placeholder="输入JavaScript代码">
</div>
<div class="devtools-panel network-panel active"></div>
<div class="devtools-panel styles-panel"></div>
</div>
<button class="expand-collapse">收起</button>
`;
const toggleButton = document.createElement('button');
toggleButton.className = 'toggle-devtools';
toggleButton.textContent = '开发工具';
document.body.appendChild(container);
document.body.appendChild(toggleButton);
return {container, toggleButton};
}
// 元素检查器功能
function initElementInspector(elementsPanel) {
let highlight = document.createElement('div');
highlight.className = 'element-highlight';
document.body.appendChild(highlight);
function updateHighlight(element) {
const rect = element.getBoundingClientRect();
highlight.style.top = rect.top + window.scrollY + 'px';
highlight.style.left = rect.left + window.scrollX + 'px';
highlight.style.width = rect.width + 'px';
highlight.style.height = rect.height + 'px';
}
function showElementInfo(element) {
elementsPanel.innerHTML = `
<h3>选中的元素:</h3>
<pre>${element.outerHTML}</pre>
<h3>计算样式:</h3>
<pre>${JSON.stringify(window.getComputedStyle(element), null, 2)}</pre>
`;
}
document.addEventListener('mousemove', (e) => {
if (!elementsPanel.classList.contains('active')) return;
const element = document.elementFromPoint(e.clientX, e.clientY);
if (element && !element.closest('.devtools-container')) {
updateHighlight(element);
highlight.style.display = 'block';
}
});
document.addEventListener('click', (e) => {
if (!elementsPanel.classList.contains('active')) return;
const element = document.elementFromPoint(e.clientX, e.clientY);
if (element && !element.closest('.devtools-container')) {
e.preventDefault();
showElementInfo(element);
}
});
}
// Console功能
function initConsole(consolePanel) {
const input = consolePanel.querySelector('.console-input');
const outputContainer = consolePanel.querySelector('.console-output-container');
function log(message, type = 'log') {
const output = document.createElement('div');
output.className = `console-output ${type}`;
output.textContent = message;
outputContainer.appendChild(output);
outputContainer.scrollTop = outputContainer.scrollHeight;
}
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn
};
console.log = function(...args) {
log(args.join(' '));
originalConsole.log.apply(console, args);
};
console.error = function(...args) {
log(args.join(' '), 'error');
originalConsole.error.apply(console, args);
};
console.warn = function(...args) {
log(args.join(' '), 'warn');
originalConsole.warn.apply(console, args);
};
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
try {
const result = eval(input.value);
log(`> ${input.value}`);
log(result);
} catch (error) {
log(error.message, 'error');
}
input.value = '';
}
});
}
// 网络请求监控
function initNetworkMonitor(networkPanel) {
const originalFetch = window.fetch;
const originalXHR = window.XMLHttpRequest.prototype.open;
function formatHeaders(headers) {
if (headers instanceof Headers) {
return Array.from(headers.entries())
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
}
return JSON.stringify(headers, null, 2);
}
window.fetch = async function(...args) {
const startTime = Date.now();
const request = args[0];
const options = args[1] || {};
try {
const response = await originalFetch.apply(this, args);
const duration = Date.now() - startTime;
const clone = response.clone();
const item = document.createElement('div');
item.className = 'network-item';
const responseText = await clone.text();
const responseHeaders = formatHeaders(response.headers);
const requestHeaders = formatHeaders(options.headers);
let formattedResponse = responseText;
try {
if (response.headers.get('content-type')?.includes('application/json')) {
formattedResponse = JSON.stringify(JSON.parse(responseText), null, 2);
}
} catch (e) {
formattedResponse = responseText;
}
item.innerHTML = `
<div class="network-summary">
<div><strong>URL:</strong> ${request instanceof Request ? request.url : request}</div>
<div><strong>方法:</strong> ${options.method || 'GET'}</div>
<div><strong>状态:</strong> ${response.status} ${response.statusText}</div>
<div><strong>耗时:</strong> ${duration}ms</div>
</div>
<div class="network-details">
<div class="network-details-section">
<h4>请求头:</h4>
<div class="network-details-content">
<pre>${requestHeaders}</pre>
</div>
</div>
<div class="network-details-section">
<h4>响应头:</h4>
<div class="network-details-content">
<pre>${responseHeaders}</pre>
</div>
</div>
<div class="network-details-section">
<h4>响应内容:</h4>
<div class="network-details-content">
<pre>${formattedResponse}</pre>
</div>
</div>
</div>
`;
item.querySelector('.network-summary').addEventListener('click', () => {
item.classList.toggle('expanded');
});
networkPanel.insertBefore(item, networkPanel.firstChild);
return response;
} catch (error) {
const item = document.createElement('div');
item.className = 'network-item error';
item.innerHTML = `
<div>URL: ${request instanceof Request ? request.url : request}</div>
<div>错误: ${error.message}</div>
`;
networkPanel.insertBefore(item, networkPanel.firstChild);
throw error;
}
};
window.XMLHttpRequest.prototype.open = function(...args) {
const startTime = Date.now();
this.addEventListener('load', function() {
const duration = Date.now() - startTime;
const item = document.createElement('div');
item.className = 'network-item';
const responseHeaders = this.getAllResponseHeaders();
let responseText = this.responseText;
try {
if (this.getResponseHeader('content-type')?.includes('application/json')) {
responseText = JSON.stringify(JSON.parse(responseText), null, 2);
}
} catch (e) {}
item.innerHTML = `
<div class="network-summary">
<div><strong>URL:</strong> ${args[1]}</div>
<div><strong>方法:</strong> ${args[0]}</div>
<div><strong>状态:</strong> ${this.status} ${this.statusText}</div>
<div><strong>耗时:</strong> ${duration}ms</div>
</div>
<div class="network-details">
<div class="network-details-section">
<h4>响应头:</h4>
<div class="network-details-content">
<pre>${responseHeaders}</pre>
</div>
</div>
<div class="network-details-section">
<h4>响应内容:</h4>
<div class="network-details-content">
<pre>${responseText}</pre>
</div>
</div>
</div>
`;
item.querySelector('.network-summary').addEventListener('click', () => {
item.classList.toggle('expanded');
});
networkPanel.insertBefore(item, networkPanel.firstChild);
});
return originalXHR.apply(this, args);
};
}
// 样式查看/修改功能
function initStylesPanel(stylesPanel) {
function updateStyles() {
const styleSheets = Array.from(document.styleSheets);
let html = '<h3>页面样式:</h3>';
styleSheets.forEach((sheet, index) => {
try {
const rules = Array.from(sheet.cssRules);
html += `<details>
<summary>样式表 ${index + 1}</summary>
<pre>${rules.map(rule => rule.cssText).join('\n')}</pre>
</details>`;
} catch (e) {
html += `<p>无法访问样式表 ${index + 1} (CORS限制)</p>`;
}
});
stylesPanel.innerHTML = html;
}
updateStyles();
}
// 初始化开发者工具
function initDevTools() {
addStyle(styles);
const {container, toggleButton} = createDevToolsUI();
const elementsPanel = container.querySelector('.elements-panel');
const consolePanel = container.querySelector('.console-panel');
const networkPanel = container.querySelector('.network-panel');
const stylesPanel = container.querySelector('.styles-panel');
const expandCollapseBtn = container.querySelector('.expand-collapse');
// 初始化各个面板功能
initElementInspector(elementsPanel);
initConsole(consolePanel);
initNetworkMonitor(networkPanel);
initStylesPanel(stylesPanel);
// 标签切换
container.querySelector('.devtools-header').addEventListener('click', (e) => {
if (e.target.classList.contains('devtools-tab')) {
const panelName = e.target.dataset.panel;
container.querySelectorAll('.devtools-tab').forEach(tab => tab.classList.remove('active'));
container.querySelectorAll('.devtools-panel').forEach(panel => panel.classList.remove('active'));
e.target.classList.add('active');
container.querySelector(`.${panelName}-panel`).classList.add('active');
}
});
// 展开/收起面板
expandCollapseBtn.addEventListener('click', () => {
container.classList.toggle('expanded');
expandCollapseBtn.textContent = container.classList.contains('expanded') ? '收起' : '展开';
});
// 显示/隐藏开发者工具
toggleButton.addEventListener('click', () => {
container.style.display = container.style.display === 'none' ? 'flex' : 'none';
if (container.style.display === 'flex') {
container.classList.add('expanded');
expandCollapseBtn.textContent = '收起';
}
});
}
// 页面加载完成后初始化
window.addEventListener('load', initDevTools);
})();