在 FastAPI 中拆分大型 HTML,推荐使用 Jinja2 模板引擎,它既支持模板继承又支持独立运行。
方案一:模板继承(推荐)
- 目录结构
project/
├── main.py
├── templates/
│ ├── base.html # 基础模板
│ ├── components/
│ │ ├── header.html # 可独立运行
│ │ ├── sidebar.html # 可独立运行
│ │ └── footer.html # 可独立运行
│ └── pages/
│ └── index.html # 主页面
└── static/
├── css/
└── js/
- 基础模板(base.html)
html
<!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 rel="stylesheet" href="/static/css/main.css">
{% block extra_css %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<script src="/static/js/main.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
- 独立可运行的组件(header.html)
html
{% extends "base.html" %}
{% block title %}Header Component{% endblock %}
{% block content %}
<header class="header">
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
<a href="/contact">联系</a>
</nav>
</header>
<!-- 独立运行时的测试内容 -->
{% if standalone %}
<div style="padding: 20px; background: #f0f0f0;">
<h3>这是 Header 组件的独立预览</h3>
</div>
{% endif %}
{% endblock %}
- 主页面组合(index.html)
html
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block content %}
{% include "components/header.html" %}
<main class="main-content">
<h1>主要内容区域</h1>
{% include "components/sidebar.html" %}
<div class="content">
<!-- 具体内容 -->
</div>
</main>
{% include "components/footer.html" %}
{% endblock %}
- FastAPI 后端代码(main.py)
python
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
app = FastAPI()
# 挂载静态文件
app.mount("/static", StaticFiles(directory="static"), name="static")
# 配置模板
templates = Jinja2Templates(directory="templates")
# 主页面路由
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse(
"pages/index.html",
{"request": request}
)
# 组件独立预览路由
@app.get("/preview/header", response_class=HTMLResponse)
async def preview_header(request: Request):
return templates.TemplateResponse(
"components/header.html",
{"request": request, "standalone": True}
)
@app.get("/preview/sidebar", response_class=HTMLResponse)
async def preview_sidebar(request: Request):
return templates.TemplateResponse(
"components/sidebar.html",
{"request": request, "standalone": True}
)
方案二:使用宏(Macros)
组件定义(macros.html)
html
{% macro render_card(title, content, image_url) %}
<div class="card">
{% if image_url %}
<img src="{{ image_url }}" alt="{{ title }}">
{% endif %}
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
{% endmacro %}
{% macro render_button(text, type="primary", url="#") %}
<a href="{{ url }}" class="btn btn-{{ type }}">{{ text }}</a>
{% endmacro %}
使用宏
html
{% from "components/macros.html" import render_card, render_button %}
{% extends "base.html" %}
{% block content %}
{{ render_card("标题", "内容描述", "/static/img/photo.jpg") }}
{{ render_button("点击我", "success", "/action") }}
{% endblock %}
方案三:动态加载(适合大型应用)
使用 HTMX 实现动态加载
html
<!-- 主页面 -->
<div id="header-container"
hx-get="/api/components/header"
hx-trigger="load"
hx-swap="innerHTML">
加载中...
</div>
FastAPI 路由
python
@app.get("/api/components/header")
async def get_header_component(request: Request):
return templates.TemplateResponse(
"components/header.html",
{"request": request},
# 只返回片段,不包含完整HTML结构
)
最佳实践建议
- 组件设计原则
python
# 为组件添加上下文管理
@app.get("/components/{component_name}")
async def render_component(
component_name: str,
request: Request,
standalone: bool = False
):
"""
standalone=True: 返回完整HTML(用于独立预览)
standalone=False: 返回片段(用于include)
"""
context = {
"request": request,
"standalone": standalone,
# 添加组件所需的数据
}
return templates.TemplateResponse(
f"components/{component_name}.html",
context
)
- 性能优化
python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_template_content(template_name: str):
"""缓存静态模板内容"""
return templates.get_template(template_name)
- 组件通信
html
<!-- 使用自定义事件 -->
<script>
document.addEventListener('headerLoaded', function(e) {
console.log('Header组件已加载', e.detail);
});
</script>
完整示例
sidebar.html(独立可运行)
html
{% if not embedded %}
<!DOCTYPE html>
<html>
<head>
<title>Sidebar Preview</title>
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
{% endif %}
<aside class="sidebar">
<ul>
<li><a href="/dashboard">仪表板</a></li>
<li><a href="/settings">设置</a></li>
</ul>
</aside>
{% if not embedded %}
</body>
</html>
{% endif %}
使用时传递 embedded 参数
python
# 作为组件使用
templates.TemplateResponse("components/sidebar.html", {
"request": request,
"embedded": True
})
# 独立预览
templates.TemplateResponse("components/sidebar.html", {
"request": request,
"embedded": False
})
这种方案既保证了运行流畅(通过模板继承和缓存),又能让组件独立运行(通过条件判断)。