Przeglądaj źródła

English Version

naibo 2 lat temu
rodzic
commit
b73a2a9954

+ 2 - 1
.temp_to_pub/.gitignore

@@ -4,4 +4,5 @@ EasySpider.app/
 EasySpider_windows_x64/user_data
 *.tmp
 *.7z*
-config.json
+config.json
+mysql_config.json

+ 6 - 0
.temp_to_pub/compress.py

@@ -48,6 +48,8 @@ if __name__ == "__main__":
         if os.path.exists("./EasySpider_windows_x64/user_data"):
             shutil.rmtree("./EasySpider_windows_x64/user_data")
         shutil.rmtree("./EasySpider_windows_x64/Data")
+        shutil.rmtree("./EasySpider_windows_x64/config.json")
+        shutil.rmtree("./EasySpider_windows_x64/mysql_config.json")
         shutil.rmtree("./EasySpider_windows_x64/execution_instances")
         os.mkdir("./EasySpider_windows_x64/Data")
         os.mkdir("./EasySpider_windows_x64/execution_instances")
@@ -61,6 +63,8 @@ if __name__ == "__main__":
             shutil.rmtree("./EasySpider_windows_x86/user_data")
         shutil.rmtree("./EasySpider_windows_x86/Data")
         shutil.rmtree("./EasySpider_windows_x86/execution_instances")
+        shutil.rmtree("./EasySpider_windows_x86/config.json")
+        shutil.rmtree("./EasySpider_windows_x86/mysql_config.json")
         os.mkdir("./EasySpider_windows_x86/Data")
         os.mkdir("./EasySpider_windows_x86/execution_instances")
         compress_folder_to_7z("./EasySpider_windows_x64", file_name)
@@ -71,6 +75,8 @@ if __name__ == "__main__":
             shutil.rmtree("./EasySpider_Linux_x64/user_data")
         shutil.rmtree("./EasySpider_Linux_x64/Data")
         shutil.rmtree("./EasySpider_Linux_x64/execution_instances")
+        shutil.rmtree("./EasySpider_Linux_x64/config.json")
+        shutil.rmtree("./EasySpider_Linux_x64/mysql_config.json")
         os.mkdir("./EasySpider_Linux_x64/Data")
         os.mkdir("./EasySpider_Linux_x64/execution_instances")
         # compress_folder_to_7z("./EasySpider_Linux_x64", file_name)

+ 3 - 3
ElectronJS/change_version.py

@@ -39,11 +39,11 @@ if __name__ == "__main__":
     file_path = "../.temp_to_pub/compress.py"
     update_file_version(file_path, version, key='easyspider_version = "')
 
-    file_path = "./src/taskGrid/logic.js"
+    file_path = "./src/taskGrid/logic_deprecated.js"
     update_file_version(file_path, version, key='"version": "')
 
-    file_path = "./src/taskGrid/logic_CN.js"
-    update_file_version(file_path, version, key='"version": "')
+#     file_path = "./src/taskGrid/logic.js"
+#     update_file_version(file_path, version, key='"version": "')
 
     file_path = "../ExecuteStage/easyspider_executestage.py"
     update_file_version(file_path, version, key='"version": "')

+ 7 - 0
ElectronJS/detect_chinese.py

@@ -0,0 +1,7 @@
+import re
+
+with open('src/taskGrid/FlowChart.js', 'r', encoding='utf-8') as file:
+    for line in file:
+        line = re.split('//', line)[0]
+        if re.search('[\u4e00-\u9fff]', line):
+            print(line)

+ 4 - 4
ElectronJS/main.js

