Greasy Fork 支持简体中文。

全国统一规范电子税务局税号填写辅助工具

在电子税务局旁边显示一个小窗口,方便登录不同的公司,支持开关控制是否自动填写账号密码

// ==UserScript==
// @name         全国统一规范电子税务局税号填写辅助工具
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  在电子税务局旁边显示一个小窗口,方便登录不同的公司,支持开关控制是否自动填写账号密码
// @author       Herohub
// @match        https://*.chinatax.gov.cn:8443/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
    'use strict';

    // 判断是否为登录页面,若不是则直接退出脚本执行
    const isLoginPage = window.location.href.includes('login?redirect_uri');
    if (!isLoginPage) return;

    // 创建浮窗元素并设置样式
    const createFloatWindow = () => {
        const floatWindow = document.createElement('div');
        Object.assign(floatWindow.style, {
            position: 'fixed',
            top: '50%',
            right: '20px',
            transform: 'translateY(-50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '15px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            fontFamily: 'Arial,sans-serif',
            zIndex: 10000
        });
        return floatWindow;
    };

    const floatWindow = createFloatWindow();
    document.body.appendChild(floatWindow);

    // 创建按钮元素并设置样式和点击事件
    const createButton = (text, clickHandler, isDelete = false) => {
        const button = document.createElement('button');
        Object.assign(button.style, {
            marginRight: '5px',
            padding: '5px 10px',
            border: 'none',
            borderRadius: '3px',
            background: isDelete? '#dc3545' : '#007BFF',
            color: '#fff',
            cursor: 'pointer',
            transition: 'background-color 0.3s'
        });
        button.textContent = text;
        button.addEventListener('click', clickHandler);
        // 鼠标悬停效果
        button.addEventListener('mouseover', () => button.style.background = isDelete? '#c82333' : '#0056b3');
        button.addEventListener('mouseout', () => button.style.background = isDelete? '#dc3545' : '#007BFF');
        return button;
    };

    // 定义全局变量
    let isEditMode = false;
    let lastSelectedItem = GM_getValue('lastSelectedItem', null);
    let originalWidth = null;
    let isAutoFillAccount = GM_getValue('isAutoFillAccount', false);
    let accountInfo = GM_getValue('accountInfo', null);

    // 添加按钮,点击触发添加新条目功能
    const addButton = createButton('添加', addItem);
    floatWindow.appendChild(addButton);

    // 编辑/完成按钮,点击切换编辑模式
    let editButton = createButton('编辑', toggleEditMode);
    floatWindow.appendChild(editButton);

    // 导入按钮,初始隐藏,用于导入数据
    const importButton = createButton('导入', importData);
    importButton.style.display = 'none';
    floatWindow.appendChild(importButton);

    // 导出按钮,初始隐藏,用于导出数据
    const exportButton = createButton('导出', exportData);
    exportButton.style.display = 'none';
    floatWindow.appendChild(exportButton);

    // 账号自动填写开关按钮,点击切换自动填充状态
    const accountAutoFillSwitch = createButton(isAutoFillAccount? '自动填账号密码: 开' : '自动填账号密码: 关', toggleAccountAutoFill);
    floatWindow.appendChild(accountAutoFillSwitch);

    // 分割线,用于区分按钮和条目列表
    const separator = document.createElement('hr');
    separator.style.cssText = 'margin:15px 0;border:none;border - top:1px solid #e0e0e0';
    floatWindow.appendChild(separator);

    // 内容列表容器,用于显示税号条目
    const itemList = document.createElement('div');
    floatWindow.appendChild(itemList);

    // 加载已保存的税号内容
    const savedItems = GM_getValue('autoFillItems', []);
    savedItems.forEach(item => addItemToWindow(item.content, item.note, itemList));

    // 添加新条目函数,点击添加按钮时调用
    function addItem() {
        const modal = createAddModal();
        document.body.appendChild(modal);
    }

    // 创建添加新条目模态框
    function createAddModal() {
        const modal = document.createElement('div');
        Object.assign(modal.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            gap: '10px'
        });

        const taxNumberInput = document.createElement('input');
        taxNumberInput.placeholder = '请输入税号';
        taxNumberInput.style.padding = '8px';
        taxNumberInput.style.border = '1px solid #ccc';
        taxNumberInput.style.borderRadius = '3px';
        modal.appendChild(taxNumberInput);

        const noteInput = document.createElement('input');
        noteInput.placeholder = '请输入备注';
        noteInput.style.padding = '8px';
        noteInput.style.border = '1px solid #ccc';
        noteInput.style.borderRadius = '3px';
        modal.appendChild(noteInput);

        const confirmButton = createButton('确认', () => {
            const taxNumber = taxNumberInput.value.trim();
            const note = noteInput.value.trim();
            if (!taxNumber ||!note) {
                alert('税号和备注都不能为空,请重新输入。');
                return;
            }
            addItemToWindow(taxNumber, note, itemList);
            const items = GM_getValue('autoFillItems', []);
            items.push({ content: taxNumber, note });
            GM_setValue('autoFillItems', items);
            adjustWindowWidth();
            document.body.removeChild(modal);
        });
        modal.appendChild(confirmButton);

        const cancelButton = createButton('取消', () => {
            document.body.removeChild(modal);
        }, true);
        modal.appendChild(cancelButton);

        return modal;
    }

    // 在主文档和 iframe 中查找输入框
    function findInputInIframes(selector) {
        const iframes = document.querySelectorAll('iframe');
        for (let i = 0; i < iframes.length; i++) {
            try {
                const input = iframes[i].contentDocument.querySelector(selector);
                if (input) {
                    return input;
                }
            } catch (e) {
                // 处理跨域访问 iframe 的情况,跳过当前 iframe 继续查找
                continue;
            }
        }
        // 若在所有 iframe 中都未找到,在主文档中查找
        return document.querySelector(selector);
    }

    // 将新条目添加到窗口列表中
    function addItemToWindow(content, note, container) {
        const itemDiv = document.createElement('div');
        Object.assign(itemDiv.style, {
            padding: '8px 0',
            cursor: 'pointer',
            borderBottom: '1px solid #f0f0f0',
            transition: 'background-color 0.3s',
            userSelect: 'none',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between'
        });

        const textSpan = document.createElement('span');
        textSpan.textContent = note;
        itemDiv.appendChild(textSpan);

        const buttonContainer = document.createElement('div');
        const editBtn = createButton('编辑', () => editItem(itemDiv, content, note));
        const deleteBtn = createButton('删除', () => deleteItem(itemDiv, content), true);
        editBtn.style.display = 'none';
        deleteBtn.style.display = 'none';
        buttonContainer.appendChild(editBtn);
        buttonContainer.appendChild(deleteBtn);
        itemDiv.appendChild(buttonContainer);

        itemDiv.addEventListener('click', () => {
            // 延迟 500 毫秒执行填充操作,确保页面元素加载完成
            setTimeout(() => {
                const taxInput = findInputInIframes('input.el-input__inner[placeholder="统一社会信用代码/纳税人识别号"]');
                if (taxInput) {
                    taxInput.value = content;
                    triggerInputEvents(taxInput);
                } else {
                    console.error('未找到纳税人识别号输入框,请检查页面元素。');
                }

                if (isAutoFillAccount && accountInfo) {
                    const accountInput = findInputInIframes('input[placeholder="居民身份证号码/手机号码/用户名"]');
                    const passwordInput = findInputInIframes('input[placeholder="个人用户密码"]');
                    if (accountInput) {
                        accountInput.value = accountInfo.account;
                        triggerInputEvents(accountInput);
                    }
                    if (passwordInput) {
                        passwordInput.value = accountInfo.password;
                        triggerInputEvents(passwordInput);
                    } else {
                        console.error('未找到密码输入框,请检查页面元素。');
                    }
                }

                clearSelection();
                itemDiv.style.background = '#e0f7fa';
                lastSelectedItem = content;
                GM_setValue('lastSelectedItem', lastSelectedItem);
            }, 500);
        });

        itemDiv.addEventListener('mouseover', () => itemDiv.style.background = '#f9f9f9');
        itemDiv.addEventListener('mouseout', () => {
            if (itemDiv.dataset.content!== lastSelectedItem) {
                itemDiv.style.background = '#fff';
            }
        });

        itemDiv.dataset.content = content;
        container.appendChild(itemDiv);

        if (content === lastSelectedItem) {
            itemDiv.style.background = '#e0f7fa';
        }
    }

    // 触发输入框的 input 和 change 事件,模拟用户输入操作
    function triggerInputEvents(input) {
        const inputEvent = new Event('input', { bubbles: true });
        const changeEvent = new Event('change', { bubbles: true });
        input.dispatchEvent(inputEvent);
        input.dispatchEvent(changeEvent);
    }

    // 清除所有条目的选中状态
    function clearSelection() {
        const items = itemList.querySelectorAll('div');
        items.forEach(item => {
            if (item.dataset.content!== lastSelectedItem) {
                item.style.background = '#fff';
            }
        });
    }

    // 切换编辑模式,显示或隐藏编辑和删除按钮
    function toggleEditMode() {
        isEditMode =!isEditMode;
        const items = itemList.querySelectorAll('div');
        const showOrHideButtons = (element, show) => {
            element.style.display = show? 'inline-block' : 'none';
        };

        items.forEach(item => {
            const editBtn = item.querySelector('div button:nth-child(1)');
            const deleteBtn = item.querySelector('div button:nth-child(2)');
            showOrHideButtons(editBtn, isEditMode);
            showOrHideButtons(deleteBtn, isEditMode);
            item.style.cursor = isEditMode? 'move' : 'pointer';
            item.draggable = isEditMode;
        });

        editButton.textContent = isEditMode? '完成' : '编辑';
        showOrHideButtons(importButton, isEditMode);
        showOrHideButtons(exportButton, isEditMode);

        if (isEditMode) {
            if (originalWidth === null) {
                originalWidth = parseFloat(getComputedStyle(floatWindow).width);
            }
            setupDragSort();
        }
        adjustWindowWidth();
    }

    // 编辑已有条目信息
    function editItem(itemDiv, oldContent, oldNote) {
        const editModal = document.createElement('div');
        Object.assign(editModal.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            gap: '10px'
        });

        const taxNumberInput = document.createElement('input');
        taxNumberInput.value = oldContent;
        taxNumberInput.style.padding = '8px';
        taxNumberInput.style.border = '1px solid #ccc';
        taxNumberInput.style.borderRadius = '3px';
        editModal.appendChild(taxNumberInput);

        const noteInput = document.createElement('input');
        noteInput.value = oldNote;
        noteInput.style.padding = '8px';
        noteInput.style.border = '1px solid #ccc';
        noteInput.style.borderRadius = '3px';
        editModal.appendChild(noteInput);

        const confirmButton = createButton('确认', () => {
            const newContent = taxNumberInput.value.trim();
            const newNote = noteInput.value.trim();
            if (!newContent ||!newNote) {
                alert('税号和备注都不能为空,请重新输入。');
                return;
            }

            const textSpan = itemDiv.querySelector('span');
            textSpan.textContent = newNote;
            itemDiv.dataset.content = newContent;

            const items = GM_getValue('autoFillItems', []);
            const index = items.findIndex(item => item.content === oldContent);
            if (index!== -1) {
                items[index] = { content: newContent, note: newNote };
                GM_setValue('autoFillItems', items);
            }
            adjustWindowWidth();
            document.body.removeChild(editModal);
        });
        editModal.appendChild(confirmButton);

        const cancelButton = createButton('取消', () => {
            document.body.removeChild(editModal);
        }, true);
        editModal.appendChild(cancelButton);

        document.body.appendChild(editModal);
    }

    // 删除指定条目
    function deleteItem(itemDiv, content) {
        const items = GM_getValue('autoFillItems', []);
        const newItems = items.filter(item => item.content!== content);
        GM_setValue('autoFillItems', newItems);
        itemList.removeChild(itemDiv);
        if (content === lastSelectedItem) {
            lastSelectedItem = null;
            GM_setValue('lastSelectedItem', null);
        }
        adjustWindowWidth();
    }

    // 设置拖动排序功能,允许用户通过拖动条目改变顺序
    function setupDragSort() {
        let draggedItem = null;
        itemList.addEventListener('dragstart', (e) => {
            draggedItem = e.target;
            setTimeout(() => draggedItem.style.display = 'none', 0);
        });

        itemList.addEventListener('dragover', (e) => {
            e.preventDefault();
        });

        itemList.addEventListener('drop', (e) => {
            e.preventDefault();
            const target = e.target.closest('div');
            if (target && target!== draggedItem) {
                const items = Array.from(itemList.children);
                const draggedIndex = items.indexOf(draggedItem);
                const targetIndex = items.indexOf(target);
            if (draggedIndex < targetIndex) {
                    itemList.insertBefore(draggedItem, target.nextSibling);
                } else {
                    itemList.insertBefore(draggedItem, target);
                }
                updateItemOrder();
            }
            draggedItem.style.display = 'block';
            draggedItem = null;
        });
    }

    // 更新条目顺序并保存到存储中
    function updateItemOrder() {
        const items = Array.from(itemList.children);
        const newItemOrder = items.map(item => {
            const note = item.querySelector('span').textContent;
            const content = item.dataset.content;
            return { content, note };
        });
        GM_setValue('autoFillItems', newItemOrder);
    }

    // 调整浮窗宽度以适应内容变化
    function adjustWindowWidth() {
        let maxWidth = 0;
        const items = itemList.querySelectorAll('span');
        items.forEach(item => {
            const rect = item.getBoundingClientRect();
            if (rect.width > maxWidth) {
                maxWidth = rect.width;
            }
        });
        const buttonWidth = 100;
        const padding = 30;
        let width = maxWidth + buttonWidth + padding;
        if (isEditMode) {
            width *= 2;
        }
        floatWindow.style.width = width + 'px';
        if (!isEditMode) {
            originalWidth = width;
        }
    }

    // 导入数据功能,支持用户以逗号分号及换行符格式导入税号信息
    function importData() {
        const importModal = document.createElement('div');
        Object.assign(importModal.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            gap: '10px'
        });

        const dataTextarea = document.createElement('textarea');
        dataTextarea.placeholder = '请输入要导入的数据(格式:税号,备注;税号,备注;...)';
        dataTextarea.style.padding = '8px';
        dataTextarea.style.border = '1px solid #ccc';
        dataTextarea.style.borderRadius = '3px';
        importModal.appendChild(dataTextarea);

        const confirmButton = createButton('确认导入', () => {
            const dataStr = dataTextarea.value.trim();
            if (!dataStr) {
                alert('导入数据不能为空,请重新输入。');
                return;
            }
            try {
                const lines = dataStr.split(';');
                const importedItems = [];
                for (const line of lines) {
                    const [content, note] = line.split(',');
                    if (content && note) {
                        importedItems.push({ content: content.trim(), note: note.trim() });
                    } else {
                        throw new Error('导入数据格式错误,请检查。');
                    }
                }
                itemList.innerHTML = '';
                GM_setValue('autoFillItems', importedItems);
                importedItems.forEach(item => addItemToWindow(item.content, item.note, itemList));
                adjustWindowWidth();
                alert('数据导入成功!');
                document.body.removeChild(importModal);
            } catch (error) {
                alert(`数据导入失败:${error.message}`);
            }
        });
        importModal.appendChild(confirmButton);

        const cancelButton = createButton('取消', () => {
            document.body.removeChild(importModal);
        }, true);
        importModal.appendChild(cancelButton);

        document.body.appendChild(importModal);
    }

    // 导出数据功能,将当前存储的税号信息以逗号分号及换行符格式导出
    function exportData() {
        const items = GM_getValue('autoFillItems', []);
        const dataStr = items.map(item => `${item.content},${item.note}`).join(';\n');
        const exportModal = document.createElement('div');
        Object.assign(exportModal.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            gap: '10px'
        });

        const dataTextarea = document.createElement('textarea');
        dataTextarea.value = dataStr;
        dataTextarea.readOnly = true;
        dataTextarea.style.padding = '8px';
        dataTextarea.style.border = '1px solid #ccc';
        dataTextarea.style.borderRadius = '3px';
        exportModal.appendChild(dataTextarea);

        const copyButton = createButton('复制数据', () => {
            dataTextarea.select();
            document.execCommand('copy');
            alert('数据已复制到剪贴板!');
        });
        exportModal.appendChild(copyButton);

        const closeButton = createButton('关闭', () => {
            document.body.removeChild(exportModal);
        }, true);
        exportModal.appendChild(closeButton);

        document.body.appendChild(exportModal);
    }

    // 切换账号自动填写开关状态
    function toggleAccountAutoFill() {
        isAutoFillAccount =!isAutoFillAccount;
        accountAutoFillSwitch.textContent = isAutoFillAccount? '自动填账号密码: 开' : '自动填账号密码: 关';
        GM_setValue('isAutoFillAccount', isAutoFillAccount);

        if (isAutoFillAccount) {
            if (!accountInfo) {
                const modal = createAccountInfoModal();
                document.body.appendChild(modal);
            } else {
                // 延迟 500 毫秒执行填充操作,确保页面元素加载完成
                setTimeout(() => {
                    const accountInput = findInputInIframes('input[placeholder="居民身份证号码/手机号码/用户名"]');
                    const passwordInput = findInputInIframes('input[placeholder="个人用户密码"]');
                    if (accountInput) {
                        accountInput.value = accountInfo.account;
                        triggerInputEvents(accountInput);
                    }
                    if (passwordInput) {
                        passwordInput.value = accountInfo.password;
                        triggerInputEvents(passwordInput);
                    }
                }, 500);
            }
        } else {
            accountInfo = null;
            GM_setValue('accountInfo', null);
            const accountInput = findInputInIframes('input[placeholder="居民身份证号码/手机号码/用户名"]');
            const passwordInput = findInputInIframes('input[placeholder="个人用户密码"]');
            if (accountInput) {
                accountInput.value = '';
                triggerInputEvents(accountInput);
            }
            if (passwordInput) {
                passwordInput.value = '';
                triggerInputEvents(passwordInput);
            }
        }
    }

    // 创建输入账号和密码的模态框
    function createAccountInfoModal() {
        const modal = document.createElement('div');
        Object.assign(modal.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: '#fff',
            border: '1px solid #ccc',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,.1)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            gap: '10px'
        });

        const accountInput = document.createElement('input');
        accountInput.placeholder = '请输入账号';
        accountInput.style.padding = '8px';
        accountInput.style.border = '1px solid #ccc';
        accountInput.style.borderRadius = '3px';
        modal.appendChild(accountInput);

        const passwordInput = document.createElement('input');
        passwordInput.type = 'password';
        passwordInput.placeholder = '请输入密码';
        passwordInput.style.padding = '8px';
        passwordInput.style.border = '1px solid #ccc';
        passwordInput.style.borderRadius = '3px';
        modal.appendChild(passwordInput);

        const confirmButton = createButton('确认', () => {
            const account = accountInput.value.trim();
            const password = passwordInput.value.trim();
            if (!account ||!password) {
                alert('账号和密码都不能为空,请重新输入。');
                return;
            }
            accountInfo = { account, password };
            GM_setValue('accountInfo', accountInfo);
            // 延迟 500 毫秒执行填充操作,确保页面元素加载完成
            setTimeout(() => {
                const accountInputOnPage = findInputInIframes('input[placeholder="居民身份证号码/手机号码/用户名"]');
                const passwordInputOnPage = findInputInIframes('input[placeholder="个人用户密码"]');
                if (accountInputOnPage) {
                    accountInputOnPage.value = account;
                    triggerInputEvents(accountInputOnPage);
                }
                if (passwordInputOnPage) {
                    passwordInputOnPage.value = password;
                    triggerInputEvents(passwordInputOnPage);
                }
            }, 500);
            document.body.removeChild(modal);
        });
        modal.appendChild(confirmButton);

        const cancelButton = createButton('取消', () => {
            isAutoFillAccount = false;
            accountAutoFillSwitch.textContent = '自动填账号密码: 关';
            GM_setValue('isAutoFillAccount', isAutoFillAccount);
            document.body.removeChild(modal);
        }, true);
        modal.appendChild(cancelButton);

        return modal;
    }

    // 初始调整窗口宽度,以适应初始内容
    adjustWindowWidth();

    // 如果自动填充开关开启且已有账号信息,延迟 500 毫秒后自动填充账号和密码
    if (isAutoFillAccount && accountInfo) {
        setTimeout(() => {
            const accountInput = findInputInIframes('input[placeholder="居民身份证号码/手机号码/用户名"]');
            const passwordInput = findInputInIframes('input[placeholder="个人用户密码"]');
            if (accountInput) {
                accountInput.value = accountInfo.account;
                triggerInputEvents(accountInput);
            }
            if (passwordInput) {
                passwordInput.value = accountInfo.password;
                triggerInputEvents(passwordInput);
            }
        }, 500);
    }

})();