ARTICLE DETAIL

资讯详情

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

用MATLAB App Designer构建图书管理系统:从界面到数据持久化

用MATLAB App Designer构建图书管理系统:从界面到数据持久化 简介这份基于MATLAB的图书管理系统源码包专门面向正在学习MATLAB GUI编程或需要完成课程设计的高校学生围绕图书信息录入、修改、删除、借阅归还以及多条件查询三大核心功能展开能帮助读者快速掌握使用MATLAB搭建小型桌面管理系统的整体思路。压缩包内共14个文件包含5个.fig界面布局文件、5个.m业务逻辑脚本、1个.xls图书数据表、1个.mat数据文件、1份README说明文档和1张界面背景图压缩后仅1.62MB结构紧凑但五脏俱全。源码中不仅实现了常规的图书增删改查还通过判断图书是否处于借阅状态来完成借书与还书操作并额外设计了借阅次数统计和读者信用积分模块使系统具备更真实的管理场景。已有228人学习下载对于想快速理解MATLAB界面与数据处理相结合的同学来说这是一份轻量、易读、可直接运行的参考样例也方便在此基础上进行二次扩展。1. 基于matlab图书管理系统把借还书流程做成一个能点能查的桌面工具很多小型单位其实没有独立图书管理系统图书管理员手里只有一张不断增补的 Excel 表。于是总会有人问能不能用 MATLAB 做一个图书管理系统这个问题在课程设计群里出现频率很高甚至比基于 java 的图书管理系统毕业设计更难回答因为 MATLAB 用户通常不是软件工程师他们只是熟悉脚本、矩阵和绘图却被要求交付一个带界面的完整工具。基于 matlab 图书管理系统的合理定位是用 App Designer 做桌面端用 table 结构存放图书和读者数据用几条严格的状态转移函数完成借书、还书、续借再通过 .mat、Excel 或 JDBC 保住数据。它适合内网单机或小团队使用也适合作为 MATLAB GUI 课程设计题目。数据量超过几千册、并发写入频繁时这套方案会露怯但理解它的边界才不会被“图书管理系统”四个字带偏。2. 先立边界模块划分与数据模型决定系统复杂度2.1 最小可用的四个模块第一反应是照着基于 web 的图书管理系统抄一份功能清单什么登录、权限、分页、批量导入、超期罚款提醒全上。我一般会劝退这种做法因为 MATLAB 桌面程序的强项在计算和交互不在高并发 Web 服务。按我自己的经验一个能在课程设计或部门内部讲清楚的项目只需要四个模块图书档案录入、修改、下架图书管理书名、作者、分类、馆藏位置。读者档案维护读者编号、姓名、部门、可借额度。借阅业务办理借出、归还、续借并记录每本书的当前状态。查询统计按书名、作者、分类模糊查询同时统计在馆、借出、逾期数量。这四个模块看起来很简单但已经能覆盖“找到书、借走、还回来、知道谁没还”这条完整链路。登录权限这类功能不是不能加而是它会让基于 matlab 图书管理系统的项目复杂度翻倍收益却不明显。课程设计演示时裁判更关心借还流程是否严谨、数据是否持久化、异常输入会不会让程序崩掉。2.2 数据形态.mat、Excel 和数据库怎么组合重要的选择不是界面而是数据放在哪。这里先给结论单机演示和部门小工具用 .mat 文件保存运行时数据再定期导出一份 Excel 给不装 MATLAB 的人看如果要求多台电脑同时操作再用 MySQL 或 MariaDB。存储方式优点缺点适用场景.mat 文件保留 string、datetime、table 原始类型读写速度最快无法直接用 Excel 查看多进程并发不安全单机课程设计、个人小工具Excel 文件便于人工核对能直接进入办公流程每次全表读写日期显示容易乱与管理员交接、提交成果MySQL 数据库支持多人并发、事务回滚数据与程序分离需要安装数据库服务配置 JDBC 麻烦内网多用户、正式一点的管理系统实际开发里我倾向于运行时只操作内存里的 table 对象点击“保存”时同时写 .mat 和 .xlsx。这样即使 MATLAB 异常退出至少还有 Excel 副本。数据库连接作为进阶改造项放到第五章再展开。2.3 用表格定字段图书表、读者表和借阅记录表数据结构可以直接影响后面每一条代码的写法。基于 matlab 图书管理系统里最少要有三张表BookDB 存图书ReaderDB 存读者BorrowLog 存每一次借还记录。图书表的字段我一般这样定字段名类型说明BookIDstring图书编号主键例如 B001Titlestring书名Authorstring作者Categorystring分类例如 MATLAB、计算机、文学Locationstring馆藏位置例如 A-2-3Statusstring当前状态可借 / 借出 / 下架BorrowerIDstring当前借阅人编号未借出时为空字符串DueDatedatetime应还日期未借出时为空 NaT这里强烈建议把字符串类型统一成 string 而不是 char。string 数组可以直接用contains批量模糊匹配空值用表达比和 NaN 混在一起好处理得多。创建空表的代码是bookFields {BookID,Title,Author,Category,Location,Status,BorrowerID,DueDate}; bookTypes {string,string,string,string,string,string,string,datetime}; BookDB table(Size,[0 8], ... VariableTypes, bookTypes, ... VariableNames, bookFields);这段代码先定义列名和列类型然后生成一个 0 行 8 列的 table。用Size加VariableTypes建空表比先创建 cell 再转换要稳因为 datetime 列会自动初始化为 NaTstring 列自动初始化为不会出现缺失值类型错乱。后续增加图书时直接BookDB [BookDB; newRow];即可。借阅记录表不参与图书当前状态的判断它只负责留痕记录编号、图书编号、读者编号、借出日期、应还日期、实际归还日期。有了它才能回答“某本书一年被借过几次”“谁经常逾期”这类统计问题。状态字段只允许可借、借出、下架三个取值并且只能通过借书、还书、下架三个函数改变不能直接改表这是把系统做稳的第一步。3. 用 App Designer 和 uifigure 搭出图书管理系统的操作界面3.1 为什么选 App Designer 而不是 GUIDE如果你在 MATLAB 命令窗口输入appdesigner打开的是图形化设计器而不是老旧的 GUIDE。GUIDE 从 R2016a 前后就不再做新功能维护新版 MATLAB 里它仍然能跑但布局控件、表格交互、显示效果都停留在旧时代。基于 matlab 图书管理系统的新项目直接站在 App Designer 上做。App Designer 设计器可以拖拽控件自动生成 .mlapp 文件但那类文件对代码审查不太友好。我更常用 uifigure 加 uigridlayout 手写界面原因有三个第一代码是纯文本方便放进 Git第二可以在一个 .m 文件里同时写界面和业务函数不用每次打开设计器第三控制组件尺寸和间距时数字一眼就能看懂。下面是官方没有强制但很实用的做法。3.2 最小可运行界面查询框、读者输入框、借书按钮和表格先给一个可以直接执行的界面骨架。把下面的函数保存成bookManagerExample.m调用bookManagerExample就会弹出一个包含查询、借书和表格的窗口。function bookManagerExample() % 初始化空书库 BookDB initBookDB(); cfg struct(Capacity, 5, DueDays, 30, RenewLimit, 1, FinePerDay, 0.1); % 创建主窗口 fig uifigure(Name, 基于MATLAB图书管理系统, Position, [80 80 960 620]); g uigridlayout(fig, [3 4], ... RowHeight, {36, 36, 1x}, ... ColumnWidth, {110, 1x, 110, 110}); % 第一行查询区 qLabel uilabel(g, Text, 查询条件); qLabel.Layout.Row 1; qLabel.Layout.Column 1; qField uieditfield(g, text, Placeholder, 输入书名、作者或分类); qField.Layout.Row 1; qField.Layout.Column 2; searchBtn uibutton(g, push, Text, 查询, ... ButtonPushedFcn, (s, e) doSearch()); searchBtn.Layout.Row 1; searchBtn.Layout.Column 3; % 第二行借书区 rLabel uilabel(g, Text, 读者证号); rLabel.Layout.Row 2; rLabel.Layout.Column 1; rField uieditfield(g, text, Placeholder, 例如 R001); rField.Layout.Row 2; rField.Layout.Column 2; borrowBtn uibutton(g, push, Text, 借出选中图书, ... ButtonPushedFcn, (s, e) doBorrow()); borrowBtn.Layout.Row 2; borrowBtn.Layout.Column 3; returnBtn uibutton(g, push, Text, 归还选中图书, ... ButtonPushedFcn, (s, e) doReturn()); returnBtn.Layout.Row 2; returnBtn.Layout.Column 4; % 第三行数据表格 tbl uitable(g, Data, BookDB, ColumnEditable, false); tbl.Layout.Row 3; tbl.Layout.Column [1 4]; % 查询回调 function doSearch() idx searchBooks(BookDB, qField.Value); tbl.Data BookDB(idx, :); end % 借书回调 function doBorrow() sel tbl.Selection; if isempty(sel) uialert(fig, 请先在表格里选择一本书, 没有选中图书); return; end try BookDB borrowBook(BookDB, ... BookDB.BookID(sel(1)), rField.Value, cfg); tbl.Data BookDB; catch e uialert(fig, e.message, 借书失败); end end % 还书回调 function doReturn() sel tbl.Selection; if isempty(sel) uialert(fig, 请先在表格里选择一本书, 没有选中图书); return; end try BookDB returnBook(BookDB, BookDB.BookID(sel(1))); tbl.Data BookDB; catch e uialert(fig, e.message, 还书失败); end end end这个界面的核心是uitable的 Data 属性直接接收 table 对象。旧版figure加uit的那一套还需要转换 cell而uifigure环境下的表格可以直接显示 datetime 和 string列名自动取变量名。Layout.Row和Layout.Column指定了控件在第几行第几列ColumnWidth里写1x表示该列占据剩余宽度。查询按钮并不修改 BookDB只把筛选后的表格写进tbl.Data这是最省事的“查询”实现。注意doBorrow里返回的BookDB是嵌套函数共享的工作区变量。MATLAB 对嵌套函数有一个特殊规则只要嵌套函数内部给BookDB赋值外层函数里所有使用它的嵌套函数都会看到更新。doReturn同样直接更新表格数据逻辑非常直白。3.3 界面上的关键参数ColumnEditable、Selection、Placeholder表格区域有三个参数值得单独说。ColumnEditable设置成false是为了防止用户直接双击单元格改掉 Status 字段。基于 matlab 图书管理系统的状态必须走函数变更否则会出现“书已经借给别人状态却还是可借”的矛盾。Selection在uitable中返回的是行号一行是数值标量多行是向量所以用sel(1)只取第一行。Placeholder是编辑框的灰字提示它不会进入 Value也不会参与查询只是告诉操作者这里该填什么。实际参数说明如下控件属性参数含义建议值RowHeight每行高度1x 表示表格占满剩余空间查询行 36、操作行 36ColumnWidth每列宽度1x 让编辑框自适应拉宽固定列写数字主列写 1xColumnEditable是否允许用户在界面上直接改单元格falseButtonPushedFcn按钮点击回调函数句柄(s,e) doSearch()这里有一个常见误用把ColumnEditable设成true后用户改了单元格但 BookDB 里的内容不会自动改变界面显示和真实数据很快就不一致。所以宁可所有列都不可编辑把所有修改都收口到业务函数里。4. 核心业务函数查询、借书、还书与数字化的状态机4.1 查询用 contains 对 string 数组做模糊搜索查询功能是最容易被写复杂的地方。有人会写循环遍历每个字符串再手动判断strfind是否为空。其实 MATLAB 的contains函数可以直接处理 string 数组一个命令返回逻辑索引向量。function idx searchBooks(BookDB, keyword) kwd string(keyword); if kwd idx true(height(BookDB), 1); return; end idx contains(BookDB.BookID, kwd, IgnoreCase, true) | ... contains(BookDB.Title, kwd, IgnoreCase, true) | ... contains(BookDB.Author, kwd, IgnoreCase, true) | ... contains(BookDB.Category, kwd, IgnoreCase, true); endkwd 判断是否没有输入查询条件这时返回全部行。后面的四行contains分别对编号、书名、作者、分类做子串匹配IgnoreCase, true对英文有意义对中文也不会有副作用。这里之所以全部用 string 数组是因为contains对字符向量元胞数组处理不如 string 自然。如果你把 BookDB 的 Title 列设成 cellstr遇到空元胞会返回[0x0]逻辑索引长度就对不上了。4.2 借书、还书把状态流转写成两个严格函数借书不是“改一个表格单元格”它需要做四件事确认书存在、确认书可借、确认读者没超额度、更新书的状态和应还日期。还书则必须反过来确认书处于借出状态再清空借阅人并恢复可借。function BookDB borrowBook(BookDB, bookID, readerID, cfg) row find(BookDB.BookID string(bookID), 1); if isempty(row) error(BookManager:NoBook, 没有找到图书编号 %s, bookID); end if BookDB.Status(row) ~ 可借 error(BookManager:NotAvailable, 图书当前状态为 %s, BookDB.Status(row)); end if isempty(readerID) error(BookManager:NoReader, 读者证号不能为空); end countBorrowed sum(BookDB.BorrowerID string(readerID) BookDB.Status 借出); if countBorrowed cfg.Capacity error(BookManager:QuotaExceeded, 读者 %s 已借满 %d 本, readerID, cfg.Capacity); end BookDB.Status(row) 借出; BookDB.BorrowerID(row) string(readerID); BookDB.DueDate(row) datetime(today) days(cfg.DueDays); end function BookDB returnBook(BookDB, bookID) row find(BookDB.BookID string(bookID), 1); if isempty(row) error(BookManager:NoBook, 没有找到图书编号 %s, bookID); end if BookDB.Status(row) ~ 借出 error(BookManager:NotBorrowed, 这本书当前没有借出); end BookDB.Status(row) 可借; BookDB.BorrowerID(row) ; BookDB.DueDate(row) NaT; end两个函数的逻辑都是“先校验后变更”这是状态机的基本要求。countBorrowed用BorrowerID readerID Status 借出求出该读者当前借出数量避免一个人把全馆书都借走。datetime(today) days(cfg.DueDays)的写法比手动拼接yyyy-MM-dd字符串要稳因为后续判断超期时可以直接比较 datetime。这里有个细节BookDB.Status(row) ~ 可借中的比较对象是 string 标量可借如果状态列里混入 charMATLAB 会先自动转换规则建议项目里彻底放弃 char所有状态值都写成带双引号的 string。NaT是 datetime 的空值表示没有应还日期它在表格里显示为NaT不会被误认为某个具体日期。4.3 借阅规则需要调的三个参数Capacity、DueDays、RenewLimit业务规则不要散落在函数里最好集中成一个 cfg 结构体。下面这张表可以直接用于课程设计文档参数含义默认值说明Capacity单个读者最多同时借几本5超过后借书函数报错DueDays默认借期单位天30借出时由当前日期加天数生成 DueDateRenewLimit最多续借次数1每本书同一借阅周期内可续借一次FinePerDay逾期每天罚金单位元0.1统计逾期时用于计算罚款cfg可以设计成函数的第二个参数传入这样后续做单元测试时可以构造一个Capacity为 1 的小额度读者专门验证超借限制。示例代码里初始化cfg的方式是cfg struct( ... Capacity, 5, ... DueDays, 30, ... RenewLimit, 1, ... FinePerDay, 0.1);建议把这样一个 cfg 结构体放到保存数据的 .mat 文件里与图书数据一起持久化。修改借阅规则时不用重新编译改完保存再加载即可。5. 数据持久化.mat、Excel 导出和 JDBC 连接 MySQL5.1 别只存 .mat保存策略与工作目录问题想把界面数据留下来最直接的是save命令。共享变量 BookDB、借阅日志和 cfg 一起保存dataPath data; if ~exist(dataPath, dir) mkdir(dataPath); end save(fullfile(dataPath, bookdata.mat), BookDB, BorrowLog, cfg);fullfile比直接拼接字符串更安全它会自动处理系统和路径分隔符。exist(dataPath, dir)检查目录是否存在避免首次运行时 mkdir 报错。读取时用load(fullfile(dataPath, bookdata.mat))但要注意如果工作区里已经有一个叫 BookDB 的变量load默认会覆盖它记得先备份当前数据再加载。在毕业设计里最常出现的问题是save文件路径写的是相对路径而作业运行时当前目录切换到了别处。我一般会在程序启动最前面加一句rootDir fileparts(mfilename(fullpath));再基于这个 rootDir 构造绝对路径。这样无论从哪里双击运行数据都会落在程序文件夹下不依赖用户的手动 cd。5.2 用 writetable 导出 Excel 给不装 MATLAB 的人图书台账最终要交给管理员看管理员大概率只有 WPS 或 Excel。定期导出一个 xlsx 文件非常省事writetable(BookDB, fullfile(dataPath, book_export.xlsx), ... Sheet, 图书台账); if ~isempty(BorrowLog) writetable(BorrowLog, fullfile(dataPath, book_export.xlsx), ... Sheet, 借阅记录, WriteMode, overwritesheet); endwritetable的第一个参数是 table列名自动作为 Excel 表头。第二个参数是文件路径。Sheet指定工作表名WriteMode, overwritesheet表示每次覆盖那一张表而不是追加行。这里有个容易踩的坑同一文件写多个 sheet 时第一次调用会创建文件第二次调用必须带WriteMode否则默认是inplace如果工作表名不同会直接替换整个文件内容导致前面写的 sheet 消失。Excel 里的日期列有时会显示成一串数字因为 .xlsx 内部存储的是日期序列值writetable 默认写入的是 datetime 对象Excel 会识别成数字。解决办法不是改代码而是在 Excel 里选中该列把单元格格式设为“短日期”。5.3 用 JDBC 连接 MySQL让系统支持多人同时用当数据需要被多台电脑同时读写.mat 文件就不合适了。标准做法是让基于 matlab 图书管理系统连接 MySQL。手动操作路径是下载 MySQL Connector/J 的 jar 包把 jar 路径加入 classpath然后调用database函数。如果你安装了 Database Toolbox代码可以很简洁javaclasspath(mysql-connector-j-8.2.0.jar); conn database(library, root, your_password, Vendor, MySQL); if isempty(conn.Message) sqlwrite(conn, books, BookDB); close(conn); enddatabase四个参数分别是数据库名、用户名、密码和数据库厂家。javaclasspath只需要在本次 MATLAB 会话中执行一次。sqlwrite会根据 table 的变量名自动匹配数据库列名所以 MySQL 里那张表的列名最好和 BookDB 的变量名保持一致例如BookID、Title、Status、DueDate否则要写一大堆列映射。需要注意两点。第一没有 Database Toolbox 时database函数不可用需要手动写 Java JDBC 调用代码能跑但可维护性较差。如果只是课程设计不建议爬这个坑。第二MySQL 和 MATLAB 之间传汉字时连接字符串里要显式指定字符编码常见做法是characterEncodingutf8。没配编码时中文数据写入后会出现乱码这是数据库连接最典型的排错方向。6. 验证与排错用回归脚本和边界数据把借阅流程跑稳6.1 用脚本做冒烟测试别只靠手点界面图形界面演示时容易顺风顺水真正容易出问题的是借还状态互相覆盖。所以我一写完业务函数就会做一个不依赖界面的测试脚本function testBorrowAndReturn() bookFields {BookID,Title,Author,Category,Location,Status,BorrowerID,DueDate}; bookTypes {string,string,string,string,string,string,string,datetime}; BookDB table(B001, MATLAB 深度学习, 张三, 计算机, A-1-1, ... 可借, , NaT, VariableNames, bookFields); cfg struct(Capacity, 1, DueDays, 30, RenewLimit, 1, FinePerDay, 0.1); BookDB borrowBook(BookDB, B001, R001, cfg); assert(BookDB.Status(1) 借出); assert(BookDB.BorrowerID(1) R001); assert(BookDB.DueDate(1) datetime(today)); % 同一本书不能二次借出 try borrowBook(BookDB, B001, R002, cfg); error(应该借出失败却成功了); catch e assert(strcmp(e.identifier, BookManager:NotAvailable)); end BookDB returnBook(BookDB, B001); assert(BookDB.Status(1) 可借); assert(BookDB.BorrowerID(1) ); disp(PASS); end测试脚本把 BookDB 初始化为一行数据先借出再断言状态和日期然后尝试借给第二个人期望抛出NotAvailable错误。assert在条件不满足时直接抛错所以脚本执行到最后打印PASS就说明这条主流程没被改坏。每次改完业务函数在命令行跑一次testBorrowAndReturn比反复在界面上点按钮快得多。6.2 常见排错类型不匹配、中文乱码和日期显示开发基于 matlab 图书管理系统过程中有三个报错和界面问题经常出现。第一未定义与 cell 类型的输入参数相对应的运算符 。这是把 table 的字符串列设成了 cellstr然后又用比较。解决办法是把建表时的类型统一改成 string或者在读取旧数据后用string(BookDB.Status)转换一次。第二含中文数据在 load 后显示为乱码。这个问题几乎都出现在 MATLAB 版本或系统区域设置不一致时处理方式是别用旧版的fig类控件直接用uifigure并确保 .m 文件另存为 UTF-8 编码。R2023b 这类新版安装后默认会正确识别中文字符串不需要额外处理。第三Excel 导出的日期列变成数字。这个问题不影响数据本身只影响观感。解决方法是让用户手选日期列或者在导出后调用一次 ActiveX 写单元格格式。ActiveX 方案依赖 Windows Excel通用性差我更推荐直接告诉管理员“日期列请设置成短日期格式”。把冒烟测试放进启动回调里每次打开系统自动跑一遍核心流程比临时手点更让人放心。跑完之后再真正进入界面数据校验、状态流转、持久化这三件事就都有了兜底。本文还有配套的精品资源点击获取
返回列表