package utils import ( "fmt" "html/template" "net/http" "path/filepath" "sync" "github.com/gin-gonic/gin/render" ) // templateFuncs 定义模板函数 func templateFuncs() template.FuncMap { return template.FuncMap{ "add": func(a, b int) int { return a + b }, "sub": func(a, b int) int { return a - b }, "html": func(s string) template.HTML { return template.HTML(s) }, "markdown": RenderMarkdown, } } // TemplateRenderer 自定义模板渲染器,支持多模板和主题切换 type TemplateRenderer struct { templates map[string]*template.Template mu sync.RWMutex // 读写锁,支持运行时重新加载 } // Lookup 查找指定名称的模板 func (r *TemplateRenderer) Lookup(name string) *template.Template { r.mu.RLock() defer r.mu.RUnlock() return r.templates[name] } // Instance 创建渲染实例 func (r *TemplateRenderer) Instance(name string, data any) render.Render { return &htmlRender{ tmpl: r.Lookup(name), data: data, name: name, } } // htmlRender HTML渲染器 type htmlRender struct { tmpl *template.Template data any name string } // Render 执行模板渲染 func (r *htmlRender) Render(w http.ResponseWriter) error { r.WriteContentType(w) return r.tmpl.ExecuteTemplate(w, "base.html", r.data) } // WriteContentType 设置内容类型 func (r *htmlRender) WriteContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/html; charset=utf-8") } // LoadTemplates 加载指定主题的模板 func LoadTemplates(theme string) *TemplateRenderer { r := &TemplateRenderer{ templates: make(map[string]*template.Template), } r.reload(theme) return r } // Reload 重新加载主题模板(线程安全) func (r *TemplateRenderer) Reload(theme string) { r.mu.Lock() defer r.mu.Unlock() r.reload(theme) } // reload 内部重载方法(需要持有锁) func (r *TemplateRenderer) reload(theme string) { templateDir := "templates/" + theme // 需要独立渲染的页面模板 pages := []string{"index", "post", "page"} // 收集主题目录下的公共局部模板(例如 sidebar/header/footer), // 这些模板通过 {{template "xxx" .}} 被 base.html 引用, // 必须与页面模板一起解析,否则渲染时会因模板未定义而失败(白屏)。 partials := []string{} if matches, err := filepath.Glob(templateDir + "/*.html"); err == nil { for _, f := range matches { base := filepath.Base(f) if base == "base.html" { continue } name := base[:len(base)-len(".html")] isPage := false for _, p := range pages { if p == name { isPage = true break } } if !isPage { partials = append(partials, f) } } } // 为每个页面创建独立的模板组合 newTemplates := make(map[string]*template.Template) for _, pageName := range pages { // 加载 base.html、对应页面模板以及所有公共局部模板 allFiles := make([]string, 0, 2+len(partials)) allFiles = append(allFiles, templateDir+"/base.html", templateDir+"/"+pageName+".html", ) allFiles = append(allFiles, partials...) // 解析模板文件 tmpl, err := template.New("").Funcs(templateFuncs()).ParseFiles(allFiles...) if err != nil { // 如果解析失败,记录错误但不中断 fmt.Printf("警告: 加载主题 %s 的 %s 页面模板失败: %v\n", theme, pageName, err) continue } newTemplates[pageName] = tmpl } // 更新模板映射 r.templates = newTemplates fmt.Printf("主题 %s 加载完成,共加载 %d 个页面模板\n", theme, len(newTemplates)) } // 全局模板渲染器实例 var globalRenderer *TemplateRenderer // SetGlobalRenderer 设置全局渲染器 func SetGlobalRenderer(r *TemplateRenderer) { globalRenderer = r } // GetGlobalRenderer 获取全局渲染器 func GetGlobalRenderer() *TemplateRenderer { return globalRenderer }