强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第6章:布局与包含

第6章:布局与包含

6.1 布局系统概述

Jekyll 的布局(Layout)系统是页面渲染的骨架。布局定义了页面的外层 HTML 结构,内容则被注入到布局的 {{ content }} 位置。

┌─────────────────────────────┐
│  _layouts/default.html      │
│  ┌─────────────────────────┐│
│  │  <html>                 ││
│  │  <head>...</head>       ││
│  │  <body>                 ││
│  │  ┌─────────────────────┐││
│  │  │  {{ content }}      │││  ← 页面内容注入位置
│  │  │  (from page/post)   │││
│  │  └─────────────────────┘││
│  │  </body>                ││
│  │  </html>                ││
│  └─────────────────────────┘│
└─────────────────────────────┘

渲染顺序

1. 读取页面文件 (e.g., _posts/2025-01-15-hello.md)
2. 解析 Front Matter,确定 layout
3. 将 Markdown → HTML → {{ content }}
4. 将 content 注入 layout 模板
5. 如果 layout 指定了父 layout,继续向上嵌套
6. 输出最终 HTML

6.2 创建基本布局

布局文件存放

所有布局文件放在 _layouts/ 目录下:

_layouts/
├── default.html    # 默认布局
├── post.html       # 文章布局
├── page.html       # 页面布局
├── home.html       # 首页布局
└── docs.html       # 文档布局

default.html 布局

<!DOCTYPE html>
<html lang="{{ site.lang | default: 'zh-CN' }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% if page.title %}{{ page.title }} - {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
  <meta name="description" content="{{ page.description | default: site.description | strip_html | truncatewords: 50 }}">
  <link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
  {% seo %}
  {% feed_meta %}
</head>
<body>
  {% include header.html %}

  <main class="container">
    {{ content }}
  </main>

  {% include footer.html %}

  <script src="{{ '/assets/js/main.js' | relative_url }}"></script>
</body>
</html>

post.html 布局

---
layout: default
---
<article class="post">
  <header class="post-header">
    <h1 class="post-title">{{ page.title }}</h1>
    <div class="post-meta">
      <time datetime="{{ page.date | date_to_xmlschema }}">
        {{ page.date | date: "%Y年%m月%d日" }}
      </time>
      {% if page.author %}
        <span class="author">{{ page.author }}</span>
      {% endif %}
      {% if page.categories.size > 0 %}
        <span class="categories">
          {% for cat in page.categories %}
            <a href="{{ '/categories/' | append: cat | relative_url }}">{{ cat }}</a>{% unless forloop.last %}、{% endunless %}
          {% endfor %}
        </span>
      {% endif %}
    </div>
    {% if page.tags.size > 0 %}
      <div class="post-tags">
        {% for tag in page.tags %}
          <a href="{{ '/tags/' | append: tag | relative_url }}" class="tag">{{ tag }}</a>
        {% endfor %}
      </div>
    {% endif %}
  </header>

  <div class="post-content">
    {{ content }}
  </div>

  <footer class="post-footer">
    {% include share.html %}
    {% include related-posts.html %}
    {% if page.comments != false %}
      {% include comments.html %}
    {% endif %}
  </footer>
</article>

page.html 布局

---
layout: default
---
<div class="page">
  {% if page.title %}
    <h1 class="page-title">{{ page.title }}</h1>
  {% endif %}
  <div class="page-content">
    {{ content }}
  </div>
</div>

home.html 布局

---
layout: default
---
<div class="home">
  <section class="hero">
    <h1>{{ site.title }}</h1>
    <p>{{ site.description }}</p>
  </section>

  <section class="post-list">
    <h2>最新文章</h2>
    {% for post in site.posts limit: 5 %}
      <article class="post-card">
        <h3><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h3>
        <time>{{ post.date | date: "%Y-%m-%d" }}</time>
        <p>{{ post.excerpt | strip_html | truncatewords: 30 }}</p>
      </article>
    {% endfor %}
  </section>
</div>

6.3 布局继承(Layout Inheritance)

布局可以嵌套使用,形成继承链。子布局通过 Front Matter 指定父布局。

继承链示例

default.html
  └── post.html
       └── featured-post.html
<!-- _layouts/default.html -->
<!DOCTYPE html>
<html>
<head><title>{{ page.title }}</title></head>
<body>
  <header>Site Header</header>
  <main>{{ content }}</main>
  <footer>Site Footer</footer>
</body>
</html>
<!-- _layouts/post.html -->
---
layout: default
---
<article>
  <h1>{{ page.title }}</h1>
  <div class="meta">{{ page.date | date: "%Y-%m-%d" }}</div>
  <div class="content">{{ content }}</div>
</article>
<!-- _layouts/featured-post.html -->
---
layout: post
---
<div class="featured-badge">⭐ 精选文章</div>
{{ content }}
<div class="featured-cta">
  <a href="/subscribe">订阅获取更多精选内容</a>
</div>
# _posts/2025-01-15-important.md
---
title: "重要文章"
layout: featured-post
---
文章内容...

渲染结果

