Compare commits
3 Commits
93aa31219d
...
59f5a87275
| Author | SHA1 | Date | |
|---|---|---|---|
| 59f5a87275 | |||
| 02caa45efc | |||
| f4f5ad6bd1 |
37
backend/Dockerfile
Normal file
37
backend/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Build stage
|
||||
FROM golang:1.25-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Copy go mod and sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download all dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/main.go
|
||||
|
||||
# Final stage
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install ca-certificates for HTTPS
|
||||
RUN apk --no-cache add ca-certificates tzdata
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=builder /app/main .
|
||||
COPY --from=builder /app/.env.example .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run
|
||||
CMD ["./main"]
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"gpt-manager-go/internal/middleware"
|
||||
"gpt-manager-go/internal/repository"
|
||||
"gpt-manager-go/internal/service"
|
||||
"gpt-manager-go/internal/static"
|
||||
)
|
||||
|
||||
// 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("/", static.Handler())
|
||||
|
||||
// CORS 中间件包装
|
||||
return corsMiddleware(mux)
|
||||
}
|
||||
|
||||
@@ -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-BIETROXK.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 _};
|
||||
2
backend/internal/static/dist/assets/CardKeysPage-BfgEvPHq.js
vendored
Normal file
2
backend/internal/static/dist/assets/CardKeysPage-BfgEvPHq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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-BIETROXK.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};
|
||||
1
backend/internal/static/dist/assets/DashboardPage-BlLDgadq.js
vendored
Normal file
1
backend/internal/static/dist/assets/DashboardPage-BlLDgadq.js
vendored
Normal 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-BIETROXK.js";import{_ as f,a as m,b as _,c as u}from"./CardTitle.vue_vue_type_script_setup_true_lang-BZWhSLyf.js";import{_ as p}from"./Skeleton.vue_vue_type_script_setup_true_lang-DLBxrxfg.js";import{u as M}from"./accounts-DexZCWCe.js";import{R as V}from"./refresh-cw-x2W3gvqp.js";import{C as $}from"./circle-x-d27Sm-GD.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};
|
||||
@@ -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-BIETROXK.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-BYnju-Ld.js";import{u as N,r as m}from"./index-D7j6Bokf.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};
|
||||
1
backend/internal/static/dist/assets/JoinPage-fgRz2aae.js
vendored
Normal file
1
backend/internal/static/dist/assets/JoinPage-fgRz2aae.js
vendored
Normal 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-BIETROXK.js";import{_ as N,a as T,b as K,c as L}from"./CardTitle.vue_vue_type_script_setup_true_lang-BZWhSLyf.js";import{_ as S}from"./CardDescription.vue_vue_type_script_setup_true_lang-PYRWivA-.js";import{_ as g,a as k}from"./Label.vue_vue_type_script_setup_true_lang-C4mrJ3gg.js";import{i as z}from"./invite-rZ0cGmoH.js";import{C as P}from"./circle-x-d27Sm-GD.js";import{L as U}from"./index-D7j6Bokf.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};
|
||||
1
backend/internal/static/dist/assets/Label.vue_vue_type_script_setup_true_lang-C4mrJ3gg.js
vendored
Normal file
1
backend/internal/static/dist/assets/Label.vue_vue_type_script_setup_true_lang-C4mrJ3gg.js
vendored
Normal 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-BIETROXK.js";import{u as T,i as U,r as z}from"./index-D7j6Bokf.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};
|
||||
1
backend/internal/static/dist/assets/LoginPage-DmXSAfKa.js
vendored
Normal file
1
backend/internal/static/dist/assets/LoginPage-DmXSAfKa.js
vendored
Normal 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-BIETROXK.js";import{_ as B,a as R,b as U,c as h}from"./CardTitle.vue_vue_type_script_setup_true_lang-BZWhSLyf.js";import{_ as j}from"./CardDescription.vue_vue_type_script_setup_true_lang-PYRWivA-.js";import{_,a as v}from"./Label.vue_vue_type_script_setup_true_lang-C4mrJ3gg.js";import{L as q}from"./index-D7j6Bokf.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
1
backend/internal/static/dist/assets/Skeleton.vue_vue_type_script_setup_true_lang-DLBxrxfg.js
vendored
Normal file
1
backend/internal/static/dist/assets/Skeleton.vue_vue_type_script_setup_true_lang-DLBxrxfg.js
vendored
Normal 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-BIETROXK.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 _};
|
||||
1
backend/internal/static/dist/assets/TeamInvitesPage-CDyGxj0w.js
vendored
Normal file
1
backend/internal/static/dist/assets/TeamInvitesPage-CDyGxj0w.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/static/dist/assets/TeamsPage-DxLnC5Oz.js
vendored
Normal file
1
backend/internal/static/dist/assets/TeamsPage-DxLnC5Oz.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/static/dist/assets/accounts-DexZCWCe.js
vendored
Normal file
1
backend/internal/static/dist/assets/accounts-DexZCWCe.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{y as r,z as m,r as o,q as c}from"./index-BIETROXK.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};
|
||||
1
backend/internal/static/dist/assets/circle-x-d27Sm-GD.js
vendored
Normal file
1
backend/internal/static/dist/assets/circle-x-d27Sm-GD.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{c}from"./index-BIETROXK.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};
|
||||
7
backend/internal/static/dist/assets/index-BIETROXK.js
vendored
Normal file
7
backend/internal/static/dist/assets/index-BIETROXK.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/static/dist/assets/index-C_xOPDav.css
vendored
Normal file
1
backend/internal/static/dist/assets/index-C_xOPDav.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/static/dist/assets/index-D7j6Bokf.js
vendored
Normal file
1
backend/internal/static/dist/assets/index-D7j6Bokf.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/static/dist/assets/invite-rZ0cGmoH.js
vendored
Normal file
1
backend/internal/static/dist/assets/invite-rZ0cGmoH.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{y as t}from"./index-BIETROXK.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};
|
||||
1
backend/internal/static/dist/assets/refresh-cw-x2W3gvqp.js
vendored
Normal file
1
backend/internal/static/dist/assets/refresh-cw-x2W3gvqp.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import{c as e}from"./index-BIETROXK.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
14
backend/internal/static/dist/index.html
vendored
Normal 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>mygo Team</title>
|
||||
<script type="module" crossorigin src="/assets/index-BIETROXK.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
1
backend/internal/static/dist/vite.svg
vendored
Normal 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 |
56
backend/internal/static/static.go
Normal file
56
backend/internal/static/static.go
Normal 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
|
||||
}
|
||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
app:
|
||||
container_name: gpt-manager
|
||||
build: ./backend
|
||||
ports:
|
||||
- "${EXPOSE_PORT}:8080"
|
||||
depends_on:
|
||||
- db
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- PORT=${PORT}
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
container_name: gpt-manager-db
|
||||
image: postgres:16-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_DB=${DB_NAME}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -4,7 +4,7 @@
|
||||
<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>
|
||||
<title>mygo Team</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -77,8 +77,8 @@ async function loadInvitations() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await listInvitations(accountId.value)
|
||||
if (response.data.success && response.data.data) {
|
||||
invitations.value = response.data.data
|
||||
if (response.data.success) {
|
||||
invitations.value = response.data.data || []
|
||||
} else {
|
||||
toast.error(response.data.message || '获取邀请列表失败')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user