mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix(admin-spa): 修复API Key创建成功后的弹窗显示问题
- 完全复刻原版的API Key成功弹窗样式 - 修复API Key字段读取问题,支持apiKey和key两种字段名 - 添加完整的显示/隐藏功能,默认隐藏API Key内容 - 实现与原版一致的警告提示和关闭确认 - 优化复制功能,添加降级方案
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
pre[data-v-747f035b]{white-space:pre-wrap;word-wrap:break-word}.tab-content[data-v-3c03d38e]{min-height:calc(100vh - 300px)}.table-container[data-v-3c03d38e]{overflow-x:auto;border-radius:12px;border:1px solid rgba(0,0,0,.05)}.table-row[data-v-3c03d38e]{transition:all .2s ease}.table-row[data-v-3c03d38e]:hover{background-color:#00000005}.loading-spinner[data-v-3c03d38e]{width:24px;height:24px;border:2px solid #e5e7eb;border-top:2px solid #3b82f6;border-radius:50%;animation:spin-3c03d38e 1s linear infinite}@keyframes spin-3c03d38e{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.api-key-date-picker[data-v-3c03d38e] .el-input__inner{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.api-key-date-picker[data-v-3c03d38e] .el-input__inner:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1));--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.api-key-date-picker[data-v-3c03d38e] .el-range-separator{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{c as b,r as x,q as f,x as a,z as s,L as i,Q as y,u as o,P as m,Y as _,K as u,aq as c,O as g,y as n}from"./vue-vendor-CKToUHZx.js";import{_ as v,u as w}from"./index-CJmHAjMf.js";/* empty css */import"./element-plus-B8Fs_0jW.js";import"./vendor-BDiMbLwQ.js";const h={class:"flex items-center justify-center min-h-screen p-6"},k={class:"glass-strong rounded-3xl p-10 w-full max-w-md shadow-2xl"},L={class:"text-center mb-8"},S={class:"w-20 h-20 mx-auto mb-6 bg-gradient-to-br from-blue-500/20 to-purple-500/20 border border-gray-300/30 rounded-2xl flex items-center justify-center backdrop-blur-sm overflow-hidden"},V=["src"],I={key:1,class:"fas fa-cloud text-3xl text-gray-700"},N={key:1,class:"w-12 h-12 bg-gray-300/50 rounded animate-pulse"},q={key:0,class:"text-3xl font-bold text-white mb-2 header-title"},D={key:1,class:"h-9 w-64 bg-gray-300/50 rounded animate-pulse mx-auto mb-2"},E=["disabled"],j={key:0,class:"fas fa-sign-in-alt mr-2"},B={key:1,class:"loading-spinner mr-2"},M={key:0,class:"mt-6 p-4 bg-red-500/20 border border-red-500/30 rounded-xl text-red-800 text-sm text-center backdrop-blur-sm"},F={__name:"LoginView",setup(O){const e=w(),d=b(()=>e.oemLoading),l=x({username:"",password:""});f(()=>{e.loadOemSettings()});const p=async()=>{await e.login(l.value)};return(T,t)=>(n(),a("div",h,[s("div",k,[s("div",L,[s("div",S,[d.value?(n(),a("div",N)):(n(),a(y,{key:0},[o(e).oemSettings.siteIconData||o(e).oemSettings.siteIcon?(n(),a("img",{key:0,src:o(e).oemSettings.siteIconData||o(e).oemSettings.siteIcon,alt:"Logo",class:"w-12 h-12 object-contain",onError:t[0]||(t[0]=r=>r.target.style.display="none")},null,40,V)):(n(),a("i",I))],64))]),!d.value&&o(e).oemSettings.siteName?(n(),a("h1",q,m(o(e).oemSettings.siteName),1)):d.value?(n(),a("div",D)):i("",!0),t[3]||(t[3]=s("p",{class:"text-gray-600 text-lg"},"管理后台",-1))]),s("form",{onSubmit:_(p,["prevent"]),class:"space-y-6"},[s("div",null,[t[4]||(t[4]=s("label",{class:"block text-sm font-semibold text-gray-900 mb-3"},"用户名",-1)),u(s("input",{"onUpdate:modelValue":t[1]||(t[1]=r=>l.value.username=r),type:"text",required:"",class:"form-input w-full",placeholder:"请输入用户名"},null,512),[[c,l.value.username]])]),s("div",null,[t[5]||(t[5]=s("label",{class:"block text-sm font-semibold text-gray-900 mb-3"},"密码",-1)),u(s("input",{"onUpdate:modelValue":t[2]||(t[2]=r=>l.value.password=r),type:"password",required:"",class:"form-input w-full",placeholder:"请输入密码"},null,512),[[c,l.value.password]])]),s("button",{type:"submit",disabled:o(e).loginLoading,class:"btn btn-primary w-full py-4 px-6 text-lg font-semibold"},[o(e).loginLoading?i("",!0):(n(),a("i",j)),o(e).loginLoading?(n(),a("div",B)):i("",!0),g(" "+m(o(e).loginLoading?"登录中...":"登录"),1)],8,E)],32),o(e).loginError?(n(),a("div",M,[t[6]||(t[6]=s("i",{class:"fas fa-exclamation-triangle mr-2"},null,-1)),g(m(o(e).loginError),1)])):i("",!0)])]))}},P=v(F,[["__scopeId","data-v-4a19afbe"]]);export{P as default};
|
||||
@@ -1,3 +0,0 @@
|
||||
import{aR as k,r as x,aW as C,q as O,x as m,z as e,u as i,K as T,aq as N,L as _,O as v,C as j,P as S,y as g}from"./vue-vendor-CKToUHZx.js";import{s as c}from"./toast-BvwA7Mwb.js";import{a as D,_ as F}from"./index-CJmHAjMf.js";import"./element-plus-B8Fs_0jW.js";import"./vendor-BDiMbLwQ.js";const E=k("settings",()=>{const l=x({siteName:"Claude Relay Service",siteIcon:"",siteIconData:"",updatedAt:null}),r=x(!1),p=x(!1),d=async()=>{r.value=!0;try{const s=await D.get("/admin/oem-settings");return s&&s.success&&(l.value={...l.value,...s.data},f()),s}catch(s){throw console.error("Failed to load OEM settings:",s),s}finally{r.value=!1}},a=async s=>{p.value=!0;try{const o=await D.put("/admin/oem-settings",s);return o&&o.success&&(l.value={...l.value,...o.data},f()),o}catch(o){throw console.error("Failed to save OEM settings:",o),o}finally{p.value=!1}},w=async()=>{const s={siteName:"Claude Relay Service",siteIcon:"",siteIconData:"",updatedAt:null};return l.value={...s},await a(s)},f=()=>{if(l.value.siteName&&(document.title=`${l.value.siteName} - 管理后台`),l.value.siteIconData||l.value.siteIcon){const s=document.querySelector('link[rel="icon"]')||document.createElement("link");s.rel="icon",s.href=l.value.siteIconData||l.value.siteIcon,document.querySelector('link[rel="icon"]')||document.head.appendChild(s)}};return{oemSettings:l,loading:r,saving:p,loadOemSettings:d,saveOemSettings:a,resetOemSettings:w,applyOemSettings:f,formatDateTime:s=>s?new Date(s).toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}):"",validateIconFile:s=>{const o=[];return s.size>350*1024&&o.push("图标文件大小不能超过 350KB"),["image/x-icon","image/png","image/jpeg","image/jpg","image/svg+xml"].includes(s.type)||o.push("不支持的文件类型,请选择 .ico, .png, .jpg 或 .svg 文件"),{isValid:o.length===0,errors:o}},fileToBase64:s=>new Promise((o,n)=>{const t=new FileReader;t.onload=u=>o(u.target.result),t.onerror=n,t.readAsDataURL(s)})}}),B={class:"settings-container"},V={class:"card p-6"},R={key:0,class:"text-center py-12"},M={key:1,class:"table-container"},A={class:"min-w-full"},q={class:"divide-y divide-gray-200/50"},z={class:"table-row"},K={class:"px-6 py-4"},L={class:"table-row"},U={class:"px-6 py-4"},$={class:"space-y-3"},P={key:0,class:"inline-flex items-center gap-3 p-3 bg-gray-50 rounded-lg"},W=["src"],G={class:"px-6 py-6",colspan:"2"},H={class:"flex items-center justify-between"},J={class:"flex gap-3"},Q=["disabled"],X={key:0,class:"loading-spinner mr-2"},Y={key:1,class:"fas fa-save mr-2"},Z=["disabled"],ee={key:0,class:"text-sm text-gray-500"},te={__name:"SettingsView",setup(l){const r=E(),{loading:p,saving:d,oemSettings:a}=C(r),w=x();O(async()=>{try{await r.loadOemSettings()}catch{c("加载设置失败","error")}});const f=async()=>{try{const n={siteName:a.value.siteName,siteIcon:a.value.siteIcon,siteIconData:a.value.siteIconData},t=await r.saveOemSettings(n);t&&t.success?c("OEM设置保存成功","success"):c((t==null?void 0:t.message)||"保存失败","error")}catch{c("保存OEM设置失败","error")}},b=async()=>{if(confirm(`确定要重置为默认设置吗?
|
||||
|
||||
这将清除所有自定义的网站名称和图标设置。`))try{const n=await r.resetOemSettings();n&&n.success?c("已重置为默认设置","success"):c("重置失败","error")}catch{c("重置失败","error")}},h=async n=>{const t=n.target.files[0];if(!t)return;const u=r.validateIconFile(t);if(!u.isValid){u.errors.forEach(y=>c(y,"error"));return}try{const y=await r.fileToBase64(t);a.value.siteIconData=y}catch{c("文件读取失败","error")}n.target.value=""},I=()=>{a.value.siteIcon="",a.value.siteIconData=""},s=()=>{console.warn("Icon failed to load")},o=r.formatDateTime;return(n,t)=>(g(),m("div",B,[e("div",V,[t[12]||(t[12]=e("div",{class:"flex flex-col md:flex-row justify-between items-center gap-4 mb-6"},[e("div",null,[e("h3",{class:"text-xl font-bold text-gray-900 mb-2"},"其他设置"),e("p",{class:"text-gray-600"},"自定义网站名称和图标")])],-1)),i(p)?(g(),m("div",R,t[2]||(t[2]=[e("div",{class:"loading-spinner mx-auto mb-4"},null,-1),e("p",{class:"text-gray-500"},"正在加载设置...",-1)]))):(g(),m("div",M,[e("table",A,[e("tbody",q,[e("tr",z,[t[4]||(t[4]=e("td",{class:"px-6 py-4 whitespace-nowrap w-48"},[e("div",{class:"flex items-center"},[e("div",{class:"w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center mr-3"},[e("i",{class:"fas fa-font text-white text-xs"})]),e("div",null,[e("div",{class:"text-sm font-semibold text-gray-900"},"网站名称"),e("div",{class:"text-xs text-gray-500"},"品牌标识")])])],-1)),e("td",K,[T(e("input",{"onUpdate:modelValue":t[0]||(t[0]=u=>i(a).siteName=u),type:"text",class:"form-input w-full max-w-md",placeholder:"Claude Relay Service",maxlength:"100"},null,512),[[N,i(a).siteName]]),t[3]||(t[3]=e("p",{class:"text-xs text-gray-500 mt-1"},"将显示在浏览器标题和页面头部",-1))])]),e("tr",L,[t[9]||(t[9]=e("td",{class:"px-6 py-4 whitespace-nowrap w-48"},[e("div",{class:"flex items-center"},[e("div",{class:"w-8 h-8 bg-gradient-to-br from-purple-500 to-purple-600 rounded-lg flex items-center justify-center mr-3"},[e("i",{class:"fas fa-image text-white text-xs"})]),e("div",null,[e("div",{class:"text-sm font-semibold text-gray-900"},"网站图标"),e("div",{class:"text-xs text-gray-500"},"Favicon")])])],-1)),e("td",U,[e("div",$,[i(a).siteIconData||i(a).siteIcon?(g(),m("div",P,[e("img",{src:i(a).siteIconData||i(a).siteIcon,alt:"图标预览",class:"w-8 h-8",onError:s},null,40,W),t[6]||(t[6]=e("span",{class:"text-sm text-gray-600"},"当前图标",-1)),e("button",{onClick:I,class:"text-red-600 hover:text-red-900 font-medium hover:bg-red-50 px-3 py-1 rounded-lg transition-colors"},t[5]||(t[5]=[e("i",{class:"fas fa-trash mr-1"},null,-1),v("删除 ",-1)]))])):_("",!0),e("div",null,[e("input",{type:"file",ref_key:"iconFileInput",ref:w,onChange:h,accept:".ico,.png,.jpg,.jpeg,.svg",class:"hidden"},null,544),e("button",{onClick:t[1]||(t[1]=u=>n.$refs.iconFileInput.click()),class:"btn btn-success px-4 py-2"},t[7]||(t[7]=[e("i",{class:"fas fa-upload mr-2"},null,-1),v(" 上传图标 ",-1)])),t[8]||(t[8]=e("span",{class:"text-xs text-gray-500 ml-3"},"支持 .ico, .png, .jpg, .svg 格式,最大 350KB",-1))])])])]),e("tr",null,[e("td",G,[e("div",H,[e("div",J,[e("button",{onClick:f,disabled:i(d),class:j(["btn btn-primary px-6 py-3",{"opacity-50 cursor-not-allowed":i(d)}])},[i(d)?(g(),m("div",X)):(g(),m("i",Y)),v(" "+S(i(d)?"保存中...":"保存设置"),1)],10,Q),e("button",{onClick:b,class:"btn bg-gray-100 text-gray-700 hover:bg-gray-200 px-6 py-3",disabled:i(d)},t[10]||(t[10]=[e("i",{class:"fas fa-undo mr-2"},null,-1),v(" 重置为默认 ",-1)]),8,Z)]),i(a).updatedAt?(g(),m("div",ee,[t[11]||(t[11]=e("i",{class:"fas fa-clock mr-1"},null,-1)),v(" 最后更新:"+S(i(o)(i(a).updatedAt)),1)])):_("",!0)])])])])])]))])]))}},le=F(te,[["__scopeId","data-v-3508e0de"]]);export{le as default};
|
||||
File diff suppressed because one or more lines are too long
2
web/admin-spa/dist/assets/index-CJmHAjMf.js
vendored
2
web/admin-spa/dist/assets/index-CJmHAjMf.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,31 +1,36 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||||
<div class="modal-content w-full max-w-lg p-8 mx-auto">
|
||||
<div class="modal-content w-full max-w-lg p-8 mx-auto max-h-[90vh] overflow-y-auto custom-scrollbar">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center">
|
||||
<i class="fas fa-check text-white"></i>
|
||||
<div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center">
|
||||
<i class="fas fa-check text-white text-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900">API Key 创建成功</h3>
|
||||
<p class="text-sm text-gray-600">请妥善保存您的 API Key</p>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900">API Key 创建成功</h3>
|
||||
</div>
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
@click="handleClose"
|
||||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 成功提示 -->
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-8 h-8 bg-green-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-shield-alt text-white text-sm"></i>
|
||||
<!-- 警告提示 -->
|
||||
<div class="bg-amber-50 border-l-4 border-amber-400 p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<div class="w-6 h-6 bg-amber-400 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<i class="fas fa-exclamation-triangle text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-800 mb-1">请妥善保管您的 API Key</h4>
|
||||
<p class="text-sm text-gray-600">API Key 只会显示一次,关闭此窗口后将无法再次查看完整密钥。请立即复制并保存到安全的地方。</p>
|
||||
<div class="ml-3">
|
||||
<h5 class="font-semibold text-amber-900 mb-1">重要提醒</h5>
|
||||
<p class="text-sm text-amber-800">
|
||||
这是您唯一能看到完整 API Key 的机会。关闭此窗口后,系统将不再显示完整的 API Key。请立即复制并妥善保存。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,68 +38,54 @@
|
||||
<!-- API Key 信息 -->
|
||||
<div class="space-y-4 mb-6">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">名称</label>
|
||||
<p class="text-gray-900">{{ apiKey.name }}</p>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">API Key 名称</label>
|
||||
<div class="p-3 bg-gray-50 rounded-lg border">
|
||||
<span class="text-gray-900 font-medium">{{ apiKey.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="apiKey.description">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">描述</label>
|
||||
<p class="text-gray-600 text-sm">{{ apiKey.description }}</p>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">备注</label>
|
||||
<div class="p-3 bg-gray-50 rounded-lg border">
|
||||
<span class="text-gray-700">{{ apiKey.description || '无描述' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">API Key</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
:type="showFullKey ? 'text' : 'password'"
|
||||
:value="apiKey.key"
|
||||
readonly
|
||||
class="form-input w-full pr-24 font-mono text-sm bg-gray-50"
|
||||
>
|
||||
<div class="absolute right-1 top-1 flex gap-1">
|
||||
<div class="p-4 pr-14 bg-gray-900 rounded-lg border font-mono text-sm text-white break-all min-h-[60px] flex items-center">
|
||||
{{ getDisplayedApiKey() }}
|
||||
</div>
|
||||
<div class="absolute top-3 right-3">
|
||||
<button
|
||||
@click="toggleKeyVisibility"
|
||||
type="button"
|
||||
class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm transition-colors"
|
||||
:title="showFullKey ? '隐藏' : '显示'"
|
||||
class="btn-icon-sm hover:bg-gray-800 bg-gray-700"
|
||||
:title="showFullKey ? '隐藏API Key' : '显示完整API Key'"
|
||||
>
|
||||
<i :class="showFullKey ? 'fas fa-eye-slash' : 'fas fa-eye'"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="copyApiKey"
|
||||
type="button"
|
||||
class="px-3 py-1.5 bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg text-sm transition-colors"
|
||||
title="复制"
|
||||
>
|
||||
<i class="fas fa-copy"></i>
|
||||
{{ copied ? '已复制' : '复制' }}
|
||||
<i :class="['fas', showFullKey ? 'fa-eye-slash' : 'fa-eye', 'text-gray-300']"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
点击眼睛图标切换显示模式,使用下方按钮复制完整 API Key
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用说明 -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<h4 class="font-semibold text-gray-800 mb-2">
|
||||
<i class="fas fa-info-circle mr-2 text-blue-500"></i>使用说明
|
||||
</h4>
|
||||
<div class="text-sm text-gray-700 space-y-2">
|
||||
<p>1. 在 HTTP 请求头中添加:</p>
|
||||
<code class="block bg-white rounded px-3 py-2 text-xs">Authorization: Bearer {{ apiKey.key }}</code>
|
||||
|
||||
<p class="pt-2">2. 请求示例:</p>
|
||||
<pre class="bg-white rounded px-3 py-2 text-xs overflow-x-auto">curl -X POST {{ currentBaseUrl }}v1/messages \
|
||||
-H "Authorization: Bearer {{ apiKey.key }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "claude-3-opus-20240229", "messages": [...]}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
class="btn btn-primary px-6 py-2.5"
|
||||
@click="copyApiKey"
|
||||
class="flex-1 btn btn-primary py-3 px-6 font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<i class="fas fa-copy"></i>
|
||||
复制 API Key
|
||||
</button>
|
||||
<button
|
||||
@click="handleClose"
|
||||
class="px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
我已保存
|
||||
</button>
|
||||
@@ -105,7 +96,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { showToast } from '@/utils/toast'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -118,31 +109,62 @@ const props = defineProps({
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const showFullKey = ref(false)
|
||||
const copied = ref(false)
|
||||
|
||||
// 计算基础 URL
|
||||
const currentBaseUrl = computed(() => {
|
||||
return `${window.location.protocol}//${window.location.host}/api/`
|
||||
})
|
||||
|
||||
// 切换密钥可见性
|
||||
const toggleKeyVisibility = () => {
|
||||
showFullKey.value = !showFullKey.value
|
||||
}
|
||||
|
||||
// 获取显示的API Key
|
||||
const getDisplayedApiKey = () => {
|
||||
const key = props.apiKey.apiKey || props.apiKey.key || ''
|
||||
if (!key) return ''
|
||||
|
||||
if (showFullKey.value) {
|
||||
return key
|
||||
} else {
|
||||
// 显示前8个字符和后4个字符,中间用●代替
|
||||
if (key.length <= 12) return key
|
||||
return key.substring(0, 8) + '●'.repeat(Math.max(0, key.length - 12)) + key.substring(key.length - 4)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 API Key
|
||||
const copyApiKey = async () => {
|
||||
const key = props.apiKey.apiKey || props.apiKey.key || ''
|
||||
if (!key) {
|
||||
showToast('API Key 不存在', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.apiKey.key)
|
||||
copied.value = true
|
||||
await navigator.clipboard.writeText(key)
|
||||
showToast('API Key 已复制到剪贴板', 'success')
|
||||
|
||||
// 3秒后重置复制状态
|
||||
setTimeout(() => {
|
||||
copied.value = false
|
||||
}, 3000)
|
||||
} catch (error) {
|
||||
showToast('复制失败,请手动复制', 'error')
|
||||
console.error('Failed to copy:', error)
|
||||
// 降级方案:创建一个临时文本区域
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = key
|
||||
document.body.appendChild(textArea)
|
||||
textArea.select()
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
showToast('API Key 已复制到剪贴板', 'success')
|
||||
} catch (fallbackError) {
|
||||
showToast('复制失败,请手动复制', 'error')
|
||||
} finally {
|
||||
document.body.removeChild(textArea)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
const confirmed = confirm(
|
||||
'关闭后将无法再次查看完整的API Key,请确保已经妥善保存。\n\n确定要关闭吗?'
|
||||
)
|
||||
if (confirmed) {
|
||||
emit('close')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user