Namecheap 购物车域名列表

支持PC 用于统计购物车域名列表,方便统计计算

// ==UserScript==
// @name        Namecheap 购物车域名列表
// @icon        data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgODUgNDYuNTciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjU5Ljk5IiB5MT0iNDQuMTYiIHgyPSI4MC4wMSIgeTI9IjEuMjMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNkNDIwMmMiLz48c3RvcCBvZmZzZXQ9IjAuMSIgc3RvcC1jb2xvcj0iI2RjM2QyOSIgc3RvcC1vcGFjaXR5PSIwLjc5Ii8+PHN0b3Agb2Zmc2V0PSIwLjIiIHN0b3AtY29sb3I9IiNlNDU5MjYiIHN0b3Atb3BhY2l0eT0iMC41OCIvPjxzdG9wIG9mZnNldD0iMC4zMiIgc3RvcC1jb2xvcj0iI2VhNzEyMyIgc3RvcC1vcGFjaXR5PSIwLjQiLz48c3RvcCBvZmZzZXQ9IjAuNDMiIHN0b3AtY29sb3I9IiNmMDg1MjEiIHN0b3Atb3BhY2l0eT0iMC4yNSIvPjxzdG9wIG9mZnNldD0iMC41NSIgc3RvcC1jb2xvcj0iI2Y0OTQxZiIgc3RvcC1vcGFjaXR5PSIwLjE0Ii8+PHN0b3Agb2Zmc2V0PSIwLjY4IiBzdG9wLWNvbG9yPSIjZjc5ZjFlIiBzdG9wLW9wYWNpdHk9IjAuMDYiLz48c3RvcCBvZmZzZXQ9IjAuODIiIHN0b3AtY29sb3I9IiNmOGE1MWQiIHN0b3Atb3BhY2l0eT0iMC4wMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2Y5YTcxZCIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIHgxPSIxNzc3LjQ4IiB5MT0iMTA0OC43NiIgeDI9IjE3OTcuNSIgeTI9IjEwMDUuODMiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTgwMy41IDEwNTIuNikgcm90YXRlKDE4MCkiIHhsaW5rOmhyZWY9IiNhIi8+PC9kZWZzPjx0aXRsZT5jbi1sb2dvPC90aXRsZT48cGF0aCBkPSJNNzcuMTcuNzFhNy44Nyw3Ljg3LDAsMCwwLTYuODgsNC4wNWwtLjE2LjMzTDY0LDE3LjI1bC03LjgsMTUuMzcsNS4xMSwxMC4wNy4yOC41NUE4LDgsMCwwLDAsNjUsNDYuNDdhOC4wNSw4LjA1LDAsMCwwLDMuNDEtMy4yM2wuMjgtLjU1TDg0LDEyLjQ4bC4zNy0uNzNhNy44Niw3Ljg2LDAsMCwwLTcuMTktMTFaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMSAtMC43MSkiIGZpbGw9IiNmZjUxMDAiLz48cGF0aCBkPSJNMjkuODUsMTUuMzRsLTUuMS0xMC0uMjgtLjU1YTcuODksNy44OSwwLDAsMC0zLjQtMy4yMiw3LjkyLDcuOTIsMCwwLDAtMy40LDMuMjFsLS4yOS41NkwyLjA1LDM1LjUybC0uMzcuNzJhNy44Niw3Ljg2LDAsMCwwLDE0LjA2LDdsLjE3LS4zMiw2LjE3LTEyLjE2TDI5Ljg3LDE1LjRaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMSAtMC43MSkiIGZpbGw9IiNmZjUxMDAiLz48cGF0aCBkPSJNNzcuMTUuNzFhNy44Niw3Ljg2LDAsMCwwLTYuODcsNC4wNWwtLjE3LjMzTDYzLjk0LDE3LjI1LDU2LjEzLDMyLjYybDUuMTIsMTAuMDcuMjguNTVhNy45NCw3Ljk0LDAsMCwwLDMuNDEsMy4yMyw3Ljk0LDcuOTQsMCwwLDAsMy40MS0zLjIzbC4yOS0uNTVMODQsMTIuNDhsLjM2LS43M2E3Ljg2LDcuODYsMCwwLDAtNy4xOS0xMVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xIC0wLjcxKSIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGQ9Ik04Ljg2LDQ3LjI5YTcuODYsNy44NiwwLDAsMCw2Ljg3LTRsLjE3LS4zMyw2LjE4LTEyLjE2LDcuOC0xNS4zN0wyNC43Nyw1LjMxbC0uMjgtLjU1YTgsOCwwLDAsMC0zLjQyLTMuMjMsOCw4LDAsMCwwLTMuNDEsMy4yM2wtLjI4LjU1TDIsMzUuNTJsLS4zNy43M2E3Ljg2LDcuODYsMCwwLDAsNy4xOSwxMVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xIC0wLjcxKSIgZmlsbD0idXJsKCNiKSIvPjxwYXRoIGQ9Ik0yOS44NSwxNS4zNGwtNS4xLTEwLS4yOC0uNTVhNy45NCw3Ljk0LDAsMCwwLTMuNDEtMy4yM0E4LjMxLDguMzEsMCwwLDEsMjIuNTUsMWE4LjE2LDguMTYsMCwwLDEsMi0uMjVIMzUuMjNhNy45Miw3LjkyLDAsMCwxLDYuODYsNGwuMjguNTVMNTYuMTgsMzIuNjZsNS4wOSwxMCwuMjguNTVBOCw4LDAsMCwwLDY1LDQ2LjQ3YTguMDUsOC4wNSwwLDAsMS0zLjQ3LjgxSDUwLjc5YTcuOTEsNy45MSwwLDAsMS02Ljg1LTRsLS4yOS0uNTVaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMSAtMC43MSkiIGZpbGw9IiNmZjhjNDQiLz48L3N2Zz4K
// @match       *://www.namecheap.com/shoppingcart/*
// @grant       GM_addStyle
// @version     2025.7.25
// @author      ayersltd
// @description 支持PC 用于统计购物车域名列表,方便统计计算
// @license     GPL-3.0-only
// @require     https://cdnjs.cloudflare.com/ajax/libs/fuse.js/6.6.2/fuse.min.js
// @namespace   https://greasyfork.org/users/1497923
// ==/UserScript==

