☰
学生公寓管理系统毕设实战:Spring Boot + Vue 从数据库设计到避坑指南
2026/10/10 3:33:26 网站建设 项目流程

简介:一份面向高校学生公寓信息化建设场景的论文资源,适合计算机相关专业学生、宿管系统开发人员及课程设计/毕业设计参考者。论文围绕学生公寓管理系统展开,涵盖项目背景、系统需求与可行性分析、总体设计、数据库概念/逻辑/物理设计等核心内容,并基于ASP.NET与SQL Server给出用户登录、修改密码、添加学生信息、楼栋/寝室/专业/学院/班级管理等模块的详细实现方案。资源包共1个文件,为doc格式文档,整体大小约619KB;从内容预览可见论文结构完整,含摘要、目录、各章节正文、结论、致谢与参考文献,目录层次清晰,便于快速检索和对照学习。目前已有149人次学习浏览,对正在撰写同类管理系统论文或需要梳理设计思路的读者具有较强的参考价值。

1. 学生公寓管理系统论文.doc:一份毕设文档背后的完整需求地图

“学生公寓管理系统论文.doc” 这个标题,指向的是每年毕业季高频出现的一类经典题目:管理信息系统开发加论文撰写。这个 .doc 文档不是一篇纯文字报告,它背后通常绑定了一套能跑的前后端项目,文档里装的是需求分析、系统设计、数据库设计、核心代码实现、测试记录五部分内容。

系统要解决的核心问题很具体:把宿管手里的 Excel 台账和纸质登记本换成一套在线流程。学生信息维护、宿舍床位分配、入住退宿登记、报修工单流转、水电费计算统计、访客出入登记,这些事务一旦数据量上来,纯靠人工记录必然出错,系统的作用就是把流程固化到数据库和接口层。

如果你正在做毕设选题,或者接到一个学生公寓管理系统的课程设计任务,这篇笔记会按这类项目最常见的落地顺序,把技术选型、数据库设计、核心实现、常见踩坑、论文组织讲清楚。全是能直接复现的细节,不是泛泛的通识介绍。

2. 技术选型与系统架构:Spring Boot + Vue 为什么是主流答案

2.1 三套技术栈对比:选型不只看你会不会

学生公寓管理系统是典型的 CRUD 密集型业务系统,特点是流程长、状态多、报表多,几乎没有算法难度。选技术栈的第一原则是:你能在两周内独立写完,并且出问题时能搜到解决方案。

技术栈上手难度资料量答辩印象适合人群
Spring Boot + MyBatis + MySQL + Vue中高最多主流、企业向科班、时间充裕
Python Flask/Django + MySQL低多一般跨专业、时间紧
PHP ThinkPHP + MySQL中少偏旧已有 PHP 基础

我一般会推荐 Spring Boot + MyBatis + MySQL + Vue 这套组合。Spring Boot 内置 Tomcat,打包后一个 java -jar 命令就能跑起来;MyBatis 写复杂统计 SQL 非常直接,比 JPA 少了一层对象关系映射的黑匣子;前端 Vue 配 Element UI,后台管理页面两三天就能搭完。这套组合的排错经验在网上一搜一大把,毕设周期内基本不会因为环境问题卡住。

实际上 Spring 生态里还有 Spring Data JPA 可选,我的建议是毕设用 MyBatis 更稳。JPA 的级联更新和懒加载对新手来说是个黑匣子,查询性能出了问题不好排查,答辩时老师追问数据访问细节容易答不上来。MyBatis 的 SQL 全部写在 XML 里,每一条都看得见,出问题直接复制出来跑一遍,定位很快。

如果你对 Java 不太熟,用 Python Flask 也完全可行。但要注意,Flask 默认的 SQLite 在并发写入时有文件锁限制,这类系统里宿管和学生可能同时操作,入住登记、床位分配并发一上来,SQLite 会报 database is locked。趁早换 MySQL,或者至少做好连接池配置,否则答辩演示时两个人同时点分配床位,容易现场翻车。

2.2 角色权限与九个功能模块:先画清楚再写代码

