API信息批量提取器

专为Apifox设计的API接口信息批量提取工具,支持自动监听、数据预览和批量导出

目前為 2025-07-01 提交的版本,檢視 最新版本

// ==UserScript==
// @name         API信息批量提取器
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  专为Apifox设计的API接口信息批量提取工具,支持自动监听、数据预览和批量导出
// @description:en  Batch API information extractor tool designed for Apifox, supports auto-monitoring, data preview and batch export
// @author       xiaoma
// @license      MIT
// @homepage     https://github.com/api-extractor/userscript
// @supportURL   https://github.com/api-extractor/userscript/issues
// @match        https://app.apifox.com/*
// @match        https://*.apifox.com/*
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBmaWxsPSIjNDA5RUZGIi8+Cjwvc3ZnPgo=
// @grant        none
// @run-at       document-end
// @noframes

// ==/UserScript==

(function() {
    'use strict';

    // 全局常量定义
    const STORAGE_KEY = 'api_extractor_data';
    const POSITION_STORAGE_KEY = 'api_extractor_panel_position';
    const UI_CONTAINER_ID = 'api-extractor-control-panel';

    // 全局状态管理
    let extractedApiDataCollection = [];
    let isAutoExtracting = false;
    let currentApiUrl = '';
    let apiUrlObserver = null;
    let existDialog = false;

    /**
     * 初始化脚本
     */
    function initScript() {
        try {
            loadSavedData();
            createUIControlPanel();
            console.log('API提取器已启动');
        } catch (errorInfo) {
            console.error('脚本初始化失败:', errorInfo);
        }
    }

    /**
     * 加载本地存储的数据
     */
    function loadSavedData() {
        try {
            const storedData = localStorage.getItem(STORAGE_KEY);
            extractedApiDataCollection = storedData ? JSON.parse(storedData) : [];
        } catch (errorInfo) {
            console.warn('加载存储数据失败:', errorInfo);
            extractedApiDataCollection = [];
        }
    }

    /**
     * 保存数据到本地存储
     */
    function saveDataToLocalStorage() {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(extractedApiDataCollection));
        } catch (errorInfo) {
            console.error('保存数据失败:', errorInfo);
        }
    }

    /**
     * 创建UI控制面板
     */
    function createUIControlPanel() {
        // 检查是否已存在控制面板
        if (document.getElementById(UI_CONTAINER_ID)) return;

        // 获取保存的位置
        const savedPosition = getSavedPanelPosition();

        const controlPanelContainer = document.createElement('div');
        controlPanelContainer.id = UI_CONTAINER_ID;
        controlPanelContainer.style.cssText = `
            position: fixed;
            top: ${savedPosition.top}px;
            left: ${savedPosition.left}px;
            z-index: 9999;
            background: #fafbfc;
            border: 1px solid #e1e8ed;
            border-radius: 6px;
            padding: 12px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            width: 300px;
            cursor: move;
            user-select: none;
            transition: opacity 0.2s;
        `;

        // 标题栏
        const titleBar = document.createElement('div');
        titleBar.style.cssText = `
            font-weight: 500;
            color: #536471;
            margin-bottom: 8px;
            text-align: center;
            font-size: 13px;
            padding-bottom: 6px;
        `;
        titleBar.textContent = 'API提取器';

        // 按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; flex-direction: column; gap: 6px;';

        // 提取当前API按钮
        const extractButton = createButton('提取当前API', '#9373ee', handleExtractCurrentApi);

        // 下载全部按钮
        const downloadButton = createButton(`下载全部(${extractedApiDataCollection.length})`, '#9373ee', handleDownloadAllData);

        // 预览数据按钮
        const previewButton = createButton('预览数据', '#ef6820', handlePreviewData);

        // 清空数据按钮
        const clearButton = createButton('清空数据', '#dc3545', handleClearAllData);

        // 自动提取按钮
        const autoExtractButton = createButton('自动提取', '#1890ff', handleAutoExtract);

        // 组装UI
        buttonContainer.appendChild(extractButton);
        buttonContainer.appendChild(downloadButton);
        buttonContainer.appendChild(previewButton);
        buttonContainer.appendChild(autoExtractButton);
        buttonContainer.appendChild(clearButton);

        // 存储按钮引用
        window.autoExtractButtonRef = autoExtractButton;

        // 信息显示区域
        const infoArea = document.createElement('div');
        infoArea.id = 'api-extractor-info';
        infoArea.style.cssText = `
            background: #f8f9fa;
            border: 1px solid #e1e8ed;
            border-radius: 4px;
            padding: 8px;
            margin-top: 8px;
            margin-bottom: 8px;
            font-size: 12px;
            color: #536471;
            min-height: 20px;
            font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
            line-height: 1.3;
            white-space: pre-wrap;
            word-wrap: break-word;
        `;
        infoArea.textContent = '就绪';

        controlPanelContainer.appendChild(titleBar);
        controlPanelContainer.appendChild(infoArea);
        controlPanelContainer.appendChild(buttonContainer);

        document.body.appendChild(controlPanelContainer);

        // 存储按钮引用以便后续更新
        window.downloadButtonRef = downloadButton;

        // 添加拖拽功能
        makePanelDraggable(controlPanelContainer);
    }

    /**
     * 创建通用按钮
     */
    function createButton(buttonText, backgroundColor, clickHandler) {
        const buttonElement = document.createElement('button');
        buttonElement.textContent = buttonText;
        buttonElement.style.cssText = `
            background: ${backgroundColor};
            color: white;
            border: none;
            border-radius: 4px;
            padding: 6px 10px;
            cursor: pointer;
            font-size: 12px;
            font-weight: 400;
            transition: all 0.2s ease;
            line-height: 1.2;
        `;

        // 鼠标悬停效果
        buttonElement.addEventListener('mouseenter', () => {
            buttonElement.style.transform = 'translateY(-1px)';
            buttonElement.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
        });

        buttonElement.addEventListener('mouseleave', () => {
            buttonElement.style.transform = 'translateY(0)';
            buttonElement.style.boxShadow = 'none';
        });

        buttonElement.addEventListener('click', clickHandler);
        return buttonElement;
    }

    /**
     * 获取保存的面板位置
     */
    function getSavedPanelPosition() {
        try {
            const savedPosition = localStorage.getItem(POSITION_STORAGE_KEY);
            if (savedPosition) {
                return JSON.parse(savedPosition);
            }
        } catch (error) {
            console.warn('读取面板位置失败:', error);
        }

        // 默认位置:视口正上方居中
        return {
            top: 20,
            left: Math.max(20, (window.innerWidth - 300) / 2)
        };
    }

    /**
     * 保存面板位置
     */
    function savePanelPosition(top, left) {
        try {
            const position = { top, left };
            localStorage.setItem(POSITION_STORAGE_KEY, JSON.stringify(position));
        } catch (error) {
            console.warn('保存面板位置失败:', error);
        }
    }

    /**
     * 使面板可拖拽
     */
    function makePanelDraggable(panel) {
        let isDragging = false;
        let dragOffsetX = 0;
        let dragOffsetY = 0;

        panel.addEventListener('mousedown', function(e) {
            // 防止在按钮上开始拖拽
            if (e.target.tagName === 'BUTTON') return;

            isDragging = true;

            // 设置拖拽时的半透明效果
            panel.style.opacity = '0.7';

            // 计算鼠标相对于面板的偏移
            const rect = panel.getBoundingClientRect();
            dragOffsetX = e.clientX - rect.left;
            dragOffsetY = e.clientY - rect.top;

            // 阻止文本选择
            e.preventDefault();
        });

        document.addEventListener('mousemove', function(e) {
            if (!isDragging) return;

            // 计算新位置
            let newLeft = e.clientX - dragOffsetX;
            let newTop = e.clientY - dragOffsetY;

            // 边界检查,确保面板不会超出视口
            const maxLeft = window.innerWidth - panel.offsetWidth;
            const maxTop = window.innerHeight - panel.offsetHeight;

            newLeft = Math.max(0, Math.min(newLeft, maxLeft));
            newTop = Math.max(0, Math.min(newTop, maxTop));

            // 更新面板位置
            panel.style.left = newLeft + 'px';
            panel.style.top = newTop + 'px';
        });

        document.addEventListener('mouseup', function() {
            if (!isDragging) return;

            isDragging = false;

            // 恢复不透明度
            panel.style.opacity = '1';

            // 保存当前位置
            const rect = panel.getBoundingClientRect();
            savePanelPosition(rect.top, rect.left);
        });
    }

    /**
     * 处理提取当前API
     */
    function handleExtractCurrentApi() {
        try {
            const apiData = extractCurrentPageApiInfo();

            if (!apiData.apiName) {
                showInfoInPanel('未检测到有效的API接口信息');
                return;
            }

            // 检查是否已存在相同API
            const existingIndex = extractedApiDataCollection.findIndex(item =>
                item.apiName === apiData.apiName && item.apiUrl === apiData.apiUrl
            );

            if (existingIndex !== -1) {
                // 更新现有数据
                extractedApiDataCollection[existingIndex] = apiData;
                showInfoInPanel(`已更新: ${apiData.apiName}`);
            } else {
                // 添加新数据
                extractedApiDataCollection.push(apiData);
                showInfoInPanel(`已提取: ${apiData.apiName}`);
            }

            saveDataToLocalStorage();
            updateDownloadButtonText();

        } catch (errorInfo) {
            console.error('提取API信息失败:', errorInfo);
            showInfoInPanel('提取失败: ' + errorInfo.message);
        }
    }

    /**
     * 提取当前页面的API信息
     */
    function extractCurrentPageApiInfo() {
        const apiInfo = {
            apiName: '',
            apiUrl: '',
            requestMethod: '',
            requestParams: [],
            responseInfo: '暂无响应信息'
        };

        // 提取接口名称
        apiInfo.apiName = safeExtractText('.name-text-nO_wGQ .copyable-NYoI4L');

        // 提取请求方式和URL
        const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn');
        if (pathInfoContainer) {
            apiInfo.requestMethod = safeExtractText('.base-info-path-lbh3Yn code', pathInfoContainer);
            apiInfo.apiUrl = safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer);
        }

        // 提取请求参数
        apiInfo.requestParams = extractRequestParamsStructure();

        return apiInfo;
    }

    /**
     * 安全提取文本内容
     */
    function safeExtractText(selector, parentElement = document) {
        try {
            const element = parentElement.querySelector(selector);
            return element ? element.textContent.trim() : '';
        } catch (error) {
            console.warn(`提取文本失败 ${selector}:`, error);
            return '';
        }
    }

    /**
     * 提取请求参数结构(支持树状结构)
     */
    function extractRequestParamsStructure() {
        try {
            // 查找 JsonSchemaViewer 根容器
            const schemaViewer = document.querySelector('.JsonSchemaViewer');
            if (!schemaViewer) {
                console.log('[DEBUG] 未找到 JsonSchemaViewer 容器');
                return [];
            }

            // 查找主要的 data-level="0" 容器
            const mainContainer = schemaViewer.querySelector('[data-level="0"]');
            if (!mainContainer) {
                console.log('[DEBUG] 未找到 data-level="0" 容器');
                return [];
            }

            console.log('[DEBUG] 找到主容器,子元素数量:', mainContainer.children.length);

            const paramList = [];

            // 获取容器内的所有直接子元素
            const allChildren = Array.from(mainContainer.children);

            // 收集所有子参数容器
            const childContainers = allChildren.filter(child =>
                child.querySelector('.index_child-stack__WPMqo')
            );

            console.log(`[DEBUG] 找到 ${childContainers.length} 个子参数容器`);

            console.log('[DEBUG] 开始遍历子元素...');

            for (let i = 0; i < allChildren.length; i++) {
                const child = allChildren[i];

                console.log(`[DEBUG] 子元素 ${i}:`, {
                    tagName: child.tagName,
                    className: child.className,
                    dataLevel: child.getAttribute('data-level'),
                    style: child.style ? child.style.marginLeft : 'no style',
                    hasParamNode: !!child.querySelector('.index_node__G6-Qx')
                });

                // 检查是否是参数节点容器
                if (child.style && child.style.marginLeft === '0px') {
                    console.log(`[DEBUG] 子元素 ${i} 符合marginLeft条件`);

                    const paramNode = child.querySelector('.index_node__G6-Qx');
                    if (paramNode) {
                        console.log(`[DEBUG] 子元素 ${i} 找到参数节点`);

                        const paramInfo = parseBasicParamInfo(paramNode);
                        if (paramInfo.paramName) {
                            console.log(`[DEBUG] 解析到参数: ${paramInfo.paramName}`);

                            // 新的匹配策略:根据已知的父子关系来匹配
                            const childParams = findChildParamsByParentName(paramInfo.paramName, childContainers);
                            console.log(`[DEBUG] 为 ${paramInfo.paramName} 找到 ${childParams.length} 个子参数:`, childParams.map(c => c.paramName));

                            if (childParams.length > 0) {
                                paramInfo.children = childParams;
                            }

                            paramList.push(paramInfo);
                        }
                    }
                } else {
                    console.log(`[DEBUG] 子元素 ${i} 不符合marginLeft条件, marginLeft:`, child.style ? child.style.marginLeft : 'no style');
                }
            }

            console.log('[DEBUG] 最终提取的参数数量:', paramList.length);
            return paramList;

        } catch (error) {
            console.error('[DEBUG] 提取参数结构失败:', error);
            return [];
        }
    }

    /**
     * 根据父参数名称查找对应的子参数
     */
    function findChildParamsByParentName(parentName, childContainers) {
        console.log(`[DEBUG] 为父参数 ${parentName} 查找子参数...`);

        // 定义已知的父子关系映射
        const parentChildMap = {
            'functionList': ['funcId', 'funcName'],
            'machineTypeList': ['machineTypeId', 'machineTypeName'],
            'packTypeInternationals': ['businessType', 'content', 'id', 'lang']
        };

        const expectedChildren = parentChildMap[parentName];
        if (!expectedChildren) {
            console.log(`[DEBUG] ${parentName} 不在已知的父子关系映射中`);
            return [];
        }

        console.log(`[DEBUG] ${parentName} 期望的子参数:`, expectedChildren);

        // 在所有子参数容器中查找匹配的子参数
        for (let container of childContainers) {
            const foundChildren = extractChildrenFromLevel1Container(container);
            console.log(`[DEBUG] 容器中的子参数:`, foundChildren.map(c => c.paramName));

            // 检查是否包含期望的子参数
            const matchCount = foundChildren.filter(child =>
                expectedChildren.includes(child.paramName)
            ).length;

            console.log(`[DEBUG] 匹配数量: ${matchCount}/${expectedChildren.length}`);

            // 如果匹配数量大于等于期望数量的一半,就认为找到了对应的容器
            if (matchCount >= Math.ceil(expectedChildren.length / 2)) {
                console.log(`[DEBUG] 为 ${parentName} 找到匹配的子参数容器`);
                return foundChildren.filter(child => expectedChildren.includes(child.paramName));
            }
        }

        console.log(`[DEBUG] 未为 ${parentName} 找到匹配的子参数容器`);
        return [];
    }

    /**
     * 从 data-level="1" 容器中提取子参数
     */
    function extractChildrenFromLevel1Container(level1Container) {
        const childParams = [];

        try {
            // 查找所有子参数节点
            const childNodes = level1Container.querySelectorAll('.index_child-stack__WPMqo > .index_node__G6-Qx');

            childNodes.forEach(childNode => {
                const childParam = parseBasicParamInfo(childNode);
                if (childParam.paramName) {
                    childParams.push(childParam);
                }
            });

        } catch (error) {
            console.warn('提取子参数失败:', error);
        }

        return childParams;
    }

    /**
     * 解析基本参数信息
     */
    function parseBasicParamInfo(node) {
        const paramInfo = {
            paramName: '',
            paramType: '',
            isRequired: false,
            paramDescription: ''
        };

        try {
            // 获取参数名
            const paramNameElement = node.querySelector('.propertyName-Zh4tse .copyable-NYoI4L');
            paramInfo.paramName = paramNameElement ? paramNameElement.textContent.trim() : '';

            // 获取参数类型
            const typeElement = node.querySelector('.sl-type span');
            paramInfo.paramType = typeElement ? typeElement.textContent.trim() : '';

            // 判断是否必填
            const optionalFlag = node.querySelector('.index_optional__O33wK');
            paramInfo.isRequired = !optionalFlag;

            // 获取参数描述 - 优先从 json-schema-viewer__description 获取
            let paramDescription = '';

            // 首先尝试从描述区域获取
            const descriptionElement = node.querySelector('.json-schema-viewer__description p');
            if (descriptionElement && descriptionElement.textContent.trim()) {
                paramDescription = descriptionElement.textContent.trim();
            }
            // 如果没有,尝试从 title 属性获取
            else {
                const titleElement = node.querySelector('.index_additionalInformation__title__cjvSn[title]');
                if (titleElement && titleElement.getAttribute('title')) {
                    paramDescription = titleElement.getAttribute('title');
                }
                // 最后尝试从 titleElement 的文本内容获取
                else if (titleElement && titleElement.textContent.trim()) {
                    paramDescription = titleElement.textContent.trim();
                }
            }

            paramInfo.paramDescription = paramDescription;

        } catch (error) {
            console.warn('解析基本参数信息失败:', error);
        }

        return paramInfo;
    }

    /**
     * 处理下载全部数据
     */
    function handleDownloadAllData() {
        if (extractedApiDataCollection.length === 0) {
            showInfoInPanel('暂无数据可下载');
            return;
        }

        try {
            const exportData = {
                exportTime: new Date().toLocaleString('zh-CN'),
                dataCount: extractedApiDataCollection.length,
                apiList: extractedApiDataCollection
            };

            const dataString = JSON.stringify(exportData, null, 2);
            const fileName = `API接口数据_${new Date().toISOString().slice(0,10)}.json`;

            downloadJsonFile(dataString, fileName);
            showInfoInPanel(`已下载 ${extractedApiDataCollection.length} 条API数据`);

        } catch (errorInfo) {
            console.error('下载数据失败:', errorInfo);
            showInfoInPanel('下载失败: ' + errorInfo.message);
        }
    }

    /**
     * 下载JSON文件
     */
    function downloadJsonFile(dataContent, fileName) {
        const dataUrl = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataContent);
        const downloadLink = document.createElement('a');
        downloadLink.href = dataUrl;
        downloadLink.download = fileName;
        downloadLink.style.display = 'none';

        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
    }

    /**
     * 处理清空所有数据
     */
    function handleClearAllData() {
        if (extractedApiDataCollection.length === 0) {
            showInfoInPanel('暂无数据需要清空');
            return;
        }

        if (confirm(`确定要清空所有 ${extractedApiDataCollection.length} 条API数据吗?`)) {
            const clearedCount = extractedApiDataCollection.length;
            extractedApiDataCollection = [];
            saveDataToLocalStorage();
            updateDownloadButtonText();
            showInfoInPanel(`已清空 ${clearedCount} 条API数据`);
        }
    }

    /**
     * 处理预览数据
     */
    function handlePreviewData() {
        if (extractedApiDataCollection.length === 0) {
            showInfoInPanel('暂无数据可预览');
            return;
        }

        const previewData = {
            exportTime: new Date().toLocaleString('zh-CN'),
            dataCount: extractedApiDataCollection.length,
            apiList: extractedApiDataCollection
        };

        showJsonPreviewModal(previewData);
    }

    /**
     * 处理自动提取
     */
    function handleAutoExtract() {
        if (!isAutoExtracting) {
            // 开始自动提取
            startAutoExtract();
        } else {
            // 停止自动提取
            stopAutoExtract();
        }
    }

    /**
     * 开始自动提取
     */
    function startAutoExtract() {
        isAutoExtracting = true;

        // 获取当前接口URL
        const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn');
        currentApiUrl = pathInfoContainer ? safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer) : '';

        // 更新按钮状态
        updateAutoExtractButton();

        // 先提取当前页面
        setTimeout(() => {
            handleExtractCurrentApi();
        }, 500);

        // 开始监听接口URL变化
        startApiUrlMonitoring();

        showInfoInPanel('自动提取已开启,正在监听接口变化');
    }

    /**
     * 停止自动提取
     */
    function stopAutoExtract() {
        isAutoExtracting = false;

        // 更新按钮状态
        updateAutoExtractButton();

        // 停止接口URL监听
        stopApiUrlMonitoring();

        showInfoInPanel('自动提取已停止');
    }

    /**
     * 更新自动提取按钮状态
     */
    function updateAutoExtractButton() {
        if (window.autoExtractButtonRef) {
            if (isAutoExtracting) {
                window.autoExtractButtonRef.textContent = '停止提取';
                window.autoExtractButtonRef.style.background = '#dc3545';
            } else {
                window.autoExtractButtonRef.textContent = '自动提取';
                window.autoExtractButtonRef.style.background = '#1890ff';
            }
        }
    }

    /**
     * 开始监听接口URL变化
     */
    function startApiUrlMonitoring() {
        const targetContainer = document.querySelector('.base-info-path-lbh3Yn');
        if (!targetContainer) {
            showInfoInPanel('未找到接口信息容器,无法开启自动提取');
            stopAutoExtract();
            return;
        }

        // 使用MutationObserver监听DOM变化
        apiUrlObserver = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'characterData' || mutation.type === 'subtree') {
                    handleApiUrlChange();
                }
            });
        });

        // 开始观察目标容器及其子元素
        apiUrlObserver.observe(targetContainer, {
            childList: true,
            subtree: true,
            characterData: true,
            attributes: false
        });

        // 备用定时检查
        window.apiUrlCheckInterval = setInterval(() => {
            if (isAutoExtracting) {
                handleApiUrlChange();
            }
        }, 100);
    }

    /**
     * 停止监听接口URL变化
     */
    function stopApiUrlMonitoring() {
        // 停止MutationObserver
        if (apiUrlObserver) {
            apiUrlObserver.disconnect();
            apiUrlObserver = null;
        }

        // 清除定时器
        if (window.apiUrlCheckInterval) {
            clearInterval(window.apiUrlCheckInterval);
            window.apiUrlCheckInterval = null;
        }
    }

    /**
     * 处理接口URL变化
     */
    function handleApiUrlChange() {
        if (!isAutoExtracting) return;

        const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn');
        if (!pathInfoContainer) return;

        const newApiUrl = safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer);

        if (newApiUrl && newApiUrl !== currentApiUrl) {
            currentApiUrl = newApiUrl;

            // 延迟提取,确保页面内容完全更新
            setTimeout(() => {
                if (isAutoExtracting) {
                    console.log('检测到接口变化:', newApiUrl);
                    handleExtractCurrentApi();
                }
            }, 800);
        }
    }

    /**
     * 更新下载按钮文本
     */
    function updateDownloadButtonText() {
        if (window.downloadButtonRef) {
            window.downloadButtonRef.textContent = `下载全部(${extractedApiDataCollection.length})`;
        }
    }

    /**
     * 在面板中显示信息
     */
    function showInfoInPanel(content) {
        const infoArea = document.getElementById('api-extractor-info');

        if (!infoArea) return;

        // 更新内容
        infoArea.textContent = content;
    }

    /**
     * 清除所有模态框
     */
    function clearAllModals() {
        // 移除所有可能的模态框
        const modalSelectors = [
            '#json-preview-modal',
            '[id$="-modal"]',
            '[class*="modal"]',
            '[style*="z-index: 10001"]'
        ];

        modalSelectors.forEach(selector => {
            const modals = document.querySelectorAll(selector);
            modals.forEach(modal => modal.remove());
        });

        // 重置弹窗状态
        existDialog = false;
    }

    /**
     * 显示JSON预览模态框
     */
    function showJsonPreviewModal(data) {
        // 如果已存在弹窗,先销毁
        if (existDialog) {
            clearAllModals();
        }

        // 设置弹窗存在标记
        existDialog = true;

        // 创建模态框容器
        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'json-preview-modal';
        modalOverlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 10001;
            display: flex;
            align-items: center;
            justify-content: center;
            backdrop-filter: blur(2px);
        `;

        // 创建模态框内容
        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            background: #ffffff;
            border-radius: 8px;
            padding: 20px;
            max-width: 90%;
            max-height: 80%;
            overflow: hidden;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            display: flex;
            flex-direction: column;
            min-width: 600px;
        `;

        // 标题栏
        const modalHeader = document.createElement('div');
        modalHeader.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #e9ecef;
        `;
        modalHeader.innerHTML = `
            <h3 style="margin: 0; color: #536471; font-size: 15px; font-weight: 500;">JSON数据预览 (${data.dataCount}条)</h3>
            <div>
                <button id="copy-json-btn" style="
                    background: #17b26a;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 6px 12px;
                    cursor: pointer;
                    font-size: 12px;
                    margin-right: 6px;
                    font-weight: 400;
                ">复制</button>
                <button id="close-modal-btn" style="
                    background: #9aa0a6;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 6px 12px;
                    cursor: pointer;
                    font-size: 12px;
                    font-weight: 400;
                ">关闭</button>
            </div>
        `;

        // JSON内容区域
        const jsonContent = document.createElement('pre');
        jsonContent.style.cssText = `
            background: #f8f9fa;
            border: 1px solid #e1e8ed;
            border-radius: 4px;
            padding: 12px;
            margin: 0;
            overflow: auto;
            flex: 1;
            font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
            font-size: 12px;
            line-height: 1.4;
            color: #536471;
            white-space: pre-wrap;
            word-wrap: break-word;
        `;
        jsonContent.textContent = JSON.stringify(data, null, 2);

        // 组装模态框
        modalContent.appendChild(modalHeader);
        modalContent.appendChild(jsonContent);
        modalOverlay.appendChild(modalContent);
        document.body.appendChild(modalOverlay);

        // 绑定事件
        document.getElementById('close-modal-btn').addEventListener('click', () => {
            modalOverlay.remove();
            existDialog = false;
        });

        document.getElementById('copy-json-btn').addEventListener('click', () => {
            navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
                showInfoInPanel('JSON数据已复制到剪贴板');
            }).catch(() => {
                showInfoInPanel('复制失败,请手动选择复制');
            });
        });

        // 点击背景关闭
        modalOverlay.addEventListener('click', (e) => {
            if (e.target === modalOverlay) {
                modalOverlay.remove();
                existDialog = false;
            }
        });

        // ESC键关闭
        const escHandler = (e) => {
            if (e.key === 'Escape') {
                modalOverlay.remove();
                existDialog = false;
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initScript);
    } else {
        initScript();
    }

})();