Quellcode durchsuchen

修改页面刷新失效问题

肖栋 vor 2 Wochen
Ursprung
Commit
7676ef8c62

+ 23 - 0
deploy/nginx.conf

@@ -0,0 +1,23 @@
+# History 模式 SPA 回退:刷新 /mcp/guide、/mcp/online 等路由时返回 index.html
+# 使用方式:将 root 改为实际静态资源目录后,include 或复制到 Nginx 配置中
+
+server {
+  listen 80;
+  server_name _;
+
+  # 改为构建产物目录,例如 /usr/share/nginx/html 或项目 dist 路径
+  root /usr/share/nginx/html;
+  index index.html;
+
+  # 真实静态文件(js/css/图片)优先按文件返回,避免被回退成 HTML
+  location /assets/ {
+    try_files $uri =404;
+    expires 7d;
+    add_header Cache-Control "public";
+  }
+
+  # 前端路由:文件不存在时回退到 index.html
+  location / {
+    try_files $uri $uri/ /index.html;
+  }
+}

+ 1 - 0
src/assets/icons/close-icon.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="16" height="16" viewBox="0 0 16 16"><path d="M3.7062500453674314,2.29375052220459C3.3156250453674314,1.90312552420459,2.6812500953674316,1.90312552420459,2.2906250953674316,2.29375052220459C1.9000000953674316,2.68437552220459,1.9000000953674316,3.31875042220459,2.2906250953674316,3.70937542220459L6.584374945367432,8.00000052220459L2.2937501053674314,12.29374982220459C1.9031251073674316,12.68437482220459,1.9031251073674316,13.31874982220459,2.2937501053674314,13.70937482220459C2.6843751053674314,14.09999982220459,3.3187500453674317,14.09999982220459,3.7093750453674317,13.70937482220459L8.000000045367432,9.41562512220459L12.293750345367432,13.70624982220459C12.684375345367432,14.09687482220459,13.318750345367432,14.09687482220459,13.709375345367432,13.70624982220459C14.100000345367432,13.31562482220459,14.100000345367432,12.68125082220459,13.709375345367432,12.29062582220459L9.415625145367432,8.00000052220459L13.706250345367431,3.70625042220459C14.096875345367431,3.31562542220459,14.096875345367431,2.68125057220459,13.706250345367431,2.29062557220459C13.315625345367431,1.9000005722045898,12.681251345367432,1.9000005722045898,12.290626345367432,2.29062557220459L8.000000045367432,6.58437542220459L3.7062500453674314,2.29375052220459Z" fill="#86909C" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/download_JSON.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="12" height="12" viewBox="0 0 12 12"><path d="M6.800000195367431,0.80000001C6.800000195367431,0.35749999,6.442500095367432,0,5.999999995367432,0C5.557500395367431,0,5.200000295367432,0.35750002,5.200000295367432,0.80000001L5.200000295367432,6.0675001L4.165000195367432,5.0324998C3.8525001953674316,4.7199998,3.3450002953674316,4.7199998,3.0325002953674316,5.0324998C2.7200002953674316,5.3449998,2.7200002953674316,5.8525,3.0325002953674316,6.165L5.432500395367431,8.5649996C5.745000395367431,8.8774996,6.252500495367432,8.8774996,6.565000495367432,8.5649996L8.965000595367432,6.1649995C9.277500595367432,5.8524995,9.277500595367432,5.3449993,8.965000595367432,5.0324993C8.652500595367432,4.7199993,8.145000495367432,4.7199993,7.832500495367432,5.0324993L6.800000195367431,6.0675001L6.800000195367431,0.80000001ZM2.0000000953674317,8C1.1175001253674317,8,0.40000009536743164,8.7175007,0.40000009536743164,9.6000004L0.40000009536743164,10.400001C0.40000009536743164,11.2825,1.1175001253674317,12,2.0000000953674317,12L10.000000495367432,12C10.882500095367432,12,11.600000095367431,11.2825,11.600000095367431,10.400001L11.600000095367431,9.6000004C11.600000095367431,8.7175007,10.882500095367432,8,10.000000495367432,8L8.827500795367431,8L7.412500395367432,9.415C6.632500195367432,10.195001,5.365000295367432,10.195001,4.584999995367432,9.415L3.1725000953674316,8L2.0000000953674317,8ZM9.599999895367432,9.4000006C9.931371195367431,9.4000006,10.200000295367431,9.6686296,10.200000295367431,10C10.200000295367431,10.33137,9.931371195367431,10.6,9.599999895367432,10.6C9.268629595367432,10.6,9.000000495367432,10.33137,9.000000495367432,10C9.000000495367432,9.6686296,9.268629595367432,9.4000006,9.599999895367432,9.4000006Z" fill="#10B981" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/search-icon.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="13" height="13.000045776367188" viewBox="0 0 13 13.000045776367188"><path d="M10.562237498657227,5.2804961C10.562237498657227,6.4457593,10.183971498657227,7.5221677,9.546757698657226,8.3954811L12.760751498657227,11.612014C13.078088498657227,11.929351,13.078088498657227,12.444707,12.760751498657227,12.762045C12.443415498657227,13.079381,11.928058498657226,13.079381,11.610721498657227,12.762045L8.396726598657226,9.5455122C7.523413198657226,10.182726,6.447004798657226,10.560992,5.281741598657226,10.560992C2.3647751986572265,10.560992,0.0012454986572265625,8.1974621,0.0012454986572265625,5.2804961C0.0012454986572265625,2.3635299,2.3647751986572265,0,5.281741598657226,0C8.198707598657226,0,10.562237498657227,2.3635297,10.562237498657227,5.2804961ZM5.281741598657226,8.936224C7.300744098657226,8.936224,8.937469498657226,7.2994986,8.937469498657226,5.2804961C8.937469498657226,3.2614932,7.300744098657226,1.624768,5.281741598657226,1.624768C3.2627386986572264,1.624768,1.6260134986572266,3.2614932,1.6260134986572266,5.2804961C1.6260134986572266,7.2994986,3.2627386986572264,8.936224,5.281741598657226,8.936224Z" fill="#C9CDD4" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

BIN
src/assets/images/test_instruction_blue.jpg


BIN
src/assets/images/test_instruction_blue.png


BIN
src/assets/images/test_instruction_green.jpg


BIN
src/assets/images/test_instruction_green.png


BIN
src/assets/images/test_instruction_purple.jpg


BIN
src/assets/images/test_instruction_purple.png


+ 11 - 1
src/components/mcpSiderBar.vue

@@ -42,7 +42,7 @@
           type="button"
           class="mcp-sidebar-child"
           :class="{ 'is-active': child.key === activeKey }"
-          @click="navigateToServer(child)"
+          @click="handleChildClick(child)"
         >
           {{ child.label }}
         </button>
@@ -156,6 +156,16 @@ const navigateToServer = (child) => {
   });
 };
 