学生公寓管理系统我习惯拆成三个角色:超级管理员负责账号和数据字典,宿管员负责学生、宿舍、入住退宿、报修、水电费录入,学生端负责查空床、报修、查费用、请假登记。角色不一样,前端菜单和后端接口权限就不一样,所以编码之前第一件事是把角色定下来。

功能模块固定拆成九个:

  1. 学生信息管理:增删改查、Excel 导入导出
  2. 楼栋与房间管理:楼栋维护、房间床位初始化
  3. 床位分配与调宿:分配、调换、释放
  4. 入住退宿登记:流程记录和状态更新
  5. 报修工单:提交、受理、完成、评价
  6. 水电费管理:抄表录入、费用计算、缴费状态
  7. 访客登记:来访记录、离校记录
  8. 公告管理:发布、置顶、过期下架
  9. 系统用户与角色:管理员账号、角色分配

这九个模块看起来多,其实核心只有两个状态机:床位状态(空闲/占用)和报修单状态(待受理/处理中/已完成)。其余模块本质都是标准 CRUD 加一个列表查询。把这两个状态机想清楚,系统就完成了一半。

2.3 前后端接口契约:耦合越弱,返工越少

接口约定必须在写代码之前定好。不然后端返回的字段名、状态码、分页参数和前端对不上,联调阶段全是扯皮。

接口路径按资源命名:

  • GET /api/student/page 分页查询学生
  • POST /api/student 新增学生
  • PUT /api/student/{id} 修改学生
  • DELETE /api/student/{id} 删除学生
  • POST /api/check-in 入住登记
  • POST /api/check-out 退宿登记
  • POST /api/transfer 调宿申请

统一返回体:

{ "code": 200, "message": "success", "data": {} }

分页参数约定 pageNum 和 pageSize,前端 Element UI 的 el-pagination 默认就是这两个字段名,后端 PageHelper 也原生支持,能少写一层字段转换。

项目目录结构也建议定好。前端按 views、api、router 三层分,后端按 controller、service、mapper、entity 四层分。分层别省,后面写论文的“系统实现”章节时,每一层的职责描述直接就能用。

登录鉴权这块,毕设规模下不建议引入 Spring Security 全家桶,配置成本会拖慢进度。用 JWT 加一个 HandlerInterceptor 拦截器,前端 Vue Router 加路由守卫,够用了。

提示:前后端分离后,接口文档建议用 ApiPost 这类工具维护。答辩时评阅老师看到接口文档和数据库设计文档,对工作量的评估会高一个档次。

3. 数据库设计:从表结构到床位分配的并发约束

3.1 十张核心表:先定关系再写代码

数据库设计是这类管理系统最容易出问题的环节。表设计不合理,后面每个查询都要 join 两三张表,SQL 越写越长,出错概率也越大。而且这类系统后期需求基本都是统计报表驱动——某栋楼空了多少床位、本月水费收缴率是多少——全靠表结构支撑。

我一般拆成十张核心表:

表名用途关键字段
t_admin管理员账号id, username, password, role
t_student学生信息id, student_no, name, gender, major, dorm_status
t_building宿舍楼栋id, building_name, manager
t_room房间id, building_id, room_no, floor, bed_count
t_bed床位id, room_id, bed_no, student_id, status
t_checkin_record入住退宿记录id, student_id, bed_id, type, operate_time
t_repair_order报修单id, student_id, room_id, content, status
t_fee_record水电费记录id, room_id, fee_month, water_fee, elec_fee, pay_status
t_visitor访客登记id, room_id, visitor_name, visit_time, leave_time
t_announcement公告id, title, content, create_time

关系要点:

  • t_bed.student_id 必须加唯一约束,一个人同时只能占一个床位,这是数据库层面的硬约束
  • t_checkin_record.type 用 0 入住、1 退宿、2 调宿,保留全部历史记录,统计和追溯都有依据
  • t_fee_record 按“房间 + 账期月份”做唯一键,避免同一房间同月水电费被重复录入

