Просмотр исходного кода

完成授权页面与登录页面

肖栋 2 недель назад
Родитель
Сommit
fd7061f129
34 измененных файлов с 1494 добавлено и 180 удалено
  1. 1 1
      index.html
  2. 0 0
      src/assets/icons/MCP_serve_violet.svg
  3. 1 0
      src/assets/icons/authorization_successful.svg
  4. 0 0
      src/assets/icons/available_loading_tools.svg
  5. 1 0
      src/assets/icons/connection_guide_blue.svg
  6. 1 0
      src/assets/icons/connection_service.svg
  7. 1 0
      src/assets/icons/copy_the_response_result.svg
  8. 1 0
      src/assets/icons/disconnect_icon.svg
  9. 1 0
      src/assets/icons/download_of_response_results.svg
  10. 1 0
      src/assets/icons/no_response_copy.svg
  11. 1 0
      src/assets/icons/no_response_result.svg
  12. 1 0
      src/assets/icons/no_response_to_download.svg
  13. 0 0
      src/assets/icons/service_warning.svg
  14. 0 0
      src/assets/icons/share_blue.svg
  15. 0 0
      src/assets/icons/share_white.svg
  16. 1 0
      src/assets/icons/verification_status.svg
  17. 1 0
      src/assets/icons/verify_immediately.svg
  18. 0 0
      src/assets/icons/visit_icon.svg
  19. BIN
      src/assets/images/doubao_img.png
  20. BIN
      src/assets/images/test_instruction_blue.png
  21. BIN
      src/assets/images/test_instruction_green.png
  22. BIN
      src/assets/images/test_instruction_purple.png
  23. 55 15
      src/components/login.vue
  24. 2 3
      src/components/pcHeader.vue
  25. 347 102
      src/pages/home/index.vue
  26. 52 0
      src/pages/login/index.vue
  27. 594 0
      src/pages/mcp/authSuccess.vue
  28. 296 47
      src/pages/mcp/onlineTest.vue
  29. 6 0
      src/pages/mcp/serverDetail.vue
  30. 31 5
      src/pages/pc/index.vue
  31. 61 2
      src/router/index.js
  32. 1 1
      src/utils/config.js
  33. 34 0
      src/utils/mcpToken.js
  34. 3 4
      src/utils/request.js

+ 1 - 1
index.html

@@ -6,7 +6,7 @@
   <link rel="icon" type="image/svg+xml" href="/vite.png" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no,viewport-fit=cover" />
   <script type="text/javascript" src="https://g.alicdn.com/jssdk/u-link/index.min.js"></script>
-  <title>尚云 AI</title>
+  <title>尚云 MCP</title>
 </head>
 
 <body>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/MCP_serve_violet.svg


+ 1 - 0
src/assets/icons/authorization_successful.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="34" height="34" viewBox="0 0 34 34"><path d="M31.927320500854492,2.705269244265747C32.94003150085449,3.4417868942657472,33.16665450085449,4.8581668942657465,32.43013550085449,5.8708786942657465L14.300473500854492,30.799165294265748C13.910967500854492,31.33739029426575,13.309006500854492,31.670237294265746,12.643308500854491,31.726893294265746C11.977610500854492,31.783549294265747,11.333157500854492,31.535682294265747,10.865751300854493,31.068278294265745L1.8009195908544922,22.003446294265746C0.9156821408544922,21.118209294265746,0.9156821408544922,19.680583294265748,1.8009195908544922,18.795346294265748C2.6861571008544924,17.910109294265744,4.123782600854492,17.910109294265744,5.009020100854492,18.795346294265748L12.197148500854492,25.983474294265747L28.76879550085449,3.201002774265747C29.505311500854493,2.188291155265747,30.921691500854493,1.961670314265747,31.934406500854493,2.698187974265747L31.927320500854492,2.705269244265747Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/available_loading_tools.svg


+ 1 - 0
src/assets/icons/connection_guide_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="12" height="12" viewBox="0 0 12 12"><path d="M11.780104462493897,6.529699572384644C12.073078462493896,6.236724572384643,12.073078462493896,5.760933172384644,11.780104462493897,5.467958172384644L8.030024062493897,1.7178780323846436C7.737049062493896,1.4249030203846436,7.261258162493896,1.4249030203846436,6.968283162493896,1.7178780323846436C6.675308262493896,2.0108530523846433,6.675308262493896,2.486644562384644,6.968283162493896,2.7796195723846435L9.438647762493897,5.249984772384643L0.7501814324938965,5.249984772384643C0.3353288424938965,5.249984772384643,0.00016546249389648438,5.585148072384643,0.00016546249389648438,6.000000672384644C0.00016546249389648438,6.414853772384643,0.3353288424938965,6.7500169723846435,0.7501814324938965,6.7500169723846435L9.438647762493897,6.7500169723846435L6.968283162493896,9.220381472384643C6.675308262493896,9.513356972384644,6.675308262493896,9.989147872384644,6.968283162493896,10.282122372384643C7.261258162493896,10.575097772384643,7.737049062493896,10.575097772384643,8.030024062493897,10.282122372384643L11.780104462493897,6.532042772384644L11.780104462493897,6.529699572384644Z" fill="#1678FF" fill-opacity="1"/></svg>

