肖栋 21 годин тому
батько
коміт
b94383de4c

+ 1 - 0
src/assets/icons/cut_blue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="14" height="14" viewBox="2 2 14 14"><defs><clipPath id="master_svg0_73_4739"><rect x="2" y="2" width="14" height="14" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_73_4739)"><path d="M12.888767625,13.97699465234375L14.078766625,13.95599365234375L14.106767625,15.35599465234375L10.599766725,15.41899365234375L10.536766025,11.91199395234375L11.936767625,11.89099405234375L11.957765625,12.92699465234375C14.092765625,11.24699405234375,14.456767625,8.15999455234375,12.783767625,6.03199385234375C11.831767125,4.82099385234375,10.375766725,4.12799375234375,8.842766325,4.15599385234375L8.898766525,2.74899377234375C12.384766625,2.73499378234375,15.226766625,5.5489938523437505,15.240765625,9.03499415234375C15.247765625,10.95999335234375,14.379767625,12.77299365234375,12.888767625,13.97699465234375ZM5.111766125,4.02299385234375L3.921765925,4.04399355234375L3.900765925,2.64399363134375L7.407766325,2.58099365234375L7.470766025,6.08799365234375L6.070765925,6.108993252343749L6.042766125,5.07299375234375C3.9077658250000002,6.75299355234375,3.543765725,9.83999395234375,5.216765925,11.96799375234375C6.168766225000001,13.17199365234375,7.624766325,13.87199265234375,9.157766825,13.84399365234375L9.101766125000001,15.25099365234375C5.615766025,15.26499465234375,2.773765412,12.45099445234375,2.759765625,8.96499345234375C2.75976572513581,7.04699425234375,3.620765805,5.22699405234375,5.111766125,4.02299385234375Z" fill="#1678FF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

+ 148 - 5
src/pages/mcp/onlineTest.vue

@@ -269,7 +269,7 @@
             <template v-else>
               <div v-if="requiredParams.length" class="otp-param-required-box">
                 <div
-                  v-for="param in requiredParams"
+                  v-for="(param, paramIndex) in requiredParams"
                   :key="param.name"
                   class="otp-param-required-item"
                 >
@@ -285,6 +285,31 @@
                     class="otp-param-input otp-param-input--required"
                     :placeholder="param.placeholder || param.desc"
                   />
+                  <div
+                    v-if="paramIndex === 0 && displayRecommendQuestions.length"
+                    class="otp-recommend"
+                  >
+                    <div class="otp-recommend-main">
+                      <span class="otp-recommend-label">推荐问题:</span>
+                      <button
+                        v-for="(question, qIndex) in displayRecommendQuestions"
+                        :key="`${recommendPageIndex}-${qIndex}`"
+                        type="button"
+                        class="otp-recommend-chip"
+                        @click="applyRecommendQuestion(question, param)"
+                      >
+                        {{ question }}
+                      </button>
+                    </div>
+                    <button
+                      type="button"
+                      class="otp-recommend-refresh"
+                      @click="refreshRecommendQuestions"
+                    >
+                      <icon-svg name="cut_blue" size="14px" />
+                      <span>换一换</span>
+                    </button>
+                  </div>
                 </div>
               </div>
 
@@ -567,6 +592,27 @@ const requestParams = ref([]);
 const responseStructure = ref([]);
 const paramTab = ref("request");
 const jsonMode = ref(false);
+/** 推荐问题:后续接接口填充,暂无数据则不展示 */
+const recommendQuestionPool = ref([]);
+const recommendPageIndex = ref(0);
+const displayRecommendQuestions = computed(() => {
+  const pool = recommendQuestionPool.value;
+  if (!pool.length) return [];
+  const pageSize = 2;
+  const start = (recommendPageIndex.value * pageSize) % pool.length;
+  const list = [];
+  for (let i = 0; i < pageSize && i < pool.length; i += 1) {
+    list.push(pool[(start + i) % pool.length]);
+  }
+  return list;
+});
+const refreshRecommendQuestions = () => {
+  recommendPageIndex.value += 1;
+};
+const applyRecommendQuestion = (question, param) => {
+  if (!param || !question) return;
+  param.value = question;
+};
 const requestJsonText = ref("{}");
 const tokenVisible = ref(false);
 const resultTab = ref("业务数据");
@@ -642,6 +688,8 @@ const resetTestState = () => {
   requestParams.value = [];
   responseStructure.value = [];
   requestJsonText.value = "{}";
+  recommendQuestionPool.value = [];
+  recommendPageIndex.value = 0;
   searchResult.value = null;
   resultMeta.value = { code: null, time: null, ok: false, label: "-" };
 };
