Selaa lähdekoodia

feat: 优化界面

周玉环 2 viikkoa sitten
vanhempi
commit
1b8d90a7d8
8 muutettua tiedostoa jossa 131 lisäystä ja 75 poistoa
  1. 2 0
      .gitignore
  2. 8 1
      README.md
  3. BIN
      client/public/advich.jpg
  4. 2 1
      client/public/index.html
  5. 7 2
      client/src/App.vue
  6. 8 71
      server/index.js
  7. 1 0
      server/package.json
  8. 103 0
      server/utils.js

+ 2 - 0
.gitignore

@@ -7,3 +7,5 @@ client/package-lock.json
 server/node_modules
 
 server/package-lock.json
+
+server/screenshots/*

+ 8 - 1
README.md

@@ -1 +1,8 @@
-# 站点测试工具
+# 站点测试工具
+
+## 开发
+```bash
+$ cd client && npm install
+$ cd server && npm install
+$ cd server && npm run dev
+```

BIN
client/public/advich.jpg


+ 2 - 1
client/public/index.html

@@ -2,7 +2,8 @@
 <html lang="en">
 <head>
   <meta charset="UTF-8">
-  <title>Vue2 + Express</title>
+  <title>未迟站点测试工具</title>
+  <link rel="icon" href="/advich.jpg" />
 </head>
 <body>
   <div id="app"></div>

+ 7 - 2
client/src/App.vue

@@ -17,10 +17,11 @@
     </select>
     <input type="text" v-model="targetUrl" placeholder="请输入网站地址">
     <button @click="fetchData">测试</button>
-    <p style="white-space: pre-wrap;">{{ title1 }}</p>
+    <p style="white-space: pre-wrap;" v-show="!loading" >{{ title1 }}</p>
     <p style="white-space: pre-wrap;">{{ message }}</p>
 
     <p style="white-space: pre-wrap;">{{ title2 }}</p>
+    <div v-if="loading">获取测试结果中...</div>
     <!-- App.vue 里渲染 -->
     <div class="table-content">
       <table class="k6-table" border="1" cellpadding="6" cellspacing="0">
@@ -88,6 +89,7 @@ export default {
       testType: "1", // 新增的下拉框绑定值,默认选择负载测试
       protocal: "http://",
       imgUrl: "",
+      loading: false
     };
   },
   methods: {
@@ -98,6 +100,7 @@ export default {
       this.imgUrl = "";
       this.title1 = this.targetUrl + "测试结果:";
       const timeStamp = new Date().getTime();
+      this.loading = true;
       // 修改请求URL,包含测试类型参数
       fetch(
         `/api/k6?targetUrl=${this.protocal + this.targetUrl}&timeStamp=` +
@@ -150,7 +153,9 @@ export default {
               tbody.appendChild(tr);
             }
           });
-        });
+        }).finally(() => {
+          this.loading = false;
+        })
     },
   },
 };

+ 8 - 71
server/index.js

@@ -2,83 +2,20 @@ const express = require('express');
 const path = require('path');
 const { spawn } = require('child_process');
 const cors = require('cors');
+const { parseK6StdoutTable, watchClient } = require("./utils");
 
 const app = express();
-const PORT = process.env.NODE_ENV === 'development' ? 8080 : 9001;
-const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
-const CLIENT_DIST_DIR = process.env.NODE_ENV === 'development' ? path.join(__dirname, '../client/dist') : path.join(__dirname, './public');
-
-function parseK6StdoutTable(text) {
-  const keysToKeep = new Set([
-    'checks_total',
-    'checks_succeeded',
-    'checks_failed',
-    'browser_data_received',
-    'browser_data_sent',
-    'browser_http_req_duration',
-    'browser_http_req_failed',
-    'browser_web_vital_cls',
-    'browser_web_vital_fcp',
-    'browser_web_vital_ttfb',
-    'checks',
-    'data_received',
-    'data_sent',
-    'iteration_duration',
-    'iterations',
-    'vus',
-    'vus_max'
-  ]);
-
-  const lines = text.split('\n');
-  const result = {};
-
-  for (let line of lines) {
-    line = line.replace(/^[✓×\s]+/, ''); // 去掉开头 ✓ × 空格
-
-    if (!line.includes(':')) continue;
-
-    let [keyPart, valPart] = line.split(':');
-    if (!valPart) continue;
-
-    const key = keyPart.trim().replace(/\.+$/, '');
-    if (!keysToKeep.has(key)) continue;
-
-    valPart = valPart.trim();
 
-    if (/(\w+\()?=/.test(valPart)) {
-      const stats = {};
-      const regex = /([^\s=]+)=([^\s]+)/g;
-      let match;
-      while ((match = regex.exec(valPart)) !== null) {
-        stats[match[1]] = match[2];
-      }
-      result[key] = stats;
-
-    } else {
-      const parts = valPart.split(/\s+/);
-
-      if (parts.length === 4 && parts[2].endsWith('/s')) {
-        result[key] = {
-          value: parts[0] + ' ' + parts[1],
-          rate: parts[2] + ' ' + parts[3],
-        };
-      } else if (parts.length === 2 && parts[1].endsWith('/s')) {
-        result[key] = {
-          value: parts[0],
-          rate: parts[1],
-        };
-      } else {
-        result[key] = valPart;
-      }
-    }
-  }
+const isDev = process.env.NODE_ENV === 'development';
+const PORT = isDev ? 8080 : 9001;
+const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
+const CLIENT_DIST_DIR = isDev ? path.join(__dirname, '../client/dist') : path.join(__dirname, './public');
 
-  return result;
+// 开发模式下监听client文件变动执行打包脚本
+if (isDev) {
+  watchClient()
 }
 
-
-
-
 // ====== 中间件 ======
 app.use(cors());
 

+ 1 - 0
server/package.json

@@ -10,6 +10,7 @@
   "dependencies": {
     "cors": "^2.8.5",
     "express": "^4.17.1",
+    "chokidar": "^4.0.3",
     "nodemon": "^2.0.7"
   },
   "devDependencies": {}

+ 103 - 0
server/utils.js

@@ -0,0 +1,103 @@
+const chokidar = require("chokidar");
+const { exec } = require("child_process");
+const path = require("path")
+
+exports.parseK6StdoutTable = function (text) {
+  const keysToKeep = new Set([
+    "checks_total",
+    "checks_succeeded",
+    "checks_failed",
+    "browser_data_received",
+    "browser_data_sent",
+    "browser_http_req_duration",
+    "browser_http_req_failed",
+    "browser_web_vital_cls",
+    "browser_web_vital_fcp",
+    "browser_web_vital_ttfb",
+    "checks",
+    "data_received",
+    "data_sent",
+    "iteration_duration",
+    "iterations",
+    "vus",
+    "vus_max",
+  ]);
+
+  const lines = text.split("\n");
+  const result = {};
+
+  for (let line of lines) {
+    line = line.replace(/^[✓×\s]+/, ""); // 去掉开头 ✓ × 空格
+
+    if (!line.includes(":")) continue;
+
+    let [keyPart, valPart] = line.split(":");
+    if (!valPart) continue;
+
+    const key = keyPart.trim().replace(/\.+$/, "");
+    if (!keysToKeep.has(key)) continue;
+
+    valPart = valPart.trim();
+
+    if (/(\w+\()?=/.test(valPart)) {
+      const stats = {};
+      const regex = /([^\s=]+)=([^\s]+)/g;
+      let match;
+      while ((match = regex.exec(valPart)) !== null) {
+        stats[match[1]] = match[2];
+      }
+      result[key] = stats;
+    } else {
+      const parts = valPart.split(/\s+/);
+
+      if (parts.length === 4 && parts[2].endsWith("/s")) {
+        result[key] = {
+          value: parts[0] + " " + parts[1],
+          rate: parts[2] + " " + parts[3],
+        };
+      } else if (parts.length === 2 && parts[1].endsWith("/s")) {
+        result[key] = {
+          value: parts[0],
+          rate: parts[1],
+        };
+      } else {
+        result[key] = valPart;
+      }
+    }
+  }
+
+  return result;
+};
+
+exports.watchClient = () => {
+  const watchPath = path.join(__dirname, '../client/src');
+
+  // 初始化 watcher
+  const watcher = chokidar.watch(watchPath, {
+    ignored: /(^|[\/\\])\../, // 忽略隐藏文件
+    persistent: true,
+  });
+
+  // 添加事件监听
+  watcher
+    .on("add", (path) => runScript(`新增文件:${path}`))
+    .on("change", (path) => runScript(`文件修改:${path}`))
+    .on("unlink", (path) => runScript(`删除文件:${path}`));
+
+  // 你想在文件变更后执行的脚本(例如执行某个命令)
+  function runScript(changeInfo) {
+    console.log(`[触发] ${changeInfo}`);
+
+    // 例如执行 build 脚本或 shell 命令
+    exec("cd ../client && npm run build", (error, stdout, stderr) => {
+      if (error) {
+        console.error(`执行出错: ${error.message}`);
+        return;
+      }
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+      console.log(`stdout: ${stdout}`);
+    });
+  }
+};