(function() {
    // 添加自定义样式
    GM_addStyle(`
        #domainListPanel {
            position: fixed;
            top: 50px;
            right: 50px;
            background: #fff;
            border: 1px solid #e1e1e1;
            border-radius: 8px;
            box-shadow: 0 8px 30px rgba(0,0,0,0.12);
            z-index: 99999;
            width: 380px;
            max-height: 85vh;
            display: flex;
            flex-direction: column;
            font-family: 'Segoe UI', system-ui, sans-serif;
        }

        .panel-header {
            padding: 16px;
            background: #f8f8f8;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 8px 8px 0 0;
        }

        .panel-title {
            font-weight: 600;
            font-size: 16px;
            color: #333;
            margin: 0;
        }

        .panel-close {
            background: none;
            border: none;
            font-size: 18px;
            cursor: pointer;
            color: #888;
        }

        .panel-actions {
            padding: 12px 16px;
            display: flex;
            gap: 8px;
            border-bottom: 1px solid #eee;
        }

        .search-box {
            flex: 1;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }

        .action-btn {
            padding: 6px 12px;
            background: #f2f2f2;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
            font-size: 13px;
        }

        .action-btn:hover {
            background: #e8e8e8;
        }

        .panel-content {
            flex: 1;
            overflow: auto;
            padding: 0;
        }

        .domain-list {
            list-style: none;
            padding: 0;
            margin: 0;
        }

        .domain-item {
            padding: 12px 16px;
            border-bottom: 1px solid #f5f5f5;
            font-size: 14px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .domain-name {
            word-break: break-all;
            padding-right: 10px;
        }

        .domain-count {
            background: #f0f5ff;
            color: #2f5bea;
            border-radius: 12px;
            padding: 3px 8px;
            font-size: 12px;
            font-weight: 500;
            min-width: 60px;
            text-align: center;
        }

        .panel-footer {
            padding: 12px 16px;
            text-align: center;
            background: #f8f8f8;
            border-top: 1px solid #eee;
            font-size: 13px;
            color: #666;
            border-radius: 0 0 8px 8px;
        }

        .view-toggle {
            display: flex;
            gap: 8px;
            margin-bottom: 12px;
            justify-content: center;
        }

        .view-option {
            padding: 4px 12px;
            border-radius: 15px;
            background: #f0f0f0;
            cursor: pointer;
            font-size: 12px;
        }

        .view-option.active {
            background: #e1ecff;
            color: #2f5bea;
            font-weight: 500;
        }

        .section-title {
            padding: 10px 16px;
            background: #f8f8f8;
            font-weight: 600;
            font-size: 13px;
            color: #666;
            border-bottom: 1px solid #eee;
            position: sticky;
            top: 0;
        }

        .tld-group {
            margin-bottom: 15px;
        }
    `);

    // 主功能
    function initDomainList() {
        // 创建面板容器
        const panel = document.createElement('div');
        panel.id = 'domainListPanel';

        // 添加头部
        panel.innerHTML = `
            <div class="panel-header">
                <h3 class="panel-title">域名购物车</h3>
                <button class="panel-close">×</button>
            </div>
            <div class="panel-actions">
                <input type="text" class="search-box" placeholder="搜索域名..." id="domainSearch">
                <button class="action-btn" id="copyAllBtn">复制所有</button>
            </div>
            <div class="view-toggle">
                <span class="view-option active" data-view="list">列表视图</span>
                <span class="view-option" data-view="tld">按后缀分组</span>
            </div>
            <div class="panel-content">
                <ul class="domain-list" id="domainList"></ul>
            </div>
            <div class="panel-footer">
                <div class="domain-count">共计: <span id="totalCount">0</span> 个域名</div>
            </div>
        `;

        document.body.appendChild(panel);

        // 获取元素引用
        const domainList = document.getElementById('domainList');
        const searchBox = document.getElementById('domainSearch');
        const copyAllBtn = document.getElementById('copyAllBtn');
        const totalCount = document.getElementById('totalCount');
        const closeBtn = document.querySelector('.panel-close');
        const viewOptions = document.querySelectorAll('.view-option');

        let allDomains = [];
        let currentView = 'list';

        // 关闭面板
        closeBtn.addEventListener('click', () => {
            panel.style.display = 'none';
        });

        // 视图切换
        viewOptions.forEach(option => {
            option.addEventListener('click', () => {
                viewOptions.forEach(o => o.classList.remove('active'));
                option.classList.add('active');
                currentView = option.dataset.view;
                updateDomainListView(allDomains);
            });
        });

        // 搜索功能
        searchBox.addEventListener('input', (e) => {
            const keyword = e.target.value.trim().toLowerCase();
            if (!keyword) {
                updateDomainListView(allDomains);
                return;
            }

            const filteredDomains = allDomains.filter(domain =>
                domain.toLowerCase().includes(keyword)
            );

            updateDomainListView(filteredDomains);
        });

        // 复制所有域名
        copyAllBtn.addEventListener('click', () => {
            if (allDomains.length === 0) return;

            const domainsText = allDomains.join('\n');
            navigator.clipboard.writeText(domainsText).then(() => {
                showNotification(`已复制 ${allDomains.length} 个域名到剪贴板`);
            });
        });

        // 拖拽功能
        initPanelDrag(panel);

        // 初始加载域名
        setTimeout(loadDomainList, 1500);

        // 加载域名列表
        function loadDomainList() {
            const domains = [];

            // 使用XPath查找所有域名文本节点
            const xpath = "//div[@data-e2e-id='sc-product-container']//p[@data-e2e-id='sc-product-sub-title']/text()";
            const result = document.evaluate(
                xpath,
                document,
                null,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                null
            );

            // 提取域名文本
            for (let i = 0; i < result.snapshotLength; i++) {
                const node = result.snapshotItem(i);
                const domain = node.nodeValue.trim();
                if (domain) domains.push(domain);
            }

            if (domains.length === 0) {
                showNotification("未找到有效域名");
                return;
            }

            allDomains = domains;
            totalCount.textContent = domains.length;
            updateDomainListView(domains);
        }

        // 更新域名列表视图
        function updateDomainListView(domains) {
            domainList.innerHTML = '';

            if (domains.length === 0) {
                domainList.innerHTML = '<div class="domain-item">未找到匹配域名</div>';
                return;
            }

            if (currentView === 'tld') {
                renderTLDGroupView(domains);
            } else {
                renderListView(domains);
            }
        }

        // 渲染列表视图
        function renderListView(domains) {
            domains.forEach((domain, index) => {
                const li = document.createElement('li');
                li.className = 'domain-item';
                li.innerHTML = `
                    <span class="domain-name">${index + 1}. ${domain}</span>
                `;
                domainList.appendChild(li);
            });
        }

        // 渲染TLD分组视图
        function renderTLDGroupView(domains) {
            // 按TLD分组
            const groups = {};

            domains.forEach(domain => {
                const tld = domain.split('.').pop();
                if (!groups[tld]) groups[tld] = [];
                groups[tld].push(domain);
            });

            // 渲染分组视图
            Object.entries(groups).forEach(([tld, domainListInGroup]) => {
                const section = document.createElement('div');
                section.className = 'tld-group';

                section.innerHTML = `
                    <div class="section-title">.${tld} (${domainListInGroup.length})</div>
                `;

                const ul = document.createElement('ul');
                ul.className = 'domain-list';

                domainListInGroup.forEach((domain, index) => {
                    const li = document.createElement('li');
                    li.className = 'domain-item';
                    li.innerHTML = `<span class="domain-name">${index + 1}. ${domain}</span>`;
                    ul.appendChild(li);
                });

                section.appendChild(ul);
                domainList.appendChild(section);
            });
        }
    }

    // 拖拽功能
    function initPanelDrag(panel) {
        let isDragging = false;
        let offsetX, offsetY;

        const header = panel.querySelector('.panel-header');

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                panel.style.left = `${e.clientX - offsetX}px`;
                panel.style.top = `${e.clientY - offsetY}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });
    }

    // 显示通知
    function showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: #4caf50;
            color: white;
            padding: 12px 24px;
            border-radius: 4px;
            z-index: 10000;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            animation: fadeInOut 3s ease;
        `;

        document.body.appendChild(notification);

        setTimeout(() => {
            notification.remove();
        }, 3000);
    }

    // 延迟初始化,确保页面加载完成
    if (document.readyState === 'complete') {
        initDomainList();
    } else {
        window.addEventListener('load', initDomainList);
    }
})();