|
@@ -0,0 +1,296 @@
|
|
|
+<template>
|
|
|
+ <div class="site-test">
|
|
|
+ <h1>未迟 【站点测试工具】</h1>
|
|
|
+
|
|
|
+ <!-- 新增的下拉选择框 -->
|
|
|
+ <select v-model="testType" class="test-type-select">
|
|
|
+ <option value="1">国内节点</option>
|
|
|
+ <option value="2">中亚节点</option>
|
|
|
+ <option value="3">日本节点</option>
|
|
|
+ <option value="4">欧洲节点</option>
|
|
|
+ <option value="4">北美节点</option>
|
|
|
+ </select>
|
|
|
+
|
|
|
+ <select v-model="protocal" class="test-type-select1">
|
|
|
+ <option value="http://">http://</option>
|
|
|
+ <option value="https://">https://</option>
|
|
|
+ </select>
|
|
|
+ <input type="text" v-model="targetUrl" placeholder="请输入网站地址">
|
|
|
+ <button @click="fetchData">测试</button>
|
|
|
+ <!-- <p style="white-space: pre-wrap; font-size: 20px; " 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 v-show="resultTableDisplay" class="table-content">
|
|
|
+ <table class="k6-table" border="1" cellpadding="6" cellspacing="0">
|
|
|
+ <thead>
|
|
|
+ <tr >
|
|
|
+ <th colSpan="3" style="font-size: 17px; color: white; background-color: #42b983;">{{ title1 }}</th>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <th>指标名称</th>
|
|
|
+ <th>指标项</th>
|
|
|
+ <th>数值</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="k6-metrics-tbody">
|
|
|
+ <!-- 动态填充 -->
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <img :src="imgUrl"></img> -->
|
|
|
+ <!-- <div>
|
|
|
+ <div ref="terminalContainer" class="terminal-container"></div>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+// import { Terminal } from 'xterm';
|
|
|
+// import 'xterm/css/xterm.css';
|
|
|
+const metricLabelMap = {
|
|
|
+ checks_total: "总检查数",
|
|
|
+ checks_succeeded: "检查通过率",
|
|
|
+ checks_failed: "检查失败率",
|
|
|
+ browser_data_received: "浏览器接收数据量",
|
|
|
+ browser_data_sent: "浏览器发送数据量",
|
|
|
+ browser_http_req_duration: "浏览器 HTTP 请求耗时",
|
|
|
+ browser_http_req_failed: "浏览器 HTTP 请求失败率",
|
|
|
+ browser_web_vital_cls: "浏览器内容布局偏移(CLS)",
|
|
|
+ browser_web_vital_fcp: "浏览器首次内容绘制(FCP)",
|
|
|
+ browser_web_vital_ttfb: "浏览器首字节时间(TTFB)",
|
|
|
+ checks: "检查通过率",
|
|
|
+ data_received: "接收数据量",
|
|
|
+ data_sent: "发送数据量",
|
|
|
+ iteration_duration: "单次迭代耗时",
|
|
|
+ iterations: "总迭代次数",
|
|
|
+ vus: "虚拟用户数",
|
|
|
+ vus_max: "最大虚拟用户数",
|
|
|
+};
|
|
|
+
|
|
|
+const fieldKeyMap = {
|
|
|
+ avg: "平均",
|
|
|
+ min: "最小",
|
|
|
+ med: "中位数",
|
|
|
+ max: "最大",
|
|
|
+ "p(90)": "P90",
|
|
|
+ "p(95)": "P95",
|
|
|
+ value: "数值",
|
|
|
+ rate: "速率",
|
|
|
+};
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ targetUrl: "",
|
|
|
+ title1: "测试结果",
|
|
|
+ title2: "",
|
|
|
+ message: "",
|
|
|
+ testType: "1", // 新增的下拉框绑定值,默认选择负载测试
|
|
|
+ protocal: "http://",
|
|
|
+ imgUrl: "",
|
|
|
+ loading: false,
|
|
|
+ resultTableDisplay: false
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ fetchData() {
|
|
|
+ // const term = new Terminal();
|
|
|
+ // term.open(this.$refs.terminalContainer);
|
|
|
+ this.title2 = "";
|
|
|
+ this.imgUrl = "";
|
|
|
+ this.resultTableDisplay = false;
|
|
|
+ this.title1 = this.targetUrl + "测试结果";
|
|
|
+ const timeStamp = new Date().getTime();
|
|
|
+ this.loading = true;
|
|
|
+
|
|
|
+ // 修改请求URL,包含测试类型参数
|
|
|
+ fetch(
|
|
|
+ `/api/k6?targetUrl=${this.protocal + this.targetUrl}&timeStamp=` +
|
|
|
+ timeStamp
|
|
|
+ )
|
|
|
+ .then((res) => res.json())
|
|
|
+ .then((data) => {
|
|
|
+ this.resultTableDisplay = true;
|
|
|
+ this.imgUrl = "/static/screenshot_" + timeStamp + ".png";
|
|
|
+ const tbody = document.getElementById("k6-metrics-tbody");
|
|
|
+ tbody.innerHTML = "";
|
|
|
+
|
|
|
+ Object.entries(data).forEach(([metricKey, metricVal]) => {
|
|
|
+ const metricLabel = metricLabelMap[metricKey] || metricKey;
|
|
|
+
|
|
|
+ if (typeof metricVal === "object" && !Array.isArray(metricVal)) {
|
|
|
+ const subKeys = Object.keys(metricVal);
|
|
|
+ subKeys.forEach((subKey, idx) => {
|
|
|
+ const tr = document.createElement("tr");
|
|
|
+
|
|
|
+ if (idx === 0) {
|
|
|
+ const nameTd = document.createElement("td");
|
|
|
+ nameTd.textContent = metricLabel;
|
|
|
+ nameTd.rowSpan = subKeys.length;
|
|
|
+ tr.appendChild(nameTd);
|
|
|
+ }
|
|
|
+
|
|
|
+ const subKeyTd = document.createElement("td");
|
|
|
+ subKeyTd.textContent = fieldKeyMap[subKey] || subKey;
|
|
|
+
|
|
|
+ const valTd = document.createElement("td");
|
|
|
+ valTd.textContent = metricVal[subKey];
|
|
|
+
|
|
|
+ tr.appendChild(subKeyTd);
|
|
|
+ tr.appendChild(valTd);
|
|
|
+
|
|
|
+ tbody.appendChild(tr);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ const tr = document.createElement("tr");
|
|
|
+ const nameTd = document.createElement("td");
|
|
|
+ nameTd.textContent = metricLabel;
|
|
|
+ const subKeyTd = document.createElement("td");
|
|
|
+ subKeyTd.textContent = "";
|
|
|
+ const valTd = document.createElement("td");
|
|
|
+ valTd.textContent = metricVal;
|
|
|
+
|
|
|
+ tr.appendChild(nameTd);
|
|
|
+ tr.appendChild(subKeyTd);
|
|
|
+ tr.appendChild(valTd);
|
|
|
+ tbody.appendChild(tr);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const trImgTitle = document.createElement("tr");
|
|
|
+ const thImgTitle = document.createElement("th");
|
|
|
+ thImgTitle.colSpan = 3;
|
|
|
+ thImgTitle.textContent = "站点截图";
|
|
|
+ trImgTitle.appendChild(thImgTitle);
|
|
|
+ tbody.appendChild(trImgTitle);
|
|
|
+
|
|
|
+ const trImg = document.createElement("tr");
|
|
|
+ const tdImg = document.createElement("td");
|
|
|
+ tdImg.colSpan = 3;
|
|
|
+ const img = document.createElement("img");
|
|
|
+ img.src = this.imgUrl;
|
|
|
+ // 将img添加到td中
|
|
|
+ tdImg.appendChild(img);
|
|
|
+ // 将td添加到tr中
|
|
|
+ trImg.appendChild(tdImg);
|
|
|
+ // 最后将tr添加到tbody中
|
|
|
+ tbody.appendChild(trImg);
|
|
|
+ }).finally(() => {
|
|
|
+ this.loading = false;
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style>
|
|
|
+#app {
|
|
|
+ font-family: Avenir, Helvetica, Arial, sans-serif;
|
|
|
+ -webkit-font-smoothing: antialiased;
|
|
|
+ -moz-osx-font-smoothing: grayscale;
|
|
|
+}
|
|
|
+
|
|
|
+.site-test {
|
|
|
+ text-align: center;
|
|
|
+ color: #2c3e50;
|
|
|
+ margin-top: 60px;
|
|
|
+}
|
|
|
+.test-type-select {
|
|
|
+ padding: 8px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ width: 120px;
|
|
|
+ height: 40px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.test-type-select1 {
|
|
|
+ padding: 8px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ width: 80px;
|
|
|
+ height: 40px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+input {
|
|
|
+ width: 400px;
|
|
|
+ height: 23px;
|
|
|
+ padding: 8px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-left: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+button {
|
|
|
+ margin-left: 10px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ background-color: #42b983;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+button:hover {
|
|
|
+ background-color: #3aa876;
|
|
|
+}
|
|
|
+
|
|
|
+.table-content {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+table.k6-table {
|
|
|
+ width: 60%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
|
+ "Helvetica Neue", Arial;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+table.k6-table thead {
|
|
|
+ background-color: #f7f7f7;
|
|
|
+}
|
|
|
+
|
|
|
+table.k6-table th,
|
|
|
+table.k6-table td {
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ padding: 10px 12px;
|
|
|
+ color: #000;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* table.k6-table th {
|
|
|
+ text-align: center;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ table.k6-table td:first-child {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+ }
|
|
|
+
|
|
|
+ table.k6-table td:nth-child(2) {
|
|
|
+ text-align: center;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ table.k6-table td:nth-child(3) {
|
|
|
+ text-align: center;
|
|
|
+ color: #000;
|
|
|
+ } */
|
|
|
+
|
|
|
+table.k6-table tbody tr:hover {
|
|
|
+ background-color: #f0f8ff;
|
|
|
+}
|
|
|
+</style>
|