多个页面共享相同的布局,但内容不同
基础模板定义了网站的基本结构,使用 {% block %} 标签定义可被子模板覆盖的区域。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的网站{% endblock %}</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
{% block styles %}{% endblock %}
<!-- 全局CSS -->
<style>
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding-top: 56px; /* 导航栏高度 */
}
.content-wrapper {
min-height: calc(100vh - 200px);
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">
<i class="fas fa-rocket me-2"></i>MySite
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
{% block navbar %}
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index') }}">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('about') }}">关于</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('contact') }}">联系</a>
</li>
</ul>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('profile') }}">
<i class="fas fa-user me-1"></i>{{ current_user.username }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">退出</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('register') }}">注册</a>
</li>
{% endif %}
</ul>
{% endblock %}
</div>
</div>
</nav>
<!-- 内容区域 -->
<div class="container content-wrapper">
<!-- 面包屑导航 -->
{% block breadcrumb %}
<nav aria-label="breadcrumb" class="mt-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('index') }}">首页</a></li>
{% block breadcrumb_items %}{% endblock %}
</ol>
</nav>
{% endblock %}
<!-- 主内容块 -->
<main>
{% block content %}
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
这是基础模板的默认内容。请在子模板中覆盖此块。
</div>
{% endblock %}
</main>
</div>
<!-- 页脚 -->
<footer class="bg-dark text-white py-4 mt-5">
<div class="container">
{% block footer %}
<div class="row">
<div class="col-md-4">
<h5>MySite</h5>
<p>一个使用Flask构建的示例网站</p>
</div>
<div class="col-md-4">
<h5>快速链接</h5>
<ul class="list-unstyled">
<li><a href="{{ url_for('index') }}" class="text-white-50">首页</a></li>
<li><a href="{{ url_for('about') }}" class="text-white-50">关于我们</a></li>
<li><a href="{{ url_for('contact') }}" class="text-white-50">联系我们</a></li>
</ul>
</div>
<div class="col-md-4">
<h5>联系我们</h5>
<p><i class="fas fa-envelope me-2"></i> contact@example.com</p>
<p><i class="fas fa-phone me-2"></i> +86 123-4567-8901</p>
</div>
</div>
<div class="text-center mt-3 pt-3 border-top border-secondary">
<p class="mb-0">© 2023 MySite. 保留所有权利.</p>
</div>
{% endblock %}
</div>
</footer>
<!-- 脚本 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
<!-- 全局脚本 -->
<script>
// 全局JavaScript代码
document.addEventListener('DOMContentLoaded', function() {
// 工具提示初始化
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
});
</script>
</body>
</html>
{% block block_name %}{% endblock %} 定义可覆盖区域子模板使用 {% extends "base.html" %} 来继承基础模板,然后覆盖或扩展特定的块。
{% extends "base.html" %}
{# 覆盖标题块 #}
{% block title %}欢迎来到我的网站 - 首页{% endblock %}
{# 添加额外的CSS #}
{% block styles %}
<style>
.hero-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 100px 0;
border-radius: 10px;
margin-bottom: 40px;
}
.feature-card {
transition: transform 0.3s;
}
.feature-card:hover {
transform: translateY(-5px);
}
</style>
{% endblock %}
{# 覆盖导航栏块 #}
{% block navbar %}
{{ super() }} {# 包含父模板的内容 #}
<!-- 可以添加额外的导航项 -->
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('blog') }}">
<i class="fas fa-blog me-1"></i>博客
</a>
</li>
</ul>
{% endblock %}
{# 覆盖面包屑项目 #}
{% block breadcrumb_items %}
<li class="breadcrumb-item active" aria-current="page">首页</li>
{% endblock %}
{# 覆盖主内容块 #}
{% block content %}
<!-- 英雄区域 -->
<div class="hero-section text-center">
<h1 class="display-4 fw-bold">欢迎来到我们的网站</h1>
<p class="lead">我们提供最优质的服务和最先进的解决方案</p>
<a href="{{ url_for('about') }}" class="btn btn-light btn-lg mt-3">
了解更多
</a>
</div>
<!-- 特性展示 -->
<div class="row">
<div class="col-md-4 mb-4">
<div class="card feature-card h-100">
<div class="card-body text-center">
<i class="fas fa-bolt fa-3x text-primary mb-3"></i>
<h5 class="card-title">快速高效</h5>
<p class="card-text">我们的服务响应迅速,确保最佳用户体验。</p>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card feature-card h-100">
<div class="card-body text-center">
<i class="fas fa-shield-alt fa-3x text-success mb-3"></i>
<h5 class="card-title">安全可靠</h5>
<p class="card-text">采用最先进的安全技术保护您的数据。</p>
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<div class="card feature-card h-100">
<div class="card-body text-center">
<i class="fas fa-headset fa-3x text-warning mb-3"></i>
<h5 class="card-title">优质支持</h5>
<p class="card-text">24/7客户支持,随时为您解决问题。</p>
</div>
</div>
</div>
</div>
<!-- 动态内容 -->
{% if recent_posts %}
<div class="mt-5">
<h2 class="mb-4">最新文章</h2>
<div class="row">
{% for post in recent_posts %}
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<p class="card-text">{{ post.excerpt|truncate(100) }}</p>
<small class="text-muted">
<i class="fas fa-calendar me-1"></i>
{{ post.created_at|date('Y-m-d') }}
</small>
</div>
<div class="card-footer bg-transparent border-top-0">
<a href="{{ url_for('post_detail', slug=post.slug) }}"
class="btn btn-outline-primary btn-sm">
阅读更多
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{# 添加额外的JavaScript #}
{% block scripts %}
<script>
// 首页特定的JavaScript
document.addEventListener('DOMContentLoaded', function() {
console.log('首页加载完成');
// 为特性卡片添加点击事件
document.querySelectorAll('.feature-card').forEach(card => {
card.addEventListener('click', function() {
this.classList.toggle('shadow-lg');
});
});
});
</script>
{% endblock %}
{% extends "base.html" %}
{% block title %}关于我们 - 我的网站{% endblock %}
{% block styles %}
<style>
.team-member {
text-align: center;
padding: 20px;
}
.team-member img {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 15px;
}
.timeline {
position: relative;
padding-left: 30px;
}
.timeline:before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: #007bff;
}
</style>
{% endblock %}
{% block breadcrumb_items %}
<li class="breadcrumb-item active" aria-current="page">关于我们</li>
{% endblock %}
{% block content %}
<h1 class="mb-4">关于我们</h1>
<div class="row">
<div class="col-lg-6">
<h2>我们的故事</h2>
<p>我们成立于2010年,一直致力于为客户提供最优质的技术解决方案。</p>
<p>我们的使命是通过创新技术让世界变得更美好。</p>
</div>
<div class="col-lg-6">
<h2>我们的价值观</h2>
<ul>
<li><strong>创新</strong>:不断探索新技术</li>
<li><strong>诚信</strong>:诚实对待每一位客户</li>
<li><strong>卓越</strong>:追求最高标准的质量</li>
<li><strong>合作</strong>:团队合作共创价值</li>
</ul>
</div>
</div>
<!-- 时间线 -->
<div class="mt-5">
<h2>发展历程</h2>
<div class="timeline mt-4">
{% for milestone in milestones %}
<div class="mb-4 position-relative">
<div class="position-absolute" style="left: -30px; top: 0;">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center"
style="width: 30px; height: 30px;">
{{ loop.index }}
</div>
</div>
<h5>{{ milestone.year }} - {{ milestone.title }}</h5>
<p>{{ milestone.description }}</p>
</div>
{% endfor %}
</div>
</div>
<!-- 团队成员 -->
<div class="mt-5">
<h2 class="mb-4">我们的团队</h2>
<div class="row">
{% for member in team_members %}
<div class="col-md-4 mb-4">
<div class="team-member">
<img src="{{ member.avatar }}" alt="{{ member.name }}">
<h5>{{ member.name }}</h5>
<p class="text-muted">{{ member.position }}</p>
<p>{{ member.bio }}</p>
<div>
{% if member.linkedin %}
<a href="{{ member.linkedin }}" class="text-decoration-none me-2">
<i class="fab fa-linkedin fa-lg"></i>
</a>
{% endif %}
{% if member.github %}
<a href="{{ member.github }}" class="text-decoration-none me-2">
<i class="fab fa-github fa-lg"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block footer %}
{# 完全覆盖页脚,不显示父模板内容 #}
<footer class="bg-dark text-white py-4 mt-5">
<div class="container text-center">
<p>© 2023 关于页面特别页脚</p>
<p><small>这个页面的页脚与基础模板不同</small></p>
</div>
</footer>
{% endblock %}
{% block title %}
默认标题
{% endblock %}
子模板可以不覆盖,使用默认内容
{% block styles %}
{% endblock %}
子模板可以填充内容
{% block sidebar %}
{% block sidebar_menu %}
{% endblock %}
{% endblock %}
块中可以包含其他块
{# base.html #}
<html>
<body>
{% block content %}
{% block header %}
<h1>默认标题</h1>
{% endblock %}
{% block main %}
<p>默认内容</p>
{% endblock %}
{% endblock %}
</body>
</html>
{# child.html #}
{% extends "base.html" %}
{# 直接覆盖content块 #}
{% block content %}
<div class="container">
{{ super() }} {# 包含父模板的header和main块 #}
</div>
{% endblock %}
{# 或者单独覆盖某个块 #}
{% block header %}
<h1 class="special">特殊标题</h1>
{% endblock %}
{{ super() }} 用于在子模板中引用父模板中同名块的内容。
{# 父模板 #}
{% block head %}
<meta name="description" content="默认描述">
<link rel="stylesheet" href="/css/base.css">
{% endblock %}
{# 子模板 #}
{% block head %}
{{ super() }} {# 包含父模板的内容 #}
{# 添加额外内容 #}
<meta name="keywords" content="额外关键字">
<link rel="stylesheet" href="/css/custom.css">
{% endblock %}
<meta name="description" content="默认描述">
<link rel="stylesheet" href="/css/base.css">
<meta name="keywords" content="额外关键字">
<link rel="stylesheet" href="/css/custom.css">
父模板内容 + 子模板内容
{# base.html #}
{% block scripts %}
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
{% endblock %}
{# child.html #}
{% block scripts %}
{{ super() }}
<script src="/js/page-specific.js"></script>
{% endblock %}
{# grandchild.html #}
{% block scripts %}
{{ super() }}
<script src="/js/even-more-specific.js"></script>
{% endblock %}
最终结果:所有层级的脚本都会被包含,顺序是:父 → 子 → 孙
可以创建多层继承结构,形成更细粒度的模板层次。
<!DOCTYPE html>
<html>
{% block head %}{% endblock %}
<body>
{% block body %}
{% block header %}{% endblock %}
{% block main %}{% endblock %}
{% block footer %}{% endblock %}
{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block head %}
{{ super() }}
<!-- 特定区域的样式 -->
<link rel="stylesheet" href="/css/section.css">
{% endblock %}
{% block header %}
<nav>区域导航</nav>
{% endblock %}
{% block main %}
<div class="section-content">
{% block content %}{% endblock %}
</div>
{% endblock %}
{% extends "section_base.html" %}
{% block head %}
{{ super() }}
<!-- 博客文章特定样式 -->
<link rel="stylesheet" href="/css/blog.css">
{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<div>{{ post.content }}</div>
</article>
{% endblock %}
| 特性 | {% extends %} | {% include %} |
|---|---|---|
| 目的 | 创建模板层次结构 | 引入可重用组件 |
| 用法 | 必须是模板的第一个标签 | 可以在模板的任何位置使用 |
| 数量 | 只能继承一个模板 | 可以包含多个模板 |
| 内容 | 覆盖父模板的块 | 直接插入整个模板内容 |
| 适合场景 | 页面布局框架 | 导航栏、页脚、侧边栏等组件 |
| 变量作用域 | 继承所有变量 | 可以传递特定变量 |
{# base.html #}
<!DOCTYPE html>
<html>
<body>
{% include 'navbar.html' %}
{% block content %}{% endblock %}
{% include 'footer.html' %}
</body>
</html>
{# home.html #}
{% extends "base.html" %}
{% block content %}
<h1>首页</h1>
{% include 'featured_products.html' %}
{% include 'latest_news.html' %}
{% endblock %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的博客{% endblock %}</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
<div id="app">
{% include 'partials/header.html' %}
<div class="container mt-4">
{% block content_wrapper %}
{% block content %}{% endblock %}
{% endblock %}
</div>
{% include 'partials/footer.html' %}
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block extra_css %}
{{ super() }}
<link href="/css/blog.css" rel="stylesheet">
{% endblock %}
{% block content_wrapper %}
<div class="row">
<!-- 主要内容 -->
<div class="col-md-8">
{% block blog_content %}{% endblock %}
</div>
<!-- 侧边栏 -->
<div class="col-md-4">
{% block sidebar %}
{% include 'blog/partials/sidebar.html' %}
{% endblock %}
</div>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script src="/js/blog.js"></script>
{% endblock %}
{% extends "blog_base.html" %}
{% block title %}{{ post.title }} - 我的博客{% endblock %}
{% block blog_content %}
<article class="blog-post">
<header class="mb-4">
<h1 class="display-4">{{ post.title }}</h1>
<div class="post-meta text-muted">
<i class="fas fa-user me-1"></i>
{{ post.author.name }}
<i class="fas fa-calendar ms-3 me-1"></i>
{{ post.published_at|date('Y-m-d') }}
<i class="fas fa-eye ms-3 me-1"></i>
{{ post.views }} 阅读
</div>
</header>
<div class="post-content">
{{ post.content|safe }}
</div>
<div class="post-tags mt-4">
{% for tag in post.tags %}
<a href="{{ url_for('tag_posts', tag_slug=tag.slug) }}"
class="badge bg-secondary me-1">
#{{ tag.name }}
</a>
{% endfor %}
</div>
</article>
<!-- 评论部分 -->
{% include 'blog/partials/comments.html' %}
<!-- 相关文章 -->
{% if related_posts %}
<div class="mt-5">
相关文章
<div class="row">
{% for related in related_posts %}
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5>
<a href="{{ url_for('post_detail', slug=related.slug) }}">
{{ related.title }}
</a>
</h5>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{% block sidebar %}
{{ super() }}
<!-- 添加额外的侧边栏内容 -->
<div class="card mt-4">
<div class="card-body">
<h5>目录</h5>
<nav id="toc"></nav>
</div>
</div>
{% endblock %}
{% block extra_js %}
{{ super() }}
<script>
// 生成目录
document.addEventListener('DOMContentLoaded', function() {
const headings = document.querySelectorAll('.post-content h2, .post-content h3');
const toc = document.getElementById('toc');
headings.forEach((heading, index) => {
const id = 'heading-' + index;
heading.id = id;
const link = document.createElement('a');
link.href = '#' + id;
link.className = 'd-block py-1';
link.textContent = heading.textContent;
if (heading.tagName === 'H3') {
link.style.paddingLeft = '20px';
}
toc.appendChild(link);
});
});
</script>
{% endblock %}
from flask import render_template
from models import Post
@app.route('/blog/<slug>')
def post_detail(slug):
"""博客文章详情页"""
post = Post.query.filter_by(slug=slug).first_or_404()
# 增加阅读量
post.increment_views()
# 获取相关文章
related_posts = Post.get_related_posts(post, limit=3)
return render_template(
'blog/post_detail.html',
post=post,
related_posts=related_posts,
# 传递给include模板的变量
show_comments=True,
allow_commenting=post.allow_comments
)
@app.route('/blog/category/<category_slug>')
def category_posts(category_slug):
"""分类文章列表页"""
category = Category.query.filter_by(slug=category_slug).first_or_404()
posts = Post.query.filter_by(category_id=category.id).paginate(page=1, per_page=10)
return render_template(
'blog/category.html',
category=category,
posts=posts,
page_title=f"{category.name} - 文章分类"
)
通过本教程,你已经掌握了Flask模板继承的核心概念:
{% extends %} 继承基础模板{% block %} 覆盖父模板内容{{ super() }} 引用父模板内容{% include %} 和 {% extends %}