(Python)待办事项升级网页版(html)(Python项目)

发布于:2025-08-07 ⋅ 阅读:(12) ⋅ 点赞:(0)

源代码:

app.py

from flask import Flask, render_template, request, redirect, url_for, jsonify
import json
import os

app = Flask(__name__)

# 数据存储文件
DATA_FILE = "todos.json"

def load_todos():
    """从文件加载待办事项"""
    if os.path.exists(DATA_FILE):
        try:
            with open(DATA_FILE, "r") as f:
                return json.load(f)
        except:
            return []
    return []

def save_todos(todos):
    """保存待办事项到文件"""
    with open(DATA_FILE, "w") as f:
        json.dump(todos, f)

@app.route('/')
def index():
    """显示主页"""
    todos = load_todos()
    # 计算完成和未完成的任务数量
    completed = sum(1 for todo in todos if todo["done"])
    not_completed = len(todos) - completed
    return render_template('index.html', todos=todos, 
                           completed=completed, not_completed=not_completed)

@app.route('/add', methods=['POST'])
def add_todo():
    """添加新任务"""
    task = request.form.get('task')
    if task:
        todos = load_todos()
        todos.append({"task": task, "done": False})
        save_todos(todos)
    return redirect(url_for('index'))

@app.route('/toggle/<int:index>')
def toggle_todo(index):
    """切换任务状态(完成/未完成)"""
    todos = load_todos()
    if 0 <= index < len(todos):
        todos[index]["done"] = not todos[index]["done"]
        save_todos(todos)
    return redirect(url_for('index'))

@app.route('/delete/<int:index>')
def delete_todo(index):
    """删除任务"""
    todos = load_todos()
    if 0 <= index < len(todos):
        todos.pop(index)
        save_todos(todos)
    return redirect(url_for('index'))

@app.route('/clear')
def clear_completed():
    """清除已完成的任务"""
    todos = load_todos()
    # 只保留未完成的任务
    todos = [todo for todo in todos if not todo["done"]]
    save_todos(todos)
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

index.html

