HTML 模块化方案

在 FastAPI 中拆分大型 HTML,推荐使用 Jinja2 模板引擎,它既支持模板继承又支持独立运行。

方案一:模板继承(推荐)

  1. 目录结构

project/

├── main.py

├── templates/

│ ├── base.html # 基础模板

│ ├── components/

│ │ ├── header.html # 可独立运行

│ │ ├── sidebar.html # 可独立运行

│ │ └── footer.html # 可独立运行

│ └── pages/

│ └── index.html # 主页面

└── static/

├── css/

└── js/

  1. 基础模板(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>
  1. 独立可运行的组件(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 %}
  1. 主页面组合(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 %}
  1. 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结构
    )

最佳实践建议

  1. 组件设计原则
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
    )
  1. 性能优化
python 复制代码
from functools import lru_cache

@lru_cache(maxsize=128)
def get_template_content(template_name: str):

    """缓存静态模板内容"""

    return templates.get_template(template_name)
  1. 组件通信
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
})

这种方案既保证了运行流畅(通过模板继承和缓存),又能让组件独立运行(通过条件判断)。

相关推荐
咩咩啃树皮3 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
阳光是sunny3 小时前
LangGraph中的Reducer是什么
前端·人工智能·后端
触底反弹3 小时前
一文搞懂 Tailwind CSS 弹性布局:从原理到实战
前端·css·html
阳光是sunny3 小时前
从链到图:LangGraph 入门基础全解析
前端·人工智能·后端
小林ixn4 小时前
从 ??= 到 onKeyDown:一个 React 组件的“自我修养”
前端·javascript·react.js
REDcker4 小时前
显示分辨率标准对照详解
前端·网络·分辨率·显示·屏幕
এ慕ོ冬℘゜4 小时前
前端实战:jQuery 多条件联合搜索(标题模糊查询 + 日历时间段筛选)
前端·javascript·jquery
武子康4 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
一点一木5 小时前
从60首歌到1个网站:输入你的故事,还你一首歌
前端·github
IT_陈寒6 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端