使用 Flask 和 Bootstrap 制作轻量级单词背诵 Web 应用
在这篇博客中,我将分享如何使用 Flask 和 Bootstrap 制作一个轻量级的单词背诵 Web 应用,该应用支持手机和 Web 端,并可以私有化部署在本地。
前言
本人近期准备考研,在英语学习方面觉得一些app太繁琐,花哨,我仅需要的一个功能,就是排查有那些不会的单词,这种简单的功能在市面的app上很少见,故搭建属于自己的app,希望有能力和兴趣的朋友可以通过本文搭建适合自己学习方法的背单词app,通过这种方式,用户可以方便地进行单词自测,并标记出不熟悉的单词,进行针对性记忆。
技术栈
- Flask:用于构建 Web 服务器。
安装Flask:使用pip安装Flask:
pip install flask
- Bootstrap:用于构建响应式前端页面,适配手机和 Web 端。
目录结构
app.py//应用代码
index.html//前端
mistakes.txt//错误记录
words.txt//词库
服务代码
from flask import Flask, send_from_directory, request, jsonify
import os
app = Flask(__name__)
@app.route('/')
def serve_index():
return send_from_directory('.', 'index.html')
@app.route('/words.txt')
def serve_words():
return send_from_directory('.', 'words.txt')
@app.route('/mistakes.txt')
def serve_mistakes():
return send_from_directory('.', 'mistakes.txt')
@app.route('/save_mistake', methods=['POST'])
def save_mistake():
word = request.json.get('word')
if word:
with open('mistakes.txt', 'a', encoding='utf-8') as file:
file.write(" ".join(word) + "\n")
return jsonify({"status": "success"}), 200
return jsonify({"status": "error"}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
以上代码定义了一个简单的 Flask 应用,包括以下几个路由:
/
:返回index.html
文件,即前端页面。/words.txt
:返回包含单词列表的文本文件。/mistakes.txt
:返回包含用户标记的错误单词的文本文件。/save_mistake
:接收用户提交的错误单词,并保存到mistakes.txt
文件中
前端界面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单词练习</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #2c3e50;
color: #ecf0f1;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
width: 400px;
padding: 20px;
background-color: #34495e;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input {
width: 100%;
padding: 8px;
border: none;
border-radius: 4px;
font-size: 16px;
}
.button {
width: 100%;
padding: 10px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-bottom: 10px;
}
.button-primary {
background-color: #3498db;
color: #ecf0f1;
}
.button-secondary {
background-color: #e67e22;
color: #ecf0f1;
}
.button-success {
background-color: #2ecc71;
color: #ecf0f1;
}
.button-danger {
background-color: #e74c3c;
color: #ecf0f1;
}
.word-display {
font-size: 24px;
text-align: center;
margin: 20px 0;
}
.card {
background-color: #ecf0f1;
color: #2c3e50;
border-radius: 8px;
padding: 20px;
margin: 10px 0;
cursor: pointer;
transition: all 0.3s ease;
}
.card.collapsed {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card.expanded {
white-space: normal;
}
</style>
</head>
<body>
<div class="container">
<div class="form-group">
<label for="start-id">起始编号:</label>
<input type="number" id="start-id">
</div>
<div class="form-group">
<label for="end-id">结束编号:</label>
<input type="number" id="end-id">
</div>
<button class="button button-primary" onclick="showWords()">显示单词</button>
<div class="word-display" id="word-display"></div>
<button class="button button-success" onclick="nextWord(true)" id="correct-button" disabled>正确</button>
<button class="button button-danger" onclick="nextWord(false)" id="wrong-button" disabled>错误</button>
<button class="button button-secondary" onclick="showMistakes()">查看错误单词</button>
<div id="mistake-display"></div>
</div>
<script>
let words = [];
let mistakes = [];
let displayWords = [];
let currentIndex = 0;
// 载入单词数据
async function loadWords() {
const response = await fetch('words.txt');
if (response.ok) {
const text = await response.text();
words = text.split('\n').map(line => line.trim().split(/\s+/));
} else {
console.error('Failed to load words.txt');
}
}
// 显示指定范围的单词
function showWords() {
const startId = parseInt(document.getElementById('start-id').value);
const endId = parseInt(document.getElementById('end-id').value);
displayWords = words.filter(word => startId <= parseInt(word[0]) && parseInt(word[0]) <= endId);
currentIndex = 0;
updateWord();
}
// 更新当前显示的单词
function updateWord() {
const wordDisplay = document.getElementById('word-display');
wordDisplay.innerHTML = ''; // 清空原有内容
if (currentIndex < displayWords.length) {
const word = displayWords[currentIndex];
const card = document.createElement('div');
card.className = 'card collapsed';
card.innerText = word[1];
card.onclick = function() {
if (card.classList.contains('collapsed')) {
card.classList.remove('collapsed');
card.classList.add('expanded');
card.innerText = `${word[1]}: ${word[2]}`;
document.getElementById('correct-button').disabled = false;
document.getElementById('wrong-button').disabled = false;
} else {
card.classList.remove('expanded');
card.classList.add('collapsed');
card.innerText = word[1];
document.getElementById('correct-button').disabled = true;
document.getElementById('wrong-button').disabled = true;
}
};
wordDisplay.appendChild(card);
} else {
document.getElementById('word-display').innerText = "没有更多的单词。";
document.getElementById('correct-button').disabled = true;
document.getElementById('wrong-button').disabled = true;
}
}
// 处理下一个单词
function nextWord(correct) {
if (!correct) {
mistakes.push(displayWords[currentIndex]);
saveMistake(displayWords[currentIndex]);
}
currentIndex++;
updateWord();
}
// 保存错误单词
async function saveMistake(word) {
const response = await fetch('/save_mistake', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ word }),
});
if (response.ok) {
console.log('Mistake saved');
} else {
console.error('Failed to save mistake');
}
}
// 显示错误单词
async function showMistakes() {
const response = await fetch('mistakes.txt');
if (response.ok) {
const text = await response.text();
const mistakeWords = text.split('\n').map(line => line.trim().split(' ')).filter(line => line.length > 0);
const mistakeDisplay = document.getElementById('mistake-display');
mistakeDisplay.innerHTML = ''; // 清空原有内容
mistakeWords.forEach(word => {
const card = document.createElement('div');
card.className = 'card collapsed';
card.innerText = word[1];
card.onclick = function() {
if (card.classList.contains('collapsed')) {
card.classList.remove('collapsed');
card.classList.add('expanded');
card.innerText = `${word[1]}: ${word[2]}`;
} else {
card.classList.remove('expanded');
card.classList.add('collapsed');
card.innerText = word[1];
}
};
mistakeDisplay.appendChild(card);
});
} else {
console.error('Failed to load mistakes.txt');
}
}
// 初始化加载单词数据
loadWords();
</script>
</body>
</html>
以上 HTML 文件使用 Bootstrap 来构建响应式页面,通过 JavaScript 实现单词加载和错误单词标记功能。
部署与使用
-
将上述代码保存在
vocab_app
目录中。 -
运行 Flask 应用:
python app.py
-
打开浏览器,访问
http://localhost:5000
,即可看到单词背诵页面。 -
在手机端,同样访问
http://localhost:5000
,即可看到单词背诵页面。zhu
注:localhost即使用cmd 查询ipconfig后的ipv4地址:如192.168.xx.xx
展示
单词测试 | 词库 | 点击显示 |
总结
通过 Flask 和 Bootstrap,我们可以轻松地构建一个支持手机和 Web 端的单词背诵应用。用户可以方便地加载单词列表并标记错误单词,进行针对性记忆。
希望这篇博客能对你有所帮助,如果有任何问题或建议,欢迎留言讨论!