首页 / 资讯中心 / 文章详情

Python+MySQL+Tkinter图书馆管理系统源码实战:从跑通到答辩

Python+MySQL+Tkinter图书馆管理系统源码实战:从跑通到答辩 ★ FEATURED ARTICLE
简介这是一套基于Python与MySQL的图形化图书馆管理系统完整源码面向Python初学者、数据库开发者以及需要完成课设或毕设的高校学生。系统涵盖图书管理、读者管理、借阅归还、权限控制与统计报表等核心模块后端使用Python处理业务逻辑通过pymysql等库连接MySQL数据库前端采用HTML、CSS、JavaScript配合Bootstrap、Layui等框架搭建直观交互界面并包含数据库表设计、查询与更新逻辑、异常捕获与日志记录等关键实现。压缩包共222个文件以13个Python源码、26个HTML页面、37个JS脚本、75个GIF演示动图为主另有CSS样式、字体图标、SQLite数据库文件及配置文件整体大小9.53MB目录结构清晰便于直接运行和二次开发。目前已有3427人学习下载对于希望快速掌握GUI程序设计与MySQL操作、理解图书馆业务流程的开发者而言是一份实用且完整的参考资料也可直接用于相关课程项目或毕业设计。1. PythonMySQL 图形化界面图书馆管理系统源码拿来跑通是入门改到能答辩才是目的PythonMySQL 图形化界面图书馆管理系统源码听名字像是一个解压就能跑的打包项目实际拿到的包却多数要你先折腾数据库。更常见的翻车场景是界面能开但登录按钮一按就报连接失败或者图书列表是空的因为你根本没导入建库脚本。别再盯着代码找 bug 了先把环境跑通才是正路。这类源码包的完整形态是用 Tkinter 做桌面窗口、用 MySQL 存图书、读者、借阅数据把管书、借书、还书、查询串成一个闭环。它的价值不在「打开就能用」而在于你补全数据库配置后能看清一套桌面图书管理软件怎么从窗口布局到 SQL 查询首尾相连。最适合两类人一类是要交数据库课程设计或 Python 课程作业的学生一类是想快速理解「GUI 数据库」配套逻辑的开发者。与其急着点运行不如先把选型讲清楚因为后面你踩的坑多半出在这两层。2. 技术选型与架构为什么是 Tkinter MySQL而不是 PyQt SQLite很多网上流传的图书管理源码最初只是命令行程序后面被人套了一层薄薄的 Tkinter 界面就拿去交差。这不是贬义而是说明 Tkinter 和 MySQL 的组合有一个很现实的好处两者都在教学大纲里答辩时你既能讲「界面事件怎么绑定」也能讲「表结构怎么设计、事务怎么提交」知识点覆盖面比纯命令行项目好太多。换成 PyQt SQLite界面和数据库都能用但老师很可能问一句「为什么要用 PyQt它比 Tkinter 好在哪里」这个问题如果你没真正上手过 PyQt很容易答成背书。Tkinter 的选择逻辑里还有一个很现实的因素叫「依赖面」它是 Python 自带的标准库不需要额外下载几十 MB 的 Qt 运行库在机房或者新电脑上部署时只要装好 Python 和 pymysql多半就能直接跑。PyQt5 虽然控件更美观但打包出来的 exe 体积从几十 MB 起步还涉及 Qt 插件路径这种莫名的坑。对一个小型图书管理系统来说Tkinter 的 ttk 控件完全够用代价只是界面观感没那么现代。搞懂这两点后面对源码的改造才有底。下面我会把界面库的取舍边界、本机环境怎么准备、源码包怎么组织逐层拆开。2.1 图形界面库怎么选Tkinter 的取舍与边界Tkinter 能撑起图书管理系统的核心交互靠的是三个控件。Treeview 负责数据表格多列展示书名、作者、库存、状态配合showheadings还能隐藏首列那个默认空列Entry 和 Combobox 负责表单输入前者用于书名、作者这种自由文本后者用于选择图书分类这种枚举值messagebox 负责操作结果的弹窗提示比如「借书成功」或者「库存不足」。这些控件组合起来已经能覆盖图书管理九成以上的界面需求。但 Tkinter 的边界也很清楚它不适合做复杂的数据可视化。如果源码里打算展示「每月借阅量」折线图Tkinter 自带的 Canvas 没有现成图表控件常见做法是引入 matplotlib 的FigureCanvasTkAgg把 matplotlib 的 Figure 嵌入 Tk 窗口。这里的坑在于 matplotlib 的事件循环要和 Tk 主循环配合否则窗口刷新时会闪烁甚至卡住。另一个边界是自定义控件的成本想做网页后台那种侧边栏、卡片、圆角按钮Tkinter 要写大量样式代码效果仍然一般。我的建议是如果源码界面是「窗口 左侧按钮 右侧表格」这种传统布局Tkinter 够用真想做成产品级界面才需要换 PyQt 重写。对比点TkinterPyQt5安装成本Python 自带零额外安装需要 pip 安装 PyQt5依赖较多界面观感保守但可用现代化样式表成熟打包体积较小一般 30MB 内通常 50MB 起步学习曲线低事件绑定直观中等信号与槽机制需要适应适用场景课程设计、内部工具产品化、商用桌面应用选 Tkinter 的另一个理由是代码可读性。同一段借书逻辑Tkinter 的事件函数就是一个普通def do_borrow():直接绑在 Button 的 command 上PyQt5 里如果用信号与槽代码分层更多对刚接触桌面开发的人来说理解成本更高。对于拿到源码包要快速上手的人Tkinter 是阻力最小的路径。2.2 环境准备Python 版本、MySQL 版本与依赖安装顺序我建议的环境组合是 Windows 10/11 Python 3.1064 位 MySQL 8.0。Python 版本不用追最新部分第三方库对 3.13 的支持可能滞后MySQL 5.7 也可以用但 8.0 的默认认证插件caching_sha2_password会让老版本 pymysql 直接连不上。安装顺序上先装 MySQL 服务再装 pip 依赖最后导入建库脚本。# 1. 检查 Python 版本64 位优先 python --version # 2. 安装连接 MySQL 的驱动pymysql 是源码包里最常见的 pip install pymysql # 3. 连接 MySQL 8 时默认加密插件需要这个依赖否则可能报 not available pip install cryptography装完依赖后先不要直接跑源码。可以用下面这段最小连接脚本确认环境真的通。注意把 password 改成你自己的 MySQL root 密码database 先填mysql因为图书库还没有创建。import pymysql conn pymysql.connect( host127.0.0.1, # 本机用 127.0.0.1不要写 localhost避免 IPv6 解析慢 port3306, # MySQL 默认端口安装时改过就填改后的 userroot, password你的密码, databasemysql, # 系统库测试连接用 charsetutf8mb4, ) cur conn.cursor() cur.execute(SELECT VERSION()) print(cur.fetchone()) conn.close()这段脚本如果能在终端打印出类似(8.0.36,)的结果说明 Python 到 MySQL 的链路已经通了。三个参数最容易错password 必须和安装 MySQL 时设置的一致port 如果安装时改成 3307 而这里仍写 3306 会直接超时database 如果填不存在的库名会报Unknown database。之后源码包里的配置也要和这里的参数保持一致。还有一类情况是电脑上装了 MySQL 但忘了密码。常见做法是先用管理员权限停掉 MySQL 服务在my.ini的[mysqld]段加一行skip-grant-tables重启后用空密码登录并改密码改完一定要把这行删掉再重启。这个操作不难但容易忘记最关键的「改完密码后删掉跳过授权表的配置」否则数据库等于裸奔。如果你只是本地开发且源码包里自带建库脚本也可以创建一个专用账号比如lib_user只给这个账号library库的权限比一直用 root 更干净。2.3 源码包的项目结构认清每个目录再动手完整跑通之前先把结构弄清楚。一个典型的 PythonMySQL 图书馆管理系统源码包通常包含以下几类文件文件/目录作用大概率需要改main.py程序入口创建主窗口并启动界面否db_tool.py数据库连接与查询封装是数据库密码ui_admin.py主管理界面包含表格和按钮按需ui_login.py管理员登录窗口按需sql/library.sql建库、建表、初始化的 SQL 脚本否config.ini数据库 host、port、user、password 配置是requirements.txt依赖清单否拿到包后我的建议是不急着读代码而是按顺序做三件事第一用 MySQL 客户端执行sql/library.sql把数据库和表建出来第二打开config.ini或db_tool.py把密码改成自己的第三运行main.py如果窗口弹出来并能查询到数据说明已经跑通了。接下来再面对代码你会发现自己带着「已经知道结果是什么」的状态去读源码比从头读快得多。这里有一个容易忽略的小问题SQL 脚本的导入。如果源码包里的 sql 文件是 UTF-8 编码而 MySQL 命令行默认字符集不是 UTF-8导入时中文注释会变乱码。常见做法是用 source 命令导入前先执行SET NAMES utf8mb4;或者在 Workbench 里打开文件并设置编码后再 Run SQL Script。字符集问题我会在第四章专门讲因为这是全项目里最玄学也最常出问题的点。3. 数据库设计与初始化从建库脚本到图书借阅核心流程选型和环境搞定后接下来就是库里那张表长什么样的问题这也是源码包能不能跑起来的根本。图书管理业务本质是「图书表 读者表 借阅记录表」三张表外加一些为了查询方便而做的设计。如果表结构设计得乱后面界面再花哨也白搭。很多源码包跑不通不是因为 Python 代码有错而是建库脚本没执行或者执行了但表名和代码里对不上。这一章我按「建表 → 连接封装 → 借还书流程」的顺序走每一步都给出能直接抄的代码。你在源码里看到的内容可能有细微差别但核心结构和业务逻辑八九不离十。3.1 建库建表图书表、读者表、借阅表怎么设计才不被老师挑毛病先看一套最少但完整的建库建表脚本CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library; CREATE TABLE book ( bid INT AUTO_INCREMENT PRIMARY KEY COMMENT 图书编号, bname VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) DEFAULT NULL COMMENT 作者, publisher VARCHAR(80) DEFAULT NULL COMMENT 出版社, price DECIMAL(6,2) DEFAULT 0.00 COMMENT 价格, stock INT NOT NULL DEFAULT 0 COMMENT 当前可借库存, total INT NOT NULL DEFAULT 0 COMMENT 总册数, category VARCHAR(30) DEFAULT NULL COMMENT 分类 ) COMMENT图书表; CREATE TABLE reader ( rid INT AUTO_INCREMENT PRIMARY KEY COMMENT 读者编号, rname VARCHAR(50) NOT NULL COMMENT 姓名, rclass VARCHAR(50) DEFAULT NULL COMMENT 班级/部门, rphone VARCHAR(20) DEFAULT NULL COMMENT 联系电话 ) COMMENT读者表; CREATE TABLE borrow_record ( record_id INT AUTO_INCREMENT PRIMARY KEY, bid INT NOT NULL, rid INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在借 1已还 2逾期, FOREIGN KEY (bid) REFERENCES book(bid), FOREIGN KEY (rid) REFERENCES reader(rid), INDEX idx_borrow_status (status) ) COMMENT借阅记录表;这里的几个设计点写代码的人不一定会在源码注释里说明但你答辩时一定要能讲。第一bid用自增主键图书编号不要用「B001」这种字符串否则插入时还得自己生成编号自增省掉这一步。第二stock和total要分开同一本书可能有多个副本total记录这本图书总共进过多少册stock记录当前还剩多少可借还书时只做stock1total不动这样既能查可借量也能查馆藏量。第三借阅记录表里status用 TINYINT 而不是 VARCHAR 存文字省空间、索引友好显示的时候再在 Python 里映射成「在借 / 已还 / 逾期」。status字段还有一个容易踩坑的设计争议逾期状态可以不在写入时算而是每次查询时用due_date CURDATE() AND return_date IS NULL动态判断。这种方案省一个字段但如果补录历史还书日期动态判定会随日期变化。既然借阅表里已经有return_date我建议保留这个冗余字段数据写入时就把状态定好查询和统计都更直接。3.2 连接数据库把 pymysql 封装成公共模块避免到处重复源码包里如果每个界面文件都自己写一遍pymysql.connect改密码的时候你会想摔键盘。我一般会单独做一个db_tool.py把连接和通用查询封装好所有界面文件统一从它这里取连接# db_tool.py全项目统一从这里取连接 import pymysql DB_CONFIG { host: 127.0.0.1, port: 3306, user: root, password: 123456, database: library, charset: utf8mb4, } def get_conn(): conn pymysql.connect(**DB_CONFIG) # 让查询结果支持按键名访问例如 row[bname]比下标清晰 return conn def execute_query(sql, paramsNone): conn get_conn() try: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(sql, params) return cursor.fetchall() finally: conn.close() def execute_update(sql, paramsNone): conn get_conn() try: with conn.cursor() as cursor: cursor.execute(sql, params) conn.commit() return cursor.rowcount except Exception: conn.rollback() raise finally: conn.close()这里有两个设计决策值得注意。为什么每次调用都新建连接而不是全局维护一个连接对象小项目并发量低短连接足够全局连接对象一旦断掉重连逻辑写起来反而复杂。为什么不直接用默认游标而是用 DictCursor因为默认游标返回的是元组代码里写row[1]根本看不出取的是书名还是作者用字典游标后写row[bname]可读性好得多对新手也友好。在execute_update里conn.commit()放在try中任何一步异常都会走except触发rollback()然后重新抛出异常。调用方可以用rowcount判断影响行数比如还书时如果没匹配到在借记录rowcount为 0界面就能给出「没有找到在借记录」的提示。所有 SQL 参数都通过params传给cursor.execute禁止用字符串拼接这段代码在答辩时也能体现你有安全意识。3.3 核心流程借书、还书、查询的完整链路借书是整个系统里最容易出错的一步因为它涉及「查库存 → 插入借阅记录 → 扣减库存」三个动作。如果只做插入不做扣减多借几本书后库存就是负数如果只做扣减不校验库存为 0 时还能借出去逻辑就崩了。下面是借书的完整函数def borrow_book(rid, bid, days30): sql_check_stock SELECT stock FROM book WHERE bid%s FOR UPDATE sql_insert INSERT INTO borrow_record(bid, rid, borrow_date, due_date, status) VALUES(%s, %s, CURDATE(), DATE_ADD(CURDATE(), INTERVAL %s DAY), 0) sql_update_stock UPDATE book SET stockstock-1 WHERE bid%s conn get_conn() try: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute(sql_check_stock, (bid,)) row cursor.fetchone() if not row or row[stock] 0: raise ValueError(库存不足) cursor.execute(sql_insert, (rid, bid, days)) cursor.execute(sql_update_stock, (bid,)) conn.commit() return True except Exception: conn.rollback() raise finally: conn.close()FOR UPDATE是这个函数里的关键点执行SELECT stock FROM book WHERE bid%s FOR UPDATE时MySQL 会锁住这一行直到事务提交或回滚。这样两个用户同时借同一本最后一本书时第二个请求会等第一个请求结束然后发现库存已经变成 0而不是两个人同时读到 1。这在单机课程设计里可能看不出差别但属于「写对了没人夸、写错了答辩被问住」的知识点。还书函数的逻辑要更简单但要保证「更新记录 恢复库存」也在同一个事务里def return_book(record_id): conn get_conn() try: with conn.cursor(pymysql.cursors.DictCursor) as cursor: cursor.execute( SELECT bid FROM borrow_record WHERE record_id%s AND status0, (record_id,), ) row cursor.fetchone() if not row: raise ValueError(没有找到在借记录) cursor.execute( UPDATE borrow_record SET return_dateCURDATE(), status1 WHERE record_id%s, (record_id,), ) cursor.execute( UPDATE book SET stockstock1 WHERE bid%s, (row[bid],), ) conn.commit() except Exception: conn.rollback() raise finally: conn.close()查询借阅记录时界面通常希望直接显示读者姓名和书名而不是两个孤零零的编号。这时要用 JOIN 把三张表串起来SELECT br.record_id, r.rname, b.bname, br.borrow_date, br.due_date FROM borrow_record br JOIN reader r ON br.rid r.rid JOIN book b ON br.bid b.bid WHERE br.status 0 ORDER BY br.borrow_date DESC;这段 SQL 查询出来的结果每一行都能直接丢给 Treeview 做展示。理解了这三个核心链路源码包里界面按钮绑定的事件本质就是对execute_query和execute_update的再包装。4. 实战避坑连不上 MySQL、中文乱码、窗口卡死、打包失败的现场源码跑不起来大多不是逻辑问题而是环境问题。这一章列了我在实际调试中遇到最多的五类情况每一条都按「现象 → 原因 → 解决」来写。你遇到其中某一条时不用从头读直接定位到对应小节就行。4.1 MySQL 8 连不上认证插件报错先别急着改代码现象运行最小连接脚本或源码时抛Authentication plugin caching_sha2_password cannot be loaded或者是RuntimeError: cryptography package is required for sha256_password or caching_sha2_password。原因MySQL 8.0 默认认证插件是caching_sha2_passwordpymysql 在这种认证方式下传输密码需要 cryptography 库支持。如果你的 pymysql 版本较老或者环境里没装 cryptography连接时就会直接报错。解决先执行pip install cryptography再试一次。如果还不行就不要动 root 账号的认证插件而是新建一个专用账号用兼容模式认证CREATE USER lib_userlocalhost IDENTIFIED WITH mysql_native_password BY 这里写密码; GRANT ALL PRIVILEGES ON library.* TO lib_userlocalhost; FLUSH PRIVILEGES;然后把源码里DB_CONFIG的 user 改成lib_userpassword 改成上面设置的密码。这里不推荐把 root 从 caching_sha2 改成 mysql_native_password因为 root 权限太大为了一个课程设计去削弱整个数据库的默认安全策略属于过度让步。专用账号还能顺便回答老师「你考虑了权限分离吗」这种问题。4.2 中文乱码四处字符集少改一处就罢工现象导入 sql 文件后表里中文全变成???或者 Tkinter 界面显示正常但用 MySQL 命令行一查全是乱码再或者插入数据时报Incorrect string value: \xE4\xB8\xAD... for column。原因字符集链路很长MySQL 服务端、数据库、连接、SQL 文件本身的编码必须一致。Windows 下常见的坑是SQL 文件用记事本编辑后保存成了 ANSI 编码MySQL 客户端却按 UTF-8 解析或者连接参数写charsetutf8而库表是utf8mb4。解决一个不漏地把以下几处全部统一成 utf8mb4。建库语句里带DEFAULT CHARACTER SET utf8mb4连接参数写charsetutf8mb4命令行导入前先执行SET NAMES utf8mb4;SQL 文件用 VSCode 或记事本另存为「UTF-8 without BOM」。命令行导入的完整姿势mysql -u root -p library SET NAMES utf8mb4; source D:/library.sql;顺手解释一个选择MySQL 里utf8不是完全版最多支持 3 字节字符存 emoji 或生僻字会失败utf8mb4才是完整版。不管源码文件里写的是 utf8 还是 gbk你接手后都统一改成 utf8mb4 最省心。4.3 界面卡死长查询堵住了 Tkinter 主循环现象点击「查询全部借阅记录」后整个窗口卡住Windows 标题栏出现「未响应」数据量到几万条时几乎必现。原因Tkinter 是单线程事件循环事件函数里执行的数据库查询是同步阻塞的。数据库没有返回结果之前界面既不能重绘也不能处理点击表现出来就是卡死。解决把查询放到子线程里执行拿到结果后再跳回主线程刷新界面。Tkinter 不是线程安全的子线程里直接操作控件会有偶发崩溃所以要借助after方法回调。import threading def on_query(self): # 启动子线程daemonTrue 保证程序退出时子线程不会阻塞 threading.Thread(targetself._load_data, daemonTrue).start() def _load_data(self): # 子线程里执行 SQL不阻塞界面 rows execute_query(SELECT * FROM borrow_record) # 回到主线程刷新表格0 表示主循环空闲就执行 self.after(0, self._render, rows) def _render(self, rows): self.tree.delete(*self.tree.get_children()) for row in rows: self.tree.insert(, end, valuesrow)代码里的self.after(0, self._render, rows)是关键第一个参数 0 表示不延迟第二个参数是回到主线程后要执行的方法第三个参数是传给它的数据。直接在子线程里操作self.tree.insert看起来也能跑但偶发崩溃时你会排查到怀疑人生所以老老实实回到主线程再更新控件。4.4 打包 exe 后闪退先别怪桌面环境多半是资源路径和看不到报错现象本地python main.py一切正常PyInstaller 打包成 exe 后双击闪退放在别人电脑上提示找不到config.ini或library.sql。原因一是打包时用了-w隐藏控制台闪退时看不到任何报错二是代码里用了相对路径读取配置exe 的工作目录是启动位置不是 exe 所在目录三是--add-data没把 ini 或 sql 资源打包进去程序运行时找不到文件。解决先把-w去掉打一次包在控制台窗口里看到真实报错。然后统一用resource_path()处理资源路径import sys import os def resource_path(relative_path): if hasattr(sys, _MEIPASS): # PyInstaller 打包后的临时资源目录 return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)打包命令里用--add-data把资源文件打进去pyinstaller -F -w --add-data config.ini;. --add-data sql/library.sql;sql main.py注意 Windows 上--add-data的分隔符是分号;Linux 和 macOS 上是冒号:。我给别人的机器打包踩过几次同样的坑现在每次打包完都会先看一眼生成的warn-*.txt文件里面会列出 pyinstaller 找不到的依赖和资源这个文件比猜闪退原因管用得多。4.5 自己换驱动翻车pymysql 与 mysql-connector-python 的差异现象源码用的是 pymysql你听人说 mysql-connector-python 更「官方」改完连接后报TypeError: __init__() got an unexpected keyword argument cursorclass或者事务提交相关的参数报错。原因mysql-connector-python 的连接函数签名、游标类型参数和 pymysql 不是一一对应。把 pymysql 的连接写法直接搬过去参数名对不上就会这种错。两个驱动在日期格式、事务自动提交策略上也略有差异。解决项目里已经写好的驱动不要轻易换。pymysql 是纯 Python 实现部署简单性能对课程设计完全够用换驱动不是改一行 import 的事得逐项核对连接参数、游标方法和事务行为。如果担心 pymysql 的性能先去加索引和优化 SQL而不是换驱动。一个小型图书馆管理系统的查询量远没到需要纠结驱动性能的程度。5. 进阶加一个借阅统计视图再补一个管理员登录课程设计直接加分源码包里的基础功能跑通后你还需要一个能拿出来讲的亮点。常见做法是增加一个借阅统计视图和一个带密码哈希的管理员登录这两个功能实现成本低但答辩时很能打。先建视图CREATE VIEW vw_borrow_stats AS SELECT b.bname, COUNT(br.record_id) AS borrow_count FROM borrow_record br JOIN book b ON br.bid b.bid WHERE br.status 1 GROUP BY b.bid ORDER BY borrow_count DESC;视图把「哪些书借得最多」变成了一个普通查询在 Tkinter 里放一个按钮点击后执行SELECT * FROM vw_borrow_stats把结果填进 Treeview 就能展示。这里的关键是只统计status1的已还记录避免在借图书被重复计算如果想要月度统计可以在 WHERE 里加YEAR(br.borrow_date) YEAR(CURDATE())或直接加MONTH(br.borrow_date)。登录部分建议用一张独立的小表密码不要存明文CREATE TABLE admin ( aid INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE, password_hash CHAR(64) NOT NULL );写入时在 Python 里用hashlib.sha256加盐生成哈希登录时只比较哈希值。示例逻辑import hashlib def make_password(pwd, salt): return hashlib.sha256((salt pwd).encode()).hexdigest()在登录界面上限制失败三次就退出程序这个细节很多课程设计都想不到但能直接体现「安全意识」。做完这两个功能后记得做一次全流程验证新建一个读者录入三本不同书分别借出再还回看库存是否回到原值看统计视图里排序是否正确再用错误密码登录三次确认程序能退出。这轮验证跑下来比你口头说「能运行」有说服力得多。我见过太多把按钮事件全部堆在 main.py 里的做法改一个按钮得在一千行代码里翻半天。我现在做东西会习惯性地让界面层和逻辑层分文件放功能能跑只是第一步分得开才是真会。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站