Tampermonkey Script for Autofilling Form Data
// ==UserScript==
// @name AutoFill
// @name:zh 凯普顿自动填表
// @name:en AutoFill
// @version 1.1
// @author peng shaowei
// @description 自动填写表单数据的Tampermonkey脚本
// @description:en Tampermonkey Script for Autofilling Form Data
// @match */*
// @grant GM_setValue
// @grant GM_getValue
// @license GPL-3.0
// @history 1.1 自动填充用户填写的数据,新增恢复初始状态功能
// @history 1.0 初始版本,支持保存和恢复表单数据
// @namespace https://gitee.com/pengsw95
// ==/UserScript==
(function() {
// 引入按钮组件样式
const modalStyle = `
/* 设置界面容器 */
.settings-modal {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
width: 400px;
padding: 20px;
background-color: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
border-radius: 4px;
display: flex;
flex-direction: column;
position: fixed;
opacity: 0.8;
transition: opacity 0.3s ease;
}
.settings-modal:hover {
/* 鼠标悬停时的样式 */
opacity: 1;
}
/* 标签样式 */
.settings-label {
font-weight: bold;
margin-bottom: 10px;
display: block;
}
/* 输入框样式 */
.settings-input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f8f8f8;
color: #333;
box-sizing: border-box;
transition: border-color 0.3s ease;
}
.settings-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
}
/* 按钮Div */
.settings-buttons {
display: flex;
}
/* 按钮样式 */
.settings-button {
flex: 1;
padding: 8px 16px; /* 调整按钮的内边距 */
margin-top: 10px;
border: none;
border-radius: 4px;
color: #fff;
background-color: #007bff;
cursor: pointer;
transition: background-color 0.3s ease;
font-size: 14px; /* 调整按钮的字体大小 */
}
.settings-button:not(:last-child) {
margin-right: 20px;
}
.settings-button:hover {
background-color: #0056b3;
}
.settings-button:active {
background-color: #004f9d;
}
/* 关闭按钮样式 */
.close-button {
position: absolute;
top: 10px;
right: 10px;
padding: 6px;
border: none;
background-color: transparent;
color: #333;
font-size: 18px;
cursor: pointer;
}
.close-button:hover {
color: #007bff;
}
`;
// 统一字段选择器
const FORM_SELECTOR = 'input, textarea, select';
// 创建 Shadow DOM 容器
const container = document.createElement('div');
container.id = 'autofill-container';
document.documentElement.appendChild(container);
const shadow = container.attachShadow({mode: 'open'});
// 创建设置按钮
function createSettingsButton() {
const button = document.createElement('button');
button.innerText = '自动填表';
button.style.cssText = `
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
opacity: 0.8;
color: gray;
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
cursor: pointer;
`.replace(/\n\s*/g, ' ').trim();
button.addEventListener('click', createSettingsModal);
shadow.appendChild(button);
}
// 创建设置模态框
function createSettingsModal() {
if (shadow.querySelector('.settings-modal')) return;
const modal = document.createElement('div');
modal.className = 'settings-modal';
modal.style.zIndex = '9999'; // 解决部分网站会遮挡的问题
const currentDiff = getDiffData();
modal.innerHTML = `
<style>${modalStyle}</style>
<button class="close-button">×</button>
<div class="settings-label">
<span>表单数据:</span>
</div>
<input class="settings-input" type="text" value='${currentDiff}'>
<div class="settings-buttons">
<button class="settings-button btn-save">保存</button>
<button class="settings-button btn-fill">恢复</button>
<button class="settings-button btn-clear">恢复初始</button>
</div>
`;
// 绑定事件
modal.querySelector('.close-button').addEventListener('click', closeSettingsModal);
modal.querySelector('.btn-save').addEventListener('click', saveFormData);
modal.querySelector('.btn-fill').addEventListener('click', fillForm);
modal.querySelector('.btn-clear').addEventListener('click', clearFormData);
shadow.appendChild(modal);
}
// 关闭设置模态框
function closeSettingsModal() {
const modal = shadow.querySelector('.settings-modal');
if (modal) modal.remove();
}
// 保存初始表单状态
let initialFormState = {};
// 获取字段唯一标识:优先用 name,其次 placeholder,再次 aria-label
function getFieldKey(field) {
return field.name || field.placeholder || field.getAttribute('aria-label') || '';
}
// 判断节点是否可见(自身及所有祖先均不含 display:none)
function isVisible(el) {
let node = el;
while (node && node !== document.documentElement) {
if (getComputedStyle(node).display === 'none') return false;
node = node.parentElement;
}
return true;
}
// 触发 Vue 响应式更新(同时兼容普通表单)
function setFieldValue(field, value) {
const proto = field.tagName === 'TEXTAREA'
? window.HTMLTextAreaElement.prototype
: window.HTMLInputElement.prototype;
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value');
if (nativeSetter && nativeSetter.set) {
nativeSetter.set.call(field, value);
} else {
field.value = value;
}
field.dispatchEvent(new Event('input', { bubbles: true }));
field.dispatchEvent(new Event('change', { bubbles: true }));
}
// 捕获初始状态
function captureInitialState() {
initialFormState = {};
const formFields = document.querySelectorAll(FORM_SELECTOR);
formFields.forEach(field => {
const key = getFieldKey(field);
if (key && isVisible(field)) {
initialFormState[key] = field.value;
}
});
// console.log('AutoFill: 基准状态已更新', initialFormState);
}
// 获取表单差异数据
function getDiffData() {
const formFields = document.querySelectorAll(FORM_SELECTOR);
const diffData = {};
formFields.forEach(field => {
const key = getFieldKey(field);
if (key && isVisible(field)) {
const initialVal = initialFormState[key];
if (field.value !== initialVal) {
diffData[key] = field.value;
}
}
});
return JSON.stringify(diffData);
}
// 保存表单
function saveFormData() {
const inputField = shadow.querySelector('.settings-input').value.trim();
try {
JSON.parse(inputField);
localStorage.setItem('formData', inputField);
} catch (e) {
alert('保存失败:JSON 格式不正确。');
return;
}
closeSettingsModal();
}
// 清空表单
function clearFormData() {
localStorage.removeItem('formData');
document.querySelectorAll(FORM_SELECTOR).forEach(field => {
const key = getFieldKey(field);
if (key && key in initialFormState) {
setFieldValue(field, initialFormState[key]);
}
});
closeSettingsModal();
}
// 自动填充
function fillForm() {
const savedFormData = localStorage.getItem('formData');
if (savedFormData) {
try {
const parsedData = JSON.parse(savedFormData);
const formFields = document.querySelectorAll(FORM_SELECTOR);
formFields.forEach(function(field) {
const key = getFieldKey(field);
if (key && key in parsedData) {
setFieldValue(field, parsedData[key]);
}
});
} catch (error) { console.error('AutoFill解析失败:', error); }
}
if (shadow.querySelector('.settings-modal')) closeSettingsModal();
}
// 在页面完全加载后执行
window.addEventListener('load', function() {
// 记录初始状态
captureInitialState();
// 添加设置按钮
createSettingsButton();
});
})();