template


技能:创建博客模块模板

概述

为博客模块化系统创建新的模块模板(header/footer/nav/section 等),包含 .njk 模板、.css 样式、.schema.json 配置定义三件套,并同步到数据库、创建实例、添加到页面。

项目背景

本项目是基于 Node.js + Nunjucks + MySQL 的博客系统,采用模块化页面组合架构。每个页面由多个模块按 sort_order 顺序渲染,模块类型包括 header、footer、nav、section 等。

模板文件结构

src/templates/
  {type}/              # 模块类型目录:header, footer, nav, section
    {slug}.njk         # Nunjucks 模板(HTML结构)
    {slug}.css         # 样式文件
    {slug}.schema.json # 配置 JSON Schema(定义可配置项)

创建步骤

第一步:创建三件套文件

.schema.json 规范

  • 顶层 type: "object"properties 定义可配置字段
  • 每个字段需要 typetitle,可选 defaultdescription
  • 颜色字段用 "format": "color"
  • 数组字段(如导航链接)用 type: "array" + items
  • 必须有 required 数组

示例:

{
  "type": "object",
  "properties": {
    "title": { "type": "string", "title": "标题", "default": "默认值" },
    "bgColor": { "type": "string", "format": "color", "title": "背景颜色", "default": "#ffffff" },
    "links": {
      "type": "array",
      "title": "链接列表",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "title": "名称" },
          "url": { "type": "string", "title": "地址" }
        },
        "required": ["name", "url"]
      }
    }
  },
  "required": ["title"]
}

.njk 模板规范

  • 使用 Nunjucks 语法,变量来自 schema 定义的字段 + section 类型的动态数据
  • {{ varName }} 输出变量,{% if %} 条件渲染,{% for %} 循环
  • style 属性中可使用 CSS 变量:style="--var: {{ configValue }}"
  • HTML 内容用 {{ content | safe }} 输出

section 类型模板可使用的动态数据变量:

  • 文章列表页:articles(数组), categories(数组), pagination(对象), currentCategoryId
  • 文章详情页:article(对象, 含 contentHtml), prev, next
  • 文章字段:id, title, summary, created_at_fmt, category_id, category_name
  • 分页字段:page, pageSize, total, totalPages

.css 样式规范

  • 纯 CSS,不使用预处理器
  • 用模块特有的类名前缀避免冲突(如 .aside-nav, .article-list-section
  • 响应式用 @media (max-width: 768px)@media (min-width: 769px)
  • 可使用 CSS 变量 var(--name, fallback)
  • 应该遵循pc端、移动端样式最佳实践,视觉审美
  • 公共配置 移动端字体,字体大小,间距,间隔,整体风格

第二步:同步模板到数据库

TOKEN=$(curl -s -X POST http://localhost:3300/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"admin123"}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["token"])')

curl -X POST http://localhost:3300/api/module-templates/sync \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"dryRun":false}'

第三步:创建模块实例

curl -X POST http://localhost:3300/api/module-instances \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "实例名称",
    "type_id": <type_id>,
    "template_id": <template_id>,
    "config_data": { ... schema 定义的配置值 ... }
  }'

第四步:添加到页面

# site_category_id 指定该模块属于哪个站点分类的配置方案(可选,不传则为通用配置)
curl -X POST http://localhost:3300/api/pages/{pageId}/modules \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"module_instance_id": <instance_id>, "site_category_id": <category_id>}'

第五步:调整模块顺序

# site_category_id 指定操作哪个分类下的配置(不传则操作通用配置)
curl -X PUT http://localhost:3300/api/pages/{pageId}/modules \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"site_category_id": <category_id>, "modules":[{"module_instance_id":"<id1>"},{"module_instance_id":"<id2>"}]}'

模块类型标识符对照

标识符 名称 说明
header 页头 网站顶部区域
nav 导航 主导航菜单
section 内容区 页面主体内容
footer 页脚 网站底部区域

注意:type_id 使用雪花算法生成的 BIGINT,不是固定数字。创建实例时需先通过 GET /api/module-types 查询实际 ID。

关键文件

  • 模板目录: src/templates/{type}/{slug}.*
  • 模板加载器: src/services/templateLoader.js(本地文件优先 + 数据库兜底)
  • 渲染中间件: src/middleware/systemModules.js(按 sort_order 渲染所有模块,section 注入动态数据)
  • 布局模板: src/views/layouts/base.njk(hasPageModules=true 时纯模块渲染,否则回退旧布局)
  • 同步 API: src/routes/api/module-templates.jsPOST /api/module-templates/sync

站点分类与多配置方案(v2.4.0)

同一页面可按站点分类拥有多套模块配置方案。site_category_idpage_module_configs 表上:

  • 添加模块到页面时传 site_category_id 指定归属哪个分类方案
  • 批量更新模块顺序时传 site_category_id 限定操作范围
  • 前台渲染时只使用当前激活分类对应的配置
  • 不传 site_category_id 则为通用配置(NULL

注意事项

  • section 类型模板的 config_data 会与动态数据合并,config_data 中的同名字段会被动态数据覆盖
  • 模板 CSS 会被收集到 <style> 标签中内联输出,注意类名不要冲突
  • 开发环境不缓存模板,修改文件后刷新即可看到效果
  • 新增模块类型需要先在 module_types 表中添加记录