@@ -681,6 +729,7 @@ watch(
 
 watch(activeToolCode, () => {
   syncParamsFromTool();
+  recommendPageIndex.value = 0;
   searchResult.value = null;
   resultMeta.value = { code: null, time: null, ok: false, label: "-" };
 });
@@ -2397,6 +2446,66 @@ const runTest = async () => {
   background: #FFFFFF;
 }
 
+.otp-recommend {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.otp-recommend-main {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 6px;
+  min-width: 0;
+  flex: 1;
+}
+
+.otp-recommend-label {
+  flex-shrink: 0;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #64748B;
+  line-height: 18px;
+  text-align: left;
+}
+
+.otp-recommend-chip {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 4px 6px;
+  border: 1px solid #E2E8F0;
+  border-radius: 4px;
+  background: #F8FAFC;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #333333;
+  line-height: 18px;
+  text-align: justify;
+  cursor: pointer;
+}
+
+.otp-recommend-refresh {
+  display: inline-flex;
+  align-items: center;
+  flex-shrink: 0;
+  gap: 4px;
+  margin: 0;
+  padding: 0;
+  border: none;
+  background: transparent;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #1678FF;
+  line-height: 18px;
+  text-align: justify;
+  cursor: pointer;
+}
+
 .otp-param-input--optional {
   padding: 12px;
   background: #F8FAFC;
@@ -2453,10 +2562,20 @@ const runTest = async () => {
 .otp-result {
   flex: 1.5 1.5 0;
   min-width: 0;
+  align-self: stretch;
+  display: flex;
+  flex-direction: column;
   position: sticky;
   top: 0;
   max-height: 100%;
+  min-height: 0;
   overflow: hidden;
+
+  &:has(.otp-result-card.is-empty) {
+    max-height: none;
+    min-height: 560px;
+    overflow: visible;
+  }
 }
 
 .otp-result-card {
@@ -2464,7 +2583,10 @@ const runTest = async () => {
   display: flex;
   flex-direction: column;
   gap: 16px;
-  min-height: 560px;
+  flex: 1;
+  min-height: min(560px, 100%);
+  max-height: 100%;
+  overflow: hidden;
   padding: 16px;
   background: #FFFFFF;
   box-shadow: 0px 4px 24px 0px rgba(15, 23, 42, 0.06);
@@ -2472,8 +2594,12 @@ const runTest = async () => {
   border: 1px solid #E2E8F0;
 
   &.is-empty {
+    flex: none;
     gap: 0;
     padding: 0;
+    height: 560px;
+    min-height: 560px;
+    max-height: 560px;
     background: #0F172A;
     box-shadow: none;
     border-color: transparent;
@@ -2486,6 +2612,7 @@ const runTest = async () => {
   align-items: center;
   justify-content: space-between;
   gap: 12px;
+  flex-shrink: 0;
   padding-bottom: 16px;
   border-bottom: 1px solid #E2E8F0;
 }
@@ -2603,6 +2730,7 @@ const runTest = async () => {
   display: flex;
   align-items: center;
   gap: 14px;
+  flex-shrink: 0;
   margin-top: 0;
   padding: 14px 16px;
   background: #F8FAFC;
@@ -2659,6 +2787,7 @@ const runTest = async () => {
 .otp-result-tabs {
   display: flex;
   gap: 24px;
+  flex-shrink: 0;
   margin: 0;
   border-bottom: 1px solid #E2E8F0;
 }
@@ -2696,8 +2825,9 @@ const runTest = async () => {
 
 .otp-result-code {
   box-sizing: border-box;
-  flex: none;
-  height: 418px;
+  flex: 1 1 418px;
+  min-height: 0;
+  height: auto;
   margin: 0;
   padding: 12px;
   overflow: auto;
@@ -2728,6 +2858,7 @@ const runTest = async () => {
   align-items: center;
   justify-content: center;
   align-self: center;
+  flex-shrink: 0;
   margin: 0;
   padding: 10px 20px;
   border-radius: 8px;
@@ -2843,8 +2974,14 @@ const runTest = async () => {
   }
 
   .otp-result-card {
-    min-height: 480px;
+    min-height: min(480px, 100%);
     padding: 12px;
+
+    &.is-empty {
+      height: 560px;
+      min-height: 560px;
+      max-height: 560px;
+    }
   }
 
   .otp-result-code {
@@ -2874,6 +3011,12 @@ const runTest = async () => {
 
   .otp-result-card {
     min-height: 360px;
+
+    &.is-empty {
+      height: 560px;
+      min-height: 560px;
+      max-height: 560px;
+    }
   }
 }
 </style>

+ 11 - 3
src/pages/mcp/serverDetail.vue

@@ -326,7 +326,7 @@
       <div class="mcp-server-side-card mcp-server-side-card-related">
         <div class="mcp-server-related-head">
           <icon-svg name="related_service" size="16px" class="mcp-server-related-head-icon" />
-          <span class="mcp-server-related-head-title">相关服务</span>
+          <span class="mcp-server-related-head-title">热门服务</span>
         </div>
         <div class="mcp-server-related-list">
           <button
@@ -339,7 +339,11 @@
             <div class="mcp-server-related-item-top">
               <span
                 class="mcp-server-related-dot"
-                :class="item.dot === 'yellow' ? 'mcp-server-related-dot--yellow' : 'mcp-server-related-dot--blue'"
+                :class="{
+                  'mcp-server-related-dot--yellow': item.dot === 'yellow',
+                  'mcp-server-related-dot--purple': item.dot === 'purple',
+                  'mcp-server-related-dot--blue': item.dot !== 'yellow' && item.dot !== 'purple',
+                }"
               ></span>
               <span class="mcp-server-related-name">{{ item.label }}</span>
               <icon-svg name="related_service_redirection" size="12px" class="mcp-server-related-arrow" />
@@ -523,7 +527,7 @@ const mapServerDetail = (data) => {
     id: String(item.id || ""),
     label: item.name || "",
     desc: item.describe || item.remark || "",
-    dot: index % 2 === 0 ? "blue" : "yellow",
+    dot: index % 3 === 0 ? "blue" : index % 3 === 1 ? "yellow" : "purple",
   }));
 
   return {
@@ -2127,6 +2131,10 @@ const createToken = async () => {
   background: #F59E0B;
 }
 
+.mcp-server-related-dot--purple {
+  background: #7C3AED;
+}
+
 .mcp-server-related-name {
   flex: 1;
   min-width: 0;