feat: 将前端 dist 嵌入 Go 后端实现单文件部署

This commit is contained in:
sar
2026-01-14 13:33:15 +08:00
parent 93aa31219d
commit f4f5ad6bd1
23 changed files with 103 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ import (
"gpt-manager-go/internal/middleware" "gpt-manager-go/internal/middleware"
"gpt-manager-go/internal/repository" "gpt-manager-go/internal/repository"
"gpt-manager-go/internal/service" "gpt-manager-go/internal/service"
"gpt-manager-go/internal/static"
) )
// SetupRoutes 设置路由 // SetupRoutes 设置路由
@@ -86,6 +87,9 @@ func SetupRoutes(db *sql.DB) http.Handler {
mux.Handle("/api/cardkeys", middleware.AuthMiddleware(protectedMux)) mux.Handle("/api/cardkeys", middleware.AuthMiddleware(protectedMux))
mux.Handle("/api/cardkeys/", middleware.AuthMiddleware(protectedMux)) mux.Handle("/api/cardkeys/", middleware.AuthMiddleware(protectedMux))
// 静态文件服务(前端)
mux.Handle("/", static.Handler())
// CORS 中间件包装 // CORS 中间件包装
return corsMiddleware(mux) return corsMiddleware(mux)
} }

View File

@@ -0,0 +1 @@
import{d as t,h as o,n as r,u as n,v as c,x as l,o as p}from"./index-Dn7IzCAm.js";const i=t({__name:"CardDescription",props:{class:{}},setup(s){const e=s;return(a,d)=>(p(),o("p",{"data-slot":"card-description",class:r(n(c)("text-muted-foreground text-sm",e.class))},[l(a.$slots,"default")],2))}});export{i as _};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{d as r,h as e,x as o,n as c,u as n,v as d,o as l}from"./index-Dn7IzCAm.js";const _=r({__name:"Card",props:{class:{}},setup(s){const a=s;return(t,p)=>(l(),e("div",{"data-slot":"card",class:c(n(d)("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",a.class))},[o(t.$slots,"default")],2))}}),i=r({__name:"CardContent",props:{class:{}},setup(s){const a=s;return(t,p)=>(l(),e("div",{"data-slot":"card-content",class:c(n(d)("px-6",a.class))},[o(t.$slots,"default")],2))}}),m=r({__name:"CardHeader",props:{class:{}},setup(s){const a=s;return(t,p)=>(l(),e("div",{"data-slot":"card-header",class:c(n(d)("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",a.class))},[o(t.$slots,"default")],2))}}),f=r({__name:"CardTitle",props:{class:{}},setup(s){const a=s;return(t,p)=>(l(),e("h3",{"data-slot":"card-title",class:c(n(d)("leading-none font-semibold",a.class))},[o(t.$slots,"default")],2))}});export{_,m as a,f as b,i as c};

View File

