This commit is contained in:
KevinLiao
2025-07-31 13:37:23 +08:00
18 changed files with 232 additions and 46 deletions

View File

@@ -1 +1 @@
1.1.58 1.1.62

View File

@@ -739,10 +739,28 @@ class ClaudeAccountService {
throw new Error('Account not found'); throw new Error('Account not found');
} }
// 获取或创建会话窗口
const updatedAccountData = await this.updateSessionWindow(accountId, accountData);
// 设置限流状态和时间 // 设置限流状态和时间
accountData.rateLimitedAt = new Date().toISOString(); updatedAccountData.rateLimitedAt = new Date().toISOString();
accountData.rateLimitStatus = 'limited'; updatedAccountData.rateLimitStatus = 'limited';
await redis.setClaudeAccount(accountId, accountData);
// 限流结束时间 = 会话窗口结束时间
if (updatedAccountData.sessionWindowEnd) {
updatedAccountData.rateLimitEndAt = updatedAccountData.sessionWindowEnd;
const windowEnd = new Date(updatedAccountData.sessionWindowEnd);
const now = new Date();
const minutesUntilEnd = Math.ceil((windowEnd - now) / (1000 * 60));
logger.warn(`🚫 Account marked as rate limited until session window ends: ${accountData.name} (${accountId}) - ${minutesUntilEnd} minutes remaining`);
} else {
// 如果没有会话窗口使用默认1小时兼容旧逻辑
const oneHourLater = new Date(Date.now() + 60 * 60 * 1000);
updatedAccountData.rateLimitEndAt = oneHourLater.toISOString();
logger.warn(`🚫 Account marked as rate limited (1 hour default): ${accountData.name} (${accountId})`);
}
await redis.setClaudeAccount(accountId, updatedAccountData);
// 如果有会话哈希,删除粘性会话映射 // 如果有会话哈希,删除粘性会话映射
if (sessionHash) { if (sessionHash) {
@@ -750,7 +768,6 @@ class ClaudeAccountService {
logger.info(`🗑️ Deleted sticky session mapping for rate limited account: ${accountId}`); logger.info(`🗑️ Deleted sticky session mapping for rate limited account: ${accountId}`);
} }
logger.warn(`🚫 Account marked as rate limited: ${accountData.name} (${accountId})`);
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
logger.error(`❌ Failed to mark account as rate limited: ${accountId}`, error); logger.error(`❌ Failed to mark account as rate limited: ${accountId}`, error);
@@ -769,6 +786,7 @@ class ClaudeAccountService {
// 清除限流状态 // 清除限流状态
delete accountData.rateLimitedAt; delete accountData.rateLimitedAt;
delete accountData.rateLimitStatus; delete accountData.rateLimitStatus;
delete accountData.rateLimitEndAt; // 清除限流结束时间
await redis.setClaudeAccount(accountId, accountData); await redis.setClaudeAccount(accountId, accountData);
logger.success(`✅ Rate limit removed for account: ${accountData.name} (${accountId})`); logger.success(`✅ Rate limit removed for account: ${accountData.name} (${accountId})`);
@@ -789,17 +807,32 @@ class ClaudeAccountService {
// 检查是否有限流状态 // 检查是否有限流状态
if (accountData.rateLimitStatus === 'limited' && accountData.rateLimitedAt) { if (accountData.rateLimitStatus === 'limited' && accountData.rateLimitedAt) {
const rateLimitedAt = new Date(accountData.rateLimitedAt);
const now = new Date(); const now = new Date();
const hoursSinceRateLimit = (now - rateLimitedAt) / (1000 * 60 * 60);
// 优先使用 rateLimitEndAt基于会话窗口
if (accountData.rateLimitEndAt) {
const rateLimitEndAt = new Date(accountData.rateLimitEndAt);
// 如果当前时间超过限流结束时间,自动解除
if (now >= rateLimitEndAt) {
await this.removeAccountRateLimit(accountId);
return false;
}
return true;
} else {
// 兼容旧数据使用1小时限流
const rateLimitedAt = new Date(accountData.rateLimitedAt);
const hoursSinceRateLimit = (now - rateLimitedAt) / (1000 * 60 * 60);
// 如果限流超过1小时自动解除 // 如果限流超过1小时自动解除
if (hoursSinceRateLimit >= 1) { if (hoursSinceRateLimit >= 1) {
await this.removeAccountRateLimit(accountId); await this.removeAccountRateLimit(accountId);
return false; return false;
}
return true;
} }
return true;
} }
return false; return false;
@@ -821,13 +854,29 @@ class ClaudeAccountService {
const rateLimitedAt = new Date(accountData.rateLimitedAt); const rateLimitedAt = new Date(accountData.rateLimitedAt);
const now = new Date(); const now = new Date();
const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60)); const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60));
const minutesRemaining = Math.max(0, 60 - minutesSinceRateLimit);
let minutesRemaining;
let rateLimitEndAt;
// 优先使用 rateLimitEndAt基于会话窗口
if (accountData.rateLimitEndAt) {
rateLimitEndAt = accountData.rateLimitEndAt;
const endTime = new Date(accountData.rateLimitEndAt);
minutesRemaining = Math.max(0, Math.ceil((endTime - now) / (1000 * 60)));
} else {
// 兼容旧数据使用1小时限流
minutesRemaining = Math.max(0, 60 - minutesSinceRateLimit);
// 计算预期的结束时间
const endTime = new Date(rateLimitedAt.getTime() + 60 * 60 * 1000);
rateLimitEndAt = endTime.toISOString();
}
return { return {
isRateLimited: minutesRemaining > 0, isRateLimited: minutesRemaining > 0,
rateLimitedAt: accountData.rateLimitedAt, rateLimitedAt: accountData.rateLimitedAt,
minutesSinceRateLimit, minutesSinceRateLimit,
minutesRemaining minutesRemaining,
rateLimitEndAt // 新增:限流结束时间
}; };
} }
@@ -835,7 +884,8 @@ class ClaudeAccountService {
isRateLimited: false, isRateLimited: false,
rateLimitedAt: null, rateLimitedAt: null,
minutesSinceRateLimit: 0, minutesSinceRateLimit: 0,
minutesRemaining: 0 minutesRemaining: 0,
rateLimitEndAt: null
}; };
} catch (error) { } catch (error) {
logger.error(`❌ Failed to get rate limit info for account: ${accountId}`, error); logger.error(`❌ Failed to get rate limit info for account: ${accountId}`, error);

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
pre[data-v-2c02f1f7]{white-space:pre-wrap;word-wrap:break-word}.tab-content[data-v-61e76aa7]{min-height:calc(100vh - 300px)}.table-container[data-v-61e76aa7]{overflow-x:auto;border-radius:12px;border:1px solid rgba(0,0,0,.05)}.table-row[data-v-61e76aa7]{transition:all .2s ease}.table-row[data-v-61e76aa7]:hover{background-color:#00000005}.loading-spinner[data-v-61e76aa7]{width:24px;height:24px;border:2px solid #e5e7eb;border-top:2px solid #3b82f6;border-radius:50%;animation:spin-61e76aa7 1s linear infinite}@keyframes spin-61e76aa7{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.api-key-date-picker[data-v-61e76aa7] .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-61e76aa7] .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-61e76aa7] .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

View File

@@ -0,0 +1 @@
pre[data-v-2c02f1f7]{white-space:pre-wrap;word-wrap:break-word}.tab-content[data-v-277a81a9]{min-height:calc(100vh - 300px)}.table-container[data-v-277a81a9]{overflow-x:auto;border-radius:12px;border:1px solid rgba(0,0,0,.05)}.table-row[data-v-277a81a9]{transition:all .2s ease}.table-row[data-v-277a81a9]:hover{background-color:#00000005}.loading-spinner[data-v-277a81a9]{width:24px;height:24px;border:2px solid #e5e7eb;border-top:2px solid #3b82f6;border-radius:50%;animation:spin-277a81a9 1s linear infinite}@keyframes spin-277a81a9{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.api-key-date-picker[data-v-277a81a9] .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-277a81a9] .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-277a81a9] .el-range-separator{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}

View File

@@ -1 +1 @@
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-Ch5822Og.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",{class:"space-y-6",onSubmit:_(p,["prevent"])},[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-82195a01"]]);export{P as default}; 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-9AMT1Op2.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",{class:"space-y-6",onSubmit:_(p,["prevent"])},[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-82195a01"]]);export{P as default};