<!DOCTYPE html>
<html>
<head><title>重要文章</title></head>
<body>
  <header>Site Header</header>
  <main>
    <article>
      <h1>重要文章</h1>
      <div class="meta">2025-01-15</div>
      <div class="content">
        <div class="featured-badge">⭐ 精选文章</div>
        <p>文章内容...</p>
        <div class="featured-cta">
          <a href="/subscribe">订阅获取更多精选内容</a>
        </div>
      </div>
    </article>
  </main>
  <footer>Site Footer</footer>
</body>
</html>

6.4 包含文件(Includes)

_includes/ 目录存放可复用的模板片段,通过 {% include %} 标签引入。

基本语法

<!-- 简单包含 -->
{% include header.html %}

<!-- 传递参数 -->
{% include image.html src="/images/photo.jpg" alt="照片" width="800" %}

<!-- 动态文件名 -->
{% include {{ page.sidebar }} %}

header.html 示例

<!-- _includes/header.html -->
<header class="site-header">
  <div class="container">
    <a href="{{ '/' | relative_url }}" class="site-title">{{ site.title }}</a>
    <nav class="site-nav">
      {% for item in site.data.navigation %}
        <a href="{{ item.url | relative_url }}"
           {% if page.url == item.url %}class="active"{% endif %}>
          {{ item.title }}
        </a>
      {% endfor %}
    </nav>
  </div>
</header>

footer.html 示例

<!-- _includes/footer.html -->
<footer class="site-footer">
  <div class="container">
    <p>&copy; {{ site.time | date: "%Y" }} {{ site.title }}</p>
    <nav>
      <a href="{{ '/privacy' | relative_url }}">隐私政策</a>
      <a href="{{ '/sitemap.xml' | relative_url }}">站点地图</a>
    </nav>
  </div>
</footer>

传递参数的 include

<!-- _includes/image.html -->
{% if include.src %}
<figure class="{% if include.align %}align-{{ include.align }}{% endif %}">
  <img
    src="{{ include.src | relative_url }}"
    alt="{{ include.alt | default: '' }}"
    {% if include.width %}width="{{ include.width }}"{% endif %}
    {% if include.height %}height="{{ include.height }}"{% endif %}
    loading="lazy"
  >
  {% if include.caption %}
    <figcaption>{{ include.caption }}</figcaption>
  {% endif %}
</figure>
{% endif %}
<!-- 使用 -->
{% include image.html
   src="/images/blog/jekyll-banner.jpg"
   alt="Jekyll 横幅"
   width="800"
   align="center"
   caption="Jekyll 静态站点生成器" %}

条件 include

<!-- 根据页面配置决定是否包含 -->
{% if page.toc %}
  {% include toc.html content=content %}
{% endif %}

{% if page.mermaid %}
  {% include mermaid.html %}
{% endif %}

{% if jekyll.environment == "production" %}
  {% include analytics.html %}
{% endif %}

6.5 include 变量作用域

<!-- _includes/author-card.html -->
{% comment %}
  include.author 是传递进来的参数
  page.* 和 site.* 仍然可以访问
{% endcomment %}

{% assign author_data = site.data.authors[include.author] %}
{% if author_data %}
<div class="author-card">
  <img src="{{ author_data.avatar }}" alt="{{ author_data.name }}">
  <h4>{{ author_data.name }}</h4>
  <p>{{ author_data.bio }}</p>
</div>
{% endif %}
<!-- 在页面中使用 -->
{% include author-card.html author=page.author %}

注意事项

  • include 中的 assign 变量不会影响包含者的变量
  • include 中可以直接访问 pagesitecontent 等全局对象
  • 传递参数使用 name=value 语法,多个参数用空格分隔

6.6 多布局策略

按内容类型划分

_layouts/
├── default.html        # 基础 HTML 骨架
├── home.html           # 首页(继承 default)
├── post.html           # 文章(继承 default)
├── page.html           # 静态页面(继承 default)
├── docs.html           # 文档页面(继承 default)
├── archive.html        # 归档页面(继承 default)
├── 404.html            # 404 页面(继承 default)
└── minimal.html        # 最小布局(继承 default,无导航栏)

docs.html 文档布局

---
layout: default
---
<div class="docs-layout">
  <aside class="docs-sidebar">
    <nav class="docs-nav">
      {% assign sorted_docs = site.docs | sort: "order" %}
      {% for doc in sorted_docs %}
        <a href="{{ doc.url | relative_url }}"
           {% if page.url == doc.url %}class="active"{% endif %}>
          {{ doc.title }}
        </a>
      {% endfor %}
    </nav>
  </aside>

  <main class="docs-content">
    <h1>{{ page.title }}</h1>
    {{ content }}

    {% if page.prev_url or page.next_url %}
      <nav class="docs-pagination">
        {% if page.prev_url %}
          <a href="{{ page.prev_url | relative_url }}" class="prev">← {{ page.prev_title }}</a>
        {% endif %}
        {% if page.next_url %}
          <a href="{{ page.next_url | relative_url }}" class="next">{{ page.next_title }} →</a>
        {% endif %}
      </nav>
    {% endif %}
  </main>