+const handleChildClick = (child) => {
+  if (child?.id) {
+    navigateToServer(child);
+    return;
+  }
+  if (child?.key) {
+    navigateTo(child.key);
+  }
+};
+
 const handleItemClick = (item) => {
   if (item.children?.length) {
     const index = expandedKeys.value.indexOf(item.key);

+ 3 - 3
src/components/pcHeader.vue

@@ -2,7 +2,7 @@
     <header class="desktop-header">
         <div class="header-inner">
             <div class="header-left">
-                <div class="brand-wrapper" @click="navigateToRoute('/dialog')">
+                <div class="brand-wrapper" @click="navigateToRoute('/')">
                     <icon-svg name="systemLogoCircle" size="40px" />
                     <div class="brand-text">
                         <span class="brand-title">环评云助手</span>
@@ -22,7 +22,7 @@
                     <button
                         type="button"
                         class="navigation-item active"
-                        @click="navigateToRoute('/mcp')"
+                        @click="navigateToRoute('/')"
                     >
                         MCP
                     </button>
@@ -241,7 +241,7 @@ const desktopNavigationItems = Object.freeze([
     { key: "shop", label: "云商城", type: "external", target: SHOP_URL },
     { key: "live", label: "直播", type: "external", target: LIVE_URL },
     { key: "academy", label: "云学苑", type: "external", target: HOME_WEBSITE_LEARN_URL },
-    { key: "approveUpload", label: "云助手AI", type: "route", target: "/mcp" },
+    { key: "approveUpload", label: "云助手AI", type: "external", target: "https://ai.eiacloud.com/#/dialog" },
 ]);
 
 const quickActionItems = Object.freeze([

+ 4 - 0
src/components/svgIcon.vue

@@ -91,4 +91,8 @@ const svgAttrs = computed(() => {
   overflow: hidden;
   outline: none;
 }
+
+.svg-icon use {
+  pointer-events: none;
+}
 </style>

+ 306 - 97
src/pages/guide/index.vue

@@ -46,7 +46,7 @@
                   <div class="home-guide-token-icons">
                     <icon-svg :name="guideTokenVisible ? 'eye_not_displayed' : 'eye_show'" size="24px"
                       class="home-guide-token-icon" @click="guideTokenVisible = !guideTokenVisible" />
-                    <icon-svg name="token_copy" size="14px" class="home-guide-token-icon" @click="copyText(guideToken)" />
+                    <icon-svg name="http_copy" size="14px" class="home-guide-token-icon" @click="copyText(guideToken)" />
                   </div>
                 </div>
                 <button type="button" class="home-guide-token-btn" @click="createToken">
@@ -163,7 +163,7 @@
                       </div>
                       <div class="home-guide-code-title">mcp.json</div>
                       <button type="button" class="home-guide-code-copy" @click="copyGuideMcpJson">
-                        <icon-svg name="" size="14px" class="home-guide-code-copy-icon" />
+                        <icon-svg name="copy_icon_white" size="14px" class="home-guide-code-copy-icon" />
                         <span>复制</span>
                       </button>
                     </div>
@@ -173,11 +173,28 @@
                       </div>
                       <pre class="home-guide-code-pre"><span class="code-punct">{</span>
   <span class="code-key">"mcpServers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-    <span class="code-key">"hpyzs-mcp"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-      <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"http://www.eiacloud.cn:9010/mcp"</span><span class="code-punct">,</span>
-      <span class="code-key">"type"</span><span class="code-punct">:</span> <span class="code-string">"http"</span><span class="code-punct">,</span>
+    <span class="code-key">"hpyzs-law-keyword"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-keyword"</span><span class="code-punct">,</span>
       <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-c61015e418b046d99e1bxxxxxxxx"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+      <span class="code-punct">}</span>
+    <span class="code-punct">},</span>
+    <span class="code-key">"hpyzs-law-semantic"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-semantic"</span><span class="code-punct">,</span>
+      <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+      <span class="code-punct">}</span>
+    <span class="code-punct">},</span>
+    <span class="code-key">"hpyzs-qa"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/qa"</span><span class="code-punct">,</span>
+      <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+      <span class="code-punct">}</span>
+    <span class="code-punct">},</span>
+    <span class="code-key">"hpyzs-emission"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/emission"</span><span class="code-punct">,</span>
+      <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">}</span>
   <span class="code-punct">}</span>
@@ -186,16 +203,16 @@
                   </div>
                 </div>
 
-                <div v-else-if="guideToolTab === 'ima'" class="home-guide-tool-body home-guide-ima-body">
+                <div v-else-if="guideToolTab === 'doubao'" class="home-guide-tool-body home-guide-doubao-body">
                   <div class="home-guide-ima-left">
                     <div class="home-guide-ima-alert">
                       <icon-svg name="ima_wps_warning" size="16px" class="home-guide-ima-alert-icon" />
                       <div class="home-guide-ima-alert-text">
-                        {{ guideImaContent.alert }}
+                        {{ guideDoubaoContent.alert }}
                       </div>
                     </div>
                     <div class="home-guide-ima-steps">
-                      <div v-for="step in guideImaContent.steps" :key="step.num" class="home-guide-ima-step">
+                      <div v-for="step in guideDoubaoContent.steps" :key="step.num" class="home-guide-ima-step">
                         <div class="home-guide-ima-step-num">{{ step.num }}</div>
                         <div class="home-guide-ima-step-title">{{ step.title }}</div>
                       </div>
@@ -203,35 +220,110 @@
                     <div class="home-guide-ima-notes">
                       <div class="home-guide-ima-notes-title">注意事项</div>
                       <ul class="home-guide-ima-notes-list">
-                        <li v-for="item in guideImaContent.notes" :key="item">
+                        <li v-for="item in guideDoubaoContent.notes" :key="item">
                           {{ item }}
                         </li>
                       </ul>
                     </div>
                   </div>
-                  <div class="home-guide-ima-right">
-                    <div class="home-guide-ima-panel home-guide-ima-panel--config">
-                      <div class="home-guide-ima-panel-title">【配置】</div>
-                      <div v-for="line in guideImaContent.configLines" :key="line" class="home-guide-ima-panel-line">
-                        {{ line }}
+                  <div class="home-guide-doubao-right">
+                    <img
+                      src="@/assets/images/doubao_img.png"
+                      alt="豆包自定义连接器示意"
+                      class="home-guide-doubao-img"
+                      width="608"
+                      height="472"
+                    />
+                  </div>
+                </div>
+
+                <div v-else-if="guideToolTab === 'qoderwork'" class="home-guide-tool-body">
+                  <div class="home-guide-tool-steps">
+                    <div class="home-guide-tool-step">
+                      <div class="home-guide-tool-step-num">1</div>
+                      <div class="home-guide-tool-step-copy">
+                        <div class="home-guide-tool-step-title">打开配置入口</div>
+                        <div class="home-guide-tool-step-desc">
+                          左侧「扩展」→「连接器」→ 右上角「+ 添加」→「粘贴 JSON 配置」→ 在「通过 JSON 导入」对话框粘贴下方全量 JSON → 点击「导入」2
+                        </div>
                       </div>
                     </div>
-                    <div class="home-guide-ima-panel home-guide-ima-panel--verify">
-                      <div class="home-guide-ima-panel-title">【验证】</div>
-                      <div class="home-guide-ima-panel-hint">
-                        {{ guideImaContent.verifyIntro }}
+                    <div class="home-guide-tool-step">
+                      <div class="home-guide-tool-step-num">2</div>
+                      <div class="home-guide-tool-step-copy">
+                        <div class="home-guide-tool-step-title">粘贴 MCP 配置</div>
+                        <div class="home-guide-tool-step-desc">
+                          将右侧「配置」中的 JSON 粘贴到客户端对应位置并保存。
+                        </div>
                       </div>
-                      <div class="home-guide-ima-quote">{{ guideImaContent.verifyQuote }}</div>
-                      <div class="home-guide-ima-panel-hint">
-                        {{ guideImaContent.verifyResult }}
+                    </div>
+                    <div class="home-guide-tool-step">
+                      <div class="home-guide-tool-step-num">3</div>
+                      <div class="home-guide-tool-step-copy">
+                        <div class="home-guide-tool-step-title">配置文件位置</div>
+                        <div class="home-guide-tool-step-desc">
+                          macOS:~/.qoderwork/mcp.json Windows:C:\Users\用户名\.qoderwork\mcp.json
+                        </div>
                       </div>
-                      <div class="home-guide-ima-check">
-                        <span class="home-guide-ima-check-marks">
-                          <span>√</span>
-                          <span>√</span>
-                        </span>
-                        <span class="home-guide-ima-check-text">{{ guideImaContent.verifyCheck }}</span>
+                    </div>
+                    <div class="home-guide-note">
+                      <div class="home-guide-note-head">
+                        <div class="home-guide-note-icon-box">
+                          <icon-svg name="WorkBuddy_attention" size="12px" class="home-guide-note-icon" />
+                        </div>
+                        <div class="home-guide-note-title">注意事项</div>
                       </div>
