### 代码概述
您希望以现有的 HTML 文件为基础,通过 **Python 自动生成** 类似的网页模板文件。生成过程中,可以动态替换其中的变量,例如漫画名称(`comic_name`)、章节数量(`maxChapter`)等内容。
为了实现自动化生成,我们可以使用 **Python 的字符串模板引擎** 或 **Jinja2 模板引擎**,后者更适用于 HTML 这类复杂结构的模板化生成。
---
### 代码解析
我们将使用 **Jinja2** 模板引擎来生成 HTML 页面,因为它支持变量替换、条件判断、循环等功能,非常适合用于网页模板化。
#### 步骤如下:
1. **安装 Jinja2**
```bash
pip install jinja2
```
2. **创建模板文件(template.html)**
```html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>本地图片翻页器</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.container {
position: relative;
max-width: 800px;
margin: 20px auto;
}
#gallery {
max-width: 100%;
height: auto;
display: block;
}
.button-container {
position: absolute;
bottom: 10px;
right: 10px;
}
button {
font-size: 16px;
padding: 10px 20px;
margin-left: 10px;
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
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: 20px;
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 {
width: 40px;
height: 40px;
font-size: 0;
cursor: pointer;
background-color: #111;
color: white;
border: none;
position: fixed;
top: 50%;
left: 0;
transform: translateY(-50%);
z-index: 101;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 4px 4px 0;
transition: all 0.3s;
}
.openbtn::before {
content: '\25B7';
font-size: 20px;
color: white;
}
.openbtn.open::before {
content: '\25C2';
}
.openbtn:hover {
background-color: #444;
}
#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;
}
</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`;
imgElement.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 () {
initImage();
updateNextButton();
updatePrevButton();
loadSidebar();
updateSidebarHighlight();
};
imgElement.onerror = function () {
alert("图片加载失败,请手动切换");
};
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>
```
3. **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')
# 配置参数
context = {
"comic_name": "one_piece", # 可动态替换
"maxChapter": 19 # 可动态替换
}
# 渲染并保存生成的 HTML
output_html = template.render(context)
with open('output.html', 'w', encoding='utf-8') as f:
f.write(output_html)
print("HTML 文件生成成功!")
```
---
### 知识点
1. **Jinja2 模板引擎**:用于将 HTML 模板与变量分离,实现动态内容生成。
- 支持变量替换、循环、条件判断等逻辑。
2. **字符串模板替换**:在模板中使用 `{{ variable }}` 占位符,在 Python 中进行动态赋值。
- 类似 JavaScript 的模板字符串,但适用于后端生成 HTML。
3. **Python 文件写入**:使用 `open()` 和 `with` 上下文管理器安全地写入文件。
- 确保文件操作完成后自动关闭文件流。