### 代码概述
为了实现 **将 Python 生成的 HTML 文件以漫画名称(comic_name)命名** 的功能,我们需要:
1. 使用 **Jinja2 模板引擎** 来动态生成 HTML 内容;
2. 将 `comic_name` 作为变量传递给模板;
3. 在 Python 脚本中根据 `comic_name` 动态生成 HTML 文件名;
4. 生成的文件格式为:`${comic_name}.html`,例如 `one_piece.html`。
---
### 完整代码示例
#### ✅ 1. 模板文件 `template.html`
```html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>漫画阅读器 - {{ comic_name }}</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #000;
}
.container {
position: relative;
max-width: 100%;
margin: 0 auto;
text-align: center;
overflow: hidden;
}
#gallery {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
border: none;
}
.button-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 200px;
margin: 10px auto;
z-index: 10;
}
button {
font-size: 14px;
padding: 8px 16px;
background-color: rgba(255, 255, 255, 0.8);
border: none;
border-radius: 4px;
cursor: pointer;
}
#mySidebar {
height: 100%;
width: 200px;
position: fixed;
z-index: 100;
top: 0;
left: -200px;
background-color: #f4f4f4;
overflow-x: hidden;
transition: 0.3s;
padding-top: 60px;
}
#mySidebar a {
padding: 10px 15px;
text-decoration: none;
font-size: 16px;
color: black;
display: block;
transition: 0.3s;
}
#mySidebar a:hover,
#mySidebar a.active {
color: white;
background-color: #222;
}
#main {
transition: margin-left .3s;
padding: 16px;
margin-left: 20px;
}
.openbtn {
position: fixed;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 30px;
height: 30px;
font-size: 0;
background-color: #111;
color: white;
border: none;
cursor: pointer;
z-index: 101;
}
.openbtn::before {
content: '\25B7';
font-size: 16px;
color: white;
}
.openbtn.open::before {
content: '\25C2';
}
#toast {
visibility: hidden;
min-width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 4px;
padding: 10px;
position: fixed;
z-index: 102;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
}
#toast.show {
visibility: visible;
opacity: 1;
}
@media (max-width: 600px) {
.button-container {
width: 150px;
margin: 5px auto;
}
button {
font-size: 12px;
padding: 6px 12px;
}
#mySidebar {
width: 150px;
}
#mySidebar a {
font-size: 14px;
padding: 8px 12px;
}
}
</style>
</head>
<body>
<div id="mySidebar" class="sidebar">
<a href="#">目录</a>
</div>
<div id="main">
<button class="openbtn" onclick="toggleNav()"></button>
<div class="container">
<img id="gallery" src="" alt="图片加载失败">
<div class="button-container">
<button id="prevButton" onclick="prevImage()">上一张</button>
<button id="nextButton" onclick="nextAction()">下一张</button>
</div>
</div>
</div>
<div id="toast">已经是最后一话了</div>
<script>
const comic_name = "{{ comic_name }}";
let currentIndex = { chapter: 0, pic: 1 };
const imgElement = document.getElementById("gallery");
const nextButton = document.getElementById("nextButton");
const prevButton = document.getElementById("prevButton");
const maxChapter = {{ maxChapter }};
function initImage() {
updateImage(currentIndex.chapter, currentIndex.pic);
}
function getMaxPic(chapter) {
return 5;
}
function updateImage(chapter, pic) {
const newSrc = `http://localhost:8080/${comic_name}/${chapter}/${pic}.webp`;
const img = new Image();
img.onload = () => {
imgElement.src = newSrc;
};
img.onerror = () => {
showToast("图片加载失败");
imgElement.src = "";
};
img.src = newSrc;
}
function imageExists(url, callback) {
const img = new Image();
img.onload = () => callback(true);
img.onerror = () => callback(false);
img.src = url;
}
function updateNextButton() {
const nextPic = currentIndex.pic + 1;
const currentPicUrl = `http://localhost:8080/${comic_name}/${currentIndex.chapter}/${nextPic}.webp`;
imageExists(currentPicUrl, function(exists) {
if (exists) {
nextButton.textContent = "下一张";
nextButton.style.display = '';
} else {
if (currentIndex.chapter < maxChapter) {
nextButton.textContent = "下一话";
nextButton.style.display = '';
} else {
nextButton.style.display = 'none';
}
}
});
}
function updatePrevButton() {
if (currentIndex.pic === 1) {
prevButton.style.display = 'none';
} else {
prevButton.style.display = '';
}
}
function prevImage() {
if (currentIndex.pic > 1) {
currentIndex.pic--;
updateImage(currentIndex.chapter, currentIndex.pic);
updatePrevButton();
updateNextButton();
updateSidebarHighlight();
} else if (currentIndex.chapter > 0) {
currentIndex.chapter--;
currentIndex.pic = getMaxPic(currentIndex.chapter);
updateImage(currentIndex.chapter, currentIndex.pic);
updatePrevButton();
updateNextButton();
updateSidebarHighlight();
}
}
function nextAction() {
if (nextButton.textContent === "下一张") {
currentIndex.pic++;
updateImage(currentIndex.chapter, currentIndex.pic);
updateNextButton();
updatePrevButton();
updateSidebarHighlight();
} else {
if (currentIndex.chapter < maxChapter) {
currentIndex.chapter++;
currentIndex.pic = 1;
updateImage(currentIndex.chapter, 1);
updateNextButton();
updatePrevButton();
updateSidebarHighlight();
} else {
showToast("已经是最后一话了");
}
}
}
function toggleNav() {
const sidebar = document.getElementById("mySidebar");
const btn = document.querySelector(".openbtn");
if (sidebar.style.left === "0px") {
sidebar.style.left = "-200px";
btn.classList.remove("open");
} else {
sidebar.style.left = "0";
btn.classList.add("open");
}
}
function loadSidebar() {
const sidebar = document.getElementById("mySidebar");
for (let i = 0; i <= maxChapter; i++) {
const link = document.createElement("a");
link.href = `javascript:void(0);`;
link.textContent = `第${i}话`;
link.dataset.chapter = i;
link.onclick = (function(chapter) {
return function() {
currentIndex.chapter = chapter;
currentIndex.pic = 1;
updateImage(chapter, 1);
updateNextButton();
updatePrevButton();
updateSidebarHighlight();
toggleNav();
};
})(i);
sidebar.appendChild(link);
}
}
function updateSidebarHighlight() {
const links = document.querySelectorAll("#mySidebar a[data-chapter]");
links.forEach(link => {
const chapter = parseInt(link.dataset.chapter);
if (chapter === currentIndex.chapter) {
link.classList.add("active");
} else {
link.classList.remove("active");
}
});
}
window.onload = function() {
setTimeout(() => {
initImage();
updateNextButton();
updatePrevButton();
loadSidebar();
updateSidebarHighlight();
}, 100);
};
function showToast(message) {
const toast = document.getElementById("toast");
toast.textContent = message;
toast.className = "show";
setTimeout(() => {
toast.className = toast.className.replace("show", "");
}, 2000);
}
</script>
</body>
</html>
```
#### ✅ 2. Python 脚本 `generate_html.py`
```python
from jinja2 import Environment, FileSystemLoader
import os
# 设置模板路径
template_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(template_dir))
# 加载模板
template = env.get_template('template.html')
# 配置参数
comics = [
{"comic_name": "one_piece", "maxChapter": 19},
{"comic_name": "naruto", "maxChapter": 25},
{"comic_name": "bleach", "maxChapter": 30},
]
# 为每部漫画生成 HTML 文件
for comic in comics:
output_filename = f"{comic['comic_name']}.html"
output_html = template.render(comic)
with open(output_filename, 'w', encoding='utf-8') as f:
f.write(output_html)
print(f"HTML 文件 {output_filename} 已生成!")
```
---
### ✅ 功能说明
| 功能 | 说明 |
|------|------|
| 模板渲染 | 使用 Jinja2 模板动态替换漫画名称和章节信息 |
| 文件命名 | 生成的 HTML 文件名为 `${comic_name}.html` |
| 多漫画支持 | 可一次生成多部漫画页面 |
---
### 知识点
1. **Jinja2 模板引擎**:用于将 HTML 模板与变量分离,实现动态内容生成。
- 支持变量替换、循环、条件判断等逻辑。
2. **Python 文件写入**:使用 `open()` 和 `with` 上下文管理器安全地写入文件。
- 确保文件操作完成后自动关闭文件流。
3. **变量命名与文件命名规则**:通过变量 `comic_name` 构建输出文件名,确保 HTML 文件按漫画名命名。