尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Vue博物馆馆藏管理系统设计与实现全解析

SpringBoot+Vue博物馆馆藏管理系统设计与实现全解析 如果你正在找一套能直接跑起来的 Java 毕业设计项目或者你是博物馆、档案馆、文化馆这类机构的开发人员想给线下展陈做一套线上馆藏管理系统那这篇内容应该能帮你省下不少摸索的时间。这个项目就是典型的 SpringBoot Vue 前后端分离架构配合 MySQL 做数据存储、MyBatis 做持久层操作覆盖了文物信息登记、分类管理、借展记录、修复跟踪、用户权限控制这些核心业务。我把它从零到一完整实现了一遍今天把设计思路、核心代码、踩坑记录全部梳理出来给你一份可以直接照着复现的参考。项目本身不复杂但涉及的业务状态比较多尤其适合用来理解“真实管理系统”和“CRUD 演示”之间的差别。我会从整体架构设计开始讲起再落到数据库建模、后端接口、前端页面、权限控制、文件上传这些关键环节最后把我在实际开发中遇到过的问题和排查过程整理成清单。无论你是学生做课题还是工作中需要快速搭建一个内部管理系统这篇内容应该都能覆盖到你需要的部分。1. 整体设计与技术选型思路1.1 为什么是 SpringBoot Vue而不是别的组合先聊技术选型。这种管理系统市面上常见的组合无非就是 SpringBoot Thymeleaf 服务端渲染、SpringBoot Vue 前后端分离、或者直接 SSM 套个前端模板。我最终选了 SpringBoot Vue 前后端分离核心原因有两个。第一业务系统后期一定会面临界面调整和功能扩展。服务端渲染的方案在初期写起来快但一旦页面逻辑复杂了JavaScript 和模板引擎混在一起维护成本会迅速升高。Vue 的组件化开发让页面结构足够清晰表格、弹窗、表单这些都能拆成独立组件后续加功能只需要新增组件和路由不用动旧页面。第二前后端分离之后后端只需要专注提供 RESTful API前端可以独立开发和调试。这一点对多人协作很重要。我做这个项目的时候前端页面和后端接口就是同步推进的约定好接口返回格式之后两边甚至可以并行开发最后再联调效率明显更高。MyBatis 的选择也比较直接。虽然 JPA 写 CRUD 更快但在这种馆藏管理系统里多表联查的场景特别多比如查询文物时要关联分类、场馆、当前状态还要带出最近的借展记录。MyBatis 的 SQL 是手写的复杂查询的掌控力更强SQL 优化也能精确到每一条语句。配合 MySQL这套组合在中小型管理系统里属于非常成熟的方案网上参考资料多出了问题也好排查。1.2 核心业务模块拆解抛开技术先想清楚这个系统到底要管什么。博物馆的馆藏管理核心对象是“文物”或者“藏品”但围绕这个核心对象又牵扯出很多关联业务。我在设计时拆成了这几个模块藏品信息管理这是最基础的模块负责登记文物的名称、编号、年代、质地、尺寸、来源、图片、简介等信息。这里让我纠结了一段时间的是“编号”字段。文物编号在馆藏管理里相当于身份证号必须保证唯一而且通常带有分类含义比如“瓷器-宋-0001”。我在实现时做成了分类编码加序号自动生成用户添加藏品的时候只需要选分类编号自动拼接减少手输错误。分类管理文物分类在博物馆业务里有一套常用体系比如按质地分陶瓷、金属、玉石、书画等也可以按时代分。系统里做成可配置的树形结构支持无限层级这样不同场馆可以按自己的标准维护分类。场馆与展陈管理记录文物在哪个场馆、哪个展区展示或者是否在库房保管。考虑到文物经常在库房和展厅之间流动这里做了“存放位置”的关联设计每次移动都会生成一条流转记录。借展管理文物外借是博物馆业务里比较高频的事务记录借出时间、归还时间、借展单位、经手人等。这里必须处理一个状态约束问题一件文物如果已经在“外借中”就不能再被借出或移动到其他展区。修复与保养记录记录文物的修复历史、保养计划、当前保存状态评估。这个模块看似简单但对馆藏管理来说很重要尤其是陶瓷、书画这类需要定期维护的藏品。系统管理用户管理、角色管理、权限分配、操作日志。管理员账号可以维护其他用户的权限范围普通用户只能管理自己权限范围内的数据。1.3 为什么单独强调“状态机”设计这个项目里让我觉得最有价值的设计不是 CRUD而是文物状态流转的约束。如果只做一张表存数据、几个接口增删改查那也就是个“数据库管理工具”谈不上管理系统。真正让系统具备业务逻辑的是状态管理。文物的状态我设计了这样几种在库、展厅展出、修复中、外借中、下架/封存。每一个状态都有对应的触发动作比如“登记入馆”把状态置为在库“布展”把状态置为展厅展出“借出”把状态置为外借中。状态不是随意跳转的必须走合法的转换路径。举例来说一件正在展厅展出的文物如果要外借必须先执行“撤展”动作把状态变为在库然后才能执行“借出”操作。这个约束逻辑放在后端 Service 层做校验前端只是做按钮显隐控制真正防错的是后端代码。实际开发中这种设计很关键因为前端能改、接口能被绕过不能只依赖 UI 层面的限制。2. 数据库设计与核心表结构2.1 主要数据表关系图数据库是这个系统的地基。我设计了 8 张核心表user用户表role角色表user_role用户角色关联表category藏品分类表artifact藏品主表artifact_status_log状态流转日志表exhibition_record借展/展览记录表restoration_record修复记录表表之间的关系基本是一个分类下有多件藏品一件藏品有多条状态日志、多条借展记录、多条修复记录用户和角色是多对多关系。藏品主表不直接存放借展次数、修复次数这类冗余字段需要的时候通过关联表统计保持数据的一致性。2.2 藏品主表的关键字段设计artifact表是核心表字段设计上我重点考虑了几个问题。首先是图片存储方案。文物图片不能直接以二进制大字段存到数据库里会严重影响性能。我采用的是“上传文件到本地磁盘数据库存访问路径”的方式。系统启动时配置虚拟路径映射前端访问图片 URL 就能直接加载。其次是状态字段。我没有直接用普通整型表示状态而是用 VARCHAR 类型存储状态枚举的字符串值比如IN_STORAGE、ON_DISPLAY、ON_LOAN、UNDER_RESTORATION。这样做的缺点是存储空间稍大一点但优点是代码可读性强查数据的时候一眼就能看懂状态不需要对照数字字典表对后续维护更友好。以下是藏品表的建表 SQL我精简了注释保留了核心字段CREATE TABLE artifact ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, artifact_code VARCHAR(64) NOT NULL UNIQUE COMMENT 文物编号, name VARCHAR(200) NOT NULL COMMENT 文物名称, category_id BIGINT NOT NULL COMMENT 所属分类ID, era VARCHAR(100) COMMENT 年代, material VARCHAR(100) COMMENT 质地/材料, size_desc VARCHAR(255) COMMENT 尺寸描述, source VARCHAR(255) COMMENT 来源, description TEXT COMMENT 文物简介, cover_image VARCHAR(255) COMMENT 封面图片路径, status VARCHAR(30) NOT NULL DEFAULT IN_STORAGE COMMENT 当前状态, current_location VARCHAR(200) COMMENT 当前位置, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT藏品信息表;这里必须给category_id和status加索引因为列表页最常见的查询条件就是按分类筛选、按状态筛选。不加索引数据量到几万条之后查询速度会明显下坠。2.3 状态日志表记录业务痕迹artifact_status_log是一张非常重要但容易被忽略的表。每次文物状态发生变化系统都会往这张表里插入一条记录包含旧状态、新状态、操作人、操作时间、备注。这就形成了完整的“业务痕迹”。为什么一定要保留状态日志因为馆藏管理涉及审计需求。接展单位可能要求提供某件文物近期的状态变化记录或者在内部复盘时追溯“这件文物是什么时候从库房调到展厅的、经手人是谁”。如果没有日志表这些信息只能靠人工翻阅纸质档案效率极低。实现上日志写入和状态更新放在同一个事务里保证要么同时成功、要么同时失败不能出现“状态改了但没留下日志”的情况。2.4 分类表的树形结构处理分类表用parent_id自关联实现树形结构CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, code VARCHAR(50) NOT NULL COMMENT 分类编码, parent_id BIGINT DEFAULT 0 COMMENT 父分类ID0表示根节点, sort_order INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;后端在返回分类树的时候用递归方式把平铺数据组装成一棵多叉树返回给前端渲染成级联选择器或者树形下拉框。这里有个小技巧前端拿到树形结构后如果用户选择了父分类我们需要连带查出所有子分类下的藏品不能只按当前分类查。我是在 SQL 里使用了IN子查询先把当前分类及其所有子分类的 ID 查出来再作为条件去查藏品列表确保筛选结果完整。3. 后端核心实现与关键代码解析3.1 SpringBoot 项目基础搭建项目基础架构是标准的 SpringBoot 三层结构Controller 接收请求、Service 处理业务逻辑、Mapper 访问数据库。我用的是 SpringBoot 2.7.x 版本JDK 1.8依赖管理用 Maven。之所以不用最新的 SpringBoot 3.x是因为考虑到很多学校和企业环境还在用 JDK 8而且 MyBatis 相关的 starter 在 SpringBoot 2.x 上兼容性最稳定各种资料也最好查。核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependencyapplication.yml里需要特别注意 MyBatis 的配置我习惯把 Mapper 接口和 XML 文件分开存放XML 统一放在resources/mapper目录下然后在配置中指定mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.museum.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl那个map-underscore-to-camel-case配置非常关键。数据库字段是下划线命名比如create_timeJava 实体类是驼峰命名比如createTime打开这个开关之后MyBatis 会自动完成映射省去一大堆 resultMap 手写工作。log-impl建议开发阶段打开控制台能看到每一条 SQL 语句和执行参数排查问题效率高很多。3.2 统一返回结果封装前后端分离项目接口必须有一套统一的返回格式否则前端处理数据会很痛苦。我封装了一个ResultT类public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }所有接口统一返回Result对象前端拿到之后先判断code是否为 200再处理data。这样设计的好处是前端 axios 拦截器里可以统一处理错误提示不需要每个页面单独写错误的回调逻辑。3.3 藏品列表的分页查询实现列表页是最常用的功能藏品数量多的时候必须做分页。我用了 PageHelper 插件它的用法非常简单查询之前调用PageHelper.startPage()后面的查询会自动拼上 LIMIT 语句并且返回的PageInfo对象里自带总条数、当前页、总页数等分页信息。public PageResultArtifactVO getArtifactList(ArtifactQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListArtifactVO list artifactMapper.selectArtifactList(query); PageInfoArtifactVO pageInfo new PageInfo(list); return PageResult.of(pageInfo); }这里有个容易踩的坑PageHelper.startPage()必须紧跟 Mapper 查询方法中间不能穿插其他查询语句否则分页会作用到错误的 SQL 上。我在项目里统一在 Service 方法第一行调用并且把分页参数封装到ArtifactQuery对象里避免参数散落。MyBatis 的 XML 里核心查询语句如下重点是多表联查。这个语句会关联分类表、状态日志表分组取最新状态并支持名称模糊搜索、分类筛选、状态筛选select idselectArtifactList resultTypecom.example.museum.vo.ArtifactVO SELECT a.id, a.artifact_code, a.name, a.era, a.material, a.cover_image, a.status, a.current_location, a.create_time, c.name AS category_name FROM artifact a LEFT JOIN category c ON a.category_id c.id where if testkeyword ! null and keyword ! AND (a.name LIKE CONCAT(%, #{keyword}, %) OR a.artifact_code LIKE CONCAT(%, #{keyword}, %)) /if if testcategoryId ! null AND a.category_id IN ( SELECT id FROM category WHERE id #{categoryId} OR parent_id #{categoryId} ) /if if teststatus ! null and status ! AND a.status #{status} /if /where ORDER BY a.create_time DESC /select3.4 文物状态流转的后端校验状态修改是我重点处理的地方。以“借出”操作为例前端点击借出按钮后后端要做几步校验文物存在性校验当前状态是否为“在库”或“展厅展出”如果正在修复中或者已经外借直接拒绝操作校验通过后开启事务更新文物状态为“外借中”同时插入状态变更日志插入借展记录核心 Service 代码Transactional(rollbackFor Exception.class) public void loanOutArtifact(LoanOutRequest request) { Artifact artifact artifactMapper.selectById(request.getArtifactId()); if (artifact null) { throw new BusinessException(文物不存在); } if (!IN_STORAGE.equals(artifact.getStatus())) { throw new BusinessException(当前状态不允许借出仅限在库文物可借出); } // 更新状态 artifactMapper.updateStatus(request.getArtifactId(), ON_LOAN); // 插入状态日志 statusLogMapper.insert(StatusLog.builder() .artifactId(request.getArtifactId()) .oldStatus(artifact.getStatus()) .newStatus(ON_LOAN) .operatorId(request.getOperatorId()) .remark(借出至 request.getLoanOrg()) .build()); // 插入借展记录 exhibitionRecordMapper.insert(ExhibitionRecord.builder() .artifactId(request.getArtifactId()) .loanOrg(request.getLoanOrg()) .loanDate(request.getLoanDate()) .returnDate(request.getReturnDate()) .build()); }事务注解一定不能少。一旦后续步骤出现异常前面的状态更新也会回滚保证数据一致性。这一步是我觉得很多同行容易忽略的地方几个操作之间没有事务保护出问题的时候数据会非常混乱。3.5 JWT 登录认证与权限控制权限控制我采用了 JWT 拦截器的方案。用户登录成功后后端生成一个 JWT Token里面包含用户 ID、用户名、角色信息。前端把 Token 存储在 localStorage每次请求在 axios 拦截器中放入请求头Authorization: Bearer xxx。后端写了一个拦截器对需要认证的接口统一解析 Token解析失败直接返回 401。权限控制的粒度上我实现了角色级别的控制管理员可以执行所有操作普通用户只能执行查询类操作录入员可以新增和修改藏品信息但不能删除数据和审批借展请求。具体实现是在方法上标注自定义注解RequirePermission(value artifact:add)然后通过拦截器判断当前用户的权限集是否包含该值。权限集在登录成功后从数据库加载存入 JWT 中每次请求拦截时解析出来做判断。这样就不用在每个 Controller 方法里重复写权限判断逻辑也方便权限调整。4. 前端 Vue 实现与页面交互4.1 Vue 项目结构和路由配置前端用的是 Vue 2 Element UI 这套组合。选 Vue 2 的理由很简单Element UI 对 Vue 2 的支持最成熟组件全、文档多、上手快中文社区资料丰富遇到问题很容易搜到答案。Vue 3 虽然更好但配套的 Element Plus 在某些细节上和旧项目有差异如果队伍不熟悉反而增加不必要的成本。项目结构如下src/ ├── api/ // 接口请求封装 │ ├── artifact.js │ ├── category.js │ ├── exhibition.js │ └── login.js ├── router/ // 路由配置 ├── store/ // Vuex 状态存储 ├── views/ // 页面组件 │ ├── login/ │ ├── layout/ │ ├── artifact/ │ ├── category/ │ └── system/ ├── utils/ // 工具函数 └── App.vue路由配置采用静态路由加动态路由结合的方式。基础路由比如登录页、首页是静态的业务页面根据用户权限动态挂载。用户登录后前端拿到该用户可访问的菜单列表通过router.addRoutes()动态注册。这个设计能保证页面入口和用户权限一一对应避免普通用户手动修改 URL 访问管理页面。4.2 藏品列表页与搜索区交互藏品列表页是系统的门面用户每天面对最多的就是它。页面结构上我将搜索区、表格区、分页区拆成三个模块。搜索区支持名称/编号关键字输入、分类下拉选择、状态下拉选择。每次搜索条件变化触发loadData()重新请求接口。这里有一个交互细节值得提一下分类下拉框我用的是el-cascader级联选择器因为分类是树形的选择父分类后后端会自动连带查询子分类下的藏品。用户在选择分类时可能并不会意识到自己在选的是“陶瓷”还是“宋代陶瓷”如果只选“陶瓷”系统把下面所有子类都查出来这种体验更符合直觉。表格区固定展示 ID、编号、名称、分类、年代、状态、位置、操作列这几列。文物名称过长时使用show-overflow-tooltip属性鼠标悬停显示完整名称避免表格行被撑高影响浏览体验。状态列根据不同的值渲染不同颜色的标签比如在库显示蓝色、展出显示绿色、外借显示橙色、修复中显示红色视觉上一眼就能识别异常状态。4.3 文物新增与编辑表单新增和编辑文物共用一个对话框组件通过type参数区分是新增还是编辑。表单校验我没偷懒使用了 Element UI 的表单校验规则必填字段实时校验。其中图片上传单独处理使用el-upload组件上传成功后拿到后端返回的 URL存到表单的coverImage字段里。表单提交的流程是图片先上传到后端拿到 URL 后连同其他字段一起提交到保存接口。这里要注意的问题是如果用户填写到一半关闭了弹窗已经上传的图片就成了“孤儿文件”占用服务器空间。我做了个简单的缓解方案后端存储上传文件时记录文件上传时间和来源业务模块每周定时清理未关联业务数据的临时文件。这个功能不复杂但托管运营场景下很实用。4.4 动态菜单与权限按钮控制前端权限控制分两层菜单级和按钮级。菜单级实现方式是登录后调用后端/api/user/menus接口拿到当前用户的菜单树动态生成侧边栏导航。按钮级则是自定义了一个指令v-permission传入需要的权限标识比如v-permissionartifact:add如果没有该权限直接在渲染阶段把按钮从 DOM 中移除。按钮级权限很容易被忽视但它是正式系统里必须做的一层。只做菜单控制的话用户虽然看不到菜单入口但可以猜接口地址直接访问。前端移除按钮配合后端的接口权限校验才能形成完整的权限闭环。4.5 前端调试与联合开发经验开发阶段建议通过代理方式解决跨域问题。Vue 项目的vue.config.js中配置 devServer 代理把/api前缀的请求转发到后端 8080 端口module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这样配置之后前端开发环境下请求接口不需要处理 CORS 跨域问题后端的拦截器也能正常获取到 Token。等项目上线部署时再把前端打包后的静态文件放到 SpringBoot 的static目录下或者用 Nginx 做反向代理实现同域部署彻底规避跨域。5. 常见问题与排查技巧实录5.1 MyBatis 查询结果为空但 SQL 正常这个坑我很早之前就踩过。SQL 在 Navicat 里执行能查到数据但接口返回却是空列表大概率是实体类属性名和数据库列名映射不上。我排查时第一件事就是看 MyBatis 的日志输出对比 SQL 查询出来的列名和 Java 实体类的属性名。如果列名是create_time而实体属性是createTime又没有开启驼峰映射结果集就会填不进对象里。解决办法就是在application.yml里打开map-underscore-to-camel-case: true或者在 XML 中用resultMap显式映射。我前面已经提过这个配置这里再次强调是因为它的出现频率实在太高了。5.2 前端跨域请求导致的登录失效前后端分离项目最常见的问题之一前端请求能通但登录接口一调用后端的 Session 里拿不到用户信息。原因在于跨域请求默认不带 Cookie如果后端用的是 Session 机制就识别不了登录状态。我用了 JWT把 Token 放在请求头里传递从根本上绕开了这个问题。如果你使用 Session 方案需要在前端 axios 配置中设置withCredentials: true同时后端配置跨域过滤器时指定允许的域名和allowCredentials(true)两者缺一不可。5.3 大文本字段和 LocalDateTime 的 JSON 序列化问题藏品简介是 TEXT 类型从数据库查出来是字符串这个没问题。但 LocalDateTime 类型字段如果不做序列化处理前端拿到的可能是一串数组格式的默认序列化结果比如[2024, 5, 20, 10, 30, 0]很不好处理。我是在application.yml里配置了 Jackson 的日期格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8统一之后所有接口返回的时间字段都是2025-01-15 14:30:00这样的字符串前端直接展示不需要额外格式化。5.4 图片上传后刷新页面 404这个问题的本质是 SpringBoot 默认只处理静态资源目录下的文件上传到自定义目录的图片不会自动映射成可访问的 URL。解决办法是添加一个资源映射配置类Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath /); } }这里的uploadPath是上传文件存储的物理路径。注意 Windows 和 Linux 路径写法有差异Linux 下要写成file:/home/museum/upload/Windows 下是file:C:/museum/upload/。路径配置错了不会报启动错误而是访问图片时一直 404排查起来有点绕。5.5 事务不生效的问题状态流转操作里我加了Transactional注解但有几种情况会导致事务不生效。比如方法内部this调用同类里的另一个Transactional方法事务会失效因为 Spring 的事务是基于 AOP 代理实现的内部调用不经过代理对象。另一个是异常被 catch 吞掉了事务感知不到异常也就不会回滚。我的习惯是Transactional只放在 Service 入口方法上并且异常抛出时不捕获底层异常或者通过rollbackFor Exception.class明确指定回滚条件。6. 项目构建部署与源码使用说明6.1 后端打包与运行后端打包用 Maven 的 package 命令在项目根目录执行mvn clean package -DskipTests打出来的 jar 包在target目录下运行只需要 Java 环境java -jar museum-system.jar --spring.profiles.activeprod生产环境的数据库连接配置通过application-prod.yml指定不写在默认配置里避免开发配置泄露到生产环境。6.2 前端构建与发布前端发布前需要先执行构建命令生成静态资源文件npm install npm run build构建产物在dist目录下。最简单的部署方式是把这个目录下的所有文件拷贝到 SpringBoot 项目的src/main/resources/static/目录下重新打包后就能直接访问不需要额外配置 Nginx。如果访问量比较大还是建议用 Nginx 独立部署前端资源后端只提供接口服务性能和灵活性都更好。6.3 初始化数据说明项目里附带了一份初始化 SQL 脚本包含建表语句和基础数据。基础数据里内置了一个管理员账号admin/admin123登录后可以在系统管理模块创建其他用户和角色。分类表里预设了几组常用分类比如陶瓷、书画、玉石、金属器、杂项你可以按实际需要自行调整。我个人的建议是刚拿到项目不要急着改代码先用管理员账号登录系统完整走一遍“添加藏品 — 布展 — 借出 — 归还”的流程把系统的数据流转逻辑跑通。理解了状态是怎么变化的再去改代码会顺手很多。改代码时优先从 Service 层的业务校验逻辑入手这是最能体现系统业务规则的部分也是面试官最喜欢问的部分。最后再分享一个我在做这类系统时的小体会不要只盯着增删改查能不能跑通一定要多想想“一笔数据在真实业务里是怎么流转的”。你把这个想明白了表结构、状态设计、权限模型自然就出来了。系统做得好不好往往不取决于用了多新的技术而取决于你是不是真的理解了它要管理的业务。这个项目后续想扩展可以加上二维码标签打印、馆藏盘点移动端、数据分析大屏这些方向都是在现有基础上比较自然的延伸。
返回列表