</div>

6.7 页面结构设计模式

模式1:经典博客

┌─────────────────────────────────────────┐
│              header.html                │
│  Logo   Navigation   Search   RSS      │
├─────────────────────┬───────────────────┤
│                     │                   │
│  主内容区           │  侧边栏           │
│  {{ content }}      │  ├── 最新文章     │
│                     │  ├── 分类列表     │
│  文章列表           │  ├── 标签云       │
│  分页导航           │  └── 关于我       │
│                     │                   │
├─────────────────────┴───────────────────┤
│              footer.html                │
│  版权   链接   社交媒体                  │
└─────────────────────────────────────────┘

模式2:文档站点

┌─────────────────────────────────────────┐
│              header.html                │
├──────────┬──────────────────────────────┤
│          │                              │
│  侧边栏  │  文档内容                    │
│  导航    │  ├── 标题                    │
│  目录    │  ├── 正文                    │
│  搜索    │  ├── 代码示例               │
│          │  ├── 上/下篇导航             │
│          │  └── 编辑链接               │
│          │                              │
├──────────┴──────────────────────────────┤
│              footer.html                │
└─────────────────────────────────────────┘

模式3:落地页

┌─────────────────────────────────────────┐
│  ┌─────────────────────────────────────┐│
│  │        Hero Section                 ││
│  │   大标题 + CTA 按钮                 ││
│  └─────────────────────────────────────┘│
│  ┌──────┐ ┌──────┐ ┌──────┐            │
│  │ 特性1│ │ 特性2│ │ 特性3│            │
│  └──────┘ └──────┘ └──────┘            │
│  ┌─────────────────────────────────────┐│
│  │        详细介绍                      ││
│  └─────────────────────────────────────┘│
│  ┌─────────────────────────────────────┐│
│  │        CTA Section                  ││
│  └─────────────────────────────────────┘│
└─────────────────────────────────────────┘

6.8 高级技巧

条件性 CSS 类

<body class="page-{{ page.layout }} {% if page.sidebar %}has-sidebar{% endif %}">
<!-- 根据页面类型加载不同样式 -->
{% case page.layout %}
  {% when "post" %}
    <link rel="stylesheet" href="{{ '/assets/css/post.css' | relative_url }}">
  {% when "docs" %}
    <link rel="stylesheet" href="{{ '/assets/css/docs.css' | relative_url }}">
  {% when "home" %}
    <link rel="stylesheet" href="{{ '/assets/css/home.css' | relative_url }}">
{% endcase %}

动态 include

<!-- 根据页面配置选择不同的侧边栏 -->
{% assign sidebar = page.sidebar | default: "default-sidebar" %}
{% include sidebar/{{ sidebar }}.html %}

布局中的面包屑导航

<!-- _includes/breadcrumb.html -->
<nav aria-label="breadcrumb" class="breadcrumb">
  <ol>
    <li><a href="{{ '/' | relative_url }}">首页</a></li>
    {% if page.layout == "post" %}
      <li><a href="{{ '/blog' | relative_url }}">博客</a></li>
      {% if page.categories.size > 0 %}
        {% for cat in page.categories %}
          <li><a href="{{ '/categories/' | append: cat | relative_url }}">{{ cat }}</a></li>
        {% endfor %}
      {% endif %}
      <li aria-current="page">{{ page.title }}</li>
    {% elsif page.layout == "docs" %}
      <li><a href="{{ '/docs' | relative_url }}">文档</a></li>
      <li aria-current="page">{{ page.title }}</li>
    {% else %}
      <li aria-current="page">{{ page.title }}</li>
    {% endif %}
  </ol>
</nav>

6.9 业务场景:多语言文档站点

# _config.yml
collections:
  docs_en:
    output: true
    permalink: /en/docs/:name/
  docs_zh:
    output: true
    permalink: /zh/docs/:name/
<!-- _layouts/doc.html -->
---
layout: default
---
{% assign lang = page.url | slice: 1, 2 %}
<div class="doc-layout" lang="{{ lang }}">
  <aside class="doc-sidebar">
    {% if lang == "zh" %}
      {% for doc in site.docs_zh sort: "order" %}
        <a href="{{ doc.url }}">{{ doc.title }}</a>
      {% endfor %}
    {% else %}
      {% for doc in site.docs_en sort: "order" %}
        <a href="{{ doc.url }}">{{ doc.title }}</a>
      {% endfor %}
    {% endif %}
  </aside>
  <main>
    <h1>{{ page.title }}</h1>
    {{ content }}
  </main>
</div>

6.10 扩展阅读


本章小结

要点说明
布局_layouts/ 中的 HTML 模板,通过 {{ content }} 注入内容
继承子布局通过 Front Matter 的 layout 指定父布局
包含_includes/ 中的可复用片段,支持参数传递
作用域include 可以访问 pagesite 等全局对象
设计模式根据站点类型选择经典博客、文档站、落地页等模式

下一章:文章管理