ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Flask+SQLite实战:从零搭建Web任务管理系统全流程复盘

Flask+SQLite实战:从零搭建Web任务管理系统全流程复盘 在开发圈里经常会有这样一种体会一开始以为只是一个小工具结果从技术选型、环境搭建、页面交互到运行验证每一步都要踩几个坑。本文通过一个代号为“雷丘”的练手项目完整复盘一个小系统从零到可运行的全过程并整理常见问题与工程化建议。如果你正在做自己的第一个 Web 练手项目或者想把零散知识点串成一条完整流程这篇文章应该能帮你少走一些弯路。1. 背景与核心概念1.1 一个“玩一把”的小项目为什么会“真不好混”很多开发者都经历过类似的场景看到一个框架或者新工具心里想的是“这也不难我花半天跑通一个 Demo 就行”结果真正动手之后才发现 Demo 和完整项目之间隔着很多隐藏问题。问题往往不会出现在“主流程”上而是出现在环境的细节里Python 版本和依赖库版本不匹配。虚拟环境没有激活pip 安装到了全局环境。SQLite 建表语句写好后没有先初始化数据库就直接启动应用。Flask 路由写对了但前端表单提交的字段名和后端接收的参数不一致。页面能打开但一旦用到日期、中文、编码相关的功能就出现各种乱码。一个看起来“奢侈”的小项目之所以最后让人感叹“真不好混”通常不是因为某个单独的知识点很难而是因为整个链路太长需求设计、数据建模、后端接口、前端页面、联调验证每一环都必须对得上。任何一个环节出现偏差都会以“报错”或“结果不符合预期”的形式反馈回来。本文要做的是把这条完整链路走一遍。我们用一个简单的“任务管理系统”作为示例项目代号就叫“雷丘”。系统功能不复杂但足够覆盖 Flask Web 开发中最常见的技术点路由、模板、表单、数据库、增删改查、浏览器重定向。1.2 技术选型为什么用 Flask SQLite在选择技术栈时有一个基本原则练手项目优先选择简单、透明、容易排错的技术而不是一上来就上全家桶。“雷丘”项目选择了 Flask 和 SQLite原因如下组件选型理由Web 框架Flask轻量、路由写法直观、模板系统成熟适合快速搭建单体小应用数据库SQLitePython 内置 sqlite3 模块零配置文件适合单机和个人项目前端Jinja2 模板 原生 HTML/CSS不引入前后端分离减少构建工具带来的额外复杂度依赖管理pip requirements.txt简单清晰方便复现环境这套组合非常适合理解 Web 应用的基本原理浏览器发送 HTTP 请求Flask 根据路由找到处理函数处理函数读写 SQLite 数据库再把数据交给模板渲染成 HTML 返回给浏览器。整个流程没有中间层遮挡每一个环节都能单独验证。1.3 项目落地的通用流程在正式写代码之前先梳理一个小型 Web 项目的通用流程。之后无论你换什么框架这套流程都可以复用需求分析明确系统要解决什么问题包含哪些核心功能。技术选型确定语言、框架、数据库、部署方式。项目结构设计划分目录和模块避免所有代码堆在一个文件里。数据库设计设计表结构、字段类型、默认值、索引。后端开发实现路由和处理函数编写数据访问层。前端开发编写页面模板提交表单并展示数据。联调验证从用户视角完整走一遍所有功能。异常处理与加固补充参数校验、日志、异常提示。部署与后续维护设置运行环境规划备份和升级方式。很多人做练手项目只关注第 5 步和第 6 步结果到了第 7 步才意识到前面留下了多少隐患。这也是本文想重点强调的部分。2. 环境准备与版本说明2.1 版本需要结合实际环境调整技术栈版本更新比较快不同环境下表现可能不同。本文示例以常见的 Python 3 环境为例重点演示配置思路和实现方法不锁定某个具体小版本。建议准备好以下环境操作系统Windows 10/11、macOS 或主流 Linux 发行版均可。Python3.8 及以上版本建议使用 3.10 左右的稳定版本。Flask通过 pip 安装建议使用 2.x 版本具体以安装时官方发布情况为准。数据库SQLitePython 内置 sqlite3无需额外安装。浏览器Chrome、Edge 或 Firefox 均可。编辑器VS Code、PyCharm 等任意支持 Python 的编辑器。如果你的环境版本和本文不一致不需要紧张核心代码逻辑基本通用。遇到报错时优先检查版本兼容性。2.2 项目目录结构本章节后面的所有代码都基于下面的目录结构leiqiu-task/ ├── app.py # Flask 主程序定义路由 ├── models.py # 数据库操作模块 ├── requirements.txt # 依赖清单 ├── static/ │ └── style.css # 页面样式 └── templates/ └── index.html # 任务列表页面在项目开发中把代码拆分成不同模块不是“为了分而分”而是为了让每一部分职责清晰app.py只负责路由和 HTTP 逻辑。models.py只负责读写数据库。templates/index.html只负责页面展示。static/style.css只负责美化界面。这样做的直接好处是将来如果要更换数据库只需要修改models.py如果要调整页面样式不需要碰后端代码如果某个路由报错可以快速定位是处理函数的问题还是模板的问题。2.3 创建虚拟环境并安装依赖为了避免项目依赖污染全局 Python 环境强烈建议使用虚拟环境。下面是命令示例。在 Windows PowerShell 或 CMD 中执行mkdir leiqiu-task cd leiqiu-task python -m venv venv venv\Scripts\activate在 macOS 或 Linux 中执行mkdir leiqiu-task cd leiqiu-task python3 -m venv venv source venv/bin/activate激活虚拟环境后命令行提示符会多出(venv)前缀。然后创建requirements.txt文件内容如下flask2.0安装依赖pip install -r requirements.txt安装完成后可以用下面的命令确认 Flask 是否可用python -c import flask; print(flask.__version__)如果输出版本号说明依赖安装成功。3. 核心原理拆解在进入完整项目之前先拆解几个核心概念。理解这些原理后遇到报错时你会更容易判断是哪里出了问题。3.1 Flask 的路由与视图函数Flask 的核心机制是“路由 视图函数”。所谓路由就是 URL 路径与处理函数之间的映射关系。看一个最小示例from flask import Flask app Flask(__name__) app.route(/) def index(): return Hello, Leiqiu! if __name__ __main__: app.run()当浏览器访问http://127.0.0.1:5000/时Flask 会匹配到根路径/然后执行index()函数把返回的字符串发送给浏览器。这里有几个初学者容易忽略的点app.route(/)是装饰器它把index函数注册为根路径的处理器。函数返回值可以是字符串也可以是 HTML、JSON 或重定向响应。app.run()默认启动在本地 5000 端口监听地址为127.0.0.1。3.2 SQLite 与数据持久化SQLite 是一个嵌入式关系型数据库数据保存在一个普通文件中非常适合单机应用。在“雷丘”项目中我们会使用 Python 内置的sqlite3模块操作数据库。核心步骤如下建立连接。获取游标。执行 SQL 语句。提交事务。关闭连接。为了避免每个函数都重复这段样板代码我们会把数据库连接和操作封装到models.py中。这里要注意一个概念connection 连接和cursor 游标不是同一个东西。连接代表与数据库文件之间的会话游标用于执行 SQL 并获取结果。在代码中连接负责提交和回滚游标负责具体执行。3.3 模板渲染与表单交互Flask 默认使用 Jinja2 模板引擎。模板文件放在templates目录下后端通过render_template函数把数据传给模板。模板中的{{ 变量 }}用于输出变量{% for %}和{% if %}用于控制逻辑。表单交互的流程是浏览器通过 GET 请求打开页面。用户在表单中输入内容点击提交按钮。浏览器通过 POST 请求把表单数据发送到后端。Flask 在视图函数中通过request.form.get(字段名)获取数据。后端处理完数据后通过redirect让浏览器重新访问某个页面。这一步看似简单但很容易出错。最常见的错误是表单中name属性写的是task_title后端读取的却是title导致数据一直为空。3.4 重定向与请求上下文在用户完成“新增任务”或“删除任务”操作后如果直接把操作结果渲染成一个新页面浏览器地址栏会停留在 POST 请求的 URL 上。这时候用户刷新页面浏览器可能会再次提交上一次的表单造成重复数据。更规范的做法是处理完 POST 请求后返回一个重定向响应让浏览器重新 GET 请求列表页面。这种方式叫做 POST/Redirect/GET 模式是 Web 开发中非常经典的做法。from flask import redirect, url_for app.route(/add, methods[POST]) def add(): # 保存任务... return redirect(url_for(index))这样既避免了重复提交也保证了浏览器的 URL 不会停留在一个 POST 接口上。4. 完整实战雷丘任务管理系统4.1 需求分析“雷丘”项目要做成一个极简任务管理系统满足以下需求用户可以在页面上输入任务名称。可以设置任务优先级高、中、低。可以设置截止日期。新增的任务显示在列表中。任务可以标记为“已完成”。任务可以删除。列表支持按“全部 / 待办 / 已完成”筛选。这些功能虽然简单但已经覆盖了 Web 开发最常用的“增删改查”中的增、删、改、查四种操作。4.2 数据库模型设计任务表tasks的字段设计如下字段类型说明idINTEGER PRIMARY KEY AUTOINCREMENT主键自增titleTEXT NOT NULL任务名称priorityINTEGER DEFAULT 3优先级1 高2 中3 低due_dateTEXT截止日期格式 YYYY-MM-DDdoneINTEGER DEFAULT 0是否完成0 未完成1 已完成created_atTEXT创建时间默认当前本地时间设计时要注意几点done字段使用 INTEGER 类型0 表示 False1 表示 True这是 SQLite 的常见做法。due_date使用 TEXT 类型保存类似2025-06-01的日期字符串简单直观。created_at使用 SQLite 的datetime(now, localtime)生成本地时间避免 UTC 时间与本地时间不一致的问题。4.3 编写 models.py文件路径leiqiu-task/models.pyimport sqlite3 from contextlib import closing DB_PATH leiqiu.db def get_connection(): 获取数据库连接设置行工厂为 sqlite3.Row conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): 初始化数据库表结构 with closing(get_connection()) as conn, closing(conn.cursor()) as cur: cur.execute( CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, priority INTEGER DEFAULT 3, due_date TEXT, done INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime(now, localtime)) ) ) conn.commit() def add_task(title, priority, due_date): 新增任务 with closing(get_connection()) as conn: conn.execute( INSERT INTO tasks (title, priority, due_date) VALUES (?, ?, ?), (title, priority, due_date), ) conn.commit() def list_tasks(statusall): 查询任务列表支持 all / undone / done 三种筛选条件 sql SELECT id, title, priority, due_date, done, created_at FROM tasks params () if status undone: sql WHERE done 0 elif status done: sql WHERE done 1 sql ORDER BY done ASC, priority ASC, id DESC with closing(get_connection()) as conn: rows conn.execute(sql, params).fetchall() return [dict(row) for row in rows] def update_task_done(task_id, done): 更新任务完成状态 with closing(get_connection()) as conn: conn.execute(UPDATE tasks SET done ? WHERE id ?, (done, task_id)) conn.commit() def delete_task(task_id): 删除任务 with closing(get_connection()) as conn: conn.execute(DELETE FROM tasks WHERE id ?, (task_id,)) conn.commit()这里有几个细节值得说明contextlib.closing可以确保连接和游标在使用后自动关闭。使用sqlite3.Row后查询结果可以通过字段名访问也可以转换为字典方便模板渲染。SQL 中的?是占位符用来防止 SQL 注入。永远不要用字符串拼接的方式拼接用户输入。ORDER BY done ASC会让未完成任务排在上面priority ASC让高优先级排在前id DESC则让新任务展示在前。4.4 编写 app.py文件路径leiqiu-task/app.pyfrom flask import Flask, render_template, request, redirect, url_for import models app Flask(__name__) app.route(/) def index(): 任务列表页 status request.args.get(status, all) tasks models.list_tasks(status) return render_template(index.html, taskstasks, statusstatus) app.route(/add, methods[POST]) def add(): 新增任务 title request.form.get(title, ).strip() priority request.form.get(priority, 3) due_date request.form.get(due_date, ) if not title: return redirect(url_for(index)) models.add_task(title, priority, due_date) return redirect(url_for(index)) app.route(/done/int:task_id) def done(task_id): 标记任务完成 models.update_task_done(task_id, 1) return redirect(request.referrer or url_for(index)) app.route(/delete/int:task_id) def delete(task_id): 删除任务 models.delete_task(task_id) return redirect(request.referrer or url_for(index)) if __name__ __main__: models.init_db() app.run(debugTrue)路由部分说明request.args.get(status, all)从 URL 查询参数中读取筛选条件例如/?statusundone。request.form.get(title, )读取表单中nametitle的值。request.referrer表示来源页面 URL用户点击“完成”后可以回到之前所在的列表体验更好。主入口启动前先执行models.init_db()确保数据库表一定存在。4.5 编写前端模板文件路径leiqiu-task/templates/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title雷丘任务管理/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} /head body div classcontainer h1雷丘任务管理/h1 form classadd-form methodpost action{{ url_for(add) }} input typetext nametitle placeholder输入任务名称 required select namepriority option value1高优先级/option option value2中优先级/option option value3 selected低优先级/option /select input typedate namedue_date button typesubmit新增任务/button /form div classfilters a href{{ url_for(index, statusall) }} class{{ active if status all }}全部/a a href{{ url_for(index, statusundone) }} class{{ active if status undone }}待办/a a href{{ url_for(index, statusdone) }} class{{ active if status done }}已完成/a /div ul classtask-list {% for task in tasks %} li class{{ done-item if task.done else }} div classtask-info span classtask-title{{ task.title }}/span span classtask-meta 优先级{{ task.priority }} | 截止{{ task.due_date or 无 }} | 创建{{ task.created_at }} /span /div div classtask-actions {% if not task.done %} a href{{ url_for(done, task_idtask.id) }}完成/a {% endif %} a classdelete href{{ url_for(delete, task_idtask.id) }}删除/a /div /li {% else %} li classempty暂无任务先添加一个吧。/li {% endfor %} /ul /div /body /html文件路径leiqiu-task/static/style.cssbody { font-family: PingFang SC, Microsoft YaHei, sans-serif; background: #f5f6f8; margin: 0; padding: 20px; } .container { max-width: 720px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .add-form { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; } .add-form input[typetext] { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; } .filters { margin-bottom: 16px; } .filters a { margin-right: 12px; color: #333; text-decoration: none; padding: 4px 8px; border-radius: 4px; } .filters a.active { background: #3370ff; color: #fff; } .task-list { list-style: none; padding: 0; } .task-list li { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding: 12px 0; } .task-info { display: flex; flex-direction: column; } .task-title { font-size: 16px; font-weight: 500; } .task-meta { font-size: 12px; color: #888; margin-top: 4px; } .done-item .task-title { text-decoration: line-through; color: #999; } .task-actions a { margin-left: 12px; color: #3370ff; text-decoration: none; } .task-actions a.delete { color: #e5484d; } .empty { color: #999; text-align: center; justify-content: center !important; }4.6 启动与运行确保已经激活虚拟环境然后执行python app.py启动成功后会看到类似下面的输出* Serving Flask app app * Debug mode: on * Running on http://127.0.0.1:5000在浏览器中打开http://127.0.0.1:5000即可访问“雷丘任务管理系统”。运行后的核心功能演示流程如下先在输入框中填写任务名称例如“完成 Flask 教程阅读”。选择优先级“高”。选择截止日期。点击“新增任务”。列表页会出现新任务并且状态为“待办”。点击“完成”任务文字会变成删除线样式。点击“删除”任务从列表中消失。这里需要强调一个容易踩坑的点浏览器地址栏里的 URL 结构会随着筛选条件变化。例如点击“待办”后地址会变成http://127.0.0.1:5000/?statusundone。在这个页面点击“完成”时request.referrer会带着statusundone参数所以页面仍然停留在“待办”筛选视图不会跳回“全部”。这个细节能让体验更完整也是很多人没有注意到的。5. 常见问题与排查思路5.1 常见报错整理下面表格整理了“雷丘”项目以及类似 Flask SQLite 小项目中容易遇到的问题可以作为排查清单使用。问题现象常见原因解决思路ModuleNotFoundError: No module named flask未安装 Flask或安装到了别的 Python 环境激活虚拟环境后执行pip install -r requirements.txt启动时提示端口被占用5000 端口已被其他程序占用修改app.run(port5001)或关闭占用进程页面能打开但新增任务后列表没有变化表单name属性与后端读取字段不一致检查前端name和后端request.form.get的参数名是否一致中文字符显示为乱码页面未声明 UTF-8 编码在 HTML 中添加meta charsetUTF-8刷新页面时会重复提交任务表单提交后直接渲染页面没有重定向使用 POST/Redirect/GET 模式提交后redirect到列表页修改了代码但页面没有变化Flask debug 模式未开启或浏览器缓存开启debugTrue或强制刷新浏览器缓存数据库文件被锁定写入报错多个进程或连接同时写入 SQLite避免多进程写入规范使用with closing自动释放连接点击“完成”后跳转地址不对request.referrer可能为空或包含无效地址使用request.referrer or url_for(index)做兜底时间比本地时间少了 8 小时SQLite 默认使用 UTC 时间建表时使用datetime(now, localtime)5.2 典型排错过程数据库表不存在的报错如果你在没有调用init_db()的情况下直接访问页面执行查询时会出现类似下面的报错sqlite3.OperationalError: no such table: tasks遇到这个错误时按下面的顺序排查检查项目根目录下是否生成了leiqiu.db文件。如果没有生成说明init_db()没有执行。检查app.py的启动入口是否写了models.init_db()。如果数据库文件存在但仍然报错可能是数据库路径不在当前工作目录下。可以打印DB_PATH的绝对路径确认。解决方案其实很简单在应用启动前先初始化表结构。如果表已经存在CREATE TABLE IF NOT EXISTS不会重复创建也不会影响已有数据。5.3 典型排错过程POST 表单数据为空另一个高频问题是点击提交后任务没有保存列表仍然为空。排查顺序如下打开浏览器开发者工具选择 Network 面板。重新提交表单观察 POST 请求的状态码和 Form Data。确认 Form Data 中是否有title字段。如果没有说明前端的name属性写错了。如果有检查后端是否读取了正确的字段名。这类问题大多不是框架问题而是“前端字段名”和“后端读取名”不一致。把字段名统一成同一个命名风格例如都使用task_title能减少此类问题。6. 工程化改进与最佳实践6.1 使用配置对象管理环境信息在上面的示例中DB_PATH是直接写在models.py里的。对于练手项目这样可以但一旦项目要部署到不同环境路径、端口、密钥等信息都会变化。更规范的做法是把配置集中到一个config.py文件中。import os class Config: BASE_DIR os.path.abspath(os.path.dirname(__file__)) DB_PATH os.path.join(BASE_DIR, leiqiu.db) SECRET_KEY os.environ.get(SECRET_KEY, dev-secret-key)然后在models.py中引用from config import Config DB_PATH Config.DB_PATH这样做的好处是配置和业务代码分离。环境变量可以覆盖默认值。换一台机器后不需要改业务代码只需要调整配置。6.2 参数校验与异常处理当前示例中如果用户提交了一个空标题我们直接重定向回列表页。这样做虽然不会报错但用户不知道发生了什么。更友好的做法是把错误信息传给模板在页面上明确提示。另外数据库操作也可能异常例如磁盘已满、数据库文件损坏等。在正式项目中应该对关键操作添加异常处理def add_task(title, priority, due_date): try: with closing(get_connection()) as conn: conn.execute( INSERT INTO tasks (title, priority, due_date) VALUES (?, ?, ?), (title, priority, due_date), ) conn.commit() except sqlite3.Error as exc: # 真实项目中这里应该记录日志 raise RuntimeError(保存任务失败) from exc异常处理的目的是“不要把原始异常直接抛给用户”而是转换成更友好的提示同时保留日志信息供开发者排查。6.3 日志记录调试阶段的print在小型项目中够用但在正式环境中应该使用logging模块记录关键操作。import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) def add_task(title, priority, due_date): logger.info(新增任务%s优先级%s截止日期%s, title, priority, due_date) # 后续代码日志的价值在于当问题发生时你可以回溯“用户做了什么操作”“系统在哪里失败”。特别是涉及删除、更新这类不可逆操作时日志是事后排查的重要依据。6.4 数据库备份与迁移SQLite 数据库是单个文件备份非常简单。最简单的备份方式是复制文件。在 Windows 下可以手动复制在 Linux 下可以使用命令cp leiqiu.db leiqiu_backup_20250601.db如果需要定时备份可以写一个简单的脚本通过系统定时任务执行。对于生产环境建议在备份前先确认没有正在执行的写入操作避免复制到不一致的状态。关于表结构变更SQLite 的表结构修改能力有限。常见的做法是导出数据。创建新表。迁移数据。删除旧表。在动手前一定要先备份数据库文件。6.5 部署与生产环境注意事项app.run(debugTrue)只适合本地开发。生产环境绝不能使用 debug 模式原因有两点debug 模式会在出错时展示堆栈信息可能泄露代码路径和系统信息。debug 模式自带调试器存在远程执行代码的风险。在 Linux 服务器上更常见的部署方式是使用 Gunicorn 或 uWSGI Nginx。以 Gunicorn 为例gunicorn -w 2 -b 127.0.0.1:8000 app:app需要注意的是Gunicorn 并非 Windows 官方支持如果你在 Windows 上部署可以考虑 Waitress 或其他 WSGI 服务器。无论采用哪种方式都要记得关闭 debug 模式。设置安全的SECRET_KEY。定时备份 SQLite 数据库。观察访问日志和错误日志。6.6 自动化测试与版本管理项目虽然小但“先写测试”的习惯可以慢慢培养。下面是一个使用pytest测试add_task和list_tasks的简单示例import models def test_add_and_list_task(tmp_path, monkeypatch): # 使用临时数据库文件避免污染真实数据 monkeypatch.setattr(models, DB_PATH, str(tmp_path / test.db)) models.init_db() models.add_task(测试任务, 1, 2025-12-31) tasks models.list_tasks(all) assert len(tasks) 1 assert tasks[0][title] 测试任务版本管理方面建议在项目一开始就使用 Gitgit init git add . git commit -m init leiqiu task project同时在.gitignore中忽略虚拟环境和数据库文件venv/ __pycache__/ *.pyc leiqiu.db数据库文件不应该提交到代码仓库因为它是运行时的数据资产而不是代码。7. 总结与下一步学习路线“雷丘”项目虽然只是一个任务管理小系统但它把 Web 开发中非常重要的几个环节串了起来需求梳理、环境搭建、数据库设计、后端路由、前端模板、表单交互、联调排错、工程化加固。你从这篇文章里应该至少掌握了以下几点为什么小项目也会“真不好混”以及如何通过工程化思维降低复杂度。Flask 路由、视图函数、模板渲染、POST/Redirect/GET 模式的基本用法。SQLite 的建表、增删改查、占位符防注入、连接管理。前后端字段名不一致、数据库表缺失、端口占用等高频问题的排查方法。配置分离、日志、备份、关闭 debug、自动化测试等生产环境注意事项。下一步可以继续学习的内容也比较清晰把“雷丘”项目改造成使用 SQLAlchemy ORM体验代码层面对比。增加用户登录和权限控制理解 Session 与 Cookie。把前端从原生 HTML 升级为 Bootstrap增加交互体验。把 SQLite 换成 MySQL理解数据库差异和连接池。尝试使用 Gunicorn Nginx 部署到云服务器。增加 RESTful API 接口配合 Postman 做接口测试。动手实践是最好的学习方式。建议你不要满足于复现代码而是试着给“雷丘”项目加一个“编辑任务名称”的功能或者加一个“按关键字搜索”的输入框。只要你真正把这些功能从头到尾做完你就会发现下一次再玩类似的“奢侈项目”不会再觉得“真不好混”了。
返回列表