OSS JSON Viewer Pro

专业级OSS文件查看器

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OSS JSON Viewer Pro
// @namespace    http://your-namespace.com
// @version      5.1
// @description  专业级OSS文件查看器
// @match        *://*.aliyuncs.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 立即停止原始加载
    window.stop();
    document.documentElement.innerHTML = '';

    // 创建容器
    const container = document.createElement('div');
    container.id = 'json-viewer-pro';
    document.documentElement.appendChild(container);

    // 加载动画
    container.innerHTML = `<div class="loader"></div>`;

    // 样式注入
    GM_addStyle(`
        #json-viewer-pro {
            padding: 20px;
            font-family: 'Consolas', monospace;
            background: #1e1e1e;
            color: #d4d4d4;
            min-height: 100vh;
        }
        .json-key {
            color: #9cdcfe;
            font-weight: bold;
        }
        .json-string {
            color: #ce9178;
            background: rgba(206,145,120,0.1);
            padding: 2px 4px;
            border-radius: 3px;
        }
        .json-number {
            color: #b5cea8;
            font-weight: bold;
        }
        .json-boolean {
            color: #569cd6;
            font-style: italic;
        }
        .json-null {
            color: #808080;
            font-style: italic;
        }
        .loader {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 2s linear infinite;
            margin: 20px auto;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        .json-item {
            margin: 4px 0;
            padding-left: 20px;
            position: relative;
            border-left: 1px solid rgba(255,255,255,0.1);
        }
        .json-item::before {
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            width: 12px;
            height: 100%;
            border-left: 1px solid rgba(255,255,255,0.1);
        }
        .json-item:hover {
            background: rgba(255,255,255,0.03);
        }
        .json-collapse {
            cursor: pointer;
            margin-right: 5px;
        }
        .json-collapse::after {
            content: '▼';
            font-size: 10px;
            color: #9cdcfe;
            margin-right: 5px;
        }
        .json-collapse.collapsed::after {
            content: '▶';
        }
        .json-count {
            color: #888;
            font-size: 0.9em;
            margin-left: 5px;
        }
        .json-image {
            max-width: 100%;
            max-height: 200px;
            margin: 5px 0;
            border-radius: 3px;
            box-shadow: 0 0 5px rgba(0,0,0,0.2);
        }
        .json-text {
            white-space: pre-wrap;
            background: rgba(255,255,255,0.05);
            padding: 5px;
            border-radius: 3px;
            margin: 5px 0;
        }
        .json-content {
            margin-left: 8px;
            padding-left: 12px;
            border-left: 1px dashed rgba(255,255,255,0.1);
        }
        button:hover {
            background: #1177bb !important;
        }
        button:active {
            background: #0e5389 !important;
        }
    `);

    // 获取数据
    GM_xmlhttpRequest({
        method: 'GET',
        url: location.href,
        responseType: 'arraybuffer',
        onload: (res) => {
            container.innerHTML = '';
            try {
                const decoder = new TextDecoder('utf-8');
                const jsonData = JSON.parse(decoder.decode(res.response));
                renderJSON(jsonData);
            } catch(e) {
                container.innerHTML = `<div class="error">数据解析失败: ${e.message}</div>`;
            }
        },
        onerror: (err) => {
            container.innerHTML = `<div class="error">请求失败: ${err.statusText}</div>`;
        }
    });

    function addControlButtons(container, data) {
        const buttonStyle = `
            padding: 6px 12px;
            margin-right: 10px;
            background: #0e639c;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
        `;

        const buttonContainer = document.createElement('div');
        buttonContainer.style.marginBottom = '20px';

        // 复制按钮
        const copyButton = document.createElement('button');
        copyButton.textContent = '复制 JSON';
        copyButton.style.cssText = buttonStyle;
        copyButton.onclick = () => {
            navigator.clipboard.writeText(JSON.stringify(data, null, 2))
                .then(() => {
                    copyButton.textContent = '已复制!';
                    setTimeout(() => copyButton.textContent = '复制 JSON', 2000);
                })
                .catch(err => alert('复制失败: ' + err));
        };

        // 展开全部按钮
        const expandButton = document.createElement('button');
        expandButton.textContent = '展开全部';
        expandButton.style.cssText = buttonStyle;
        expandButton.onclick = () => {
            container.querySelectorAll('.json-content').forEach(content => {
                content.style.display = '';
            });
            container.querySelectorAll('.json-collapse').forEach(collapse => {
                collapse.classList.remove('collapsed');
            });
        };

        // 折叠全部按钮
        const collapseButton = document.createElement('button');
        collapseButton.textContent = '折叠全部';
        collapseButton.style.cssText = buttonStyle;
        collapseButton.onclick = () => {
            container.querySelectorAll('.json-content').forEach(content => {
                content.style.display = 'none';
            });
            container.querySelectorAll('.json-collapse').forEach(collapse => {
                collapse.classList.add('collapsed');
            });
        };

        buttonContainer.appendChild(copyButton);
        buttonContainer.appendChild(expandButton);
        buttonContainer.appendChild(collapseButton);

        return buttonContainer;
    }

    function renderJSON(data) {
        const countChildren = (obj) => {
            if (!obj || typeof obj !== 'object') return 0;
            return Object.keys(obj).length;
        };

        const render = (obj, indent = 0) => {
            if (Array.isArray(obj)) {
                return obj.map(item => {
                    if (typeof item === 'object' && item !== null) {
                        return `
                            <div class="json-item">
                                {
                                ${renderObject(item, indent + 1)}
                                }
                            </div>
                        `;
                    }
                    return `
                        <div class="json-item">
                            ${formatValue(item)}
                        </div>
                    `;
                }).join('');
            }
            return renderObject(obj, indent);
        };

        const renderObject = (obj, indent = 0) => {
            return Object.entries(obj).map(([key, value]) => {
                const indentStr = '&nbsp;'.repeat(indent * 4);
                const count = countChildren(value);

                if (typeof value === 'object' && value !== null) {
                    const isArray = Array.isArray(value);
                    return `
                        <div class="json-item" data-json='${JSON.stringify(value)}'>
                            <span class="json-collapse"></span>
                            <span class="json-key">"${key}"</span>:
                            ${isArray ?
                                `[<span class="json-count">${count} items</span>` :
                                `{<span class="json-count">${count} keys</span>`}
                            <div class="json-content">
                                ${render(value, indent + 1)}
                            </div>
                            ${isArray ? ']' : '}'},
                        </div>
                    `;
                }

                // Handle image URLs
                if (typeof value === 'string' &&
                    (value.match(/\.(jpeg|jpg|gif|png)$/) ||
                     value.startsWith('data:image/'))) {
                    return `
                        <div class="json-item">
                            <span class="json-key">"${key}"</span>:
                            <img src="${value}" class="json-image" alt="${key} image"/>
                        </div>
                    `;
                }

                // Handle multi-line text
                if (typeof value === 'string' && value.includes('\n')) {
                    return `
                        <div class="json-item">
                            <span class="json-key">"${key}"</span>:
                            <div class="json-text">${value}</div>
                        </div>
                    `;
                }

                return `
                    <div class="json-item">
                        <span class="json-key">"${key}"</span>:
                        ${formatValue(value)},
                    </div>
                `;
            }).join('');
        };

        const formatValue = (value) => {
            if (typeof value === 'string') {
                // Skip formatting if it's an image URL or multi-line text
                if (value.match(/\.(jpeg|jpg|gif|png)$/) ||
                    value.startsWith('data:image/') ||
                    value.includes('\n')) {
                    return '';
                }
                return `<span class="json-string">"${value}"</span>`;
            }
            if (typeof value === 'number') return `<span class="json-number">${value}</span>`;
            if (typeof value === 'boolean') return `<span class="json-boolean">${value}</span>`;
            if (value === null) return `<span class="json-null">null</span>`;
            return '';
        };

        container.innerHTML = '';
        container.appendChild(addControlButtons(container, data));
        const jsonContainer = document.createElement('div');
        jsonContainer.style.marginTop = '20px';
        jsonContainer.innerHTML = render(data);
        container.appendChild(jsonContainer);

        // 修改折叠功能
        document.querySelectorAll('.json-collapse').forEach(collapse => {
            collapse.addEventListener('click', function() {
                const parent = this.parentElement;
                const content = parent.querySelector('.json-content');
                const countSpan = parent.querySelector('.json-count');
                const jsonData = JSON.parse(parent.getAttribute('data-json'));
                const isArray = Array.isArray(jsonData);

                if (content.style.display === 'none') {
                    content.style.display = '';
                    countSpan.textContent = isArray ?
                        `${jsonData.length} items` :
                        `${Object.keys(jsonData).length} keys`;
                } else {
                    content.style.display = 'none';
                    countSpan.textContent = isArray ?
                        `${jsonData.length} items` :
                        `${Object.keys(jsonData).length} keys`;
                }

                this.classList.toggle('collapsed');
            });
        });
    }
})();