<!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>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
            padding: 20px;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
            padding: 30px;
        }
        
        h1 {
            text-align: center;
            margin-bottom: 30px;
            color: #2c3e50;
        }
        
        .todo-form {
            display: flex;
            margin-bottom: 20px;
        }
        
        .todo-form input {
            flex: 1;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
            transition: border-color 0.3s;
        }
        
        .todo-form input:focus {
            border-color: #3498db;
            outline: none;
        }
        
        .todo-form button {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 12px 20px;
            margin-left: 10px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        }
        
        .todo-form button:hover {
            background-color: #2980b9;
        }
        
        .todo-stats {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            padding: 10px;
            background-color: #f8f9fa;
            border-radius: 4px;
            font-size: 14px;
        }
        
        .todo-stats span {
            display: inline-block;
            padding: 4px 8px;
            border-radius: 4px;
        }
        
        .total-tasks {
            background-color: #e3f2fd;
            color: #1976d2;
        }
        
        .completed-tasks {
            background-color: #e8f5e9;
            color: #388e3c;
        }
        
        .pending-tasks {
            background-color: #fff3e0;
            color: #f57c00;
        }
        
        .todo-list {
            list-style-type: none;
        }
        
        .todo-item {
            display: flex;
            align-items: center;
            padding: 15px;
            border-bottom: 1px solid #eee;
            transition: background-color 0.3s;
        }
        
        .todo-item:hover {
            background-color: #f9f9f9;
        }
        
        .todo-item.completed .todo-text {
            text-decoration: line-through;
            color: #95a5a6;
        }
        
        .todo-checkbox {
            margin-right: 15px;
            width: 20px;
            height: 20px;
            cursor: pointer;
        }
        
        .todo-text {
            flex: 1;
            font-size: 16px;
        }
        
        .delete-btn {
            background: none;
            border: none;
            color: #e74c3c;
            cursor: pointer;
            font-size: 18px;
            opacity: 0.7;
            transition: opacity 0.3s;
        }
        
        .delete-btn:hover {
            opacity: 1;
        }
        
        .clear-btn {
            display: block;
            margin: 20px auto 0;
            padding: 10px 20px;
            background-color: #f5f5f5;
            color: #e74c3c;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        
        .clear-btn:hover {
            background-color: #ffeef0;
        }
        
        @media (max-width: 600px) {
            .container {
                padding: 15px;
            }
            
            .todo-form {
                flex-direction: column;
            }
            
            .todo-form input {
                margin-bottom: 10px;
            }
            
            .todo-form button {
                margin-left: 0;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>📋 待办事项清单</h1>
        
        <form class="todo-form" action="/add" method="POST">
            <input type="text" name="task" placeholder="添加新任务..." required>
            <button type="submit">添加任务</button>
        </form>
        
        <div class="todo-stats">
            <span class="total-tasks">总任务: {{ todos|length }}</span>
            <span class="completed-tasks">已完成: {{ completed }}</span>
            <span class="pending-tasks">未完成: {{ not_completed }}</span>
        </div>
        
        <ul class="todo-list">
            {% for todo in todos %}
            <li class="todo-item {% if todo.done %}completed{% endif %}">
                <a href="{{ url_for('toggle_todo', index=loop.index0) }}">
                    <input type="checkbox" class="todo-checkbox" {% if todo.done %}checked{% endif %}>
                </a>
                <span class="todo-text">{{ todo.task }}</span>
                <a href="{{ url_for('delete_todo', index=loop.index0) }}" class="delete-btn">🗑️</a>
            </li>
            {% else %}
            <li class="todo-item">
                <span class="todo-text" style="text-align: center; width: 100%;">暂无任务,添加一个吧!</span>
            </li>
            {% endfor %}
        </ul>
        
        {% if completed > 0 %}
        <button class="clear-btn" onclick="location.href='{{ url_for('clear_completed') }}'">清除已完成任务</button>
        {% endif %}
    </div>
</body>
</html>

文件夹结构:

todo_web_app/
├── app.py          # Flask应用主文件
├── templates/      # HTML模板文件夹
│   └── index.html  # 主页模板
└── todos.json      # 数据存储文件(自动生成)

代码详解:

一、项目结构设计原理

1. 为什么需要这样的文件结构?

  • Flask框架要求:Flask遵循MVC(模型-视图-控制器)设计模式

    • app.py:控制器(Controller) - 处理业务逻辑

    • templates/index.html:视图(View) - 展示用户界面

    • todos.json:模型(Model) - 数据存储

  • 模板文件夹命名:Flask默认在templates文件夹中查找HTML模板文件

  • 数据文件位置:JSON数据文件放在项目根目录,便于读写

2. 为什么需要Web框架?

  • 处理HTTP协议:管理请求/响应生命周期

  • 路由管理:将URL映射到处理函数

  • 模板渲染:动态生成HTML内容

  • 会话管理:处理用户状态(本项目未使用)

二、app.py 代码逐行详解

# 导入必要的库
from flask import Flask, render_template, request, redirect, url_for
import json
import os

# 创建Flask应用实例
app = Flask(__name__)

# 数据存储文件
DATA_FILE = "todos.json"

def load_todos():
    """从文件加载待办事项"""
    # 检查文件是否存在
    if os.path.exists(DATA_FILE):
        try:
            # 打开文件并读取JSON内容
            with open(DATA_FILE, "r") as f:
                return json.load(f)
        except:
            # 如果读取失败(如文件为空或格式错误),返回空列表
            return []
    # 文件不存在时返回空列表
    return []

def save_todos(todos):
    """保存待办事项到文件"""
    # 将待办事项列表写入JSON文件
    with open(DATA_FILE, "w") as f:
        json.dump(todos, f)

# 定义根路由,处理主页请求
@app.route('/')
def index():
    """显示主页"""
    # 加载待办事项
    todos = load_todos()
    # 计算已完成任务数
    completed = sum(1 for todo in todos if todo["done"])
    # 计算未完成任务数
    not_completed = len(todos) - completed
    
    # 渲染index.html模板,并传入数据
    return render_template('index.html', 
                           todos=todos,
                           completed=completed,
                           not_completed=not_completed)

# 添加任务的路由,只接受POST请求
@app.route('/add', methods=['POST'])
def add_todo():
    """添加新任务"""
    # 从表单获取任务内容
    task = request.form.get('task')
    if task:
        # 加载现有任务
        todos = load_todos()
        # 添加新任务(默认为未完成)
        todos.append({"task": task, "done": False})
        # 保存更新后的任务列表
        save_todos(todos)
    # 重定向回主页
    return redirect(url_for('index'))

# 切换任务状态的路由
@app.route('/toggle/<int:index>')
def toggle_todo(index):
    """切换任务状态"""
    todos = load_todos()
    # 检查索引是否有效
    if 0 <= index < len(todos):
        # 切换完成状态(True变False,False变True)
        todos[index]["done"] = not todos[index]["done"]
        save_todos(todos)
    return redirect(url_for('index'))

# 删除任务的路由
@app.route('/delete/<int:index>')
def delete_todo(index):
    """删除任务"""
    todos = load_todos()
    if 0 <= index < len(todos):
        # 删除指定索引的任务
        todos.pop(index)
        save_todos(todos)
    return redirect(url_for('index'))

# 清除已完成任务的路由
@app.route('/clear')
def clear_completed():
    """清除已完成的任务"""
    todos = load_todos()
    # 创建新列表,只包含未完成的任务
    new_todos = [todo for todo in todos if not todo["done"]]
    save_todos(new_todos)
    return redirect(url_for('index'))

# 程序入口
if __name__ == '__main__':
    # 确保templates文件夹存在
    if not os.path.exists('templates'):
        os.makedirs('templates')
        print("已创建templates文件夹")
    
    # 启动Flask开发服务器
    # debug=True 表示开启调试模式(自动重载代码并显示详细错误)
    app.run(debug=True, port=5001)  # 指定端口5001,避免与其他应用冲突

关键点解析:

  1. 路由系统

    • @app.route('/'):装饰器将URL路径映射到处理函数

    • 动态路由@app.route('/toggle/<int:index>')<int:index>捕获URL中的整数参数

  2. 请求方法

    • 默认只处理GET请求

    • methods=['POST']明确指定处理POST请求

  3. 重定向模式

    • 操作后重定向回主页(redirect(url_for('index')))

    • 避免浏览器重复提交(POST/重定向/GET模式)

  4. 数据持久化

    • load_todos()save_todos()封装数据读写

    • JSON格式简单易读,适合小型应用

  5. 调试模式

    • app.run(debug=True)启用调试模式

    • 修改代码后自动重启服务器

    • 显示详细错误信息

三、index.html 代码逐行详解

<!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>
        /* 基础样式重置 */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        /* 页面整体样式 */
        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
            padding: 20px;
        }
        
        /* 内容容器 */
        .container {
            max-width: 800px;
            margin: 0 auto;
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
            padding: 30px;
        }
        
        /* 标题样式 */
        h1 {
            text-align: center;
            margin-bottom: 30px;
            color: #2c3e50;
        }
        
        /* 任务表单样式 */
        .todo-form {
            display: flex;
            margin-bottom: 20px;
        }
        
        .todo-form input {
            flex: 1;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
            transition: border-color 0.3s;
        }
        
        .todo-form input:focus {
            border-color: #3498db;
            outline: none;
        }
        
        .todo-form button {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 12px 20px;
            margin-left: 10px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        }
        
        .todo-form button:hover {
            background-color: #2980b9;
        }
        
        /* 任务统计样式 */
        .todo-stats {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            padding: 10px;
            background-color: #f8f9fa;
            border-radius: 4px;
            font-size: 14px;
        }
        
        .todo-stats span {
            display: inline-block;
            padding: 4px 8px;
            border-radius: 4px;
        }
        
        .total-tasks {
            background-color: #e3f2fd;
            color: #1976d2;
        }
        
        .completed-tasks {
            background-color: #e8f5e9;
            color: #388e3c;
        }
        
        .pending-tasks {
            background-color: #fff3e0;
            color: #f57c00;
        }
        
        /* 任务列表样式 */
        .todo-list {
            list-style-type: none;
        }
        
        .todo-item {
            display: flex;
            align-items: center;
            padding: 15px;
            border-bottom: 1px solid #eee;
            transition: background-color 0.3s;
        }
        
        .todo-item:hover {
            background-color: #f9f9f9;
        }
        
        /* 已完成任务样式 */
        .todo-item.completed .todo-text {
            text-decoration: line-through;
            color: #95a5a6;
        }
        
        /* 复选框样式 */
        .todo-checkbox {
            margin-right: 15px;
            width: 20px;
            height: 20px;
            cursor: pointer;
        }
        
        /* 任务文本样式 */
        .todo-text {
            flex: 1;
            font-size: 16px;
        }
        
        /* 删除按钮样式 */
        .delete-btn {
            background: none;
            border: none;
            color: #e74c3c;
            cursor: pointer;
            font-size: 18px;
            opacity: 0.7;
            transition: opacity 0.3s;
        }
        
        .delete-btn:hover {
            opacity: 1;
        }
        
        /* 清除按钮样式 */
        .clear-btn {
            display: block;
            margin: 20px auto 0;
            padding: 10px 20px;
            background-color: #f5f5f5;
            color: #e74c3c;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        
        .clear-btn:hover {
            background-color: #ffeef0;
        }
        
        /* 响应式设计 - 小屏幕适配 */
        @media (max-width: 600px) {
            .container {
                padding: 15px;
            }
            
            .todo-form {
                flex-direction: column;
            }
            
            .todo-form input {
                margin-bottom: 10px;
            }
            
            .todo-form button {
                margin-left: 0;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>📋 待办事项清单</h1>
        
        <!-- 添加任务的表单 -->
        <!-- action="/add" 表示提交到/add路由 -->
        <!-- method="POST" 使用POST方法提交 -->
        <form class="todo-form" action="/add" method="POST">
            <input type="text" name="task" placeholder="添加新任务..." required>
            <button type="submit">添加任务</button>
        </form>
        
        <!-- 任务统计信息 -->
        <!-- 使用Jinja2模板变量显示统计 -->
        <div class="todo-stats">
            <span class="total-tasks">总任务: {{ todos|length }}</span>
            <span class="completed-tasks">已完成: {{ completed }}</span>
            <span class="pending-tasks">未完成: {{ not_completed }}</span>
        </div>
        
        <!-- 任务列表 -->
        <ul class="todo-list">
            <!-- 遍历待办事项 -->
            {% for todo in todos %}
            <!-- 根据任务状态添加completed类 -->
            <li class="todo-item {% if todo.done %}completed{% endif %}">
                <!-- 切换状态链接 -->
                <a href="{{ url_for('toggle_todo', index=loop.index0) }}">
                    <!-- 根据状态显示复选框 -->
                    <input type="checkbox" class="todo-checkbox" {% if todo.done %}checked{% endif %}>
                </a>
                <!-- 任务内容 -->
                <span class="todo-text">{{ todo.task }}</span>
                <!-- 删除任务链接 -->
                <a href="{{ url_for('delete_todo', index=loop.index0) }}" class="delete-btn">🗑️</a>
            </li>
            <!-- 如果没有任务 -->
            {% else %}
            <li class="todo-item">
                <span class="todo-text" style="text-align: center; width: 100%;">暂无任务,添加一个吧!</span>
            </li>
            {% endfor %}
        </ul>
        
        <!-- 清除已完成任务按钮(只在有完成的任务时显示) -->
        {% if completed > 0 %}
        <button class="clear-btn" onclick="location.href='{{ url_for('clear_completed') }}'">
            清除已完成任务
        </button>
        {% endif %}
    </div>
</body>
</html>

关键点解析:

  1. Jinja2模板引擎

    • {{ variable }}:输出变量值

    • {% for ... %}:循环结构

    • {% if ... %}:条件判断

    • loop.index0:当前循环索引(从0开始)

  2. 动态内容生成

    • 后端传入todoscompletednot_completed等变量

    • 模板根据这些数据动态生成HTML

  3. URL生成

    • url_for('函数名'):生成对应路由的URL

    • url_for('toggle_todo', index=loop.index0):生成带参数的URL

  4. 响应式设计

    • 使用CSS媒体查询适配不同屏幕尺寸

    • 移动端优化布局

  5. 用户交互元素

    • 表单提交:添加新任务

    • 链接点击:切换状态、删除任务

    • 按钮点击:清除已完成任务

四、工作流程解析

1. 用户访问主页 (GET /)

用户请求 -> Flask路由(index函数) -> 加载数据 -> 渲染模板 -> 返回HTML

2. 用户添加任务 (POST /add)

表单提交 -> Flask路由(add_todo函数) -> 处理数据 -> 保存到文件 -> 重定向到主页

3. 用户切换任务状态 (GET /toggle/<index>)

点击链接 -> Flask路由(toggle_todo函数) -> 修改状态 -> 保存数据 -> 重定向

4. 用户删除任务 (GET /delete/<index>)

点击删除图标 -> Flask路由(delete_todo函数) -> 删除任务 -> 保存数据 -> 重定向

5. 用户清除已完成任务 (GET /clear)

点击按钮 -> Flask路由(clear_completed函数) -> 过滤任务 -> 保存数据 -> 重定向

运行结果:

打开网址网页版待办事项

注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!


网站公告

今日签到

点亮在社区的每一天
去签到