+                      <ul class="home-guide-note-list">
+                        <li>type 字段为 "streamableHttp"(H 大写),与 WorkBuddy 一致</li>
+                        <li>不需要在 URL 末尾添加 /mcp 后缀</li>
+                        <li>将 YOUR_ACCESS_TOKEN 替换为页面「获取 Token」生成的真实 Token</li>
+                        <li>导入后可在「连接器 → 已安装」查看状态;改配置后需完全退出并重启 QoderWork</li>
+                      </ul>
+                    </div>
+                  </div>
+                  <div class="home-guide-code">
+                    <div class="home-guide-code-bar">
+                      <div class="home-guide-code-dots">
+                        <span></span>
+                        <span></span>
+                        <span></span>
+                      </div>
+                      <div class="home-guide-code-title">mcp.json</div>
+                      <button type="button" class="home-guide-code-copy" @click="copyGuideQoderworkJson">
+                        <icon-svg name="copy_icon_white" size="14px" class="home-guide-code-copy-icon" />
+                        <span>复制</span>
+                      </button>
+                    </div>
+                    <div class="home-guide-code-body">
+                      <div class="home-guide-code-lines">
+                        <span v-for="line in guideQoderworkLines" :key="line">{{ line }}</span>
+                      </div>
+                      <pre class="home-guide-code-pre"><span class="code-punct">{</span>
+  <span class="code-key">"hpyzs-law-keyword"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+    <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-keyword"</span><span class="code-punct">,</span>
+    <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+    <span class="code-punct">}</span>
+  <span class="code-punct">},</span>
+  <span class="code-key">"hpyzs-law-semantic"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+    <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-semantic"</span><span class="code-punct">,</span>
+    <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+    <span class="code-punct">}</span>
+  <span class="code-punct">},</span>
+  <span class="code-key">"hpyzs-qa"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+    <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/qa"</span><span class="code-punct">,</span>
+    <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+    <span class="code-punct">}</span>
+  <span class="code-punct">},</span>
+  <span class="code-key">"hpyzs-emission"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+    <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/emission"</span><span class="code-punct">,</span>
+    <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
+    <span class="code-punct">}</span>
+  <span class="code-punct">}</span>
+<span class="code-punct">}</span></pre>
                     </div>
                   </div>
                 </div>
@@ -293,7 +385,7 @@
                     <icon-svg name="systemLogoCircle" size="38px" class="home-guide-cli-logo-icon" />
                     <div class="home-guide-cli-head-copy">
                       <div class="home-guide-cli-name">
-                        <span class="home-guide-cli-pkg">@eiacloud/mcp-cli</span>
+                        <span class="home-guide-cli-pkg">hpyzs-mcp</span>
                         <span class="home-guide-cli-sep">·</span>
                         <span class="home-guide-cli-alias">环评云助手命令行工具</span>
                       </div>
@@ -321,15 +413,15 @@
                           <code class="home-guide-cli-command-text">{{ command.text }}</code>
                         </div>
                         <button type="button" class="home-guide-cli-command-copy" @click="copyText(command.text)">
-                          <icon-svg name="" size="12px" class="home-guide-cli-command-copy-icon" />
+                          <icon-svg name="copy_icon_violet" size="12px" class="home-guide-cli-command-copy-icon" />
                           <span>复制</span>
                         </button>
                       </div>
                     </div>
                     <div class="home-guide-cli-footer">
-                      <span class="home-guide-cli-footer-tip">初始化命令已自动填入真实 Token</span>
+                      <span class="home-guide-cli-footer-tip">{{ guideCliFooterTip }}</span>
                       <button type="button" class="home-guide-cli-copy-all" @click="copyAllCliCommands">
-                        <icon-svg name="" size="12px" class="home-guide-cli-copy-all-icon" />
+                        <icon-svg name="copy_icon_white" size="12px" class="home-guide-cli-copy-all-icon" />
                         <span>全部复制</span>
                       </button>
                     </div>
@@ -360,7 +452,8 @@
                   <div v-for="(question, questionIndex) in card.questions" :key="`${card.title}-${questionIndex}`"
                     class="home-guide-verify-item">
                     <span>{{ question }}</span>
-                    <icon-svg name="Connection_Copy" size="13px" />
+                    <icon-svg name="Connection_Copy" size="13px" class="home-guide-verify-copy"
+                      @click="copyText(question)" />
                   </div>
                 </div>
               </div>
@@ -372,6 +465,7 @@
 </template>
 <script setup>
 import { computed, onMounted, ref } from "vue";
+import { ElMessage } from "element-plus";
 import { getMcpToken, createMcpToken } from "@/api/mcp";
 import globalStore from "@/store/modules/global";
 
@@ -430,40 +524,29 @@ const guideAccessTab = ref("mcp");
 const guideToolTab = ref("workbuddy");
 const guideTools = [
   { key: "workbuddy", label: "WorkBuddy", icon: true },
-  { key: "ima", label: "IMA", icon: false },
+  { key: "doubao", label: "豆包", icon: false },
+  { key: "qoderwork", label: "Qoderwork", icon: false },
   { key: "wps", label: "WPS Comate", icon: false },
 ];
