浏览代码

Ctrl+Click & Hint of mobile mode & Default Browser Location & Click with coordinate

naibo 1 年之前
父节点
当前提交
7901ee2877

二进制
ElectronJS/EasySpider_en.crx


二进制
ElectronJS/EasySpider_zh.crx


+ 1 - 1
ElectronJS/config.json

@@ -1 +1 @@
-{"webserver_address":"http://localhost","webserver_port":8074,"user_data_folder":"./user_data","debug":false,"copyright":1,"sys_version":"x64","mysql_config_path":"./mysql_config.json","absolute_user_data_folder":"/Users/naibo/Documents/EasySpider/ElectronJS/user_data"}
+{"webserver_address":"http://localhost","webserver_port":8074,"user_data_folder":"","debug":false,"copyright":1,"sys_version":"x64","mysql_config_path":"./mysql_config.json","absolute_user_data_folder":"/Users/naibo/Documents/EasySpider/ElectronJS/user_data"}

+ 57 - 26
ElectronJS/main.js

@@ -108,8 +108,8 @@ let invoke_window = null;
 function createWindow() {
     // Create the browser window.
     mainWindow = new BrowserWindow({
-        width: 550,
-        height: 750,
+        width: 600,
+        height: 800,
         webPreferences: {
             preload: path.join(__dirname, 'src/js/preload.js')
         },
@@ -231,7 +231,7 @@ async function findElementAcrossAllWindows(msg, notifyBrowser = true, scrollInto
         xpath = msg.message.xpath;
     } catch {
         //如果msg.pathList存在,说明是循环中的元素
-        if(msg.pathList != undefined && msg.pathList != null && msg.pathList != ""){
+        if (msg.pathList != undefined && msg.pathList != null && msg.pathList != "") {
             xpath = msg.pathList[0].trim();
         } else {
             xpath = msg.xpath;
@@ -301,15 +301,18 @@ async function beginInvoke(msg, ws) {
         // This method has to be called on macOS before changing the window's bounds, otherwise it will throw an error.
         // It will prompt an accessibility permission request dialog, if needed.
         if (process.platform != "linux" && process.platform != "darwin") {
-            const {windowManager} = require("node-window-manager");
-            const window = windowManager.getActiveWindow();
-            console.log(window);
-            windowManager.requestAccessibility();
-            // Sets the active window's bounds.
-            let size = screen.getPrimaryDisplay().workAreaSize
-            let width = parseInt(size.width)
-            let height = parseInt(size.height * 0.6)
-            window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
+            // 非用户信息模式下,设置窗口位置
+            if (config.user_data_folder == null || config.user_data_folder == undefined || config.user_data_folder == "") {
+                const {windowManager} = require("node-window-manager");
+                const window = windowManager.getActiveWindow();
+                console.log(window);
+                windowManager.requestAccessibility();
+                // Sets the active window's bounds.
+                let size = screen.getPrimaryDisplay().workAreaSize
+                let width = parseInt(size.width)
+                let height = parseInt(size.height * 0.6)
+                window.setBounds({x: 0, y: size.height * 0.4, height: height, width: width});
+            }
         }
         flowchart_window.show();
         // flowchart_window.openDevTools();
@@ -337,7 +340,7 @@ async function beginInvoke(msg, ws) {
                 if (type.indexOf("Click") >= 0 || type.indexOf("Move") >= 0) {
                     let element = await findElementAcrossAllWindows(message, notifyBrowser = true, scrollIntoView = false);
                     if (type.indexOf("Click") >= 0) {
-                        await click_element(element);
+                        await click_element(element, type);
                     } else if (type.indexOf("Move") >= 0) {
                         await driver.actions().move({origin: element}).perform();
                     }
@@ -366,6 +369,9 @@ async function beginInvoke(msg, ws) {
                     }
                     xpath = parent_xpath + xpath;
                 }
+                if (xpath.includes("point(")) {
+                    xpath = "//body";
+                }
                 let elementInfo = {"iframe": parameters.iframe, "xpath": xpath, "id": -1};
                 //用于跳转到元素位置
                 let element = await findElementAcrossAllWindows(elementInfo);
@@ -492,8 +498,13 @@ async function beginInvoke(msg, ws) {
                         }
                     }
                 } else if (option == 2 || option == 7) { //点击事件
-                    let elementInfo = {"iframe": parameters.iframe, "xpath": parameters.xpath, "id": -1};
-                    if (parameters.useLoop) {
+                    let xpath = parameters.xpath;
+                    let point = parameters.xpath;
+                    if (xpath.includes("point(")) {
+                        xpath = "//body"
+                    }
+                    let elementInfo = {"iframe": parameters.iframe, "xpath": xpath, "id": -1};
+                    if (parameters.useLoop && !parameters.xpath.includes("point(")) {
                         let parent_node = JSON.parse(msg.message.parentNode);
                         let parent_xpath = parent_node.parameters.xpath;
                         if (parent_node.parameters.loopType == 2) {
@@ -504,7 +515,11 @@ async function beginInvoke(msg, ws) {
                     let element = await findElementAcrossAllWindows(elementInfo, notifyBrowser = false); //通过此函数找到元素并切换到对应的窗口
                     await execute_js(parameters.beforeJS, element, parameters.beforeJSWaitTime);
                     if (option == 2) {
-                        await click_element(element);
+                        if (parameters.xpath.includes("point(")) {
+                            await click_element(element, point);
+                        } else {
+                            await click_element(element);
+                        }
                         let alertHandleType = parameters.alertHandleType;
                         if (alertHandleType == 1) {
                             try {
@@ -731,11 +746,25 @@ async function beginInvoke(msg, ws) {
     }
 }
 
-async function click_element(element) {
+async function click_element(element, type = "click") {
     try {
-        await element.click();
-        //ctrl+click
-        // await driver.actions().keyDown(Key.CONTROL).click(element).keyUp(Key.CONTROL).perform();
+        if (type == "loopClickEvery") {
+            await driver.actions().keyDown(Key.CONTROL).click(element).keyUp(Key.CONTROL).perform();
+        } else if (type.includes("point(")) {
+            //point(10, 20)表示点击坐标为(10, 20)的位置
+            let point = type.substring(6, type.length - 1).split(",");
+            let x = parseInt(point[0]);
+            let y = parseInt(point[1]);
+            // let actions = driver.actions();
+            // await actions.move({origin: element}).perform();
+            // await actions.move({x: x, y: y}).perform();
+            // await actions.click().perform();
+            let script = `document.elementFromPoint(${x}, ${y}).click();`;
+            await driver.executeScript(script);
+        } else {
+            await element.click();
+        }
+
     } catch (e) {
         console.log(e);
         await driver.executeScript("arguments[0].click();", element);
@@ -819,12 +848,12 @@ wss.on('connection', function (ws) {
                     await driver.switchTo().window(current_handle);
                     console.log("New tab opened, change current_handle to: ", current_handle);
                     // 调整浏览器窗口大小,不然扩展会白屏
-                    // let size = await driver.manage().window().getRect();
-                    // let width = size.width;
-                    // let height = size.height;
-                    // await driver.manage().window().setRect({width: width, height: height + 10});
-                    // // height = height - 1;
-                    // await driver.manage().window().setRect({width: width, height: height});
+                    let size = await driver.manage().window().getRect();
+                    let width = size.width;
+                    let height = size.height;
+                    await driver.manage().window().setRect({width: width, height: height + 10});
+                    // height = height - 1;
+                    await driver.manage().window().setRect({width: width, height: height});
                 }
                 await new Promise(resolve => setTimeout(resolve, 2000));
                 handle_pairs[msg.message.id] = current_handle;
@@ -894,6 +923,8 @@ async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
         options.addArguments("--user-data-dir=" + dir);
         config.user_data_folder = user_data_folder;
         fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
+    } else {
+        config.user_data_folder = "";
     }
     if (mobile) {
         const mobileEmulation = {

二进制
ElectronJS/src/img/toggle.png


+ 7 - 7
ElectronJS/src/index.html

@@ -94,9 +94,9 @@ For individual users, EasySpider is a completely free and ad-free open-source so
             </div>
             <div v-else-if="step == 1">
                 <h4 style="margin-top: 20px">Please select design mode</h4>
-                <p style="margin-top: 20px; text-align: justify; width:310px; margin-left: 18%">
-                    Clean Mode: Start with a clean browser with no cookie/user data.</p>
-                <p style="text-align: justify; width:310px; margin-left: 18%">
+                <p style="margin-top: 20px; text-align: justify; width:290px; margin-left: 25%">
+                    Clean Mode: Start with a clean browser with no cookies/user data.</p>
+                <p style="text-align: justify; width:290px; margin-left: 25%">
                     Data Mode: Start with a browser that stores user data such as website login information and cookies.</p>
                 <p><a @click="startDesign('en')"
                       class="btn btn-primary btn-lg"
@@ -177,10 +177,10 @@ For individual users, EasySpider is a completely free and ad-free open-source so
             </div>
             <div v-else-if="step == 1">
                 <h4 style="margin-top: 20px">请选择设计模式</h4>
-                <p style="margin-top: 20px; text-align: left; width:320px; margin-left: 18%">
+                <p style="margin-top: 20px; text-align: left; width:310px; margin-left: 24%">
                     纯净版浏览器:无任何用户信息的浏览器。</p>
-                <p style="text-align: left; width:320px; margin-left: 18%">
-                    带用户信息的浏览器:保存有用户数据,如网站的登录信息,cookie的浏览器。</p>
+                <p style="text-align: left; width:310px; margin-left: 24%">
+                    带用户信息的浏览器:保存有用户数据,如网站的登录信息,cookies的浏览器。</p>
                 <p><a @click="startDesign('zh')"
                       class="btn btn-primary btn-lg"
                       style="margin-top: 15px; width: 320px;height:60px;padding-top:12px;color:white;">使用纯净版浏览器设计</a>
@@ -203,7 +203,7 @@ For individual users, EasySpider is a completely free and ad-free open-source so
                 <h4 style="margin-top: 20px">指定用户信息目录</h4>
                 <div style="margin: 0 auto; width:90%">
                     <p style="margin-top: 20px; text-align: left">
-                        请在下方指定用户信息目录。设置后,浏览器将加载目录里的cookie,如用户的登录信息等内容,目录不变的情况下,每次设计和执行时浏览器都会加载此目录里的数据。</p>
+                        请在下方指定用户信息目录。设置后,浏览器将加载目录里的cookies,如用户的登录信息等内容,目录不变的情况下,每次设计和执行时浏览器都会加载此目录里的数据。</p>
                     <p style="margin-top: 10px; text-align: left">例如:设置了./user_data文件夹,并在设计过程中登录了知乎网站,则下次再次设计或者执行任务时指定./user_data文件夹,打开知乎网站页面会仍然保留之前的登录状态。</p>
                     <p style="margin-top: 10px; text-align: left">如果有多套配置,可以设置不同的目录,每个目录为一套,如果目录不存在将会被自动创建。</p>
                     <p><textarea class="form-control" style="min-height: 50px;"

+ 6 - 6
ElectronJS/src/taskGrid/FlowChart.html

@@ -146,7 +146,7 @@
                         <p><input spellcheck=false onkeydown="inputDelete(event)" type="checkbox" v-model='useLoop'></input>Use element located by xpath relative to the loop</p>
                     </div>
                     <div>
-                        <label>XPath: <span style="font-size: 30px!important;" title="Relative XPATH writing: start with /, e.g. the loop item XPATH is /html/body/div[1], your input is /*[@id='tab-customer'], then the final addressed xpath is: /html/body/div[1]/*[@id='tab-customer']">☺</span></label>
+                        <label>XPath (Or use "point(10,10)" to represent clicking on the web page at coordinate position (10, 10), suitable for the situation when need to click on a blank area to leave popup dialog): <span style="font-size: 30px!important;" title="Relative XPATH writing: start with /, e.g. the loop item XPATH is /html/body/div[1], your input is /*[@id='tab-customer'], then the final addressed xpath is: /html/body/div[1]/*[@id='tab-customer']">☺</span></label>
                         <textarea spellcheck=false onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["xpath"]'></textarea>
                         <p><button type="button" data-toggle="modal" data-target="#myModal_XPath" @click="changeXPaths(nowNode['parameters']['allXPaths'])" class="btn btn-primary" style="margin-top: 10px">Click here to view other equivalent XPath expressions</button></p>
                     </div>
@@ -690,11 +690,11 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
                     <input spellcheck=false onkeydown="inputDelete(event)" id="serviceDescription" name="serviceDescription" class="form-control"></input>
                     <label>Export Data Format (Excel/CSV/TXT/Database):</label>
                     <select id="outputFormat" class="form-control">
-                        <option value = "xlsx">XLSX (EXCEL, we suggest using the CSV format if the length of a single cell exceeds 500)</option>
-                        <option value = "csv">CSV</option>
-                        <option value = "txt">TXT</option>
-                        <option value = "json">JSON</option>
-                        <option value = "mysql">MySQL Database</option>
+                        <option value="xlsx">XLSX (Excel file, recommended use CSV format when single cell exceeds 500 characters)</option>
+                        <option value="csv">CSV (Recommended for collecting long articles)</option>
+                        <option value="txt">TXT</option>
+                        <option value="json">JSON</option>
+                        <option value="mysql">MySQL Database (recommended for large amounts of data)</option>
                     </select>
                     <label>Export File Name/Database Table Name (Can use ../ to represent relative path to change the file save location,the keyword "current_time" will be replaced with the timestamp when the task is executed):</label>
                     <input spellcheck=false onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>

+ 3 - 3
ElectronJS/src/taskGrid/FlowChart_CN.html

@@ -146,7 +146,7 @@
                         <p><input spellcheck=false onkeydown="inputDelete(event)" type="checkbox" v-model='useLoop'></input>使用相对循环内的XPath定位到的元素</p>
                     </div>
                     <div>
-                        <label>XPath: <span style="font-size: 30px!important;" title="相对XPATH写法:以/开头,如循环项XPATH为/html/body/div[1],您的输入为/*[@id='tab-customer'],则最终寻址的xpath为:/html/body/div[1]/*[@id='tab-customer']">☺</span></label>
+                        <label>XPath(或者用point(10,10)表示点击网页坐标位置(10, 10)以用来点击空白区域推出弹窗对话框等): <span style="font-size: 30px!important;" title="相对XPATH写法:以/开头,如循环项XPATH为/html/body/div[1],您的输入为/*[@id='tab-customer'],则最终寻址的xpath为:/html/body/div[1]/*[@id='tab-customer']">☺</span></label>
                         <textarea spellcheck=false onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["xpath"]'></textarea>
                         <p><button type="button" data-toggle="modal" data-target="#myModal_XPath" @click="changeXPaths(nowNode['parameters']['allXPaths'])" class="btn btn-primary" style="margin-top: 10px">点此查看其他等价的XPath</button></p>
                     </div>
@@ -691,10 +691,10 @@ print(emotlib.emoji()) # 使用其中的函数。
                     <label>导出数据格式(Excel/CSV/TXT/数据库,<a href="https://www.bilibili.com/video/BV1os4y1679S/" target="_blank">查看MySQL操作教程</a>):</label>
                     <select id="outputFormat" class="form-control">
                         <option value = "xlsx">XLSX(即EXCEL文件,建议单个单元格长度超过500时使用CSV格式存储)</option>
-                        <option value = "csv">CSV</option>
+                        <option value = "csv">CSV(采集长文章推荐使用此格式)</option>
                         <option value = "txt">TXT</option>
                         <option value = "json">JSON</option>
-                        <option value = "mysql">MySQL数据库</option>
+                        <option value = "mysql">MySQL数据库(大量数据推荐使用)</option>
                     </select>
                     <label>导出文件名/数据库表格名称(可使用../表示相对路径以改变文件保存位置,名称中的“current_time”会被替换为执行任务时的时间戳):</label>
                     <input spellcheck=false onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>

+ 16 - 2
ElectronJS/src/taskGrid/newTask.html

@@ -42,6 +42,19 @@
 <!--                <button type="submit" id="sendWithCookie" style="margin-top: 10px" class="btn btn-primary">{{"Start Design with cookie data from local browser~带本地数据开始设计" | lang}}</button>-->
 <!--            </div>-->
             <div style="margin-top: 20px">
+                <div v-if="mobile">
+                    <div v-if="language=='zh'">
+                        <p>提示:手机模式设计时如果没有出现操作提示框,请按键盘的Ctrl+Shift+I组合键(MacOS为Command+Option+I组合键)打开开发者工具,然后<b>双击</b>“切换设备”按钮,即可正常出现并使用操作提示框。</p>
+                    </div>
+                    <div v-else>
+                        <p>Tip: If the operation prompt box does not appear when designing in mobile mode, please press the
+                            Ctrl+Shift+I key combination (MacOS is the Command+Option+I key combination) to open the
+                            developer tool, and then <b>double-click</b> the "Toggle Device Toolbar" button, you can normally appear and use the operation toolbox.</p>
+                    </div>
+                    <img src="../img/toggle.png" alt="" style="width: 100%;height: 100%">
+                    <p></p>
+                </div>
+
                 <h5>{{"Example 1~示例1" | lang}}</h5>
                 <p>{{"(Right click) Select a large product block -> Click the 'Select All' option -> Click the 'Select Child Elements' option -> Click the 'Collect Data' option, you can collect the information of all products, and will be saved by sub-field. ~ (右键)选中一个大商品块 -> 自动检测到同类型商品块 -> 点击“选中全部”选项 -> 点击“选中子元素”选项 -> 点击“采集数据”选项,即可采集到所有商品的所有信息,并分成不同字段保存。" | lang}}</p>
                 <img src="../img/animation_zh.gif" alt="" style="width: 100%;height: 100%">
@@ -54,17 +67,18 @@
             </div>
         </div>
     </div>
-
 </body>
 
 </html>
 <script src="global.js"></script>
 <script>
-    var app = new Vue({
+    let app = new Vue({
         el: '#newTask',
         data: {
             backEndAddressServiceWrapper: getUrlParam("backEndAddressServiceWrapper"),
             user_data_folder: "",
+            mobile: getUrlParam("mobile"),
+            language: getUrlParam("lang"),
         },
         methods: {
             gotoHome: function () {

+ 1 - 1
ElectronJS/src/taskGrid/taskInfo.html

@@ -50,7 +50,7 @@
             <p style="word-wrap: break-word;word-break: break-all;overflow: hidden;max-height: 100px;">{{"Create Time:~创建时间:" | lang}} {{dateFormat(task["create_time"])}}</p>
             <p style="word-wrap: break-word;word-break: break-all;overflow: hidden;max-height: 100px;">{{"Update Time:~更新时间:" | lang}} {{dateFormat(task["update_time"])}}</p>
             <p>{{"Operations (Please close this window and select 'Design Task' button if you want to modify task with a browser)~操作(如要带浏览器修改任务流程请关闭此窗口并选择设计任务)" | lang}}</p>
-            <p><a style="margin-top: 5px" href="javascript:void(0)" v-on:click="modifyTask(task['id'],task['url'])" class="btn btn-primary">{{"Modify Task Workflow~修改任务流程" | lang}}</a>
+            <p><a style="margin-top: 5px" href="javascript:void(0)" v-on:click="modifyTask(task['id'],task['url'])" class="btn btn-primary">{{"Modify Task~修改任务" | lang}}</a>
                 <a style="margin-top: 5px" href="javascript:void(0)" v-on:click="invokeTask(task['id'],task['url'])" class="btn btn-primary">{{"Execute Task~执行任务" | lang}}</a></p>
             <p>{{"Input Parameters~输入参数" | lang}}</p>
             <table class="table table-bordered">

文件差异内容过多而无法显示
+ 0 - 0
ElectronJS/tasks/296.json


+ 1 - 1
ExecuteStage/.vscode/launch.json

@@ -12,7 +12,7 @@
             "justMyCode": false,
             //  "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"]
             // "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"]
-            "args": ["--ids", "[28]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
+            "args": ["--ids", "[61]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
         "--read_type", "remote"]
             // "args": "--ids '[97]' --user_data 1 --server_address http://localhost:8074 --config_folder '/Users/naibo/Documents/EasySpider/ElectronJS/' --headless 0 --read_type remote --config_file_name config.json --saved_file_name"
         }

+ 22 - 7
ExecuteStage/easyspider_executestage.py

@@ -466,9 +466,9 @@ class BrowserThread(Thread):
     def run(self):
         # 挨个执行程序
         for i in range(len(self.links)):
-            self.print_and_log("正在执行第", i + 1, "/ ", len(self.links), "个链接")
+            self.print_and_log("正在执行第", i + 1, "/", len(self.links), "个链接")
             self.print_and_log("Executing link", i + 1,
-                               "/ ", len(self.links))
+                               "/", len(self.links))
             self.executeNode(0)
             self.urlId = self.urlId + 1
         files = os.listdir("Data/Task_" + str(self.id) + "/" + self.saveName)
@@ -1208,7 +1208,7 @@ class BrowserThread(Thread):
                 if len(elements) == 0:
                     self.print_and_log("Loop element not found: ",
                                        xpath)
-                    self.print_and_log("找不到循环元素: ", xpath)
+                    self.print_and_log("找不到循环元素", xpath)
                 index = 0
                 while index < len(elements):
                     try:
@@ -1258,7 +1258,7 @@ class BrowserThread(Thread):
                     index = index + 1
             except NoSuchElementException:
                 self.print_and_log("Loop element not found: ", xpath)
-                self.print_and_log("找不到循环元素: ", xpath)
+                self.print_and_log("找不到循环元素", xpath)
             except Exception as e:
                 raise
         elif int(node["parameters"]["loopType"]) == 2:  # 固定元素列表
@@ -1303,7 +1303,7 @@ class BrowserThread(Thread):
                     index, element = self.handleHistory(node, path, thisHistoryURL, thisHistoryLength, index, element=element)
                 except NoSuchElementException:
                     self.print_and_log("Loop element not found: ", path)
-                    self.print_and_log("找不到循环元素: ", path)
+                    self.print_and_log("找不到循环元素", path)
                     index += 1
                     continue  # 循环中找不到元素就略过操作
                 except Exception as e:
@@ -1533,7 +1533,10 @@ class BrowserThread(Thread):
                 clickPath, self.outputParameters, self)
             xpath = replace_field_values(
                 param["xpath"], self.outputParameters, self)
-            if param["useLoop"]:  # 使用循环的情况下,传入的clickPath就是实际的xpath
+            if xpath.find("point(") >= 0:  # 如果xpath中包含point(),说明是相对坐标的点击
+                index = 0
+                path = "//body"
+            elif param["useLoop"]:  # 使用循环的情况下,传入的clickPath就是实际的xpath
                 if xpath == "":
                     path = clickPath
                 else:
@@ -1567,7 +1570,19 @@ class BrowserThread(Thread):
         except:
             newTab = 0
         try:
-            if click_way == 0:  # 用selenium的点击方法
+            if xpath.find("point(") >= 0:  # 如果xpath中包含point(),说明是相对坐标的点击
+                point = xpath.split("point(")[1].split(")")[0].split(",")
+                x = int(point[0])
+                y = int(point[1])
+                # try:
+                #     actions = ActionChains(self.browser)  # 实例化一个action对象
+                #     actions.move_to_element(element).perform()
+                #     actions.move_by_offset(x, y).perform()
+                #     actions.click().perform()
+                # except Exception as e:
+                script = "document.elementFromPoint(" + str(x) + "," + str(y) + ").click();"
+                self.browser.execute_script(script)
+            elif click_way == 0:  # 用selenium的点击方法
                 try:
                     actions = ActionChains(self.browser)  # 实例化一个action对象
                     if newTab == 1:  # 在新标签页打开

+ 2 - 2
ExecuteStage/utils.py

@@ -9,8 +9,8 @@ import time
 import uuid
 # import keyboard
 from openpyxl import Workbook, load_workbook
-import pandas as pd
-import xlsxwriter
+# import pandas as pd
+# import xlsxwriter
 import requests
 from urllib.parse import urlparse
 import pymysql

+ 4 - 0
Extension/manifest_v3/src/content-scripts/main.js

@@ -320,3 +320,7 @@ let closeButton = document.getElementById("closeButton");
 closeButton.addEventListener("click", function() {
     toolkit.style.display = "none"; // 隐藏元素
 });
+let closeButtonLeft = document.getElementById("closeButtonLeft");
+closeButtonLeft.addEventListener("click", function() {
+    toolkit.style.display = "none"; // 隐藏元素
+});

+ 3 - 2
Extension/manifest_v3/src/content-scripts/toolkit.vue

@@ -3,6 +3,7 @@
     <!--    <div id="EasySpiderResizer" style="width: 10px; height: 10px; background-color: black; position: absolute; left: 0; bottom: 0; cursor: ne-resize;"></div>-->
     <div id="EasySpiderResizer"
          style="width: 10px; height: 10px; position: absolute; left: 0; top: 0; cursor: nw-resize;"></div>
+    <span id="closeButtonLeft">&#x2716;</span>
     <span id="closeButton">&#x2716;</span>
     <div v-if="lang == 'zh'">
       <div class="tooldrag">✍操作台(点此拖动,左上角调整大小)</div>
@@ -33,7 +34,7 @@
             <p style="color:black; margin-top: 10px">● 操作完成后,如点击”确认采集“后任务流程图内没有”提取数据“操作被添加,<strong>重试一次</strong>即可。
             </p>
             <p style="color:black; margin-top: 10px">●
-              如果此操作台把页面元素挡住了,可以点击此操作台右下角的×按钮键关闭操作台。</p>
+              如果此操作台把页面元素挡住了,可以点击此操作台左下角或右下角的×按钮键关闭操作台。</p>
             {{ initial() }}
           </div>
           <div v-if="list.nl.length==1">
@@ -243,7 +244,7 @@
               again.</p>
             <p style="color:black; margin-top: 10px">● If this toolbox blocks the page element, you can click the ×
               button in the
-              lower right corner of this toolbox to close it.</p>
+              lower left/right corner of this toolbox to close it.</p>
             {{ initial() }}
           </div>
           <div v-if="list.nl.length==1">

+ 14 - 2
Extension/manifest_v3/src/style/toolkit.css

@@ -188,11 +188,23 @@
 #closeButton {
     position: absolute;
     bottom: 0;
-    right: 0;
+    right: 4px;
     cursor: pointer;
     padding: 5px;
-    opacity: 0.05;
+    opacity: 0.2;
 }
 #closeButton:hover {
   opacity: 1.0;
 }
+
+#closeButtonLeft {
+    position: absolute;
+    bottom: 0;
+    left: 4px;
+    cursor: pointer;
+    padding: 5px;
+    opacity: 0.2;
+}
+#closeButtonLeft:hover {
+  opacity: 1.0;
+}

部分文件因为文件数量过多而无法显示