153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
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)
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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 := []string{
|
|
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
|
|
}
|