فهرست منبع

token间距调整,运行测试放大结果展示修改,在线测试页面中间内容宽度跳动问题

肖栋 1 هفته پیش
والد
کامیت
a7979f9404
5فایلهای تغییر یافته به همراه140 افزوده شده و 29 حذف شده
  1. 9 5
      src/pages/guide/index.vue
  2. 7 4
      src/pages/home/index.vue
  3. 118 15
      src/pages/mcp/onlineTest.vue
  4. 5 5
      src/pages/mcp/serverDetail.vue
  5. 1 0
      src/pages/pc/index.vue

+ 9 - 5
src/pages/guide/index.vue

@@ -1097,13 +1097,15 @@ const guideVerifyCards = [
   box-sizing: border-box;
   display: flex;
   align-items: center;
-  width: 100%;
-  max-width: 400px;
+  flex: 0 0 auto;
+  width: auto;
+  max-width: none;
   min-width: 0;
   height: 44px;
   padding: 12px 14px;
   background: #1E293B;
   border-radius: 6px;
+  white-space: nowrap;
 }
 
 .home-guide-token-tag {
@@ -1130,13 +1132,16 @@ const guideVerifyCards = [
   color: #E5E7EB;
   line-height: 18px;
   text-align: left;
+  white-space: nowrap;
+  flex: 0 0 auto;
 }
 
 .home-guide-token-icons {
   display: flex;
   align-items: center;
   gap: 10px;
-  margin-left: auto;
+  margin-left: 20px;
+  flex-shrink: 0;
 }
 
 .home-guide-token-icon {
@@ -2337,9 +2342,8 @@ const guideVerifyCards = [
   }
 
   .home-guide-token-bar {
-    flex: 1 1 auto;
+    flex: 0 0 auto;
     width: auto;
-    min-width: 0;
     max-width: none;
   }
 

+ 7 - 4
src/pages/home/index.vue

@@ -2074,14 +2074,14 @@ const guideVerifyCards = [
   box-sizing: border-box;
   display: flex;
   align-items: center;
-  flex: 1 1 auto;
+  flex: 0 0 auto;
   width: auto;
-  min-width: 0;
-  max-width: 400px;
+  max-width: none;
   height: 44px;
   padding: 12px 14px;
   background: #1E293B;
   border-radius: 6px;
+  white-space: nowrap;
 }
 
 .home-guide-token-tag {
@@ -2108,13 +2108,16 @@ const guideVerifyCards = [
   color: #E5E7EB;
   line-height: 18px;
   text-align: left;
+  white-space: nowrap;
+  flex: 0 0 auto;
 }
 
 .home-guide-token-icons {
   display: flex;
   align-items: center;
   gap: 10px;
-  margin-left: auto;
+  margin-left: 20px;
+  flex-shrink: 0;
 }
 
 .home-guide-token-icon {

+ 118 - 15
src/pages/mcp/onlineTest.vue

@@ -481,6 +481,19 @@
             <pre class="otp-detail-code" v-html="detailJsonHtml"></pre>
           </div>
         </div>
+        <div class="otp-detail-foot">
+          <div class="otp-detail-foot-left">
+            <span>数据大小: {{ detailByteSize }} 字节</span>
+            <span>行数: {{ detailLineCount }}</span>
+            <span v-if="detailSearchKeyword" class="otp-detail-foot-search">
+              搜索: "{{ detailSearchKeyword }}" ({{ detailSearchMatchCount }} 个匹配)
+            </span>
+          </div>
+          <div v-if="detailIsSlow" class="otp-detail-foot-slow">
+            <icon-svg name="yellow_alert" size="12px" />
+            <span>响应较慢</span>
+          </div>
+        </div>
       </div>
     </el-dialog>
   </div>
@@ -1035,6 +1048,35 @@ const highlightJson = (jsonText) => {
   return html;
 };
 
+/** 在已着色 HTML 的文本节点中高亮关键词 */
+const highlightSearchInHtml = (html, keyword) => {
+  const kw = String(keyword || "").trim();
+  if (!kw) return html;
+  const escapedKw = escapeHtml(kw).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+  const re = new RegExp(escapedKw, "gi");
+  return String(html)
+    .split(/(<[^>]+>)/g)
+    .map((part) => {
+      if (!part || part.startsWith("<")) return part;
+      return part.replace(re, (matched) => `<mark class="otp-json-search-hit">${matched}</mark>`);
+    })
+    .join("");
+};
+
+const countSearchMatches = (text, keyword) => {
+  const kw = String(keyword || "").trim();
+  if (!kw) return 0;
+  const source = String(text || "").toLowerCase();
+  const needle = kw.toLowerCase();
+  let count = 0;
+  let index = 0;
+  while ((index = source.indexOf(needle, index)) !== -1) {
+    count += 1;
+    index += needle.length;
+  }
+  return count;
+};
+
 const responseJsonHtml = computed(() => highlightJson(responseJson.value));
 
 const hasTestResult = computed(() => searchResult.value != null);
@@ -1042,23 +1084,29 @@ const hasTestResult = computed(() => searchResult.value != null);
 const resultDetailVisible = ref(false);
 const resultSearch = ref("");
 
-const detailDisplayJson = computed(() => {
+const detailSearchKeyword = computed(() => resultSearch.value.trim());
+
+const detailByteSize = computed(() =>
+  new TextEncoder().encode(responseJson.value || "").length,
+);
+
+const detailLineCount = computed(() => {
   const text = responseJson.value || "";
-  const keyword = resultSearch.value.trim();
-  if (!keyword) return text;
-  const lower = keyword.toLowerCase();
-  return text
-    .split("\n")
-    .filter((line) => line.toLowerCase().includes(lower))
-    .join("\n");
+  return text ? text.split("\n").length : 0;
 });
 
-const detailLineCount = computed(() => {
-  const text = detailDisplayJson.value || "";
-  return text ? text.split("\n").length : 1;
+const detailSearchMatchCount = computed(() =>
+  countSearchMatches(responseJson.value, detailSearchKeyword.value),
+);
+
+const detailIsSlow = computed(() => {
+  const time = resultMeta.value?.time;
+  return time != null && Number(time) >= 1000;
 });
 
-const detailJsonHtml = computed(() => highlightJson(detailDisplayJson.value));
+const detailJsonHtml = computed(() =>
+  highlightSearchInHtml(highlightJson(responseJson.value), detailSearchKeyword.value),
+);
 
 const openResultDetail = () => {
   resultSearch.value = "";
@@ -1335,6 +1383,7 @@ const runTest = async () => {
 .otp-side {
   flex: 1 1 0;
   min-width: 0;
+  overflow: hidden;
   box-sizing: border-box;
   padding: 16px;
   background: #FFFFFF;
@@ -1424,6 +1473,7 @@ const runTest = async () => {
 .otp-main {
   flex: 3 3 0;
   min-width: 0;
+  overflow: hidden;
   display: flex;
   flex-direction: column;
   gap: 16px;
@@ -1806,13 +1856,15 @@ const runTest = async () => {
   align-items: center;
   gap: 8px;
   box-sizing: border-box;
-  width: 100%;
+  width: auto;
+  max-width: 100%;
   min-width: 0;
   min-height: 36px;
   padding: 6px 12px;
   background: #F8FAFC;
   border-radius: 8px;
   border: 1px solid #E2E8F0;
+  white-space: nowrap;
 }
 
 .otp-token-get-btn {
@@ -1844,7 +1896,7 @@ const runTest = async () => {
 }
 
 .otp-token-text {
-  flex: 1;
+  flex: 1 1 auto;
   min-width: 0;
   font-family: PingFang SC, PingFang SC;
   font-weight: 400;
@@ -1852,13 +1904,16 @@ const runTest = async () => {
   color: #1E293B;
   line-height: 20px;
   text-align: left;
-  word-break: break-all;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
 .otp-token-actions {
   display: flex;
   align-items: center;
   gap: 8px;
+  margin-left: 12px;
   flex-shrink: 0;
 }
 
@@ -2350,6 +2405,7 @@ const runTest = async () => {
 .otp-result {
   flex: 1.5 1.5 0;
   min-width: 0;
+  overflow: hidden;
 }
 
 .otp-result-card {
@@ -2769,6 +2825,13 @@ const runTest = async () => {
   color: #86EFAC;
 }
 
+.otp-json-search-hit {
+  background: #FACC15;
+  color: #0F172A;
+  border-radius: 2px;
+  padding: 0 1px;
+}
+
 .otp-result-dialog.el-dialog {
   max-width: 960px;
   padding: 0;
@@ -3013,4 +3076,44 @@ const runTest = async () => {
   white-space: pre-wrap;
   word-break: break-word;
 }
+
+.otp-detail-foot {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  margin-top: -6px; /* 抵消父级 gap:16px,使与代码框间距为 10px */
+  padding-top: 10px;
+  border-top: 1px solid #E2E8F0;
+}
+
+.otp-detail-foot-left {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 16px;
+  min-width: 0;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #64748B;
+  line-height: 18px;
+}
+
+.otp-detail-foot-search {
+  color: #1678FF;
+}
+
+.otp-detail-foot-slow {
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  flex-shrink: 0;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #F59E0B;
+  line-height: 18px;
+}
 </style>

+ 5 - 5
src/pages/mcp/serverDetail.vue

@@ -1961,11 +1961,12 @@ const createToken = async () => {
   box-sizing: border-box;
   display: flex;
   align-items: center;
-  width: 100%;
+  width: auto;
   margin-top: 12px;
   padding: 12px 14px;
   background: #1E293B;
   border-radius: 6px;
+  white-space: nowrap;
 }
 
 .mcp-server-token-tag {
@@ -1983,22 +1984,21 @@ const createToken = async () => {
 
 .mcp-server-token-value {
   margin-left: 8px;
-  min-width: 0;
-  flex: 1;
+  flex: 0 0 auto;
   font-family: PingFang SC, PingFang SC;
   font-weight: 500;
   font-size: 13px;
   color: #E5E7EB;
   line-height: 18px;
   text-align: left;
-  word-break: break-all;
+  white-space: nowrap;
 }
 
 .mcp-server-token-icons {
   display: flex;
   align-items: center;
   gap: 10px;
-  margin-left: auto;
+  margin-left: 20px;
   flex-shrink: 0;
 }
 

+ 1 - 0
src/pages/pc/index.vue

@@ -87,6 +87,7 @@ onBeforeMount(async () => {
   height: 100%;
   min-height: 0;
   overflow-y: auto;
+  scrollbar-gutter: stable;
 }
 
 .page-main {