AutoFill

自动填写表单数据的Tampermonkey脚本

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
    });
})();