feat: 完善 WebUI 功能

This commit is contained in:
foxhui
2025-12-20 18:35:53 +08:00
Unverified
parent c8c7aec0e1
commit 10c96e420f
32 changed files with 1395 additions and 507 deletions
@@ -1 +1 @@
import{c as i,I as u}from"./index-N039yHLa.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z"}}]},name:"desktop",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=c({},t,e.attrs);return i(u,c({},n,{icon:l}),null)};o.displayName="DesktopOutlined";o.inheritAttrs=!1;export{o as D};
import{c as i,I as u}from"./index-BGGdzQd9.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z"}}]},name:"desktop",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=c({},t,e.attrs);return i(u,c({},n,{icon:l}),null)};o.displayName="DesktopOutlined";o.inheritAttrs=!1;export{o as D};
-1
View File
@@ -1 +0,0 @@
import{c as l,I as q,j as I,r as x,k as D,o as F,l as T,b as _,d as i,w as s,i as y,e as c,f as v,u as g,t as S,S as L,g as h,h as f,m as G,F as J}from"./index-N039yHLa.js";var Q={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"};function w(p){for(var n=1;n<arguments.length;n++){var a=arguments[n]!=null?Object(arguments[n]):{},o=Object.keys(a);typeof Object.getOwnPropertySymbols=="function"&&(o=o.concat(Object.getOwnPropertySymbols(a).filter(function(t){return Object.getOwnPropertyDescriptor(a,t).enumerable}))),o.forEach(function(t){R(p,t,a[t])})}return p}function R(p,n,a){return n in p?Object.defineProperty(p,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):p[n]=a,p}var k=function(n,a){var o=w({},n,a.attrs);return l(q,w({},o,{icon:Q}),null)};k.displayName="AppstoreOutlined";k.inheritAttrs=!1;const W={style:{display:"flex","align-items":"center","justify-content":"space-between"}},X={style:{display:"flex","align-items":"center"}},Y={style:{"font-weight":"600","font-size":"15px"}},Z={key:0},K={key:2},ee={key:4,style:{"font-size":"12px",color:"#8c8c8c","margin-top":"4px"}},te={style:{"text-align":"right"}},ae={__name:"adapters",setup(p){const n=I(),a=x(!1),o=x(null),t=D({});F(async()=>{await Promise.all([n.fetchAdaptersMeta(),n.fetchAdapterConfig()])});const O=T(()=>n.adaptersMeta),C=m=>{o.value=m;const r=n.adapterConfig[m.id]||{};Object.keys(t).forEach(u=>delete t[u]),m.configSchema&&m.configSchema.forEach(u=>{r[u.key]!==void 0?t[u.key]=r[u.key]:t[u.key]=u.default}),a.value=!0},V=async()=>{if(!o.value)return;const m={[o.value.id]:{...t}};await n.saveAdapterConfig(m)&&(a.value=!1)};return(m,r)=>{const u=c("a-button"),b=c("a-card"),z=c("a-list-item"),A=c("a-list"),U=c("a-empty"),H=c("a-input"),j=c("a-input-number"),M=c("a-switch"),P=c("a-select"),B=c("a-form-item"),E=c("a-form"),N=c("a-drawer"),$=c("a-layout");return i(),_($,{style:{background:"transparent"}},{default:s(()=>[l(b,{title:"适配器管理",bordered:!1},{extra:s(()=>[l(u,{type:"link",onClick:g(n).fetchAdaptersMeta},{default:s(()=>[...r[2]||(r[2]=[h("刷新列表",-1)])]),_:1},8,["onClick"])]),default:s(()=>[l(A,{grid:{gutter:16,xs:1,sm:2,md:3,lg:3,xl:4,xxl:4},"data-source":O.value},{renderItem:s(({item:e})=>[l(z,null,{default:s(()=>[l(b,{hoverable:"",onClick:d=>C(e),bodyStyle:{padding:"16px"}},{default:s(()=>[v("div",W,[v("div",X,[l(g(k),{style:{"font-size":"20px",color:"#1890ff","margin-right":"12px"}}),v("span",Y,S(e.id),1)]),l(g(L),{style:{"font-size":"16px",color:"#8c8c8c"}})])]),_:2},1032,["onClick"])]),_:2},1024)]),_:1},8,["data-source"])]),_:1}),o.value?(i(),_(N,{key:0,open:a.value,"onUpdate:open":r[1]||(r[1]=e=>a.value=e),title:`配置适配器 - ${o.value.name}`,width:"500",placement:"right"},{footer:s(()=>[v("div",te,[l(u,{style:{"margin-right":"8px"},onClick:r[0]||(r[0]=e=>a.value=!1)},{default:s(()=>[...r[3]||(r[3]=[h("取消",-1)])]),_:1}),l(u,{type:"primary",onClick:V},{default:s(()=>[...r[4]||(r[4]=[h("保存配置",-1)])]),_:1})])]),default:s(()=>[!o.value.configSchema||o.value.configSchema.length===0?(i(),f("div",Z,[l(U,{description:"该适配器没有可配置项"})])):(i(),_(E,{key:1,layout:"vertical"},{default:s(()=>[(i(!0),f(J,null,G(o.value.configSchema,e=>(i(),_(B,{key:e.key,label:e.label,required:e.required},{default:s(()=>[e.type==="string"?(i(),_(H,{key:0,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,placeholder:e.placeholder},null,8,["value","onUpdate:value","placeholder"])):y("",!0),e.type==="number"?(i(),_(j,{key:1,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,min:e.min,max:e.max,style:{width:"100%"}},null,8,["value","onUpdate:value","min","max"])):y("",!0),e.type==="boolean"?(i(),f("div",K,[l(M,{checked:t[e.key],"onUpdate:checked":d=>t[e.key]=d},null,8,["checked","onUpdate:checked"])])):y("",!0),e.type==="select"?(i(),_(P,{key:3,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,options:e.options},null,8,["value","onUpdate:value","options"])):y("",!0),e.note?(i(),f("div",ee,S(e.note),1)):y("",!0)]),_:2},1032,["label","required"]))),128))]),_:1}))]),_:1},8,["open","title"])):y("",!0)]),_:1})}}};export{ae as default};
+1
View File
@@ -0,0 +1 @@
import{c as l,I as q,j as I,r as b,k as D,o as F,l as T,b as _,d as u,w as s,e as v,f as c,g as y,u as h,t as w,S as L,h as g,i as f,m as G,F as J}from"./index-BGGdzQd9.js";var Q={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"};function S(p){for(var n=1;n<arguments.length;n++){var a=arguments[n]!=null?Object(arguments[n]):{},o=Object.keys(a);typeof Object.getOwnPropertySymbols=="function"&&(o=o.concat(Object.getOwnPropertySymbols(a).filter(function(t){return Object.getOwnPropertyDescriptor(a,t).enumerable}))),o.forEach(function(t){R(p,t,a[t])})}return p}function R(p,n,a){return n in p?Object.defineProperty(p,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):p[n]=a,p}var k=function(n,a){var o=S({},n,a.attrs);return l(q,S({},o,{icon:Q}),null)};k.displayName="AppstoreOutlined";k.inheritAttrs=!1;const W={style:{display:"flex","align-items":"center","justify-content":"space-between",gap:"8px"}},X={style:{display:"flex","align-items":"center","min-width":"0",flex:"1"}},Y={style:{"font-weight":"600","font-size":"14px",overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap"}},Z={key:0},K={key:2},ee={key:4,style:{"font-size":"12px",color:"#8c8c8c","margin-top":"4px"}},te={style:{"text-align":"right"}},ae={__name:"adapters",setup(p){const n=I(),a=b(!1),o=b(null),t=D({});F(async()=>{await Promise.all([n.fetchAdaptersMeta(),n.fetchAdapterConfig()])});const O=T(()=>n.adaptersMeta),C=m=>{o.value=m;const r=n.adapterConfig[m.id]||{};Object.keys(t).forEach(i=>delete t[i]),m.configSchema&&m.configSchema.forEach(i=>{r[i.key]!==void 0?t[i.key]=r[i.key]:t[i.key]=i.default}),a.value=!0},V=async()=>{if(!o.value)return;const m={[o.value.id]:{...t}};await n.saveAdapterConfig(m)&&(a.value=!1)};return(m,r)=>{const i=c("a-button"),x=c("a-card"),z=c("a-list-item"),A=c("a-list"),U=c("a-empty"),H=c("a-input"),j=c("a-input-number"),M=c("a-switch"),P=c("a-select"),B=c("a-form-item"),E=c("a-form"),N=c("a-drawer"),$=c("a-layout");return u(),_($,{style:{background:"transparent"}},{default:s(()=>[l(x,{title:"适配器管理",bordered:!1},{extra:s(()=>[l(i,{type:"link",onClick:h(n).fetchAdaptersMeta},{default:s(()=>[...r[2]||(r[2]=[g("刷新列表",-1)])]),_:1},8,["onClick"])]),default:s(()=>[l(A,{grid:{gutter:16,xs:1,sm:2,md:3,lg:3,xl:4,xxl:4},"data-source":O.value},{renderItem:s(({item:e})=>[l(z,null,{default:s(()=>[l(x,{hoverable:"",onClick:d=>C(e),bodyStyle:{padding:"12px 16px"}},{default:s(()=>[y("div",W,[y("div",X,[l(h(k),{style:{"font-size":"18px",color:"#1890ff","margin-right":"8px","flex-shrink":"0"}}),y("span",Y,w(e.id),1)]),l(h(L),{style:{"font-size":"16px",color:"#8c8c8c","flex-shrink":"0"}})])]),_:2},1032,["onClick"])]),_:2},1024)]),_:1},8,["data-source"])]),_:1}),o.value?(u(),_(N,{key:0,open:a.value,"onUpdate:open":r[1]||(r[1]=e=>a.value=e),title:`配置适配器 - ${o.value.id}`,width:"500",placement:"right"},{footer:s(()=>[y("div",te,[l(i,{style:{"margin-right":"8px"},onClick:r[0]||(r[0]=e=>a.value=!1)},{default:s(()=>[...r[3]||(r[3]=[g("取消",-1)])]),_:1}),l(i,{type:"primary",onClick:V},{default:s(()=>[...r[4]||(r[4]=[g("保存配置",-1)])]),_:1})])]),default:s(()=>[!o.value.configSchema||o.value.configSchema.length===0?(u(),f("div",Z,[l(U,{description:"该适配器没有可配置项"})])):(u(),_(E,{key:1,layout:"vertical"},{default:s(()=>[(u(!0),f(J,null,G(o.value.configSchema,e=>(u(),_(B,{key:e.key,label:e.label,required:e.required},{default:s(()=>[e.type==="string"?(u(),_(H,{key:0,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,placeholder:e.placeholder},null,8,["value","onUpdate:value","placeholder"])):v("",!0),e.type==="number"?(u(),_(j,{key:1,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,min:e.min,max:e.max,style:{width:"100%"}},null,8,["value","onUpdate:value","min","max"])):v("",!0),e.type==="boolean"?(u(),f("div",K,[l(M,{checked:t[e.key],"onUpdate:checked":d=>t[e.key]=d},null,8,["checked","onUpdate:checked"])])):v("",!0),e.type==="select"?(u(),_(P,{key:3,value:t[e.key],"onUpdate:value":d=>t[e.key]=d,options:e.options},null,8,["value","onUpdate:value","options"])):v("",!0),e.note?(u(),f("div",ee,w(e.note),1)):v("",!0)]),_:2},1032,["label","required"]))),128))]),_:1}))]),_:1},8,["open","title"])):v("",!0)]),_:1})}}};export{ae as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
[data-v-9531bced]::-webkit-scrollbar{width:6px;height:6px}[data-v-9531bced]::-webkit-scrollbar-thumb{background:#ccc;border-radius:3px}[data-v-9531bced]::-webkit-scrollbar-track{background:#f1f1f1}html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}@-ms-viewport{width:device-width}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{-webkit-text-decoration:underline dotted;text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}
[data-v-b38da084]::-webkit-scrollbar{width:6px;height:6px}[data-v-b38da084]::-webkit-scrollbar-thumb{background:#ccc;border-radius:3px}[data-v-b38da084]::-webkit-scrollbar-track{background:#f1f1f1}html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}@-ms-viewport{width:device-width}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{-webkit-text-decoration:underline dotted;text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.log-container[data-v-a8b1f18a]{max-height:600px;overflow-y:auto;font-family:Consolas,Monaco,monospace;font-size:12px;background:#fafafa;border-radius:4px;padding:12px}.log-line[data-v-a8b1f18a]{padding:4px 0;border-bottom:1px solid #f0f0f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.log-line[data-v-a8b1f18a]:hover{background:#e6f7ff;white-space:normal;word-break:break-all}.log-time[data-v-a8b1f18a]{color:#8c8c8c}.log-module[data-v-a8b1f18a]{color:#1890ff;margin-right:8px}.log-message[data-v-a8b1f18a]{color:#333}.level-erro .log-message[data-v-a8b1f18a]{color:#ff4d4f}.level-warn .log-message[data-v-a8b1f18a]{color:#faad14}.level-dbug .log-message[data-v-a8b1f18a]{color:#722ed1}.toolbar[data-v-a8b1f18a]{margin-bottom:16px}.toolbar-row[data-v-a8b1f18a]{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px}.toolbar-row[data-v-a8b1f18a]:last-child{margin-bottom:0}@media(min-width:768px){.toolbar[data-v-a8b1f18a]{display:flex;justify-content:space-between;align-items:center;gap:12px}.toolbar-row[data-v-a8b1f18a]{margin-bottom:0}.toolbar-row[data-v-a8b1f18a]:last-child{flex:1;max-width:300px}}
+2
View File
@@ -0,0 +1,2 @@
import{c as l,I as P,_ as q,j as J,r as p,l as Q,o as X,a as Y,b as N,d as b,w as s,g as v,f,h as m,u as C,R as k,y as Z,D as K,i as L,e as S,t as _,m as ee,F as te,s as R,M as ae,z as le,A as ne,B as oe}from"./index-BGGdzQd9.js";var se={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M304 280h56c4.4 0 8-3.6 8-8 0-28.3 5.9-53.2 17.1-73.5 10.6-19.4 26-34.8 45.4-45.4C450.9 142 475.7 136 504 136h16c28.3 0 53.2 5.9 73.5 17.1 19.4 10.6 34.8 26 45.4 45.4C650 218.9 656 243.7 656 272c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-40-8.8-76.7-25.9-108.1a184.31 184.31 0 00-74-74C596.7 72.8 560 64 520 64h-16c-40 0-76.7 8.8-108.1 25.9a184.31 184.31 0 00-74 74C304.8 195.3 296 232 296 272c0 4.4 3.6 8 8 8z"}},{tag:"path",attrs:{d:"M940 512H792V412c76.8 0 139-62.2 139-139 0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8a63 63 0 01-63 63H232a63 63 0 01-63-63c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 76.8 62.2 139 139 139v100H84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h148v96c0 6.5.2 13 .7 19.3C164.1 728.6 116 796.7 116 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-44.2 23.9-82.9 59.6-103.7a273 273 0 0022.7 49c24.3 41.5 59 76.2 100.5 100.5S460.5 960 512 960s99.8-13.9 141.3-38.2a281.38 281.38 0 00123.2-149.5A120 120 0 01836 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-79.3-48.1-147.4-116.7-176.7.4-6.4.7-12.8.7-19.3v-96h148c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM716 680c0 36.8-9.7 72-27.8 102.9-17.7 30.3-43 55.6-73.3 73.3C584 874.3 548.8 884 512 884s-72-9.7-102.9-27.8c-30.3-17.7-55.6-43-73.3-73.3A202.75 202.75 0 01308 680V412h408v268z"}}]},name:"bug",theme:"outlined"};function D(n){for(var t=1;t<arguments.length;t++){var a=arguments[t]!=null?Object(arguments[t]):{},r=Object.keys(a);typeof Object.getOwnPropertySymbols=="function"&&(r=r.concat(Object.getOwnPropertySymbols(a).filter(function(c){return Object.getOwnPropertyDescriptor(a,c).enumerable}))),r.forEach(function(c){re(n,c,a[c])})}return n}function re(n,t,a){return t in n?Object.defineProperty(n,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):n[t]=a,n}var j=function(t,a){var r=D({},t,a.attrs);return l(P,D({},r,{icon:se}),null)};j.displayName="BugOutlined";j.inheritAttrs=!1;var ce={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"};function E(n){for(var t=1;t<arguments.length;t++){var a=arguments[t]!=null?Object(arguments[t]):{},r=Object.keys(a);typeof Object.getOwnPropertySymbols=="function"&&(r=r.concat(Object.getOwnPropertySymbols(a).filter(function(c){return Object.getOwnPropertyDescriptor(a,c).enumerable}))),r.forEach(function(c){ie(n,c,a[c])})}return n}function ie(n,t,a){return t in n?Object.defineProperty(n,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):n[t]=a,n}var I=function(t,a){var r=E({},t,a.attrs);return l(P,E({},r,{icon:ce}),null)};I.displayName="WarningOutlined";I.inheritAttrs=!1;const ue={class:"toolbar"},de={class:"toolbar-row"},fe={class:"toolbar-row"},ve={style:{"margin-bottom":"12px",color:"#8c8c8c","font-size":"12px"}},me={key:0,style:{color:"#1890ff","margin-left":"8px"}},pe={class:"log-container"},ge={class:"log-time"},_e={class:"log-module"},he={class:"log-message"},Oe={__name:"logs",setup(n){const t=J(),a=p([]),r=p(!1),c=p(0),h=p(!1),g=p(null),y=p(""),w=p("all"),U={INFO:{color:"#1890ff",icon:oe},WARN:{color:"#faad14",icon:I},ERRO:{color:"#ff4d4f",icon:ne},DBUG:{color:"#722ed1",icon:j}},z=async()=>{r.value=!0;try{const o=await fetch("/admin/logs?lines=500",{headers:t.getHeaders()});if(o.ok){const e=await o.json();a.value=A(e.logs||[]),c.value=e.total||0}}catch{R.error("获取日志失败")}finally{r.value=!1}},A=o=>o.map((e,u)=>{const d=e.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[(\w+)\] \[([^\]]+)\] (.*)$/);return d?{id:u,time:d[1],level:d[2],module:d[3],message:d[4],raw:e}:{id:u,raw:e,level:"INFO",time:"",module:"",message:e}}),x=Q(()=>a.value.filter(o=>{if(w.value!=="all"&&o.level!==w.value)return!1;if(y.value){const e=y.value.toLowerCase();return o.raw.toLowerCase().includes(e)}return!0})),F=()=>{ae.confirm({title:"确认清除日志",content:"此操作将删除所有系统日志文件,是否继续?",okText:"确认清除",okType:"danger",cancelText:"取消",async onOk(){try{(await fetch("/admin/logs",{method:"DELETE",headers:t.getHeaders()})).ok?(R.success("日志已清除"),a.value=[],c.value=0):R.error("清除失败")}catch{R.error("请求失败")}}})},M=()=>{const o=a.value.map(O=>O.raw).join(`
`),e=new Blob([o],{type:"text/plain"}),u=URL.createObjectURL(e),d=document.createElement("a");d.href=u,d.download=`system-${new Date().toISOString().split("T")[0]}.log`,d.click(),URL.revokeObjectURL(u)},T=o=>{h.value=o,o?(z(),g.value=setInterval(z,5e3)):g.value&&(clearInterval(g.value),g.value=null)};return X(()=>{z()}),Y(()=>{g.value&&clearInterval(g.value)}),(o,e)=>{const u=f("a-select-option"),d=f("a-select"),O=f("a-button"),B=f("a-tooltip"),V=f("a-space"),W=f("a-input-search"),$=f("a-tag"),H=f("a-empty"),G=f("a-card");return b(),N(G,{title:"系统日志",bordered:!1},{default:s(()=>[v("div",ue,[v("div",de,[l(d,{value:w.value,"onUpdate:value":e[0]||(e[0]=i=>w.value=i),style:{width:"90px"},size:"small"},{default:s(()=>[l(u,{value:"all"},{default:s(()=>[...e[3]||(e[3]=[m("全部",-1)])]),_:1}),l(u,{value:"INFO"},{default:s(()=>[...e[4]||(e[4]=[m("INFO",-1)])]),_:1}),l(u,{value:"WARN"},{default:s(()=>[...e[5]||(e[5]=[m("WARN",-1)])]),_:1}),l(u,{value:"ERRO"},{default:s(()=>[...e[6]||(e[6]=[m("ERROR",-1)])]),_:1}),l(u,{value:"DBUG"},{default:s(()=>[...e[7]||(e[7]=[m("DEBUG",-1)])]),_:1})]),_:1},8,["value"]),l(V,{size:4},{default:s(()=>[l(B,{title:h.value?"关闭自动刷新":"开启自动刷新"},{default:s(()=>[l(O,{size:"small",type:h.value?"primary":"default",onClick:e[1]||(e[1]=i=>T(!h.value))},{icon:s(()=>[l(C(k))]),_:1},8,["type"])]),_:1},8,["title"]),l(B,{title:"导出日志"},{default:s(()=>[l(O,{size:"small",onClick:M},{icon:s(()=>[l(C(Z))]),_:1})]),_:1}),l(B,{title:"清除日志"},{default:s(()=>[l(O,{size:"small",danger:"",onClick:F},{icon:s(()=>[l(C(K))]),_:1})]),_:1})]),_:1})]),v("div",fe,[l(W,{value:y.value,"onUpdate:value":e[2]||(e[2]=i=>y.value=i),placeholder:"搜索日志",size:"small","enter-button":"","allow-clear":"",style:{width:"100%"}},null,8,["value"])])]),v("div",ve,[m(" 共 "+_(c.value)+" 条日志,当前显示 "+_(x.value.length)+" 条 ",1),h.value?(b(),L("span",me,[l(C(k),{spin:!0}),e[8]||(e[8]=m(" 自动刷新中 ",-1))])):S("",!0)]),v("div",pe,[(b(!0),L(te,null,ee(x.value,i=>(b(),L("div",{key:i.id,class:le(["log-line","level-"+i.level.toLowerCase()])},[v("span",ge,_(i.time),1),l($,{color:U[i.level]?.color||"#8c8c8c",size:"small",style:{margin:"0 8px"}},{default:s(()=>[m(_(i.level),1)]),_:2},1032,["color"]),v("span",_e,"["+_(i.module)+"]",1),v("span",he,_(i.message),1)],2))),128)),x.value.length===0?(b(),N(H,{key:0,description:"暂无日志"})):S("",!0)])]),_:1})}}},ye=q(Oe,[["__scopeId","data-v-a8b1f18a"]]);export{ye as default};
@@ -1 +1 @@
import{_ as b,j as w,k,o as c,b as C,d as S,w as n,c as o,f as e,e as a,g as i}from"./index-N039yHLa.js";const B={style:{"margin-bottom":"8px"}},T={style:{"margin-bottom":"8px"}},z={style:{"margin-bottom":"8px"}},U={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},j={style:{"margin-bottom":"8px"}},M={style:{"margin-bottom":"8px"}},q={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},L={__name:"server",setup(N){const d=w(),s=k({port:5173,authToken:"",keepaliveMode:"comment",queueBuffer:2,imageLimit:5});c(async()=>{await d.fetchServerConfig(),Object.assign(s,d.serverConfig)});const m=async()=>{await d.saveServerConfig(s)};return(V,t)=>{const p=a("a-input-number"),r=a("a-col"),y=a("a-input-password"),u=a("a-select-option"),g=a("a-select"),f=a("a-row"),v=a("a-button"),x=a("a-card"),_=a("a-layout");return S(),C(_,{style:{background:"transparent"}},{default:n(()=>[o(x,{title:"服务器设置",bordered:!1,style:{width:"100%"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",B,[t[5]||(t[5]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"监听端口",-1)),t[6]||(t[6]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 设置服务器监听的端口号,默认为 5173 ",-1)),o(p,{value:s.port,"onUpdate:value":t[0]||(t[0]=l=>s.port=l),min:1,max:65535,placeholder:"请输入端口号",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",T,[t[7]||(t[7]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"鉴权 Token",-1)),t[8]||(t[8]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 用于 API 请求鉴权的密钥,留空则不启用鉴权 ",-1)),o(y,{value:s.authToken,"onUpdate:value":t[1]||(t[1]=l=>s.authToken=l),placeholder:"请输入 Token",type:"password"},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",z,[t[11]||(t[11]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"心跳包类型",-1)),t[12]||(t[12]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 选择 SSE 流式响应的心跳包格式 ",-1)),o(g,{value:s.keepaliveMode,"onUpdate:value":t[2]||(t[2]=l=>s.keepaliveMode=l),style:{width:"100%"},placeholder:"请选择心跳包类型"},{default:n(()=>[o(u,{value:"comment"},{default:n(()=>[...t[9]||(t[9]=[i("Comment - 注释格式",-1)])]),_:1}),o(u,{value:"content"},{default:n(()=>[...t[10]||(t[10]=[i("Content - 内容格式",-1)])]),_:1})]),_:1},8,["value"])])]),_:1})]),_:1}),e("div",U,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[13]||(t[13]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1}),o(x,{title:"队列设置",bordered:!1,style:{width:"100%","margin-top":"10px"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",j,[t[14]||(t[14]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"队列缓冲区大小",-1)),t[15]||(t[15]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 非流式请求的额外排队数(设为 0 则不限制非流式请求数量)"),e("br"),i(" 实际队列上限 = Workers数量 + 缓冲区大小 ")],-1)),o(p,{value:s.queueBuffer,"onUpdate:value":t[3]||(t[3]=l=>s.queueBuffer=l),min:0,max:100,placeholder:"默认为 2",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",M,[t[16]||(t[16]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"图片数量上限",-1)),t[17]||(t[17]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 单次请求最多支持的图片附件数量"),e("br"),i(" 网页最多支持10个附件,超出会被丢弃 ")],-1)),o(p,{value:s.imageLimit,"onUpdate:value":t[4]||(t[4]=l=>s.imageLimit=l),min:1,max:10,placeholder:"默认为 5",style:{width:"100%"}},null,8,["value"])])]),_:1})]),_:1}),e("div",q,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[18]||(t[18]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1})]),_:1})}}},A=b(L,[["__scopeId","data-v-bd32923f"]]);export{A as default};
import{_ as b,j as w,k,o as c,b as C,d as S,w as n,c as o,g as e,f as a,h as i}from"./index-BGGdzQd9.js";const B={style:{"margin-bottom":"8px"}},T={style:{"margin-bottom":"8px"}},z={style:{"margin-bottom":"8px"}},U={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},j={style:{"margin-bottom":"8px"}},M={style:{"margin-bottom":"8px"}},q={style:{display:"flex","justify-content":"flex-end","margin-top":"24px"}},L={__name:"server",setup(N){const d=w(),s=k({port:5173,authToken:"",keepaliveMode:"comment",queueBuffer:2,imageLimit:5});c(async()=>{await d.fetchServerConfig(),Object.assign(s,d.serverConfig)});const m=async()=>{await d.saveServerConfig(s)};return(V,t)=>{const p=a("a-input-number"),r=a("a-col"),y=a("a-input-password"),u=a("a-select-option"),g=a("a-select"),f=a("a-row"),v=a("a-button"),x=a("a-card"),_=a("a-layout");return S(),C(_,{style:{background:"transparent"}},{default:n(()=>[o(x,{title:"服务器设置",bordered:!1,style:{width:"100%"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",B,[t[5]||(t[5]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"监听端口",-1)),t[6]||(t[6]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 设置服务器监听的端口号,默认为 5173 ",-1)),o(p,{value:s.port,"onUpdate:value":t[0]||(t[0]=l=>s.port=l),min:1,max:65535,placeholder:"请输入端口号",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",T,[t[7]||(t[7]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"鉴权 Token",-1)),t[8]||(t[8]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 用于 API 请求鉴权的密钥,留空则不启用鉴权 ",-1)),o(y,{value:s.authToken,"onUpdate:value":t[1]||(t[1]=l=>s.authToken=l),placeholder:"请输入 Token",type:"password"},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",z,[t[11]||(t[11]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"心跳包类型",-1)),t[12]||(t[12]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}}," 选择 SSE 流式响应的心跳包格式 ",-1)),o(g,{value:s.keepaliveMode,"onUpdate:value":t[2]||(t[2]=l=>s.keepaliveMode=l),style:{width:"100%"},placeholder:"请选择心跳包类型"},{default:n(()=>[o(u,{value:"comment"},{default:n(()=>[...t[9]||(t[9]=[i("Comment - 注释格式",-1)])]),_:1}),o(u,{value:"content"},{default:n(()=>[...t[10]||(t[10]=[i("Content - 内容格式",-1)])]),_:1})]),_:1},8,["value"])])]),_:1})]),_:1}),e("div",U,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[13]||(t[13]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1}),o(x,{title:"队列设置",bordered:!1,style:{width:"100%","margin-top":"10px"}},{default:n(()=>[o(f,{gutter:[16,16]},{default:n(()=>[o(r,{xs:24,md:12},{default:n(()=>[e("div",j,[t[14]||(t[14]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"队列缓冲区大小",-1)),t[15]||(t[15]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 非流式请求的额外排队数(设为 0 则不限制非流式请求数量)"),e("br"),i(" 实际队列上限 = Workers数量 + 缓冲区大小 ")],-1)),o(p,{value:s.queueBuffer,"onUpdate:value":t[3]||(t[3]=l=>s.queueBuffer=l),min:0,max:100,placeholder:"默认为 2",style:{width:"100%"}},null,8,["value"])])]),_:1}),o(r,{xs:24,md:12},{default:n(()=>[e("div",M,[t[16]||(t[16]=e("div",{style:{"font-weight":"600","margin-bottom":"4px"}},"图片数量上限",-1)),t[17]||(t[17]=e("div",{style:{"font-size":"12px",color:"#8c8c8c","margin-bottom":"8px"}},[i(" 单次请求最多支持的图片附件数量"),e("br"),i(" 网页最多支持10个附件,超出会被丢弃 ")],-1)),o(p,{value:s.imageLimit,"onUpdate:value":t[4]||(t[4]=l=>s.imageLimit=l),min:1,max:10,placeholder:"默认为 5",style:{width:"100%"}},null,8,["value"])])]),_:1})]),_:1}),e("div",q,[o(v,{type:"primary",onClick:m},{default:n(()=>[...t[18]||(t[18]=[i(" 保存设置 ",-1)])]),_:1})])]),_:1})]),_:1})}}},A=b(L,[["__scopeId","data-v-bd32923f"]]);export{A as default};
-1
View File
@@ -1 +0,0 @@
import{x as i,j as a,s as r}from"./index-N039yHLa.js";const u=i("system",{state:()=>({status:"",version:"1.0.0",systemVersion:"",uptime:0,cpuUsage:0,memoryUsage:{total:0,used:0,free:0},stats:{totalRequests:0,successRate:0,activeWorkers:0,totalWorkers:0,avgResponseTime:0}}),actions:{async fetchStatus(){const t=a();try{const e=await fetch("/admin/status",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.$patch(s)}}catch(e){console.error("Failed to fetch system status:",e)}},async fetchStats(){const t=a();try{const e=await fetch("/admin/stats",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.stats=s}}catch(e){console.error("Failed to fetch stats:",e)}},async restartService(t={}){const e=a(),{loginMode:s,workerName:n}=t;try{const o=await(await fetch("/admin/restart",{method:"POST",headers:{...e.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({loginMode:s,workerName:n})})).json();return o.success?(r.success(o.message||"服务重启中..."),!0):(r.error("重启失败"),!1)}catch{return r.error("重启请求失败"),!1}},async stopService(){const t=a();try{const s=await(await fetch("/admin/stop",{method:"POST",headers:t.getHeaders()})).json();return s.success?(r.success(s.message||"服务停止中..."),!0):(r.error("停止失败"),!1)}catch{return r.error("停止请求失败"),!1}}}});export{u};
+1
View File
@@ -0,0 +1 @@
import{x as i,j as a,s as r}from"./index-BGGdzQd9.js";const u=i("system",{state:()=>({status:"",version:"1.0.0",systemVersion:"",uptime:0,cpuUsage:0,memoryUsage:{total:0,used:0,free:0},safeMode:{enabled:!1,reason:null},stats:{totalRequests:0,successRate:0,activeWorkers:0,totalWorkers:0,avgResponseTime:0}}),actions:{async fetchStatus(){const t=a();try{const e=await fetch("/admin/status",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.$patch(s)}}catch(e){console.error("Failed to fetch system status:",e)}},async fetchStats(){const t=a();try{const e=await fetch("/admin/stats",{headers:t.getHeaders()});if(e.ok){const s=await e.json();this.stats=s}}catch(e){console.error("Failed to fetch stats:",e)}},async restartService(t={}){const e=a(),{loginMode:s,workerName:n}=t;try{const o=await(await fetch("/admin/restart",{method:"POST",headers:{...e.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({loginMode:s,workerName:n})})).json();return o.success?(r.success(o.message||"服务重启中..."),!0):(r.error("重启失败"),!1)}catch{return r.error("重启请求失败"),!1}},async stopService(){const t=a();try{const s=await(await fetch("/admin/stop",{method:"POST",headers:t.getHeaders()})).json();return s.success?(r.success(s.message||"服务停止中..."),!0):(r.error("停止失败"),!1)}catch{return r.error("停止请求失败"),!1}}}});export{u};
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,8 +6,8 @@
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebAI2API</title>
<script type="module" crossorigin src="/assets/index-N039yHLa.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CAMDRWk5.css">
<script type="module" crossorigin src="/assets/index-BGGdzQd9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B8cer5ye.css">
</head>
<body>
+348 -4
View File
@@ -1,13 +1,19 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { Modal } from 'ant-design-vue';
import { Modal, message } from 'ant-design-vue';
import {
DashboardOutlined,
SettingOutlined,
ToolOutlined,
PoweroffOutlined,
GithubOutlined
GithubOutlined,
ApiOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
LoadingOutlined,
InboxOutlined,
PictureOutlined
} from '@ant-design/icons-vue';
import { useSettingsStore } from '@/stores/settings';
import LoginModal from '@/components/auth/LoginModal.vue';
@@ -29,6 +35,189 @@ const enterIconLoading = () => {
}, 500);
};
// 接口测试抽屉
const apiTestDrawer = ref(false);
const apiTestResults = ref({
models: { status: 'pending', data: null, error: null },
cookies: { status: 'pending', data: null, error: null },
chat: { status: 'pending', data: null, error: null }
});
const chatTestPrompt = ref('Say hello in one word');
const chatTestModel = ref('');
const chatModelList = ref([]);
const chatImageList = ref([]);
const chatStreamMode = ref(false);
const chatStreamContent = ref('');
// 获取模型列表
const fetchModelList = async () => {
try {
const res = await fetch('/v1/models', { headers: settingsStore.getHeaders() });
if (res.ok) {
const data = await res.json();
chatModelList.value = data.data || [];
if (chatModelList.value.length > 0 && !chatTestModel.value) {
chatTestModel.value = chatModelList.value[0].id;
}
}
} catch (e) {
console.error('获取模型列表失败', e);
}
};
// 图片转 base64
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
});
};
// 图片上传前检查
const beforeUpload = (file) => {
const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
message.error('仅支持 PNG, JPEG, GIF, WebP 格式');
return false;
}
if (chatImageList.value.length >= 10) {
message.error('最多上传 10 张图片');
return false;
}
return false; // 阻止自动上传,手动处理
};
// 处理图片选择
const handleImageChange = async (info) => {
const file = info.file;
if (file.status === 'removed') {
chatImageList.value = chatImageList.value.filter(f => f.uid !== file.uid);
return;
}
try {
const base64 = await fileToBase64(file.originFileObj || file);
chatImageList.value.push({
uid: file.uid,
name: file.name,
base64
});
} catch (e) {
message.error('图片读取失败');
}
};
const testApi = async (type) => {
apiTestResults.value[type].status = 'loading';
apiTestResults.value[type].error = null;
apiTestResults.value[type].data = null;
chatStreamContent.value = '';
try {
let url, options;
if (type === 'models') {
url = '/v1/models';
options = { headers: settingsStore.getHeaders() };
} else if (type === 'cookies') {
url = '/v1/cookies';
options = { headers: settingsStore.getHeaders() };
} else if (type === 'chat') {
url = '/v1/chat/completions';
// 构建消息内容
let content;
if (chatImageList.value.length > 0) {
// 多模态请求
content = [
{ type: 'text', text: chatTestPrompt.value }
];
for (const img of chatImageList.value) {
content.push({
type: 'image_url',
image_url: { url: img.base64 }
});
}
} else {
content = chatTestPrompt.value;
}
options = {
method: 'POST',
headers: { ...settingsStore.getHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({
model: chatTestModel.value,
messages: [{ role: 'user', content }],
stream: chatStreamMode.value
})
};
// 流式请求处理
if (chatStreamMode.value) {
const res = await fetch(url, options);
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.error?.message || `HTTP ${res.status}`);
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') continue;
try {
const json = JSON.parse(data);
const delta = json.choices?.[0]?.delta?.content || '';
chatStreamContent.value += delta;
} catch { /* 忽略解析错误 */ }
}
}
}
apiTestResults.value[type].status = 'success';
apiTestResults.value[type].data = { content: chatStreamContent.value };
return;
}
}
const res = await fetch(url, options);
const data = await res.json();
if (res.ok) {
apiTestResults.value[type].status = 'success';
apiTestResults.value[type].data = data;
} else {
apiTestResults.value[type].status = 'error';
apiTestResults.value[type].error = data.error?.message || `HTTP ${res.status}`;
}
} catch (e) {
apiTestResults.value[type].status = 'error';
apiTestResults.value[type].error = e.message;
}
};
const openApiTestDrawer = () => {
apiTestDrawer.value = true;
// 重置状态
Object.keys(apiTestResults.value).forEach(key => {
apiTestResults.value[key] = { status: 'pending', data: null, error: null };
});
chatImageList.value = [];
// 获取模型列表
fetchModelList();
};
// 菜单 key 到路由路径的映射
const menuRoutes = {
'dash': '/',
@@ -37,7 +226,8 @@ const menuRoutes = {
'settings-browser': '/settings/browser',
'settings-adapters': '/settings/adapters',
'tools-display': '/tools/display',
'tools-cache': '/tools/cache'
'tools-cache': '/tools/cache',
'tools-logs': '/tools/logs'
};
// 处理菜单点击
@@ -134,7 +324,13 @@ onMounted(async () => {
<div class="logo" style="font-size: 1.25rem; font-weight: bold; color: #1890ff; margin-right: 24px;">
WebAI2API
</div>
<a-flex justify="end" align="center" style="flex: 1;">
<a-flex justify="end" align="center" style="flex: 1;" :gap="12">
<a-button @click="openApiTestDrawer">
<template #icon>
<ApiOutlined />
</template>
接口测试
</a-button>
<a-button danger :loading="iconLoading" @click="enterIconLoading">
<template #icon>
<PoweroffOutlined />
@@ -172,6 +368,7 @@ onMounted(async () => {
</template>
<a-menu-item key="tools-display">虚拟显示器</a-menu-item>
<a-menu-item key="tools-cache">缓存与重启</a-menu-item>
<a-menu-item key="tools-logs">日志查看器</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
@@ -193,6 +390,153 @@ onMounted(async () => {
</a-layout>
</a-layout>
</a-layout>
<!-- 接口测试抽屉 -->
<a-drawer v-model:open="apiTestDrawer" title="接口测试" placement="right" :width="500">
<a-space direction="vertical" style="width: 100%" size="large">
<!-- Models 接口 -->
<a-card title="GET /v1/models" size="small">
<template #extra>
<a-button size="small" type="primary" @click="testApi('models')"
:loading="apiTestResults.models.status === 'loading'">
测试
</a-button>
</template>
<div v-if="apiTestResults.models.status === 'success'">
<a-tag color="success">
<CheckCircleOutlined /> 成功
</a-tag>
<div style="margin-top: 8px; font-size: 12px; color: #8c8c8c;">
返回 {{ apiTestResults.models.data?.data?.length || 0 }} 个模型
</div>
</div>
<div v-else-if="apiTestResults.models.status === 'error'">
<a-tag color="error">
<CloseCircleOutlined /> 失败
</a-tag>
<div style="margin-top: 8px; font-size: 12px; color: #ff4d4f;">
{{ apiTestResults.models.error }}
</div>
</div>
<div v-else style="color: #8c8c8c; font-size: 12px;">点击测试按钮开始</div>
</a-card>
<!-- Cookies 接口 -->
<a-card title="GET /v1/cookies" size="small">
<template #extra>
<a-button size="small" type="primary" @click="testApi('cookies')"
:loading="apiTestResults.cookies.status === 'loading'">
测试
</a-button>
</template>
<div v-if="apiTestResults.cookies.status === 'success'">
<a-tag color="success">
<CheckCircleOutlined /> 成功
</a-tag>
<div style="margin-top: 8px; font-size: 12px; color: #8c8c8c;">
返回 {{ apiTestResults.cookies.data?.cookies?.length || 0 }} Cookie
</div>
</div>
<div v-else-if="apiTestResults.cookies.status === 'error'">
<a-tag color="error">
<CloseCircleOutlined /> 失败
</a-tag>
<div style="margin-top: 8px; font-size: 12px; color: #ff4d4f;">
{{ apiTestResults.cookies.error }}
</div>
</div>
<div v-else style="color: #8c8c8c; font-size: 12px;">点击测试按钮开始</div>
</a-card>
<!-- Chat 接口 -->
<a-card title="POST /v1/chat/completions" size="small">
<template #extra>
<a-button size="small" type="primary" @click="testApi('chat')"
:loading="apiTestResults.chat.status === 'loading'" :disabled="!chatTestModel">
测试
</a-button>
</template>
<!-- 模型选择 -->
<div style="margin-bottom: 12px;">
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 4px;">模型</div>
<a-select v-model:value="chatTestModel" style="width: 100%" size="small" placeholder="选择模型" show-search>
<a-select-option v-for="model in chatModelList" :key="model.id" :value="model.id">
{{ model.id }}
</a-select-option>
</a-select>
</div>
<!-- 提示词 -->
<div style="margin-bottom: 12px;">
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 4px;">提示词</div>
<a-textarea v-model:value="chatTestPrompt" placeholder="输入提示词" :rows="2" size="small" />
</div>
<!-- 图片上传 -->
<div style="margin-bottom: 12px;">
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 4px;">
附加图片 ({{ chatImageList.length }}/10)
</div>
<a-upload-dragger :file-list="[]" :multiple="true" :before-upload="beforeUpload" @change="handleImageChange"
accept=".png,.jpg,.jpeg,.gif,.webp" :show-upload-list="false" style="padding: 8px;">
<p style="margin: 0;">
<InboxOutlined style="font-size: 24px; color: #1890ff;" />
</p>
<p style="font-size: 12px; margin: 4px 0 0 0; color: #8c8c8c;">
点击或拖拽上传图片 (PNG/JPEG/GIF/WebP)
</p>
</a-upload-dragger>
<div v-if="chatImageList.length > 0" style="margin-top: 8px; display: flex; flex-wrap: wrap; gap: 4px;">
<a-tag v-for="img in chatImageList" :key="img.uid" closable
@close="chatImageList = chatImageList.filter(i => i.uid !== img.uid)">
<PictureOutlined /> {{ img.name.slice(0, 15) }}{{ img.name.length > 15 ? '...' : '' }}
</a-tag>
</div>
</div>
<!-- 流式选项 -->
<div style="margin-bottom: 12px;">
<a-checkbox v-model:checked="chatStreamMode">流式响应</a-checkbox>
</div>
<!-- 测试结果 -->
<!-- 流式模式实时显示内容 -->
<div v-if="chatStreamMode && apiTestResults.chat.status === 'loading'"
style="background: #fafafa; padding: 12px; border-radius: 4px; font-size: 12px;">
<div style="color: #1890ff; margin-bottom: 8px;">
<LoadingOutlined /> 正在接收流式响应...
</div>
<pre style="white-space: pre-wrap; word-break: break-all; margin: 0; min-height: 50px;">{{ chatStreamContent ||
'等待内容...' }}</pre>
</div>
<div v-else-if="apiTestResults.chat.status === 'success'">
<a-tag color="success">
<CheckCircleOutlined /> 成功
</a-tag>
<div
style="margin-top: 8px; font-size: 12px; max-height: 200px; overflow-y: auto; background: #fafafa; padding: 8px; border-radius: 4px;">
<pre v-if="chatStreamMode" style="white-space: pre-wrap; word-break: break-all; margin: 0;">{{
apiTestResults.chat.data?.content || '' }}</pre>
<pre v-else style="white-space: pre-wrap; word-break: break-all; margin: 0;">{{
JSON.stringify(apiTestResults.chat.data, null, 2) }}</pre>
</div>
</div>
<div v-else-if="apiTestResults.chat.status === 'error'">
<a-tag color="error">
<CloseCircleOutlined /> 失败
</a-tag>
<div style="margin-top: 8px; font-size: 12px; color: #ff4d4f;">
{{ apiTestResults.chat.error }}
</div>
</div>
<div v-else-if="apiTestResults.chat.status === 'loading' && !chatStreamMode"
style="color: #1890ff; font-size: 12px;">
<LoadingOutlined /> 请求中可能需要较长时间...
</div>
</a-card>
</a-space>
</a-drawer>
</div>
</template>
+20
View File
@@ -95,6 +95,26 @@ onUnmounted(() => {
<template>
<a-layout style="width: 100%; background: transparent;">
<!-- 安全模式告警横幅 -->
<a-alert v-if="systemStore.safeMode?.enabled" type="error" show-icon style="margin-bottom: 16px;" closable>
<template #message>
<span style="font-weight: 600;"> 安全模式</span>
</template>
<template #description>
<div>
<p style="margin-bottom: 8px;">
服务因初始化失败进入安全模式OpenAI API 不可用
</p>
<p style="margin-bottom: 8px; color: #cf1322;">
<b>原因</b>{{ systemStore.safeMode.reason }}
</p>
<p style="margin: 0;">
请前往系统设置修改正确的配置后重启服务
</p>
</div>
</template>
</a-alert>
<!-- 响应式布局手机竖向电脑横向 -->
<a-row :gutter="[16, 16]" style="margin-bottom: 24px">
<!-- 系统信息卡片 -->
+11 -8
View File
@@ -69,13 +69,16 @@ const handleSave = async () => {
<a-list :grid="{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 3, xl: 4, xxl: 4 }" :data-source="adapters">
<template #renderItem="{ item }">
<a-list-item>
<a-card hoverable @click="handleEdit(item)" :bodyStyle="{ padding: '16px' }">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center;">
<AppstoreOutlined style="font-size: 20px; color: #1890ff; margin-right: 12px;" />
<span style="font-weight: 600; font-size: 15px;">{{ item.id }}</span>
<a-card hoverable @click="handleEdit(item)" :bodyStyle="{ padding: '12px 16px' }">
<div style="display: flex; align-items: center; justify-content: space-between; gap: 8px;">
<div style="display: flex; align-items: center; min-width: 0; flex: 1;">
<AppstoreOutlined
style="font-size: 18px; color: #1890ff; margin-right: 8px; flex-shrink: 0;" />
<span
style="font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{
item.id }}</span>
</div>
<SettingOutlined style="font-size: 16px; color: #8c8c8c;" />
<SettingOutlined style="font-size: 16px; color: #8c8c8c; flex-shrink: 0;" />
</div>
</a-card>
</a-list-item>
@@ -84,8 +87,8 @@ const handleSave = async () => {
</a-card>
<!-- 配置抽屉 -->
<a-drawer v-if="currentAdapter" v-model:open="drawerVisible" :title="`配置适配器 - ${currentAdapter.name}`"
width="500" placement="right">
<a-drawer v-if="currentAdapter" v-model:open="drawerVisible" :title="`配置适配器 - ${currentAdapter.id}`" width="500"
placement="right">
<div v-if="!currentAdapter.configSchema || currentAdapter.configSchema.length === 0">
<a-empty description="该适配器没有可配置项" />
</div>
+306
View File
@@ -0,0 +1,306 @@
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { useSettingsStore } from '@/stores/settings';
import {
ReloadOutlined,
DeleteOutlined,
SearchOutlined,
DownloadOutlined,
WarningOutlined,
CloseCircleOutlined,
InfoCircleOutlined,
BugOutlined
} from '@ant-design/icons-vue';
import { message, Modal } from 'ant-design-vue';
const settingsStore = useSettingsStore();
const logs = ref([]);
const loading = ref(false);
const total = ref(0);
const autoRefresh = ref(false);
const refreshInterval = ref(null);
const searchText = ref('');
const levelFilter = ref('all');
// 日志级别配置
const levelConfig = {
'INFO': { color: '#1890ff', icon: InfoCircleOutlined },
'WARN': { color: '#faad14', icon: WarningOutlined },
'ERRO': { color: '#ff4d4f', icon: CloseCircleOutlined },
'DBUG': { color: '#722ed1', icon: BugOutlined }
};
// 获取日志
const fetchLogs = async () => {
loading.value = true;
try {
const res = await fetch('/admin/logs?lines=500', {
headers: settingsStore.getHeaders()
});
if (res.ok) {
const data = await res.json();
logs.value = parseLogs(data.logs || []);
total.value = data.total || 0;
}
} catch (e) {
message.error('获取日志失败');
} finally {
loading.value = false;
}
};
// 解析日志行
const parseLogs = (lines) => {
return lines.map((line, index) => {
// 格式: 2025-12-20 17:00:00.000 [INFO] [模块] 消息
const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[(\w+)\] \[([^\]]+)\] (.*)$/);
if (match) {
return {
id: index,
time: match[1],
level: match[2],
module: match[3],
message: match[4],
raw: line
};
}
return { id: index, raw: line, level: 'INFO', time: '', module: '', message: line };
});
};
// 过滤后的日志
const filteredLogs = computed(() => {
return logs.value.filter(log => {
// 级别过滤
if (levelFilter.value !== 'all' && log.level !== levelFilter.value) {
return false;
}
// 搜索过滤
if (searchText.value) {
const search = searchText.value.toLowerCase();
return log.raw.toLowerCase().includes(search);
}
return true;
});
});
// 清除日志
const clearLogs = () => {
Modal.confirm({
title: '确认清除日志',
content: '此操作将删除所有系统日志文件,是否继续?',
okText: '确认清除',
okType: 'danger',
cancelText: '取消',
async onOk() {
try {
const res = await fetch('/admin/logs', {
method: 'DELETE',
headers: settingsStore.getHeaders()
});
if (res.ok) {
message.success('日志已清除');
logs.value = [];
total.value = 0;
} else {
message.error('清除失败');
}
} catch (e) {
message.error('请求失败');
}
}
});
};
// 导出日志
const exportLogs = () => {
const content = logs.value.map(l => l.raw).join('\n');
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `system-${new Date().toISOString().split('T')[0]}.log`;
a.click();
URL.revokeObjectURL(url);
};
// 切换自动刷新
const toggleAutoRefresh = (newState) => {
autoRefresh.value = newState;
if (newState) {
fetchLogs(); // 立即刷新一次
refreshInterval.value = setInterval(fetchLogs, 5000);
} else {
if (refreshInterval.value) {
clearInterval(refreshInterval.value);
refreshInterval.value = null;
}
}
};
onMounted(() => {
fetchLogs();
});
onUnmounted(() => {
if (refreshInterval.value) {
clearInterval(refreshInterval.value);
}
});
</script>
<template>
<a-card title="系统日志" :bordered="false">
<!-- 工具栏 -->
<div class="toolbar">
<!-- 第一行级别筛选和操作按钮 -->
<div class="toolbar-row">
<a-select v-model:value="levelFilter" style="width: 90px" size="small">
<a-select-option value="all">全部</a-select-option>
<a-select-option value="INFO">INFO</a-select-option>
<a-select-option value="WARN">WARN</a-select-option>
<a-select-option value="ERRO">ERROR</a-select-option>
<a-select-option value="DBUG">DEBUG</a-select-option>
</a-select>
<a-space :size="4">
<a-tooltip :title="autoRefresh ? '关闭自动刷新' : '开启自动刷新'">
<a-button size="small" :type="autoRefresh ? 'primary' : 'default'"
@click="toggleAutoRefresh(!autoRefresh)">
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip title="导出日志">
<a-button size="small" @click="exportLogs">
<template #icon>
<DownloadOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip title="清除日志">
<a-button size="small" danger @click="clearLogs">
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</div>
<!-- 第二行搜索框 -->
<div class="toolbar-row">
<a-input-search v-model:value="searchText" placeholder="搜索日志" size="small" enter-button allow-clear
style="width: 100%;" />
</div>
</div>
<!-- 统计信息 -->
<div style="margin-bottom: 12px; color: #8c8c8c; font-size: 12px;">
{{ total }} 条日志当前显示 {{ filteredLogs.length }}
<span v-if="autoRefresh" style="color: #1890ff; margin-left: 8px;">
<ReloadOutlined :spin="true" /> 自动刷新中
</span>
</div>
<!-- 日志列表 -->
<div class="log-container">
<div v-for="log in filteredLogs" :key="log.id" class="log-line" :class="'level-' + log.level.toLowerCase()">
<span class="log-time">{{ log.time }}</span>
<a-tag :color="levelConfig[log.level]?.color || '#8c8c8c'" size="small" style="margin: 0 8px;">
{{ log.level }}
</a-tag>
<span class="log-module">[{{ log.module }}]</span>
<span class="log-message">{{ log.message }}</span>
</div>
<a-empty v-if="filteredLogs.length === 0" description="暂无日志" />
</div>
</a-card>
</template>
<style scoped>
.log-container {
max-height: 600px;
overflow-y: auto;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 12px;
background: #fafafa;
border-radius: 4px;
padding: 12px;
}
.log-line {
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.log-line:hover {
background: #e6f7ff;
white-space: normal;
word-break: break-all;
}
.log-time {
color: #8c8c8c;
}
.log-module {
color: #1890ff;
margin-right: 8px;
}
.log-message {
color: #333;
}
.level-erro .log-message {
color: #ff4d4f;
}
.level-warn .log-message {
color: #faad14;
}
.level-dbug .log-message {
color: #722ed1;
}
/* 工具栏样式 */
.toolbar {
margin-bottom: 16px;
}
.toolbar-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.toolbar-row:last-child {
margin-bottom: 0;
}
/* 大屏幕:工具栏一行显示 */
@media (min-width: 768px) {
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.toolbar-row {
margin-bottom: 0;
}
.toolbar-row:last-child {
flex: 1;
max-width: 300px;
}
}
</style>
+1
View File
@@ -13,6 +13,7 @@ const routes = [
{ path: '/settings/adapters', component: () => import('@/components/settings/adapters.vue') },
{ path: '/tools/display', component: () => import('@/components/tools/display.vue') },
{ path: '/tools/cache', component: () => import('@/components/tools/cache.vue') },
{ path: '/tools/logs', component: () => import('@/components/tools/logs.vue') },
];
const router = createRouter({
+6
View File
@@ -16,6 +16,12 @@ export const useSystemStore = defineStore('system', {
free: 0
},
// 安全模式状态
safeMode: {
enabled: false,
reason: null
},
// 仪表盘统计信息
stats: {
totalRequests: 0,
+4
View File
@@ -16,6 +16,10 @@ export default defineConfig({
'/admin': {
target: 'http://127.0.0.1:3000',
changeOrigin: true
},
'/v1': {
target: 'http://127.0.0.1:3000',
changeOrigin: true
}
}
}