@@ -0,0 +1 @@
import{c as b,d as w,p as k,j as g,h as i,f as d,a as r,i as v,b as t,w as s,u as e,_ as h,e as n,n as C,U as j,t as c,o}from"./index-Dn7IzCAm.js";import{_ as f,a as m,b as _,c as u}from"./CardTitle.vue_vue_type_script_setup_true_lang-nfBPK_e7.js";import{_ as p}from"./Skeleton.vue_vue_type_script_setup_true_lang-BCg0BhRz.js";import{u as M}from"./accounts-m0t2YqdJ.js";import{R as V}from"./refresh-cw-6LwrArgC.js";import{C as $}from"./circle-x-DpzkZ8l0.js";const z=b("armchair",[["path",{d:"M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3",key:"irtipd"}],["path",{d:"M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z",key:"1qyhux"}],["path",{d:"M5 18v2",key:"ppbyun"}],["path",{d:"M19 18v2",key:"gy7782"}]]);const A=b("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]),B={class:"space-y-6"},T={class:"flex items-center justify-between"},D={class:"grid gap-4 md:grid-cols-2 lg:grid-cols-4"},N={key:1,class:"text-2xl font-bold"},S={key:1,class:"text-2xl font-bold text-green-600"},L={key:1,class:"text-2xl font-bold text-red-600"},R={key:1,class:"text-2xl font-bold"},U={class:"flex items-center justify-between"},q={class:"text-destructive"},J=w({__name:"DashboardPage",setup(E){const l=M();k(()=>{x()});async function x(){try{await l.fetchAccounts()}catch(y){g.error(y.message||"加载数据失败")}}return(y,a)=>(o(),i("div",B,[d("div",T,[a[1]||(a[1]=d("h1",{class:"text-2xl font-bold"},"Dashboard",-1)),t(e(h),{variant:"outline",size:"sm",onClick:x,disabled:e(l).loading},{default:s(()=>[t(e(V),{class:C(["h-4 w-4 mr-2",e(l).loading&&"animate-spin"])},null,8,["class"]),a[0]||(a[0]=n(" 刷新 ",-1))]),_:1},8,["disabled"])]),d("div",D,[t(e(f),null,{default:s(()=>[t(e(m),{class:"flex flex-row items-center justify-between space-y-0 pb-2"},{default:s(()=>[t(e(_),{class:"text-sm font-medium"},{default:s(()=>[...a[2]||(a[2]=[n("Team 总数",-1)])]),_:1}),t(e(j),{class:"h-4 w-4 text-muted-foreground"})]),_:1}),t(e(u),null,{default:s(()=>[e(l).loading?(o(),r(e(p),{key:0,class:"h-8 w-16"})):(o(),i("div",N,c(e(l).totalTeams),1))]),_:1})]),_:1}),t(e(f),null,{default:s(()=>[t(e(m),{class:"flex flex-row items-center justify-between space-y-0 pb-2"},{default:s(()=>[t(e(_),{class:"text-sm font-medium"},{default:s(()=>[...a[3]||(a[3]=[n("有效订阅",-1)])]),_:1}),t(e(A),{class:"h-4 w-4 text-green-500"})]),_:1}),t(e(u),null,{default:s(()=>[e(l).loading?(o(),r(e(p),{key:0,class:"h-8 w-16"})):(o(),i("div",S,c(e(l).validTeams),1))]),_:1})]),_:1}),t(e(f),null,{default:s(()=>[t(e(m),{class:"flex flex-row items-center justify-between space-y-0 pb-2"},{default:s(()=>[t(e(_),{class:"text-sm font-medium"},{default:s(()=>[...a[4]||(a[4]=[n("无效订阅",-1)])]),_:1}),t(e($),{class:"h-4 w-4 text-red-500"})]),_:1}),t(e(u),null,{default:s(()=>[e(l).loading?(o(),r(e(p),{key:0,class:"h-8 w-16"})):(o(),i("div",L,c(e(l).invalidTeams),1))]),_:1})]),_:1}),t(e(f),null,{default:s(()=>[t(e(m),{class:"flex flex-row items-center justify-between space-y-0 pb-2"},{default:s(()=>[t(e(_),{class:"text-sm font-medium"},{default:s(()=>[...a[5]||(a[5]=[n("剩余席位",-1)])]),_:1}),t(e(z),{class:"h-4 w-4 text-muted-foreground"})]),_:1}),t(e(u),null,{default:s(()=>[e(l).loading?(o(),r(e(p),{key:0,class:"h-8 w-16"})):(o(),i("div",R,c(e(l).totalAvailableSeats),1))]),_:1})]),_:1})]),e(l).error?(o(),r(e(f),{key:0,class:"border-destructive"},{default:s(()=>[t(e(u),{class:"pt-6"},{default:s(()=>[d("div",U,[d("p",q,c(e(l).error),1),t(e(h),{variant:"outline",size:"sm",onClick:x},{default:s(()=>[...a[6]||(a[6]=[n(" 重试 ",-1)])]),_:1})])]),_:1})]),_:1})):v("",!0)]))}});export{J as default};

View File

@@ -0,0 +1 @@
import{d,a as c,o as l,w as r,x as i,I as v,J as h,u as e,p as w,D as p,P,c as D,v as f,b as y,i as $,f as k,h as x,n as B}from"./index-Dn7IzCAm.js";import{L as O,M as z,F as M,J as C,N as T,O as F,Q as q,R as E,S as b,U as I,W as R}from"./PaginationPrevious.vue_vue_type_script_setup_true_lang-Cpcjkb4w.js";import{u as N,r as m}from"./index-DTrJzHuX.js";var A=d({__name:"DialogPortal",props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(s){const t=s;return(a,o)=>(l(),c(e(O),v(h(t)),{default:r(()=>[i(a.$slots,"default")]),_:3},16))}}),V=A,j=d({__name:"DialogTrigger",props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"button"}},setup(s){const t=s,a=z(),{forwardRef:o,currentElement:n}=N();return a.contentId||=M(void 0,"reka-dialog-content"),w(()=>{a.triggerElement.value=n.value}),(u,g)=>(l(),c(e(P),p(t,{ref:e(o),type:u.as==="button"?"button":void 0,"aria-haspopup":"dialog","aria-expanded":e(a).open.value||!1,"aria-controls":e(a).open.value?e(a).contentId:void 0,"data-state":e(a).open.value?"open":"closed",onClick:e(a).onOpenToggle}),{default:r(()=>[i(u.$slots,"default")]),_:3},16,["type","aria-expanded","aria-controls","data-state","onClick"]))}}),J=j;const U=D("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);const L=D("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),W=d({__name:"Dialog",props:{open:{type:Boolean},defaultOpen:{type:Boolean},modal:{type:Boolean}},emits:["update:open"],setup(s,{emit:t}){const n=C(s,t);return(u,g)=>(l(),c(e(T),p({"data-slot":"dialog"},e(n)),{default:r(_=>[i(u.$slots,"default",v(h(_)))]),_:3},16))}}),S=d({__name:"DialogOverlay",props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{},class:{}},setup(s){const t=s,a=m(t,"class");return(o,n)=>(l(),c(e(F),p({"data-slot":"dialog-overlay"},e(a),{class:e(f)("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",t.class)}),{default:r(()=>[i(o.$slots,"default")]),_:3},16,["class"]))}}),X=d({inheritAttrs:!1,__name:"DialogContent",props:{forceMount:{type:Boolean},disableOutsidePointerEvents:{type:Boolean},asChild:{type:Boolean},as:{},class:{},showCloseButton:{type:Boolean,default:!0}},emits:["escapeKeyDown","pointerDownOutside","focusOutside","interactOutside","openAutoFocus","closeAutoFocus"],setup(s,{emit:t}){const a=s,o=t,n=m(a,"class"),u=C(n,o);return(g,_)=>(l(),c(e(V),null,{default:r(()=>[y(S),y(e(q),p({"data-slot":"dialog-content"},{...g.$attrs,...e(u)},{class:e(f)("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",a.class)}),{default:r(()=>[i(g.$slots,"default"),s.showCloseButton?(l(),c(e(E),{key:0,"data-slot":"dialog-close",class:"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"},{default:r(()=>[y(e(L)),_[0]||(_[0]=k("span",{class:"sr-only"},"Close",-1))]),_:1})):$("",!0)]),_:3},16,["class"])]),_:3}))}}),G=d({__name:"DialogDescription",props:{asChild:{type:Boolean},as:{},class:{}},setup(s){const t=s,a=m(t,"class"),o=b(a);return(n,u)=>(l(),c(e(I),p({"data-slot":"dialog-description"},e(o),{class:e(f)("text-muted-foreground text-sm",t.class)}),{default:r(()=>[i(n.$slots,"default")]),_:3},16,["class"]))}}),Y=d({__name:"DialogFooter",props:{class:{}},setup(s){const t=s;return(a,o)=>(l(),x("div",{"data-slot":"dialog-footer",class:B(e(f)("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",t.class))},[i(a.$slots,"default")],2))}}),Z=d({__name:"DialogHeader",props:{class:{}},setup(s){const t=s;return(a,o)=>(l(),x("div",{"data-slot":"dialog-header",class:B(e(f)("flex flex-col gap-2 text-center sm:text-left",t.class))},[i(a.$slots,"default")],2))}}),ee=d({__name:"DialogTitle",props:{asChild:{type:Boolean},as:{},class:{}},setup(s){const t=s,a=m(t,"class"),o=b(a);return(n,u)=>(l(),c(e(R),p({"data-slot":"dialog-title"},e(o),{class:e(f)("text-lg leading-none font-semibold",t.class)}),{default:r(()=>[i(n.$slots,"default")]),_:3},16,["class"]))}}),ae=d({__name:"DialogTrigger",props:{asChild:{type:Boolean},as:{}},setup(s){const t=s;return(a,o)=>(l(),c(e(J),p({"data-slot":"dialog-trigger"},t),{default:r(()=>[i(a.$slots,"default")]),_:3},16))}});export{U as P,W as _,ae as a,X as b,Z as c,ee as d,G as e,Y as f};