@@ -93,7 +93,7 @@ function createWindow() {
 
     // and load the index.html of the app.
     // mainWindow.loadFile('src/index.html');
-    mainWindow.loadURL(server_address + '/index.html?user_data_folder=' + config.user_data_folder);
+    mainWindow.loadURL(server_address + '/index.html?user_data_folder=' + config.user_data_folder, { extraHeaders: 'pragma: no-cache\n' });
     // 隐藏菜单栏
     const {Menu} = require('electron');
     Menu.setApplicationMenu(null);
@@ -118,7 +118,7 @@ async function beginInvoke(msg, ws) {
                 url = server_address + `/taskGrid/FlowChart.html?id=${msg.message.id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address;
             }
             console.log(url);
-            flowchart_window.loadURL(url);
+            flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
         }
         mainWindow.hide();
         // Prints the currently focused window bounds.
@@ -475,7 +475,7 @@ function handleOpenBrowser(event, lang = "en", user_data_folder = "", mobile = f
         url = server_address + `/taskGrid/FlowChart_CN.html?id=${id}&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address+ "&mobile=" + mobile.toString();
     }
     // and load the index.html of the app.
-    flowchart_window.loadURL(url);
+    flowchart_window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
     if(process.platform != "darwin"){
         flowchart_window.hide();
     }
@@ -495,7 +495,7 @@ function handleOpenInvoke(event, lang = "en") {
         url = server_address + `/taskGrid/taskList.html?type=1&wsport=${websocket_port}&backEndAddressServiceWrapper=` + server_address + "&lang=zh";
     }
     // and load the index.html of the app.
-    window.loadURL(url);
+    window.loadURL(url, { extraHeaders: 'pragma: no-cache\n' });
     window.maximize();
     mainWindow.hide();
     window.on('close', function (event) {

+ 4 - 4
ElectronJS/src/index.html

@@ -95,7 +95,7 @@
                       style="margin-top: 15px; width: 300px;height:60px;padding-top:12px;color:white">Start Data Mode</a>
                 </p>
 
-                <a @click="step = 0" class="btn btn-outline-primary btn-lg"style="margin-top: 10px; width: 322px;height:45px;padding-top:5px">Go to Home Page</a>
+                <a @click="step = 0" class="btn btn-outline-primary btn-lg"style="margin-top: 10px; width: 302px;height:45px;padding-top:5px">Go to Home Page</a>
 
             </div>
             <div v-else-if="step == 2">
@@ -111,13 +111,13 @@
                 </div>
                 <p><a @click="startDesign('en', true)"
                       class="btn btn-primary btn-lg"
-                      style="margin-top: 15px; width: 320px;height:60px;padding-top:12px;color:white">Start Design</a></p>
+                      style="margin-top: 15px; width: 300px;height:60px;padding-top:12px;color:white">Start Design</a></p>
                 <p>
                 <p><a @click="startDesign('en', true, true)"
                       class="btn btn-primary btn-lg"
-                      style="margin-top: 15px; width: 320px;height:60px;padding-top:12px;color:white">Start Design (Mobile)</a></p>
+                      style="margin-top: 15px; width: 300px;height:60px;padding-top:12px;color:white">Start Design (Mobile)</a></p>
                 <p>
-                    <a @click="step = 0" class="btn btn-outline-primary btn-lg"style="margin-top: 10px; width: 322px;height:45px;padding-top:5px">Go to Home Page</a>
+                    <a @click="step = 0" class="btn btn-outline-primary btn-lg"style="margin-top: 10px; width: 302px;height:45px;padding-top:5px">Go to Home Page</a>
                 </p>
             </div>
         </div>

+ 93 - 30
ElectronJS/src/taskGrid/FlowChart.html

@@ -18,8 +18,8 @@
     <div id="tip" class="alert alert-success alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
         <button type="button" class="close" data-dismiss="alert">&times;</button> Hint: Save Successfully!
     </div>
-    <div id="tip2" class="alert alert-danger alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
-        <button type="button" class="close" data-dismiss="alert">&times;</button> Tip: Save failed, if you have multiple custom actions, their option names must be set to different names!
+    <div id="tipError" class="alert alert-danger alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
+        <button type="button" class="close" data-dismiss="alert">&times;</button> <span id="error_message">提示:保存名不符合MySQL表名规范,请重试!</span>
     </div>
     <div id="navigator">
         <nav aria-label="breadcrumb" v-if="type==1">
@@ -115,6 +115,21 @@
                     <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
                     <label>Wait time after scrolling (in seconds): </label>
                     <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
+                    <p style="margin-top: 10px">
+                        <a class="btn btn-primary" data-toggle="collapse" href="#collapseOpenPage" role="button" aria-expanded="false" aria-controls="collapseExample">
+                            Click here to expand/collapse advanced operations
+                        </a>
+                    </p>
+                    <div :class="{collapse: true, 'show': nowNode['parameters']['cookies'].length!=0}" id="collapseOpenPage">
+                        <div>
+                            <label>Set Cookies after page loaded: </label>
+                            <p style="margin-bottom: 20px;color:white"><a class="btn btn-primary" @click="getCookies">
+                              Click here to get cookies of current page
+                            </a></p>
+                            <textarea onkeydown="inputDelete(event)" class="form-control" rows="2"
+                                      placeholder='key=value, one pair per line' v-model='nowNode["parameters"]["cookies"]' id="pageCookies" style="font-size: 14px!important;"></textarea>
+                        </div>
+                    </div>
                 </div>
 
                 <div class="elements" v-if="nodeType==2">
@@ -221,6 +236,19 @@
                                 <input onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='paras.parameters[paraIndex]["afterJSWaitTime"]'></input>
                             </div>
                         </div>
+                        <label>Parameter type conversion (for Excel and Database):</label>
+                        <select v-model='paras.parameters[paraIndex]["paraType"]' class="form-control">
+                            <option value = "text">Text (for single values estimated to exceed 10,000 in length, please choose Large Text)</option>
+                            <option value = "int">Integer (up to 9 digits)</option>
+                            <option value = "double">Floating Number (Decimal)</option>
+                            <option value = "mediumText">Large Text (single value length exceeding 10,000 but less than 1,000,000)</option>
+                            <option value = "datetime">Date Time</option>
+                            <option value = "date">Date</option>
+                            <option value = "time">Time</option>
+                            <option value = "varchar">Small Text (single value length less than 50)</option>
+                            <option value = "longText">Extra Large Text (single value length exceeding 1,000,000)</option>
+                            <option value = "bigInt">Large Integer (more than 9 digits)</option>
+                        </select>
                         <label>Extract Type</label>
                         <select v-model='paras.parameters[paraIndex]["contentType"]' class="form-control">
                             <option :value = 0>Text (include child element)</option>
@@ -232,7 +260,8 @@
                             <option :value = 6>Webpage Title</option>
                             <option :value = 7>Element Screenshot</option>
                             <option :value = 8>OCR Results</option>
-                            <option :value = 9>The return value after executing JavaScript script on this element  (start with 'return ')</option>
+                            <option :value = 9>Return value of JavaScript code (for this element), starting with 'return')</option>
+                            <option :value = 12>System command return value</option>
                             <option :value = 10>Selected value of the current select box</option>
                             <option :value = 11>Selected text of the current select box</option>
                         </select>
@@ -263,6 +292,11 @@
 <!--                            <option :value = 0>普通提取</option>-->
 <!--                            <option :value = 1>OCR提取</option>-->
 <!--                        </select>-->
+                        <label style="margin-top: 15px">Whether to save this field: (Choose 'No' if you only want to treat this field as a variable and not save it):</label>
+                        <select v-model='paras.parameters[paraIndex]["recordASField"]' class="form-control">
+                            <option :value = 1>Yes</option>
+                            <option :value = 0>No</option>
+                        </select>
                         <label>Parameter Description:</label>
                         <textarea onkeydown="inputDelete(event)" class="form-control" style="min-height: 60px" v-model='paras.parameters[paraIndex]["desc"]'></textarea>
                         <label>Default value when cannot find this element:</label>
@@ -310,13 +344,15 @@
                 <div class="elements" v-if="nodeType==5">
                     <p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Action is inside iframe</p>
                     <label>Custom Action Mode</label>
-                    <select v-model='nowNode["parameters"]["codeMode"]' class="form-control">
+                    <select v-model='codeMode' class="form-control">
                         <option value = 0>Execute JavaScript script</option>
                         <option value = 1>Execute operating system-level command</option>
                         <option v-if="nowNode['isInLoop']" value = 2>Execute JavaScript script for the current element inside the loop</option>
+                        <option v-if="nowNode['isInLoop']" value = 3>Exit Current Loop (the "Break" operation)</option>
+
                     </select>
 
-                    <div>
+                    <div v-if='nowNode["parameters"]["codeMode"] < 3'>
                         <label>Code (Use Field["FieldName"] to input the last extracted value of a field): </label>
                         <textarea onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["code"]' placeholder="Please input a JavaScript command or a system command. For example, document.body.innerText = '1' is an example of a JavaScript command, and python D:/test.py is an example of a system command. If you choose to execute a JavaScript script for the current iteration, you can represent the element of the current iteration using arguments[0]. For instance, arguments[0].style.color = 'blue' sets the color of the element in the current iteration to blue."></textarea>
                         <p style="margin-top: 15px">Whether to record the output/return value of the execution as a field: </p>
@@ -324,6 +360,20 @@
                             <option value = 0>No</option>
                             <option value = 1>Yes</option>
                         </select></p>
+                        <p><label>Convert parameter type to:</label>
+                        <select v-model='paras.parameters[paraIndex]["paraType"]' class="form-control">
+                            <option value = "text">Text (for single values estimated to exceed 10,000 in length, please choose Large Text)</option>
+                            <option value = "int">Integer (up to 9 digits)</option>
+                            <option value = "double">Floating Number (Decimal)</option>
+                            <option value = "mediumText">Large Text (single value length exceeding 10,000 but less than 1,000,000)</option>
+                            <option value = "datetime">Date Time</option>
+                            <option value = "date">Date</option>
+                            <option value = "time">Time</option>
+                            <option value = "varchar">Small Text (single value length less than 50)</option>
+                            <option value = "longText">Extra Large Text (single value length exceeding 1,000,000)</option>
+                            <option value = "bigInt">Large Integer (more than 9 digits)</option>
+                        </select>
+                        </p>
                         <label>Maximum wait time for script execution (0 represents unlimited wait time): </label>
                         <input onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='nowNode["parameters"]["waitTime"]'></input>
                     </div>
@@ -403,19 +453,6 @@
                     <label v-if='parseInt(loopType) == 0'>Max Loop time(0 means infinite):</label>
                     <input onkeydown="inputDelete(event)" required v-if='parseInt(loopType) == 0' class="form-control" type="number" v-model.number='nowNode["parameters"]["exitCount"]'></input>
 
-
-                    <label>Waiting time in seconds after a history record rollback: </label>
-                    <input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input>
-                    <label>After executed, whether scroll down:</label>
-                    <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
-                        <option value = 0>No Scrolling</option>
-                        <option value = 1>Scroll one screen</option>
-                        <option value = 2>Scroll to the end</option>
-                      </select>
-                    <label>Scroll Times:</label>
-                    <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
-                    <label>Wait time after scrolling (in seconds):</label>
-                    <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
                     <div id="breakAdvanced" v-if='nowNode["parameters"]["loopType"] < 5'>
                         <div>
                             <p><label>(Advanced Operation) Define loop exit condition using code/script:</label></p>
@@ -432,6 +469,18 @@
                             </div>
                         </div>
                     </div>
+                    <label>Waiting time in seconds after a history record rollback: </label>
+                    <input onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input>
+                    <label>After executed, whether scroll down:</label>
+                    <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
+                        <option value = 0>No Scrolling</option>
+                        <option value = 1>Scroll one screen</option>
+                        <option value = 2>Scroll to the end</option>
+                    </select>
+                    <label>Scroll Times:</label>
+                    <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
+                    <label>Wait time after scrolling (in seconds):</label>
+                    <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
                 </div>
 
                 <div class="elements" v-if="nodeType==9">
@@ -452,6 +501,7 @@
                         <option value = 6>Return value of system command</option>
                         <option v-if="nowNode['isInLoop']" value = 7>Return value of JavaScript command for the current loop item</option>
                       </select>
+
                     <div v-if='TClass > 0 && TClass < 5'>
                         <label>Text/Element XPath to Include: <span style="font-size: 30px!important;" title="Relative XPath syntax: starts with /, e.g., if the XPath of the loop item is /html/body/div[1], and you input /*[@id='tab-customer'], the final XPath will be: /html/body/div[1]/*[@id='tab-customer']">☺</span></label>
                         <textarea onkeydown="inputDelete(event)" required placeholder="If the current loop contains elements, input the xpath of the relative element (such as '/div[2]/div[1]/img', if written in relative path, it should be written as '/*//img', which means checking whether there exists an 'img' tag among all the descendant elements of the current loop item.)." class="form-control" rows="3" v-model='nowNode["parameters"]["value"]'></textarea>
@@ -468,7 +518,6 @@
                         <label>Maximum wait time for script execution (0 represents unlimited wait time): </label>
                         <input onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='nowNode["parameters"]["waitTime"]'></input>
                     </div>
-
                 </div>
                 <div style="margin-top:5px">
                     <label>Seconds <b>after executed</b> (Can be set to a decimal, such as 0.5):</label>
@@ -495,25 +544,38 @@
                     <h4 class="modal-title" id="myModalLabel">Save Task</h4>
                     <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                 </div>
-                <div class="modal-body">
+                <div class="modal-body" style="height:400px;overflow: auto">
                     <input onkeydown="inputDelete(event)" id="serviceId" type="hidden" name="serviceId" value="-1"></input>
                     <input onkeydown="inputDelete(event)" id="url" type="hidden" name="url" value="about:blank"></input>
+                    <input id="create_time" type="hidden"></input>
                     <label>Task Name:</label>
-                    <input onkeydown="inputDelete(event)" required name="serviceName" value="New Crawler Task" id="serviceName" class="form-control"></input>
+                    <input onkeydown="inputDelete(event)" required name="serviceName" value="New Web Scraping Task" id="serviceName" class="form-control"></input>
                     <label>Task Description:</label>
                     <input onkeydown="inputDelete(event)" id="serviceDescription" name="serviceDescription" class="form-control"></input>
-                    <label>How many data to save each time (the larger the value, the faster the collection speed, but there is a risk of data loss):</label>
-                    <input onkeydown="inputDelete(event)" type="number" value="10" id="saveThreshold" name="saveThreshold" class="form-control"></input>
-                    <label>Is the page an extreme anti-crawler website such as Cloudflare:</label>
+                    <label>Export Data Format (Excel/CSV/TXT/Database):</label>
+                    <select id="outputFormat" class="form-control">
+                        <option value = "xlsx">XLSX (EXCEL)</option>
+                        <option value = "csv">CSV</option>
+                        <option value = "txt">TXT</option>
+                        <option value = "mysql">MySQL Database</option>
+                    </select>
+                    <label>Export File Name/Database Table Name (The keyword "current_time" will be replaced with the timestamp when the task is executed):</label>
+                    <input onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>
+                    <label>Is it an extreme anti-scraping website like Cloudflare?</label>
                     <select id="cloudflare" name="cloudflare" class="form-control">
-                        <option value = 0>No</option>
-                        <option value = 1>Yes</option>
+                        <option value=0>No</option>
+                        <option value=1>Yes</option>
                     </select>
-                    <label>Browser simulation type:</label>
+                    <label>Browser Emulation Type:</label>
                     <select id="environment" name="environment" class="form-control">
-                        <option value = 0>Personal Computer</option>
-                        <option value = 1>Mobile (not support on Cloudflare mode)</option>
+                        <option value=0>Desktop</option>
+                        <option value=1>Mobile (Not supported under Cloudflare mode)</option>
                     </select>
+                    <label>Save Data Every N Rows (The larger the value, the faster the scraping speed, but there is a risk of data loss if unexpectedly exited):</label>
+                    <input onkeydown="inputDelete(event)" type="number" value="10" id="saveThreshold" name="saveThreshold" class="form-control"></input>
+                    <label>Maximum Display Length of Data in Console Preview:</label>
+                    <input onkeydown="inputDelete(event)" type="number" value="15" id="maxViewLength" class="form-control"></input>
+
                 </div>
                 <div class="modal-footer">
                     <button type="button" id="saveAsButton" class="btn btn-outline-primary">Save as</button>
@@ -527,9 +589,10 @@
 
 
 </body>
+
 <script src="FlowChart.js"></script>
-<script src="logic.js"></script>
 <script src="global.js"></script>
+<script src="logic.js"></script>
 
 <script>
     var navigator = new Vue({

+ 71 - 40
ElectronJS/src/taskGrid/FlowChart.js

@@ -14,6 +14,7 @@ let root = {
         useLoop: false, //是否使用循环中的元素
         xpath: "", //xpath
         wait: 0,
+        waitType: 0,
     },
     isInLoop: false, //是否处于循环内
 };
@@ -26,6 +27,8 @@ let option = 0; //工具箱选项
 let title = "";
 let parameterNum = 1; //记录目前的参数个数
 
+// window.resizeTo( screen.availWidth, screen.availHeight );
+
 //处理逻辑层
 let app = new Vue({
     el: '#app',
@@ -34,13 +37,14 @@ let app = new Vue({
         index: vueData,
         nodeType: 0, // 当前元素的类型
         nowNode: null, // 用来临时存储元素的节点
+        codeMode: 0, //代码模式
         loopType: -1, //点击循环时候用来循环选项
         useLoop: false, //记录是否使用循环内元素
         nowArrow: { "position": -1, "pId": 0, "num": 0 },
         paras: { "parameters": [] }, //提取数据的参数列表
         TClass: -1, //条件分支的条件类别
         paraIndex: 0, //当前参数的index
-        XPaths: "",
+        XPaths: "", //xpath列表
     },
     watch: {
         nowArrow: { //变量发生变化的时候进行一些操作
@@ -65,7 +69,7 @@ let app = new Vue({
                 }
             }
         },
-        loopType: {
+        loopType: { //循环类型发生变化的时候更新参数值
             handler: function(newVal, oldVal) {
                 this.nowNode["parameters"]["loopType"] = newVal;
             }
@@ -85,8 +89,22 @@ let app = new Vue({
                 this.nowNode["parameters"]["paras"] = newVal["parameters"];
             }
         },
+        codeMode: {
+            handler: function(newVal, oldVal) {
+                this.nowNode["parameters"]["codeMode"] = newVal;
+            }
+        }
     },
     methods: {
+        getCookies: function() { //获取cookies
+            let command = new WebSocket("ws://localhost:"+getUrlParam("wsport"))
+            command.onopen = function() {
+                let message = {
+                    type: 7, //消息类型,0代表连接操作
+                };
+                this.send(JSON.stringify(message));
+            };
+        },
         changeXPaths: function (XPaths){
             let result = "";
             for (let i = 0; i < XPaths.length; i++) {
@@ -99,30 +117,35 @@ let app = new Vue({
                 "nodeType": 0,
                 "contentType": 0,
                 "relative": false,
-                "name": "Custom_Field_" + this.nowNode["parameters"]["paras"].length.toString(),
+                "name": LANG("自定义参数_" + this.nowNode["parameters"]["paras"].length.toString(),"Custom_Field_" + this.nowNode["parameters"]["paras"].length.toString()),
                 "desc": "",
                 "extractType": 0,
-                "relativeXPath": "",
+                "relativeXPath": "//body",
+                "recordASField": 1,
                 "allXPaths": [],
                 "exampleValues": [
                     {
                         "num": 0,
-                        "value": "Custom_Value_0"
+                        "value": LANG("自定义值", "Custom_Value")
                     }
                 ],
                 "default": "",
                 "beforeJS": "",
                 "beforeJSWaitTime": 0,
                 "JS": "",
+                "paraType": "text",
                 "JSWaitTime": 0,
                 "afterJS": "",
                 "afterJSWaitTime": 0,
                 "downloadPic": 0
             });
             this.paraIndex = this.nowNode["parameters"]["paras"].length - 1;
+            setTimeout(function(){$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部
+            }, 200);
         },
         modifyParas: function(i) { //修改第i个参数
             this.paraIndex = i;
+            console.log(this.paras);
         },
         deleteParas: function(i) { //删除第i个参数
             this.nowNode["parameters"]["paras"].splice(i, 1);
@@ -151,13 +174,13 @@ let app = new Vue({
                 return "OuterHTML";
             }
             if (nodeType == 2) {
-                return "Link Address";
+                return LANG("链接地址", "Link Address");
             } else if (nodeType == 1) {
-                return "Link Text";
+                return LANG("链接文本","Link Text");
             } else if (nodeType == 4) {
-                return "Image Address";
+                return LANG("图片地址","Image Address");
             } else {
-                return "Text";
+                return LANG("文本","Text");
             }
         }
     }
@@ -193,12 +216,18 @@ function newNode(node) {
             <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
     } else if (type == 2) //判断
     {
-        return `<div class="loop clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
+        return LANG(`<div class="loop clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
+                    <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
+                    <p class="branchAdd" data="${id}">点击此处在最左边增加条件分支</p>
+                    <div class="judge" id = "${id}">
+                    </div></div>
+                    <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`,
+            `<div class="loop clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
                     <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
                     <p class="branchAdd" data="${id}">Click here to add a new condition to the left most</p>
                     <div class="judge" id = "${id}">
                     </div></div>
-                    <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
+                    <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`);
     } else //判断分支
     {
         return `<div class="branch clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
@@ -209,19 +238,7 @@ function newNode(node) {
     }
 }
 
-function elementMousedown(e) {
-    if (e.button == 2) //右键点击
-    {
-        if (nowNode != null) {
-            nowNode.style.borderColor = "skyblue";
-        }
-        nowNode = this;
-        vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
-        this.style.borderColor = "blue";
-        handleElement(); //处理元素
-    }
-    e.stopPropagation(); //防止冒泡
-}
+
 
 function branchMouseDown(e) {
     if (e.button == 2) //右键点击
@@ -234,7 +251,7 @@ function branchMouseDown(e) {
             parentId: 0,
             type: 3,
             option: 10,
-            title: "Condition",
+            title: LANG("条件分支", "Condition"),
             sequence: [],
             isInLoop: false,
         };
@@ -267,7 +284,7 @@ function branchClick(e) {
         parentId: 0,
         type: 3,
         option: 10,
-        title: "Condition",
+        title: LANG("条件分支", "Condition"),
         sequence: [],
         isInLoop: false,
     };
@@ -280,6 +297,20 @@ function branchClick(e) {
     e.stopPropagation(); //防止冒泡
 }
 
+function elementMousedown(e) {
+    if (e.button == 2) //右键点击
+    {
+        if (nowNode != null) {
+            nowNode.style.borderColor = "skyblue";
+        }
+        nowNode = this;
+        vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
+        this.style.borderColor = "blue";
+        handleElement(); //处理元素
+    }
+    e.stopPropagation(); //防止冒泡
+}
+
 //元素点击事件
 function elementClick(e) {
     if (nowNode != null) {
@@ -304,9 +335,8 @@ function arrowClick(e) {
 function addElement(op, para) {
     option = op;
     if (option == 1) { //打开网页选项
-        title = "Open Page";
-    }
-    else {
+        title = LANG("打开网页", "Open Page");
+    } else {
         title = $(".options")[option - 1].innerHTML; //获取新增操作名称
     }
 
@@ -323,7 +353,7 @@ function toolBoxKernel(e, para = null) {
         if (nowNode == null) {
             e.stopPropagation(); //防止冒泡
         } else if (nowNode.getAttribute("dataType") > 0) {
-            alert("Cannot copy loop, if and condition!");
+            showError(LANG("循环和判断、条件分支不可复制!", "Cannot copy loop, if and condition!"));
             e.stopPropagation(); //防止冒泡
         } else {
             let position = parseInt(nowNode.getAttribute('position'));
@@ -345,7 +375,7 @@ function toolBoxKernel(e, para = null) {
         if (nowNode == null) {
             e.stopPropagation(); //防止冒泡
         } else if ($(nowNode).is(".branch")) {
-            alert("Cannot move condition branch!");
+            showError(LANG("判断分支不可移动!", "Cannot move condition branch!"));
             e.stopPropagation(); //防止冒泡
         } else {
             let position = parseInt(nowNode.getAttribute('position'));
@@ -377,7 +407,7 @@ function toolBoxKernel(e, para = null) {
                 app._data.nowArrow = { "position": nodeList[element[0]]["position"], "pId": nodeList[element[0]]["parentId"], "num": 0 };
                 $("#" + nodeList[element[0]]["id"]).click();
             } else {
-                alert("Cannot move inside self!");
+                showError(LANG("自己不能移动到自己的节点里!", "Cannot move inside self!"));
             }
             e.stopPropagation(); //防止冒泡
         }
@@ -407,7 +437,7 @@ function toolBoxKernel(e, para = null) {
                 index: l + 1,
                 type: 3,
                 option: 10,
-                title: "Condition",
+                title: LANG("条件分支", "Condition"),
                 sequence: [],
                 isInLoop: false,
             };
@@ -417,7 +447,7 @@ function toolBoxKernel(e, para = null) {
                 index: l + 2,
                 type: 3,
                 option: 10,
-                title: "Condition",
+                title: LANG("条件分支", "Condition"),
                 sequence: [],
                 isInLoop: false,
             };
@@ -462,7 +492,7 @@ $(".options").mousedown(function() {
     option = parseInt(this.getAttribute("data"));
     title = this.innerHTML;
     if (option >= 10 && option <= 12 && (nowNode == null || nowNode.getAttribute("id") == 0)) {
-        alert("No element is selected now!");
+        showError(LANG("目前未选中元素。", "No element selected。"));
     } else if (option == 12) {
         deleteElement();
         $(".options")[12].click();
@@ -537,11 +567,11 @@ function refresh(nowArrowReset = true) {
 
 function deleteElement() {
     if (nowNode.getAttribute("id") == 0) {
-        alert("No element is selected now!"); //root
+        showError(LANG("当前未选中元素!", "No element is selected now!"));
         return;
     }
     // if (nodeList[actionSequence[nowNode.getAttribute("data")]]["option"] == 1) {
-    //     alert("Cannot delete the element of Open Page!");
+    //     showError("打开网页操作不可删除!");
     //     return;
     // }
     let position = parseInt(nowNode.getAttribute('position'));
@@ -575,7 +605,7 @@ document.oncontextmenu = function() {
     //删除元素
 document.onkeydown = function(e) {
     if (nowNode != null && e.keyCode == 46) {
-        // if (confirm("Do you really want to delete the selected operation?")) {
+        // if (confirm("确定要删除元素吗?")) {
             deleteElement();
         // }
     } else { //ctrl+s保存服务
@@ -588,7 +618,7 @@ document.onkeydown = function(e) {
             location.reload();
         } else if (currKey == 123) {
             console.log("打开devtools")
-            let command = new WebSocket("ws://localhost:8084")
+            let command = new WebSocket("ws://localhost:"+getUrlParam("wsport"))
             command.onopen = function() {
                 let message = {
                     type: 6, //消息类型,0代表连接操作
@@ -602,5 +632,6 @@ document.onkeydown = function(e) {
 function inputDelete(e) {
     if (e.keyCode == 46) {
         e.stopPropagation(); //输入框按delete应该正常运行
+        //Electron中如果有showError或者confirm,执行后会卡死输入框,所以最好不要用
     }
-}
+}

+ 7 - 9
ElectronJS/src/taskGrid/FlowChart_CN.html

@@ -18,10 +18,9 @@
     <div id="tip" class="alert alert-success alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
         <button type="button" class="close" data-dismiss="alert">&times;</button> 提示:保存成功!
     </div>
-    <div id="tipMySQL" class="alert alert-danger alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
-        <button type="button" class="close" data-dismiss="alert">&times;</button> 提示:保存名不符合MySQL表名规范,请重试!
+    <div id="tipError" class="alert alert-danger alert-dismissible fade show" style="position: fixed; width:100%;display: none;">
+        <button type="button" class="close" data-dismiss="alert">&times;</button> <span id="error_message">提示:保存名不符合MySQL表名规范,请重试!</span>
     </div>
-
     <div id="navigator">
         <nav aria-label="breadcrumb" v-if="type==1">
             <ol class="breadcrumb" style="padding-left:23px;padding-bottom: 0;margin-bottom:0;background-color: white">
@@ -37,7 +36,6 @@
         <div style="width: 200px;float:left;overflow: auto">
             <div class="toolbox" style="text-align:center;margin: 20px;border-radius:10px;border:navy solid;background-color:rgb(242,243,245);z-index: 9999;">
                 <div style="padding: 10px;border-radius:10px;font-size: 20px;">工具箱</div>
-<!--                window.resizeTo( screen.availWidth, screen.availHeight );-->
                 <button type="button" id="save" data-toggle="modal" data-target="#myModal" onmousedown="$('#myModal').modal('show');" class="btn btn-primary">保存任务</button>
                  <button type="button" data=1 class="btn btn-outline-primary options">打开网页</button>
                 <button type="button" data=2 class="btn btn-outline-primary options">点击元素</button>
@@ -117,7 +115,6 @@
                     <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
                     <label>滚动后等待时间(秒):</label>
                     <input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
-
                     <p style="margin-top: 10px">
                         <a class="btn btn-primary" data-toggle="collapse" href="#collapseOpenPage" role="button" aria-expanded="false" aria-controls="collapseExample">
                             点此展开/折叠高级操作
@@ -133,7 +130,6 @@
                                       placeholder='key=value形式,每行一个键值对' v-model='nowNode["parameters"]["cookies"]' id="pageCookies" style="font-size: 14px!important;"></textarea>
                         </div>
                     </div>
-
                 </div>
 
                 <div class="elements" v-if="nodeType==2">
@@ -506,7 +502,7 @@
                         <option v-if="nowNode['isInLoop']" value = 7>针对当前循环项的JavaScript命令返回值(需以return 开头)</option>
                       </select>
 
-                    <div v-if='TClass>0 && TClass <5'>
+                    <div v-if='TClass > 0 && TClass < 5'>
                         <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>
                         <textarea onkeydown="inputDelete(event)" required placeholder="如果是当前循环包含元素,则输入相对元素的xpath(如/div[2]/div[1]/img,如果写相对路径,需要写成/*//img,即检测当前循环项所有的子孙元素是否存在img标签)。" class="form-control" rows="3" v-model='nowNode["parameters"]["value"]'></textarea>
                     </div>
@@ -551,6 +547,7 @@
                 <div class="modal-body" style="height:400px;overflow: auto">
                     <input onkeydown="inputDelete(event)" id="serviceId" type="hidden" name="serviceId" value="-1"></input>
                     <input onkeydown="inputDelete(event)" id="url" type="hidden" name="url" value="about:blank"></input>
+                    <input id="create_time" type="hidden"></input>
                     <label>任务名称:</label>
                     <input onkeydown="inputDelete(event)" required name="serviceName" value="新web采集任务" id="serviceName" class="form-control"></input>
                     <label>任务描述:</label>
@@ -592,9 +589,10 @@
 
 
 </body>
-<script src="FlowChart_CN.js"></script>
-<script src="logic_CN.js"></script>
+
+<script src="FlowChart.js"></script>
 <script src="global.js"></script>
+<script src="logic.js"></script>
 
 <script>
     var navigator = new Vue({

+ 0 - 627
ElectronJS/src/taskGrid/FlowChart_CN.js

@@ -1,627 +0,0 @@
-//处理表现层
-let nodeList = Array(); //所有新生成的节点全部存储在这里,并且有唯一索引号,所有的定位均通过index进行,即将图保存下来了
-let root = {
-    index: 0, //在nodeList中的索引号
-    id: 0,
-    parentId: 0,
-    type: -1,
-    option: 0,
-    title: "root",
-    sequence: [],
-    parameters: {
-        history: 1,
-        tabIndex: 0,
-        useLoop: false, //是否使用循环中的元素
-        xpath: "", //xpath
-        wait: 0,
-        waitType: 0,
-    },
-    isInLoop: false, //是否处于循环内
-};
-nodeList.push(root);
-let queue = new Array();
-let actionSequence = new Array(); //存储图结构,每个元素为在nodelist里面的索引值,下面的id和pid根据此数组进行索引,然后再在nodelist里找
-let nowNode = null; //存储现在所在的节点
-let vueData = { nowNodeIndex: 0 }; //存储目前所在节点的索引号,不能直接使用变量而需要用对象包起来
-let option = 0; //工具箱选项
-let title = "";
-let parameterNum = 1; //记录目前的参数个数
-
-//处理逻辑层
-let app = new Vue({
-    el: '#app',
-    data: {
-        list: { nl: nodeList },
-        index: vueData,
-        nodeType: 0, // 当前元素的类型
-        nowNode: null, // 用来临时存储元素的节点
-        codeMode: 0, //代码模式
-        loopType: -1, //点击循环时候用来循环选项
-        useLoop: false, //记录是否使用循环内元素
-        nowArrow: { "position": -1, "pId": 0, "num": 0 },
-        paras: { "parameters": [] }, //提取数据的参数列表
-        TClass: -1, //条件分支的条件类别
-        paraIndex: 0, //当前参数的index
-        XPaths: "", //xpath列表
-    },
-    watch: {
-        nowArrow: { //变量发生变化的时候进行一些操作
-            deep: true,
-            handler: function(newVal, oldVal) {
-                let arrlist = document.getElementsByClassName("arrow");
-                if (oldVal != null) {
-                    for (let i = 0; i < arrlist.length; i++) {
-                        if (arrlist[i].getAttribute("position") == oldVal["position"] &&
-                            arrlist[i].getAttribute("pid") == oldVal["pId"]) {
-                            arrlist[i].style.backgroundColor = ""; // 时刻指示现在应该插入的节点的位置
-                            break;
-                        }
-                    }
-                }
-                for (let i = 0; i < arrlist.length; i++) {
-                    if (arrlist[i].getAttribute("position") == newVal["position"] &&
-                        arrlist[i].getAttribute("pid") == newVal["pId"]) {
-                        arrlist[i].style.backgroundColor = "lavender"; // 时刻指示现在应该插入的节点的位置
-                        break;
-                    }
-                }
-            }
-        },
-        loopType: { //循环类型发生变化的时候更新参数值
-            handler: function(newVal, oldVal) {
-                this.nowNode["parameters"]["loopType"] = newVal;
-            }
-        },
-        TClass: {
-            handler: function(newVal, oldVal) {
-                this.nowNode["parameters"]["class"] = newVal;
-            }
-        },
-        useLoop: {
-            handler: function(newVal, oldVal) {
-                this.nowNode["parameters"]["useLoop"] = newVal;
-            }
-        },
-        paras: {
-            handler: function(newVal, oldVal) {
-                this.nowNode["parameters"]["paras"] = newVal["parameters"];
-            }
-        },
-        codeMode: {
-            handler: function(newVal, oldVal) {
-                this.nowNode["parameters"]["codeMode"] = newVal;
-            }
-        }
-    },
-    methods: {
-        getCookies: function() { //获取cookies
-            let command = new WebSocket("ws://localhost:"+getUrlParam("wsport"))
-            command.onopen = function() {
-                let message = {
-                    type: 7, //消息类型,0代表连接操作
-                };
-                this.send(JSON.stringify(message));
-            };
-        },
-        changeXPaths: function (XPaths){
-            let result = "";
-            for (let i = 0; i < XPaths.length; i++) {
-                result += XPaths[i] + "\n";
-            }
-            this.XPaths = result;
-        },
-        addPara: function() { //添加参数
-            this.nowNode["parameters"]["paras"].push({
-                "nodeType": 0,
-                "contentType": 0,
-                "relative": false,
-                "name": "自定义参数_" + this.nowNode["parameters"]["paras"].length.toString(),
-                "desc": "",
-                "extractType": 0,
-                "relativeXPath": "//body",
-                "recordASField": 1,
-                "allXPaths": [],
-                "exampleValues": [
-                    {
-                        "num": 0,
-                        "value": "自定义字段"
-                    }
-                ],
-                "default": "",
-                "beforeJS": "",
-                "beforeJSWaitTime": 0,
-                "JS": "",
-                "paraType": "text",
-                "JSWaitTime": 0,
-                "afterJS": "",
-                "afterJSWaitTime": 0,
-                "downloadPic": 0
-            });
-            this.paraIndex = this.nowNode["parameters"]["paras"].length - 1;
-        },
-        modifyParas: function(i) { //修改第i个参数
-            this.paraIndex = i;
-            console.log(this.paras);
-        },
-        deleteParas: function(i) { //删除第i个参数
-            this.nowNode["parameters"]["paras"].splice(i, 1);
-            //如果参数删除完了,就把提取数据也删掉
-            if (this.nowNode["parameters"]["paras"].length == 0) {
-                deleteElement();
-            }
-        },
-        upParas: function(i) { //上移第i个参数
-            if (i != 0) {
-                let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
-                this.nowNode["parameters"]["paras"].splice(i - 1, 0, t);
-            }
-        },
-        downParas: function(i) { //下移第i个参数
-            if (i != this.nowNode["parameters"]["paras"].length - 1) {
-                let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
-                this.nowNode["parameters"]["paras"].splice(i + 1, 0, t);
-            }
-        },
-
-        getType: function(nodeType, contentType) { //根据类型得到字段名称
-            if (contentType == 2) {
-                return "InnerHTML";
-            } else if (contentType == 3) {
-                return "OuterHTML";
-            }
-            if (nodeType == 2) {
-                return "链接地址";
-            } else if (nodeType == 1) {
-                return "链接文本";
-            } else if (nodeType == 4) {
-                return "图片地址";
-            } else {
-                return "文本";
-            }
-        }
-    }
-})
-
-//深复制
-function DeepClone(obj) {
-    if (obj === null || typeof obj !== 'object') return obj;
-    let cpObj = obj instanceof Array ? [] : {};
-    for (let key in obj) cpObj[key] = DeepClone(obj[key]);
-    return cpObj;
-}
-
-// 根据元素类型返回不同元素的样式
-function newNode(node) {
-    id = node["id"];
-    title = node["title"];
-    type = node["type"];
-    if (type == 0) //顺序
-    {
-        return `<div class="sequence"><div class="node clk" data="${id}" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}>
-                <div >
-                    <p>${title}</p>
-                </div>
-            </div>
-            <p class="arrow" position=${node["position"]} data = "${id}" pId=${node["parentId"]}>↓</p></div>`;
-    } else if (type == 1) //循环
-    {
-        return `<div class="loop clk" data="${id}" dataType=${type} id = "${id}" position=${node["position"]} pId=${node["parentId"]}>
-             <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
-                <p class="arrow" position=-1 data = "${id}" pId=${id}>↓</p>
-            </div>
-            <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
-    } else if (type == 2) //判断
-    {
-        return `<div class="loop clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
-                    <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
-                    <p class="branchAdd" data="${id}">点击此处在最左边增加条件分支</p>
-                    <div class="judge" id = "${id}">
-                    </div></div>
-                    <p class="arrow" data = "${id}" position=${node["position"]} pId=${node["parentId"]}>↓</p></div>`;
-    } else //判断分支
-    {
-        return `<div class="branch clk" dataType=${type} data="${id}" position=${node["position"]} pId=${node["parentId"]}>
-                    <p style="background:#d6d6d6;text-align:left;padding:2px">${title}</p>
-                    <p data = "${id}" class="arrow" position=-1 pId=${id}>↓</p>
-                    <div id = "${id}">
-                    </div></div>`;
-    }
-}
-
-
-
-function branchMouseDown(e) {
-    if (e.button == 2) //右键点击
-    {
-        let judgeId = this.getAttribute('data');
-        let l = nodeList.length;
-        let t = {
-            index: l,
-            id: 0,
-            parentId: 0,
-            type: 3,
-            option: 10,
-            title: "条件分支",
-            sequence: [],
-            isInLoop: false,
-        };
-        addParameters(t)
-        nodeList.push(t);
-        nodeList[actionSequence[judgeId]]["sequence"].splice(0, 0, t.index);
-        refresh();
-        app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
-        $("#" + t["id"]).click();
-    }
-    e.stopPropagation(); //防止冒泡
-}
-
-function arrowMouseDown(e) {
-    if (e.button == 2) //右键点击
-    {
-        if (option != 0) {
-            app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
-        }
-        toolBoxKernel(e);
-    }
-}
-//增加分支点击事件
-function branchClick(e) {
-    let judgeId = this.getAttribute('data');
-    let l = nodeList.length;
-    let t = {
-        index: l,
-        id: 0,
-        parentId: 0,
-        type: 3,
-        option: 10,
-        title: "条件分支",
-        sequence: [],
-        isInLoop: false,
-    };
-    addParameters(t);
-    nodeList.push(t);
-    nodeList[actionSequence[judgeId]]["sequence"].splice(0, 0, t.index);
-    refresh();
-    app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
-    $("#" + t["id"]).click();
-    e.stopPropagation(); //防止冒泡
-}
-
-function elementMousedown(e) {
-    if (e.button == 2) //右键点击
-    {
-        if (nowNode != null) {
-            nowNode.style.borderColor = "skyblue";
-        }
-        nowNode = this;
-        vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
-        this.style.borderColor = "blue";
-        handleElement(); //处理元素
-    }
-    e.stopPropagation(); //防止冒泡
-}
-
-//元素点击事件
-function elementClick(e) {
-    if (nowNode != null) {
-        nowNode.style.borderColor = "skyblue";
-    }
-    nowNode = this;
-    vueData.nowNodeIndex = actionSequence[this.getAttribute("data")];
-    this.style.borderColor = "blue";
-    handleElement(); //处理元素
-    e.stopPropagation(); //防止冒泡
-}
-
-//箭头点击事件
-function arrowClick(e) {
-    if (option != 0) {
-        app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
-    }
-    toolBoxKernel(e);
-}
-
-//增加元素函数
-function addElement(op, para) {
-    option = op;
-    if (option == 1) { //打开网页选项
-        title = "打开网页";
-    } else {
-        title = $(".options")[option - 1].innerHTML; //获取新增操作名称
-    }
-
-    toolBoxKernel(null, para);
-}
-
-// 工具箱操作函数
-function toolBoxKernel(e, para = null) {
-    if (option == 13) { //调整锚点
-        // let tarrow = DeepClone(app.$data.nowArrow);
-        // refresh();
-        // app._data.nowArrow =tarrow;
-    } else if (option == 11) { //复制操作
-        if (nowNode == null) {
-            e.stopPropagation(); //防止冒泡
-        } else if (nowNode.getAttribute("dataType") > 0) {
-            alert("循环和判断、条件分支不可复制!");
-            e.stopPropagation(); //防止冒泡
-        } else {
-            let position = parseInt(nowNode.getAttribute('position'));
-            let pId = nowNode.getAttribute('pId');
-            let tt = nodeList[nodeList[actionSequence[pId]]["sequence"][position]]; //在相应位置添加新元素
-            t = DeepClone(tt); //浅复制元素
-            let l = nodeList.length;
-            t.index = l;
-            nodeList.push(t);
-            let position2 = parseInt(app._data.nowArrow['position']);
-            let pId2 = app._data.nowArrow['pId'];
-            nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, t.index); //在相应位置添加新元素
-            refresh(); //重新渲染页面
-            app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
-            $("#" + t["id"]).click(); //复制后点击复制后的元素
-            e.stopPropagation(); //防止冒泡
-        }
-    } else if (option == 10) { //剪切操作
-        if (nowNode == null) {
-            e.stopPropagation(); //防止冒泡
-        } else if ($(nowNode).is(".branch")) {
-            alert("判断分支不可移动!");
-            e.stopPropagation(); //防止冒泡
-        } else {
-            let position = parseInt(nowNode.getAttribute('position'));
-            let pId = nowNode.getAttribute('pId');
-            let position2 = parseInt(app._data.nowArrow['position']);
-            let pId2 = app._data.nowArrow['pId'];
-            let id = nowNode.getAttribute('data');
-            let pidt = pId2;
-            let move = true;
-            console.log(pidt, id);
-            while (pidt != 0) {
-                if (pidt == id) {
-                    move = false;
-                    break;
-                }
-                pidt = nodeList[actionSequence[pidt]]["parentId"];
-            }
-            if (move) //如果自己要移动到自己节点里就不允许移动
-            {
-                let element = nodeList[actionSequence[pId]]["sequence"].splice(position, 1); //在相应位置删除元素
-                if (pId == pId2 && position < position2) //如果要移动的位置属于同一层并且是从前往后移动,注意需要控制数组插入位置向前错位
-                {
-                    position2--;
-                }
-                console.log(element);
-                nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, element[0]); //在相应位置添加新元素
-                refresh(); //重新渲染页面
-                console.log(nodeList[element[0]]);
-                app._data.nowArrow = { "position": nodeList[element[0]]["position"], "pId": nodeList[element[0]]["parentId"], "num": 0 };
-                $("#" + nodeList[element[0]]["id"]).click();
-            } else {
-                alert("自己不能移动到自己的节点里!");
-            }
-            e.stopPropagation(); //防止冒泡
-        }
-    } else if (option > 0) { //新增操作
-        let l = nodeList.length;
-        let t = {
-            id: 0,
-            index: l,
-            parentId: 0,
-            type: 0,
-            option: option,
-            title: title,
-            sequence: [],
-            isInLoop: false,
-        };
-        nodeList.push(t);
-        if (option == 8) //循环
-        {
-            t["type"] = 1;
-        } else if (option == 9) //判断
-        {
-            t["type"] = 2;
-            // 增加两个分支
-            let nt = {
-                id: 0,
-                parentId: 0,
-                index: l + 1,
-                type: 3,
-                option: 10,
-                title: "条件分支",
-                sequence: [],
-                isInLoop: false,
-            };
-            let nt2 = {
-                id: 0,
-                parentId: 0,
-                index: l + 2,
-                type: 3,
-                option: 10,
-                title: "条件分支",
-                sequence: [],
-                isInLoop: false,
-            };
-            t["sequence"].push(nt.index);
-            t["sequence"].push(nt2.index);
-            nodeList.push(nt)
-            nodeList.push(nt2);
-            addParameters(nt); //增加选项的默认参数
-            addParameters(nt2); //增加选项的默认参数
-        }
-        let position = parseInt(app._data.nowArrow['position']);
-        let pId = app._data.nowArrow['pId'];
-        nodeList[actionSequence[pId]]["sequence"].splice(position + 1, 0, t.index); //在相应位置添加新元素
-        refresh(); //重新渲染页面
-        //下面是确定添加元素之后下一个要插入的节点的位置
-        app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
-        addParameters(t); //增加选项的默认参数
-        if (para != null) {
-            modifyParameters(t, para);
-        }
-        if (option == 8) //循环情况下应插入在循环里面
-        {
-            app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
-            $("#" + t["id"]).click();
-        } else if (option == 9) //判断插入到第一个判断条件中
-        {
-            app._data.nowArrow = { "position": -1, "pId": nt["id"], "num": 0 };
-            $("#" + nt["id"]).click();
-        } else {
-            $("#" + t["id"]).click();
-        }
-
-        if (e != null)
-            e.stopPropagation(); //防止冒泡
-        option = 0;
-        return t;
-    }
-    option = 0;
-}
-
-$(".options").mousedown(function() {
-    option = parseInt(this.getAttribute("data"));
-    title = this.innerHTML;
-    if (option >= 10 && option <= 12 && (nowNode == null || nowNode.getAttribute("id") == 0)) {
-        alert("目前未选中元素");
-    } else if (option == 12) {
-        deleteElement();
-        $(".options")[12].click();
-    }
-});
-
-function bindEvents() {
-    // 清空原来的listener然后再添加新的listener
-    //以下绑定了左右键的行为
-    let rect = document.getElementsByClassName('clk');
-    for (let i = 0, rule; rule = rect[i++];) {
-        rule.removeEventListener('mousedown', elementMousedown);
-        rule.addEventListener('mousedown', elementMousedown);
-        rule.removeEventListener('click', elementClick);
-        rule.addEventListener('click', elementClick);
-    }
-    let arr = document.getElementsByClassName('arrow');
-    for (let i = 0, rule; rule = arr[i++];) {
-        rule.removeEventListener('click', arrowClick);
-        rule.addEventListener('click', arrowClick);
-        rule.removeEventListener('mousedown', arrowMouseDown);
-        rule.addEventListener('mousedown', arrowMouseDown);
-    }
-    let branch = document.getElementsByClassName('branchAdd');
-    for (let i = 0, rule; rule = branch[i++];) {
-        rule.removeEventListener('click', branchClick);
-        rule.addEventListener('click', branchClick);
-        rule.removeEventListener('mousedown', branchMouseDown);
-        rule.addEventListener('mousedown', branchMouseDown);
-    }
-}
-
-//重新画图
-function refresh(nowArrowReset = true) {
-    $("#0").empty();
-    $("#0").append(`<div style="border-radius: 50%;width: 40px;height: 40px;border:solid;border-color:seagreen;margin:5px auto;background-color:lightcyan;margin-top:20px">
-                        <p style="font-size: 24px!important;text-align: center;margin-left: 6px;font-family:'Times New Roman'">▶</p>
-                        </div>
-                        <p id="firstArrow" class="arrow" position=-1 pId=0>↓</p>`);
-    actionSequence.splice(0);
-    queue.splice(0);
-    let idd = 1;
-    queue.push(0);
-    actionSequence.push(0);
-    while (queue.length != 0) {
-        let nd = queue.shift(); //取出父元素并建立对子元素的链接
-        for (let i = 0; i < nodeList[nd].sequence.length; i++) {
-            nodeList[nodeList[nd].sequence[i]].parentId = nodeList[nd].id;
-            nodeList[nodeList[nd].sequence[i]]["position"] = i;
-            nodeList[nodeList[nd].sequence[i]].id = idd++;
-            //检测元素是否位于循环内
-            if (nodeList[nd].option == 8 || nodeList[nd].isInLoop) {
-                nodeList[nodeList[nd].sequence[i]].isInLoop = true;
-            } else {
-                nodeList[nodeList[nd].sequence[i]].isInLoop = false;
-            }
-            queue.push(nodeList[nd].sequence[i]);
-            actionSequence.push(nodeList[nd].sequence[i]);
-        }
-    }
-    if (nowArrowReset) //如果要重置锚点位置
-    {
-        app._data.nowArrow = { "position": nodeList[0].sequence.length - 1, "pId": 0, "num": 0 }; //设置默认要添加的位置是元素流程最开头处
-    }
-    //第一个元素不渲染
-    for (let i = 1; i < actionSequence.length; i++) {
-        let parentId = nodeList[actionSequence[i]]["parentId"];
-        $("#" + parentId).append(newNode(nodeList[actionSequence[i]]));
-    }
-    bindEvents();
-}
-
-function deleteElement() {
-    if (nowNode.getAttribute("id") == 0) {
-        alert("当前未选中元素!"); //root
-        return;
-    }
-    // if (nodeList[actionSequence[nowNode.getAttribute("data")]]["option"] == 1) {
-    //     alert("打开网页操作不可删除!");
-    //     return;
-    // }
-    let position = parseInt(nowNode.getAttribute('position'));
-    let pId = nowNode.getAttribute('pId');
-    let tnode = nodeList[actionSequence[pId]]["sequence"].splice(position, 1); //在相应位置删除元素
-    //循环的标记已经被删除的元素,因为删除循环后,循环内的元素也会
-    let queue = new Array();
-    queue.push(tnode[0]);
-    while (queue.length > 0) {
-        let index = queue.shift();
-        nodeList[index]["id"] = -1; //标记服务已被删除
-        for (let i = 0; i < nodeList[index]["sequence"].length; i++) {
-            queue.push(nodeList[index]["sequence"][i]);
-        }
-    }
-    app._data["nowNode"] = null;
-    app._data["nodeType"] = 0;
-    vueData.nowNodeIndex = 0;
-    if (nowNode.getAttribute("datatype") == 3) { //如果删掉的是条件分支的话
-        pId = nowNode.parentNode.parentNode.getAttribute('pId');
-        position = nowNode.parentNode.parentNode.getAttribute('position');
-    }
-    app.$data.nowArrow = { position: position - 1, "pId": pId, "num": 0 }; //删除元素后锚点跳转到当前元素的上一个节点
-    refresh(false); //重新渲染页面
-    nowNode = null; //取消选择
-}
-
-document.oncontextmenu = function() {
-        return false;
-    } //屏蔽右键菜单
-    //删除元素
-document.onkeydown = function(e) {
-    if (nowNode != null && e.keyCode == 46) {
-        // if (confirm("确定要删除元素吗?")) {
-            deleteElement();
-        // }
-    } else { //ctrl+s保存服务
-        let currKey = 0;
-        currKey = e.keyCode || e.which || e.charCode;
-        if (currKey == 83 && (e.ctrlKey || e.metaKey)) {
-            $('#save').click();
-            return true;
-        } else if (currKey == 116) {
-            location.reload();
-        } else if (currKey == 123) {
-            console.log("打开devtools")
-            let command = new WebSocket("ws://localhost:"+getUrlParam("wsport"))
-            command.onopen = function() {
-                let message = {
-                    type: 6, //消息类型,0代表连接操作
-                };
-                this.send(JSON.stringify(message));
-            };
-        }
-    }
-}
-
-function inputDelete(e) {
-    if (e.keyCode == 46) {
-        e.stopPropagation(); //输入框按delete应该正常运行
-        //Electron中如果有alert或者confirm,执行后会卡死输入框,所以最好不要用
-    }
-}

+ 31 - 1
ElectronJS/src/taskGrid/global.js

@@ -1,3 +1,26 @@
+// function isExtract() { //检测当前锚点之前的元素是否为提取数据字段
+//     if (app.$data.nowArrow["position"] == -1) {
+//         return false;
+//     } else if (nodeList[nodeList[app.$data.nowArrow["pId"]].sequence[app.$data.nowArrow["position"]]]["option"] == 3) {
+//         return true;
+//     } else {
+//         return false;
+//     }
+// }
+
+function DateFormat(datetime) {
+    let date = new Date(datetime);
+
+// Format the date and time
+    let formatted = date.getFullYear() +
+        '-' + String(date.getMonth() + 1).padStart(2, '0') +
+        '-' + String(date.getDate()).padStart(2, '0') +
+        ' ' + String(date.getHours()).padStart(2, '0') +
+        ':' + String(date.getMinutes()).padStart(2, '0') +
+        ':' + String(date.getSeconds()).padStart(2, '0');
+    return formatted;
+}
+
 function getUrlParam(name) {
     let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
     let r = window.location.search.substr(1).match(reg); //匹配目标参数
@@ -11,4 +34,11 @@ Vue.filter('lang', function (value) {
     } else {
         return value.split("~")[0];
     }
-})
+})
+
+function isValidMySQLTableName(tableName) {
+    // 正则表达式:以字母或汉字开头,后接字母、数字、下划线或汉字的字符串,长度为1到64字符
+    const pattern = /^[\u4e00-\u9fa5a-zA-Z][\u4e00-\u9fa5a-zA-Z0-9_]{0,63}$/;
+    return pattern.test(tableName);
+}
+

+ 125 - 94
ElectronJS/src/taskGrid/logic.js

@@ -1,4 +1,4 @@
-exampleMsg = { //示例消息
+let exampleMsg = { //示例消息
     "type": 0, //消息类型,1代表增加操作
     "data": {
         "option": 1, //增加选项
@@ -8,7 +8,7 @@ exampleMsg = { //示例消息
     }
 }
 console.log(JSON.stringify(exampleMsg));
-ws = new WebSocket("ws://localhost:"+getUrlParam("wsport"));
+let ws = new WebSocket("ws://localhost:"+getUrlParam("wsport"));
 ws.onopen = function() {
     // Web Socket 已连接上,使用 send() 方法发送数据
     console.log("已连接");
@@ -36,11 +36,12 @@ ws.onmessage = function(evt) {
     } else {
         handleAddElement(evt); //处理增加元素操作
     }
-
 };
 
 function changeGetDataParameters(msg, i) {
     msg["parameters"][i]["default"] = ""; //找不到元素时候的默认值
+    msg["parameters"][i]["paraType"] = "text"; //参数类型
+    msg["parameters"][i]["recordASField"] = 1; //是否记录为字段值
     msg["parameters"][i]["beforeJS"] = ""; //执行前执行的js
     msg["parameters"][i]["beforeJSWaitTime"] = 0; //执行前js等待时间
     msg["parameters"][i]["JS"] = ""; //如果是JS,需要执行的js
@@ -48,19 +49,9 @@ function changeGetDataParameters(msg, i) {
     msg["parameters"][i]["afterJS"] = ""; //执行后执行的js
     msg["parameters"][i]["afterJSWaitTime"] = 0; //执行后js等待时间
     msg["parameters"][i]["downloadPic"] = 0; //是否下载图片
-    msg["parameters"][i]["iframe"] = false; //是否在iframe中
 }
 
 
-function extractTitle(html) {
-    var match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
-    if (match && match[1]) {
-        return "Collect" + match[1];
-    } else {
-        return "New Web Collection Task";
-    }
-}
-
 function handleAddElement(msg) {
     if (msg["type"] == "openPage") {
         addElement(1, msg);
@@ -83,7 +74,7 @@ function handleAddElement(msg) {
         addElement(8, msg);
         addElement(2, msg);
     } else if (msg["type"] == "singleCollect" || msg["type"] == "multiCollectNoPattern") {
-        if (app._data.nowNode != null && app._data["nowNode"]["option"] == 3) { //如果当前点击的动作就是提取数据
+        if (app._data.nowNode != null && app._data["nowNode"]["option"] == 3) { //如果现在节点就是提取数据节点,直接在此节点添加参数,而不是生成一个新的提取数据节点
             for (let i = 0; i < msg["parameters"].length; i++) {
                 changeGetDataParameters(msg, i);
                 app._data["nowNode"]["parameters"]["paras"].push(msg["parameters"][i]);
@@ -97,10 +88,17 @@ function handleAddElement(msg) {
         addElement(8, msg);
         addElement(3, msg);
         notifyParameterNum(msg["parameters"].length); //通知浏览器端参数的个数变化
+    } else if(msg["type"] == "GetCookies"){
+        for(let node of nodeList){
+            if(node["option"] == 1){
+                node["parameters"]["cookies"] = msg["message"];
+                $("#pageCookies").val(msg["message"]);
+                break;
+            }
+        }
     }
 }
 
-
 function notifyParameterNum(num) {
     parameterNum += num;
     let message = {
@@ -110,17 +108,9 @@ function notifyParameterNum(num) {
     };
     window.ws.send(JSON.stringify(message));
 }
-// function isExtract() { //检测当前锚点之前的元素是否为提取数据字段
-//     if (app.$data.nowArrow["position"] == -1) {
-//         return false;
-//     } else if (nodeList[nodeList[app.$data.nowArrow["pId"]].sequence[app.$data.nowArrow["position"]]]["option"] == 3) {
-//         return true;
-//     } else {
-//         return false;
-//     }
-// }
 
-// 流程图元素点击后的处理逻辑
+
+// 流程图元素点击后的处理逻辑,注意在FlowChart_CN.js中watch的那些数据的加载都需要在这里执行!!!
 function handleElement() {
     app._data["nowNode"] = nodeList[vueData.nowNodeIndex];
     app._data["nodeType"] = app._data["nowNode"]["option"];
@@ -130,6 +120,8 @@ function handleElement() {
     } else if (app._data["nodeType"] == 3) {
         app._data.paraIndex = 0; //参数索引初始化
         app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"];
+    } else if(app._data["nodeType"] == 5){
+        app._data.codeMode = app._data["nowNode"]["parameters"]["codeMode"];
     } else if (app._data["nodeType"] == 10) {
         app._data.TClass = app._data["nowNode"]["parameters"]["class"];
     }
@@ -149,7 +141,6 @@ function addParameters(t) {
         beforeJSWaitTime: 0, //执行前js等待时间
         afterJS: "", //执行后执行的js
         afterJSWaitTime: 0, //执行后js等待时间
-        iframe: false, //是否在iframe中
     }; //公共参数处理
     if (t.option == 1) {
         t["parameters"]["url"] = "about:blank";
@@ -158,6 +149,7 @@ function addParameters(t) {
         t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
         t["parameters"]["scrollCount"] = 1; //滚动次数
         t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
+        t["parameters"]["cookies"] = ""; //cookies
     } else if (t.option == 2) { //点击元素
         t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
         t["parameters"]["scrollCount"] = 1; //滚动次数
@@ -183,6 +175,7 @@ function addParameters(t) {
         t["parameters"]["code"] = "";
         t["parameters"]["waitTime"] = 0; //最长等待时间
         t["parameters"]["recordASField"] = 0; //是否记录脚本输出
+        t["parameters"]["paraType"] = "text"; //记录脚本输出的字段索引
     } else if (t.option == 8) { //循环
         t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
         t["parameters"]["scrollCount"] = 1; //滚动次数
@@ -208,7 +201,23 @@ function addParameters(t) {
     }
 }
 
-//修改元素参数
+
+function updateUI() {
+    refresh(false);
+    app.$data.nowArrow["num"]++; //改变元素的值,通知画图,重新对锚点画图
+    let tnodes = document.getElementsByClassName("clk");
+    let position = nodeList[vueData.nowNodeIndex]["position"];
+    let pid = nodeList[vueData.nowNodeIndex]["parentId"];
+    for (let i = 0; i < tnodes.length; i++) {
+        if (position == tnodes[i].getAttribute("position") && pid == tnodes[i].getAttribute("pId")) {
+            tnodes[i].style.borderColor = "blue"; // 点击了确定按钮之后需要重新对选中的颜色画框
+            nowNode = tnodes[i];
+            break;
+        }
+    }
+}
+
+//修改元素参数,注意所有socket传过来的参数都需要在这里赋值给操作
 function modifyParameters(t, para) {
     t["parameters"]["history"] = para["history"];
     t["parameters"]["tabIndex"] = para["tabIndex"];
@@ -240,9 +249,9 @@ function modifyParameters(t, para) {
         t["parameters"]["xpath"] = para["xpath"];
         t["parameters"]["allXPaths"] = para["allXPaths"];
         if (para["nextPage"]) { //循环点击下一页的情况下
-            t["title"] = "Loop click next page"
+            t["title"] = LANG("循环点击下一页", "Loop click next page");
         } else {
-            t["title"] = "Loop"
+            t["title"] = LANG("循环", "Loop");
         }
         if (para["loopType"] == 2) //如果是固定元素列表
         {
@@ -256,43 +265,41 @@ function modifyParameters(t, para) {
     }
 }
 
+function showError(msg, time=4000) {
+    $("#error_message").text(msg);
+    $("#tipError").slideDown(); //提示框
+    let fadeout = setTimeout(function() {
+        $("#tipError").slideUp();
+    }, time);
+}
+
 //点击确定按钮时的处理
-$("#confirm").mousedown(function() {
-    refresh(false);
-    app.$data.nowArrow["num"]++; //改变元素的值,通知画图,重新对锚点画图
-    let tnodes = document.getElementsByClassName("clk");
-    let position = nodeList[vueData.nowNodeIndex]["position"];
-    let pid = nodeList[vueData.nowNodeIndex]["parentId"];
-    for (let i = 0; i < tnodes.length; i++) {
-        if (position == tnodes[i].getAttribute("position") && pid == tnodes[i].getAttribute("pId")) {
-            tnodes[i].style.borderColor = "blue"; // 点击了确定按钮之后需要重新对选中的颜色画框
-            nowNode = tnodes[i];
-            break;
-        }
-    }
-});
+$("#confirm").mousedown(updateUI);
 
-//获取url中的参数
-function getUrlParam(name) {
-    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
-    var r = window.location.search.substr(1).match(reg); //匹配目标参数
-    if (r != null) return unescape(r[2]);
-    return "";
-}
+//点击保存任务按钮时的处理
+$("#saveButton").mousedown(function() {
+    saveService(0);
+});
+//点击另存为任务按钮时的处理
+$("#saveAsButton").mousedown(function() {
+    saveService(1);
+});
 
-var sId = getUrlParam('id');
-var backEndAddressServiceWrapper = getUrlParam("backEndAddressServiceWrapper");
+let sId = getUrlParam('id');
+let backEndAddressServiceWrapper = getUrlParam("backEndAddressServiceWrapper");
 let mobile = getUrlParam("mobile");
 if (mobile == "true") {
     $("#environment").val(1);
 }
 
+
 function saveService(type) {
     let serviceId = $("#serviceId").val();
-    let text = "Confirm to save this task (If cannot click, can press Enter)? ";
+    let text = LANG("确认要保存任务吗(不能用鼠标点击时,请按键盘回车键)?", "Are you sure to save the task (if you can't use the mouse to click, please press the enter key)?");
     if (type == 1) { //任务另存为
         serviceId = -1;
-        text = "Confirm to save as another task in the system (If cannot click, can press Enter)?";
+        $("#create_time").val(new Date().toLocaleString());
+        text = LANG("确认要另存为任务吗(不能用鼠标点击时,请按键盘回车键)?", "Are you sure to save the task as (if you can't use the mouse to click, please press the enter key)?");
     }
     // if (confirm(text)) {
         let serviceName = $("#serviceName").val();
@@ -320,8 +327,8 @@ function saveService(type) {
                             nodeId: i, //记录操作位于的节点位置,重要!!!
                             nodeName: nodeList[i]["title"],
                             value: nodeList[i]["parameters"]["links"],
-                            desc: "List of URLs to be collected, separated by \\n for multiple lines",
-                            type: "string",
+                            desc: LANG("要采集的网址列表,多行以\\n分开","List of URLs to be collected, separated by \\n for multiple lines",),
+                            type: "text",
                             exampleValue: nodeList[i]["parameters"]["links"]
                         });
                         links = nodeList[i]["parameters"]["links"];
@@ -335,8 +342,8 @@ function saveService(type) {
                             name: "inputText_" + inputIndex++,
                             nodeName: nodeList[i]["title"],
                             nodeId: i,
-                            desc: "The text to be entered, such as 'computer' at eBay search box",
-                            type: "string",
+                            desc: LANG("要输入的文本,如京东搜索框输入:电脑","The text to be entered, such as 'computer' at eBay search box"),
+                            type: "text",
                             exampleValue: nodeList[i]["parameters"]["value"],
                             value: nodeList[i]["parameters"]["value"],
                         });
@@ -349,8 +356,8 @@ function saveService(type) {
                             name: "loopText_" + inputIndex++,
                             nodeId: i,
                             nodeName: nodeList[i]["title"],
-                            desc:"Text/URL to be entered, multiple lines should be separated by \\n",
-                            type: "string",
+                            desc: LANG("要输入的文本/网址,多行以\\n分开", "Text/URL to be entered, multiple lines should be separated by \\n"),
+                            type: "text",
                             exampleValue: nodeList[i]["parameters"]["textList"],
                             value: nodeList[i]["parameters"]["textList"],
                         });
@@ -360,7 +367,7 @@ function saveService(type) {
                             name: "loopTimes_" + nodeList[i]["title"] + "_" + inputIndex++,
                             nodeId: i,
                             nodeName: nodeList[i]["title"],
-                            desc: "Number of loop executions, 0 means unlimited loops (until element not found)",
+                            desc: LANG("循环" + nodeList[i]["title"] + "执行的次数(0代表无限循环)", "Number of loop executions for loop "+nodeList[i]["title"]+", 0 means unlimited loops (until element not found)"),
                             type: "int",
                             exampleValue: nodeList[i]["parameters"]["exitCount"],
                             value: nodeList[i]["parameters"]["exitCount"],
@@ -375,33 +382,35 @@ function saveService(type) {
                                 id: outputIndex++,
                                 name: nodeList[i]["parameters"]["paras"][j]["name"],
                                 desc: nodeList[i]["parameters"]["paras"][j]["desc"],
-                                type: "string",
+                                type: nodeList[i]["parameters"]["paras"][j]["paraType"],
+                                recordASField: nodeList[i]["parameters"]["paras"][j]["recordASField"],
                                 exampleValue: nodeList[i]["parameters"]["paras"][j]["exampleValues"][0]["value"],
                             });
                         }
                     }
                 } else if (nodeList[i]["option"] == 5) //自定义操作
                 {
-                    if (nodeList[i]["parameters"]["recordASField"] == 1) {
-                        let id = outputIndex++;
-                        let title = nodeList[i]["title"];
-                        // if (outputNames.indexOf(title) >= 0) { //参数名称已经被添加
-                        //     $('#myModal').modal('hide');
-                        //     $("#tip2").slideDown(); //提示框
-                        //     fadeout = setTimeout(function() {
-                        //         $("#tip2").slideUp();
-                        //     }, 5000);
-                        //     return;
-                        // }
-                        outputNames.push(title);
-                        outputParameters.push({
-                            id: id,
-                            name: title,
-                            desc: "Output of custom action",
-                            type: "string",
-                            exampleValue: "",
-                        });
-                    }
+                    // if (nodeList[i]["parameters"]["recordASField"] == 1) {
+                    let id = outputIndex++;
+                    let title = nodeList[i]["title"];
+                    // if (outputNames.indexOf(title) >= 0) { //参数名称已经被添加
+                    //     $('#myModal').modal('hide');
+                    //     $("#tip2").slideDown(); //提示框
+                    //     fadeout = setTimeout(function() {
+                    //         $("#tip2").slideUp();
+                    //     }, 5000);
+                    //     return;
+                    // }
+                    outputNames.push(title);
+                    outputParameters.push({
+                        id: id,
+                        name: title,
+                        desc: LANG("自定义操作返回的数据","Output of custom action"),
+                        type: nodeList[i]["parameters"]["paraType"],
+                        recordASField: nodeList[i]["parameters"]["recordASField"],
+                        exampleValue: "",
+                    });
+                    // }
                 } else if (nodeList[i]["option"] == 9) //条件判断
                 {
                     containJudge = true;
@@ -413,17 +422,28 @@ function saveService(type) {
             "name": serviceName,
             "url": url,
             "links": links,
-            "create_time": new Date().toLocaleString(),
+            "create_time": parseInt(serviceId) == -1 ? new Date().toLocaleString() : $("#create_time").val(),
+            "update_time": new Date().toLocaleString(),
             "version": "0.3.5",
             "saveThreshold": saveThreshold,
             "cloudflare": cloudflare,
             "environment": environment,
+            "maxViewLength": parseInt($("#maxViewLength").val()),
+            "outputFormat": $("#outputFormat").val(),
+            "saveName": $("#saveName").val(),
             "containJudge": containJudge,
             "desc": serviceDescription,
             "inputParameters": inputParameters,
             "outputParameters": outputParameters,
             "graph": nodeList, //图结构要存储下来
         };
+        if(serviceInfo.outputFormat=="mysql"){
+            if(!isValidMySQLTableName(serviceInfo.saveName)) {
+                $('#myModal').modal('hide');
+                showError(LANG("提示:保存名不符合MySQL表名规范,请重试!","The save name is not valid for MySQL table name!"));
+                return;
+            }
+        }
         $.post(backEndAddressServiceWrapper + "/manageTask", { paras: JSON.stringify(serviceInfo) },
             function(result) { $("#serviceId").val(parseInt(result)) });
         // alert("保存成功!");
@@ -432,31 +452,42 @@ function saveService(type) {
         let fadeout = setTimeout(function() {
             $("#tip").slideUp();
         }, 2000);
-
     // }
 }
 
-//点击保存任务按钮时的处理
-$("#saveButton").mousedown(function() {
-    saveService(0);
-});
-//点击另存为任务按钮时的处理
-$("#saveAsButton").mousedown(function() {
-    saveService(1);
-});
-
-
 if (sId != null && sId != -1) //加载任务
 {
     $.get(backEndAddressServiceWrapper + "/queryTask?id=" + sId, function(result) {
         nodeList = result["graph"];
         app.$data.list.nl = nodeList;
+        for(let node of nodeList){ //兼容旧版本
+            if(node["option"] == 1){
+                if(!("cookies" in node["parameters"])) {
+                    node["parameters"]["cookies"] = "";
+                }
+            }
+        }
         $("#serviceName").val(result["name"]);
         $("#serviceId").val(result["id"]);
         $("#url").val(result["url"]);
         $("#serviceDescription").val(result["desc"]);
+        for(let key of Object.keys(result)){
+            try{
+                $("#"+key).val(result[key]);
+            } catch(e){
+                console.log(e);
+            }
+        }
         refresh();
     });
 } else {
     refresh(); //新增任务
 }
+
+function LANG(zh, en) {
+    if(window.location.href.indexOf("_CN") != -1){
+        return zh;
+    } else {
+        return en;
+    }
+}

+ 0 - 511
ElectronJS/src/taskGrid/logic_CN.js

@@ -1,511 +0,0 @@
-exampleMsg = { //示例消息
-    "type": 0, //消息类型,1代表增加操作
-    "data": {
-        "option": 1, //增加选项
-        "parameters": { //传入的参数
-            "url": "https://www.baidu.com"
-        }
-    }
-}
-console.log(JSON.stringify(exampleMsg));
-ws = new WebSocket("ws://localhost:"+getUrlParam("wsport"));
-ws.onopen = function() {
-    // Web Socket 已连接上,使用 send() 方法发送数据
-    console.log("已连接");
-    message = {
-        type: 0, //消息类型,0代表链接操作
-        message: {
-            id: 2, //socket id
-        }
-    };
-    this.send(JSON.stringify(message));
-};
-ws.onclose = function() {
-    // 关闭 websocket
-    console.log("连接已关闭...");
-};
-let old_title = "";
-ws.onmessage = function(evt) {
-    evt = JSON.parse(evt.data);
-    console.log(evt);
-    if (evt["type"] == "title") { //如果不是特殊处理的话,默认全部是增加元素操作
-        if (old_title == "New Task") { //只记录第一次的title
-            $("#serviceName").val(evt.data.title);
-        }
-        old_title = evt.data.title;
-    } else {
-        handleAddElement(evt); //处理增加元素操作
-    }
-
-};
-
-function changeGetDataParameters(msg, i) {
-    msg["parameters"][i]["default"] = ""; //找不到元素时候的默认值
-    msg["parameters"][i]["paraType"] = "text"; //参数类型
-    msg["parameters"][i]["recordASField"] = 1; //是否记录为字段值
-    msg["parameters"][i]["beforeJS"] = ""; //执行前执行的js
-    msg["parameters"][i]["beforeJSWaitTime"] = 0; //执行前js等待时间
-    msg["parameters"][i]["JS"] = ""; //如果是JS,需要执行的js
-    msg["parameters"][i]["JSWaitTime"] = 0; //JS等待时间
-    msg["parameters"][i]["afterJS"] = ""; //执行后执行的js
-    msg["parameters"][i]["afterJSWaitTime"] = 0; //执行后js等待时间
-    msg["parameters"][i]["downloadPic"] = 0; //是否下载图片
-}
-
-
-function extractTitle(html) {
-    var match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
-    if (match && match[1]) {
-        return "采集" + match[1];
-    } else {
-        return "采集新Web页面";
-    }
-}
-
-function handleAddElement(msg) {
-    if (msg["type"] == "openPage") {
-        addElement(1, msg);
-    } else if (msg["type"] == "singleClick") {
-        addElement(2, msg);
-    } else if (msg["type"] == "inputText") {
-        addElement(4, msg);
-    } else if (msg["type"] == "changeOption"){
-        addElement(6, msg);
-    } else if (msg["type"] == "mouseMove") {
-        addElement(7, msg);
-    } else if (msg["type"] == "loopMouseMove") {
-        addElement(8, msg);
-        addElement(7, msg);
-    } else if (msg["type"] == "loopClickSingle") {
-        addElement(8, msg);
-        addElement(2, msg);
-        app._data.nowArrow["position"] = -1; //循环点击单个元素,下一个要插入的位置一般在元素上方
-    } else if (msg["type"] == "loopClickEvery") {
-        addElement(8, msg);
-        addElement(2, msg);
-    } else if (msg["type"] == "singleCollect" || msg["type"] == "multiCollectNoPattern") {
-        if (app._data.nowNode != null && app._data["nowNode"]["option"] == 3) { //如果现在节点就是提取数据节点,直接在此节点添加参数,而不是生成一个新的提取数据节点
-            for (let i = 0; i < msg["parameters"].length; i++) {
-                changeGetDataParameters(msg, i);
-                app._data["nowNode"]["parameters"]["paras"].push(msg["parameters"][i]);
-            }
-            app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"];
-        } else {
-            addElement(3, msg);
-        }
-        notifyParameterNum(msg["parameters"].length); //通知浏览器端参数的个数变化
-    } else if (msg["type"] == "multiCollectWithPattern") {
-        addElement(8, msg);
-        addElement(3, msg);
-        notifyParameterNum(msg["parameters"].length); //通知浏览器端参数的个数变化
-    } else if(msg["type"] == "GetCookies"){
-        for(let node of nodeList){
-            if(node["option"] == 1){
-                node["parameters"]["cookies"] = msg["message"];
-                $("#pageCookies").val(msg["message"]);
-                break;
-            }
-        }
-    }
-}
-
-
-function notifyParameterNum(num) {
-    parameterNum += num;
-    let message = {
-        type: 3, //消息类型,3代表元素增加事件
-        from: 1, //0代表从浏览器到流程图,1代表从流程图到浏览器
-        message: { "pipe": JSON.stringify({ "type": 0, "value": parameterNum }) } // {}全选{BS}退格
-    };
-    window.ws.send(JSON.stringify(message));
-}
-// function isExtract() { //检测当前锚点之前的元素是否为提取数据字段
-//     if (app.$data.nowArrow["position"] == -1) {
-//         return false;
-//     } else if (nodeList[nodeList[app.$data.nowArrow["pId"]].sequence[app.$data.nowArrow["position"]]]["option"] == 3) {
-//         return true;
-//     } else {
-//         return false;
-//     }
-// }
-
-// 流程图元素点击后的处理逻辑,注意在FlowChart_CN.js中watch的那些数据的加载都需要在这里执行!!!
-function handleElement() {
-    app._data["nowNode"] = nodeList[vueData.nowNodeIndex];
-    app._data["nodeType"] = app._data["nowNode"]["option"];
-    app._data.useLoop = app._data["nowNode"]["parameters"]["useLoop"];
-    if (app._data["nodeType"] == 8) {
-        app._data.loopType = app._data["nowNode"]["parameters"]["loopType"];
-    } else if (app._data["nodeType"] == 3) {
-        app._data.paraIndex = 0; //参数索引初始化
-        app._data.paras.parameters = app._data["nowNode"]["parameters"]["paras"];
-    } else if(app._data["nodeType"] == 5){
-      app._data.codeMode = app._data["nowNode"]["parameters"]["codeMode"];
-    } else if (app._data["nodeType"] == 10) {
-        app._data.TClass = app._data["nowNode"]["parameters"]["class"];
-    }
-}
-
-// 新增元素时的默认参数处理
-function addParameters(t) {
-    t["parameters"] = {
-        history: 1,
-        tabIndex: 0,
-        useLoop: false, //是否使用循环中的元素
-        xpath: "", //xpath
-        iframe: false, //是否在iframe中
-        wait: 0, //执行后等待
-        waitType: 0, //等待类型,0代表固定时间,1代表随机等待
-        beforeJS: "", //执行前执行的js
-        beforeJSWaitTime: 0, //执行前js等待时间
-        afterJS: "", //执行后执行的js
-        afterJSWaitTime: 0, //执行后js等待时间
-    }; //公共参数处理
-    if (t.option == 1) {
-        t["parameters"]["url"] = "about:blank";
-        t["parameters"]["links"] = "about:blank";
-        t["parameters"]["maxWaitTime"] = 10; //最长等待时间
-        t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
-        t["parameters"]["scrollCount"] = 1; //滚动次数
-        t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
-        t["parameters"]["cookies"] = ""; //cookies
-    } else if (t.option == 2) { //点击元素
-        t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
-        t["parameters"]["scrollCount"] = 1; //滚动次数
-        t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
-        t["parameters"]["clickWay"] = 0; //点击方式,0代表selenium点击,1代表js点击
-        t["parameters"]["maxWaitTime"] = 10; //最长等待时间
-        t["parameters"]["paras"] = []; //默认参数列表
-        t["parameters"]["wait"] = 2; //点击后等待时间默认2s
-        t["parameters"]["beforeJS"] = ""; //执行前执行的js
-        t["parameters"]["beforeJSWaitTime"] = 0; //执行前js等待时间
-        t["parameters"]["afterJS"] = ""; //执行后执行的js
-        t["parameters"]["afterJSWaitTime"] = 0; //执行后js等待时间
-    } else if (t.option == 3) { //提取数据
-        t["parameters"]["paras"] = []; //默认参数列表
-    } else if (t.option == 4) { //输入文字
-        t["parameters"]["value"] = "";
-        t["parameters"]["beforeJS"] = ""; //执行前执行的js
-        t["parameters"]["beforeJSWaitTime"] = 0; //执行前js等待时间
-        t["parameters"]["afterJS"] = ""; //执行后执行的js
-        t["parameters"]["afterJSWaitTime"] = 0; //执行后js等待时间
-    } else if(t.option == 5) { //自定义操作
-        t["parameters"]["codeMode"] = 0; //代码模式,0代表JS, 2代表系统级别
-        t["parameters"]["code"] = "";
-        t["parameters"]["waitTime"] = 0; //最长等待时间
-        t["parameters"]["recordASField"] = 0; //是否记录脚本输出
-        t["parameters"]["paraType"] = "text"; //记录脚本输出的字段索引
-    } else if (t.option == 8) { //循环
-        t["parameters"]["scrollType"] = 0; //滚动类型,0不滚动,1向下滚动1屏,2滚动到底部
-        t["parameters"]["scrollCount"] = 1; //滚动次数
-        t["parameters"]["scrollWaitTime"] = 1; //滚动后等待时间
-        t["parameters"]["loopType"] = 0; //默认循环类型
-        t["parameters"]["xpath"] = "";
-        t["parameters"]["pathList"] = "";
-        t["parameters"]["textList"] = "";
-        t["parameters"]["code"] = ""; //执行的代码
-        t["parameters"]["waitTime"] = 0; //最长等待时间
-        t["parameters"]["exitCount"] = 0; //执行多少次后退出循环,0代表不设置此条件
-        t["parameters"]["historyWait"] = 2; //历史记录回退时间,用于循环点击每个链接的情况下点击链接后不打开新标签页的情况
-        t["parameters"]["breakMode"] = 0; //break类型,0代表JS,2代表系统命令
-        t["parameters"]["breakCode"] = ""; //break条件
-        t["parameters"]["breakCodeWaitTime"] = 0; //break条件等待时间
-    } else if (t.option == 9) { //条件
-
-    } else if (t.option == 10) { //条件分支
-        t["parameters"]["class"] = 0; //0代表什么条件都没有,1代表当前页面包括文本,2代表当前页面包括元素,3代表当前循环包括文本,4代表当前循环包括元素
-        t["parameters"]["value"] = ""; //相关值
-        t["parameters"]["code"] = ""; //code
-        t["parameters"]["waitTime"] = 0; //最长等待时间
-    }
-}
-
-//修改元素参数,注意所有socket传过来的参数都需要在这里赋值给操作
-function modifyParameters(t, para) {
-    t["parameters"]["history"] = para["history"];
-    t["parameters"]["tabIndex"] = para["tabIndex"];
-    t["parameters"]["iframe"] = para["iframe"];
-    if (t.option == 1) {
-        t["parameters"]["url"] = para["url"];
-        t["parameters"]["links"] = para["links"];
-        $("#serviceDescription").val(para["url"]);
-        $("#url").val(para["url"]);
-    } else if (t.option == 2) { //鼠标点击事件
-        t["parameters"]["xpath"] = para["xpath"];
-        t["parameters"]["useLoop"] = para["useLoop"];
-        t["parameters"]["allXPaths"] = para["allXPaths"];
-    } else if (t.option == 4) { //输入文字事件
-        t["parameters"]["value"] = para["value"];
-        t["parameters"]["xpath"] = para["xpath"];
-        t["parameters"]["allXPaths"] = para["allXPaths"];
-    } else if(t.option == 6){
-        t["parameters"]["xpath"] = para["xpath"];
-        t["parameters"]["allXPaths"] = para["allXPaths"];
-        t["parameters"]["optionMode"] = para["optionMode"];
-        t["parameters"]["optionValue"] = para["optionValue"];
-    } else if(t.option == 7){
-        t["parameters"]["xpath"] = para["xpath"];
-        t["parameters"]["useLoop"] = para["useLoop"];
-        t["parameters"]["allXPaths"] = para["allXPaths"];
-    } else if (t.option == 8) { //循环事件
-        t["parameters"]["loopType"] = para["loopType"];
-        t["parameters"]["xpath"] = para["xpath"];
-        t["parameters"]["allXPaths"] = para["allXPaths"];
-        if (para["nextPage"]) { //循环点击下一页的情况下
-            t["title"] = "循环点击下一页"
-        } else {
-            t["title"] = "循环"
-        }
-        if (para["loopType"] == 2) //如果是固定元素列表
-        {
-            t["parameters"]["pathList"] = para["pathList"].join("\n");
-        }
-    } else if (t.option == 3) { //采集数据
-        for (let i = 0; i < para["parameters"].length; i++) {
-            changeGetDataParameters(para, i);
-        }
-        t["parameters"]["paras"] = para["parameters"];
-    }
-}
-
-function updateUI() {
-    refresh(false);
-    app.$data.nowArrow["num"]++; //改变元素的值,通知画图,重新对锚点画图
-    let tnodes = document.getElementsByClassName("clk");
-    let position = nodeList[vueData.nowNodeIndex]["position"];
-    let pid = nodeList[vueData.nowNodeIndex]["parentId"];
-    for (let i = 0; i < tnodes.length; i++) {
-        if (position == tnodes[i].getAttribute("position") && pid == tnodes[i].getAttribute("pId")) {
-            tnodes[i].style.borderColor = "blue"; // 点击了确定按钮之后需要重新对选中的颜色画框
-            nowNode = tnodes[i];
-            break;
-        }
-    }
-}
-
-//点击确定按钮时的处理
-$("#confirm").mousedown(updateUI);
-
-//获取url中的参数
-function getUrlParam(name) {
-    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
-    var r = window.location.search.substr(1).match(reg); //匹配目标参数
-    if (r != null) return unescape(r[2]);
-    return "";
-}
-
-var sId = getUrlParam('id');
-var backEndAddressServiceWrapper = getUrlParam("backEndAddressServiceWrapper");
-let mobile = getUrlParam("mobile");
-if (mobile == "true") {
-    $("#environment").val(1);
-}
-
-
-function isValidMySQLTableName(tableName) {
-    // 正则表达式:以字母或汉字开头,后接字母、数字、下划线或汉字的字符串,长度为1到64字符
-    const pattern = /^[\u4e00-\u9fa5a-zA-Z][\u4e00-\u9fa5a-zA-Z0-9_]{0,63}$/;
-    return pattern.test(tableName);
-}
-function saveService(type) {
-    let serviceId = $("#serviceId").val();
-    let text = "确认要保存任务吗(不能用鼠标点击时,请按键盘回车键)?";
-    if (type == 1) { //任务另存为
-        serviceId = -1;
-        text = "确认要另存为任务吗(不能用鼠标点击时,请按键盘回车键)?";
-    }
-    // if (confirm(text)) {
-        let serviceName = $("#serviceName").val();
-        let url = $("#url").val();
-        let serviceDescription = $("#serviceDescription").val();
-        let inputParameters = [];
-        let outputParameters = [];
-        let outputNames = [];
-        let inputIndex = 0;
-        let outputIndex = 0;
-        let links = ""; //记录所有的link
-        let containJudge = false; //是否含有判断语句
-        let saveThreshold = parseInt($("#saveThreshold").val());
-        let cloudflare = parseInt($("#cloudflare").val());
-        let environment = parseInt($("#environment").val());
-        for (let i = 1; i < nodeList.length; i++) {
-            if (nodeList[i]["id"] != -1) { //已经被删除的节点不进行统计
-                if (nodeList[i]["option"] == 1) //打开网页操作,统计输入框输入操作
-                {
-                    if (!nodeList[i]["parameters"]["useLoop"]) //如果不是使用循环里的文本
-                    {
-                        inputParameters.push({
-                            id: inputIndex,
-                            name: "urlList_" + inputIndex++,
-                            nodeId: i, //记录操作位于的节点位置,重要!!!
-                            nodeName: nodeList[i]["title"],
-                            value: nodeList[i]["parameters"]["links"],
-                            desc: "要采集的网址列表,多行以\\n分开",
-                            type: "text",
-                            exampleValue: nodeList[i]["parameters"]["links"]
-                        });
-                        links = nodeList[i]["parameters"]["links"];
-                    }
-                } else if (nodeList[i]["option"] == 4) //输入文字操作
-                {
-                    if (!nodeList[i]["parameters"]["useLoop"]) //如果不是使用循环里的文本
-                    {
-                        inputParameters.push({
-                            id: inputIndex,
-                            name: "inputText_" + inputIndex++,
-                            nodeName: nodeList[i]["title"],
-                            nodeId: i,
-                            desc: "要输入的文本,如京东搜索框输入:电脑",
-                            type: "text",
-                            exampleValue: nodeList[i]["parameters"]["value"],
-                            value: nodeList[i]["parameters"]["value"],
-                        });
-                    }
-                } else if (nodeList[i]["option"] == 8) //循环操作
-                {
-                    if (parseInt(nodeList[i]["parameters"]["loopType"]) > 2 && parseInt(nodeList[i]["parameters"]["loopType"]) < 5) { //循环中的循环输入文本或循环输入网址
-                        inputParameters.push({
-                            id: inputIndex,
-                            name: "loopText_" + inputIndex++,
-                            nodeId: i,
-                            nodeName: nodeList[i]["title"],
-                            desc: "要输入的文本/网址,多行以\\n分开",
-                            type: "text",
-                            exampleValue: nodeList[i]["parameters"]["textList"],
-                            value: nodeList[i]["parameters"]["textList"],
-                        });
-                    } else if (parseInt(nodeList[i]["parameters"]["loopType"]) == 0) {
-                        inputParameters.push({
-                            id: inputIndex,
-                            name: "loopTimes_" + nodeList[i]["title"] + "_" + inputIndex++,
-                            nodeId: i,
-                            nodeName: nodeList[i]["title"],
-                            desc: "循环" + nodeList[i]["title"] + "执行的次数(0代表无限循环)",
-                            type: "int",
-                            exampleValue: nodeList[i]["parameters"]["exitCount"],
-                            value: nodeList[i]["parameters"]["exitCount"],
-                        });
-                    }
-                } else if (nodeList[i]["option"] == 3) //提取数据操作
-                {
-                    for (let j = 0; j < nodeList[i]["parameters"]["paras"].length; j++) {
-                        if (outputNames.indexOf(nodeList[i]["parameters"]["paras"][j]["name"]) < 0) { //参数名称还未被添加
-                            outputNames.push(nodeList[i]["parameters"]["paras"][j]["name"]);
-                            outputParameters.push({
-                                id: outputIndex++,
-                                name: nodeList[i]["parameters"]["paras"][j]["name"],
-                                desc: nodeList[i]["parameters"]["paras"][j]["desc"],
-                                type: nodeList[i]["parameters"]["paras"][j]["paraType"],
-                                recordASField: nodeList[i]["parameters"]["paras"][j]["recordASField"],
-                                exampleValue: nodeList[i]["parameters"]["paras"][j]["exampleValues"][0]["value"],
-                            });
-                        }
-                    }
-                } else if (nodeList[i]["option"] == 5) //自定义操作
-                {
-                    // if (nodeList[i]["parameters"]["recordASField"] == 1) {
-                    let id = outputIndex++;
-                    let title = nodeList[i]["title"];
-                    // if (outputNames.indexOf(title) >= 0) { //参数名称已经被添加
-                    //     $('#myModal').modal('hide');
-                    //     $("#tip2").slideDown(); //提示框
-                    //     fadeout = setTimeout(function() {
-                    //         $("#tip2").slideUp();
-                    //     }, 5000);
-                    //     return;
-                    // }
-                    outputNames.push(title);
-                    outputParameters.push({
-                        id: id,
-                        name: title,
-                        desc: "自定义操作返回的数据",
-                        type: nodeList[i]["parameters"]["paraType"],
-                        recordASField: nodeList[i]["parameters"]["recordASField"],
-                        exampleValue: "",
-                    });
-                    // }
-                } else if (nodeList[i]["option"] == 9) //条件判断
-                {
-                    containJudge = true;
-                }
-            }
-        }
-        let serviceInfo = {
-            "id": parseInt(serviceId),
-            "name": serviceName,
-            "url": url,
-            "links": links,
-            "create_time": new Date().toLocaleString(),
-            "version": "0.3.5",
-            "saveThreshold": saveThreshold,
-            "cloudflare": cloudflare,
-            "environment": environment,
-            "maxViewLength": parseInt($("#maxViewLength").val()),
-            "outputFormat": $("#outputFormat").val(),
-            "saveName": $("#saveName").val(),
-            "containJudge": containJudge,
-            "desc": serviceDescription,
-            "inputParameters": inputParameters,
-            "outputParameters": outputParameters,
-            "graph": nodeList, //图结构要存储下来
-        };
-        if(serviceInfo.outputFormat=="mysql"){
-            if(!isValidMySQLTableName(serviceInfo.saveName)) {
-                $('#myModal').modal('hide');
-                $("#tipMySQL").slideDown(); //提示框
-                let fadeout = setTimeout(function() {
-                    $("#tipMySQL").slideUp();
-                }, 4000);
-                return;
-            }
-        }
-        $.post(backEndAddressServiceWrapper + "/manageTask", { paras: JSON.stringify(serviceInfo) },
-            function(result) { $("#serviceId").val(parseInt(result)) });
-        // alert("保存成功!");
-        $('#myModal').modal('hide');
-        $("#tip").slideDown(); //提示框
-        let fadeout = setTimeout(function() {
-            $("#tip").slideUp();
-        }, 2000);
-
-    // }
-}
-
-//点击保存任务按钮时的处理
-$("#saveButton").mousedown(function() {
-    saveService(0);
-});
-//点击另存为任务按钮时的处理
-$("#saveAsButton").mousedown(function() {
-    saveService(1);
-});
-
-
-if (sId != null && sId != -1) //加载任务
-{
-    $.get(backEndAddressServiceWrapper + "/queryTask?id=" + sId, function(result) {
-        nodeList = result["graph"];
-        app.$data.list.nl = nodeList;
-        for(let node of nodeList){ //兼容旧版本
-            if(node["option"] == 1){
-                if(!("cookies" in node["parameters"])) {
-                    node["parameters"]["cookies"] = "";
-                }
-            }
-        }
-        $("#serviceName").val(result["name"]);
-        $("#serviceId").val(result["id"]);
-        $("#url").val(result["url"]);
-        $("#serviceDescription").val(result["desc"]);
-        for(let key of Object.keys(result)){
-            try{
-                $("#"+key).val(result[key]);
-            } catch(e){
-                console.log(e);
-            }
-        }
-        refresh();
-    });
-} else {
-    refresh(); //新增任务
-}

+ 3 - 0
ElectronJS/src/taskGrid/taskInfo.html

@@ -47,6 +47,8 @@
             <p>{{"Task Name:~任务名称:" | lang}} {{task["name"]}}</p>
             <p style="word-wrap: break-word;word-break: break-all;overflow: hidden;max-height: 100px;">{{"Task Description:~任务描述:" | lang}} {{task["desc"]}}</p>
             <p style="word-wrap: break-word;word-break: break-all;overflow: hidden;max-height: 100px;">{{"Example URL:~样例网址:" | lang}} {{task["url"]}}</p>
+            <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>
                 <a style="margin-top: 5px;" href="javascript:void(0)" v-on:click="invokeTask(task['id'],task['url'])" class="btn btn-primary">{{"Invoke Task~调用任务" | lang}}</a></p>
@@ -125,6 +127,7 @@
             backEndAddressServiceWrapper: getUrlParam("backEndAddressServiceWrapper"),
         },
         methods: {
+            dateFormat: DateFormat,
             gotoHome:function(){
               let url = "";
               if(getUrlParam("lang")=="zh"){

+ 1 - 0
ElectronJS/tasks/155.json

@@ -0,0 +1 @@
+{"id":155,"name":"京东全球版-专业的综合网上购物商城","url":"https://www.jd.com","links":"https://www.jd.com","create_time":"7/8/2023, 4:15:43 AM","update_time":"7/8/2023, 4:25:42 AM","version":"0.3.5","saveThreshold":10,"cloudflare":0,"environment":0,"maxViewLength":15,"outputFormat":"mysql","saveName":"current_time","containJudge":false,"desc":"https://www.jd.com","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://www.jd.com","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://www.jd.com"}],"outputParameters":[],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","wait":0,"waitType":0},"isInLoop":false},{"id":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"url":"https://www.jd.com","links":"https://www.jd.com","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":0,"option":2,"title":"点击元素","sequence":[],"isInLoop":false,"position":1,"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[]}}]}

Plik diff jest za duży
+ 0 - 0
ElectronJS/tasks/156.json


Plik diff jest za duży
+ 0 - 0
ElectronJS/tasks/34.json


+ 1 - 1
media/readme_back.md

@@ -136,7 +136,7 @@ Yin: Professor of College of Computer Science and Technology of Zhejiang Univers
 
 ### 后台流程图部分
 * ServiceGrid/frontEnd/FlowChart.html
-* ServiceGrid/frontEnd/FlowChart.js
+* ServiceGrid/frontEnd/FlowChart_Deprecated.js
 * ServiceGrid/frontEnd/FlowChart.css
 * ServiceGrid/frontEnd/logic.css
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików