源代码:

app.py

from flask import Flask, render_template, request, redirect, url_for, jsonify
import json
import osapp = 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) - completedreturn 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函数) -> 过滤任务 -> 保存数据 -> 重定向

运行结果:

打开网址网页版待办事项

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917806.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917806.shtml
英文地址,请注明出处:http://en.pswp.cn/news/917806.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧养老破局:科技如何让“老有所养”变成“老有优养”?

随着人口老龄化加剧&#xff0c;“养老”成了社会关注的焦点。传统养老往往停留在“有地方住、有人照顾”的基础需求&#xff0c;而智慧养老则通过科技与人文的结合&#xff0c;让老年人的生活从“老有所养”升级到“老有优养”。不仅活得安心&#xff0c;更能活得有尊严、有质…

自学嵌入式 day45 ARM体系架构

一、SOCRAM&#xff1a;随机访问存储器&#xff0c;存放随机变量&#xff0c;掉电数据丢失ROM&#xff1a;只读存储器&#xff0c;存放单片机的程序、指令&#xff0c;掉电数据不丢失注&#xff1a;1、冯诺依曼架构中将数据与指令存放在同一存储器中2、哈佛架构是将数据与指令存…

HTML应用指南:利用GET请求获取全国OPPO官方授权体验店门店位置信息

本篇文章将利用GET请求从OPPO官方网站或公开接口中获取官方授权体验店的分布信息&#xff0c;并通过Python编程语言中的requests库来实现HTTP请求&#xff0c;从而提取详细的门店位置数据。随着OPPO品牌的发展和市场布局的扩展&#xff0c;其官方授权体验店已经遍布全国各大城市…

Self-RAG:基于自我反思的检索增强生成框架技术解析

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 一、核心定义与原始论文 Self-RAG&#xff08;Self-Reflective Retri…

【YOLOv8改进 - C2f融合】C2f融合DBlock(Decoder Block):解码器块,去模糊和提升图像清晰度

YOLOv8目标检测创新改进与实战案例专栏 专栏目录: YOLOv8有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例 专栏链接: YOLOv8基础解析+创新改进+实战案例 文章目录 YOLOv8目标检测创新改进与实战案例专栏 介绍 摘要 文…

LLamafactory是什么?

LLamaFactory是一个专注于大型语言模型&#xff08;LLM&#xff09;训练、微调和部署的开源工具平台&#xff0c;旨在简化大模型的应用开发流程。‌1.核心功能与特点‌LlamaFactory&#xff08;全称Large Language Model Factory&#xff09;作为一站式AI开发工具平台&#xff…

Element Plus编辑表格时的页面回显(scope)

1、前提&#xff1a;自定义列模版(把id作为参数&#xff0c;传递到调用的edit函数里)<template #default"scope"><el-button type"primary" size"small" click"edit(scope.row.id)"><el-icon><EditPen /><…

河南萌新联赛2025第四场-河南大学

今天又是坐牢的一次比赛&#xff0c;恭喜获得本次比赛称号&#xff1a;挂机王&#xff0c;一个签到题能卡住两个小时&#xff0c;这两个小时简直坐的我怀疑人生&#xff0c;实在是找不出哪里错了&#xff0c;后来快结束的时候才发现少了一个等于号&#xff0c;也不至于连签到题…

【Excel】通过Index函数向下拖动单元格并【重复引用/循环引用】数据源

文章目录CASE1: 列数据源&#xff0c;向下拖动&#xff0c;每个单元重复N次步骤1&#xff1a;基本的INDEX函数步骤2&#xff1a;添加行号计算步骤3&#xff1a;添加绝对引用以便拖动CASE2:列数据源&#xff0c;向下拖动&#xff0c;每个单元重复1次&#xff0c;周而复始步骤1&a…

潜行者2:切尔诺贝利之心 全DLC 送修改器(S2HOC)免安装中文版