View File

@@ -1 +1 @@
/* empty css */import{_ as r}from"./index-Ch5822Og.js";import{x as t,y as s,z as o,Q as d,L as a,A as c,C as g,P as i}from"./vue-vendor-CKToUHZx.js";const u={class:"flex items-center gap-4"},f={class:"w-12 h-12 bg-gradient-to-br from-blue-500/20 to-purple-500/20 border border-gray-300/30 rounded-xl flex items-center justify-center backdrop-blur-sm flex-shrink-0 overflow-hidden"},y=["src"],m={key:1,class:"fas fa-cloud text-xl text-gray-700"},h={key:1,class:"w-8 h-8 bg-gray-300/50 rounded animate-pulse"},x={class:"flex flex-col justify-center min-h-[48px]"},b={class:"flex items-center gap-3"},k={key:1,class:"h-8 w-64 bg-gray-300/50 rounded animate-pulse"},_={key:0,class:"text-gray-600 text-sm leading-tight mt-0.5"},S={__name:"LogoTitle",props:{loading:{type:Boolean,default:!1},title:{type:String,default:""},subtitle:{type:String,default:""},logoSrc:{type:String,default:""},titleClass:{type:String,default:"text-gray-900"}},setup(e){const n=l=>{l.target.style.display="none"};return(l,p)=>(s(),t("div",u,[o("div",f,[e.loading?(s(),t("div",h)):(s(),t(d,{key:0},[e.logoSrc?(s(),t("img",{key:0,src:e.logoSrc,alt:"Logo",class:"w-8 h-8 object-contain",onError:n},null,40,y)):(s(),t("i",m))],64))]),o("div",x,[o("div",b,[!e.loading&&e.title?(s(),t("h1",{key:0,class:g(["text-2xl font-bold header-title leading-tight",e.titleClass])},i(e.title),3)):e.loading?(s(),t("div",k)):a("",!0),c(l.$slots,"after-title",{},void 0,!0)]),e.subtitle?(s(),t("p",_,i(e.subtitle),1)):a("",!0)])]))}},C=r(S,[["__scopeId","data-v-718feedc"]]);export{C as L}; /* empty css */import{_ as r}from"./index-9AMT1Op2.js";import{x as t,y as s,z as o,Q as d,L as a,A as c,C as g,P as i}from"./vue-vendor-CKToUHZx.js";const u={class:"flex items-center gap-4"},f={class:"w-12 h-12 bg-gradient-to-br from-blue-500/20 to-purple-500/20 border border-gray-300/30 rounded-xl flex items-center justify-center backdrop-blur-sm flex-shrink-0 overflow-hidden"},y=["src"],m={key:1,class:"fas fa-cloud text-xl text-gray-700"},h={key:1,class:"w-8 h-8 bg-gray-300/50 rounded animate-pulse"},x={class:"flex flex-col justify-center min-h-[48px]"},b={class:"flex items-center gap-3"},k={key:1,class:"h-8 w-64 bg-gray-300/50 rounded animate-pulse"},_={key:0,class:"text-gray-600 text-sm leading-tight mt-0.5"},S={__name:"LogoTitle",props:{loading:{type:Boolean,default:!1},title:{type:String,default:""},subtitle:{type:String,default:""},logoSrc:{type:String,default:""},titleClass:{type:String,default:"text-gray-900"}},setup(e){const n=l=>{l.target.style.display="none"};return(l,p)=>(s(),t("div",u,[o("div",f,[e.loading?(s(),t("div",h)):(s(),t(d,{key:0},[e.logoSrc?(s(),t("img",{key:0,src:e.logoSrc,alt:"Logo",class:"w-8 h-8 object-contain",onError:n},null,40,y)):(s(),t("i",m))],64))]),o("div",x,[o("div",b,[!e.loading&&e.title?(s(),t("h1",{key:0,class:g(["text-2xl font-bold header-title leading-tight",e.titleClass])},i(e.title),3)):e.loading?(s(),t("div",k)):a("",!0),c(l.$slots,"after-title",{},void 0,!0)]),e.subtitle?(s(),t("p",_,i(e.subtitle),1)):a("",!0)])]))}},C=r(S,[["__scopeId","data-v-718feedc"]]);export{C as L};

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
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-Ch5822Og.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(`确定要重置为默认设置吗? 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-9AMT1Op2.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",{class:"text-red-600 hover:text-red-900 font-medium hover:bg-red-50 px-3 py-1 rounded-lg transition-colors",onClick:I},t[5]||(t[5]=[e("i",{class:"fas fa-trash mr-1"},null,-1),v("删除 ",-1)]))])):_("",!0),e("div",null,[e("input",{ref_key:"iconFileInput",ref:w,type:"file",accept:".ico,.png,.jpg,.jpeg,.svg",class:"hidden",onChange:h},null,544),e("button",{class:"btn btn-success px-4 py-2",onClick:t[1]||(t[1]=u=>n.$refs.iconFileInput.click())},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",{disabled:i(d),class:j(["btn btn-primary px-6 py-3",{"opacity-50 cursor-not-allowed":i(d)}]),onClick:f},[i(d)?(g(),m("div",X)):(g(),m("i",Y)),v(" "+S(i(d)?"保存中...":"保存设置"),1)],10,Q),e("button",{class:"btn bg-gray-100 text-gray-700 hover:bg-gray-200 px-6 py-3",disabled:i(d),onClick:b},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-d29d5f49"]]);export{le as default}; 这将清除所有自定义的网站名称和图标设置`))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",{class:"text-red-600 hover:text-red-900 font-medium hover:bg-red-50 px-3 py-1 rounded-lg transition-colors",onClick:I},t[5]||(t[5]=[e("i",{class:"fas fa-trash mr-1"},null,-1),v("删除 ",-1)]))])):_("",!0),e("div",null,[e("input",{ref_key:"iconFileInput",ref:w,type:"file",accept:".ico,.png,.jpg,.jpeg,.svg",class:"hidden",onChange:h},null,544),e("button",{class:"btn btn-success px-4 py-2",onClick:t[1]||(t[1]=u=>n.$refs.iconFileInput.click())},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",{disabled:i(d),class:j(["btn btn-primary px-6 py-3",{"opacity-50 cursor-not-allowed":i(d)}]),onClick:f},[i(d)?(g(),m("div",X)):(g(),m("i",Y)),v(" "+S(i(d)?"保存中...":"保存设置"),1)],10,Q),e("button",{class:"btn bg-gray-100 text-gray-700 hover:bg-gray-200 px-6 py-3",disabled:i(d),onClick:b},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-d29d5f49"]]);export{le as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -18,12 +18,12 @@
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin> <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net"> <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com"> <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
<script type="module" crossorigin src="/admin-next/assets/index-Ch5822Og.js"></script> <script type="module" crossorigin src="/admin-next/assets/index-9AMT1Op2.js"></script>
<link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js">
<link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js">
<link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js"> <link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js">
<link rel="stylesheet" crossorigin href="/admin-next/assets/element-plus-CPnoEkWW.css"> <link rel="stylesheet" crossorigin href="/admin-next/assets/element-plus-CPnoEkWW.css">
<link rel="stylesheet" crossorigin href="/admin-next/assets/index-CANUYAyV.css"> <link rel="stylesheet" crossorigin href="/admin-next/assets/index-V6aqLFfH.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -14,7 +14,7 @@
<!-- Token统计时间范围选择 --> <!-- Token统计时间范围选择 -->
<select <select
v-model="apiKeyStatsTimeRange" v-model="apiKeyStatsTimeRange"
class="form-input px-3 py-2 text-sm" class="px-2 py-1 text-sm text-gray-700 bg-white border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent hover:border-gray-300 transition-colors"
@change="loadApiKeys()" @change="loadApiKeys()"
> >
<option value="today"> <option value="today">
@@ -33,7 +33,8 @@
<!-- 标签筛选器 --> <!-- 标签筛选器 -->
<select <select
v-model="selectedTagFilter" v-model="selectedTagFilter"
class="form-input px-3 py-2 text-sm" class="px-2 py-1 text-sm text-gray-700 bg-white border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent hover:border-gray-300 transition-colors"
@change="currentPage = 1"
> >
<option value=""> <option value="">
所有标签 所有标签
@@ -47,7 +48,7 @@
</option> </option>
</select> </select>
<button <button
class="btn btn-primary px-6 py-3 flex items-center gap-2" class="btn btn-primary px-4 py-1.5 text-sm flex items-center gap-2"
@click.stop="openCreateApiKeyModal" @click.stop="openCreateApiKeyModal"
> >
<i class="fas fa-plus" />创建新 Key <i class="fas fa-plus" />创建新 Key
@@ -460,10 +461,10 @@
:default-time="defaultTime" :default-time="defaultTime"
size="small" size="small"
style="width: 280px;" style="width: 280px;"
@update:model-value="(value) => onApiKeyCustomDateRangeChange(key.id, value)"
class="api-key-date-picker" class="api-key-date-picker"
:clearable="true" :clearable="true"
:unlink-panels="false" :unlink-panels="false"
@update:model-value="(value) => onApiKeyCustomDateRangeChange(key.id, value)"
/> />
</div> </div>
</div> </div>
@@ -603,6 +604,99 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- 分页组件 -->
<div
v-if="filteredAndSortedApiKeys.length > 0"
class="mt-6 flex flex-col sm:flex-row justify-between items-center gap-4"
>
<div class="flex items-center gap-3">
<span class="text-sm text-gray-600">
共 {{ filteredAndSortedApiKeys.length }} 条记录
</span>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-600">每页显示</span>
<select
v-model="pageSize"
class="px-2 py-1 text-sm text-gray-700 bg-white border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent hover:border-gray-300 transition-colors"
@change="currentPage = 1"
>
<option
v-for="size in pageSizeOptions"
:key="size"
:value="size"
>
{{ size }}
</option>
</select>
<span class="text-sm text-gray-600">条</span>
</div>
</div>
<div class="flex items-center gap-2">
<!-- 上一页 -->
<button
class="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="currentPage === 1"
@click="currentPage--"
>
<i class="fas fa-chevron-left" />
</button>
<!-- 页码 -->
<div class="flex items-center gap-1">
<!-- 第一页 -->
<button
v-if="currentPage > 3"
class="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
@click="currentPage = 1"
>
1
</button>
<span
v-if="currentPage > 4"
class="px-2 text-gray-500"
>...</span>
<!-- 中间页码 -->
<button
v-for="page in pageNumbers"
:key="page"
:class="[
'px-3 py-1 text-sm font-medium rounded-md',
page === currentPage
? 'bg-blue-600 text-white'
: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-50'
]"
@click="currentPage = page"
>
{{ page }}
</button>
<!-- 最后一页 -->
<span
v-if="currentPage < totalPages - 3"
class="px-2 text-gray-500"
>...</span>
<button
v-if="totalPages > 1 && currentPage < totalPages - 2"
class="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
@click="currentPage = totalPages"
>
{{ totalPages }}
</button>
</div>
<!-- 下一页 -->
<button
class="px-3 py-1 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="currentPage === totalPages || totalPages === 0"
@click="currentPage++"
>
<i class="fas fa-chevron-right" />
</button>
</div>
</div>
</div> </div>
<!-- 模态框组件 --> <!-- 模态框组件 -->
@@ -659,6 +753,11 @@ const apiKeyDateFilters = ref({})
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]) const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
const accounts = ref({ claude: [], gemini: [] }) const accounts = ref({ claude: [], gemini: [] })
// 分页相关
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizeOptions = [5, 10, 20, 50, 100]
// 标签相关 // 标签相关
const selectedTagFilter = ref('') const selectedTagFilter = ref('')
const availableTags = ref([]) const availableTags = ref([])
@@ -672,8 +771,8 @@ const editingApiKey = ref(null)
const renewingApiKey = ref(null) const renewingApiKey = ref(null)
const newApiKeyData = ref(null) const newApiKeyData = ref(null)
// 计算排序后的API Keys // 计算筛选和排序后的API Keys(未分页)
const sortedApiKeys = computed(() => { const filteredAndSortedApiKeys = computed(() => {
// 先进行标签筛选 // 先进行标签筛选
let filteredKeys = apiKeys.value let filteredKeys = apiKeys.value
if (selectedTagFilter.value) { if (selectedTagFilter.value) {
@@ -710,6 +809,37 @@ const sortedApiKeys = computed(() => {
return sorted return sorted
}) })
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(filteredAndSortedApiKeys.value.length / pageSize.value)
})
// 计算当前页显示的API Keys
const sortedApiKeys = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredAndSortedApiKeys.value.slice(start, end)
})
// 计算要显示的页码
const pageNumbers = computed(() => {
const pages = []
const maxPagesToShow = 5
let startPage = Math.max(1, currentPage.value - Math.floor(maxPagesToShow / 2))
let endPage = Math.min(totalPages.value, startPage + maxPagesToShow - 1)
// 调整起始页
if (endPage - startPage < maxPagesToShow - 1) {
startPage = Math.max(1, endPage - maxPagesToShow + 1)
}
for (let i = startPage; i <= endPage; i++) {
pages.push(i)
}
return pages
})
// 加载账户列表 // 加载账户列表
const loadAccounts = async () => { const loadAccounts = async () => {
try { try {
@@ -771,6 +901,9 @@ const loadApiKeys = async () => {
} }
}) })
availableTags.value = Array.from(tagsSet).sort() availableTags.value = Array.from(tagsSet).sort()
// 重置到第一页
currentPage.value = 1
} }
} catch (error) { } catch (error) {
showToast('加载 API Keys 失败', 'error') showToast('加载 API Keys 失败', 'error')
@@ -787,6 +920,8 @@ const sortApiKeys = (field) => {
apiKeysSortBy.value = field apiKeysSortBy.value = field
apiKeysSortOrder.value = 'asc' apiKeysSortOrder.value = 'asc'
} }
// 排序时重置到第一页
currentPage.value = 1
} }
// 格式化数字 // 格式化数字
@@ -1093,7 +1228,7 @@ const deleteApiKey = async (keyId) => {
const copyApiStatsLink = (apiKey) => { const copyApiStatsLink = (apiKey) => {
// 构建统计页面的完整URL // 构建统计页面的完整URL
const baseUrl = window.location.origin const baseUrl = window.location.origin
const statsUrl = `${baseUrl}/admin/api-stats?apiId=${apiKey.id}` const statsUrl = `${baseUrl}/admin-next/api-stats?apiId=${apiKey.id}`
// 使用传统的textarea方法复制到剪贴板 // 使用传统的textarea方法复制到剪贴板
const textarea = document.createElement('textarea') const textarea = document.createElement('textarea')
@@ -1173,4 +1308,5 @@ onMounted(async () => {
.api-key-date-picker :deep(.el-range-separator) { .api-key-date-picker :deep(.el-range-separator) {
@apply text-gray-500; @apply text-gray-500;
} }
</style> </style>