-const guideImaContent = {
-  alert:
-    "仅支持内置连接:IMA 只能通过内置「环评云助手」连接接入,不支持粘贴 JSON 或自定义 MCP 配置。",
+const guideDoubaoContent = {
+  alert: "豆包只能通过新建自定义连接器接入,不支持直接粘贴 JSON 配置。",
   steps: [
     {
       num: 1,
-      title: "打开 ima → 发现 → MCP → 找到「环评云助手」→ 点击「连接」",
+      title:
+        "打开 豆包 → 找到「 技能·连接器·伙伴 」→ 点击「新建」→ 选择「 新建自定义连接器」 ",
     },
     {
       num: 2,
-      title: "按提示完成登录授权并启用连接即可使用",
+      title: "输入服务器名称、 URL 链接、自定义 Headers → 点击保存完成链接",
     },
     {
       num: 3,
       title:
-        "验证是否成功——在对话中发送「用环评云助手检索《大气污染物综合排放标准》中关于 VOCs 排放限值的规定」",
+        "验证是否成功——在对话中发送「用环评云助手MCP检索《大气污染物综合排放标准》中关于 VOCs 排放限值的规定」",
     },
   ],
-  notes: [
-    "仅支持内置连接,无需手动粘贴 JSON 或自定义 MCP 配置",
-    "首次使用需完成环评云助手账号登录授权,授权后连接长期有效",
-  ],
-  configLines: [
-    "# 本平台仅支持内置「环评云助手」连接",
-    "# 发现 → MCP → 环评云助手 → 一键连接并完成授权即可使用",
-  ],
-  verifyIntro: "# 在对话框中发一句:",
-  verifyQuote:
-    "「用环评云助手检索《大气污染物综合排放标准》中关于 VOCs 排放限值的规定」",
-  verifyResult: "# → 看到法规检索工具运行成功 → 返回限值 = 接入成功",
-  verifyCheck: "返回的是权威法规数据,非网络搜索",
+  notes: ["自定义 Headers 内需要填写页面「获取 Token」生成的真实 Token"],
 };
 const guideWpsContent = {
   alert:
@@ -497,26 +580,106 @@ const guideWpsContent = {
   verifyResult: " # → 看到法规检索工具运行成功 → 返回限值 = 接入成功",
   verifyCheck: "返回的是权威法规数据,非网络搜索",
 };
-const guideMcpJson = `{
+const guideAccessToken = computed(
+  () => guideToken.value || "YOUR_ACCESS_TOKEN",
+);
+const guideMcpJson = computed(() => {
+  const token = guideAccessToken.value;
+  return `{
   "mcpServers": {
-    "hpyzs-mcp": {
-      "url": "http://www.eiacloud.cn:9010/mcp",
-      "type": "http",
+    "hpyzs-law-keyword": {
+      "url": "https://mcp.eiacloud.com/law-keyword",
+      "headers": {
+        "Authorization": "Bearer ${token}"
+      }
+    },
+    "hpyzs-law-semantic": {
+      "url": "https://mcp.eiacloud.com/law-semantic",
       "headers": {
-        "Authorization": "Bearer sk-c61015e418b046d99e1bxxxxxxxx"
+        "Authorization": "Bearer ${token}"
       }
+    },
+    "hpyzs-qa": {
+      "url": "https://mcp.eiacloud.com/qa",
+      "headers": {
+        "Authorization": "Bearer ${token}"
+      }
+    },
+    "hpyzs-emission": {
+      "url": "https://mcp.eiacloud.com/emission",
+      "headers": {
+        "Authorization": "Bearer ${token}"
+      }
+    }
+  }
+}`;
+});
+const guideMcpLines = computed(() =>
+  Array.from({ length: guideMcpJson.value.split("\n").length }, (_, i) => i + 1),
+);
+const guideQoderworkJson = computed(() => {
+  const token = guideAccessToken.value;
+  return `{
+  "hpyzs-law-keyword": {
+    "url": "https://mcp.eiacloud.com/law-keyword",
+    "headers": {
+      "Authorization": "Bearer ${token}"
+    }
+  },
+  "hpyzs-law-semantic": {
+    "url": "https://mcp.eiacloud.com/law-semantic",
+    "headers": {
+      "Authorization": "Bearer ${token}"
+    }
+  },
+  "hpyzs-qa": {
+    "url": "https://mcp.eiacloud.com/qa",
+    "headers": {
+      "Authorization": "Bearer ${token}"
+    }
+  },
+  "hpyzs-emission": {
+    "url": "https://mcp.eiacloud.com/emission",
+    "headers": {
+      "Authorization": "Bearer ${token}"
     }
   }
 }`;
-const guideMcpLines = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
+});
+const guideQoderworkLines = computed(() =>
+  Array.from(
+    { length: guideQoderworkJson.value.split("\n").length },
+    (_, i) => i + 1,
+  ),
+);
 const copyText = async (text) => {
+  if (!text) {
+    ElMessage.warning("暂无内容可复制");
+    return;
+  }
   try {
-    await navigator.clipboard.writeText(text);
+    if (navigator.clipboard?.writeText) {
+      await navigator.clipboard.writeText(text);
+    } else {
+      const textarea = document.createElement("textarea");
+      textarea.value = text;
+      textarea.setAttribute("readonly", "");
+      textarea.style.position = "fixed";
+      textarea.style.left = "-9999px";
+      document.body.appendChild(textarea);
+      textarea.select();
+      const ok = document.execCommand("copy");
+      document.body.removeChild(textarea);
+      if (!ok) throw new Error("execCommand copy failed");
+    }
+    ElMessage.success("复制成功");
   } catch (error) {
     console.error(error);
+    ElMessage.error("复制失败");
   }
 };
-const copyGuideMcpJson = () => copyText(guideMcpJson);
+const copyGuideMcpJson = () => copyText(guideMcpJson.value);
+const copyGuideQoderworkJson = () => copyText(guideQoderworkJson.value);
 const guideCliSteps = [
   {
     num: 1,
@@ -526,7 +689,7 @@ const guideCliSteps = [
   {
     num: 2,
     title: "npm 全局安装环评云助手 CLI",
-    desc: "npm install -g @eiacloud/mcp-cli",
+    desc: "npm install -g hpyzs-mcp",
   },
   {
     num: 3,
@@ -539,52 +702,60 @@ const guideCliSteps = [
     desc: "不经过 LLM,适合自动化流程与数据验证。",
   },
 ];
-const guideCliCommands = [
-  {
-    label: "安装 CLI",
-    text: "npm install -g @eiacloud/mcp-cli",
-  },
-  {
-    label: "绑定 Token",
-    text: "$ eiacloud-mcp init --authorization Bearer YOUR_ACCESS_TOKEN",
-  },
-  {
-    label: "法规检索",
-    text: '$ eiacloud-mcp regulation-search "大气污染物排放标准"',
-  },
-  {
-    label: "标准查询",
-    text: '$ eiacloud-mcp standard-search "地下水质量标准"',
-  },
-];
+const guideCliCommands = computed(() => {
+  const token = guideAccessToken.value;
+  return [
+    {
+      label: "安装 CLI",
+      text: "npm install -g hpyzs-mcp",
+    },
+    {
+      label: "绑定 Token",
+      text: `hpyzs-mcp init --authorization "Bearer ${token}"`,
+    },
+    {
+      label: "标准导则关键词查询",
+      text: 'hpyzs-mcp law-keyword search_keyword_standard --name "地表水环境治疗标准"',
+    },
+    {
+      label: "法规政策关键词查询",
+      text: 'hpyzs-mcp law-keyword search_keyword_policy --name "噪声源分类指导目录"',
+    },
+  ];
+});
+const guideCliFooterTip = computed(() =>
+  guideToken.value
+    ? "初始化命令已自动填入真实 Token"
+    : "将 YOUR_ACCESS_TOKEN 替换为页面「获取 Token」生成的真实 Token",
+);
 const copyAllCliCommands = () =>
-  copyText(guideCliCommands.map((item) => item.text).join("\n"));
+  copyText(guideCliCommands.value.map((item) => item.text).join("\n"));
 const guideVerifyCards = [
   {
     icon: "environmental_legislation",
-    title: "环境法规",
+    title: "政策法规",
     questions: [
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
+      "大气污染防治法第118条餐饮油烟怎么罚?",
+      "环办政法函〔2017〕40号答复了什么?",
+      "大气污染防治法2018修订何时施行?",
     ],
   },
   {
-    icon:"Standard_guidelines",
+    icon: "Standard_guidelines",
     title: "标准导则",
     questions: [
-      "建设项目环境影响评价分类管理名录中化工项目如何分类?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
+      "GB16297表2二氧化硫二级浓度?",
+      "GB16297排气筒最低高度要求?",
+      "GB16297颗粒物无组织监控限值?",
     ],
   },
   {
-    icon:"Policy_QA",
-    title: "政策问答",
+    icon: "Policy_QA",
+    title: "问答检索",
     questions: [
-      "建设项目环境影响评价分类管理名录中化工项目如何分类?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
-      "大气污染物综合排放标准 GB16297 相关限值要求?",
+      "重大变动如何重新报批环评?",
+      "排污许可证内容变更如何重新办理?",
+      "环评批复几年不开工作废?",
     ],
   },
 ];
@@ -1118,6 +1289,7 @@ const guideVerifyCards = [
   display: flex;
   gap: 20px;
   margin-top: 20px;
+  align-items: stretch;
 }
 
 .home-guide-ima-body {
@@ -1132,6 +1304,27 @@ const guideVerifyCards = [
   flex-direction: column;
 }
 
+.home-guide-doubao-body {
+  align-items: flex-start;
+}
+
+.home-guide-doubao-body .home-guide-ima-left {
+  flex: 1;
+  min-width: 0;
+}
+
+.home-guide-doubao-right {
+  flex: 0 0 auto;
+}
+
+.home-guide-doubao-img {
+  display: block;
+  width: 608px;
+  height: 472px;
+  max-width: 100%;
+  object-fit: contain;
+}
+
 .home-guide-ima-right {
   gap: 16px;
 }
@@ -1741,21 +1934,26 @@ const guideVerifyCards = [
 }
 
 .home-guide-code {
-  display: flex;
-  flex-direction: column;
+  position: relative;
   overflow: hidden;
   border-radius: 12px;
   background: #1E293B;
   box-shadow: 0px 8px 24px 0px rgba(15, 23, 42, 0.18);
+  /* 高度由左侧步骤区撑开,代码内容不撑高整块区域 */
 }
 
 .home-guide-code-bar {
-  position: relative;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 1;
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 12px 14px;
   border-bottom: 1px solid rgba(148, 163, 184, 0.16);
+  background: #1E293B;
 }
 
 .home-guide-code-dots {
@@ -1817,6 +2015,11 @@ const guideVerifyCards = [
 }
 
 .home-guide-code-body {
+  position: absolute;
+  top: 49px;
+  right: 0;
+  bottom: 0;
+  left: 0;
   display: flex;
   gap: 14px;
   padding: 14px 16px 18px;
@@ -1995,4 +2198,10 @@ const guideVerifyCards = [
   overflow: hidden;
   text-overflow: ellipsis;
 }
+
+.home-guide-verify-copy {
+  flex-shrink: 0;
+  color: #94A3B8;
+  cursor: pointer;
+}
 </style>

+ 97 - 55
src/pages/home/index.vue

@@ -140,7 +140,10 @@
               </div>
               <div class="home-scenes-card-footer">
                 <span class="home-scenes-card-stat">近30天请求 1777万</span>
-                <span class="home-scenes-card-link">查看详情</span>
+                <router-link
+                  class="home-scenes-card-link"
+                  :to="{ name: 'mcpServer', query: { id: '2090355139104735232' } }"
+                >查看详情</router-link>
               </div>
             </div>
             <div class="home-scenes-card">
@@ -160,7 +163,10 @@
               </div>
               <div class="home-scenes-card-footer">
                 <span class="home-scenes-card-stat">近30天请求 1777万</span>
-                <span class="home-scenes-card-link">查看详情</span>
+                <router-link
+                  class="home-scenes-card-link"
+                  :to="{ name: 'mcpServer', query: { id: '2090355139108929536' } }"
+                >查看详情</router-link>
               </div>
             </div>
             <div class="home-scenes-card">
@@ -179,7 +185,10 @@
               </div>
               <div class="home-scenes-card-footer">
                 <span class="home-scenes-card-stat">近30天请求 1777万</span>
-                <span class="home-scenes-card-link">查看详情</span>
+                <router-link
+                  class="home-scenes-card-link"
+                  :to="{ name: 'mcpServer', query: { id: '2090355139108929537' } }"
+                >查看详情</router-link>
               </div>
             </div>
           </div>
@@ -259,7 +268,7 @@
                   <div class="home-guide-token-icons">
                     <icon-svg :name="guideTokenVisible ? 'eye_not_displayed' : 'eye_show'" size="24px"
                       class="home-guide-token-icon" @click="guideTokenVisible = !guideTokenVisible" />
-                    <icon-svg name="copy_close_icon" size="14px" class="home-guide-token-icon"
+                    <icon-svg name="http_copy" size="14px" class="home-guide-token-icon"
                       @click="copyText(guideToken)" />
                   </div>
                 </div>
@@ -390,25 +399,25 @@
     <span class="code-key">"hpyzs-law-keyword"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
       <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-keyword"</span><span class="code-punct">,</span>
       <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">},</span>
     <span class="code-key">"hpyzs-law-semantic"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
       <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-semantic"</span><span class="code-punct">,</span>
       <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">},</span>
     <span class="code-key">"hpyzs-qa"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
       <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/qa"</span><span class="code-punct">,</span>
       <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">},</span>
     <span class="code-key">"hpyzs-emission"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
       <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/emission"</span><span class="code-punct">,</span>
       <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">}</span>
   <span class="code-punct">}</span>
@@ -516,25 +525,25 @@
   <span class="code-key">"hpyzs-law-keyword"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
     <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-keyword"</span><span class="code-punct">,</span>
     <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
     <span class="code-punct">}</span>
   <span class="code-punct">},</span>
   <span class="code-key">"hpyzs-law-semantic"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
     <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/law-semantic"</span><span class="code-punct">,</span>
     <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
     <span class="code-punct">}</span>
   <span class="code-punct">},</span>
   <span class="code-key">"hpyzs-qa"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
     <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/qa"</span><span class="code-punct">,</span>
     <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
     <span class="code-punct">}</span>
   <span class="code-punct">},</span>
   <span class="code-key">"hpyzs-emission"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
     <span class="code-key">"url"</span><span class="code-punct">:</span> <span class="code-string">"https://mcp.eiacloud.com/emission"</span><span class="code-punct">,</span>
     <span class="code-key">"headers"</span><span class="code-punct">:</span> <span class="code-punct">{</span>
-      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
+      <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer {{ guideAccessToken }}"</span>
     <span class="code-punct">}</span>
   <span class="code-punct">}</span>
 <span class="code-punct">}</span></pre>
@@ -599,7 +608,7 @@
                     <icon-svg name="systemLogoCircle" size="38px" class="home-guide-cli-logo-icon" />
                     <div class="home-guide-cli-head-copy">
                       <div class="home-guide-cli-name">
-                        <span class="home-guide-cli-pkg">@eiacloud/mcp-cli</span>
+                        <span class="home-guide-cli-pkg">hpyzs-mcp</span>
                         <span class="home-guide-cli-sep">·</span>
                         <span class="home-guide-cli-alias">环评云助手命令行工具</span>
                       </div>
@@ -747,9 +756,9 @@ import { useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
 import { getMcpToken, createMcpToken } from "@/api/mcp";
 import globalStore from "@/store/modules/global";
-import demoImgGreen from "@/assets/images/test_instruction_green.png";
-import demoImgBlue from "@/assets/images/test_instruction_blue.png";
-import demoImgPurple from "@/assets/images/test_instruction_purple.png";
+import demoImgGreen from "@/assets/images/test_instruction_green.jpg";
+import demoImgBlue from "@/assets/images/test_instruction_blue.jpg";
+import demoImgPurple from "@/assets/images/test_instruction_purple.jpg";
 
 const router = useRouter();
 const global = globalStore();
@@ -799,6 +808,10 @@ const fetchMcpToken = async () => {
 };
 
 const createToken = async () => {
+  if (guideToken.value) {
+    ElMessage.warning("Token 已存在,无需重复获取");
+    return;
+  }
   try {
     const res = await createMcpToken();
     applyToken(res?.data);
@@ -870,64 +883,77 @@ const guideWpsContent = {
   verifyResult: " # → 看到法规检索工具运行成功 → 返回限值 = 接入成功",
   verifyCheck: "返回的是权威法规数据,非网络搜索",
 };
-const guideMcpJson = `{
+const guideAccessToken = computed(
+  () => guideToken.value || "YOUR_ACCESS_TOKEN",
+);
+const guideMcpJson = computed(() => {
+  const token = guideAccessToken.value;
+  return `{
   "mcpServers": {
     "hpyzs-law-keyword": {
       "url": "https://mcp.eiacloud.com/law-keyword",
       "headers": {
-        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+        "Authorization": "Bearer ${token}"
       }
     },
     "hpyzs-law-semantic": {
       "url": "https://mcp.eiacloud.com/law-semantic",
       "headers": {
-        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+        "Authorization": "Bearer ${token}"
       }
     },
     "hpyzs-qa": {
       "url": "https://mcp.eiacloud.com/qa",
       "headers": {
-        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+        "Authorization": "Bearer ${token}"
       }
     },
     "hpyzs-emission": {
       "url": "https://mcp.eiacloud.com/emission",
       "headers": {
-        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+        "Authorization": "Bearer ${token}"
       }
     }
   }
 }`;
-const guideMcpLines = Array.from({ length: guideMcpJson.split("\n").length }, (_, i) => i + 1);
-const guideQoderworkJson = `{
+});
+const guideMcpLines = computed(() =>
+  Array.from({ length: guideMcpJson.value.split("\n").length }, (_, i) => i + 1),
+);
+const guideQoderworkJson = computed(() => {
+  const token = guideAccessToken.value;
+  return `{
   "hpyzs-law-keyword": {
     "url": "https://mcp.eiacloud.com/law-keyword",
     "headers": {
-      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      "Authorization": "Bearer ${token}"
     }
   },
   "hpyzs-law-semantic": {
     "url": "https://mcp.eiacloud.com/law-semantic",
     "headers": {
-      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      "Authorization": "Bearer ${token}"
     }
   },
   "hpyzs-qa": {
     "url": "https://mcp.eiacloud.com/qa",
     "headers": {
-      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      "Authorization": "Bearer ${token}"
     }
   },
   "hpyzs-emission": {
     "url": "https://mcp.eiacloud.com/emission",
     "headers": {
-      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      "Authorization": "Bearer ${token}"
     }
   }
 }`;
-const guideQoderworkLines = Array.from(
-  { length: guideQoderworkJson.split("\n").length },
-  (_, i) => i + 1,
+});
+const guideQoderworkLines = computed(() =>
+  Array.from(
+    { length: guideQoderworkJson.value.split("\n").length },
+    (_, i) => i + 1,
+  ),
 );
 const copyText = async (text) => {
   if (!text) {
@@ -955,8 +981,8 @@ const copyText = async (text) => {
     ElMessage.error("复制失败");
   }
 };
-const copyGuideMcpJson = () => copyText(guideMcpJson);
-const copyGuideQoderworkJson = () => copyText(guideQoderworkJson);
+const copyGuideMcpJson = () => copyText(guideMcpJson.value);
+const copyGuideQoderworkJson = () => copyText(guideQoderworkJson.value);
 const guideCliSteps = [
   {
     num: 1,
@@ -966,7 +992,7 @@ const guideCliSteps = [
   {
     num: 2,
     title: "npm 全局安装环评云助手 CLI",
-    desc: "npm install -g @eiacloud/mcp-cli",
+    desc: "npm install -g hpyzs-mcp",
   },
   {
     num: 3,
@@ -980,31 +1006,30 @@ const guideCliSteps = [
   },
 ];
 const guideCliCommands = computed(() => {
-  const token = guideToken.value;
-  const authPart = token ? `Bearer ${token}` : "Bearer YOUR_TOKEN";
+  const token = guideAccessToken.value;
   return [
     {
       label: "安装 CLI",
-      text: "npm install -g @eiacloud/mcp-cli",
+      text: "npm install -g hpyzs-mcp",
     },
     {
       label: "绑定 Token",
-      text: `$ eiacloud-mcp init --authorization ${authPart}`,
+      text: `hpyzs-mcp init --authorization "Bearer ${token}"`,
     },
     {
-      label: "法规检索",
-      text: '$ eiacloud-mcp regulation-search "大气污染物排放标准"',
+      label: "标准导则关键词查询",
+      text: 'hpyzs-mcp law-keyword search_keyword_standard --name "地表水环境治疗标准"',
     },
     {
-      label: "标准查询",
-      text: '$ eiacloud-mcp standard-search "地下水质量标准"',
+      label: "法规政策关键词查询",
+      text: 'hpyzs-mcp law-keyword search_keyword_policy --name "噪声源分类指导目录"',
     },
   ];
 });
 const guideCliFooterTip = computed(() =>
   guideToken.value
     ? "初始化命令已自动填入真实 Token"
-    : "将 YOUR_TOKEN 替换为页面「获取 Token」生成的真实 Token",
+    : "将 YOUR_ACCESS_TOKEN 替换为页面「获取 Token」生成的真实 Token",
 );
 const copyAllCliCommands = () =>
   copyText(guideCliCommands.value.map((item) => item.text).join("\n"));
@@ -1228,6 +1253,7 @@ const guideVerifyCards = [
   display: flex;
   flex-direction: column;
   align-items: center;
+  gap: 20px;
 }
 
 .home-overview-block:last-child {
@@ -1239,6 +1265,7 @@ const guideVerifyCards = [
   display: flex;
   flex-direction: column;
   align-items: center;
+  gap: 8px;
 }
 
 .home-overview-title {
@@ -1572,6 +1599,21 @@ const guideVerifyCards = [
   color: #1678FF;
   line-height: 16px;
   text-align: left;
+  text-decoration: none;
+  cursor: pointer;
+
+  &:link,
+  &:visited,
+  &:active {
+    color: #1678FF;
+    text-decoration: none;
+  }
+
+  &:hover {
+    color: #1678FF;
+    opacity: 0.8;
+    text-decoration: none;
+  }
 }
 
 .home-scenes-lower {
@@ -1586,7 +1628,7 @@ const guideVerifyCards = [
 .home-scenes-demo {
   display: grid;
   grid-template-columns: 1fr 1fr;
-  align-items: stretch;
+  align-items: start;
   gap: 40px;
 }
 
@@ -1599,24 +1641,17 @@ const guideVerifyCards = [
 }
 
 .home-scenes-demo-preview {
-  position: relative;
+  align-self: end;
   min-width: 0;
-  height: 100%;
   overflow: hidden;
   border-radius: 10px;
 }
 
 .home-scenes-demo-preview-img {
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
   display: block;
   width: 100%;
-  height: 100%;
-  object-fit: cover;
-  object-position: top center;
+  height: auto;
+  object-fit: contain;
 }
 
 .home-scenes-demo-badge {
@@ -2066,9 +2101,9 @@ const guideVerifyCards = [
 .home-guide-features {
   display: flex;
   align-items: center;
-  gap: 50px;
+  gap: 40px;
   margin-top: 16px;
-  padding: 30px 160px;
+  padding: 30px 40px;
   background: #F8FAFC;
   border-radius: 12px;
   border: 1px solid #E2E8F0;
@@ -2082,6 +2117,12 @@ const guideVerifyCards = [
   min-width: 0;
 }
 
+.home-guide-feature > :first-child {
+  flex-shrink: 0;
+  width: 38px;
+  height: 38px;
+}
+
 .home-guide-feature-title {
   font-family: PingFang SC, PingFang SC;
   font-weight: 500;
@@ -2098,6 +2139,7 @@ const guideVerifyCards = [
   color: #64748B;
   line-height: 20px;
   text-align: left;
+  white-space: nowrap;
 }
 
 .home-guide-access-tabs {

+ 1 - 1
src/pages/login/index.vue

@@ -35,7 +35,7 @@ const onLoginClosed = () => {
     router.replace(String(redirect));
     return;
   }
-  router.replace({ name: "mcpAuth" });
+  router.replace({ name: "mcp" });
 };
 </script>
 

+ 211 - 44
src/pages/mcp/onlineTest.vue

@@ -1,7 +1,9 @@
 <template>
   <div class="otp-page">
     <div class="otp-breadcrumb">
-      <span>法规检索</span>
+      <button type="button" class="otp-breadcrumb-link" @click="goBackToService">
+        {{ breadcrumbServiceName }}
+      </button>
       <span class="otp-breadcrumb-sep">›</span>
       <span class="otp-breadcrumb-current">在线测试</span>
     </div>
@@ -115,18 +117,28 @@
                   <template v-else>暂无 Token</template>
                 </span>
                 <div v-if="headerToken" class="otp-token-actions">
-                  <icon-svg
-                    :name="tokenVisible ? 'eye_not_displayed' : 'eye_show'"
-                    size="24px"
-                    class="otp-token-icon"
+                  <button
+                    type="button"
+                    class="otp-token-action-btn"
                     @click="tokenVisible = !tokenVisible"
-                  />
-                  <icon-svg
-                    name="token_copy"
-                    size="14px"
-                    class="otp-token-icon"
-                    @click="copyText(`Bearer ${headerToken}`)"
-                  />
+                  >
+                    <icon-svg
+                      :name="tokenVisible ? 'eye_not_displayed' : 'eye_show'"
+                      size="24px"
+                      class="otp-token-icon"
+                    />
+                  </button>
+                  <button
+                    type="button"
+                    class="otp-token-action-btn"
+                    @click="copyHeaderToken"
+                  >
+                    <icon-svg
+                      name="token_copy"
+                      size="14px"
+                      class="otp-token-icon"
+                    />
+                  </button>
                 </div>
               </div>
               <div v-if="headerToken" class="otp-token-tip">
@@ -291,14 +303,24 @@
           <div class="otp-result-head">
             <div class="otp-result-title">响应结果</div>
             <div class="otp-result-actions">
-              <button type="button" class="otp-result-action" @click="copyText(responseJson)">
+              <button
+                type="button"
+                class="otp-result-action"
+                :disabled="!hasTestResult"
+                @click="copyResponseJson"
+              >
                 <icon-svg
                   :name="hasTestResult ? 'copy_the_response_result' : 'no_response_copy'"
                   size="12px"
                 />
                 <span>复制</span>
               </button>
-              <button type="button" class="otp-result-action" @click="downloadResponseJson">
+              <button
+                type="button"
+                class="otp-result-action"
+                :disabled="!hasTestResult"
+                @click="downloadResponseJson"
+              >
                 <icon-svg
                   :name="hasTestResult ? 'download_of_response_results' : 'no_response_to_download'"
                   size="12px"
@@ -361,7 +383,7 @@
     <el-dialog
       v-model="resultDetailVisible"
       class="otp-result-dialog"
-      width="720px"
+      width="980px"
       align-center
       :show-close="false"
       append-to-body
@@ -377,14 +399,14 @@
             >{{ resultMeta.code ?? "-" }}</span>
             <span class="otp-status-time">{{ resultMeta.time != null ? `${resultMeta.time}ms` : "-" }}</span>
             <button type="button" class="otp-detail-close" @click="resultDetailVisible = false">
-              <icon-svg name="" size="14px" />
+              <icon-svg name="close-icon" size="14px" />
             </button>
           </div>
         </div>
 
         <div class="otp-detail-toolbar">
           <div class="otp-detail-search">
-            <icon-svg name="" size="14px" class="otp-detail-search-icon" />
+            <icon-svg name="search-icon" size="14px" class="otp-detail-search-icon" />
             <input
               v-model="resultSearch"
               type="text"
@@ -393,12 +415,12 @@
             />
           </div>
           <div class="otp-detail-actions">
-            <button type="button" class="otp-detail-btn otp-detail-btn--primary" @click="copyText(responseJson)">
-              <icon-svg name="" size="12px" />
+            <button type="button" class="otp-detail-btn otp-detail-btn--primary" @click.stop="copyDetailJson">
+              <icon-svg name="copy_icon_white" size="12px" />
               <span>复制全部</span>
             </button>
-            <button type="button" class="otp-detail-btn otp-detail-btn--download" @click="downloadResponseJson">
-              <icon-svg name="" size="12px" />
+            <button type="button" class="otp-detail-btn otp-detail-btn--download" @click.stop="downloadDetailJson">
+              <icon-svg name="download_JSON" size="12px" />
               <span>下载JSON</span>
             </button>
           </div>
@@ -427,11 +449,13 @@
 
 <script setup>
 import { computed, onMounted, ref, watch } from "vue";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
 import { getMcpServerDetail, getMcpServerList, getMcpToolDetail, callMcpHandle } from "@/api/mcp";
 import globalStore from "@/store/modules/global";
 
 const route = useRoute();
+const router = useRouter();
 const global = globalStore();
 
 const CONNECT_STATE_KEY = "mcp_online_test_connect_state";
@@ -480,6 +504,8 @@ const resultMeta = ref({
 const tools = ref([]);
 const activeToolCode = ref("");
 const toolRequestId = ref(0);
+/** 来自 /mcp/server/${id} 的 url,不做其它来源兜底 */
+const serverDetailUrl = ref("");
 const requestParams = ref([]);
 const responseStructure = ref([]);
 const paramTab = ref("request");
@@ -536,6 +562,7 @@ const fetchServerList = async () => {
 const resetTestState = () => {
   tools.value = [];
   activeToolCode.value = "";
+  serverDetailUrl.value = "";
   requestParams.value = [];
   responseStructure.value = [];
   requestJsonText.value = "{}";
@@ -705,6 +732,7 @@ const fetchToolsByServerId = async (serverId) => {
   if (!serverId) {
     tools.value = [];
     activeToolCode.value = "";
+    serverDetailUrl.value = "";
     requestParams.value = [];
     responseStructure.value = [];
     isConnected.value = false;
@@ -712,9 +740,11 @@ const fetchToolsByServerId = async (serverId) => {
     return;
   }
   isConnected.value = false;
+  serverDetailUrl.value = "";
   try {
     const detailRes = await getMcpServerDetail(serverId);
     if (requestId !== toolRequestId.value) return;
+    serverDetailUrl.value = detailRes?.data?.url || "";
     const toolList = Array.isArray(detailRes?.data?.toolList)
       ? detailRes.data.toolList
       : [];
@@ -755,6 +785,7 @@ const fetchToolsByServerId = async (serverId) => {
     console.error(error);
     tools.value = [];
     activeToolCode.value = "";
+    serverDetailUrl.value = "";
     requestParams.value = [];
     responseStructure.value = [];
     isConnected.value = false;
@@ -816,6 +847,22 @@ const currentService = computed(
     emptyService,
 );
 
+const breadcrumbServiceName = computed(
+  () => currentService.value.name || currentService.value.serviceName || "MCP 服务",
+);
+
+const goBackToService = () => {
+  const id = activeServiceId.value || currentService.value.id;
+  if (id) {
+    router.push({
+      name: "mcpServer",
+      query: { id: String(id) },
+    });
+    return;
+  }
+  router.back();
+};
+
 const currentTool = computed(
   () =>
     tools.value.find((item) => item.code === activeToolCode.value) ||
@@ -831,12 +878,7 @@ const displayServiceName = computed(
     "-",
 );
 
-const displayServiceUrl = computed(
-  () =>
-    (isConnected.value ? currentTool.value.restfulUrl : "") ||
-    currentService.value.url ||
-    "-",
-);
+const displayServiceUrl = computed(() => serverDetailUrl.value || "-");
 
 const displayConnectType = computed(
   () =>
@@ -920,14 +962,77 @@ const openResultDetail = () => {
 };
 
 const downloadResponseJson = () => {
+  if (!hasTestResult.value) {
+    ElMessage.warning("暂无结果可下载");
+    return;
+  }
+  const text = responseJson.value || "";
+  const blob = new Blob([text], { type: "application/json;charset=utf-8" });
+  const url = URL.createObjectURL(blob);
+  const a = document.createElement("a");
+  a.href = url;
+  a.download = "response.json";
+  a.style.display = "none";
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+  URL.revokeObjectURL(url);
+  ElMessage.success({ message: "下载成功", zIndex: 5000 });
+};
+
+const copyResponseJson = () => {
+  if (!hasTestResult.value) {
+    ElMessage.warning("暂无结果可复制");
+    return;
+  }
+  copyText(responseJson.value);
+};
+
+const copyDetailJson = async () => {
+  const text = responseJson.value || "";
+  if (!hasTestResult.value || !text) {
+    ElMessage.warning({ message: "暂无结果可复制", zIndex: 5000 });
+    return;
+  }
+  try {
+    if (navigator.clipboard?.writeText) {
+      await navigator.clipboard.writeText(text);
+    } else {
+      const textarea = document.createElement("textarea");
+      textarea.value = text;
+      textarea.setAttribute("readonly", "");
+      textarea.style.position = "fixed";
+      textarea.style.left = "-9999px";
+      document.body.appendChild(textarea);
+      textarea.select();
+      const ok = document.execCommand("copy");
+      document.body.removeChild(textarea);
+      if (!ok) throw new Error("execCommand copy failed");
+    }
+    ElMessage.success({ message: "复制成功", zIndex: 5000 });
+  } catch (error) {
+    console.error(error);
+    ElMessage.error({ message: "复制失败", zIndex: 5000 });
+  }
+};
+
+const downloadDetailJson = () => {
+  if (!hasTestResult.value) {
+    ElMessage.warning({ message: "暂无结果可下载", zIndex: 5000 });
+    return;
+  }
   const text = responseJson.value || "";
   const blob = new Blob([text], { type: "application/json;charset=utf-8" });
   const url = URL.createObjectURL(blob);
   const a = document.createElement("a");
   a.href = url;
   a.download = "response.json";
+  a.style.display = "none";
+  document.body.appendChild(a);
   a.click();
+  document.body.removeChild(a);
   URL.revokeObjectURL(url);
+  ElMessage.success({ message: "下载成功", zIndex: 5000 });
 };
 
 const buildRequestParams = () => {
@@ -946,11 +1051,38 @@ const buildRequestParams = () => {
 };
 
 const copyText = async (text) => {
+  if (!text) {
+    ElMessage.warning("暂无内容可复制");
+    return;
+  }
   try {
-    await navigator.clipboard.writeText(text);
+    if (navigator.clipboard?.writeText) {
+      await navigator.clipboard.writeText(text);
+    } else {
+      const textarea = document.createElement("textarea");
+      textarea.value = text;
+      textarea.setAttribute("readonly", "");
+      textarea.style.position = "fixed";
+      textarea.style.left = "-9999px";
+      document.body.appendChild(textarea);
+      textarea.select();
+      const ok = document.execCommand("copy");
+      document.body.removeChild(textarea);
+      if (!ok) throw new Error("execCommand copy failed");
+    }
+    ElMessage.success("复制成功");
   } catch (error) {
     console.error(error);
+    ElMessage.error("复制失败");
+  }
+};
+
+const copyHeaderToken = () => {
+  if (!headerToken.value) {
+    ElMessage.warning("暂无 Token 可复制");
+    return;
   }
+  copyText(`Bearer ${headerToken.value}`);
 };
 
 const runTest = async () => {
@@ -973,29 +1105,28 @@ const runTest = async () => {
 
   if (!jsonMode.value) {
     const missing = requiredParams.value.find(
-      (item) => item.value === "" || item.value === null || item.value === undefined,
+      (item) =>
+        item.value === "" ||
+        item.value === null ||
+        item.value === undefined ||
+        (typeof item.value === "string" && !item.value.trim()),
     );
     if (missing) {
-      resultMeta.value = {
-        code: null,
-        time: null,
-        ok: false,
-        label: `请填写必填参数:${missing.name}`,
-      };
+      ElMessage.warning(`请填写必填参数:${missing.name}`);
       return;
     }
   } else {
     const missing = requiredParams.value.find((item) => {
       const val = params[item.name];
-      return val === "" || val === null || val === undefined;
+      return (
+        val === "" ||
+        val === null ||
+        val === undefined ||
+        (typeof val === "string" && !val.trim())
+      );
     });
     if (missing) {
-      resultMeta.value = {
-        code: null,
-        time: null,
-        ok: false,
-        label: `请填写必填参数:${missing.name}`,
-      };
+      ElMessage.warning(`请填写必填参数:${missing.name}`);
       return;
     }
   }
@@ -1050,6 +1181,21 @@ const runTest = async () => {
   line-height: 18px;
 }
 
+.otp-breadcrumb-link {
+  margin: 0;
+  padding: 0;
+  border: none;
+  background: transparent;
+  color: #94A3B8;
+  font: inherit;
+  line-height: inherit;
+  cursor: pointer;
+}
+
+.otp-breadcrumb-link:hover {
+  color: #1678FF;
+}
+
 .otp-breadcrumb-sep {
   color: #CBD5E1;
 }
@@ -1475,9 +1621,20 @@ const runTest = async () => {
   flex-shrink: 0;
 }
 
+.otp-token-action-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0;
+  border: none;
+  background: transparent;
+  cursor: pointer;
+  line-height: 0;
+}
+
 .otp-token-icon {
   color: #64748B;
-  cursor: pointer;
+  pointer-events: none;
 }
 
 .otp-token-tip {
@@ -2034,6 +2191,12 @@ const runTest = async () => {
   border-radius: 6px;
   color: #94A3B8;
   line-height: 16px;
+  cursor: not-allowed;
+  opacity: 0.6;
+}
+
+.otp-result-action:disabled {
+  cursor: not-allowed;
 }
 
 .otp-result-empty {
@@ -2383,6 +2546,10 @@ const runTest = async () => {
   font-size: 12px;
   line-height: 16px;
   cursor: pointer;
+
+  .svg-icon {
+    pointer-events: none;
+  }
 }
 
 .otp-detail-btn--primary {

+ 22 - 2
src/pages/mcp/serverDetail.vue

@@ -66,7 +66,7 @@
               class="mcp-server-tag"
               :class="tag.type ? `mcp-server-tag--${tag.type}` : ''"
             >
-              <icon-svg :name="tag.icon || ''" size="14px" />
+              <!-- <icon-svg :name="tag.icon || ''" size="14px" /> -->
               <span>{{ tag.label || tag }}</span>
             </span>
           </div>
@@ -283,6 +283,7 @@
 <script setup>
 import { computed, ref, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
 import { getMcpServerDetail, getMcpServerList, getMcpToolDetail } from "@/api/mcp";
 import globalStore from "@/store/modules/global";
 
@@ -631,10 +632,29 @@ watch(selectedToolIndex, (index, prevIndex) => {
 });
 
 const copyText = async (text) => {
+  if (!text) {
+    ElMessage.warning("暂无内容可复制");
+    return;
+  }
   try {
-    await navigator.clipboard.writeText(text);
+    if (navigator.clipboard?.writeText) {
+      await navigator.clipboard.writeText(String(text));
+    } else {
+      const textarea = document.createElement("textarea");
+      textarea.value = String(text);
+      textarea.setAttribute("readonly", "");
+      textarea.style.position = "fixed";
+      textarea.style.left = "-9999px";
+      document.body.appendChild(textarea);
+      textarea.select();
+      const ok = document.execCommand("copy");
+      document.body.removeChild(textarea);
+      if (!ok) throw new Error("execCommand copy failed");
+    }
+    ElMessage.success("复制成功");
   } catch (error) {
     console.error(error);
+    ElMessage.error("复制失败");
   }
 };
 

+ 10 - 10
src/router/index.js

@@ -1,4 +1,4 @@
-import { createRouter, createWebHashHistory } from "vue-router";
+import { createRouter, createWebHistory } from "vue-router";
 import { getCookie } from "@/utils/auth";
 import globalStore from "@/store/modules/global";
 
@@ -12,7 +12,8 @@ const routes = [
     children: [
       {
         path: "",
-        redirect: { name: "mcpAuth" },
+        name: "mcp",
+        component: () => import("@/pages/home/index.vue"),
       },
       {
         path: "login",
@@ -27,12 +28,7 @@ const routes = [
         component: () => import("@/pages/mcp/layout.vue"),
         children: [
           {
-            path: "",
-            name: "mcp",
-            component: () => import("@/pages/home/index.vue"),
-          },
-          {
-            path: "test",
+            path: "online",
             name: "mcpOnlineTest",
             component: () => import("@/pages/mcp/onlineTest.vue"),
           },
@@ -49,6 +45,10 @@ const routes = [
             component: () => import("@/pages/home/mcpIndex.vue"),
             children: [
               {
+                path: "",
+                redirect: { name: "mcp" },
+              },
+              {
                 path: "guide",
                 name: "guide",
                 component: () => import("@/pages/guide/index.vue"),
@@ -85,7 +85,7 @@ const routes = [
 ];
 
 const router = createRouter({
-  history: createWebHashHistory(import.meta.env.BASE_URL),
+  history: createWebHistory(import.meta.env.BASE_URL),
   routes,
   scrollBehavior() {
     return { left: 0, top: 0 };
@@ -107,7 +107,7 @@ router.beforeEach((to, _from, next) => {
         next(String(redirect));
         return;
       }
-      next({ name: "mcpAuth" });
+      next({ name: "mcp" });
       return;
     }
     next();

+ 2 - 2
src/utils/request.js

@@ -154,7 +154,7 @@ service.interceptors.response.use(
         if (isLoggedIn) {
           global.logout();
 
-          window.location.href = "/#/login?redirect=" + encodeURIComponent("/mcp/auth");
+          window.location.href = "/#/login?redirect=" + encodeURIComponent("/");
         }
       }
 
@@ -181,7 +181,7 @@ service.interceptors.response.use(
         global.logout();
 
         // 跳转到登录页
-        window.location.href = "/#/login?redirect=" + encodeURIComponent("/mcp/auth");
+        window.location.href = "/#/login?redirect=" + encodeURIComponent("/");
       }
     }
 

+ 1 - 1
vite.config.js

@@ -24,7 +24,7 @@ export default defineConfig({
       "@": resolve("src"),
     },
   },
-  base: "./", // 基础路径
+  base: "/", // History 模式下必须用绝对路径,避免 /mcp/guide 等深层路由把 JS 解析成 /mcp/assets/...
   // 反向代理
   server: {
     port: 8060,