+ 1 - 0
src/assets/icons/connection_service.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.000091552734375" viewBox="0 0 12 12.000091552734375"><path d="M3.882335908026123,0C4.2727802080261235,0,4.588223908026123,0.31544366,4.588223908026123,0.70588797L4.588223908026123,2.8235519L7.411775608026123,2.8235519L7.411775608026123,0.70588797C7.411775608026123,0.31544366,7.727219108026123,0,8.117663908026124,0C8.508108108026123,0,8.823551608026122,0.31544366,8.823551608026122,0.70588797L8.823551608026122,2.8235519L10.235327208026122,2.8235519C10.625772008026123,2.8235519,10.941216008026123,3.1389956,10.941216008026123,3.5294399C10.941216008026123,3.919884,10.625772008026123,4.2353277,10.235327208026122,4.2353277L10.235327208026122,5.6471038C10.235327208026122,7.7449145,8.708844708026124,9.4875755,6.705887808026123,9.8228722L6.705887808026123,11.294208C6.705887808026123,11.684652,6.390443808026123,12.000095,6.000000008026123,12.000095C5.609555708026123,12.000095,5.294111708026123,11.684652,5.294111708026123,11.294208L5.294111708026123,9.8228722C3.291154608026123,9.4875755,1.764671978026123,7.7449145,1.764671978026123,5.6471038L1.764671978026123,4.2353277C1.3742276680261232,4.2353277,1.058784008026123,3.919884,1.058784008026123,3.5294399C1.058784008026123,3.1389956,1.374227698026123,2.8235519,1.764671978026123,2.8235519L3.176447908026123,2.8235519L3.176447908026123,0.70588797C3.176447908026123,0.31544366,3.491891608026123,0,3.882335908026123,0Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/copy_the_response_result.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.000259399414062" viewBox="0 0 12 12.000259399414062"><path d="M5.249984243267822,0.00000286102294921875C4.422622643267822,0.00000286102294921875,3.749952043267822,0.6726735210229492,3.749952043267822,1.5000349610229493L3.749952043267822,7.500163061022949C3.749952043267822,8.32752416102295,4.422622643267822,9.00019546102295,5.249984243267822,9.00019546102295L9.750080543267822,9.00019546102295C10.577441643267822,9.00019546102295,11.250111943267822,8.32752416102295,11.250111943267822,7.500163061022949L11.250111943267822,2.798500061022949C11.250111943267822,2.3906788610229492,11.083702943267822,1.9992642610229492,10.788383943267823,1.7156644610229492L9.436010843267823,0.4171992510229492C9.157099243267822,0.15000607102294922,8.784434843267823,0.00000286102294921875,8.397707443267823,0.00000286102294921875L5.249984243267822,0.00000286102294921875ZM2.2499200432678226,3.000066961022949C1.4225586032678224,3.000066961022949,0.7498879432678223,3.672737561022949,0.7498879432678223,4.500099161022949L0.7498879432678223,10.500226861022949C0.7498879432678223,11.327587861022948,1.4225586032678224,12.000259861022949,2.2499200432678226,12.000259861022949L6.750016243267822,12.000259861022949C7.577377343267822,12.000259861022949,8.250048143267822,11.327587861022948,8.250048143267822,10.500226861022949L8.250048143267822,10.12521886102295L6.750016243267822,10.12521886102295L6.750016243267822,10.500226861022949L2.2499200432678226,10.500226861022949L2.2499200432678226,4.500099161022949L2.6249280432678224,4.500099161022949L2.6249280432678224,3.000066961022949L2.2499200432678226,3.000066961022949Z" fill="#64748B" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/disconnect_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="12" height="12.000091552734375" viewBox="0 0 12 12.000091552734375"><path d="M6.705887813531494,0.70588797C6.705887813531494,0.31544366,6.390444713531494,0,6.000000013531494,0C5.609555213531494,0,5.294112213531494,0.31544366,5.294112213531494,0.70588797L5.294112213531494,6.3529916C5.294112213531494,6.7434363,5.609555713531494,7.0588799,6.000000013531494,7.0588799C6.390444713531494,7.0588799,6.705887813531494,6.7434363,6.705887813531494,6.3529916L6.705887813531494,0.70588797ZM3.580127913531494,2.8764935C3.899983413531494,2.6536977,3.9771898135314943,2.2125177,3.754393813531494,1.8926622C3.5315978135314943,1.5728067,3.090417813531494,1.4956002,2.7705624135314944,1.7183961C1.3102568435314943,2.7375216,0.35289621353149414,4.4338589,0.35289621353149414,6.3529916C0.35289621353149414,9.4721336,2.880857513531494,12.000095,6.000000013531494,12.000095C9.119142013531494,12.000095,11.647104213531493,9.4721336,11.647104213531493,6.3529916C11.647104213531493,4.4338589,10.689743213531495,2.7375219,9.227231513531494,1.7183959C8.907375813531495,1.4956,8.468401413531494,1.5728066,8.243399613531494,1.892662C8.018397813531493,2.2125175,8.097810313531493,2.6514916,8.417665913531494,2.8764935C9.516203413531494,3.6441467,10.233121413531494,4.9147449,10.233121413531494,6.3529916C10.233121413531494,8.691246,8.336047213531494,10.58832,5.997793213531494,10.58832C3.659539413531494,10.58832,1.7646721135314942,8.691246,1.7646721135314942,6.3529916C1.7646721135314942,4.9147449,2.4815895135314943,3.6419408,3.580127913531494,2.8764935Z" fill="#EF4444" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/download_of_response_results.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="#64748B" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/no_response_copy.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.000259399414062" viewBox="0 0 12 12.000259399414062"><path d="M5.249984243267822,0.00000286102294921875C4.422622643267822,0.00000286102294921875,3.749952043267822,0.6726735210229492,3.749952043267822,1.5000349610229493L3.749952043267822,7.500163061022949C3.749952043267822,8.32752416102295,4.422622643267822,9.00019546102295,5.249984243267822,9.00019546102295L9.750080543267822,9.00019546102295C10.577441643267822,9.00019546102295,11.250111943267822,8.32752416102295,11.250111943267822,7.500163061022949L11.250111943267822,2.798500061022949C11.250111943267822,2.3906788610229492,11.083702943267822,1.9992642610229492,10.788383943267823,1.7156644610229492L9.436010843267823,0.4171992510229492C9.157099243267822,0.15000607102294922,8.784434843267823,0.00000286102294921875,8.397707443267823,0.00000286102294921875L5.249984243267822,0.00000286102294921875ZM2.2499200432678226,3.000066961022949C1.4225586032678224,3.000066961022949,0.7498879432678223,3.672737561022949,0.7498879432678223,4.500099161022949L0.7498879432678223,10.500226861022949C0.7498879432678223,11.327587861022948,1.4225586032678224,12.000259861022949,2.2499200432678226,12.000259861022949L6.750016243267822,12.000259861022949C7.577377343267822,12.000259861022949,8.250048143267822,11.327587861022948,8.250048143267822,10.500226861022949L8.250048143267822,10.12521886102295L6.750016243267822,10.12521886102295L6.750016243267822,10.500226861022949L2.2499200432678226,10.500226861022949L2.2499200432678226,4.500099161022949L2.6249280432678224,4.500099161022949L2.6249280432678224,3.000066961022949L2.2499200432678226,3.000066961022949Z" fill="#94A3B8" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/no_response_result.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="24" height="24" viewBox="0 0 24 24"><path d="M5.0249999,1.7296880079870605C4.4437499,1.4109380619870606,3.7406248,1.4250005259870606,3.16874993,1.7625005279870605C2.59687507,2.1000005579870606,2.25,2.714062917987061,2.25,3.3750002179870604L2.25,20.62499771798706C2.25,21.285935717987062,2.6015625,21.89999871798706,3.16874993,22.237498717987062C3.7359374,22.574996717987062,4.4437501,22.58906171798706,5.0249999,22.270310717987062L20.775002,13.645311717987061C21.375,13.31718771798706,21.75,12.684375717987061,21.75,11.99999971798706C21.75,11.31562471798706,21.375,10.68281221798706,20.775002,10.35468721798706L5.0249999,1.7296880079870605Z" fill="#94A3B8" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/no_response_to_download.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="#94A3B8" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/service_warning.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/share_blue.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/share_white.svg


+ 1 - 0
src/assets/icons/verification_status.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="M11.780104462493897,6.529699572384644C12.073078462493896,6.236724572384643,12.073078462493896,5.760933172384644,11.780104462493897,5.467958172384644L8.030024062493897,1.7178780323846436C7.737049062493896,1.4249030203846436,7.261258162493896,1.4249030203846436,6.968283162493896,1.7178780323846436C6.675308262493896,2.0108530523846433,6.675308262493896,2.486644562384644,6.968283162493896,2.7796195723846435L9.438647762493897,5.249984772384643L0.7501814324938965,5.249984772384643C0.3353288424938965,5.249984772384643,0.00016546249389648438,5.585148072384643,0.00016546249389648438,6.000000672384644C0.00016546249389648438,6.414853772384643,0.3353288424938965,6.7500169723846435,0.7501814324938965,6.7500169723846435L9.438647762493897,6.7500169723846435L6.968283162493896,9.220381472384643C6.675308262493896,9.513356972384644,6.675308262493896,9.989147872384644,6.968283162493896,10.282122372384643C7.261258162493896,10.575097772384643,7.737049062493896,10.575097772384643,8.030024062493897,10.282122372384643L11.780104462493897,6.532042772384644L11.780104462493897,6.529699572384644Z" fill="#22C55E" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

+ 1 - 0
src/assets/icons/verify_immediately.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.000152587890625" viewBox="0 0 16 16.000152587890625"><path d="M8.0000734,16.000147C3.5817547,16.000147,0,12.418392,0,8.0000734C0,3.5817547,3.5817547,0,8.0000734,0C12.418392,0,16.000147,3.5817547,16.000147,8.0000734C16.000147,12.418392,12.418392,16.000147,8.0000734,16.000147ZM11.687608,4.5531669C11.353229,4.3094144,10.884474,4.3844156,10.640722,4.7187934L6.9094381,9.85009L5.2812982,8.2219505C4.987546,7.9281979,4.5125413,7.9281979,4.2219138,8.2219505C3.9312866,8.5157032,3.9281611,8.9907074,4.2219138,9.2813358L6.4719343,11.531356C6.6281862,11.687608,6.8406882,11.765734,7.0594397,11.750108C7.278192,11.734483,7.4781938,11.621982,7.6063204,11.443856L11.853234,5.6000509C12.096986,5.2656732,12.021985,4.7969189,11.687608,4.5531669Z" fill="#22C55E" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/icons/visit_icon.svg


BIN
src/assets/images/doubao_img.png


BIN
src/assets/images/test_instruction_blue.png


BIN
src/assets/images/test_instruction_green.png


BIN
src/assets/images/test_instruction_purple.png


+ 55 - 15
src/components/login.vue

@@ -1,14 +1,11 @@
-<!-- 新版登录弹窗 -->
+<!-- 新版登录弹窗 / 页面内嵌 -->
 <template>
-    <el-dialog
-        v-model="loginVisable"
-        :class="['lc-dialog', { 'lc-dialog--mobile': !isPC }]"
-        align-center
-        :show-close="false"
-        :before-close="handleClose"
-        style="padding: 0;border-radius: 12px;"
+    <component
+        :is="asPage ? 'div' : 'el-dialog'"
+        v-bind="rootBind"
+        @update:model-value="onDialogVisibleChange"
     >
-        <div class="login-header">
+        <div v-if="!asPage" class="login-header">
             <!-- 关闭按钮 -->
             <icon-svg name="closeMessage" size="24px" class="dialog-close" @click="handleClose" />
         </div>
