前言
51放假前老师给我们讲解了vue3+node.js+MangoDB实现了学生座位选座系统, 要求我们后端的学员使用java代码+mysql实现后端,并且把项目运行起来。
#展示效果
首先就是登录页面,查询数据库是否有当前学号姓名来实现登录。
登录进去以后就是选座页面,非选座时段,上午,下午,晚上,是前端实现的,已经实现好了,不同时间段显示会不一样,比如现在已经过了选座时间,所以非选座时段亮了。
301-506是教室,每个教室座位不一样。
当我点击164座位,会提示我是否锁定座位,点击是就会跳到当前教室的座位页面。
下面就是锁定座位后的界面,164就是我锁定的座位,点击会弹出个人信息界面,
点击学生名单,会显示当前教室的上课学生。
这个就是我点击自己的座位后弹出的个人信息界面
这个是学生名单数据我还没有给条件,所以给一个班的学生都显示了,按理来说是只显示锁定座位了的学生。搜索按钮是可以查询以往日期学生锁定座位的情况。
以上就是整个选座功能的基本介绍了。
后端实现
数据库
首先后端还是得要创建好数据库,写好了数据库才能写好代码。 我的设计思路是创建一个学生表,存放学号,姓名,token,和班级。 然后再是座位表,因为前端需要接收一个二维数组来显示座位,所以我是这样设计的
seat_id座位id
room_i_d教室id
grid_row行
grid_col列
arr值
还需要有一个锁定座位的表,存放
seat_id座位id
is_locked存放0和1,用于表示当前座位有没有学生锁定
name当前座位锁定的学生姓名
登录
我们找到登录页面代码,如下。
const handleLogin = async () => {
try {
// 定义请求参数对象
const params = {
num: num.value,
name: name.value,
n: n.value
};
// 将参数对象转换为查询字符串
const queryString = new URLSearchParams(params).toString();
// 目标 API 的基础 URL
const baseUrl = `${BASE_URL}/api/login`;
// 拼接完整的请求 URL
const apiUrl = `${baseUrl}?${queryString}`;
// 发起 Fetch 请求
const response = await fetch(apiUrl);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP 错误! 状态码: ${response.status}`);
}
// 解析响应数据
const data = await response.json();
console.log(data);
if (data.code === 10000) {
console.log("abc");
// 存储 token 到 localStorage
localStorage.setItem('token', data.data.token);
console.log('登录成功,token 已存储');
// 登录成功后跳转到 App.vue
await router.push('/');
return;
} else {
// 显示服务器返回的错误信息
alert(data.text)
}
} catch (error) {
console.error('登录失败:', error);
alert('登录失败');
}
};
可以看到传给了后端学号姓名班级,后端需要传输给前端code10000和token。知道了需求就可以写后端了
那我们就封装给R,返回一个code和data,data里面存放token,这样就解决了
//登录
@GetMapping("/login")
public R<Student> login(@RequestParam("num") String num,
@RequestParam("name") String name,
@RequestParam("n") String n) {
System.out.println("num=" + num + "name=" + name + "n=" + n);
// 学号、姓名参数验证
if (num == null || num.isEmpty() || name == null || name.isEmpty()) {
return R.error(400, "学号和姓名不能为空");
}
try {
Student student = studentService.login(num, name);
if (student == null) {
return R.error(404, "学号或姓名错误");
}
return R.success(student);
} catch (Exception e) {
// 捕获异常并返回错误码
return R.error(500, "登录失败: " + e.getMessage());
}
}
座位显示
// 获取教室数据
const fetchClassroomData = async () => {
try {
const token = localStorage.getItem('token');
let URL = `${BASE_URL}/api/room/want?roomID=${vmRoom.value}`
const response = await fetch(URL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error(`HTTP 错误!状态:${response.status}`);
}
const data = await response.json();
console.log('教室数据:', data);
教室数据.value = data;
console.log('教室数据:', 教室数据.value);
} catch (error) {
console.error('获取教室数据出错:', error);
}
};
如上就是前端的接口代码,后端需要接收roomid,来显示当前教室的座位信息。 方法是get,并且在url上加了一个roomID
//获取所有座位信息
@GetMapping("/room/want")
public SeatDto getAllSeats(@RequestParam("roomID") int roomID) {
return seatService.getAllSeats(roomID);
}
所以我后端用了@RequestParam注解来接收。
在service层写了一些方法来将数据库中的数据存放在数组中。前端使用arr二维数组1表示座位,0表示过道来显示的。还需要一个存放座位id的二维数组layout来控制选座功能,最后返回给前端的数据是
{
arr:[1,1,0,0,1,1],
[1,1,0,0,1,1],
[1,1,0,0,1,1]
layout:[101,102,0,0,103,104],
[105,106,0,0,107,108],
[109,110,0,0,111,112],
data:{
{seatId: '101', locked: false, selected: false}
{seatId: '102', locked: false, selected: false}
{seatId: '103', locked: false, selected: false}
{seatId: '104', locked: false, selected: false}
{seatId: '105', locked: false, selected: false}
{seatId: '106', locked: false, selected: false}
{seatId: '107', locked: false, selected: false}
{seatId: '108', locked: false, selected: false}
{seatId: '109', locked: false, selected: false}
{seatId: '110', locked: false, selected: false}
{seatId: '111', locked: false, selected: false}
{seatId: '112', locked: false, selected: false}
}
}
@Override
public SeatDto getAllSeats(int roomId) {
List<Seat> seats = seatMapper.selectList(null);
SeatDto seatDto = new SeatDto();
List<SeatInfo> data = new ArrayList<>();
// 第一步:找到最大的 grid_row 和 grid_col
int maxRow = 0;
int maxCol = 0;
for (Seat seat : seats) {
maxRow = Math.max(maxRow, seat.getGridRow());
maxCol = Math.max(maxCol, seat.getGridCol());
}
// 第二步:根据最大行列创建二维数组
int[][] arr = new int[maxRow + 1][maxCol + 1]; // +1 因为索引从0开始
int[][] layout = new int[maxRow + 1][maxCol + 1]; // +1 因为索引从0开始
// 第三步:填充数据
for (Seat seat : seats) {
int row = seat.getGridRow();
int col = seat.getGridCol();
int value = seat.getArr();
String value2 = seat.getSeatId();
if (value == 0) {
value2 = "0";
}
arr[row][col] = value;
layout[row][col] = Integer.parseInt(value2);
}
seatDto.setRoomID(roomId);
seatDto.setArr(arr);
seatDto.setLayout(layout);
for (Seat seat : seats) {
SeatInfo seatInfo = new SeatInfo();
seatInfo.setSeatId(seat.getSeatId());
seatInfo.setLocked(seat.isLocked());
seatInfo.setSelected(seat.isSelected());
data.add(seatInfo);
}
seatDto.setData(data);
// System.out.println(Arrays.deepToString(arr));
// System.out.println(Arrays.deepToString(layout));
// System.out.println(seatDto);
return seatDto;
}
通过上面的方法最后也是成功让选座页面显示了出来
锁定座位
锁定座位前端代码如下
const handleSeatClick = async (seatId) => {
// 先将所有座位的选中状态置为 false
教室数据.value.data.forEach(seat => {
seat.isSelected = false;
});
// 将当前点击的座位选中状态置为 true
const currentSeat = getSeatById(seatId);
if (currentSeat) {
currentSeat.isSelected = true;
}
console.log(`你点击了座位 ID 为 ${seatId} 的座位`);
if (confirm(`确认锁定座位 ID 为 ${seatId} 的座位吗?`)) {
let 请求参数 = {
seat_id: seatId,
roomID: 教室数据.value.roomID,
token: localStorage.getItem('token'),
}
console.log(请求参数)
try {
const 响应 = await fetch(`${BASE_URL}/api/seat/pick`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(请求参数)
});
if (!响应.ok) {
throw new Error(`HTTP 错误!状态:${响应.status}`);
}
// 处理响应数据
const { code, text } = await 响应.json(); //数据
if (code === 10000) {
alert(text);
let 参数 = {
name:'look',
query:{
roomID:vmRoom.value,
ampm:ampm.value,
}
}
router.push( 参数 );
} else {
alert(text);
}
// 更新本地座位状态
if (currentSeat) {
currentSeat.isLocked = true;
}
} catch (error) {
console.error('锁定座位出错:', error);
}
} else {
// 如果取消锁定,将当前座位的选中状态置为 false
if (currentSeat) {
currentSeat.isSelected = false;
}
}
};
还是看前端给的什么数据给后端,以及什么类型的方法,可以看到是post方法,并且传输了座位id,教室id和token 所以我们后端需要接收到数据,封装在一个类里面。在通过教室id和座位id找到数据库锁定座位表中对应的座位id,在通过token查询姓名,存放在锁定座位表中,并且将开关改为true,来表示当前座位有人占用了。
//锁定座位
@PostMapping("/seat/pick")
public R pickSeat(@RequestBody Lock lock) {
seatService.pickSeat(lock);
return new R(10000,"座位锁定成功",null);
}
上面就是接口代码,Lock中存放了三个属性分别对应前端中的seatid,rooid和token。
@Override
public void pickSeat(Lock lock) {
//将座位状态改为锁定
String seatId = lock.getSeat_id();
List<Seat> seats = seatMapper.selectList(new QueryWrapper<Seat>().eq("seat_id", seatId));
//遍历seats
for (Seat seat : seats) {
seat.setLocked(true);
List<SeatStatus> seatId1 = seatStatusMapper.selectList(new QueryWrapper<SeatStatus>().eq("seat_id", seatId));
for (SeatStatus seatStatus : seatId1){
seatStatus.setLocked(true);
seatStatus.setSelected(true);
//根据token查询名字
Student student = studentMapper.selectOne(new QueryWrapper<Student>().eq("token", lock.getToken()));
seatStatus.setName(student.getStuName());
seatStatusMapper.updateById(seatStatus);
}
}
}
上面就是service层,用于实现查询,更新
获取学生列表
在锁定座位后会自动获取学生列表,下面是前端接口代码
// 新增获取学生列表方法
const fetchStudentList = async () => {
try {
// 修改fetch请求协议为https
const response = await fetch(`${BASE_URL}/api/stu/list/1`);
if (!response.ok) throw new Error('获取学生列表失败');
students.value = await response.json();
console.log('获取学生列表成功:', students.value);
} catch (error) {
console.error('获取学生列表错误:', error);
}
};
我们需要通过url最后的1也就是班级来查询当前班级中的学生列表,只需要一个简单的查询就可以完成
//返回学生列表
@GetMapping("/stu/list/{classId}")
public List<Stu> getStuList(@PathVariable String classId) {
System.out.println("classId = " + classId);
return seatService.getStuList();
}
我这边直接固定值写死了班级,为了方便展示。
@Override
public List<Stu> getStuList() {
return stuMapper.selectList(new QueryWrapper<Stu>().eq("class_id", "1234567"));
}
座位展示以及查询
最后就是最终选座后的座位页面,传输了当前教室id,当前时段,以及日期。下面是vue接口代码
// 处理搜索按钮点击事件
const handleSearch = async () => {
try {
loading.value = true;
error.value = null;
let date = selectedDate.value
vmWeek.value = vmWeekArr[new Date().getDay()];
let URL = `${BASE_URL}/api/room/look?roomID=${vmRoom.value}&m=${vmAmpm.value}&date=${date}`;
const 响应 = await fetch(URL);
if (!响应.ok) {
throw new Error(`HTTP错误!状态码:${响应.status}`);
}
rooms.value = [await 响应.json()];
} catch (err) {
error.value = `数据加载失败:${err.message}`;
} finally {
loading.value = false;
}
};
后端需要接收三个数据,并且查询对应时段的选座信息,但是我没有做出来,只做出来了一部分,就是没有时间段,只是显示了座位信息。 下面是接口
//获取教室信息
@GetMapping("/room/look")
public SeatDtos getRoomData(
@RequestParam int roomID, // 接收教室编号
@RequestParam String ampm, // 接收时间段
@RequestParam String date // 接收日期
){
SeatDtos oneSeats = seatService.getOneSeats(roomID);
System.out.println("oneSeats = " + oneSeats);
return oneSeats;
}
下面是service
@Override
public SeatDtos getOneSeats(int roomID) {
List<Seat> seats = seatMapper.selectList( new QueryWrapper<Seat>()
.eq("room_i_d", roomID)// 根据room_id查询
.orderByAsc("seat_id"));// 根据seat_id升序排序
SeatDtos seatDtos = new SeatDtos();
List<SeatInfos> data = new ArrayList<>();
// 第一步:找到最大的 grid_row 和 grid_col
int maxRow = 0;
int maxCol = 0;
for (Seat seat : seats) {
maxRow = Math.max(maxRow, seat.getGridRow());
maxCol = Math.max(maxCol, seat.getGridCol());
}
// 第二步:根据最大行列创建二维数组
int[][] layout = new int[maxRow + 1][maxCol + 1]; // +1 因为索引从0开始
// 第三步:填充数据
for (Seat seat : seats) {
int row = seat.getGridRow();
int col = seat.getGridCol();
int value = seat.getArr();
String value2 = seat.getSeatId();
if (value == 0) {
value2 = "0";
}
layout[row][col] = Integer.parseInt(value2);
}
seatDtos.setRoomID(roomID);
seatDtos.setLayout(layout);
for (Seat seat : seats) {
SeatInfos seatInfos = new SeatInfos();
List<SeatStatus> seatId1 = seatStatusMapper.selectList(new QueryWrapper<SeatStatus>().eq("seat_id", seat.getSeatId()));
for (SeatStatus seatStatus : seatId1){
if(seatStatus.isLocked()){
seatInfos.setIs_free(false);
seatInfos.setName(seatStatus.getName());
}else {
seatInfos.setIs_free(true);
}
}
seatInfos.setSeat_id(Integer.parseInt(seat.getSeatId()));
data.add(seatInfos);
}
seatDtos.setData(data);
System.out.println(data);
System.out.println(seatDtos);
return seatDtos;
}
结语
效果基本上就算是完成了,但是仍然有不足,我会在接下来时间里慢慢完善代码。主要难点在于我想数据库与后端之间怎么实现二维数组的传输。最后通过时间段来查询不同时间段的教室选座,可能还是没有想好具体要怎么做。要做什么。总之,加油!