增加模板

This commit is contained in:
2026-08-20 10:30:53 +08:00
parent 758f072494
commit 112d60b993
36 changed files with 16465 additions and 14 deletions
+63 -1
View File
@@ -5,6 +5,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
@@ -147,6 +148,67 @@ func (c *TenantSiteController) forceHTTPSStorageURLs(html string) string {
return strings.ReplaceAll(html, "http://"+host, "https://"+host)
}
// rewriteThemeAssets 将模板内书写的相对资源路径(href/src/url() 等)
// 统一改写为 /themes/{code}/ 下的绝对路径,保证任意页面深度(/、/news、
// /news/:id、/page/:path)下静态资源都能命中 beego 的 /themes 静态映射。
// 第三方模板通常直接写相对路径(如 assets/css/xxx.css),不改写会 404。
// 注:Go regexp(RE2)不支持 \1/\2 反向引用,闭合引号用 ["'] 宽松匹配;
// 值部分 [^"']* 不含引号,正常 HTML 下首尾引号必然一致,行为等价。
var (
reThemeAssetAttr = regexp.MustCompile(`(?i)\b(href|src|poster|data-src|srcset)\s*=\s*(["'])([^"']*)["']`)
reThemeAssetURL = regexp.MustCompile(`(?i)url\(\s*(["']?)([^"')]+)["']?\s*\)`)
)
func rewriteThemeAssets(html, code string) string {
prefix := "/themes/" + code + "/"
fix := func(u string) string {
u = strings.TrimSpace(u)
if u == "" {
return u
}
lower := strings.ToLower(u)
// 绝对路径/协议链接/锚点等一律不动(含引擎输出的 /uploads、存储 CDN 地址)
if strings.HasPrefix(u, "/") || strings.HasPrefix(u, "#") || strings.HasPrefix(u, "?") ||
strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") ||
strings.HasPrefix(lower, "//") || strings.HasPrefix(lower, "data:") ||
strings.HasPrefix(lower, "javascript:") || strings.HasPrefix(lower, "mailto:") ||
strings.HasPrefix(lower, "tel:") {
return u
}
u = strings.TrimPrefix(u, "./")
for strings.HasPrefix(u, "../") {
u = strings.TrimPrefix(u, "../")
}
return prefix + u
}
html = reThemeAssetAttr.ReplaceAllStringFunc(html, func(m string) string {
sub := reThemeAssetAttr.FindStringSubmatch(m)
attr, quote, val := sub[1], sub[2], sub[3]
if strings.EqualFold(attr, "srcset") {
parts := strings.Split(val, ",")
for i, p := range parts {
seg := strings.Fields(strings.TrimSpace(p))
if len(seg) > 0 {
seg[0] = fix(seg[0])
parts[i] = strings.Join(seg, " ")
}
}
val = strings.Join(parts, ", ")
} else {
val = fix(val)
}
return attr + "=" + quote + val + quote
})
// 行内 style / <style> 块里的 url() 引用
html = reThemeAssetURL.ReplaceAllStringFunc(html, func(m string) string {
sub := reThemeAssetURL.FindStringSubmatch(m)
return "url(" + sub[1] + fix(sub[2]) + sub[1] + ")"
})
return html
}
// render 解析并输出模板文件
func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.RenderCtx)) {
tid, ok := c.resolveTid()
@@ -171,7 +233,7 @@ func (c *TenantSiteController) render(file string, mod func(ctx *tagengine.Rende
c.writeHTML(500, fmt.Sprintf(renderErrorPage, "模板渲染失败"))
return
}
c.writeHTML(200, out)
c.writeHTML(200, rewriteThemeAssets(out, code))
}
// Index GET / 首页