feat(theme): 主题支持按页面分别配装饰,生成器改用真实页面画布
原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 —— 跟真实页面不像,也没法给不同页面配不同内容。 机制层: - internal/context.pageScope() 按路径归类,输出 <html data-page-scope>: box / question / admin / profile / vote(首页与 /explore 不加载主题,不列) - /user/questions 单独归为 admin:题目列表信息密,和设置表单的装饰诉求不同 - base.css 装饰槽位 6 -> 10,背景 8 层 -> 12 层;主题按作用域覆盖时同样写满 10 个 - 新增 TestDecoSlotCount:逐文件逐声明检查 10 个值。少写一个不报错, 只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角),注释守不住 生成器: - 画板改成 iframe 加载 outputs/preview-*.html(真实模板渲染),配 serve.py 起本地服务 解决 file:// 下父页面读不到 iframe DOM 的问题 - 八个页面作用域(去掉不生效的首页/探索),每个可独立配背景与最多 10 层装饰 - 图层类型:图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移,可在画板上直接拖 - 位置统一写 calc(0%|50%|100% ± Npx):百分比是相对 (容器-图片), 不用 left/bottom 关键字混搭,避免三值语法非法导致整条声明作废 - 配色只填 6 个基础色,其余 4 个必需令牌按方向派生(surface-2 往 text 靠, 浅色变深、深色变亮,方向自动正确) 配套: - zz_preview_test.go 增加单题页 / 题目列表 / 投票页预览,作为生成器画板 - docs/theme-page-scope.md 记录作用域取值、选择器特异性与 10 槽位约束 - StaticVersion 20261020e(base.css 变了) 验证:生成器导出的 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount; 真实箱主页计算样式为 12 层对齐;临时主题验证「question 覆盖为 none」生效、 未覆盖时回退全局。
This commit is contained in:
@@ -240,6 +240,12 @@ func Contexter() flamego.Handler {
|
||||
isUserPage := strings.HasPrefix(path, "/user") || strings.HasPrefix(path, "/_/")
|
||||
c.Data["IsUserPage"] = isUserPage
|
||||
|
||||
// 页面作用域:让同一套主题能在不同页面摆不同装饰(箱主页/问题页/设置页…)。
|
||||
// 模板通过 <html data-page-scope> 输出,主题 css 用
|
||||
// html[data-theme-style="x"][data-page-scope="y"] 覆盖 --tb-deco 等令牌。
|
||||
// 未知路径为空串,此时只应用全局装饰,行为与引入该机制前完全一致。
|
||||
c.Data["PageScope"] = pageScope(path)
|
||||
|
||||
// 主题:/user 后台是箱主本人的页面,跟随本人配置;
|
||||
// /_/{domain} 的箱主主题由 question.Pager 覆盖(访客看到的是箱主的选择)。
|
||||
// 空串(未选且站点无默认主题)= 不加载主题 css。
|
||||
@@ -342,3 +348,55 @@ func (c *Context) Lang() string {
|
||||
func isThemeScope(path string) bool {
|
||||
return path == "/user" || strings.HasPrefix(path, "/user/")
|
||||
}
|
||||
|
||||
// pageScope 把请求路径归类成「主题页面作用域」。
|
||||
//
|
||||
// 目的:同一个主题在不同页面可以摆不同装饰(箱主页放人物、问题页只留光、设置页干脆不放图),
|
||||
// 而不必为每种页面复制一整套主题。模板把它输出到 <html data-page-scope="...">,
|
||||
// 主题 css 用 html[data-theme-style="x"][data-page-scope="y"] 覆盖 --tb-deco 等令牌。
|
||||
//
|
||||
// 返回空串表示「不在主题作用域内」(首页/探索/登录等公开页也可返回具体值,
|
||||
// 只是这些页面通常不带 ThemeSlug,覆盖块不会生效)。
|
||||
func pageScope(path string) string {
|
||||
switch {
|
||||
case path == "/":
|
||||
return "home"
|
||||
case path == "/explore" || strings.HasPrefix(path, "/explore/"):
|
||||
return "explore"
|
||||
case path == "/login" || strings.HasPrefix(path, "/login/"),
|
||||
path == "/register" || strings.HasPrefix(path, "/register/"):
|
||||
return "auth"
|
||||
case path == "/user" || strings.HasPrefix(path, "/user/"):
|
||||
// 题目/私聊管理列表是「翻找内容」的页面,和「设置表单」的信息密度差很多,
|
||||
// 装饰要分开配:admin 更该退到背景,profile 可以保留一点氛围。
|
||||
if strings.HasPrefix(path, "/user/questions") || strings.HasPrefix(path, "/user/votes") {
|
||||
return "admin"
|
||||
}
|
||||
return "profile"
|
||||
case strings.HasPrefix(path, "/_/"):
|
||||
// /_/{domain} → 箱主页(问题列表)
|
||||
// /_/{domain}/{qid} → 单个问题
|
||||
// /_/{domain}/tag/xxx → 仍是箱主页
|
||||
rest := strings.Trim(path[len("/_/"):], "/")
|
||||
parts := strings.Split(rest, "/")
|
||||
if len(parts) == 2 && isAllDigits(parts[1]) {
|
||||
return "question"
|
||||
}
|
||||
return "box"
|
||||
case path == "/vote" || strings.HasPrefix(path, "/vote/"):
|
||||
return "vote"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isAllDigits(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user