@@ -124,7 +121,7 @@
         <div class="confirm-box-fail" v-show="qrCodeType == 4">
             <p class="p-h3">用户取消授权</p>
         </div>
-    </el-dialog>
+    </component>
 </template>
 
 <script setup>
@@ -136,11 +133,43 @@ import { getRememberInfo, remember } from '@/utils/auth';
 import { checkPhone, IsPC } from "@/utils/common";
 import { wechatLoginUrl } from '@/utils/config.js';
 
+const props = defineProps({
+    /** 作为独立页面内容展示,非弹窗 */
+    asPage: {
+        type: Boolean,
+        default: false,
+    },
+});
+
 const global = globalStore();
 const isPC = IsPC();
 const loginVisable = computed(() => global.loginVisable);
 const triggerLoginVisible = computed(() => global.triggerLoginVisible);
-const emit = defineEmits(["update:modelValue", "saveUser"]);
+const emit = defineEmits(["update:modelValue", "saveUser", "closed"]);
+const dialogVisible = computed(() => props.asPage || loginVisable.value);
+
+const rootBind = computed(() => {
+    const className = [
+        "lc-dialog",
+        { "lc-dialog--mobile": !isPC, "lc-dialog--page": props.asPage },
+    ];
+    if (props.asPage) {
+        return { class: className };
+    }
+    return {
+        modelValue: dialogVisible.value,
+        class: className,
+        alignCenter: true,
+        showClose: false,
+        beforeClose: handleClose,
+        style: "padding: 0;border-radius: 12px;",
+    };
+});
+
+const onDialogVisibleChange = (visible) => {
+    if (props.asPage) return;
+    triggerLoginVisible.value(visible);
+};
 const loginWayTab = ref([
     {
         title: "账号",
@@ -200,7 +229,7 @@ const stopAppScanLogin = () => {
 }
 
 const startAppScanLogin = () => {
-    if (loginWayIndex.value !== 2 || scanTab.value !== 'app' || !loginVisable.value) {
+    if (loginWayIndex.value !== 2 || scanTab.value !== 'app' || !dialogVisible.value) {
         return;
     }
     qrCode.getQrCode();
@@ -224,15 +253,18 @@ const handleAppScanClick = () => {
     startAppScanLogin();
 }
 
-// 关闭弹
+// 关闭弹
 const handleClose = () => {
-    triggerLoginVisible.value(false);
+    if (!props.asPage) {
+        triggerLoginVisible.value(false);
+    }
 
     if (codeTimer.value) {
         clearInterval(codeTimer.value);
     }
 
     stopAppScanLogin();
+    emit("closed");
 }
 
 onBeforeMount(() => {
@@ -244,7 +276,7 @@ onBeforeMount(() => {
         passwordNumber.value = rememberInfo.password;
     }
 
-    watch(() => loginVisable.value, (newValue, oldValue) => {
+    watch(() => dialogVisible.value, (newValue, oldValue) => {
         if (newValue) {
             // 每次打开登录弹窗时默认回到账号登录,不自动开启APP扫码
             loginWayIndex.value = 0;
@@ -543,6 +575,14 @@ onBeforeUnmount(() => {
     border-radius: 12px;
     position: relative;
 
+    &.lc-dialog--page {
+        position: relative;
+        margin: 0 auto;
+        flex-shrink: 0;
+        width: 782px;
+        max-width: calc(100vw - 32px);
+    }
+
     &.lc-dialog--mobile {
         width: calc(100vw - 32px) !important;
         max-width: calc(100vw - 32px);

+ 2 - 3
src/components/pcHeader.vue

@@ -359,7 +359,7 @@ const handleUploadMaterialClick = (shareType) => {
 
 /**
  * 退出当前登录账号。
- * 成功后清理全局登录状态,并返回对话首页。
+ * 成功后清理全局登录状态,并跳转登录页。
  */
 const logoutHandle = async () => {
     if (isLoggingOut.value) {
@@ -374,8 +374,7 @@ const logoutHandle = async () => {
         if (response.code === 200) {
             ElMessage.success("退出登录成功!");
             await global.logout();
-            await router.replace("/dialog");
-            window.location.reload();
+            await router.replace({ name: "login" });
             return;
         }
 

+ 347 - 102
src/pages/home/index.vue

@@ -23,7 +23,8 @@
             <icon-svg name="connection_guide" size="16px" />
             <span>接入指南</span>
           </button>
-          <button type="button" class="home-hero-btn home-hero-btn-secondary" @click="router.push({ name: 'mcpOnlineTest' })">
+          <button type="button" class="home-hero-btn home-hero-btn-secondary"
+            @click="router.push({ name: 'mcpOnlineTest' })">
             <icon-svg name="effect_experience" size="16px" />
             <span>效果体验</span>
           </button>
@@ -202,6 +203,13 @@
                 </div>
               </div>
             </div>
+            <div class="home-scenes-demo-preview">
+              <img
+                :src="demoCaseImages[activeDemoQuestion]"
+                :alt="demoQuestions[activeDemoQuestion]"
+                class="home-scenes-demo-preview-img"
+              />
+            </div>
           </div>
         </div>
       </section>
@@ -251,7 +259,8 @@
                   <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" @click="copyText(guideToken)" />
+                    <icon-svg name="copy_close_icon" size="14px" class="home-guide-token-icon"
+                      @click="copyText(guideToken)" />
                   </div>
                 </div>
                 <button type="button" class="home-guide-token-btn" @click="createToken">
@@ -378,11 +387,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-2fb2189e85b34845aaab9c9784f0388e"</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-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-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-c61015e418b046d99e1bxxxxxxxx"</span>
+        <span class="code-key">"Authorization"</span><span class="code-punct">:</span> <span class="code-string">"Bearer sk-2fb2189e85b34845aaab9c9784f0388e"</span>
       <span class="code-punct">}</span>
     <span class="code-punct">}</span>
   <span class="code-punct">}</span>
@@ -391,16 +417,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>
@@ -408,35 +434,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 sk-2fb2189e85b34845aaab9c9784f0388e"</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-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-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-punct">}</span>
+  <span class="code-punct">}</span>
+<span class="code-punct">}</span></pre>
                     </div>
                   </div>
                 </div>
@@ -532,7 +633,7 @@
                       </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="copy_icon_white" size="12px" class="home-guide-cli-copy-all-icon" />
                         <span>全部复制</span>
@@ -565,12 +666,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"
-                      class="home-guide-verify-copy"
-                      @click="copyText(question)"
-                    />
+                    <icon-svg name="Connection_Copy" size="13px" class="home-guide-verify-copy"
+                      @click="copyText(question)" />
                   </div>
                 </div>
               </div>
@@ -613,10 +710,30 @@
             </div>
           </div>
           <div class="home-footer-links">
-            <span>关于我们</span>
-            <span>用户协议</span>
-            <span>隐私政策</span>
-            <span>联系我们</span>
+            <a
+              class="home-footer-link"
+              href="https://www.eiacloud.com/lxwmhjzx/550.jhtml"
+              target="_blank"
+              rel="noopener noreferrer"
+            >关于我们</a>
+            <a
+              class="home-footer-link"
+              href="https://www.eiacloud.com/bbs/userAgreement"
+              target="_blank"
+              rel="noopener noreferrer"
+            >用户协议</a>
+            <a
+              class="home-footer-link"
+              href="https://www.eiacloud.com/hpy/lawstandardApp/getDetailYS"
+              target="_blank"
+              rel="noopener noreferrer"
+            >隐私政策</a>
+            <a
+              class="home-footer-link"
+              href="https://www.eiacloud.com/lxwmlxfs/669.jhtml"
+              target="_blank"
+              rel="noopener noreferrer"
+            >联系我们</a>
           </div>
         </div>
       </div>
@@ -630,6 +747,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";
 
 const router = useRouter();
 const global = globalStore();
@@ -638,7 +758,8 @@ const demoQuestions = [
   "「建设项目环境影响评价分类管理名录中化工项目如何分类?」",
   "「地下水质量标准 GB/T14848 中Ⅲ类水质的具体指标限值?」",
 ];
-const activeDemoQuestion = ref(1);
+const demoCaseImages = [demoImgGreen, demoImgBlue, demoImgPurple];
+const activeDemoQuestion = ref(0);
 const guideToken = computed(() => global.mcpToken || "");
 const guideTokenVisible = ref(false);
 const guideTokenMasked = computed(() => {
@@ -693,40 +814,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:
@@ -762,16 +872,63 @@ const guideWpsContent = {
 };
 const guideMcpJson = `{
   "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 sk-2fb2189e85b34845aaab9c9784f0388e"
+      }
+    },
+    "hpyzs-law-semantic": {
+      "url": "https://mcp.eiacloud.com/law-semantic",
       "headers": {
-        "Authorization": "Bearer sk-c61015e418b046d99e1bxxxxxxxx"
+        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
       }
+    },
+    "hpyzs-qa": {
+      "url": "https://mcp.eiacloud.com/qa",
+      "headers": {
+        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      }
+    },
+    "hpyzs-emission": {
+      "url": "https://mcp.eiacloud.com/emission",
+      "headers": {
+        "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+      }
+    }
+  }
+}`;
+const guideMcpLines = Array.from({ length: guideMcpJson.split("\n").length }, (_, i) => i + 1);
+const guideQoderworkJson = `{
+  "hpyzs-law-keyword": {
+    "url": "https://mcp.eiacloud.com/law-keyword",
+    "headers": {
+      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+    }
+  },
+  "hpyzs-law-semantic": {
+    "url": "https://mcp.eiacloud.com/law-semantic",
+    "headers": {
+      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+    }
+  },
+  "hpyzs-qa": {
+    "url": "https://mcp.eiacloud.com/qa",
+    "headers": {
+      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
+    }
+  },
+  "hpyzs-emission": {
+    "url": "https://mcp.eiacloud.com/emission",
+    "headers": {
+      "Authorization": "Bearer sk-2fb2189e85b34845aaab9c9784f0388e"
     }
   }
 }`;
-const guideMcpLines = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
+const guideQoderworkLines = Array.from(
+  { length: guideQoderworkJson.split("\n").length },
+  (_, i) => i + 1,
+);
 const copyText = async (text) => {
   if (!text) {
     ElMessage.warning("暂无内容可复制");
@@ -799,6 +956,7 @@ const copyText = async (text) => {
   }
 };
 const copyGuideMcpJson = () => copyText(guideMcpJson);
+const copyGuideQoderworkJson = () => copyText(guideQoderworkJson);
 const guideCliSteps = [
   {
     num: 1,
@@ -821,52 +979,61 @@ 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 = guideToken.value;
+  const authPart = token ? `Bearer ${token}` : "Bearer YOUR_TOKEN";
+  return [
+    {
+      label: "安装 CLI",
+      text: "npm install -g @eiacloud/mcp-cli",
+    },
+    {
+      label: "绑定 Token",
+      text: `$ eiacloud-mcp init --authorization ${authPart}`,
+    },
+    {
+      label: "法规检索",
+      text: '$ eiacloud-mcp regulation-search "大气污染物排放标准"',
+    },
+    {
+      label: "标准查询",
+      text: '$ eiacloud-mcp standard-search "地下水质量标准"',
+    },
+  ];
+});
+const guideCliFooterTip = computed(() =>
+  guideToken.value
+    ? "初始化命令已自动填入真实 Token"
+    : "将 YOUR_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 相关限值要求?",
+      "重大变动如何重新报批环评?",
+      "排污许可证内容变更如何重新办理?",
+      "环评批复几年不开工作废?",
     ],
   },
 ];
@@ -1417,7 +1584,10 @@ const guideVerifyCards = [
 }
 
 .home-scenes-demo {
-  display: flex;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  align-items: stretch;
+  gap: 40px;
 }
 
 .home-scenes-demo-copy {
@@ -1425,6 +1595,28 @@ const guideVerifyCards = [
   flex-direction: column;
   align-items: flex-start;
   gap: 20px;
+  min-width: 0;
+}
+
+.home-scenes-demo-preview {
+  position: relative;
+  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;
 }
 
 .home-scenes-demo-badge {
@@ -2040,12 +2232,38 @@ const guideVerifyCards = [
   display: flex;
   gap: 20px;
   margin-top: 20px;
+  align-items: stretch;
+}
+
+.home-guide-pending-body {
+  min-height: 280px;
 }
 
 .home-guide-ima-body {
   align-items: stretch;
 }
 
+.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-left,
 .home-guide-ima-right {
   flex: 1;
@@ -2663,21 +2881,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 {
@@ -2739,6 +2962,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;
@@ -3060,13 +3288,30 @@ const guideVerifyCards = [
     display: flex;
     align-items: center;
     gap: 24px;
+    white-space: nowrap;
+  }
+
+  .home-footer-link {
     font-family: PingFang SC, PingFang SC;
     font-weight: 300;
     font-size: 14px;
     color: #333333;
     line-height: 20px;
     text-align: left;
-    white-space: nowrap;
+    text-decoration: none;
+    cursor: pointer;
+
+    &:link,
+    &:visited,
+    &:active {
+      color: #333333;
+      text-decoration: none;
+    }
+
+    &:hover {
+      color: #1678FF;
+      text-decoration: none;
+    }
   }
 }
 </style>

+ 52 - 0
src/pages/login/index.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="login-page">
+    <Login as-page @save-user="saveUserInfo" @closed="onLoginClosed" />
+  </div>
+</template>
+
+<script setup>
+import { useRouter } from "vue-router";
+import Login from "@/components/login.vue";
+import globalStore from "@/store/modules/global";
+import { getMenuPromission } from "@/api/login";
+import { ensureMcpToken } from "@/utils/mcpToken";
+
+defineOptions({
+  name: "LoginPage",
+});
+
+const router = useRouter();
+const global = globalStore();
+
+const saveUserInfo = async (userInfo) => {
+  global.setUserInfo(userInfo.data);
+  global.setWebsid(userInfo?.extraMap?.WEBSID ?? "");
+  global.setUserId(userInfo?.data?.id ?? "");
+  global.setYundian(userInfo?.ydCount);
+  const res = await getMenuPromission();
+  global.promission = res?.data ?? [];
+  // 登录成功后拉取并保存 MCP Token
+  await ensureMcpToken();
+};
+
+const onLoginClosed = () => {
+  const redirect = router.currentRoute.value.query.redirect;
+  if (redirect) {
+    router.replace(String(redirect));
+    return;
+  }
+  router.replace({ name: "mcpAuth" });
+};
+</script>
+
+<style scoped lang="scss">
+.login-page {
+  box-sizing: border-box;
+  min-height: calc(100vh - 64px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 48px 16px 64px;
+  background: linear-gradient(135deg, #F0F7FF 0%, #F5F3FF 100%);
+}
+</style>

+ 594 - 0
src/pages/mcp/authSuccess.vue

@@ -0,0 +1,594 @@
+<template>
+  <div class="auth-success-page">
+    <div v-if="authReady" class="auth-success-card">
+      <div class="auth-success-head">
+        <div class="auth-success-brand">
+          <icon-svg name="systemLogoCircle" size="40px" />
+          <div class="auth-success-brand-copy">
+            <div class="auth-success-brand-title">环评云助手</div>
+            <div class="auth-success-brand-sub">智能环境法规标准平台</div>
+          </div>
+        </div>
+      </div>
+
+      <div class="auth-success-body">
+        <!-- 图1 -->
+        <div class="auth-success-hero">
+          <div class="auth-success-check">
+            <icon-svg name="authorization_successful" size="34px" class="auth-success-check-icon" />
+          </div>
+          <div class="auth-success-title">授权成功</div>
+          <div class="auth-success-desc">
+            已为您开通本服务「{{ serviceName }}」
+          </div>
+          <div class="auth-success-tip">
+            如 WorkBuddy 提示「工具未注册」,请重启 WorkBuddy 后重试
+          </div>
+        </div>
+
+        <!-- 图2 -->
+        <div class="auth-success-verify">
+          <div class="auth-success-verify-label">
+            <icon-svg name="verify_immediately" size="16px" />
+            <span>立即验证 · 复制即用</span>
+          </div>
+          <div class="auth-success-verify-box">
+            <div class="auth-success-example">
+              <span class="auth-success-example-text">{{ exampleQuery }}</span>
+              <button type="button" class="auth-success-copy-btn" @click="copyExample">
+                <icon-svg name="copy_icon_white" size="13px" />
+                <span>复制</span>
+              </button>
+            </div>
+            <div class="auth-success-verify-tips">
+              <div class="auth-success-verify-tip">
+                <icon-svg name="verification_status" size="12px" />
+                <span>看到环评云助手MCP · 调用成功</span>
+              </div>
+              <div class="auth-success-verify-tip">
+                <icon-svg name="verification_status" size="12px" />
+                <span>返回带文件名称、编号、实施日期、适用区域及文本内容的结构化数据 = 接入成功</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 图3 -->
+        <div class="auth-success-faq">
+          <div class="auth-success-faq-label">
+            <img
+              src="https://cdntest.eiacloud.com/ai/coze/icons/thinking.png"
+              alt=""
+              class="auth-success-faq-label-img"
+            />
+            <span>试试这些高频问法</span>
+          </div>
+          <div class="auth-success-faq-list">
+            <div
+              v-for="(item, index) in faqQueries"
+              :key="index"
+              class="auth-success-faq-item"
+            >
+              <span class="auth-success-faq-text">{{ item }}</span>
+              <button type="button" class="auth-success-faq-copy" @click="copyText(item)">
+                <icon-svg name="token_copy" size="13px" />
+              </button>
+            </div>
+          </div>
+        </div>
+
+        <!-- 图4 -->
+        <div class="auth-success-actions">
+          <div class="auth-success-actions-btns">
+            <button type="button" class="auth-success-btn auth-success-btn-primary" @click="openWorkBuddy">
+              <icon-svg name="share_white" size="14px" />
+              <span>立即打开 WorkBuddy</span>
+            </button>
+            <button
+              type="button"
+              class="auth-success-btn auth-success-btn-outline"
+              @click="goMcpHome"
+            >
+              <icon-svg name="share_blue" size="14px" />
+              <span>浏览环评云助手 MCP中心</span>
+            </button>
+          </div>
+          <div v-if="workBuddyLaunching" class="auth-success-launching">
+            正在唤起 WorkBuddy...
+          </div>
+        </div>
+
+        <div class="auth-success-footer">
+          <icon-svg name="visit_icon" size="16px" class="auth-success-footer-icon" />
+          <span class="auth-success-footer-visit">访问</span>
+          <button type="button" class="auth-success-footer-link" @click="goGuide">
+            接入指南
+          </button>
+          <icon-svg name="connection_guide_blue" size="16px" class="auth-success-footer-arrow" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed, onMounted, ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
+import { connectMcpServer } from "@/api/mcp";
+import { ensureMcpToken } from "@/utils/mcpToken";
+
+defineOptions({
+  name: "McpAuthSuccess",
+});
+
+const route = useRoute();
+const router = useRouter();
+const workBuddyLaunching = ref(false);
+const authReady = ref(false);
+const connectedServiceName = ref("");
+
+/** 服务名称:优先接口返回,其次 URL 参数 */
+const serviceName = computed(
+  () =>
+    connectedServiceName.value ||
+    String(route.query.name || route.query.serviceName || "法规导则-关键词检索"),
+);
+
+const exampleQuery = "稀土采选项目审批权限在哪一级?";
+const faqQueries = [
+  "查询大气污染物综合排放标准 GB16297 相关限值要求",
+  "建设项目环境影响评价分类管理名录中化工项目如何分类?",
+  "地下水质量标准 GB/T14848 中Ⅲ类水质的具体指标限值",
+];
+
+const goLogin = () => {
+  router.replace({
+    name: "login",
+    query: { redirect: route.fullPath },
+  });
+};
+
+const initAuth = async () => {
+  authReady.value = false;
+  try {
+    const params = {};
+    if (route.query.id) params.id = String(route.query.id);
+    const res = await connectMcpServer(params);
+    if (Number(res?.code) !== 200) {
+      goLogin();
+      return;
+    }
+    const data = res?.data;
+    if (data && typeof data === "object") {
+      connectedServiceName.value = String(data.name || data.serviceName || "");
+    }
+    await ensureMcpToken();
+    authReady.value = true;
+  } catch (error) {
+    console.error(error);
+    goLogin();
+  }
+};
+
+const copyText = async (text) => {
+  if (!text) {
+    ElMessage.warning("暂无内容可复制");
+    return false;
+  }
+  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("复制成功");
+    return true;
+  } catch (error) {
+    console.error(error);
+    ElMessage.error("复制失败");
+    return false;
+  }
+};
+
+const copyExample = () => copyText(exampleQuery);
+
+const openWorkBuddy = () => {
+  workBuddyLaunching.value = true;
+  const protocolUrl = "workbuddy://";
+  const fallbackUrl = "https://www.workbuddy.ai/";
+  const start = Date.now();
+  const onBlur = () => {
+    window.removeEventListener("blur", onBlur);
+  };
+  window.addEventListener("blur", onBlur);
+  window.location.href = protocolUrl;
+  window.setTimeout(() => {
+    window.removeEventListener("blur", onBlur);
+    if (document.hasFocus() && Date.now() - start >= 1800) {
+      window.open(fallbackUrl, "_blank");
+    }
+    workBuddyLaunching.value = false;
+  }, 2000);
+};
+
+const goMcpHome = () => {
+  router.push({ name: "mcp" });
+};
+
+const goGuide = () => {
+  router.push({ name: "guide" });
+};
+
+onMounted(() => {
+  initAuth();
+});
+</script>
+
+<style scoped lang="scss">
+.auth-success-page {
+  box-sizing: border-box;
+  min-height: calc(100vh - 64px);
+  display: flex;
+  align-items: flex-start;
+  justify-content: center;
+  padding: 48px 16px 64px;
+  background: linear-gradient(135deg, #F0F7FF 0%, #F5F3FF 100%);
+}
+
+.auth-success-card {
+  width: 100%;
+  max-width: 700px;
+  box-sizing: border-box;
+  padding: 0;
+  background: #FFFFFF;
+  border-radius: 20px;
+  overflow: hidden;
+}
+
+.auth-success-head {
+  padding: 12px 32px;
+  border-bottom: 1px solid #F0F2F5;
+}
+
+.auth-success-brand {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.auth-success-brand-title {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 17px;
+  color: #1A3A5C;
+  line-height: 22px;
+  text-align: left;
+}
+
+.auth-success-brand-sub {
+  margin-top: 2px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #8A9AB0;
+  line-height: 16px;
+  text-align: left;
+}
+
+.auth-success-body {
+  padding: 32px 40px;
+}
+
+/* 图1 */
+.auth-success-hero {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+}
+
+.auth-success-check {
+  box-sizing: border-box;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 19px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #3B82F6 0%, #06B6D4 100%);
+  box-shadow: 0px 8px 24px 0px rgba(59, 130, 246, 0.25);
+  color: #FFFFFF;
+}
+
+.auth-success-check-icon {
+  display: block;
+  color: #FFFFFF;
+}
+
+.auth-success-title {
+  margin-top: 20px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 26px;
+  color: #111827;
+  line-height: 32px;
+  text-align: center;
+}
+
+.auth-success-desc {
+  margin-top: 8px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 15px;
+  color: #4B5563;
+  line-height: 24px;
+  text-align: center;
+}
+
+.auth-success-tip {
+  margin-top: 10px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 12px;
+  color: #9CA3AF;
+  line-height: 18px;
+  text-align: center;
+}
+
+/* 图2 */
+.auth-success-verify {
+  margin-top: 20px;
+}
+
+.auth-success-verify-label {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 600;
+  font-size: 14px;
+  color: #1A3A5C;
+  line-height: 20px;
+  text-align: left;
+}
+
+.auth-success-verify-box {
+  margin-top: 20px;
+  padding: 16px;
+  background: #F5F7FA;
+  border-radius: 16px;
+}
+
+.auth-success-example {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 10px 16px;
+  background: #FFFFFF;
+  border-radius: 12px;
+}
+
+.auth-success-example-text {
+  flex: 1;
+  min-width: 0;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 14px;
+  color: #333333;
+  line-height: 22px;
+  text-align: left;
+}
+
+.auth-success-copy-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  flex-shrink: 0;
+  margin: 0;
+  padding: 9px 14px;
+  border: none;
+  border-radius: 8px;
+  background: #1678FF;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 13px;
+  color: #FFFFFF;
+  line-height: 18px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.auth-success-verify-tips {
+  margin-top: 14px;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding-left: 16px;
+}
+
+.auth-success-verify-tip {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 13px;
+  color: #666666;
+  line-height: 20px;
+  text-align: left;
+  font-style: normal;
+  white-space: nowrap;
+
+  span {
+    white-space: nowrap;
+  }
+}
+
+/* 图3 */
+.auth-success-faq {
+  margin-top: 20px;
+}
+
+.auth-success-faq-label {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 600;
+  font-size: 15px;
+  color: #1A3A5C;
+  line-height: 22px;
+  text-align: left;
+}
+
+.auth-success-faq-label-img {
+  width: 24px;
+  height: 24px;
+  flex-shrink: 0;
+  display: block;
+  object-fit: contain;
+}
+
+.auth-success-faq-list {
+  margin-top: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.auth-success-faq-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  padding: 10px 16px;
+  background: #F5F7FA;
+  border-radius: 12px;
+}
+
+.auth-success-faq-text {
+  flex: 1;
+  min-width: 0;
+  font-family: Inter, Inter;
+  font-weight: 500;
+  font-size: 14px;
+  color: #333333;
+  line-height: 22px;
+  text-align: left;
+}
+
+.auth-success-faq-copy {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  margin: 0;
+  padding: 13px;
+  border: none;
+  border-radius: 8px;
+  background: #FFFFFF;
+  color: #94A3B8;
+  cursor: pointer;
+}
+
+/* 图4 */
+.auth-success-actions {
+  margin-top: 20px;
+}
+
+.auth-success-actions-btns {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.auth-success-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  width: 100%;
+  margin: 0;
+  padding: 12px;
+  border-radius: 10px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 15px;
+  line-height: 20px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.auth-success-btn-primary {
+  border: none;
+  background: #1678FF;
+  color: #FFFFFF;
+}
+
+.auth-success-btn-outline {
+  border: 1px solid #D1D5DB;
+  background: #FFFFFF;
+  color: #1678FF;
+}
+
+.auth-success-launching {
+  margin-top: 20px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 13px;
+  color: #9CA3AF;
+  line-height: 18px;
+  text-align: center;
+}
+
+.auth-success-footer {
+  margin-top: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+}
+
+.auth-success-footer-icon,
+.auth-success-footer-arrow {
+  flex-shrink: 0;
+  color: #6B7280;
+}
+
+.auth-success-footer-arrow {
+  color: #1678FF;
+}
+
+.auth-success-footer-visit {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 13px;
+  color: #6B7280;
+  line-height: 18px;
+  text-align: center;
+}
+
+.auth-success-footer-link {
+  margin: 0;
+  padding: 0;
+  border: none;
+  background: transparent;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 13px;
+  color: #1678FF;
+  line-height: 18px;
+  text-align: center;
+  cursor: pointer;
+}
+</style>

+ 296 - 47
src/pages/mcp/onlineTest.vue

@@ -10,7 +10,7 @@
       <!-- 左侧服务列表 -->
       <aside class="otp-side">
         <div class="otp-side-title">
-          <icon-svg name="" size="20px" />
+          <icon-svg name="MCP_serve_violet" size="18px" />
           <span>MCP 服务</span>
         </div>
         <div class="otp-side-list">
@@ -48,35 +48,56 @@
               <div class="otp-header-desc">{{ currentService.describe }}</div>
             </div>
             <div class="otp-header-right">
-              <span v-if="isConnected" class="otp-connect-pill">
-                <i class="otp-connect-dot"></i>
-                <span>已连接</span>
-              </span>
-              <button
-                v-if="isConnected"
-                type="button"
-                class="otp-disconnect-btn"
-                @click="disconnectServer"
-              >
-                <icon-svg name="" size="12px" />
-                <span>断开连接</span>
-              </button>
+              <template v-if="isConnected">
+                <span class="otp-connect-pill">
+                  <i class="otp-connect-dot"></i>
+                  <span>已连接</span>
+                </span>
+                <button
+                  type="button"
+                  class="otp-disconnect-btn"
+                  @click="disconnectServer"
+                >
+                  <icon-svg name="disconnect_icon" size="12px" />
+                  <span>断开连接</span>
+                </button>
+              </template>
+              <template v-else>
+                <span class="otp-disconnect-pill">
+                  <i class="otp-disconnect-dot"></i>
+                  <span>未连接</span>
+                </span>
+                <button
+                  type="button"
+                  class="otp-connect-btn"
+                  :disabled="connecting || !activeServiceId"
+                  @click="connectServer"
+                >
+                  <icon-svg name="connection_service" size="12px" />
+                  <span>{{ connecting ? "连接中..." : "连接服务" }}</span>
+                </button>
+              </template>
             </div>
           </div>
         </section>
 
+        <div v-if="!isConnected" class="otp-connect-warn">
+          <icon-svg name="service_warning" size="14px" class="otp-connect-warn-icon" />
+          <span class="otp-connect-warn-text">当前服务未开启,请先连接服务后进行测试</span>
+        </div>
+
         <section class="otp-card otp-info-card">
           <div class="otp-info-row">
             <span class="otp-info-label">服务名称</span>
-            <span class="otp-info-value">{{ currentTool.name || "-" }}</span>
+            <span class="otp-info-value">{{ displayServiceName }}</span>
           </div>
           <div class="otp-info-row">
             <span class="otp-info-label">服务 URL</span>
-            <span class="otp-info-value">{{ currentTool.restfulUrl || "-" }}</span>
+            <span class="otp-info-value">{{ displayServiceUrl }}</span>
           </div>
           <div class="otp-info-row">
             <span class="otp-info-label">连接类型</span>
-            <span class="otp-info-value otp-info-value--medium">{{ currentTool.reqMethod || "-" }}</span>
+            <span class="otp-info-value otp-info-value--medium">{{ displayConnectType }}</span>
           </div>
           <div class="otp-info-row otp-info-row--token">
             <span class="otp-info-label">请求头配置</span>
@@ -119,8 +140,15 @@
         <section class="otp-card otp-tools-card">
           <div class="otp-tools-head">
             <span class="otp-tools-title">可用工具</span>
-            <span class="otp-tools-count">{{ tools.length }}/{{ tools.length }}</span>
+            <span v-if="isConnected" class="otp-tools-count">{{ tools.length }}/{{ tools.length }}</span>
+          </div>
+
+          <div v-if="!isConnected" class="otp-tools-empty">
+            <icon-svg name="available_loading_tools" size="24px" class="otp-tools-empty-icon" />
+            <div class="otp-tools-empty-text">请先连接MCP服务以加载可用工具</div>
           </div>
+
+          <template v-else>
           <div class="otp-tool-list">
             <button
               v-for="tool in tools"
@@ -253,6 +281,7 @@
             <span>{{ running ? "测试中..." : "运行测试" }}</span>
           </button>
           </div>
+          </template>
         </section>
       </main>
 
@@ -263,11 +292,17 @@
             <div class="otp-result-title">响应结果</div>
             <div class="otp-result-actions">
               <button type="button" class="otp-result-action" @click="copyText(responseJson)">
-                <icon-svg name="" size="12px" />
+                <icon-svg
+                  :name="hasTestResult ? 'copy_the_response_result' : 'no_response_copy'"
+                  size="12px"
+                />
                 <span>复制</span>
               </button>
-              <button type="button" class="otp-result-action">
-                <icon-svg name="" size="12px" />
+              <button type="button" class="otp-result-action" @click="downloadResponseJson">
+                <icon-svg
+                  :name="hasTestResult ? 'download_of_response_results' : 'no_response_to_download'"
+                  size="12px"
+                />
                 <span>下载</span>
               </button>
             </div>
@@ -276,10 +311,10 @@
           <template v-if="!hasTestResult">
             <div class="otp-result-empty">
               <div class="otp-result-empty-icon">
-                <icon-svg name="effect_experience" size="28px" />
+                <icon-svg name="no_response_result" size="24px" />
               </div>
               <div class="otp-result-empty-title">调用工具后查看结果</div>
-              <div class="otp-result-empty-desc">左侧选服务 · 连接后选择工具调用</div>
+              <div class="otp-result-empty-desc">左侧选服务 - 连接后选择工具调用</div>
             </div>
             <div class="otp-result-empty-foot">响应结果将以 JSON 格式展示</div>
           </template>
@@ -399,9 +434,40 @@ import globalStore from "@/store/modules/global";
 const route = useRoute();
 const global = globalStore();
 
+const CONNECT_STATE_KEY = "mcp_online_test_connect_state";
+
+const readConnectState = () => {
+  try {
+    const raw = localStorage.getItem(CONNECT_STATE_KEY);
+    if (!raw) return { connected: false, serviceId: "" };
+    const parsed = JSON.parse(raw);
+    return {
+      connected: Boolean(parsed?.connected),
+      serviceId: parsed?.serviceId ? String(parsed.serviceId) : "",
+    };
+  } catch {
+    return { connected: false, serviceId: "" };
+  }
+};
+
+const saveConnectState = (connected, serviceId = "") => {
+  try {
+    localStorage.setItem(
+      CONNECT_STATE_KEY,
+      JSON.stringify({
+        connected: Boolean(connected),
+        serviceId: serviceId ? String(serviceId) : "",
+      }),
+    );
+  } catch (error) {
+    console.error(error);
+  }
+};
+
 const serviceList = ref([]);
 const activeServiceId = ref("");
 const isConnected = ref(false);
+const connecting = ref(false);
 const running = ref(false);
 // 在线测试检索结果
 const searchResult = ref(null);
@@ -452,25 +518,68 @@ const fetchServerList = async () => {
     serviceList.value = openList.map(mapServerItem);
     global.setMcpServerList(list);
 
+    const saved = readConnectState();
     const queryId = route.query.id ? String(route.query.id) : "";
-    const matched = serviceList.value.find((item) => item.id === queryId);
-    activeServiceId.value = matched?.id || serviceList.value[0]?.id || "";
+    const matchedQuery = serviceList.value.find((item) => item.id === queryId);
+    const matchedSaved = serviceList.value.find((item) => item.id === saved.serviceId);
+    activeServiceId.value =
+      matchedQuery?.id || matchedSaved?.id || serviceList.value[0]?.id || "";
   } catch (error) {
     console.error(error);
     serviceList.value = [];
     activeServiceId.value = "";
     isConnected.value = false;
+    saveConnectState(false, "");
   }
 };
 
+const resetTestState = () => {
+  tools.value = [];
+  activeToolCode.value = "";
+  requestParams.value = [];
+  responseStructure.value = [];
+  requestJsonText.value = "{}";
+  searchResult.value = null;
+  resultMeta.value = { code: null, time: null, ok: false, label: "-" };
+};
+
 const disconnectServer = () => {
-  // 断开接口后续接入,先本地置为未连接
+  // 断开接口后续接入,先按前端状态切到未连接展示
+  ++toolRequestId.value;
   isConnected.value = false;
+  connecting.value = false;
+  resetTestState();
+  saveConnectState(false, activeServiceId.value);
+};
+
+const connectServer = async () => {
+  if (!activeServiceId.value || connecting.value) return;
+  connecting.value = true;
+  try {
+    await fetchToolsByServerId(activeServiceId.value);
+  } finally {
+    connecting.value = false;
+  }
 };
 
 watch(
   activeServiceId,
-  (id) => {
+  (id, prevId) => {
+    // 首次进入/刷新:按本地记录恢复连接或未连接
+    if (!prevId) {
+      const saved = readConnectState();
+      if (saved.connected && id) {
+        fetchToolsByServerId(id);
+      } else {
+        ++toolRequestId.value;
+        isConnected.value = false;
+        connecting.value = false;
+        resetTestState();
+        saveConnectState(false, id);
+      }
+      return;
+    }
+    // 切换服务时重新拉取并进入已连接(有工具则连上)
     fetchToolsByServerId(id);
   },
 );
@@ -599,6 +708,7 @@ const fetchToolsByServerId = async (serverId) => {
     requestParams.value = [];
     responseStructure.value = [];
     isConnected.value = false;
+    saveConnectState(false, "");
     return;
   }
   isConnected.value = false;
@@ -614,6 +724,7 @@ const fetchToolsByServerId = async (serverId) => {
       requestParams.value = [];
       responseStructure.value = [];
       isConnected.value = false;
+      saveConnectState(false, serverId);
       return;
     }
     const detailList = await Promise.all(
@@ -637,6 +748,7 @@ const fetchToolsByServerId = async (serverId) => {
     tools.value = detailList.filter(Boolean);
     activeToolCode.value = tools.value[0]?.code || "";
     isConnected.value = tools.value.length > 0;
+    saveConnectState(isConnected.value, serverId);
     syncParamsFromTool();
   } catch (error) {
     if (requestId !== toolRequestId.value) return;
@@ -646,6 +758,7 @@ const fetchToolsByServerId = async (serverId) => {
     requestParams.value = [];
     responseStructure.value = [];
     isConnected.value = false;
+    saveConnectState(false, serverId);
   }
 };
 
@@ -710,6 +823,28 @@ const currentTool = computed(
     emptyTool,
 );
 
+const displayServiceName = computed(
+  () =>
+    (isConnected.value ? currentTool.value.name : "") ||
+    currentService.value.serviceName ||
+    currentService.value.name ||
+    "-",
+);
+
+const displayServiceUrl = computed(
+  () =>
+    (isConnected.value ? currentTool.value.restfulUrl : "") ||
+    currentService.value.url ||
+    "-",
+);
+
+const displayConnectType = computed(
+  () =>
+    (isConnected.value ? currentTool.value.reqMethod : "") ||
+    currentService.value.connectType ||
+    "streamableHttp",
+);
+
 const responseJson = computed(() => {
   if (!searchResult.value) return "暂无结果,请点击「运行测试」";
   if (resultTab.value === "业务数据") {
@@ -819,6 +954,7 @@ const copyText = async (text) => {
 };
 
 const runTest = async () => {
+  if (!isConnected.value) return;
   const code = currentTool.value?.code;
   if (!code || running.value) return;
 
@@ -1135,6 +1271,110 @@ const runTest = async () => {
   cursor: pointer;
 }
 
+.otp-disconnect-pill {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  background: #F1F5F9;
+  border-radius: 6px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 12px;
+  color: #64748B;
+  line-height: 16px;
+  text-align: left;
+}
+
+.otp-disconnect-dot {
+  width: 6px;
+  height: 6px;
+  background: #CBD5E1;
+  border-radius: 6px;
+  flex-shrink: 0;
+}
+
+.otp-connect-btn {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  margin: 0;
+  padding: 6px 10px;
+  background: linear-gradient(135deg, #3B82F6 0%, #6366F1 100%);
+  box-shadow: 0px 2px 8px 0px rgba(59, 130, 246, 0.25);
+  border: none;
+  border-radius: 8px;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 14px;
+  color: #FFFFFF;
+  line-height: 20px;
+  text-align: left;
+  cursor: pointer;
+}
+
+.otp-connect-btn:disabled {
+  opacity: 0.65;
+  cursor: not-allowed;
+}
+
+.otp-connect-warn {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  /* 抵消 .otp-main 的 gap:16px,使上下视觉间距为 14px */
+  margin: calc(14px - 16px) 0;
+  padding: 10px 14px;
+  background: #FFF7ED;
+  border-radius: 8px;
+  border: 1px solid #FED7AA;
+  box-sizing: border-box;
+}
+
+.otp-connect-warn-icon {
+  flex-shrink: 0;
+  width: 14px;
+  height: 14px;
+}
+
+.otp-connect-warn-text {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 14px;
+  color: #92400E;
+  line-height: 20px;
+  text-align: left;
+}
+
+.otp-tools-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 14px;
+  margin-top: 12px;
+  padding: 30px;
+  background: #F8FAFC;
+  border-radius: 8px;
+  border: 1px solid #E2E8F0;
+  box-sizing: border-box;
+}
+
+.otp-tools-empty-icon {
+  flex-shrink: 0;
+  width: 24px;
+  height: 24px;
+}
+
+.otp-tools-empty-text {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 400;
+  font-size: 14px;
+  color: #64748B;
+  line-height: 20px;
+  text-align: left;
+}
+
 .otp-info-card {
   display: flex;
   flex-direction: column;
@@ -1728,9 +1968,11 @@ const runTest = async () => {
 
   &.is-empty {
     gap: 0;
-    background: #1E293B;
+    padding: 0;
+    background: #0F172A;
     box-shadow: none;
     border-color: transparent;
+    overflow: hidden;
   }
 }
 
@@ -1744,7 +1986,8 @@ const runTest = async () => {
 }
 
 .otp-result-card.is-empty .otp-result-head {
-  border-bottom-color: rgba(148, 163, 184, 0.24);
+  padding: 12px 16px;
+  border-bottom: 1px solid rgba(148, 163, 184, 0.24);
 }
 
 .otp-result-title {
@@ -1757,7 +2000,7 @@ const runTest = async () => {
 }
 
 .otp-result-card.is-empty .otp-result-title {
-  color: #FFFFFF;
+  color: #E2E8F0;
 }
 
 .otp-result-actions {
@@ -1784,9 +2027,13 @@ const runTest = async () => {
 }
 
 .otp-result-card.is-empty .otp-result-action {
-  border-color: transparent;
-  background: #334155;
-  color: #CBD5E1;
+  gap: 6px;
+  padding: 6px 8px;
+  background: #1E293B;
+  border: none;
+  border-radius: 6px;
+  color: #94A3B8;
+  line-height: 16px;
 }
 
 .otp-result-empty {
@@ -1795,18 +2042,18 @@ const runTest = async () => {
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  padding: 40px 16px;
+  padding: 24px 16px;
   text-align: center;
 }
 
 .otp-result-empty-icon {
+  box-sizing: border-box;
   display: inline-flex;
   align-items: center;
   justify-content: center;
-  width: 64px;
-  height: 64px;
+  padding: 20px;
   border-radius: 16px;
-  background: #334155;
+  background: #1E293B;
   color: #FFFFFF;
 }
 
@@ -1814,28 +2061,30 @@ const runTest = async () => {
   margin-top: 16px;
   font-family: PingFang SC, PingFang SC;
   font-weight: 500;
-  font-size: 15px;
-  color: #FFFFFF;
-  line-height: 22px;
+  font-size: 14px;
+  color: #E2E8F0;
+  line-height: 20px;
+  text-align: center;
 }
 
 .otp-result-empty-desc {
-  margin-top: 8px;
+  margin-top: 6px;
   font-family: PingFang SC, PingFang SC;
   font-weight: 400;
   font-size: 12px;
-  color: #94A3B8;
-  line-height: 18px;
+  color: #64748B;
+  line-height: 16px;
+  text-align: center;
 }
 
 .otp-result-empty-foot {
-  padding-top: 14px;
+  padding: 12px;
   border-top: 1px solid rgba(148, 163, 184, 0.24);
   font-family: PingFang SC, PingFang SC;
   font-weight: 400;
-  font-size: 12px;
-  color: #64748B;
-  line-height: 18px;
+  font-size: 11px;
+  color: #475569;
+  line-height: 14px;
   text-align: center;
 }
 

+ 6 - 0
src/pages/mcp/serverDetail.vue

@@ -585,6 +585,12 @@ const fetchServerDetail = async () => {
       return;
     }
 
+    // 无 id 进入时补全到地址栏,保证可分享/刷新
+    if (!route.query.id) {
+      router.replace({ name: "mcpServer", query: { id } });
+      return;
+    }
+
     // 先用列表缓存填充概览,减少切换白屏/卡顿感
     const cached = global.mcpServerList.find((item) => String(item.id) === String(id));
     if (cached) {

+ 31 - 5
src/pages/pc/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-shell">
+  <div ref="pageShellRef" class="page-shell">
     <Header
       v-if="showDesktopHeader && !isFullScreen"
       :is-logged-in="Boolean(WEBSID)"
@@ -10,21 +10,27 @@
       <router-view></router-view>
     </div>
   </div>
-  <Login @save-user="(data) => saveUserInfo(data)" v-if="loginVisable" />
+  <Login
+    v-if="loginVisable && route.name !== 'login'"
+    @save-user="(data) => saveUserInfo(data)"
+  />
 </template>
 
 <script setup>
-import { computed, onBeforeMount } from "vue";
+import { computed, nextTick, onBeforeMount, ref, watch } from "vue";
 import Header from "@/components/pcHeader.vue";
 import Login from "@/components/login.vue";
 import globalStore from "@/store/modules/global";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { getMenuPromission } from "@/api/login";
 import { IsPC } from "@/utils/common";
+import { ensureMcpToken } from "@/utils/mcpToken";
 
 const isPC = IsPC();
 const route = useRoute();
+const router = useRouter();
 const global = globalStore();
+const pageShellRef = ref(null);
 
 const WEBSID = computed(() => global.WEBSID);
 const loginVisable = computed(() => global.loginVisable);
@@ -34,8 +40,27 @@ const userInfo = computed(() => global.userInfo);
 const isFullScreen = computed(() => Boolean(route.meta?.fullScreen));
 const showDesktopHeader = computed(() => isPC);
 
+const scrollPageToTop = () => {
+  nextTick(() => {
+    if (pageShellRef.value) {
+      pageShellRef.value.scrollTop = 0;
+    }
+    window.scrollTo(0, 0);
+    document.documentElement.scrollTop = 0;
+    document.body.scrollTop = 0;
+  });
+};
+
+watch(
+  () => route.fullPath,
+  () => {
+    scrollPageToTop();
+  },
+);
+
 const openLoginDialog = () => {
-  global.triggerLoginVisible(true);
+  if (route.name === "login") return;
+  router.push({ name: "login" });
 };
 
 const saveUserInfo = async (userInfo) => {
@@ -45,6 +70,7 @@ const saveUserInfo = async (userInfo) => {
   global.setYundian(userInfo?.ydCount);
   const res = await getMenuPromission();
   globalStore().promission = res?.data ?? [];
+  await ensureMcpToken();
 };
 
 onBeforeMount(async () => {

+ 61 - 2
src/router/index.js

@@ -1,4 +1,9 @@
 import { createRouter, createWebHashHistory } from "vue-router";
+import { getCookie } from "@/utils/auth";
+import globalStore from "@/store/modules/global";
+
+/** 未登录可访问的路由 */
+const AUTH_WHITE_LIST = new Set(["login", "wechatLogin"]);
 
 const routes = [
   {
@@ -7,7 +12,15 @@ const routes = [
     children: [
       {
         path: "",
-        redirect: { name: "mcp" },
+        redirect: { name: "mcpAuth" },
+      },
+      {
+        path: "login",
+        name: "login",
+        component: () => import("@/pages/login/index.vue"),
+        meta: {
+          title: "登录",
+        },
       },
       {
         path: "mcp",
@@ -24,6 +37,14 @@ const routes = [
             component: () => import("@/pages/mcp/onlineTest.vue"),
           },
           {
+            path: "auth",
+            name: "mcpAuth",
+            component: () => import("@/pages/mcp/authSuccess.vue"),
+            meta: {
+              title: "授权成功",
+            },
+          },
+          {
             path: "",
             component: () => import("@/pages/home/mcpIndex.vue"),
             children: [
@@ -66,10 +87,48 @@ const routes = [
 const router = createRouter({
   history: createWebHashHistory(import.meta.env.BASE_URL),
   routes,
+  scrollBehavior() {
+    return { left: 0, top: 0 };
+  },
+});
+
+const isLoggedIn = () => {
+  const global = globalStore();
+  return !!(getCookie("WEBSID") || global.WEBSID);
+};
+
+router.beforeEach((to, _from, next) => {
+  const loggedIn = isLoggedIn();
+
+  if (AUTH_WHITE_LIST.has(to.name)) {
+    if (loggedIn && to.name === "login") {
+      const redirect = to.query.redirect;
+      if (redirect) {
+        next(String(redirect));
+        return;
+      }
+      next({ name: "mcpAuth" });
+      return;
+    }
+    next();
+    return;
+  }
+
+  if (!loggedIn) {
+    next({
+      name: "login",
+      query: {
+        redirect: to.fullPath,
+      },
+    });
+    return;
+  }
+
+  next();
 });
 
 router.afterEach((to) => {
-  const defaultTitle = "尚云 AI";
+  const defaultTitle = "尚云 MCP";
   const title = to.meta && to.meta.title ? to.meta.title : defaultTitle;
   document.title = title;
 });

+ 1 - 1
src/utils/config.js

@@ -1,5 +1,5 @@
 // 全局判断,true为测试,false为生产
-export const isPcBoolean = false;
+export const isPcBoolean = true;
 // 平台配置:'pc'
 export const PLATFORM = 'pc';
 

+ 34 - 0
src/utils/mcpToken.js

@@ -0,0 +1,34 @@
+import { getMcpToken, createMcpToken } from "@/api/mcp";
+import globalStore from "@/store/modules/global";
+
+/** 将接口返回写入 store(兼容字符串或 { token }) */
+export function applyMcpTokenData(data) {
+  const global = globalStore();
+  const token =
+    typeof data === "string"
+      ? data
+      : data && typeof data === "object"
+        ? data.token
+        : "";
+  if (token === null || token === undefined || token === "") {
+    global.setMcpToken("");
+    return "";
+  }
+  const value = String(token);
+  global.setMcpToken(value);
+  return value;
+}
+
+/** 登录后拉取 MCP Token;若无则创建并保存 */
+export async function ensureMcpToken() {
+  try {
+    const res = await getMcpToken();
+    const existing = applyMcpTokenData(res?.data);
+    if (existing) return existing;
+    const created = await createMcpToken();
+    return applyMcpTokenData(created?.data);
+  } catch (error) {
+    console.error(error);
+    return "";
+  }
+}

+ 3 - 4
src/utils/request.js

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

Некоторые файлы не были показаны из-за большого количества измененных файлов