serve.vue 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049
  1. <template>
  2. <div class="serve-page">
  3. <section class="registry-head">
  4. <div class="wrap">
  5. <div class="registry-inner">
  6. <div class="registry-text">
  7. <h1>MCP<span class="brand-blue">服务中心</span></h1>
  8. <div class="sub-title">企业级 MCP 智能服务中枢,打通大模型与环评专业能力</div>
  9. <p class="lead">
  10. 汇聚环评云助手全量 MCP 智能服务,遵循标准 MCP 服务目录规范。统一 Token 即可接入主流大模型,快速将法规检索、政策问答、排放限值等环评能力注入 AI 应用与 Agent,服务持续迭代更新
  11. </p>
  12. <div class="cta-row">
  13. <button type="button" class="btn-cta" :disabled="giftClaiming" @click="claimGift()">
  14. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg>
  15. 领取MCP体验礼包
  16. </button>
  17. <button type="button" class="btn-ghost" @click="scrollToMatrix">
  18. 浏览全部服务
  19. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"></path><path d="m19 12-7 7-7-7"></path></svg>
  20. </button>
  21. </div>
  22. <div class="hero-meta">
  23. <span class="hm-item">
  24. <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" fill="#EAF2FF"></circle><path d="m8.5 12.2 2.4 2.4 4.8-5" stroke="#1678FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
  25. 统一接入 <b>生态环境领域最全数据</b>
  26. </span>
  27. <span class="hm-item">
  28. <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" fill="#EAF2FF"></circle><path d="m8.5 12.2 2.4 2.4 4.8-5" stroke="#1678FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
  29. 标准 MCP 协议接入
  30. </span>
  31. <span class="hm-item">
  32. <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" fill="#EAF2FF"></circle><path d="m8.5 12.2 2.4 2.4 4.8-5" stroke="#1678FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
  33. 统一 Token 授权
  34. </span>
  35. </div>
  36. </div>
  37. <div class="hero-visual" aria-hidden="true">
  38. <div class="hv-card">
  39. <div class="hv-head">
  40. <div class="hv-title">
  41. <span class="hv-sm">
  42. <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="6" r="3"></circle><circle cx="18" cy="18" r="3"></circle><path d="M8.7 10.7 15.3 7.3"></path><path d="m8.7 13.3 6.6 3.4"></path></svg>
  43. </span>
  44. MCP 服务中枢
  45. </div>
  46. <span class="hv-live"><span class="hv-ping"></span>实时连接</span>
  47. </div>
  48. <div class="hv-flow">
  49. <div class="hv-node">
  50. <span class="hv-ico app">
  51. <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2.5"></rect><rect x="10" y="10" width="4" height="4" rx="1"></rect><path d="M9 2.5v3M15 2.5v3M9 18.5v3M15 18.5v3M2.5 9h3M2.5 15h3M18.5 9h3M18.5 15h3"></path></svg>
  52. </span>
  53. <span class="hv-nt"><span class="hv-n1">AI 应用 / Agent</span><span class="hv-n2">智能体即插即用</span></span>
  54. </div>
  55. <div class="hv-line"></div>
  56. <div class="hv-node">
  57. <span class="hv-ico hub">
  58. <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 10 5.5L12 13 2 7.5z"></path><path d="m2 12.5 10 5.5 10-5.5"></path><path d="m2 17.5 10 5.5 10-5.5"></path></svg>
  59. </span>
  60. <span class="hv-nt"><span class="hv-n1">环评云助手 MCP</span><span class="hv-n2">统一接入服务目录</span></span>
  61. </div>
  62. </div>
  63. <div class="hv-grid">
  64. <div class="hv-tile" style="color:#059669;">
  65. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>
  66. <span class="tx">法规检索</span>
  67. </div>
  68. <div class="hv-tile" style="color:#3B82F6;">
  69. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"></circle><path d="m21 21-4.3-4.3"></path><path d="M11 8v6"></path><path d="M8 11h6"></path></svg>
  70. <span class="tx">语义检索</span>
  71. </div>
  72. <div class="hv-tile" style="color:#7C3AED;">
  73. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8z"></path></svg>
  74. <span class="tx">政策问答</span>
  75. </div>
  76. <div class="hv-tile" style="color:#E8890C;">
  77. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>
  78. <span class="tx">排放限值</span>
  79. </div>
  80. <div class="hv-tile" style="color:#EF4444;">
  81. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M16 13H8"></path><path d="M16 17H8"></path><path d="M10 9H8"></path></svg>
  82. <span class="tx">报告检索</span>
  83. </div>
  84. <div class="hv-tile" style="color:#6366F1;">
  85. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 10 5.5L12 13 2 7.5z"></path><path d="m2 12.5 10 5.5 10-5.5"></path><path d="m2 17.5 10 5.5 10-5.5"></path></svg>
  86. <span class="tx">标准汇编</span>
  87. </div>
  88. </div>
  89. </div>
  90. <div class="hv-chip">生态环境领域最全数据</div>
  91. </div>
  92. </div>
  93. </div>
  94. </section>
  95. <section class="section" id="matrix">
  96. <div class="wrap">
  97. <div class="notice-bar">
  98. <span class="notice-horn">
  99. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l18-5v12L3 13v-2z"></path><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"></path></svg>
  100. </span>
  101. <div class="notice-roll">
  102. <span class="notice-text">环评云助手MCP工具限时免费调用,欢迎咨询和反馈。</span>
  103. </div>
  104. </div>
  105. <div class="matrix-bar">
  106. <div class="matrix-bar-left">
  107. <span class="matrix-count">{{ matrixCountText }}</span>
  108. </div>
  109. <div class="matrix-bar-right">
  110. <div class="matrix-search">
  111. <svg class="matrix-search-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg>
  112. <input
  113. v-model.trim="searchKeyword"
  114. type="text"
  115. placeholder="搜索服务或工具关键字"
  116. autocomplete="off"
  117. />
  118. <button
  119. v-show="searchKeyword"
  120. type="button"
  121. class="matrix-search-clear show"
  122. aria-label="清空"
  123. @click="searchKeyword = ''"
  124. >
  125. <svg width="8" height="8" viewBox="0 0 8 8" aria-hidden="true">
  126. <path
  127. d="M1.5 1.5l5 5M6.5 1.5l-5 5"
  128. fill="none"
  129. stroke="currentColor"
  130. stroke-width="1.4"
  131. stroke-linecap="round"
  132. />
  133. </svg>
  134. </button>
  135. </div>
  136. </div>
  137. </div>
  138. <div ref="serviceGridRef" class="service-grid masonry">
  139. <div
  140. v-for="(column, colIndex) in masonryColumns"
  141. :key="colIndex"
  142. class="masonry-col"
  143. >
  144. <article
  145. v-for="item in column"
  146. :key="item.id"
  147. class="svc-card"
  148. >
  149. <div class="svc-top">
  150. <div class="svc-icon" :style="item.iconStyle">
  151. <img
  152. v-if="item.iconKind === 'img'"
  153. :src="item.iconSrc"
  154. :alt="item.name"
  155. class="svc-icon-img"
  156. decoding="async"
  157. referrerpolicy="no-referrer"
  158. @error="onIconError(item)"
  159. />
  160. <span
  161. v-else-if="item.iconKind === 'inline'"
  162. class="svc-icon-inline"
  163. v-html="item.iconSrc"
  164. ></span>
  165. <svg v-else class="svc-icon-fallback" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 10 5.5L12 13 2 7.5z"></path><path d="m2 12.5 10 5.5 10-5.5"></path><path d="m2 17.5 10 5.5 10-5.5"></path></svg>
  166. </div>
  167. <h3>{{ item.name }}</h3>
  168. </div>
  169. <p class="svc-desc">{{ item.desc }}</p>
  170. <div class="svc-tools">
  171. <b>包含工具</b>
  172. <em>{{ item.tools?.length || 0 }} 个</em>
  173. </div>
  174. <div class="svc-tool-list">
  175. <div
  176. v-for="(tool, index) in item.tools"
  177. :key="tool.id || tool.code"
  178. class="tool-item"
  179. :class="{ active: selectedTool[item.id] === index }"
  180. @click="selectedTool[item.id] = index"
  181. >
  182. <span class="tool-name">{{ tool.label }}</span>
  183. <span class="tool-fee">
  184. <template v-if="tool.fee === 'limit'">
  185. <span class="fee-tag rate">{{ tool.rate }}x tokens</span>
  186. <span class="fee-tag limitfree">限时免费</span>
  187. </template>
  188. <span v-else class="fee-tag free">免费工具</span>
  189. </span>
  190. </div>
  191. </div>
  192. <div class="svc-actions">
  193. <button type="button" class="btn-sm ghost" @click="openServiceDetail(item)">
  194. <svg class="btn-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z"></path><circle cx="12" cy="12" r="3"></circle></svg>
  195. 查看详情
  196. </button>
  197. <button type="button" class="btn-sm primary" @click="openServiceSimulate(item)">
  198. <svg class="btn-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4.5 13.5H11l-1 8.5 8.5-11.5H12z"></path></svg>
  199. 模拟调用
  200. </button>
  201. </div>
  202. </article>
  203. </div>
  204. </div>
  205. <div v-show="!filteredServiceList.length" class="matrix-empty">
  206. 当前筛选条件下没有匹配的服务。
  207. </div>
  208. <div v-show="filteredServiceList.length" class="matrix-end show">
  209. 已经到底啦,共 <b>{{ filteredServiceList.length }}</b> 个服务全部加载完成
  210. </div>
  211. </div>
  212. </section>
  213. <footer class="site-footer">
  214. <div class="wrap">
  215. <div class="footer-main">
  216. <div class="footer-brand">
  217. <div class="fb-title">
  218. <span class="fb-logo">云</span>
  219. 环评云助手 · MCP服务中心
  220. </div>
  221. <p class="fb-desc">统一 Token 接入主流大模型,一站式调用法规检索、政策问答、排放限值等环评 MCP 能力。</p>
  222. </div>
  223. <div class="footer-col">
  224. <h4>本页快捷跳转</h4>
  225. <ul>
  226. <li><button type="button" :disabled="giftClaiming" @click="claimGift()"><span class="footer-link-ico">◆</span>领取MCP体验礼包</button></li>
  227. <li><button type="button" @click="scrollToTop"><span class="footer-link-ico">↑</span>回到顶部</button></li>
  228. </ul>
  229. </div>
  230. <div class="footer-col">
  231. <h4>快捷产品入口</h4>
  232. <ul>
  233. <li><button type="button" @click="openRouteInNewTab('mcp')"><span class="footer-link-ico">➤</span>MCP 服务</button></li>
  234. <li><a href="https://www.eiacloud.com" target="_blank" rel="noopener noreferrer"><span class="footer-link-ico">➤</span>环评云助手官网</a></li>
  235. <li><a href="https://www.eiacloud.com/bbs" target="_blank" rel="noopener noreferrer"><span class="footer-link-ico">➤</span>环评论坛</a></li>
  236. </ul>
  237. </div>
  238. <div class="footer-col">
  239. <h4>服务信息</h4>
  240. <ul>
  241. <li><a href="https://www.eiacloud.com/hpy/lawstandardApp/getDetailYS?type=agreement" target="_blank" rel="noopener noreferrer"><span class="footer-link-ico">◎</span>用户协议</a></li>
  242. <li><a href="https://www.eiacloud.com/hpy/lawstandardApp/getDetailYS" target="_blank" rel="noopener noreferrer"><span class="footer-link-ico">◎</span>隐私政策</a></li>
  243. <li class="footer-cs">
  244. <button type="button">
  245. <span class="footer-link-ico">◎</span>联系客服获取接入支持
  246. </button>
  247. <div class="home-footer-popover">
  248. <img src="@/assets/images/service_WeChat.png" alt="云助手客服微信" class="home-footer-popover-img" />
  249. <div class="home-footer-popover-sub">云助手客服微信</div>
  250. </div>
  251. </li>
  252. </ul>
  253. </div>
  254. </div>
  255. </div>
  256. </footer>
  257. <!-- 未登录:需先登录弹窗 -->
  258. <Teleport to="body">
  259. <div
  260. v-if="needLoginVisible"
  261. class="mcp-serve-gift wb-overlay show"
  262. @click.self="needLoginVisible = false"
  263. >
  264. <div class="wb-modal wb-gift-modal" role="dialog" aria-modal="true" aria-labelledby="needLoginTitle">
  265. <button type="button" class="wb-close" aria-label="关闭" @click="needLoginVisible = false">
  266. <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true">
  267. <path
  268. d="M2.5 2.5l7 7M9.5 2.5l-7 7"
  269. fill="none"
  270. stroke="currentColor"
  271. stroke-width="1.6"
  272. stroke-linecap="round"
  273. />
  274. </svg>
  275. </button>
  276. <div class="wb-gift-hero">
  277. <div class="wb-gift-kicker">MCP 体验礼包</div>
  278. <div class="wb-gift-visual" aria-hidden="true">
  279. <svg class="wb-gift-icon" viewBox="0 0 48 48" fill="none" stroke="#E8890C" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
  280. <rect x="6" y="16" width="36" height="26" rx="3.5"></rect>
  281. <rect x="3" y="10" width="42" height="8" rx="2.6"></rect>
  282. <path d="M24 10v32"></path>
  283. <path d="M24 10c-3.6 0-8.4-.7-8.4-4.4 0-2.4 1.9-3.6 3.8-3.6 3.2 0 4.6 4 4.6 8z"></path>
  284. <path d="M24 10c3.6 0 8.4-.7 8.4-4.4 0-2.4-1.9-3.6-3.8-3.6-3.2 0-4.6 4-4.6 8z"></path>
  285. </svg>
  286. </div>
  287. <h3 class="wb-gift-title" id="needLoginTitle">登录即可领取 {{ giftPointsText }} 云点体验礼包</h3>
  288. <p class="wb-gift-sub">用于 MCP 工具调用体验,约可完成 200 次提问</p>
  289. </div>
  290. <div class="wb-modal-foot wb-gift-foot">
  291. <button type="button" class="wb-btn ghost" @click="needLoginVisible = false">暂不领取</button>
  292. <button type="button" class="wb-btn primary" @click="goLoginToClaim">去登录领取</button>
  293. </div>
  294. </div>
  295. </div>
  296. <!-- 领取成功弹窗 -->
  297. <div
  298. v-if="claimSuccessVisible"
  299. class="mcp-serve-gift wb-overlay show"
  300. @click.self="claimSuccessVisible = false"
  301. >
  302. <div class="wb-modal wb-gift-modal" role="dialog" aria-modal="true" aria-labelledby="claimSuccessName">
  303. <button type="button" class="wb-close" aria-label="关闭" @click="claimSuccessVisible = false">
  304. <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true">
  305. <path
  306. d="M2.5 2.5l7 7M9.5 2.5l-7 7"
  307. fill="none"
  308. stroke="currentColor"
  309. stroke-width="1.6"
  310. stroke-linecap="round"
  311. />
  312. </svg>
  313. </button>
  314. <div class="wb-gift-hero">
  315. <div class="wb-gift-kicker is-ok">领取成功</div>
  316. <div
  317. class="wb-gift-visual is-ok"
  318. :class="{ 'wb-gift-open': giftOpenAnim }"
  319. aria-hidden="true"
  320. >
  321. <svg class="wb-gift-icon" viewBox="0 0 48 48" fill="none" stroke="#148A45" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
  322. <rect x="6" y="16" width="36" height="26" rx="3.5"></rect>
  323. <rect class="gift-lid" x="3" y="10" width="42" height="8" rx="2.6"></rect>
  324. <path d="M24 10v32"></path>
  325. <path d="M24 10c-3.6 0-8.4-.7-8.4-4.4 0-2.4 1.9-3.6 3.8-3.6 3.2 0 4.6 4 4.6 8z"></path>
  326. <path d="M24 10c3.6 0 8.4-.7 8.4-4.4 0-2.4-1.9-3.6-3.8-3.6-3.2 0-4.6 4-4.6 8z"></path>
  327. </svg>
  328. </div>
  329. <h3 class="wb-gift-title" id="claimSuccessName">MCP 体验礼包领取成功</h3>
  330. <p class="wb-gift-sub" v-html="claimSuccessMsg"></p>
  331. </div>
  332. <div class="wb-modal-foot wb-gift-foot">
  333. <button type="button" class="wb-btn primary" @click="claimSuccessVisible = false">开始体验</button>
  334. </div>
  335. </div>
  336. </div>
  337. </Teleport>
  338. </div>
  339. </template>
  340. <script setup>
  341. import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from "vue";
  342. import { useRouter } from "vue-router";
  343. import { ElMessage } from "element-plus";
  344. import { getMcpCenterList, getMcpReceiveYdStatus, receiveMcpYd } from "@/api/mcp";
  345. import { isUserLoggedIn } from "@/utils/auth";
  346. import globalStore from "@/store/modules/global";
  347. const router = useRouter();
  348. const global = globalStore();
  349. const serviceList = ref([]);
  350. const selectedTool = reactive({});
  351. const searchKeyword = ref("");
  352. const serviceGridRef = ref(null);
  353. const columnCount = ref(3);
  354. const giftReceived = ref(false);
  355. const giftClaiming = ref(false);
  356. /** 体验礼包赠送云点数 */
  357. const receivedYdCount = ref(0);
  358. /** receiveYd 原始返回(具体字段稍后对齐) */
  359. const receiveYdRaw = ref(null);
  360. const needLoginVisible = ref(false);
  361. const claimSuccessVisible = ref(false);
  362. const giftOpenAnim = ref(false);
  363. const claimSuccessMsg = ref("");
  364. let resizeTimer = null;
  365. const giftPointsText = computed(() => {
  366. return receivedYdCount.value > 0 ? receivedYdCount.value : 10;
  367. });
  368. const userDisplayName = computed(() => {
  369. const info = global.userInfo || {};
  370. return info.showName || info.username || "环评用户";
  371. });
  372. const userYundianBalance = computed(() => {
  373. if (global.yundian != null && global.yundian !== "") return Number(global.yundian) || 0;
  374. return Number(global.userInfo?.ydCount) || 0;
  375. });
  376. const syncSelectedTool = (list) => {
  377. list.forEach((item) => {
  378. if (selectedTool[item.id] === undefined) selectedTool[item.id] = 0;
  379. });
  380. };
  381. const parseCssStyle = (cssText) => {
  382. if (!cssText) return {};
  383. const style = {};
  384. String(cssText)
  385. .split(";")
  386. .forEach((rule) => {
  387. const colonIndex = rule.indexOf(":");
  388. if (colonIndex === -1) return;
  389. const key = rule.slice(0, colonIndex).trim();
  390. const value = rule.slice(colonIndex + 1).trim();
  391. if (!key || !value) return;
  392. const prop = key.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
  393. style[prop] = value;
  394. });
  395. return style;
  396. };
  397. const formatPayCoefficient = (value) => {
  398. if (value == null || value === "") return "";
  399. const num = Number(value);
  400. if (Number.isNaN(num)) return String(value);
  401. return String(num);
  402. };
  403. /** 统一解析 png / svg 地址,以及内联 svg 字符串 */
  404. const resolveIconSource = (iconUrl) => {
  405. const raw = String(iconUrl || "").trim();
  406. if (!raw) return { iconKind: "none", iconSrc: "" };
  407. if (/^<svg[\s>]/i.test(raw) || raw.startsWith("<?xml")) {
  408. return { iconKind: "inline", iconSrc: raw };
  409. }
  410. return { iconKind: "img", iconSrc: raw };
  411. };
  412. const onIconError = (item) => {
  413. if (!item) return;
  414. item.iconKind = "none";
  415. item.iconSrc = "";
  416. };
  417. const openRouteInNewTab = (name, query = {}) => {
  418. const { href } = router.resolve({ name, query });
  419. window.open(href, "_blank", "noopener,noreferrer");
  420. };
  421. /** 查看详情:跳服务详情,并带上当前选中工具 */
  422. const openServiceDetail = (item) => {
  423. if (!item?.id) {
  424. ElMessage.info("该服务详情页待平台公布");
  425. return;
  426. }
  427. const toolIndex = selectedTool[item.id] ?? 0;
  428. const tool = item.tools?.[toolIndex];
  429. const query = { id: item.id };
  430. if (tool?.id) query.toolId = tool.id;
  431. openRouteInNewTab("mcpServer", query);
  432. };
  433. /** 模拟调用:跳在线测试,定位服务并带上当前选中工具 */
  434. const openServiceSimulate = (item) => {
  435. const toolIndex = selectedTool[item.id] ?? 0;
  436. const tool = item.tools?.[toolIndex];
  437. const query = {};
  438. if (item?.id) query.id = item.id;
  439. if (tool?.id) query.toolId = tool.id;
  440. if (tool?.code) query.toolCode = tool.code;
  441. openRouteInNewTab("mcpOnlineTest", query);
  442. };
  443. /** payType: 1 免费工具;2 按系数计费(展示限时免费) */
  444. const mapToolItem = (tool = {}) => {
  445. const payType = Number(tool.payType);
  446. const isLimit = payType === 2;
  447. return {
  448. id: tool.id || "",
  449. label: tool.name || "",
  450. code: tool.code || "",
  451. desc: tool.describe || "",
  452. fee: isLimit ? "limit" : "free",
  453. rate: isLimit ? formatPayCoefficient(tool.payCoefficient) : "",
  454. sort: Number(tool.sort) || 0,
  455. };
  456. };
  457. const mapCenterItem = (item = {}) => {
  458. const tools = (Array.isArray(item.toolList) ? item.toolList : [])
  459. .map(mapToolItem)
  460. .sort((a, b) => a.sort - b.sort);
  461. const tags = String(item.tagSet || "")
  462. .split(",")
  463. .map((tag) => tag.trim())
  464. .filter(Boolean);
  465. const icon = resolveIconSource(item.iconUrl);
  466. return {
  467. id: item.id || "",
  468. name: item.name || "",
  469. code: item.code || "",
  470. desc: item.describe || "",
  471. tags,
  472. iconUrl: item.iconUrl || "",
  473. iconKind: icon.iconKind,
  474. iconSrc: icon.iconSrc,
  475. iconStyle: parseCssStyle(item.iconStyle),
  476. cardBackground: String(item.cardBackground || "").replace(/;+\s*$/, ""),
  477. sort: Number(item.sort) || 0,
  478. tools,
  479. };
  480. };
  481. const fetchCenterList = async () => {
  482. try {
  483. const res = await getMcpCenterList();
  484. const list = Array.isArray(res?.data) ? res.data : [];
  485. serviceList.value = list
  486. .map(mapCenterItem)
  487. .sort((a, b) => a.sort - b.sort);
  488. syncSelectedTool(serviceList.value);
  489. nextTick(updateColumnCount);
  490. } catch (error) {
  491. console.error(error);
  492. }
  493. };
  494. const fetchReceiveYdStatus = async () => {
  495. try {
  496. const res = await getMcpReceiveYdStatus();
  497. // { msg, code, receivedYdCount, claimed }
  498. giftReceived.value = res?.claimed === true || res?.claimed === 1 || res?.claimed === "1";
  499. const count = Number(res?.receivedYdCount);
  500. receivedYdCount.value = Number.isFinite(count) ? count : 0;
  501. } catch (error) {
  502. console.error(error);
  503. }
  504. };
  505. const filteredServiceList = computed(() => {
  506. const kw = searchKeyword.value.trim().toLowerCase();
  507. if (!kw) return serviceList.value;
  508. return serviceList.value.filter((item) => {
  509. const hay = [
  510. item.name,
  511. item.code,
  512. item.desc,
  513. ...(item.tags || []),
  514. ...(item.tools || []).flatMap((tool) => [tool.label, tool.code]),
  515. ]
  516. .join(" ")
  517. .toLowerCase();
  518. return hay.includes(kw);
  519. });
  520. });
  521. const masonryColumns = computed(() => {
  522. const list = filteredServiceList.value;
  523. const n = Math.max(1, columnCount.value);
  524. const cols = Array.from({ length: n }, () => []);
  525. const heights = Array.from({ length: n }, () => 0);
  526. list.forEach((item) => {
  527. let best = 0;
  528. for (let i = 1; i < n; i += 1) {
  529. if (heights[i] < heights[best]) best = i;
  530. }
  531. cols[best].push(item);
  532. heights[best] += 220 + (item.tools?.length || 0) * 52;
  533. });
  534. return cols;
  535. });
  536. const updateColumnCount = () => {
  537. const width = serviceGridRef.value?.clientWidth || 0;
  538. if (!width) return;
  539. columnCount.value = Math.max(1, Math.floor((width + 18) / (330 + 18)));
  540. };
  541. const matrixCountText = computed(() => {
  542. const total = filteredServiceList.value.length;
  543. if (searchKeyword.value.trim()) {
  544. return `筛选结果 ${total} 个服务 · 已加载 ${total} 个`;
  545. }
  546. return `共 ${total} 个服务 · 已加载 ${total} 个`;
  547. });
  548. const onWindowResize = () => {
  549. clearTimeout(resizeTimer);
  550. resizeTimer = setTimeout(updateColumnCount, 160);
  551. };
  552. onMounted(async () => {
  553. nextTick(updateColumnCount);
  554. window.addEventListener("resize", onWindowResize);
  555. window.addEventListener("keydown", onGiftModalKeydown);
  556. fetchCenterList();
  557. await fetchReceiveYdStatus();
  558. await resumeClaimAfterLogin();
  559. });
  560. onBeforeUnmount(() => {
  561. clearTimeout(resizeTimer);
  562. window.removeEventListener("resize", onWindowResize);
  563. window.removeEventListener("keydown", onGiftModalKeydown);
  564. });
  565. const onGiftModalKeydown = (e) => {
  566. if (e.key !== "Escape") return;
  567. needLoginVisible.value = false;
  568. claimSuccessVisible.value = false;
  569. };
  570. const scrollToMatrix = () => {
  571. document.getElementById("matrix")?.scrollIntoView({ behavior: "smooth", block: "start" });
  572. };
  573. const scrollToTop = () => {
  574. const main = document.querySelector(".page-main");
  575. if (main) {
  576. main.scrollTo({ top: 0, behavior: "smooth" });
  577. return;
  578. }
  579. const shell = document.querySelector(".page-shell");
  580. if (shell) {
  581. shell.scrollTo({ top: 0, behavior: "smooth" });
  582. return;
  583. }
  584. window.scrollTo({ top: 0, behavior: "smooth" });
  585. };
  586. const playGiftOpen = async () => {
  587. giftOpenAnim.value = false;
  588. await nextTick();
  589. giftOpenAnim.value = true;
  590. };
  591. const openClaimSuccessModal = async () => {
  592. const points = receivedYdCount.value > 0 ? receivedYdCount.value : 10;
  593. claimSuccessMsg.value =
  594. `尊敬的用户 <b>${userDisplayName.value}</b>,体验礼包已到账 <span class="wb-points-em">${points} 云点</span><br>当前余额 <b>${userYundianBalance.value}</b> 云点,可直接用于 MCP 工具调用`;
  595. claimSuccessVisible.value = true;
  596. await playGiftOpen();
  597. };
  598. const goLoginToClaim = () => {
  599. needLoginVisible.value = false;
  600. try {
  601. sessionStorage.setItem("mcp_claim_gift_pending", "1");
  602. } catch (_) {
  603. /* ignore */
  604. }
  605. router.push({
  606. name: "login",
  607. query: { redirect: router.currentRoute.value.fullPath || "/serve" },
  608. });
  609. };
  610. const resumeClaimAfterLogin = async () => {
  611. let pending = false;
  612. try {
  613. pending = sessionStorage.getItem("mcp_claim_gift_pending") === "1";
  614. if (pending) sessionStorage.removeItem("mcp_claim_gift_pending");
  615. } catch (_) {
  616. /* ignore */
  617. }
  618. if (!pending || !isUserLoggedIn()) return;
  619. await claimGift();
  620. };
  621. const claimGift = async () => {
  622. if (!isUserLoggedIn()) {
  623. needLoginVisible.value = true;
  624. return;
  625. }
  626. if (giftReceived.value) {
  627. openClaimSuccessModal();
  628. return;
  629. }
  630. if (giftClaiming.value) return;
  631. giftClaiming.value = true;
  632. try {
  633. const res = await receiveMcpYd();
  634. receiveYdRaw.value = res ?? null;
  635. giftReceived.value = true;
  636. await fetchReceiveYdStatus();
  637. const points = receivedYdCount.value > 0 ? receivedYdCount.value : 10;
  638. const nextBalance = userYundianBalance.value + points;
  639. global.setYundian(nextBalance);
  640. await openClaimSuccessModal();
  641. } catch (error) {
  642. console.error(error);
  643. } finally {
  644. giftClaiming.value = false;
  645. }
  646. };
  647. </script>
  648. <style scoped lang="less">
  649. .serve-page {
  650. --bg: #F6F9FD;
  651. --surface: #FFFFFF;
  652. --surface-2: #F7FAFE;
  653. --ink: #1B2533;
  654. --ink-2: #3E5063;
  655. --muted: #72859A;
  656. --line: #E4ECF6;
  657. --line-strong: #C9D8EA;
  658. --brand: #1678ff;
  659. --brand-ink: #0b63e0;
  660. --brand-soft: #EAF2FF;
  661. --brand-soft-2: #F0F6FF;
  662. --radius: 16px;
  663. --shadow-card: 0 1px 2px rgba(22, 51, 84, 0.05), 0 10px 28px -18px rgba(22, 51, 84, 0.18);
  664. --shadow-lift: 0 2px 6px rgba(22, 51, 84, 0.06), 0 20px 44px -22px rgba(22, 51, 84, 0.26);
  665. --font-sans: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", sans-serif;
  666. --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  667. box-sizing: border-box;
  668. min-height: calc(100vh - 64px);
  669. background: var(--bg);
  670. color: var(--ink);
  671. font-family: var(--font-sans);
  672. line-height: 1.6;
  673. font-size: 15px;
  674. }
  675. .serve-page *,
  676. .serve-page *::before,
  677. .serve-page *::after {
  678. box-sizing: border-box;
  679. }
  680. .serve-page button {
  681. appearance: none;
  682. font-family: inherit;
  683. outline: none;
  684. }
  685. .serve-page button:focus,
  686. .serve-page button:focus-visible {
  687. outline: none;
  688. }
  689. .wrap {
  690. max-width: 1180px;
  691. margin: 0 auto;
  692. padding: 0 24px;
  693. }
  694. .registry-head {
  695. position: relative;
  696. overflow: hidden;
  697. display: flex;
  698. align-items: center;
  699. height: 520px;
  700. box-sizing: border-box;
  701. padding: 0;
  702. background:
  703. radial-gradient(720px 400px at 84% -18%, rgba(22, 120, 255, 0.16), transparent 62%),
  704. radial-gradient(560px 380px at -6% 116%, rgba(77, 154, 255, 0.13), transparent 60%),
  705. linear-gradient(118deg, #EDF5FF 0%, #F8FBFF 46%, #E9F2FF 100%);
  706. border-bottom: 1px solid var(--line);
  707. }
  708. .registry-head::before {
  709. content: "";
  710. position: absolute;
  711. inset: 0;
  712. pointer-events: none;
  713. background-image:
  714. linear-gradient(rgba(22, 120, 255, 0.05) 1px, transparent 1px),
  715. linear-gradient(90deg, rgba(22, 120, 255, 0.05) 1px, transparent 1px);
  716. background-size: 44px 44px;
  717. -webkit-mask-image: radial-gradient(900px 480px at 66% 42%, #000 0%, transparent 78%);
  718. mask-image: radial-gradient(900px 480px at 66% 42%, #000 0%, transparent 78%);
  719. }
  720. .registry-head::after {
  721. content: "";
  722. position: absolute;
  723. right: -200px;
  724. top: -230px;
  725. width: 580px;
  726. height: 580px;
  727. border-radius: 50%;
  728. pointer-events: none;
  729. border: 1px dashed rgba(22, 120, 255, 0.20);
  730. box-shadow: 0 0 0 44px rgba(22, 120, 255, 0.035), 0 0 0 106px rgba(22, 120, 255, 0.02);
  731. }
  732. .registry-head .wrap {
  733. position: relative;
  734. z-index: 1;
  735. width: 100%;
  736. }
  737. .registry-inner {
  738. display: flex;
  739. align-items: center;
  740. gap: 56px;
  741. }
  742. .registry-text {
  743. flex: 1;
  744. min-width: 0;
  745. max-width: 656px;
  746. }
  747. .registry-head h1 {
  748. margin: 0;
  749. font-weight: 800;
  750. font-size: clamp(40px, 5.2vw, 58px);
  751. line-height: 1.12;
  752. letter-spacing: -1.5px;
  753. color: #0B1F3F;
  754. }
  755. .brand-blue {
  756. background: linear-gradient(100deg, #3B96FF, #1678FF 55%, #0A5FE0);
  757. -webkit-background-clip: text;
  758. background-clip: text;
  759. color: transparent;
  760. }
  761. .sub-title {
  762. margin-top: 16px;
  763. font-weight: 600;
  764. font-size: clamp(16px, 1.9vw, 19px);
  765. color: #2E4A6B;
  766. }
  767. .lead {
  768. margin: 22px 0 0;
  769. max-width: 600px;
  770. color: #5F7490;
  771. font-size: 14.5px;
  772. line-height: 2;
  773. }
  774. .cta-row {
  775. display: flex;
  776. align-items: center;
  777. gap: 14px;
  778. flex-wrap: wrap;
  779. margin-top: 38px;
  780. }
  781. .btn-cta,
  782. .btn-ghost {
  783. display: inline-flex;
  784. align-items: center;
  785. gap: 8px;
  786. border-radius: 999px;
  787. cursor: pointer;
  788. font-family: inherit;
  789. }
  790. .btn-cta {
  791. padding: 13px 28px;
  792. font-size: 14.5px;
  793. font-weight: 700;
  794. background: linear-gradient(135deg, #2E8CFF, #1678FF 60%, #0B63E0);
  795. color: #fff;
  796. border: none;
  797. box-shadow: 0 12px 26px -10px rgba(22, 120, 255, 0.65);
  798. }
  799. .btn-cta:hover {
  800. transform: translateY(-2px);
  801. filter: brightness(1.07);
  802. border-color: transparent;
  803. box-shadow: 0 16px 34px -12px rgba(22, 120, 255, 0.78);
  804. }
  805. .btn-cta:disabled {
  806. opacity: 0.6;
  807. cursor: not-allowed;
  808. transform: none;
  809. filter: none;
  810. box-shadow: 0 8px 18px -10px rgba(22, 120, 255, 0.4);
  811. }
  812. .btn-ghost {
  813. padding: 13px 26px;
  814. font-size: 14px;
  815. font-weight: 600;
  816. background: rgba(255, 255, 255, 0.8);
  817. color: #33506F;
  818. border: 1px solid rgba(154, 182, 214, 0.9);
  819. }
  820. .btn-ghost:hover {
  821. border-color: #1678FF;
  822. color: #1678FF;
  823. background: #fff;
  824. transform: translateY(-1px);
  825. }
  826. .hero-meta {
  827. display: flex;
  828. align-items: center;
  829. gap: 24px;
  830. flex-wrap: wrap;
  831. margin-top: 26px;
  832. }
  833. .hm-item {
  834. display: inline-flex;
  835. align-items: center;
  836. gap: 7px;
  837. font-size: 13px;
  838. color: #57708C;
  839. font-weight: 500;
  840. }
  841. .hm-item b {
  842. color: #1678FF;
  843. font-weight: 700;
  844. }
  845. .hero-visual {
  846. flex: none;
  847. width: 420px;
  848. position: relative;
  849. }
  850. .hv-card {
  851. position: relative;
  852. background: rgba(255, 255, 255, 0.82);
  853. border: 1px solid rgba(22, 120, 255, 0.16);
  854. border-radius: 22px;
  855. padding: 18px;
  856. box-shadow: 0 30px 64px -26px rgba(22, 80, 160, 0.38);
  857. animation: hvFloat 7s ease-in-out infinite;
  858. }
  859. @keyframes hvFloat {
  860. 0%, 100% { transform: translateY(0); }
  861. 50% { transform: translateY(-7px); }
  862. }
  863. .hv-head {
  864. display: flex;
  865. align-items: center;
  866. justify-content: space-between;
  867. gap: 10px;
  868. padding: 0 2px 13px;
  869. border-bottom: 1px dashed rgba(22, 120, 255, 0.2);
  870. }
  871. .hv-title {
  872. display: inline-flex;
  873. align-items: center;
  874. gap: 8px;
  875. font-size: 12.5px;
  876. font-weight: 700;
  877. color: #22395C;
  878. }
  879. .hv-sm {
  880. width: 22px;
  881. height: 22px;
  882. flex: none;
  883. border-radius: 7px;
  884. background: linear-gradient(135deg, #EAF2FF, #DCE9FF);
  885. display: inline-flex;
  886. align-items: center;
  887. justify-content: center;
  888. color: #1678FF;
  889. }
  890. .hv-live {
  891. display: inline-flex;
  892. align-items: center;
  893. gap: 6px;
  894. font-size: 11px;
  895. font-weight: 600;
  896. color: #17a14b;
  897. }
  898. .hv-ping {
  899. width: 6px;
  900. height: 6px;
  901. border-radius: 50%;
  902. background: #17a14b;
  903. animation: hvPing 2s ease-out infinite;
  904. }
  905. @keyframes hvPing {
  906. 0% { box-shadow: 0 0 0 0 rgba(23, 161, 75, 0.4); }
  907. 70% { box-shadow: 0 0 0 8px rgba(23, 161, 75, 0); }
  908. 100% { box-shadow: 0 0 0 0 rgba(23, 161, 75, 0); }
  909. }
  910. .hv-flow {
  911. display: flex;
  912. align-items: center;
  913. gap: 8px;
  914. padding: 16px 2px 14px;
  915. }
  916. .hv-node {
  917. flex: 1;
  918. min-width: 0;
  919. display: flex;
  920. align-items: center;
  921. gap: 9px;
  922. padding: 10px 11px;
  923. border-radius: 14px;
  924. background: #fff;
  925. border: 1px solid #E3ECF7;
  926. }
  927. .hv-ico {
  928. width: 32px;
  929. height: 32px;
  930. flex: none;
  931. border-radius: 9px;
  932. display: flex;
  933. align-items: center;
  934. justify-content: center;
  935. }
  936. .hv-ico.app {
  937. background: linear-gradient(135deg, #EDF4FF, #DCE9FF);
  938. color: #1678FF;
  939. }
  940. .hv-ico.hub {
  941. background: linear-gradient(135deg, #1678FF, #0A5FE0);
  942. color: #fff;
  943. }
  944. .hv-n1,
  945. .hv-n2 {
  946. display: block;
  947. white-space: nowrap;
  948. overflow: hidden;
  949. text-overflow: ellipsis;
  950. }
  951. .hv-n1 {
  952. font-size: 12px;
  953. font-weight: 700;
  954. color: #22354F;
  955. }
  956. .hv-n2 {
  957. font-size: 10.5px;
  958. color: #8CA0B8;
  959. }
  960. .hv-line {
  961. flex: none;
  962. width: 36px;
  963. height: 2px;
  964. border-radius: 2px;
  965. background: repeating-linear-gradient(90deg, rgba(22, 120, 255, 0.8) 0 5px, transparent 5px 10px);
  966. animation: hvDash 1.1s linear infinite;
  967. }
  968. @keyframes hvDash {
  969. to { background-position: 10px 0; }
  970. }
  971. .hv-grid {
  972. display: grid;
  973. grid-template-columns: repeat(3, 1fr);
  974. gap: 8px;
  975. }
  976. .hv-tile {
  977. display: flex;
  978. align-items: center;
  979. gap: 7px;
  980. min-width: 0;
  981. padding: 10px 11px;
  982. border-radius: 12px;
  983. background: #fff;
  984. border: 1px solid #E6EEF8;
  985. }
  986. .hv-tile svg {
  987. flex: none;
  988. width: 18px;
  989. height: 18px;
  990. }
  991. .hv-tile .tx {
  992. font-size: 11px;
  993. font-weight: 600;
  994. color: #3A5575;
  995. white-space: nowrap;
  996. overflow: hidden;
  997. text-overflow: ellipsis;
  998. }
  999. .hv-chip {
  1000. position: absolute;
  1001. right: -10px;
  1002. bottom: -13px;
  1003. padding: 7px 12px;
  1004. border-radius: 10px;
  1005. background: linear-gradient(135deg, #0B63E0, #1678FF);
  1006. color: #fff;
  1007. font-size: 10.5px;
  1008. font-weight: 600;
  1009. box-shadow: 0 12px 24px -10px rgba(22, 120, 255, 0.6);
  1010. }
  1011. .section {
  1012. padding: 30px 0 72px;
  1013. }
  1014. #matrix {
  1015. scroll-margin-top: 12px;
  1016. }
  1017. .notice-bar {
  1018. display: flex;
  1019. align-items: center;
  1020. gap: 10px;
  1021. padding: 11px 16px;
  1022. margin-bottom: 16px;
  1023. background: linear-gradient(90deg, #EAF2FF 0%, #F0F7FF 60%, rgba(240, 247, 255, 0) 100%);
  1024. border: 1px solid #D6E6FF;
  1025. border-radius: 10px;
  1026. overflow: hidden;
  1027. white-space: nowrap;
  1028. }
  1029. .notice-horn {
  1030. flex: none;
  1031. display: inline-flex;
  1032. color: #E8890C;
  1033. }
  1034. .notice-roll {
  1035. position: relative;
  1036. flex: 1;
  1037. min-width: 0;
  1038. height: 20px;
  1039. overflow: hidden;
  1040. }
  1041. .notice-text {
  1042. display: block;
  1043. height: 20px;
  1044. line-height: 20px;
  1045. font-size: 13.5px;
  1046. font-weight: 600;
  1047. color: #1D4ED8;
  1048. letter-spacing: 0.02em;
  1049. white-space: nowrap;
  1050. overflow: hidden;
  1051. text-overflow: ellipsis;
  1052. }
  1053. .matrix-bar {
  1054. display: flex;
  1055. align-items: center;
  1056. justify-content: space-between;
  1057. gap: 16px;
  1058. flex-wrap: wrap;
  1059. margin-bottom: 18px;
  1060. }
  1061. .matrix-bar-left,
  1062. .matrix-bar-right {
  1063. display: flex;
  1064. align-items: center;
  1065. gap: 10px;
  1066. flex-wrap: wrap;
  1067. }
  1068. .matrix-count {
  1069. font-size: 13px;
  1070. color: var(--muted);
  1071. font-variant-numeric: tabular-nums;
  1072. }
  1073. .matrix-search {
  1074. position: relative;
  1075. display: flex;
  1076. align-items: center;
  1077. gap: 7px;
  1078. width: 250px;
  1079. padding: 7px 12px;
  1080. background: var(--surface);
  1081. border: 1px solid var(--line-strong);
  1082. border-radius: 9px;
  1083. }
  1084. .matrix-search:focus-within {
  1085. border-color: var(--brand);
  1086. box-shadow: 0 0 0 3px rgba(22, 120, 255, 0.12);
  1087. }
  1088. .matrix-search-ico {
  1089. width: 15px;
  1090. height: 15px;
  1091. flex: none;
  1092. color: var(--muted);
  1093. }
  1094. .matrix-search input {
  1095. flex: 1;
  1096. min-width: 0;
  1097. border: none;
  1098. outline: none;
  1099. background: transparent;
  1100. font-size: 13px;
  1101. color: var(--ink);
  1102. font-family: var(--font-sans);
  1103. }
  1104. .matrix-search input::placeholder {
  1105. color: var(--muted);
  1106. }
  1107. .matrix-search-clear {
  1108. display: none;
  1109. flex: none;
  1110. box-sizing: border-box;
  1111. width: 16px;
  1112. height: 16px;
  1113. min-width: 16px;
  1114. min-height: 16px;
  1115. margin: 0;
  1116. padding: 0;
  1117. border: none;
  1118. border-radius: 50%;
  1119. background: var(--line-strong);
  1120. color: #fff;
  1121. font-size: 0;
  1122. line-height: 0;
  1123. cursor: pointer;
  1124. appearance: none;
  1125. align-items: center;
  1126. justify-content: center;
  1127. align-self: center;
  1128. }
  1129. .matrix-search-clear.show {
  1130. display: inline-flex;
  1131. }
  1132. .matrix-search-clear svg {
  1133. display: block;
  1134. flex: none;
  1135. }
  1136. .service-grid {
  1137. display: grid;
  1138. grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  1139. gap: 18px;
  1140. align-items: start;
  1141. }
  1142. .service-grid.masonry {
  1143. display: flex;
  1144. align-items: flex-start;
  1145. gap: 18px;
  1146. }
  1147. .masonry-col {
  1148. flex: 1;
  1149. min-width: 0;
  1150. display: flex;
  1151. flex-direction: column;
  1152. gap: 18px;
  1153. }
  1154. .svc-card {
  1155. position: relative;
  1156. display: flex;
  1157. flex-direction: column;
  1158. background:
  1159. linear-gradient(var(--brand), var(--brand)) top / 100% 3px no-repeat,
  1160. var(--surface);
  1161. border: 1px solid var(--line);
  1162. border-radius: var(--radius);
  1163. padding: 22px;
  1164. box-shadow: var(--shadow-card);
  1165. overflow: visible;
  1166. transition: transform 0.22s, box-shadow 0.22s, border-color 0.22s;
  1167. }
  1168. .svc-card:hover {
  1169. transform: translateY(-3px);
  1170. box-shadow: var(--shadow-lift);
  1171. border-color: var(--line-strong);
  1172. z-index: 2;
  1173. }
  1174. .svc-top {
  1175. display: flex;
  1176. align-items: center;
  1177. gap: 12px;
  1178. min-height: 60.8px;
  1179. }
  1180. .svc-icon {
  1181. width: 46px;
  1182. height: 46px;
  1183. border-radius: 13px;
  1184. flex: none;
  1185. color: #fff;
  1186. background: var(--brand);
  1187. display: flex;
  1188. align-items: center;
  1189. justify-content: center;
  1190. }
  1191. .svc-icon-img,
  1192. .svc-icon-fallback {
  1193. width: 24px;
  1194. height: 24px;
  1195. display: block;
  1196. flex: none;
  1197. }
  1198. .svc-icon-img {
  1199. object-fit: contain;
  1200. }
  1201. .svc-icon-inline {
  1202. width: 24px;
  1203. height: 24px;
  1204. display: inline-flex;
  1205. align-items: center;
  1206. justify-content: center;
  1207. flex: none;
  1208. line-height: 0;
  1209. }
  1210. .svc-icon-inline :deep(svg) {
  1211. width: 24px;
  1212. height: 24px;
  1213. display: block;
  1214. }
  1215. .svc-card h3 {
  1216. margin: 0;
  1217. font-family: "PingFang SC", "PingFangSC-Regular", "Helvetica Neue", sans-serif;
  1218. font-weight: 700;
  1219. font-size: 19px;
  1220. letter-spacing: 0.01em;
  1221. line-height: 1.6;
  1222. flex: 1;
  1223. min-width: 0;
  1224. display: -webkit-box;
  1225. -webkit-line-clamp: 2;
  1226. -webkit-box-orient: vertical;
  1227. overflow: hidden;
  1228. }
  1229. .svc-desc {
  1230. order: 1;
  1231. margin: 12px 0 0;
  1232. font-size: 13.5px;
  1233. color: var(--ink-2);
  1234. line-height: 1.7;
  1235. font-family: "PingFang SC", "PingFangSC-Regular", "Helvetica Neue", sans-serif;
  1236. font-weight: 400;
  1237. display: -webkit-box;
  1238. -webkit-line-clamp: 3;
  1239. -webkit-box-orient: vertical;
  1240. overflow: hidden;
  1241. }
  1242. .svc-tools {
  1243. order: 2;
  1244. display: flex;
  1245. align-items: baseline;
  1246. gap: 8px;
  1247. flex-wrap: wrap;
  1248. margin-top: 16px;
  1249. padding-top: 16px;
  1250. border-top: 1px dashed var(--line);
  1251. }
  1252. .svc-tools b {
  1253. font-size: 12.5px;
  1254. color: var(--ink-2);
  1255. font-weight: 600;
  1256. padding-top: 2px;
  1257. flex: none;
  1258. }
  1259. .svc-tools em {
  1260. font-style: normal;
  1261. font-size: 11.5px;
  1262. color: var(--muted);
  1263. }
  1264. .svc-tool-list {
  1265. order: 3;
  1266. margin-top: 12px;
  1267. display: flex;
  1268. flex-direction: column;
  1269. gap: 8px;
  1270. }
  1271. .tool-item {
  1272. display: flex;
  1273. align-items: center;
  1274. justify-content: space-between;
  1275. gap: 12px;
  1276. padding: 9px 12px;
  1277. border: 1px solid var(--line);
  1278. border-radius: 10px;
  1279. background: var(--surface-2);
  1280. cursor: pointer;
  1281. transition: 0.16s;
  1282. }
  1283. .tool-item:hover {
  1284. border-color: var(--brand);
  1285. background: var(--brand-soft-2);
  1286. }
  1287. .tool-item.active {
  1288. border-color: var(--brand);
  1289. background: var(--brand-soft);
  1290. box-shadow: 0 6px 12px -8px rgba(22, 120, 255, 0.45);
  1291. }
  1292. .tool-item .tool-name {
  1293. flex: 1;
  1294. min-width: 0;
  1295. font-size: 13px;
  1296. font-weight: 600;
  1297. color: var(--ink);
  1298. line-height: 1.5;
  1299. }
  1300. .tool-item.active .tool-name {
  1301. color: var(--brand-ink);
  1302. }
  1303. .tool-fee {
  1304. display: flex;
  1305. align-items: center;
  1306. gap: 6px;
  1307. flex: none;
  1308. }
  1309. .fee-tag {
  1310. flex: none;
  1311. display: inline-flex;
  1312. align-items: center;
  1313. gap: 5px;
  1314. font-size: 11.5px;
  1315. font-weight: 600;
  1316. padding: 3px 10px;
  1317. border-radius: 999px;
  1318. white-space: nowrap;
  1319. letter-spacing: 0.01em;
  1320. }
  1321. .fee-tag.free {
  1322. color: #0F766E;
  1323. background: #E6F7F1;
  1324. border: 1px solid #BFE9DD;
  1325. }
  1326. .fee-tag.rate {
  1327. color: #A05A00;
  1328. background: #FFF4E3;
  1329. border: 1px solid #F5E3C2;
  1330. font-family: var(--font-mono);
  1331. font-size: 10.5px;
  1332. font-variant-numeric: tabular-nums;
  1333. }
  1334. .fee-tag.limitfree {
  1335. color: #C62828;
  1336. background: #FFEBEC;
  1337. border: 1px solid #FBD5D8;
  1338. }
  1339. .svc-actions {
  1340. order: 4;
  1341. margin-top: auto;
  1342. display: flex;
  1343. gap: 10px;
  1344. flex-wrap: wrap;
  1345. justify-content: flex-end;
  1346. padding-top: 13px;
  1347. }
  1348. .btn-ico {
  1349. width: 14px;
  1350. height: 14px;
  1351. flex: none;
  1352. }
  1353. .btn-sm {
  1354. display: inline-flex;
  1355. align-items: center;
  1356. gap: 7px;
  1357. padding: 8px 16px;
  1358. border-radius: 999px;
  1359. font-size: 13px;
  1360. font-weight: 600;
  1361. cursor: pointer;
  1362. font-family: inherit;
  1363. border: 1px solid transparent;
  1364. }
  1365. .btn-sm.primary {
  1366. background: var(--brand);
  1367. color: #fff;
  1368. box-shadow: 0 8px 16px -8px rgba(22, 120, 255, 0.6);
  1369. }
  1370. .btn-sm.primary:hover {
  1371. filter: brightness(1.06);
  1372. border-color: transparent;
  1373. }
  1374. .btn-sm.ghost {
  1375. background: var(--surface);
  1376. color: var(--brand-ink);
  1377. border-color: var(--line-strong);
  1378. }
  1379. .btn-sm.ghost:hover {
  1380. border-color: var(--brand);
  1381. }
  1382. .matrix-empty {
  1383. text-align: center;
  1384. padding: 60px 20px;
  1385. color: var(--muted);
  1386. background: var(--surface);
  1387. border: 1px dashed var(--line-strong);
  1388. border-radius: var(--radius);
  1389. }
  1390. .matrix-end {
  1391. display: none;
  1392. text-align: center;
  1393. padding: 30px 20px 6px;
  1394. color: var(--muted);
  1395. font-size: 12.5px;
  1396. }
  1397. .matrix-end.show {
  1398. display: block;
  1399. }
  1400. .matrix-end::before {
  1401. content: "";
  1402. display: block;
  1403. width: 56px;
  1404. height: 1px;
  1405. background: var(--line-strong);
  1406. margin: 0 auto 16px;
  1407. }
  1408. .site-footer {
  1409. padding: 44px 0 34px;
  1410. background: #fff;
  1411. border-top: 1px solid var(--line);
  1412. }
  1413. .footer-main {
  1414. display: flex;
  1415. align-items: flex-start;
  1416. justify-content: space-between;
  1417. gap: 40px;
  1418. flex-wrap: wrap;
  1419. }
  1420. .footer-brand {
  1421. flex: 1.2;
  1422. min-width: 260px;
  1423. max-width: 380px;
  1424. }
  1425. .fb-title {
  1426. display: flex;
  1427. align-items: center;
  1428. gap: 9px;
  1429. font-size: 20px;
  1430. font-weight: 700;
  1431. color: var(--brand-ink);
  1432. }
  1433. .fb-logo {
  1434. width: 26px;
  1435. height: 26px;
  1436. border-radius: 7px;
  1437. background: linear-gradient(135deg, #2E8CFF, #1678FF);
  1438. color: #fff;
  1439. display: flex;
  1440. align-items: center;
  1441. justify-content: center;
  1442. font-size: 14px;
  1443. font-weight: 700;
  1444. }
  1445. .fb-desc {
  1446. margin-top: 12px;
  1447. font-size: 13px;
  1448. line-height: 1.9;
  1449. color: var(--muted);
  1450. }
  1451. .footer-col {
  1452. min-width: 150px;
  1453. }
  1454. .footer-col h4 {
  1455. font-size: 14px;
  1456. font-weight: 600;
  1457. color: var(--ink);
  1458. margin: 0 0 14px;
  1459. position: relative;
  1460. padding-left: 11px;
  1461. }
  1462. .footer-col h4::before {
  1463. content: "";
  1464. position: absolute;
  1465. left: 0;
  1466. top: 3px;
  1467. width: 3px;
  1468. height: 13px;
  1469. border-radius: 2px;
  1470. background: var(--brand);
  1471. }
  1472. .footer-col ul {
  1473. list-style: none;
  1474. margin: 0;
  1475. padding: 0;
  1476. }
  1477. .footer-col li {
  1478. margin-bottom: 10px;
  1479. }
  1480. .footer-col a,
  1481. .footer-col button {
  1482. background: none;
  1483. border: none;
  1484. border-radius: 0;
  1485. padding: 0;
  1486. cursor: pointer;
  1487. font-size: 13px;
  1488. font-weight: 400;
  1489. color: #43556b;
  1490. font-family: inherit;
  1491. text-align: left;
  1492. text-decoration: none;
  1493. }
  1494. .footer-col a:hover,
  1495. .footer-col button:hover {
  1496. color: var(--brand);
  1497. border-color: transparent;
  1498. }
  1499. .footer-link-ico {
  1500. margin-right: 6px;
  1501. color: #93A5BB;
  1502. }
  1503. .footer-cs {
  1504. position: relative;
  1505. }
  1506. .footer-cs:hover .home-footer-popover {
  1507. opacity: 1;
  1508. visibility: visible;
  1509. transform: translateY(0);
  1510. }
  1511. .home-footer-popover {
  1512. position: absolute;
  1513. left: 0;
  1514. bottom: calc(100% + 10px);
  1515. z-index: 20;
  1516. display: flex;
  1517. flex-direction: column;
  1518. align-items: center;
  1519. gap: 4px;
  1520. width: 140px;
  1521. padding: 10px;
  1522. background: #fff;
  1523. box-shadow: 0 0 6px rgba(0, 0, 0, 0.1);
  1524. border-radius: 8px;
  1525. opacity: 0;
  1526. visibility: hidden;
  1527. transform: translateY(4px);
  1528. transition: 0.18s;
  1529. }
  1530. .home-footer-popover-img {
  1531. display: block;
  1532. width: 100%;
  1533. height: auto;
  1534. }
  1535. .home-footer-popover-sub {
  1536. font-size: 12px;
  1537. color: #666;
  1538. line-height: 16px;
  1539. }
  1540. @media (max-width: 1160px) {
  1541. .hero-visual {
  1542. display: none;
  1543. }
  1544. .registry-text {
  1545. max-width: none;
  1546. }
  1547. .registry-head {
  1548. height: auto;
  1549. padding: 68px 0 78px;
  1550. }
  1551. }
  1552. @media (max-width: 760px) {
  1553. .footer-main {
  1554. flex-direction: column;
  1555. gap: 26px;
  1556. }
  1557. }
  1558. @media (max-width: 520px) {
  1559. .service-grid.masonry {
  1560. flex-direction: column;
  1561. }
  1562. .matrix-search {
  1563. width: 100%;
  1564. }
  1565. }
  1566. </style>
  1567. <style lang="less">
  1568. /* 礼包弹窗:Teleport 到 body,使用非 scoped 保证样式完整生效 */
  1569. .mcp-serve-gift.wb-overlay {
  1570. position: fixed;
  1571. inset: 0;
  1572. z-index: 2000;
  1573. display: flex;
  1574. align-items: center;
  1575. justify-content: center;
  1576. padding: 24px;
  1577. box-sizing: border-box;
  1578. background: rgba(16, 34, 64, 0.42);
  1579. backdrop-filter: blur(2px);
  1580. font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", sans-serif;
  1581. }
  1582. .mcp-serve-gift .wb-modal {
  1583. width: min(94vw, 460px);
  1584. background: #fff;
  1585. border-radius: 24px;
  1586. box-shadow:
  1587. 0 40px 80px -24px rgba(8, 32, 72, 0.48),
  1588. 0 0 0 1px rgba(22, 120, 255, 0.06);
  1589. overflow: hidden;
  1590. transform: translateY(0) scale(1);
  1591. box-sizing: border-box;
  1592. }
  1593. .mcp-serve-gift .wb-close {
  1594. position: absolute;
  1595. top: 16px;
  1596. right: 16px;
  1597. z-index: 3;
  1598. box-sizing: border-box;
  1599. width: 32px;
  1600. height: 32px;
  1601. min-width: 32px;
  1602. min-height: 32px;
  1603. margin: 0;
  1604. padding: 0;
  1605. border: none;
  1606. border-radius: 50%;
  1607. background: rgba(255, 255, 255, 0.78);
  1608. box-shadow: 0 4px 12px rgba(22, 51, 84, 0.08);
  1609. color: #8ca0b8;
  1610. font-size: 0;
  1611. line-height: 0;
  1612. display: inline-flex;
  1613. align-items: center;
  1614. justify-content: center;
  1615. cursor: pointer;
  1616. appearance: none;
  1617. }
  1618. .mcp-serve-gift .wb-close svg {
  1619. display: block;
  1620. flex: none;
  1621. }
  1622. .mcp-serve-gift .wb-close:hover {
  1623. background: #fff;
  1624. color: #1678ff;
  1625. }
  1626. .mcp-serve-gift .wb-gift-modal {
  1627. position: relative;
  1628. }
  1629. .mcp-serve-gift .wb-gift-hero {
  1630. position: relative;
  1631. overflow: hidden;
  1632. padding: 36px 36px 8px;
  1633. text-align: center;
  1634. background:
  1635. radial-gradient(ellipse 90% 70% at 50% -20%, rgba(255, 196, 80, 0.22), transparent 58%),
  1636. radial-gradient(ellipse 70% 55% at 100% 0%, rgba(46, 140, 255, 0.18), transparent 55%),
  1637. radial-gradient(ellipse 70% 55% at 0% 10%, rgba(22, 120, 255, 0.12), transparent 55%),
  1638. linear-gradient(180deg, #eef5ff 0%, #f8fbff 42%, #ffffff 100%);
  1639. }
  1640. .mcp-serve-gift .wb-gift-hero::before {
  1641. content: "";
  1642. position: absolute;
  1643. inset: 0;
  1644. pointer-events: none;
  1645. background-image:
  1646. linear-gradient(rgba(22, 120, 255, 0.035) 1px, transparent 1px),
  1647. linear-gradient(90deg, rgba(22, 120, 255, 0.035) 1px, transparent 1px);
  1648. background-size: 28px 28px;
  1649. -webkit-mask-image: radial-gradient(420px 220px at 50% 20%, #000 0%, transparent 75%);
  1650. mask-image: radial-gradient(420px 220px at 50% 20%, #000 0%, transparent 75%);
  1651. opacity: 0.9;
  1652. }
  1653. .mcp-serve-gift .wb-gift-hero > * {
  1654. position: relative;
  1655. z-index: 1;
  1656. }
  1657. .mcp-serve-gift .wb-gift-kicker {
  1658. display: inline-flex;
  1659. align-items: center;
  1660. gap: 7px;
  1661. margin: 0 auto 16px;
  1662. padding: 6px 14px;
  1663. border-radius: 999px;
  1664. font-size: 12.5px;
  1665. font-weight: 600;
  1666. color: #1678ff;
  1667. background: rgba(255, 255, 255, 0.78);
  1668. border: 1px solid rgba(22, 120, 255, 0.18);
  1669. box-shadow: 0 8px 20px -12px rgba(22, 120, 255, 0.45);
  1670. backdrop-filter: blur(6px);
  1671. }
  1672. .mcp-serve-gift .wb-gift-kicker.is-ok {
  1673. color: #148a45;
  1674. background: rgba(255, 255, 255, 0.82);
  1675. border-color: rgba(23, 161, 75, 0.22);
  1676. box-shadow: 0 8px 20px -12px rgba(23, 161, 75, 0.35);
  1677. }
  1678. .mcp-serve-gift .wb-gift-visual {
  1679. width: 64px;
  1680. height: 64px;
  1681. margin: 0 auto 14px;
  1682. position: relative;
  1683. border-radius: 20px;
  1684. background: radial-gradient(circle at 50% 35%, #ffffff 0%, #fff8e8 60%, #ffe9b8 100%);
  1685. box-shadow:
  1686. 0 12px 24px -12px rgba(176, 110, 0, 0.4),
  1687. 0 0 0 1px rgba(255, 200, 100, 0.25),
  1688. inset 0 1px 0 rgba(255, 255, 255, 0.9);
  1689. display: flex;
  1690. align-items: center;
  1691. justify-content: center;
  1692. }
  1693. .mcp-serve-gift .wb-gift-visual .wb-gift-icon {
  1694. width: 44px;
  1695. height: 44px;
  1696. display: block;
  1697. overflow: visible;
  1698. }
  1699. .mcp-serve-gift .wb-gift-open {
  1700. animation: mcpServeGiftPop 0.78s cubic-bezier(0.18, 1.5, 0.42, 1) both;
  1701. }
  1702. .mcp-serve-gift .wb-gift-open::before {
  1703. content: "";
  1704. position: absolute;
  1705. left: 50%;
  1706. top: 50%;
  1707. width: 64px;
  1708. height: 64px;
  1709. margin: -32px 0 0 -32px;
  1710. border-radius: 50%;
  1711. border: 2px solid rgba(23, 161, 75, 0.5);
  1712. pointer-events: none;
  1713. animation: mcpServeGiftRing 1.1s ease-out 0.18s both;
  1714. }
  1715. .mcp-serve-gift .wb-gift-open .gift-lid {
  1716. transform-origin: 24px 14px;
  1717. animation: mcpServeGiftLid 0.82s cubic-bezier(0.3, 1.6, 0.4, 1) 0.12s both;
  1718. }
  1719. @keyframes mcpServeGiftPop {
  1720. 0% {
  1721. transform: scale(0.45) rotate(-12deg);
  1722. opacity: 0;
  1723. }
  1724. 55% {
  1725. transform: scale(1.14) rotate(4deg);
  1726. opacity: 1;
  1727. }
  1728. 78% {
  1729. transform: scale(0.96) rotate(-1.5deg);
  1730. }
  1731. 100% {
  1732. transform: scale(1) rotate(0);
  1733. opacity: 1;
  1734. }
  1735. }
  1736. @keyframes mcpServeGiftLid {
  1737. 0% {
  1738. transform: translateY(0) rotate(0);
  1739. }
  1740. 45% {
  1741. transform: translateY(-13px) rotate(-14deg);
  1742. }
  1743. 100% {
  1744. transform: translateY(-8px) rotate(-9deg);
  1745. }
  1746. }
  1747. @keyframes mcpServeGiftRing {
  1748. 0% {
  1749. transform: scale(0.55);
  1750. opacity: 0.85;
  1751. }
  1752. 100% {
  1753. transform: scale(2.05);
  1754. opacity: 0;
  1755. }
  1756. }
  1757. .mcp-serve-gift .wb-gift-visual.is-ok {
  1758. background: radial-gradient(circle at 50% 35%, #ffffff 0%, #e9f9ef 55%, #b7e8c8 100%);
  1759. box-shadow:
  1760. 0 22px 44px -18px rgba(23, 161, 75, 0.38),
  1761. 0 0 0 1px rgba(23, 161, 75, 0.18),
  1762. inset 0 1px 0 rgba(255, 255, 255, 0.9);
  1763. }
  1764. .mcp-serve-gift .wb-gift-visual.is-ok svg {
  1765. width: 46px;
  1766. height: 46px;
  1767. stroke: #148a45;
  1768. fill: none;
  1769. stroke-width: 2.8;
  1770. stroke-linecap: round;
  1771. stroke-linejoin: round;
  1772. }
  1773. .mcp-serve-gift .wb-gift-title {
  1774. font-size: 22px;
  1775. font-weight: 700;
  1776. color: #0b1f3f;
  1777. letter-spacing: 0.01em;
  1778. margin: 0;
  1779. line-height: 1.4;
  1780. }
  1781. .mcp-serve-gift .wb-gift-sub {
  1782. margin: 12px auto 0;
  1783. max-width: 100%;
  1784. font-size: 14px;
  1785. line-height: 1.75;
  1786. color: #5f7490;
  1787. }
  1788. .mcp-serve-gift .wb-gift-sub b {
  1789. color: #0b1f3f;
  1790. font-weight: 600;
  1791. }
  1792. .mcp-serve-gift .wb-gift-sub .wb-points-em {
  1793. color: #e03131;
  1794. font-weight: 700;
  1795. }
  1796. .mcp-serve-gift .wb-modal-foot.wb-gift-foot {
  1797. padding: 22px 36px 30px;
  1798. display: flex;
  1799. gap: 12px;
  1800. justify-content: stretch;
  1801. text-align: center;
  1802. background: linear-gradient(180deg, rgba(255, 255, 255, 0), #fff 28%);
  1803. box-sizing: border-box;
  1804. }
  1805. .mcp-serve-gift .wb-btn {
  1806. display: inline-flex;
  1807. align-items: center;
  1808. justify-content: center;
  1809. flex: 1;
  1810. min-width: 0;
  1811. min-height: 46px;
  1812. margin: 0;
  1813. padding: 10px 20px;
  1814. border: 1px solid transparent;
  1815. border-radius: 14px;
  1816. font-size: 15px;
  1817. font-weight: 600;
  1818. font-family: inherit;
  1819. line-height: 1.2;
  1820. cursor: pointer;
  1821. appearance: none;
  1822. outline: none;
  1823. box-sizing: border-box;
  1824. transition: 0.18s;
  1825. }
  1826. .mcp-serve-gift .wb-btn.primary {
  1827. background: linear-gradient(135deg, #2e8cff, #1678ff 60%, #0b63e0);
  1828. color: #fff;
  1829. border-color: transparent;
  1830. box-shadow: 0 12px 28px -10px rgba(22, 120, 255, 0.65);
  1831. }
  1832. .mcp-serve-gift .wb-btn.primary:hover {
  1833. filter: brightness(1.07);
  1834. transform: translateY(-1px);
  1835. }
  1836. .mcp-serve-gift .wb-btn.ghost {
  1837. background: #f5f8fc;
  1838. color: #4a6078;
  1839. border-color: #e2eaf3;
  1840. box-shadow: none;
  1841. }
  1842. .mcp-serve-gift .wb-btn.ghost:hover {
  1843. background: #fff;
  1844. border-color: #1678ff;
  1845. color: #1678ff;
  1846. }
  1847. </style>