这里要特别提醒:床位不要塞在房间表里用 json 存。有的项目把床位信息直接做成 t_room 表的 json 字段,短期省事,后期想统计每个房间住了几个人,SQL 要解析 json,MyBatis 处理起来也极其难受。床位单独成表,入住率就是一个 count 加 group by 的事情。

3.2 核心建表 SQL:学生、房间、楼栋与床位

四张核心表的建表 SQL:

-- 学生表 CREATE TABLE t_student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 0 COMMENT '0男 1女', major VARCHAR(100) COMMENT '专业', phone VARCHAR(20), dorm_status TINYINT DEFAULT 0 COMMENT '0未入住 1在住', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 楼栋表 CREATE TABLE t_building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_name VARCHAR(50) NOT NULL, manager VARCHAR(50) COMMENT '宿管员' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 房间表 CREATE TABLE t_room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_no VARCHAR(20) NOT NULL, floor INT DEFAULT 1, bed_count INT DEFAULT 4 COMMENT '标准床位数', UNIQUE KEY uk_building_room (building_id, room_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 床位表 CREATE TABLE t_bed ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, bed_no VARCHAR(10) NOT NULL, student_id BIGINT DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT '0空闲 1占用', UNIQUE KEY uk_bed_student (student_id), UNIQUE KEY uk_room_bed (room_id, bed_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

参数说明:

  • student_no 加 UNIQUE,导入学生 Excel 时重复学号直接被数据库拦住,比代码里先查一遍再插入的方式可靠
  • uk_bed_student 是“一个学生一个床位”的兜底约束。代码写错了、并发出问题了,这个索引能守住最后一道防线
  • 字符集用 utf8mb4 而不是 utf8。学生姓名里出现生僻字或自定义表情时,utf8 直接报错存储失败
  • dorm_status 用 TINYINT 存 0/1,不要用字符串,字段大小、索引效率、代码比较成本都更优

房间和床位为什么拆两张表?房间属性(楼层、类型、标准床位数)是整间房共享的,床位属性(床号、占用状态、入住学生)是每张床独立的。强行合并,同一房间属性会重复 N 次,改一个床位数要改 N 行。

3.3 并发分配床位:条件 UPDATE 胜过先查后改

很多毕设系统开发时是单机单用户,并发问题暴露不出来。但答辩现场如果要演示“两个管理员同时分配同一张床位”,或者远程部署后多人使用,问题就来了。

最常见的错误写法是先查再改:

Bed bed = bedMapper.selectById(bedId); if (bed.getStatus() == 0) { bedMapper.updateStatus(bedId, 1); }

两个请求同时读到 status=0,然后各自执行 update,最终两个学生都显示分配成功。这不是理论问题,是实际会出现的翻车现场。

正确的写法是让更新语句带条件:

UPDATE t_bed SET student_id = ?, status = 1 WHERE id = ? AND status = 0;

这一步把“查状态再更新”合并成一条原子操作。影响行数等于 1 说明抢到床位,0 说明已经被占。

如果业务上要先展示床位信息给学生确认,再执行分配,中间隔了几秒,上面的 UPDATE 条件仍然能兜底。配合 Spring Boot 的 @Transactional 把整个分配流程包在事务里:

@Transactional(rollbackFor = Exception.class) public Result allocateBed(AllocateRequest req) { int rows = bedMapper.occupyBed(req.getBedId(), req.getStudentId()); if (rows == 0) { throw new BizException("床位已被占用"); } studentMapper.updateDormStatus(req.getStudentId(), 1); CheckInRecord record = new CheckInRecord(); record.setStudentId(req.getStudentId()); record.setBedId(req.getBedId()); record.setType(0); record.setOperateTime(new Date()); recordMapper.insert(record); return Result.success(); }

提示:@Transactional 默认只对 RuntimeException 回滚。若业务抛出的自定义异常不是继承 RuntimeException,必须写 rollbackFor = Exception.class,否则事务不会回滚,床位和学生状态各改一半,数据直接不一致。

MySQL 默认的可重复读隔离级别下,条件 UPDATE 会锁住匹配的行。如果查询条件走了索引,例如 WHERE id = ? AND status = 0,锁定范围就是这一行,不会锁整张表,吞吐量没问题。

4. 核心功能实现:入住登记、退宿调宿与统计报表的代码落地

4.1 入住登记:事务加条件更新,一步到位

入住登记是系统流程的起点。学生线下办理入住,或者宿管在系统里直接分配床位,核心逻辑都一样:占床位、改学生状态、写入住流水,三步必须在一个事务里完成。

@Service public class CheckInService { @Autowired private BedMapper bedMapper; @Autowired private StudentMapper studentMapper; @Autowired private CheckInRecordMapper recordMapper; @Transactional(rollbackFor = Exception.class) public void checkIn(Long studentId, Long bedId) { // 1. 条件更新抢占床位 int rows = bedMapper.occupyBed(bedId, studentId); if (rows == 0) { throw new BizException("床位不存在或已被占用"); } // 2. 校验学生状态并更新 Student stu = studentMapper.selectById(studentId); if (stu == null || stu.getDormStatus() == 1) { throw new BizException("学生不存在或已在住"); } studentMapper.updateDormStatus(studentId, 1); // 3. 写入住流水 CheckInRecord record = new CheckInRecord(); record.setStudentId(studentId); record.setBedId(bedId); record.setType(0); record.setOperateTime(new Date()); recordMapper.insert(record); } }

对应 MyBatis XML 中的 occupyBed:

<update id="occupyBed"> UPDATE t_bed SET student_id = #{studentId}, status = 1 WHERE id = #{bedId} AND status = 0 </update>

逻辑说明:先更新床位再校验学生,顺序不能反。先占床位等于先拿行锁,床位抢不到直接抛异常退出整个事务,后面不需要执行任何操作。如果先查学生再占床位,两个请求可能同时通过学生状态校验,然后竞争同一行。

参数说明:occupyBed 返回的 int 是 MyBatis 的数据库影响行数。0 代表 WHERE 条件不满足,即床位不存在或已被占用;1 代表更新成功。

这里有一个细节:先写状态变更,后写流水记录。如果先 insert 记录再 update 床位,update 失败回滚时记录也会一起回滚,表面上没问题,但行锁持有时间更长,并发稍高时更容易形成死锁。养成“先占资源、再写流水”的习惯。

4.2 退宿与调宿:状态流转的完整闭环

退宿比入住更容易写错。很多新手写的退宿只是删掉入住记录,结果学生的 dorm_status 还是 1,床位还指着已经离校的人。退宿必须同时释放三个地方的状态:床位、学生、流水。

@Transactional(rollbackFor = Exception.class) public void checkOut(Long studentId, String reason) { // 1. 查询当前床位 Bed bed = bedMapper.selectByStudentId(studentId); if (bed == null) { throw new BizException("该学生当前没有入住记录"); } // 2. 释放床位 bedMapper.releaseBed(bed.getId()); // 3. 重置学生状态 studentMapper.updateDormStatus(studentId, 0); // 4. 写退宿流水 CheckInRecord record = new CheckInRecord(); record.setStudentId(studentId); record.setBedId(bed.getId()); record.setType(1); record.setReason(reason); record.setOperateTime(new Date()); recordMapper.insert(record); }

releaseBed 的 SQL:

<update id="releaseBed"> UPDATE t_bed SET student_id = NULL, status = 0 WHERE id = #{bedId} </update>

调宿的本质是“释放旧床位 + 占用新床位”,可以看成两次状态变更的组合。为了不让中间状态被其他操作读到,必须放在同一个事务里。实际项目里经常出现调宿时新床位分配失败、旧床位已经释放的情况,学生直接变成“无床可住”。事务整体回滚是唯一的后悔药,任何一步失败都回到调宿前的状态。

提示:退宿接口要设计成幂等。前端按钮连续点击两次,第二次请求时学生已经没有入住记录,直接返回提示而不是抛异常或造成数据错乱。

4.3 入住率统计与报修工单:答辩高频追问的两个功能

入住率统计是答辩时几乎必被问到的地方。核心 SQL 用房间维度做聚合:

SELECT r.id, r.room_no, r.bed_count, COUNT(b.id) AS total_beds, SUM(CASE WHEN b.status = 1 THEN 1 ELSE 0 END) AS occupied_beds, ROUND( SUM(CASE WHEN b.status = 1 THEN 1 ELSE 0 END) / COUNT(b.id) * 100, 1 ) AS rate FROM t_room r LEFT JOIN t_bed b ON r.id = b.room_id WHERE r.building_id = #{buildingId} GROUP BY r.id, r.room_no, r.bed_count ORDER BY r.room_no

LEFT JOIN 这里必须用,不能用 INNER JOIN。如果某个房间初始化时漏建了床位,INNER JOIN 会把那一行直接过滤掉,统计结果缺房,答辩时被老师看出数据对不上很尴尬。

前端展示房间入住率,用 Vue + Element UI 进度条:

<template> <el-table :data="rooms" stripe> <el-table-column prop="room_no" label="房间号" width="100" /> <el-table-column prop="bed_count" label="标准床位数" width="100" /> <el-table-column label="入住率"> <template #default="{ row }"> <el-progress :percentage="row.rate" :status="row.rate >= 100 ? 'success' : ''" /> </template> </el-table-column> </el-table> </template> <script setup> import { ref, onMounted } from 'vue' import axios from 'axios' const rooms = ref([]) onMounted(async () => { const { data } = await axios.get('/api/room/occupancy', { params: { buildingId: 1 } }) rooms.value = data }) </script>

报修工单的状态机可以做成 0 待受理、1 处理中、2 已完成。更新接口里校验状态跳转是否合法,比如已完成不能被重新改回待受理。前端根据状态渲染不同颜色的标签,这个功能在论文里占不了多少篇幅,但截图效果很好。

统计类接口尽量在后端算好返回,不要在浏览器里拉全量数据再算。数据量小的时候没感觉,楼栋多了、数据上万条,前端算一次要卡几百毫秒,体验非常糟糕。

5. 毕设避坑清单:五个常见的翻车点与修复方法

这部分我把这类项目里复现率最高的问题列出来,每条按现象、原因、解决的格式写。避坑清单不是替代测试,而是让流程更早暴露问题。

5.1 床位重复分配:并发场景的第一个翻车点

现象:两个宿管同时给不同学生分配同一张床位,系统里出现两条入住记录,床位最终只指向一个学生,另一个学生显示在住但查不到床位。

原因:先查床位状态再改状态的“检查-更新”模式不是原子操作。两个请求同时读到 status=0,随后各自执行 update 都成功,后写的覆盖先写的。

解决:条件更新加事务。

int rows = bedMapper.occupyBed(bedId, studentId); if (rows == 0) { throw new BizException("床位已被占用"); }

数据库再加 uk_bed_student 唯一索引兜底。代码漏了,索引也能拦住重复占用。

5.2 退宿后床位仍显示占用:状态机缺失的连锁反应

现象:学生办理退宿后,房间入住率没有变化,床位还是“占用”,学生的 dorm_status 还是 1。

原因:退宿方法只写了插入一条流水记录,没有同步更新 t_bed.student_id、t_bed.status、t_student.dorm_status。三个状态源只改了一个,数据自然对不上。

解决:退宿逻辑统一放到一个事务方法里,床位释放、学生状态重置、退宿记录插入一起提交。写完做一次回归验证:退宿一个学生,然后去查房间详情、学生列表、床位列表,三处数据要一致。

5.3 日期筛选当天数据查不到:时间精度陷阱

现象:按日期筛选水电费,选 2025 年 6 月 1 日,当天记录一条都查不到,但数据库里明明有数据。

原因:后端字段是 DATETIME,存的值是“2025-06-01 10:23:45”这类带时分秒的时间。前端传的日期字符串“2025-06-01”转成 LocalDate 后和 DATETIME 做等值比较,永远为假。

解决:用范围查询代替等值比较。

<select id="selectFeeByDate" resultType="FeeRecord"> SELECT * FROM t_fee_record WHERE room_id = #{roomId} AND fee_month &gt;= #{startDate} AND fee_month &lt; #{endDate} </select>

startDate 传当天零点,endDate 传次日零点,后一个日期用开区间,正好覆盖一整天。这个坑在统计报表模块几乎人人都会踩一次。

5.4 前后端跨域报错:看着像玄学,其实是配置缺了一半

现象:前端跑在 localhost:8080,后端跑在 localhost:9090,浏览器控制台报 CORS 错误,接口全部不通。

原因:前后端分离部署在不同端口,浏览器同源策略拦截。后端没有配置跨域规则,或者配置了但不完整。

解决:后端全局配置 CORS。

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }

注意:allowedOriginPatterns("") 要和 allowCredentials(true) 搭配使用。旧版本写法 allowedOrigins("") 在开启 credentials 的情况下会被 Spring 直接拒绝,报一串 Access-Control-Allow-Origin 相关错误。这是毕设项目里最常见的跨域翻车点。

另外,如果前面还加了 Nginx 反向代理,Nginx 的配置里也要加 add_header Access-Control-Allow-Origin,否则前端绕过了同源策略,Nginx 那一层又会拦一道。

5.5 MyBatis 动态 SQL 的 AND 悬空:别再用 WHERE 1=1

现象:多条件查询学生列表,只选了性别一个筛选项,生成的 SQL 变成 WHERE AND gender = 0,直接语法报错。

原因:手动用字符串拼接 where 条件,第一个条件为空时,and 就悬空了。

解决:用 MyBatis 的 where 标签。

<select id="selectStudents" resultType="Student"> SELECT * FROM t_student <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="gender != null"> AND gender = #{gender} </if> <if test="major != null and major != ''"> AND major = #{major} </if> </where> </select>

where 标签会自动去掉第一个条件开头的 AND,比手写 WHERE 1=1 干净,也避免拼接出错。还有一个细节:LIKE 条件用 CONCAT 拼百分号,而不是直接写 '%' 加参数,这样兼容 MySQL 和 PostgreSQL 两类数据库,迁移成本低。

6. 把工作整理成论文.doc:结构模板与验证证据的取舍

6.1 论文主体结构模板

学生公寓管理系统论文结构常年稳定,按这个模板走不会偏:

章节建议篇幅核心内容
摘要250字系统目标、使用技术、实现功能、测试结果
第一章 绪论1000字背景意义、现状分析、论文组织结构
第二章 需求分析2000字角色定义、功能需求用例、非功能需求
第三章 系统设计3000字架构设计、模块设计、数据库设计、接口设计
第四章 系统实现3500字每个功能模块:截图 + 核心代码 + 说明
第五章 系统测试1500字测试环境、功能测试用例表、结果分析
第六章 总结400字完成的工作、不足与改进方向

写作顺序建议:先写需求分析,再写数据库设计,这两个部分在建表之前就应该定稿。实现章节放在代码跑通之后写,配合真实截图。测试章节是最后一个写、但最早准备素材的——每完成一个功能就顺手截图、记录输入输出,攒到后面一起补。

6.2 验证证据怎么组织

评阅老师看论文,通常先翻摘要和目录,再看实现章节的截图是否和系统一致,最后看测试用例表。所以截图务必真实:学生列表有数据、入住率有百分比、报修单有状态变化,不要用空表糊弄。测试用例表按模块列出用例编号、输入、预期输出、实际输出、结论,每模块 3 到 5 条就够。

我个人的习惯是:代码提交前先跑一遍完整链路,从录入学生、初始化楼栋房间床位、分配入住、提交报修、处理报修、退宿,一条流程走完不出错再截图。这条链路就是答辩现场演示的脚本,整理成文档就是现成的测试用例。这个方向做下来你会发现,系统的价值不在框架本身,而在于把业务流程拆成状态机、把并发安全落到数据库约束、把文档和工作量映射清楚的能力。这些能力是可以迁移到下个项目里的。希望这篇笔记能帮你在毕设这条路上少踩几个坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询