package embed_static import ( "embed" "html/template" "io/fs" "net/http" "strings" "github.com/gin-gonic/gin" ) //go:embed static/* var staticFS embed.FS //go:embed templates/* var templatesFS embed.FS // SetupEmbed 配置内嵌资源:SPA 前端 + 后台 HTML 模板 func SetupEmbed(r *gin.Engine) error { tmpl, err := LoadTemplates() if err != nil { return err } r.SetHTMLTemplate(tmpl) // React SPA 构建产物(Vite) if sub, err := fs.Sub(staticFS, "static/spa/assets"); err == nil { r.GET("/assets/*filepath", gin.WrapH(http.StripPrefix("/assets", http.FileServer(http.FS(sub))))) } // 后台管理遗留静态资源 if sub, err := fs.Sub(staticFS, "static/legacy"); err == nil { r.GET("/legacy/*filepath", gin.WrapH(http.StripPrefix("/legacy", http.FileServer(http.FS(sub))))) } return nil } // ServeSPA 返回 React SPA 入口 func ServeSPA(c *gin.Context) { data, err := staticFS.ReadFile("static/spa/index.html") if err != nil { c.String(http.StatusNotFound, "前端未构建,请运行: cd frontend && npm run build") return } c.Data(http.StatusOK, "text/html; charset=utf-8", data) } // IsSPARoute 判断是否应由 SPA 处理 func IsSPARoute(path string) bool { if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/admin") || strings.HasPrefix(path, "/uploads") || strings.HasPrefix(path, "/legacy") || strings.HasPrefix(path, "/assets") { return false } return true } func LoadTemplates() (*template.Template, error) { sub, err := fs.Sub(templatesFS, "templates") if err != nil { return nil, err } tmpl := template.New("").Funcs(template.FuncMap{ "safeHTML": func(s string) template.HTML { return template.HTML(s) }, "add": func(a, b int) int { return a + b }, "sub": func(a, b int) int { return a - b }, }) return tmpl.ParseFS(sub, "*.html", "admin/*.html") }