网盘链接&#xff1a; 潜行者2&#xff1a;切尔诺贝利之心 免安装中文版 名称&#xff1a;潜行者2&#xff1a;切尔诺贝利之心 全DLC 送修改器&#xff08;S2HOC&#xff09;免安装中文版 描述&#xff1a; 探索传奇的《潜行者》世界&#xff0c;同时体验&#xff1a; 融合…

系统运维之LiveCD详解

基本概念LiveCD是一个包含完整可运行操作系统的光盘映像&#xff0c;能够在不影响主机系统的情况下启动计算机。工作原理系统从LiveCD介质启动 将必要文件加载到内存中运行 通常使用RAM磁盘作为临时文件系统 关机后所有更改默认不保存&#xff08;除非特别配置&#xff0…

达梦分布式集群DPC_分布式任务执行拆分流程_yxy

达梦分布式集群DPC_分布式执行计划执行拆分流程 1 DPC任务拆分原理 1.1 分布式架构思想 1.2 DPC如何实现任务拆分? 2 DPC任务拆分完整示例 2.1 单表查询 2.1.1 创建分区表,存储在不同BP上 2.1.2 生成sql的最佳执行计划 2.1.3 代码生成并执行、拆分 2.1.3.1 任务拆分步骤 2.1.…

怎么免费建立自己的网站步骤

以下是免费建立个人网站的详细步骤&#xff0c;结合多种方案和工具推荐&#xff1a; 一、零基础快速建站方案 ‌选择免费建站平台‌ PageAdmin CMS‌&#xff1a; 1、提供开源模板&#xff0c;模板可以自定义界面和风格&#xff0c;同时支持原创设计和定制。 2、后台支持自定义…

使用ASIWebPageRequest库编写Objective-C下载器程序

全文目录&#xff1a;开篇语前言为什么选择ASIWebPageRequest&#xff1f;安装ASIWebPageRequest库编写下载器程序1. 导入必要的库2. 创建下载任务3. 设置下载保存路径4. 发起下载请求5. 更新下载进度6. 处理下载完成7. 处理下载失败完整代码示例8. 运行程序总结文末开篇语 哈喽…

mathtype加载项搞崩了word(上)

一、Mathtype更新后word异常 在mathtype更新后&#xff0c;打开word文件时一直报宏的错&#xff1a; 点击“取消”&#xff1a; 点击“确定”&#xff1a; 点击“确定”&#xff1a; 点击“确定”&#xff1a; 还有一堆小弹窗&#xff0c;最后还是能打开word文件&#xff1a; …

算法入门第一篇:算法核心:复杂度分析与数组基础

引言&#xff1a;为什么需要学习算法&#xff1f; 你可能也发现&#xff0c;即使是社招&#xff0c;面试官也时不时会抛出几道算法题&#xff0c;从简单的反转链表到复杂的动态规划。这常常让人感到困惑&#xff1a;我一个做游戏开发的&#xff0c;写好 Unity 的 C# 代码&…

从“听指令”到“当参谋”,阿里云AnalyticDB GraphRAG如何让AI开窍

01、背景 在智能客服与医疗问诊领域&#xff0c;用户模糊描述导致的多轮对话断裂与语义关联缺失&#xff0c;长期阻碍决策效率提升。传统 RAG 技术面临双重困境&#xff1a; 单轮检索局限&#xff1a;当用户仅反馈“空调制冷效果差”、“持续发热三天”等模糊信息时&#xff…

javascript常用实例

常见字符串操作字符串反转const reversed hello.split().reverse().join(); console.log(reversed); // olleh检查回文字符串function isPalindrome(str) {return str str.split().reverse().join(); }数组处理方法数组去重const unique [...new Set([1, 2, 2, 3])]; // [1,…

RK3568下用 Qt Charts 实现曲线数据展示

实际效果: 在工业监控、智能家居等场景中,实时数据可视化是核心需求之一。本文将介绍如何使用 Qt5 的 Charts 模块,快速实现一个支持温度、湿度、大气压和噪声四个参数的实时监测系统,包含曲线动态绘制、坐标轴自适应、多窗口布局等实用功能。 项目背景与目标 环境参数监…