View File

@@ -0,0 +1 @@
import{c as C,d as w,r as c,a as f,u as a,w as t,o as d,b as l,e as n,f as p,g as V,h as $,i as _,n as h,t as y,_ as B,j as i}from"./index-Dn7IzCAm.js";import{_ as N,a as T,b as K,c as L}from"./CardTitle.vue_vue_type_script_setup_true_lang-nfBPK_e7.js";import{_ as S}from"./CardDescription.vue_vue_type_script_setup_true_lang-4Ti-ikHw.js";import{_ as g,a as k}from"./Label.vue_vue_type_script_setup_true_lang--UC5tvAY.js";import{i as z}from"./invite-DRaEKloN.js";import{C as P}from"./circle-x-DpzkZ8l0.js";import{L as U}from"./index-DTrJzHuX.js";const j=C("circle-check",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]),D={class:"space-y-2"},E={class:"space-y-2"},F=w({__name:"JoinPage",setup(G){const u=c(""),o=c(""),r=c(!1),s=c(null),x=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;async function b(){if(!u.value.trim()){i.error("请输入邮箱");return}if(!x.test(u.value)){i.error("邮箱格式不正确");return}if(!o.value.trim()){i.error("请输入卡密");return}r.value=!0,s.value=null;try{const m=await z({email:u.value.trim(),card_key:o.value.trim()});m.data.success?(s.value={success:!0,message:"提交成功,已发起邀请!"},i.success("提交成功"),u.value="",o.value=""):(s.value={success:!1,message:m.data.message||"提交失败,请检查卡密或邮箱"},i.error(s.value.message))}catch(m){const e=m.response?.data?.message||"提交失败,请检查卡密或邮箱";s.value={success:!1,message:e},i.error(e)}finally{r.value=!1}}return(m,e)=>(d(),f(a(N),{class:"w-full max-w-md mx-4"},{default:t(()=>[l(a(T),{class:"text-center"},{default:t(()=>[l(a(K),{class:"text-2xl"},{default:t(()=>[...e[2]||(e[2]=[n("加入 Team",-1)])]),_:1}),l(a(S),null,{default:t(()=>[...e[3]||(e[3]=[n("输入邮箱和卡密,即可加入 ChatGPT Team",-1)])]),_:1})]),_:1}),l(a(L),null,{default:t(()=>[p("form",{onSubmit:V(b,["prevent"]),class:"space-y-4"},[p("div",D,[l(a(g),{for:"email"},{default:t(()=>[...e[4]||(e[4]=[n("邮箱",-1)])]),_:1}),l(a(k),{id:"email",modelValue:u.value,"onUpdate:modelValue":e[0]||(e[0]=v=>u.value=v),type:"email",placeholder:"your@email.com",disabled:r.value},null,8,["modelValue","disabled"])]),p("div",E,[l(a(g),{for:"cardKey"},{default:t(()=>[...e[5]||(e[5]=[n("卡密",-1)])]),_:1}),l(a(k),{id:"cardKey",modelValue:o.value,"onUpdate:modelValue":e[1]||(e[1]=v=>o.value=v),type:"text",placeholder:"请输入卡密",disabled:r.value},null,8,["modelValue","disabled"])]),s.value?(d(),$("div",{key:0,class:h(["flex items-center gap-2 p-3 rounded-lg text-sm",s.value.success?"bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300":"bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300"])},[s.value.success?(d(),f(a(j),{key:0,class:"h-4 w-4 shrink-0"})):(d(),f(a(P),{key:1,class:"h-4 w-4 shrink-0"})),p("span",null,y(s.value.message),1)],2)):_("",!0),l(a(B),{type:"submit",class:"w-full",disabled:r.value},{default:t(()=>[r.value?(d(),f(a(U),{key:0,class:"mr-2 h-4 w-4 animate-spin"})):_("",!0),n(" "+y(r.value?"提交中...":"提交"),1)]),_:1},8,["disabled"])],32)]),_:1})]),_:1}))}});export{F as default};

