// Package docs:用户文档骨架(嵌入 HTML,侧栏+正文密度对齐工作台)。 package docs import ( "embed" "net/http" "strings" "github.com/gin-gonic/gin" ) //go:embed content assets template.html var files embed.FS type page struct { Slug string Title string Body string } var order = []page{ {Slug: "install", Title: "安装与启动"}, {Slug: "connect", Title: "连库入门"}, {Slug: "workbench", Title: "工作台简介"}, {Slug: "faq", Title: "常见问题"}, } type Handler struct { tpl string } func New() *Handler { raw, err := files.ReadFile("template.html") if err != nil { panic("docs: template.html 缺失: " + err.Error()) } return &Handler{tpl: string(raw)} } func (h *Handler) Register(r *gin.Engine) { r.GET("/docs", func(c *gin.Context) { c.Redirect(http.StatusFound, "/docs/install") }) r.GET("/docs/:slug", h.Page) r.GET("/docs/assets/*file", h.Asset) } func (h *Handler) Page(c *gin.Context) { slug := c.Param("slug") bodyBytes, err := files.ReadFile("content/" + slug + ".html") if err != nil { c.Status(http.StatusNotFound) return } title := slug nav := strings.Builder{} for _, p := range order { cls := "nav-link" if p.Slug == slug { cls += " active" title = p.Title } nav.WriteString(`` + p.Title + ``) } html := h.tpl html = strings.ReplaceAll(html, "{{TITLE}}", title) html = strings.ReplaceAll(html, "{{NAV}}", nav.String()) html = strings.ReplaceAll(html, "{{BODY}}", string(bodyBytes)) c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Cache-Control", "no-cache") _, _ = c.Writer.WriteString(html) } func (h *Handler) Asset(c *gin.Context) { name := strings.TrimPrefix(c.Param("file"), "/") if strings.Contains(name, "..") { c.Status(http.StatusBadRequest) return } data, err := files.ReadFile("assets/" + name) if err != nil { c.Status(http.StatusNotFound) return } ct := "application/octet-stream" if strings.HasSuffix(name, ".css") { ct = "text/css; charset=utf-8" } c.Header("Content-Type", ct) c.Data(http.StatusOK, ct, data) }