فهرست منبع

upgrade screenshot tools

zxlie 6 ماه پیش
والد
کامیت
63a3b57a31

+ 4 - 1
.cursorrules

@@ -1,5 +1,7 @@
 # 角色
-你是一个Chrome浏览器扩展开发专家,对Chrome Extension Manifest V3非常熟悉。你需要帮助我开发和维护一个名为FeHelper的Chrome扩展。
+- 你是一个Chrome浏览器扩展开发专家,对Chrome Extension Manifest V3非常熟悉。你需要帮助我开发和维护一个名为FeHelper的Chrome扩展。
+- 你很了解当前这个FeHelper插件的代码结构,以及每个功能模块的实现方式。
+- 你能根据README.md文件中的插件描述,读懂插件中包含的各个工具的功能,并能根据描述,给出工具的实现方案。
 
 # 项目结构规范
 - apps/目录是项目的主目录
@@ -23,6 +25,7 @@
 - 需要在web_accessible_resources中声明可访问的资源
 - 遵循Chrome Extension的安全策略和最佳实践
 - 所有新建的工具主文件index.html,都需要检查一下是否增加了顶部导航栏,导航栏的样式需要和旧的工具保持一致(具体参考options/index.html中关于导航部分的实现:左侧是图标和工具名称,右侧是打赏按钮)
+- 每次修改代码,一定要从全局功能出发,而不是修改局部,不要额外增加我的调试成本
 
 # 注意事项
 - 权限申请需要最小化原则

+ 1 - 0
.gitignore

@@ -8,3 +8,4 @@ Users/
 output/
 output-firefox/
 output-edge/
+.vscode/

+ 55 - 2
apps/background/background.js