View File

@@ -0,0 +1 @@
import{d as m,a as V,o as v,w as $,x as C,D as B,u as d,P as O,L as P,r as q,C as h,q as D,B as E,M,N as F,h as J,n as k,v as S,O as I}from"./index-Dn7IzCAm.js";import{u as T,i as U,r as z}from"./index-DTrJzHuX.js";var R=m({__name:"Label",props:{for:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"label"}},setup(a){const s=a;return T(),(i,n)=>(v(),V(d(O),B(s,{onMousedown:n[0]||(n[0]=l=>{!l.defaultPrevented&&l.detail>1&&l.preventDefault()})}),{default:$(()=>[C(i.$slots,"default")]),_:3},16))}}),j=R;function A(a){return JSON.parse(JSON.stringify(a))}function G(a,s,i,n={}){var l,r;const{clone:o=!1,passive:c=!1,eventName:L,deep:b=!1,defaultValue:N,shouldEmit:g}=n,e=P(),_=i||e?.emit||(e==null||(l=e.$emit)===null||l===void 0?void 0:l.bind(e))||(e==null||(r=e.proxy)===null||r===void 0||(r=r.$emit)===null||r===void 0?void 0:r.bind(e?.proxy));let u=L;u=u||`update:${s.toString()}`;const x=t=>o?typeof o=="function"?o(t):A(t):t,y=()=>U(a[s])?x(a[s]):N,w=t=>{g?g(t)&&_(u,t):_(u,t)};if(c){const t=q(y());let p=!1;return h(()=>a[s],f=>{p||(p=!0,t.value=x(f),E(()=>p=!1))}),h(t,f=>{!p&&(f!==a[s]||b)&&w(f)},{deep:b}),t}else return D({get(){return y()},set(t){w(t)}})}const Q=m({__name:"Input",props:{defaultValue:{},modelValue:{},class:{}},emits:["update:modelValue"],setup(a,{emit:s}){const i=a,l=G(i,"modelValue",s,{passive:!0,defaultValue:i.defaultValue});return(r,o)=>M((v(),J("input",{"onUpdate:modelValue":o[0]||(o[0]=c=>I(l)?l.value=c:null),"data-slot":"input",class:k(d(S)("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm","focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]","aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",i.class))},null,2)),[[F,d(l)]])}}),W=m({__name:"Label",props:{for:{},asChild:{type:Boolean},as:{},class:{}},setup(a){const s=a,i=z(s,"class");return(n,l)=>(v(),V(d(j),B({"data-slot":"label"},d(i),{class:d(S)("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",s.class)}),{default:$(()=>[C(n.$slots,"default")]),_:3},16,["class"]))}});export{W as _,Q as a};

View File

@@ -0,0 +1 @@
import{d as V,k as g,r as i,a as c,w as t,u as a,l as $,m as k,o as p,b as s,e as o,f as m,g as C,_ as L,i as N,t as S,j as f}from"./index-Dn7IzCAm.js";import{_ as B,a as R,b as U,c as h}from"./CardTitle.vue_vue_type_script_setup_true_lang-nfBPK_e7.js";import{_ as j}from"./CardDescription.vue_vue_type_script_setup_true_lang-4Ti-ikHw.js";import{_,a as v}from"./Label.vue_vue_type_script_setup_true_lang--UC5tvAY.js";import{L as q}from"./index-DTrJzHuX.js";const A={class:"space-y-2"},D={class:"space-y-2"},G=V({__name:"LoginPage",setup(M){const w=$(),b=k(),x=g(),r=i(""),u=i(""),l=i(!1);async function y(){if(!r.value.trim()||!u.value.trim()){f.error("请输入账号和密码");return}l.value=!0;const d=await x.login({username:r.value.trim(),password:u.value});if(l.value=!1,d.success){f.success("登录成功");const e=b.query.redirect;w.push(e||"/admin/dashboard")}else f.error(d.message||"登录失败")}return(d,e)=>(p(),c(a(B),{class:"w-full max-w-md mx-4"},{default:t(()=>[s(a(R),{class:"text-center"},{default:t(()=>[s(a(U),{class:"text-2xl"},{default:t(()=>[...e[2]||(e[2]=[o("管理后台登录",-1)])]),_:1}),s(a(j),null,{default:t(()=>[...e[3]||(e[3]=[o("请输入您的账号和密码",-1)])]),_:1})]),_:1}),s(a(h),null,{default:t(()=>[m("form",{onSubmit:C(y,["prevent"]),class:"space-y-4"},[m("div",A,[s(a(_),{for:"username"},{default:t(()=>[...e[4]||(e[4]=[o("账号",-1)])]),_:1}),s(a(v),{id:"username",modelValue:r.value,"onUpdate:modelValue":e[0]||(e[0]=n=>r.value=n),type:"text",placeholder:"请输入账号",disabled:l.value,autocomplete:"username"},null,8,["modelValue","disabled"])]),m("div",D,[s(a(_),{for:"password"},{default:t(()=>[...e[5]||(e[5]=[o("密码",-1)])]),_:1}),s(a(v),{id:"password",modelValue:u.value,"onUpdate:modelValue":e[1]||(e[1]=n=>u.value=n),type:"password",placeholder:"请输入密码",disabled:l.value,autocomplete:"current-password"},null,8,["modelValue","disabled"])]),s(a(L),{type:"submit",class:"w-full",disabled:l.value},{default:t(()=>[l.value?(p(),c(a(q),{key:0,class:"mr-2 h-4 w-4 animate-spin"})):N("",!0),o(" "+S(l.value?"登录中...":"登录"),1)]),_:1},8,["disabled"])],32)]),_:1})]),_:1}))}});export{G as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{d as a,h as n,n as o,u as t,v as r,o as l}from"./index-Dn7IzCAm.js";const u=a({__name:"Skeleton",props:{class:{}},setup(s){const e=s;return(c,p)=>(l(),n("div",{"data-slot":"skeleton",class:o(t(r)("animate-pulse rounded-md bg-primary/10",e.class))},null,2))}});export{u as _};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{y as r,z as m,r as o,q as c}from"./index-Dn7IzCAm.js";function p(e){const a=new URLSearchParams().toString();return r.get(`/api/accounts${a?`?${a}`:""}`)}function A(e){return r.post("/api/accounts/create",e)}function w(e){return r.post(`/api/accounts/refresh?id=${e}`)}function y(e){return r.delete(`/api/accounts/delete?id=${e}`)}const S=m("accounts",()=>{const e=o([]),n=o(!1),a=o(null),u=c(()=>e.value.length),i=c(()=>e.value.filter(t=>t.is_active).length),l=c(()=>e.value.filter(t=>!t.is_active).length),d=c(()=>e.value.reduce((t,s)=>t+Math.max(0,(s.seats_entitled||0)-(s.seats_in_use||0)),0));async function f(){n.value=!0,a.value=null;try{const t=await p();if(t.data.success)e.value=t.data.data||[];else throw new Error(t.data.message||"获取账号列表失败")}catch(t){throw a.value=t.message||"获取账号列表失败",t}finally{n.value=!1}}function v(t){const s=e.value.findIndex(h=>h.id===t.id);s!==-1&&(e.value[s]=t)}return{accounts:e,loading:n,error:a,totalTeams:u,validTeams:i,invalidTeams:l,totalAvailableSeats:d,fetchAccounts:f,updateAccount:v}});export{A as c,y as d,w as r,S as u};

View File

@@ -0,0 +1 @@
import{c}from"./index-Dn7IzCAm.js";const r=c("circle-x",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);export{r as C};

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

View File

@@ -0,0 +1 @@
import{y as t}from"./index-Dn7IzCAm.js";function e(i){return t.post("/api/invite/card",i)}function a(i){return t.get(`/api/invite?account_id=${i}`)}function r(i){return t.delete("/api/invite",{data:i})}function o(i){return t.post("/api/invite",i)}export{o as a,r as d,e as i,a as l};

View File

@@ -0,0 +1 @@
import{c as e}from"./index-Dn7IzCAm.js";const t=e("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);export{t as R};

14
backend/internal/static/dist/index.html vendored Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
<script type="module" crossorigin src="/assets/index-Dn7IzCAm.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C_xOPDav.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

1
backend/internal/static/dist/vite.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,56 @@
package static
import (
"embed"
"io/fs"
"net/http"
"strings"
)
//go:embed dist/*
var StaticFiles embed.FS
// Handler 返回静态文件处理器
// 用于服务嵌入的前端静态文件
func Handler() http.Handler {
// 提取 dist 子目录
distFS, err := fs.Sub(StaticFiles, "dist")
if err != nil {
// 如果 dist 目录不存在,返回空处理器
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Static files not available", http.StatusNotFound)
})
}
fileServer := http.FileServer(http.FS(distFS))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 对于 API 路径,不处理
if strings.HasPrefix(r.URL.Path, "/api/") {
http.NotFound(w, r)
return
}
// 尝试服务静态文件
// 对于 SPA如果文件不存在返回 index.html
path := r.URL.Path
if path == "/" {
path = "/index.html"
}
// 检查文件是否存在
_, err := fs.Stat(distFS, strings.TrimPrefix(path, "/"))
if err != nil {
// 文件不存在,返回 index.html支持 SPA 路由)
r.URL.Path = "/"
}
fileServer.ServeHTTP(w, r)
})
}
// IsAvailable 检查静态文件是否可用
func IsAvailable() bool {
_, err := fs.Sub(StaticFiles, "dist")
return err == nil
}