@@ -272,11 +272,16 @@ let BgPageInstance = (function () {
 
     let _addScreenShotByPages = function(params,callback){
         chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, uri => {
-            callback({ params,uri });
+            callback({ params, uri });
         });
     };
 
     let _showScreenShotResult = function(data){
+        // 确保截图数据完整有效
+        if (!data || !data.screenshots || !data.screenshots.length) {
+            return;
+        }
+        
         chrome.DynamicToolRunner({
             tool: 'screenshot',
             withContent: data
@@ -316,7 +321,6 @@ let BgPageInstance = (function () {
         chrome.runtime.onMessage.addListener(function (request, sender, callback) {
             // 如果发生了错误,就啥都别干了
             if (chrome.runtime.lastError) {
-                console.log('chrome.runtime.lastError:',chrome.runtime.lastError);
                 return true;
             }
 
@@ -329,6 +333,10 @@ let BgPageInstance = (function () {
             else if (request.type === MSG_TYPE.CAPTURE_VISIBLE_PAGE) {
                 _captureVisibleTab(callback);
             }
+            // 直接处理content-script.js中的截图请求
+            else if (request.type === 'fh-screenshot-capture-visible') {
+                _captureVisibleTab(callback);
+            }
             // 打开动态工具页面
             else if (request.type === MSG_TYPE.OPEN_DYNAMIC_TOOL) {
                 chrome.DynamicToolRunner(request);
@@ -350,6 +358,17 @@ let BgPageInstance = (function () {
                             autoClose: 2000
                         });
                         break;
+                    case 'trigger-screenshot':
+                        // 处理从popup触发的截图请求
+                        if (request.tabId) {
+                            _triggerScreenshotTool(request.tabId);
+                        } else {
+                            chrome.DynamicToolRunner({
+                                tool: 'screenshot',
+                                noPage: true
+                            });
+                        }
+                        break;
                     case 'request-jsonformat-options':
                         Awesome.StorageMgr.get(request.params).then(result => {
                             Object.keys(result).forEach(key => {
@@ -486,6 +505,21 @@ let BgPageInstance = (function () {
     let _init = function () {
         _checkUpdate();
         _addExtensionListener();
+        
+        // 添加截图工具直接命令 - 通过右键菜单触发
+        chrome.contextMenus.onClicked.addListener((info, tab) => {
+            if (info.menuItemId === 'fehelper-screenshot-page') {
+                _triggerScreenshotTool(tab.id);
+            }
+        });
+        
+        // 创建截图工具右键菜单
+        chrome.contextMenus.create({
+            id: 'fehelper-screenshot-page',
+            title: '网页截图',
+            contexts: ['page']
+        });
+        
         Menu.rebuild();
         // 定期清理冗余的垃圾
         setTimeout(() => {
@@ -493,6 +527,25 @@ let BgPageInstance = (function () {
         }, 1000 * 10);
     };
 
+    /**
+     * 触发截图工具的执行
+     * @param {number} tabId - 标签页ID
+     */
+    function _triggerScreenshotTool(tabId) {
+        // 先尝试直接发送消息给content script
+        chrome.tabs.sendMessage(tabId, {
+            type: 'fh-screenshot-start'
+        }).then(() => {
+            // 成功触发
+        }).catch(() => {
+            // 如果发送消息失败,使用noPage模式
+            chrome.DynamicToolRunner({
+                tool: 'screenshot',
+                noPage: true
+            });
+        });
+    }
+
     return {
         pageCapture: _captureVisibleTab,
         init: _init

+ 1 - 0
apps/json-format/content-script.js

@@ -602,6 +602,7 @@ window.JsonAutoFormat = (() => {
                     thing:'inject-content-css',
                     tool: 'json-format'
                 });
+                cssInjected = true;
             }
 
             // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡

+ 1 - 0
apps/manifest.json

@@ -59,6 +59,7 @@
             "json-format/json-decode.js",
             "static/vendor/jquery/jquery-3.3.1.min.js",
             "static/vendor/evalCore.min.js",
+            "screenshot/content-script.js",
 
             "code-beautify/beautify.js",
             "code-beautify/beautify-css.js",

+ 39 - 1
apps/popup/index.js

@@ -5,6 +5,31 @@
 import Awesome from '../background/awesome.js'
 import MSG_TYPE from '../static/js/common.js';
 
+function triggerScreenshot() {
+    chrome.tabs.query({active: true, currentWindow: true}, tabs => {
+        if (!tabs || !tabs.length || !tabs[0].id) return;
+        
+        const tabId = tabs[0].id;
+        
+        // 先尝试直接发送消息给content script
+        chrome.tabs.sendMessage(tabId, {
+            type: 'fh-screenshot-start'
+        }).then(response => {
+            console.log('截图工具触发成功');
+            window.close();
+        }).catch(error => {
+            console.log('无法直接触发截图工具,尝试使用noPage模式', error);
+            // 如果发送消息失败,使用noPage模式
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing',
+                thing: 'trigger-screenshot',
+                tabId: tabId
+            });
+            window.close();
+        });
+    });
+}
+
 new Vue({
     el: '#pageContainer',
     data: {
@@ -25,7 +50,6 @@ new Vue({
     },
 
     mounted: function () {
-
         // 整个popup窗口支持上线选择
         document.body.addEventListener('keydown', e => {
             let keyCode = e.keyCode || e.which;
@@ -59,6 +83,20 @@ new Vue({
             }
 
         }, false);
+
+        // 查找截图按钮并绑定事件
+        const screenshotButtons = Array.from(document.querySelectorAll('a[data-tool="screenshot"], button[data-tool="screenshot"]'));
+        
+        screenshotButtons.forEach(button => {
+            // 移除原有的点击事件
+            const oldClick = button.onclick;
+            button.onclick = function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                triggerScreenshot();
+                return false;
+            };
+        });
     },
 
     methods: {

+ 1092 - 248
apps/screenshot/content-script.js

@@ -1,72 +1,111 @@
 /**
  * FeHelper Full Page Capture
- * @type {{scroll}}
+ * @author FeHelper
+ * @version 1.0.1
  */
 window.screenshotContentScript = function () {
-
+    // 存储截图数据的数组
     let screenshots = [];
-    let capturedData = {};
-    let MAX_PRIMARY_DIMENSION = 50000 * 2,
+    // 定义最大尺寸限制常量
+    const MAX_PRIMARY_DIMENSION = 50000 * 2,
         MAX_SECONDARY_DIMENSION = 20000 * 2,
         MAX_AREA = MAX_PRIMARY_DIMENSION * MAX_SECONDARY_DIMENSION;
-        let pageOriginalTitle = document.title;
+    // 保存原始页面标题
+    const pageOriginalTitle = document.title;
+    // 是否正在截图中
+    let isCapturing = false;
+    // 取消截图的标志
+    let isCancelled = false;
+
+    // 定义全局变量以存储原始滚动位置
+    let originalScrollLeft = 0;
+    let originalScrollTop = 0;
 
     /**
      * URL合法性校验
-     * @param url
-     * @returns {boolean}
+     * @param {string} url - 要检查的URL
+     * @returns {boolean} - URL是否合法
      */
     function isValidUrl(url) {
+        // 允许的URL模式
+        const matches = ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*'];
+        // 不允许的URL模式
+        const noMatches = [/^https?:\/\/chrome\.google\.com\/.*$/];
 
-        let matches = ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*'],
-            noMatches = [/^https?:\/\/chrome.google.com\/.*$/];
-
-        let r, i;
-        for (i = noMatches.length - 1; i >= 0; i--) {
+        // 先检查不允许的URL
+        for (let i = 0; i < noMatches.length; i++) {
             if (noMatches[i].test(url)) {
                 return false;
             }
         }
-        for (i = matches.length - 1; i >= 0; i--) {
-            r = new RegExp('^' + matches[i].replace(/\*/g, '.*') + '$');
-            if (r.test(url)) {
+        
+        // 再检查允许的URL
+        for (let i = 0; i < matches.length; i++) {
+            const pattern = matches[i].replace(/\*/g, '.*');
+            const regex = new RegExp('^' + pattern + '$');
+            if (regex.test(url)) {
                 return true;
             }
         }
+        
         return false;
     }
 
     /**
-     * 如果页面超级超级长,需要拆分成多张图片来存储
-     * @param totalWidth
-     * @param totalHeight
-     * @returns {Array}
+     * 释放canvas资源
+     * @param {Array} canvasList - 要释放的canvas列表
+     */
+    function releaseCanvasResources(canvasList) {
+        if (!canvasList || !canvasList.length) return;
+        
+        canvasList.forEach(item => {
+            if (item.ctx) {
+                item.ctx.clearRect(0, 0, item.canvas.width, item.canvas.height);
+            }
+            if (item.canvas) {
+                item.canvas.width = 0;
+                item.canvas.height = 0;
+            }
+        });
+    }
+
+    /**
+     * 初始化截图canvas
+     * @param {number} totalWidth - 总宽度
+     * @param {number} totalHeight - 总高度
+     * @returns {Array} - 初始化的canvas数组
      * @private
      */
     function _initScreenshots(totalWidth, totalHeight) {
-        let badSize = (totalHeight > MAX_PRIMARY_DIMENSION ||
+        // 检查尺寸是否超过限制
+        const badSize = (totalHeight > MAX_PRIMARY_DIMENSION ||
             totalWidth > MAX_PRIMARY_DIMENSION ||
-            totalHeight * totalWidth > MAX_AREA),
-            biggerWidth = totalWidth > totalHeight,
-            maxWidth = (!badSize ? totalWidth :
-                (biggerWidth ? MAX_PRIMARY_DIMENSION : MAX_SECONDARY_DIMENSION)),
-            maxHeight = (!badSize ? totalHeight :
-                (biggerWidth ? MAX_SECONDARY_DIMENSION : MAX_PRIMARY_DIMENSION)),
-            numCols = Math.ceil(totalWidth / maxWidth),
-            numRows = Math.ceil(totalHeight / maxHeight),
-            row, col, canvas, left, top;
-
+            totalHeight * totalWidth > MAX_AREA);
+        const biggerWidth = totalWidth > totalHeight;
+        
+        // 计算每个分块的最大尺寸
+        const maxWidth = (!badSize ? totalWidth :
+            (biggerWidth ? MAX_PRIMARY_DIMENSION : MAX_SECONDARY_DIMENSION));
+        const maxHeight = (!badSize ? totalHeight :
+            (biggerWidth ? MAX_SECONDARY_DIMENSION : MAX_PRIMARY_DIMENSION));
+        
+        // 计算分块数量
+        const numCols = Math.ceil(totalWidth / maxWidth);
+        const numRows = Math.ceil(totalHeight / maxHeight);
+        
+        // 创建结果数组
+        const result = [];
         let canvasIndex = 0;
-        let result = [];
 
-        for (row = 0; row < numRows; row++) {
-            for (col = 0; col < numCols; col++) {
-                canvas = document.createElement('canvas');
+        // 创建所有需要的canvas
+        for (let row = 0; row < numRows; row++) {
+            for (let col = 0; col < numCols; col++) {
+                const canvas = document.createElement('canvas');
                 canvas.width = (col === numCols - 1 ? totalWidth % maxWidth || maxWidth : maxWidth);
                 canvas.height = (row === numRows - 1 ? totalHeight % maxHeight || maxHeight : maxHeight);
 
-                left = col * maxWidth;
-                top = row * maxHeight;
+                const left = col * maxWidth;
+                const top = row * maxHeight;
 
                 result.push({
                     canvas: canvas,
@@ -85,123 +124,493 @@ window.screenshotContentScript = function () {
         return result;
     }
 
-
     /**
      * 从截屏中筛选有效数据
-     * @param imgLeft
-     * @param imgTop
-     * @param imgWidth
-     * @param imgHeight
-     * @param screenshots
+     * @param {number} imgLeft - 图像左边界
+     * @param {number} imgTop - 图像上边界
+     * @param {number} imgWidth - 图像宽度
+     * @param {number} imgHeight - 图像高度
+     * @param {Array} screenshotList - 截图列表
+     * @returns {Array} - 筛选后的截图列表
      * @private
      */
-    function _filterScreenshots(imgLeft, imgTop, imgWidth, imgHeight, screenshots) {
-        // Filter down the screenshots to ones that match the location
-        // of the given image.
-        let imgRight = imgLeft + imgWidth,
-            imgBottom = imgTop + imgHeight;
-        return screenshots.filter(function (screenshot) {
-            return (imgLeft < screenshot.right &&
-                imgRight > screenshot.left &&
-                imgTop < screenshot.bottom &&
-                imgBottom > screenshot.top);
-        });
+    function _filterScreenshots(imgLeft, imgTop, imgWidth, imgHeight, screenshotList) {
+        // 计算图像边界
+        const imgRight = imgLeft + imgWidth;
+        const imgBottom = imgTop + imgHeight;
+        
+        // 筛选与当前区域重叠的截图
+        return screenshotList.filter(screenshot => 
+            imgLeft < screenshot.right &&
+            imgRight > screenshot.left &&
+            imgTop < screenshot.bottom &&
+            imgBottom > screenshot.top
+        );
     }
 
+    /**
+     * 添加截图到canvas
+     * @param {Object} data - 截图数据
+     * @param {string} uri - 图片URI
+     */
+    function addScreenShot(data, uri) {
+        // 如果已取消截图,不处理
+        if (isCancelled) return;
+        
+        const image = new Image();
+        
+        // 图片加载错误处理
+        image.onerror = function() {
+            captureConfig.fail('图片加载失败');
+            releaseResources();
+        };
 
-    let addScreenShot = function (data, uri) {
-        let image = new Image();
+        image.onload = function() {
+            try {
+                data.image = {width: image.width, height: image.height};
 
-        image.onload = function () {
-            data.image = {width: image.width, height: image.height};
+                // 调整缩放比例
+                if (data.windowWidth !== image.width) {
+                    const scale = image.width / data.windowWidth;
+                    data.x *= scale;
+                    data.y *= scale;
+                    data.totalWidth *= scale;
+                    data.totalHeight *= scale;
+                }
 
-            // given device mode emulation or zooming, we may end up with
-            // a different sized image than expected, so let's adjust to
-            // match it!
-            if (data.windowWidth !== image.width) {
-                let scale = image.width / data.windowWidth;
-                data.x *= scale;
-                data.y *= scale;
-                data.totalWidth *= scale;
-                data.totalHeight *= scale;
-            }
+                // 如果是第一张截图,初始化canvas
+                if (!screenshots.length) {
+                    screenshots = _initScreenshots(data.totalWidth, data.totalHeight);
+                }
 
-            // lazy initialization of screenshot canvases (since we need to wait
-            // for actual image size)
-            if (!screenshots.length) {
-                Array.prototype.push.apply(
-                    screenshots,
-                    _initScreenshots(data.totalWidth, data.totalHeight)
+                // 获取与当前区域重叠的canvas并绘制图像
+                const matchingScreenshots = _filterScreenshots(
+                    data.x, data.y, image.width, image.height, screenshots
                 );
+                
+                matchingScreenshots.forEach(screenshot => {
+                    screenshot.ctx.drawImage(
+                        image,
+                        data.x - screenshot.left,
+                        data.y - screenshot.top
+                    );
+                });
+
+                // 如果是最后一步,调用成功回调
+                if (data.complete === 1) {
+                    captureConfig.success(data);
+                    isCapturing = false;
+                }
+            } catch (e) {
+                captureConfig.fail('处理截图时出错: ' + e.message);
+                releaseResources();
             }
+        };
+        
+        // 设置图片源
+        image.src = uri;
+    }
 
-            // draw it on matching screenshot canvases
-            _filterScreenshots(
-                data.x, data.y, image.width, image.height, screenshots
-            ).forEach(function (screenshot) {
-                screenshot.ctx.drawImage(
-                    image,
-                    data.x - screenshot.left,
-                    data.y - screenshot.top
-                );
-            });
+    /**
+     * 释放所有资源
+     */
+    function releaseResources() {
+        releaseCanvasResources(screenshots);
+        screenshots = [];
+        isCapturing = false;
+        isCancelled = false;
+    }
 
-            if (data.complete === 1) {
-                captureConfig.success(data);
+    /**
+     * 创建截图进度UI
+     * @param {string} [text='正在截取网页...'] 显示的文本
+     * @returns {HTMLElement} 创建的进度UI元素
+     */
+    function createProgressUI(text = '正在截取网页...') {
+        // 先检查是否已存在
+        let progressContainer = document.getElementById('fehelper-screenshot-progress');
+        if (progressContainer) {
+            progressContainer.querySelector('.fh-progress-text').textContent = text;
+            progressContainer.style.display = 'flex';
+            return progressContainer;
+        }
+
+        // 创建进度UI容器
+        progressContainer = document.createElement('div');
+        progressContainer.id = 'fehelper-screenshot-progress';
+        progressContainer.setAttribute('data-fh-ui', 'true');
+        progressContainer.className = 'fehelper-ui-element';
+        progressContainer.style.cssText = `
+            position: fixed;
+            bottom: 20px;
+            left: 50%;
+            transform: translateX(-50%);
+            background-color: rgba(0, 0, 0, 0.7);
+            color: white;
+            padding: 10px 20px;
+            border-radius: 5px;
+            z-index: 10000000;
+            font-size: 14px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+            width: 300px;
+        `;
+
+        // 创建文本元素
+        const textElement = document.createElement('div');
+        textElement.className = 'fh-progress-text';
+        textElement.textContent = text;
+        textElement.style.cssText = 'margin-bottom: 10px; width: 100%; text-align: center;';
+        progressContainer.appendChild(textElement);
+
+        // 创建进度条容器
+        const progressBarContainer = document.createElement('div');
+        progressBarContainer.style.cssText = `
+            width: 100%;
+            height: 10px;
+            background-color: rgba(255, 255, 255, 0.2);
+            border-radius: 5px;
+            overflow: hidden;
+            margin-bottom: 10px;
+        `;
+        progressContainer.appendChild(progressBarContainer);
+
+        // 创建进度条
+        const progressBar = document.createElement('div');
+        progressBar.className = 'fh-progress-bar';
+        progressBar.style.cssText = `
+            height: 100%;
+            width: 0%;
+            background-color: #4CAF50;
+            border-radius: 5px;
+            transition: width 0.3s;
+        `;
+        progressBarContainer.appendChild(progressBar);
+
+        // 创建百分比文本
+        const percentText = document.createElement('div');
+        percentText.className = 'fh-progress-percent';
+        percentText.textContent = '0%';
+        percentText.style.cssText = 'margin-bottom: 10px; font-size: 12px;';
+        progressContainer.appendChild(percentText);
+
+        // 创建取消按钮
+        const cancelButton = document.createElement('button');
+        cancelButton.textContent = '取消';
+        cancelButton.className = 'fh-progress-cancel';
+        cancelButton.style.cssText = `
+            background-color: #f44336;
+            border: none;
+            color: white;
+            padding: 5px 10px;
+            border-radius: 3px;
+            cursor: pointer;
+            font-size: 12px;
+        `;
+        cancelButton.onclick = () => {
+            if (window._fh_screenshot_cancel_callback && typeof window._fh_screenshot_cancel_callback === 'function') {
+                window._fh_screenshot_cancel_callback();
             }
+            progressContainer.style.display = 'none';
         };
-        image.src = uri;
-    };
+        progressContainer.appendChild(cancelButton);
 
+        document.body.appendChild(progressContainer);
+        return progressContainer;
+    }
+    
+    /**
+     * 更新截图进度UI
+     * @param {number} percent 进度百分比(0-1)
+     * @param {string} [text] 可选的文本更新
+     */
+    function updateProgressUI(percent, text) {
+        const progressContainer = document.getElementById('fehelper-screenshot-progress');
+        if (!progressContainer) return;
+
+        const progressBar = progressContainer.querySelector('.fh-progress-bar');
+        const percentText = progressContainer.querySelector('.fh-progress-percent');
+        
+        if (progressBar) {
+            const percentage = Math.min(Math.max(percent * 100, 0), 100);
+            progressBar.style.width = `${percentage}%`;
+        }
+        
+        if (percentText) {
+            percentText.textContent = `${Math.round(percent * 100)}%`;
+        }
 
+        if (text) {
+            const textElement = progressContainer.querySelector('.fh-progress-text');
+            if (textElement) {
+                textElement.textContent = text;
+            }
+        }
+    }
+    
     /**
-     * 通过网页url生成默认的文件名
-     * @param contentURL
-     * @returns {string}
+     * 取消截图操作
      */
-    function buildFilenameFromUrl() {
-        let name = location.href.split('?')[0].split('#')[0];
-        if (name) {
-            name = name
-                .replace(/^https?:\/\//, '')
-                .replace(/[^A-z0-9]+/g, '-')
-                .replace(/-+/g, '-')
-                .replace(/^[_\-]+/, '')
-                .replace(/[_\-]+$/, '');
-            name = '-' + name;
-        } else {
-            name = '';
+    function cancelCapture() {
+        if (isCapturing) {
+            isCancelled = true;
+            isCapturing = false;
+            
+            // 移除进度UI
+            const progressContainer = document.getElementById('fehelper-screenshot-progress');
+            if (progressContainer) {
+                progressContainer.style.display = 'none';
+            }
+            
+            // 恢复原始状态
+            cleanup && typeof cleanup === 'function' && cleanup();
+            
+            return true;
         }
-        return 'fehelper' + name + '-' + Date.now() + '.png';
+        return false;
+    }
+
+    /**
+     * 隐藏所有FeHelper UI元素
+     * 隐藏所有带有 fehelper-ui-element 类或 data-fh-ui 属性的元素
+     * @returns {Object} 隐藏元素的原始显示状态
+     */
+    function hideFeHelperUI() {
+        const uiElements = document.querySelectorAll('.fehelper-ui-element, [data-fh-ui="true"]');
+        const originalDisplays = {};
+
+        uiElements.forEach((element, index) => {
+            // Ensure unique ID for lookup later
+            if (!element.id) {
+                // Assign a temporary, identifiable ID
+                element.id = `fh-temp-id-${Math.random().toString(36).substring(2, 9)}`;
+            }
+            const id = element.id;
+            originalDisplays[id] = element.style.display;
+            element.style.display = 'none';
+        });
+
+        window._fh_original_displays = originalDisplays; // Store globally
+        return originalDisplays; // Keep return for compatibility if needed elsewhere
+    }
+    
+    /**
+     * 显示所有FeHelper UI元素
+     * 恢复所有带有 fehelper-ui-element 类或 data-fh-ui 属性的元素的显示状态
+     * @param {Object} originalDisplays 原始显示状态对象
+     */
+    function showFeHelperUI(originalDisplays) { // Accept argument
+        originalDisplays = originalDisplays || window._fh_original_displays || {}; // Use passed or global
+        const uiElements = document.querySelectorAll('.fehelper-ui-element, [data-fh-ui="true"]');
+
+        uiElements.forEach((element) => {
+            const id = element.id;
+            if (id && id in originalDisplays) {
+                element.style.display = originalDisplays[id];
+            } else {
+                // Default restoration if ID mismatch or not found (less reliable)
+                element.style.display = '';
+            }
+            // Remove temporary ID if added and it wasn't originally present
+            if (id && id.startsWith('fh-temp-id-') && !(id in (window._fh_original_displays || {}))) {
+                 element.removeAttribute('id');
+            }
+        });
+    }
+
+    /**
+     * 隐藏固定定位和粘性定位的元素
+     * @returns {Array} 被隐藏元素的原始样式信息,用于恢复
+     */
+    function hideFixedElements() {
+        const fixedElements = [];
+        
+        // 查找所有fixed和sticky定位的元素
+        const elements = document.querySelectorAll('*');
+        elements.forEach(el => {
+            const style = window.getComputedStyle(el);
+            if (style && (style.position === 'fixed' || style.position === 'sticky')) {
+                // 排除FeHelper自己的UI元素
+                if (el.classList.contains('fehelper-ui-element') || 
+                    el.hasAttribute('data-fh-ui') || 
+                    el.id === 'fehelper-screenshot-progress' ||
+                    el.closest('#fehelper_screenshot_container')) {
+                    return;
+                }
+                
+                // 保存原始样式
+                fixedElements.push({
+                    element: el,
+                    originalDisplay: el.style.display,
+                    originalVisibility: el.style.visibility,
+                    originalOpacity: el.style.opacity
+                });
+                
+                // 隐藏元素
+                el.style.visibility = 'hidden';
+                el.style.opacity = '0';
+            }
+        });
+        
+        window._fh_fixed_elements = fixedElements;
+        return fixedElements;
+    }
+
+    /**
+     * 恢复之前隐藏的固定定位和粘性定位元素
+     */
+    function showFixedElements() {
+        const fixedElements = window._fh_fixed_elements || [];
+
+        fixedElements.forEach(item => {
+            const el = item.element;
+            if (el) {
+                // 恢复原始样式 - 修正:恢复 visibility 和 opacity
+                el.style.visibility = item.originalVisibility !== undefined ? item.originalVisibility : '';
+                el.style.opacity = item.originalOpacity !== undefined ? item.originalOpacity : '';
+                // Display 属性通常不需要在隐藏/显示 fixed 元素时修改,注释掉以防干扰
+                // if (item.originalDisplay !== undefined) {
+                //     el.style.display = item.originalDisplay;
+                // }
+            }
+        });
+
+        window._fh_fixed_elements = null; // 清理存储的元素
     }
 
+    // 定义全局的 cleanup 函数
+    function cleanup() {
+        // 恢复滚动位置
+        window.scrollTo(originalScrollLeft, originalScrollTop);
+
+        // 恢复UI显示 - 传递存储的原始状态
+        showFeHelperUI(window._fh_original_displays || {});
+        showFixedElements();
+
+        // 隐藏进度UI
+        const progressContainer = document.getElementById('fehelper-screenshot-progress');
+        if (progressContainer) {
+            progressContainer.style.display = 'none';
+        }
+
+        // 重新显示工具条
+        const screenshotContainer = document.getElementById('fehelper_screenshot_container');
+        if (screenshotContainer) {
+            screenshotContainer.style.display = 'block';
+        }
+
+        window._fh_screenshot_in_progress = false;
+        window._fh_screenshot_cancel_callback = null;
+        window._fh_fixed_elements = null; // Clear fixed elements cache
+        window._fh_original_displays = null; // Clear UI display cache
+    }
 
     // 配置项
-    let captureConfig = {
-        // 获取原始数据,用这个
-        success: function (data) {
-            chrome.runtime.sendMessage({
-                type: 'fh-dynamic-any-thing',
-                thing: 'page-screenshot-done',
-                params: {
+    const captureConfig = {
+        // 成功回调
+        success: function(data) {
+            try {
+                // 构造正确的数据格式
+                const screenshotData = {
                     filename: buildFilenameFromUrl(),
                     screenshots: screenshots.map(ss => {
-                        ss.dataUri=ss.canvas.toDataURL()
-                        return ss;
+                        // 确保使用toDataURL生成png格式的图像
+                        const dataUri = ss.canvas.toDataURL('image/png');
+                        
+                        return {
+                            dataUri: dataUri,  // 保持与showResult函数期望的属性名一致
+                            index: ss.index,
+                            row: Math.floor(ss.top / (ss.bottom - ss.top || 1)), 
+                            col: Math.floor(ss.left / (ss.right - ss.left || 1)),
+                            left: ss.left,
+                            top: ss.top,
+                            right: ss.right,
+                            bottom: ss.bottom,
+                            width: ss.right - ss.left,
+                            height: ss.bottom - ss.top
+                        };
                     }),
                     totalWidth: data.totalWidth,
                     totalHeight: data.totalHeight
-                }
-            });
+                };
+                
+                // 使用正确的消息类型和thing
+                chrome.runtime.sendMessage({
+                    type: 'fh-dynamic-any-thing',
+                    thing: 'page-screenshot-done',
+                    params: screenshotData
+                }, function(response) {
+                    // 恢复页面标题并移除进度条
+                    document.title = pageOriginalTitle;
+                    const progressBar = document.getElementById('fehelper_screenshot_progress');
+                    if (progressBar) {
+                        progressBar.remove();
+                    }
+                    
+                    // 释放资源
+                    setTimeout(() => {
+                        releaseResources();
+                    }, 1000);
+                });
+            } catch (e) {
+                captureConfig.fail('处理截图结果时出错: ' + e.message);
+            }
         },
 
-        fail: reason => {
-            alert(reason && reason.message || reason || '稍后尝试刷新页面重试!');
+        // 失败回调
+        fail: function(reason) {
+            // 恢复页面标题
+            document.title = pageOriginalTitle;
+            
+            // 移除进度条
+            const progressBar = document.getElementById('fehelper_screenshot_progress');
+            if (progressBar) {
+                progressBar.remove();
+            }
+            
+            // 显示错误消息
+            const errorMsg = reason && reason.message || reason || '截图失败,请刷新页面重试!';
+            
+            // 创建错误提示UI
+            const errorDiv = document.createElement('div');
+            errorDiv.style.cssText = 'position:fixed;left:20%;top:20%;right:20%;z-index:1000001;padding:20px;background:rgba(255,0,0,0.7);color:#fff;text-align:center;border-radius:5px;';
+            errorDiv.textContent = errorMsg;
+            
+            // 添加关闭按钮
+            const closeButton = document.createElement('button');
+            closeButton.textContent = '关闭';
+            closeButton.style.cssText = 'margin-top:10px;padding:5px 15px;background:#fff;color:#000;border:none;border-radius:3px;cursor:pointer;';
+            closeButton.onclick = function() { 
+                errorDiv.remove(); 
+            };
+            
+            errorDiv.appendChild(document.createElement('br'));
+            errorDiv.appendChild(closeButton);
+            document.body.appendChild(errorDiv);
+            
+            // 自动关闭
+            setTimeout(() => {
+                if (document.body.contains(errorDiv)) {
+                    errorDiv.remove();
+                }
+            }, 5000);
+            
+            // 释放资源
+            releaseResources();
         },
 
-        progress: complete => {
-            let percent = parseInt(complete * 100, 10) + '%';
-            document.title = `进度:${percent}...`;
+        // 进度回调
+        progress: function(complete) {
+            if (isCancelled) return false;
+            
+            // 更新进度条
+            updateProgressUI(complete);
+            
+            // 更新页面标题
+            const percent = parseInt(complete * 100, 10) + '%';
+            document.title = `截图进度:${percent}...`;
 
             if (percent === '100%') {
                 setTimeout(() => {
@@ -213,162 +622,597 @@ window.screenshotContentScript = function () {
         }
     };
 
-
+    /**
+     * 计算数组中的最大值
+     * @param {Array} nums - 数字数组
+     * @returns {number} - 最大值
+     */
     function max(nums) {
-        return Math.max.apply(Math, nums.filter(function (x) {
-            return x;
-        }));
+        return Math.max(...nums.filter(Boolean));
     }
 
-    function goCapture(params) {
+    /**
+     * 执行可视区域截图
+     */
+    async function captureVisible() {
+        if (window._fh_screenshot_in_progress) {
+            return;
+        }
+        
+        window._fh_screenshot_in_progress = true;
+        
+        // 保存原始滚动位置
+        originalScrollTop = window.scrollY || document.documentElement.scrollTop;
+        originalScrollLeft = window.scrollX || document.documentElement.scrollLeft;
+        
+        // 隐藏工具条
+        let screenshotContainer = document.getElementById('fehelper_screenshot_container');
+        if (screenshotContainer) {
+            screenshotContainer.style.display = 'none';
+        }
+        
+        const progressUI = createProgressUI('正在截取可视区域...');
+        
+        window._fh_screenshot_cancel_callback = cleanup;
+        
+        // 隐藏干扰UI
+        hideFeHelperUI();
+        hideFixedElements();
+        
+        // 等待DOM更新
+        setTimeout(() => {
+            updateProgressUI(0.3, '正在截取可视区域...');
+            
+            // 从background.js导入MSG_TYPE不现实,这里直接使用特定值
+            chrome.runtime.sendMessage({
+                type: 'fh-screenshot-capture-visible'
+            }, response => {
+                if (response) {
+                    // 加载截图并处理
+                    const img = new Image();
+                    img.onload = function() {
+                        updateProgressUI(0.8, '正在处理截图...');
+                        
+                        // 创建与index.js中showResult函数期望的格式一致的数据
+                        const screenshotData = {
+                            filename: buildFilenameFromUrl(),
+                            screenshots: [{
+                                dataUri: response,
+                                index: 0,
+                                row: 0,
+                                col: 0,
+                                left: 0,
+                                top: 0,
+                                right: img.width,
+                                bottom: img.height,
+                                width: img.width,
+                                height: img.height
+                            }],
+                            totalWidth: img.width,
+                            totalHeight: img.height
+                        };
+                        
+                        // 使用统一的消息格式发送数据,确保只发送一次
+                        chrome.runtime.sendMessage({
+                            type: 'fh-dynamic-any-thing',
+                            thing: 'page-screenshot-done',
+                            params: screenshotData
+                        }, function(resp) {
+                            updateProgressUI(1, '截图完成');
+                            setTimeout(cleanup, 500);
+                        });
+                    };
+                    
+                    img.onerror = function(e) {
+                        updateProgressUI(1, '截图加载失败');
+                        setTimeout(cleanup, 500);
+                    };
+                    
+                    img.src = response;
+                    
+                    // 如果图片加载时间过长,设置超时
+                    setTimeout(() => {
+                        if (!img.complete) {
+                            cleanup();
+                        }
+                    }, 3000);
+                } else {
+                    const errorMessage = '截图失败,请刷新页面重试!';
+                    updateProgressUI(1, '截图失败');
+                    setTimeout(() => {
+                        alert(errorMessage);
+                        cleanup();
+                    }, 500);
+                }
+            });
+        }, 100);
+    }
 
-        if (!isValidUrl(location.href)) {
-            return captureConfig.fail('invalid url');
+    /**
+     * 执行全页面截图
+     */
+    async function captureFullPage() {
+        if (window._fh_screenshot_in_progress) {
+            return;
         }
 
-        let body = document.body,
-            originalBodyOverflowYStyle = body ? body.style.overflowY : '',
-            originalX = window.scrollX,
-            originalY = window.scrollY,
-            originalOverflowStyle = document.documentElement.style.overflow;
+        window._fh_screenshot_in_progress = true;
+        window._fh_screenshot_canceled = false; // 重命名 cancel 标志以避免冲突
 
-        if (body) {
-            body.style.overflowY = 'visible';
-        }
+        // 保存原始滚动位置
+        originalScrollTop = window.scrollY || document.documentElement.scrollTop;
+        originalScrollLeft = window.scrollX || document.documentElement.scrollLeft;
 
-        let widths = [
-                document.documentElement.clientWidth,
-                body ? body.scrollWidth : 0,
-                document.documentElement.scrollWidth,
-                body ? body.offsetWidth : 0,
-                document.documentElement.offsetWidth
-            ],
-            heights = [
-                document.documentElement.clientHeight,
-                body ? body.scrollHeight : 0,
-                document.documentElement.scrollHeight,
-                body ? body.offsetHeight : 0,
-                document.documentElement.offsetHeight
-            ],
-            fullWidth = max(widths),
-            fullHeight = max(heights),
-            windowWidth = window.innerWidth,
-            windowHeight = window.innerHeight,
-            arrangements = [],
-            scrollPad = 200,
-            yDelta = windowHeight - (windowHeight > scrollPad ? scrollPad : 0),
-            xDelta = windowWidth,
-            yPos = fullHeight - windowHeight,
-            xPos,
-            numArrangements,
-            captureVisible = false;
-
-        // During zooming, there can be weird off-by-1 types of things...
-        if (fullWidth <= xDelta + 1) {
-            fullWidth = xDelta;
-        }
+        // 计算页面尺寸
+        const pageWidth = Math.max(
+            document.documentElement.scrollWidth,
+            document.body.scrollWidth,
+            document.documentElement.offsetWidth,
+            document.body.offsetWidth
+        );
 
-        // Disable all scrollbars. We'll restore the scrollbar state when we're done
-        // taking the screenshots.
-        document.documentElement.style.overflow = 'hidden';
+        const pageHeight = Math.max(
+            document.documentElement.scrollHeight,
+            document.body.scrollHeight,
+            document.documentElement.offsetHeight,
+            document.body.offsetHeight
+        );
 
-        // 截图:可视区域
-        if (params.captureType === 'visible') {
-            arrangements = [window.scrollX, window.scrollY];
-            fullWidth = window.innerWidth;
-            fullHeight = window.innerHeight;
-            captureVisible = true;
-        } else {
-            // 全网页截图
-            while (yPos > -yDelta) {
-                xPos = 0;
-                while (xPos < fullWidth) {
-                    arrangements.push([xPos, yPos]);
-                    xPos += xDelta;
-                }
-                yPos -= yDelta;
-            }
+        // 获取视窗尺寸
+        const windowWidth = window.innerWidth;
+        const windowHeight = window.innerHeight;
+
+        // 使用视窗尺寸作为滚动步长,并计算大致步数用于UI显示
+        const totalSteps = Math.ceil(pageHeight / windowHeight) * Math.ceil(pageWidth / windowWidth);
+
+        // 隐藏工具条
+        let screenshotContainer = document.getElementById('fehelper_screenshot_container');
+        if (screenshotContainer) {
+            screenshotContainer.style.display = 'none';
         }
 
-        numArrangements = arrangements.length;
+        // 创建进度UI和取消回调
+        const progressUI = createProgressUI(`正在截取全页面 (共${totalSteps}步)...`);
+        window._fh_screenshot_cancel_callback = () => {
+             window._fh_screenshot_canceled = true; // Set the cancel flag
+             cleanup(); // Execute cleanup
+        };
+
+        // 隐藏其他干扰UI(但不隐藏固定元素)
+        hideFeHelperUI(); // 保存原始UI状态
+
+        // 创建存储截图数据的数组和已捕获位置的集合
+        const screenshots = [];
+        const capturedPositions = new Set();
+        let currentStep = 0; // 用于进度显示
+
+        // 使用异步函数和循环进行截图
+        setTimeout(async () => {
+            try {
+                // 垂直滚动循环
+                for (let y = 0; ; y += windowHeight) {
+                    // 水平滚动循环
+                    for (let x = 0; ; x += windowWidth) {
+                        if (window._fh_screenshot_canceled) { cleanup(); return; }
+
+                        // 计算目标滚动位置,限制在页面边界内
+                        const targetX = Math.min(x, pageWidth - windowWidth);
+                        const targetY = Math.min(y, pageHeight - windowHeight);
+
+                        // 滚动到目标位置
+                        window.scrollTo(targetX, targetY);
+
+                        // 等待滚动和页面重绘完成
+                        await new Promise(resolve => setTimeout(resolve, 300));
+
+                        if (window._fh_screenshot_canceled) { cleanup(); return; }
+
+                        // 获取滚动后的实际位置
+                        const actualX = window.scrollX || document.documentElement.scrollLeft;
+                        const actualY = window.scrollY || document.documentElement.scrollTop;
+                        const posKey = `${actualX},${actualY}`;
+
+                        // 如果这个精确位置已经截取过,则跳过
+                        if (capturedPositions.has(posKey)) {
+                            // 如果已到达水平末端,跳出内层循环
+                            if (x >= pageWidth - windowWidth) break;
+                            continue; // 继续内层循环的下一个x值
+                        }
+                        capturedPositions.add(posKey); // 记录新的已截取位置
 
-        function cleanUp() {
-            document.documentElement.style.overflow = originalOverflowStyle;
-            if (body) {
-                body.style.overflowY = originalBodyOverflowYStyle;
+                        // 判断是否为首屏(实际滚动位置为0,0)
+                        const isFirstScreen = (actualX === 0 && actualY === 0);
+
+                        // 非首屏时隐藏固定元素
+                        if (!isFirstScreen) {
+                            hideFixedElements();
+                            // 等待隐藏生效
+                            await new Promise(resolve => setTimeout(resolve, 50));
+                        }
+
+                        currentStep++;
+                        updateProgressUI(Math.min(0.9, currentStep / totalSteps), `正在截取第 ${currentStep}/${totalSteps} 部分...`);
+
+                        // 执行截图API调用
+                        let response;
+                        try {
+                            response = await new Promise((resolve, reject) => {
+                                chrome.runtime.sendMessage({ type: 'fh-screenshot-capture-visible' }, res => {
+                                    if (chrome.runtime.lastError) {
+                                        reject(new Error(chrome.runtime.lastError.message || '截图通讯错误'));
+                                    } else if (res) {
+                                        resolve(res);
+                                    } else {
+                                        reject(new Error('截图失败,未收到数据'));
+                                    }
+                                });
+                                // 添加超时处理
+                                setTimeout(() => reject(new Error('截图超时')), 5000);
+                            });
+                        } catch (error) {
+                             // 非首屏时尝试恢复固定元素
+                             if (!isFirstScreen && window._fh_fixed_elements) {
+                                 showFixedElements();
+                             }
+                             captureFailureHandler(error.message || '截图API调用失败');
+                             return; // 中断截图流程
+                        }
+
+
+                        if (window._fh_screenshot_canceled) {
+                            if (!isFirstScreen) showFixedElements(); // Make sure to show elements if cancelled here
+                            cleanup();
+                            return;
+                        }
+
+                        // 非首屏时恢复固定元素显示
+                        if (!isFirstScreen) {
+                            showFixedElements();
+                        }
+
+                        console.log(`截图成功 [${currentStep}]: 位置(${actualX},${actualY}), 数据长度: ${response.length}`);
+                        screenshots.push({
+                            dataUrl: response,
+                            x: actualX,
+                            y: actualY,
+                            width: windowWidth, // 截图是基于视窗尺寸的
+                            height: windowHeight,
+                            // 保留 row/col 供可能的调试或兼容性需求,但去重和排序基于 x, y
+                            row: Math.round(actualY / windowHeight),
+                            col: Math.round(actualX / windowWidth)
+                        });
+
+                        // 如果当前水平位置已覆盖页面宽度,结束内层循环
+                        if (x >= pageWidth - windowWidth) break;
+                    } // 结束内层循环 (x)
+
+                    // 如果当前垂直位置已覆盖页面高度,结束外层循环
+                    if (y >= pageHeight - windowHeight) break;
+                } // 结束外层循环 (y)
+
+                // 所有截图完成后,滚动回页面顶部
+                window.scrollTo(0, 0);
+                await new Promise(resolve => setTimeout(resolve, 100)); // 等待滚动完成
+
+                // 调用完成处理函数
+                finishCapture(screenshots, pageWidth, pageHeight);
+
+            } catch (error) {
+                // 捕获循环中的意外错误
+                captureFailureHandler(error.message || '截图过程中断');
             }
-            window.scrollTo(originalX, originalY);
-        }
 
-        (function processArrangements() {
-            if (!arrangements.length) {
-                return cleanUp();
+        }, 200); // 初始延迟,等待UI隐藏生效
+
+        /**
+         * 完成所有截图后处理 - 修改为接受参数
+         * @param {Array} capturedScreenshots 捕获到的截图数组
+         * @param {number} finalWidth 最终页面宽度
+         * @param {number} finalHeight 最终页面高度
+         */
+        function finishCapture(capturedScreenshots, finalWidth, finalHeight) {
+            if (window._fh_screenshot_canceled) {
+                 return; // 如果在 finishCapture 前取消,则不继续
             }
+            updateProgressUI(0.95, '正在处理截图...');
 
-            let next = arrangements.pop(),
-                x = next[0], y = next[1];
-
-            let complete = 1;
-            let dataX = 0;
-            let dataY = 0;
-            if (!captureVisible) {
-                window.scrollTo(x, y);
-                complete = (numArrangements - arrangements.length) / numArrangements;
-                dataX = window.scrollX;
-                dataY = window.scrollY;
+            if (!capturedScreenshots || capturedScreenshots.length === 0) {
+                updateProgressUI(1, '截图失败: 没有截取到任何内容');
+                setTimeout(cleanup, 1000);
+                return;
             }
 
-            let data = {
-                x: dataX,
-                y: dataY,
-                complete: complete,
-                windowWidth: windowWidth,
-                totalWidth: fullWidth,
-                totalHeight: fullHeight,
-                devicePixelRatio: window.devicePixelRatio,
-                tabId: window.__FH_TAB_ID__
+            console.log(`处理截图,共 ${capturedScreenshots.length} 张,页面尺寸: ${finalWidth}x${finalHeight}`);
+
+            // 基于精确的捕获滚动坐标 (x, y) 进行去重
+            const screenshotMap = new Map();
+            capturedScreenshots.forEach(ss => {
+                const key = `${ss.x},${ss.y}`;
+                if (!screenshotMap.has(key)) {
+                    screenshotMap.set(key, ss);
+                } else {
+                    console.log(`发现重复精确位置的截图: (${ss.x},${ss.y}),保留第一个`);
+                }
+            });
+
+            // 将 Map 转换回数组,并按 Y 坐标优先,然后 X 坐标排序
+            const uniqueScreenshots = Array.from(screenshotMap.values()).sort((a, b) => {
+                if (a.y !== b.y) return a.y - b.y;
+                return a.x - b.x;
+            });
+
+            console.log(`去重后共 ${uniqueScreenshots.length} 张有效截图(原 ${capturedScreenshots.length} 张)`);
+
+            // 准备发送到后台的数据格式
+            let mappedScreenshots = uniqueScreenshots.map((ss, index) => ({
+                dataUri: ss.dataUrl, // 确保属性名是 dataUri
+                x: ss.x,
+                y: ss.y,
+                width: ss.width, // 使用捕获时的视窗尺寸
+                height: ss.height,
+                index: index, // 添加索引供后台使用
+                // 添加兼容性字段(如果后台拼接逻辑需要)
+                row: ss.row,
+                col: ss.col,
+                left: ss.x,
+                top: ss.y,
+                right: ss.x + ss.width,
+                bottom: ss.y + ss.height
+            }));
+
+            console.log('准备发送所有截图分片到后台:', {
+                screenshots: mappedScreenshots.length,
+                pageWidth: finalWidth,
+                pageHeight: finalHeight
+            });
+
+            const screenshotData = {
+                filename: buildFilenameFromUrl(),
+                screenshots: mappedScreenshots,
+                totalWidth: finalWidth,
+                totalHeight: finalHeight
             };
 
-            // Need to wait for things to settle
-            window.setTimeout(function () {
-                // In case the below callback never returns, cleanup
-                let cleanUpTimeout = window.setTimeout(cleanUp, 1250);
+            // 发送消息到后台处理
+            chrome.runtime.sendMessage({
+                type: 'fh-dynamic-any-thing',
+                thing: 'page-screenshot-done',
+                params: screenshotData
+            }, function(resp) {
+                if (chrome.runtime.lastError) {
+                     updateProgressUI(1, '发送结果失败');
+                } else {
+                     updateProgressUI(1, '截图完成');
+                }
+                // 确保滚动位置在 cleanup 前恢复
+                window.scrollTo(originalScrollLeft, originalScrollTop);
+                setTimeout(cleanup, 500);
+            });
+        }
+    }
+
+    /**
+     * 执行截图操作
+     * @param {Object} params
+     * @param {String} params.captureType 截图类型:'visible'或'whole'
+     */
+    function goCapture(params) {
+        // 如果正在截图中,则忽略新的请求
+        if (isCapturing) {
+            return;
+        }
 
-                captureConfig.progress(data.complete);
+        // 如果不是http/https,就不处理
+        if (!isValidUrl(location.href)) {
+            alert('截图功能仅支持HTTP/HTTPS协议的网页!');
+            return;
+        }
 
-                chrome.runtime.sendMessage({
-                    type: 'fh-dynamic-any-thing',
-                    thing: 'add-screen-shot-by-pages',
-                    params: data
-                }).then(resp => {
-                    if(resp.uri) {
-                        addScreenShot(resp.params,resp.uri);
+        isCapturing = true;
+        isCancelled = false;
+        
+ // console.log('开始执行截图,模式:' + params.captureType);
+        
+        // 根据截图模式执行不同的操作
+        if (params.captureType === 'visible') {
+            captureVisible()
+                .then(result => {
+                    if (result && result.success) {
+                        // console.log('可视区域截图完成');
+                    }
+                })
+                .catch(error => {
+                    // console.error('截图失败:', error);
+                    alert('截图失败: ' + (error.message || '未知错误'));
+                })
+                .finally(() => {
+                    // 确保截图状态重置
+                    isCapturing = false;
+                    
+                    // 确保进度UI被移除
+                    const progressUI = document.getElementById('fehelper_screenshot_progress');
+                    if (progressUI) {
+                        progressUI.remove();
+                    }
+                });
+        } else {
+            captureFullPage()
+                .then(result => {
+                    if (result && result.success) {
+                        // console.log('全页面截图完成');
                     }
-                    window.clearTimeout(cleanUpTimeout);
-                    if (data.complete !== 1) {
-                        setTimeout(processArrangements,200);
-                    } else {
-                        cleanUp();
+                })
+                .catch(error => {
+                    // console.error('截图失败:', error);
+                    alert('截图失败: ' + (error.message || '未知错误'));
+                })
+                .finally(() => {
+                    // 确保截图状态重置
+                    isCapturing = false;
+                    
+                    // 确保进度UI被移除
+                    const progressUI = document.getElementById('fehelper_screenshot_progress');
+                    if (progressUI) {
+                        progressUI.remove();
                     }
                 });
-            }, 150);
-        })();
+        }
     }
 
-    window.screenshotNoPage = function(){
-        let elWrapper = document.createElement('div');
-        elWrapper.innerHTML = '<div id="fehelper_screenshot" style="position:fixed;left:0;top:0;right:0;z-index:1000000;padding:15px;background:#000;text-align:center;">' +
-            '<button id="btnVisible" style="margin: 0 10px;padding: 10px;border-radius: 5px;border: 1px solid #fff;cursor:pointer;">可视区域截图</button>' +
-            '<button id="btnWhole" style="margin: 0 10px;padding: 10px;border-radius: 5px;border: 1px solid #fff;cursor:pointer;">全网页截图</button></div>';
-        elAlertMsg = elWrapper.childNodes[0];
-        document.body.appendChild(elAlertMsg);
-        document.querySelector('#btnVisible').onclick = e => {
-            elAlertMsg.remove();
-            goCapture({captureType:'visible'});
-        };
-        document.querySelector('#btnWhole').onclick = e => {
-            elAlertMsg.remove();
-            goCapture({captureType:'whole'});
-        };
+    /**
+     * 创建截图选择UI
+     */
+    window.screenshotNoPage = function() {
+        // console.log('FeHelper: 截图工具触发');
+        // 如果正在截图,不创建新UI
+        if (isCapturing) {
+            alert('正在截图中,请等待当前操作完成');
+            return;
+        }
+        
+        try {
+            // 先检查是否已存在截图UI
+            const existingUI = document.getElementById('fehelper_screenshot');
+            if (existingUI) {
+                existingUI.remove();
+            }
+            
+            // 创建一个独立的div作为容器
+            const container = document.createElement('div');
+            container.id = 'fehelper_screenshot_container';
+            container.className = 'fehelper-ui-element'; // 添加类名,便于统一隐藏
+            container.style.cssText = 'position:fixed;left:0;top:0;right:0;z-index:10000000;';
+            
+            // 设置内部HTML
+            container.innerHTML = `
+                <div id="fehelper_screenshot" style="position:fixed;left:0;top:0;right:0;z-index:1000000;padding:15px;background:rgba(0,0,0,0.8);color:#fff;text-align:center;">
+                    <h3 style="margin:0 0 10px 0;font-size:16px;">FeHelper 网页截图工具</h3>
+                    <button id="btnVisible" style="margin:0 10px;padding:8px 15px;border-radius:4px;border:none;background:#4CAF50;color:#fff;cursor:pointer;font-size:14px;">可视区域截图</button>
+                    <button id="btnWhole" style="margin:0 10px;padding:8px 15px;border-radius:4px;border:none;background:#2196F3;color:#fff;cursor:pointer;font-size:14px;">全网页截图</button>
+                    <button id="btnClose" style="margin:0 10px;padding:8px 15px;border-radius:4px;border:none;background:#f44336;color:#fff;cursor:pointer;font-size:14px;">关闭</button>
+                </div>
+            `;
+            
+            // 确保DOM已准备好
+            if (!document.body) {
+                // console.error('FeHelper截图:document.body不存在,无法添加截图UI');
+                // 尝试等待DOM加载完成
+                const checkBodyInterval = setInterval(() => {
+                    if (document.body) {
+                        clearInterval(checkBodyInterval);
+                        document.body.appendChild(container);
+                        bindEvents(container);
+                    }
+                }, 100);
+                
+                // 超时处理
+                setTimeout(() => {
+                    clearInterval(checkBodyInterval);
+                    alert('页面DOM未准备好,无法启动截图工具');
+                }, 5000);
+                
+                return;
+            }
+            
+            // 添加到document.body
+            document.body.appendChild(container);
+            
+            // 绑定事件
+            bindEvents(container);
+            
+            // console.log('FeHelper截图UI已添加到页面');
+            
+        } catch (error) {
+            // console.error('FeHelper截图UI创建失败:', error);
+            alert('截图工具启动失败:' + error.message);
+        }
     };
+    
+    /**
+     * 绑定截图UI的事件
+     */
+    function bindEvents(container) {
+        const btnVisible = document.getElementById('btnVisible');
+        const btnWhole = document.getElementById('btnWhole');
+        const btnClose = document.getElementById('btnClose');
+        
+        if (btnVisible) {
+            btnVisible.onclick = function() {
+                container.remove();
+                goCapture({captureType: 'visible'});
+            };
+        }
+        
+        if (btnWhole) {
+            btnWhole.onclick = function() {
+                container.remove();
+                goCapture({captureType: 'whole'});
+            };
+        }
+        
+        if (btnClose) {
+            btnClose.onclick = function() {
+                container.remove();
+            };
+        }
+        
+        // 添加自动关闭
+        setTimeout(() => {
+            if (container && document.body.contains(container)) {
+                container.remove();
+            }
+        }, 30000);
+    }
+
+    // 添加键盘快捷键支持
+    document.addEventListener('keydown', function(e) {
+        // ESC键取消截图
+        if (e.key === 'Escape' && isCapturing) {
+            cancelCapture();
+        }
+    });
+    
+    // 添加消息监听,支持通过消息触发截图功能
+    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
+        if (request.type === 'fh-screenshot-start') {
+            // console.log('FeHelper: 收到截图请求');
+            window.screenshotNoPage();
+            sendResponse({success: true});
+            return true;
+        }
+    });
+    
+    // 初始化
+    // console.log('FeHelper: 截图功能已加载');
+
+    /**
+     * 根据网页URL生成默认文件名
+     * @returns {string} - 生成的文件名
+     */
+    function buildFilenameFromUrl() {
+        let name = location.href.split('?')[0].split('#')[0];
+        if (name) {
+            name = name
+                .replace(/^https?:\/\//, '')
+                .replace(/[^A-z0-9]+/g, '-')
+                .replace(/-+/g, '-')
+                .replace(/^[_\-]+/, '')
+                .replace(/[_\-]+$/, '');
+            name = '-' + name;
+        } else {
+            name = '';
+        }
+        return 'fehelper' + name + '-' + Date.now() + '.png';
+    }
+
+    // 在截图失败的回调中,确保错误信息被正确记录,并释放资源
+    function captureFailureHandler(reason) {
+        // console.error('截图失败:', reason);
+        updateProgressUI(1, '截图失败');
+        setTimeout(() => {
+            const errorMsg = reason && reason.message || reason || '截图失败,请刷新页面重试!';
+            alert(errorMsg);
+            cleanup();
+        }, 500);
+    }
 };

+ 10 - 1
apps/screenshot/index.css

@@ -5,7 +5,7 @@
 }
 
 .img-container {
-    overflow-y: scroll;
+    overflow: auto;
     border:10px solid #4e5d6c;
     border-right: 0;
     background-color:#4e5d6c;
@@ -14,6 +14,7 @@
     top: 120px;
     left: 0;
     right: 0;
+    max-height: calc(100vh - 150px);
 }
 
 .img-result {
@@ -69,3 +70,11 @@ th.x-op {
     position: relative;
     top: 5px;
 }
+
+/* 为canvas添加样式 */
+#imageEditor canvas {
+    display: block;
+    background-color: #fff;
+    box-shadow: 0 0 10px rgba(0,0,0,0.3);
+    width: 100%;
+}

+ 3 - 1
apps/screenshot/index.html

@@ -5,6 +5,7 @@
     <meta charset="UTF-8">
     <link rel="stylesheet" href="index.css"/>
     <script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
+    <script type="text/javascript" src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
     <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
 </head>
 <body>
@@ -26,7 +27,8 @@
                 <input id="btnSave" class="btn btn-warning btn-sm ui-fl-r" type="button" value="保存图片到本地" @click="save">
             </h5>
             <div class="panel panel-default img-container" id="imageEditor">
-                <canvas :width="totalWidth" :height="totalHeight"></canvas>
+                <canvas id="fehelper_resultCanvas"></canvas>
+                <img id="fehelper_resultImg" style="display:none;">
             </div>
         </div>
     </div>

+ 207 - 24
apps/screenshot/index.js

@@ -28,29 +28,203 @@ new Vue({
         }
     },
     methods: {
-
+        /**
+         * 显示结果到Canvas画布
+         * @param data
+         */
         showResult: function(data){
-            if (data && data.screenshots) {
-                this.totalWidth = data.totalWidth;
-                this.totalHeight = data.totalHeight;
-                let canvas = document.querySelector('#imageEditor>canvas');
-                let ctx  = canvas.getContext('2d');
-
-                data.screenshots.forEach((ss) => {
-                    let img = new Image();
-                    img.dx = ss.left;
-                    img.dy = ss.top;
-                    img.onload = function(event){
-                        ctx.drawImage(this,this.dx,this.dy,this.width,this.height);
+            if (!data || !data.screenshots || !data.screenshots.length) {
+                alert('截图数据无效,请重试!');
+                return;
+            }
+            
+            const filename = data.filename || 'fe-helper-screenshot.png';
+            const exportFileName = filename.replace(/\.(jpg|jpeg|png|gif|bmp|webp)$/i, '') + '.png';
+            this.defaultFilename = exportFileName;
+            
+            // 处理单张和多张截图的不同情况
+            if (data.screenshots.length === 1) {
+                // 单张截图 - 使用提供的尺寸
+                this._processSingleScreenshot(data);
+            } else {
+                // 多张截图 - 垂直拼接
+                this._processVerticalStitching(data);
+            }
+        },
+        
+        /**
+         * 处理单张截图
+         */
+        _processSingleScreenshot: function(data) {
+            const screenshot = data.screenshots[0];
+            const dataUri = screenshot.dataUri || screenshot.dataUrl;
+            
+            if (!dataUri) {
+                alert('截图数据无效,请重试!');
+                return;
+            }
+            
+            // 设置canvas尺寸
+            const canvas = document.getElementById('fehelper_resultCanvas');
+            if (!canvas) {
+                alert('无法找到画布元素,请刷新页面重试!');
+                return;
+            }
+            
+            // 使用提供的尺寸或截图的尺寸
+            const width = data.totalWidth || screenshot.width || screenshot.right - screenshot.left;
+            const height = data.totalHeight || screenshot.height || screenshot.bottom - screenshot.top;
+            
+            canvas.width = width;
+            canvas.height = height;
+            
+            const ctx = canvas.getContext('2d');
+            ctx.fillStyle = '#fff';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            
+            const img = new Image();
+            img.onload = () => {
+                // 绘制到画布
+                ctx.drawImage(img, 0, 0);
+                
+                // 显示处理结果
+                this._showFinalResult(canvas, this.defaultFilename);
+            };
+            
+            img.onerror = () => {
+                alert('加载截图失败,请重试!');
+                $('#fehelper_loading').hide();
+            };
+            
+            img.src = dataUri;
+        },
+        
+        /**
+         * 垂直拼接多张截图
+         */
+        _processVerticalStitching: function(data) {
+            // 将数据排序,确保按正确顺序垂直拼接
+            // 对于垂直拼接,我们主要关注y坐标和row值
+            const sortedScreenshots = [...data.screenshots].sort((a, b) => {
+                // 优先使用row进行排序
+                if (a.row !== undefined && b.row !== undefined) {
+                    return a.row - b.row;
+                }
+                
+                // 如果没有row,则使用top或y坐标排序
+                if (a.top !== undefined && b.top !== undefined) {
+                    return a.top - b.top;
+                }
+                
+                if (a.y !== undefined && b.y !== undefined) {
+                    return a.y - b.y;
+                }
+                
+                // 如果以上都没有,则使用索引值
+                return (a.index || 0) - (b.index || 0);
+            });
+            
+            // 加载所有图像
+            Promise.all(sortedScreenshots.map((screenshot, index) => {
+                return new Promise((resolve, reject) => {
+                    const dataUri = screenshot.dataUri || screenshot.dataUrl;
+                    
+                    if (!dataUri) {
+                        resolve(null);
+                        return;
+                    }
+                    
+                    const img = new Image();
+                    img.onload = () => {
+                        // 添加图像对象和计算后的尺寸信息
+                        screenshot.img = img;
+                        screenshot.width = img.width;
+                        screenshot.height = img.height;
+                        resolve(screenshot);
                     };
-                    img.src = ss.dataUri;
+                    
+                    img.onerror = () => {
+                        resolve(null);
+                    };
+                    
+                    img.src = dataUri;
                 });
-                this.defaultFilename = data.filename;
-
-                this.$nextTick(() => {
-                    this.$refs.resultBox.scrollIntoView();
+            }))
+            .then(loadedScreenshots => {
+                const validScreenshots = loadedScreenshots.filter(Boolean);
+                
+                if (validScreenshots.length === 0) {
+                    alert('没有有效的截图数据,请重试!');
+                    $('#fehelper_loading').hide();
+                    return;
+                }
+                
+                // 计算总宽度和总高度
+                // 总宽度取所有截图中的最大宽度
+                const totalWidth = Math.max(...validScreenshots.map(s => s.width || s.img.width));
+                // 总高度为所有截图高度之和
+                const totalHeight = validScreenshots.reduce((sum, s) => sum + (s.height || s.img.height), 0);
+                
+                // 设置canvas尺寸
+                const canvas = document.getElementById('fehelper_resultCanvas');
+                if (!canvas) {
+                    alert('无法找到画布元素,请刷新页面重试!');
+                    $('#fehelper_loading').hide();
+                    return;
+                }
+                
+                canvas.width = totalWidth;
+                canvas.height = totalHeight;
+                
+                const ctx = canvas.getContext('2d');
+                ctx.fillStyle = '#fff';
+                ctx.fillRect(0, 0, canvas.width, canvas.height);
+                
+                // 垂直拼接图像
+                let currentY = 0;
+                validScreenshots.forEach((screenshot, index) => {
+                    const { img } = screenshot;
+                    const width = screenshot.width || img.width;
+                    const height = screenshot.height || img.height;
+                    
+                    // 计算绘制位置,水平居中,垂直方向按顺序拼接
+                    const x = (totalWidth - width) / 2;
+                    
+                    // 绘制图像
+                    ctx.drawImage(img, x, currentY);
+                    
+                    // 更新Y坐标为下一个位置
+                    currentY += height;
                 });
+                
+                // 显示处理结果
+                this._showFinalResult(canvas, this.defaultFilename);
+            })
+            .catch(() => {
+                alert('处理截图出错,请重试!');
+                $('#fehelper_loading').hide();
+            });
+        },
+        
+        /**
+         * 显示最终结果
+         */
+        _showFinalResult: function(canvas, filename) {
+            // 显示保存按钮
+            $('.btn-save').text(`保存为 ${filename}`).show();
+            $('.btn-toolbox').show();
+            
+            // 隐藏img元素,只显示canvas
+            const resultImg = document.getElementById('fehelper_resultImg');
+            if (resultImg) {
+                resultImg.style.display = 'none';
             }
+            
+            // 显示canvas
+            canvas.style.display = 'block';
+            
+            // 隐藏加载提示
+            $('#fehelper_loading').hide();
         },
 
         save: function () {
@@ -59,12 +233,21 @@ new Vue({
                 permissions: ['downloads']
             }, (granted) => {
                 if (granted) {
-                    chrome.downloads.download({
-                        url: document.querySelector('#imageEditor>canvas').toDataURL(),
-                        saveAs: true,
-                        conflictAction: 'overwrite',
-                        filename: this.defaultFilename
-                    });
+                    try {
+                        const canvas = document.getElementById('fehelper_resultCanvas');
+                        if (!canvas) {
+                            throw new Error('找不到canvas元素');
+                        }
+                        
+                        chrome.downloads.download({
+                            url: canvas.toDataURL('image/png'),
+                            saveAs: true,
+                            conflictAction: 'overwrite',
+                            filename: this.defaultFilename
+                        });
+                    } catch (e) {
+                        alert('保存图片失败: ' + e.message);
+                    }
                 } else {
                     alert('必须接受授权,才能正常下载!');
                 }