简单宿舍管理系统(springboot+vue)


最近看了springboot和vue,为了练一下把前后端打通就自己手动写个简单的系统,测试一下,把代码放在仓库。

1.创建项目

1.前端

我的前端项目名叫Dormitory,然后添加插件element-plus(页面设计)和axios(后端交互)。

npm init vue@latest#这里插件下载我都选no,之后自己会手动下载使用
cd Dormitory
npm install
npm install element-plus
npm install axios
npm run dev

2.数据库

首先创建库,第一个是登陆功能,我就顺便创建一个简单的用户表t_user。

mysql -u root -p
create database dormitory;
use dormitory;
use dormitory;
CREATE TABLE t_user (
	uid INT AUTO_INCREMENT COMMENT '用户id',
	username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
	password CHAR(32) NOT NULL COMMENT '密码',
	role INT COMMENT '角色',
	name VARCHAR(20) NOT NULL COMMENT '姓名',
	gender INT COMMENT '性别:0-女,1-男',
	telephone VARCHAR(50) COMMENT '手机号',
	PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.后端

创建项目名叫dormitory_b,依赖库我用了三个(spring web,mybatis framework,mysql driver),然后配置一下jdk和maven环境和xml即可。

2.登陆

登陆这里我把uid和role保存到后端session中,username保存到前端cookie中,而且密码啥的我也没加密,就怎么简单怎么来。

1.前端

1.准备工作

这里我用到element-plus和icon,而且我是按需引入,所以首先下载插件:

npm install -D unplugin-vue-components unplugin-auto-import
npm install @element-plus/icons-vue

然后在vite.config.ts按需引入element-plus:

import { fileURLToPath, URL } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
});

在main.ts里将icon全局注册到App上:

import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

就可以使用svg方式使用:

 <el-icon><Menu /></el-icon>

然后配置路由用到了router,所以先下载:

npm install vue-router 

然后在src里面建一个router文件夹,里面建一个index.js写路由配置文件,然后在main.js里面挂载一下:

import router from './router';
app.use(router)

2.登陆组件

这里登陆页面我写在了LoginView里面,后面我会配置路由和组件。

<template>
    <div class="login-container">
      <div class="login-form">
        <el-form ref="login-form" :model="loginForm" label-width="80px" :rules="rules">
          <el-form-item label="用户名" prop="username">
            <el-input v-model="loginForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" v-model="loginForm.password"></el-input>
          </el-form-item>
          <el-form-item label="角色" prop="role">
        <el-radio-group v-model="loginForm.role">
          <el-radio label="admin">系统管理员</el-radio>
          <el-radio label="dorm">宿舍管理员</el-radio>
        </el-radio-group>
      </el-form-item>
          <el-form-item size="large">
            <el-button type="primary" @click="toLogin">登录</el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
  </template>
  
  <script>
  import { ref } from 'vue';
  import axios from 'axios';
  
  export default {
    setup() {
      const loginForm = ref({
        username: '',
        password: '',
        role: 'admin',
      });
  
      const rules = {
        username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
        role: [{ required: true, message: '请选择角色', trigger: 'change' }],
      };
  
      const performLogin = async () => {
        try {
            const formData = new FormData();//axios默认是json格式发送数据,但我后端接受的是user,所以将数据放到表单
            formData.append('username', loginForm.value.username);
            formData.append('password', loginForm.value.password);
            formData.append('role',loginForm.value.role=='admin'?0:1);

            const response = await axios.post('http://localhost:8080/users/login', formData);
            const data = response.data;

  
          if (data.state === 200) {
            // 登录成功后,保存用户名到cookie
            document.cookie = `username=${loginForm.value.username}; expires=; path=/`;
            alert('登录成功');
          } else if (data.state === 400) {
            alert('用户名错误');
          } else if (data.state === 401) {
            alert('密码错误');
          }else if (data.state === 402) {
            alert('角色错误');
          }
        } catch (error) {
          console.error('An error occurred:', error);
        }
      };
  
      const toLogin = () => {
        performLogin();
      };
  
      return {
        loginForm,
        rules,
        toLogin,
      };
    },
  };
  </script>
  
  <style scoped>
  .login-container {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
  }
  
  .login-form {
    padding: 80px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  }
  </style>
  

3.配置

首先是在router/index.js里面写路由配置,将这个页面路由配置一下:

import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '@/components/LoginView.vue'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/login',
      component: LoginView
    }
  ]
})
export default router

然后在App.vue里面写一下这个路由出口

<template>
  <RouterView></RouterView>
</template>

此时就可以通过http://localhost:5173/login/访问到这个页面。

2.后端

1.链接数据库

在application.properties中配置数据库。

spring.datasource.url=jdbc:mysql://localhost:3306/dormitory?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root

2.创建用户实体类

在entity/User类里面创建用户实体类,和getset,equal,tostring方法(mac的快捷键是command+n)。

public class User {
        private Integer uid;
        private String username;
        private String password;
        private Integer role;
        private String name;
        private Integer gender;
        private String phone;
}

3.数据操作持久层

1.配置

首先是配置一下mapper层,在启动类里面添加项目的mapper路径,让自动扫包。

@MapperScan("com.hckj.dormitory_b.mapper")

在application.properties中配置mapper地址。

mybatis.mapper-locations=classpath:mapper/*.xml
2.内容

在mapper/UserMapper接口里面写SQL语句的抽象方法(类添加@Mapper注解),然后resources/mapper/UserMapper.xml里面写抽象方法的映射文件(这里放在resource是因为xml是静态文件)。

User findByUsername(String username);//接口
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hckj.dormitory_b.mapper.UserMapper">
    <select id="findByUsername" resultType="com.hckj.dormitory_b.entity.User">
        select * from t_user where username=#{username}
    </select>
</mapper>
3.测试

为了登陆测试,这里先给数据库里面插入一条数据:

INSERT INTO t_user (username, password, name,role, gender, telephone)
VALUES ('zoe', '111', 'dz', 0, 0,'188');

然后测试(测试类加注解:@SpringBootTest和@RunWith(SpringRunner.class))

	@Autowired
    private UserMapper userMapper;
    @Test
    public void findByUsername(){
        System.out.println(userMapper.findByUsername("zoe"));
    }

4.中间业务层

1.异常

在登录这个业务里会出现用户没有查询到和密码不匹配,所以在service的ex包里面创建UsernameNotFoundException和PasswordNotMatchException异常类,还有一个是角色不匹配RoleNotMatchException,并都继承RuntimeException,然后生成抛出异常的5种构造方法。

2.业务实现

然后写业务层的接口(加@Service注解)并写类实现这个接口。

User login(String username, String password,Integer role);//接口
@Service
public class UserServiceImpl implements IUserService{
    @Autowired
    private UserMapper userMapper;
    @Override
    public User login(String username, String password,Integer role) {
        User result = userMapper.findByUsername(username);
        if (result == null) {
            throw new UsernameNotFoundException("用户数据不存在");
        }
        String password_ = result.getPassword();
        if (!password_.equals(password)) {
            throw new PasswordNotMatchException("用户密码错误");
        }
        Integer role_=result.getRole();
        if (!role_.equals(role)) {
            throw new RoleNotMatchException("用户角色错误");
        }
        User user = new User();
        user.setUid(result.getUid());
        user.setUsername(result.getUsername());
        user.setRole(result.getRole());
        return user;
    }
}
3.测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTests {
    @Autowired
    private IUserService userService;
    @Test
    public void login(){
        User user= userService.login("zoe","111",0);
        System.out.println(user);
    }
}

5.响应前端控制层

package com.hckj.dormitory_b.controller;

import com.hckj.dormitory_b.entity.User;
import com.hckj.dormitory_b.service.IUserService;
import com.hckj.dormitory_b.service.ex.PasswordNotMatchException;
import com.hckj.dormitory_b.service.ex.RoleNotMatchException;
import com.hckj.dormitory_b.service.ex.UsernameNotFoundException;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("users")
public class UserController {
    @Autowired
    private IUserService userService;
    @PostMapping("login")
    public Map<String, Object> login(User user, HttpSession session) {
        String username = user.getUsername();
        String password = user.getPassword();
        Integer role=user.getRole();
        Map<String, Object> response = new HashMap<>();
        try {
            User loggedInUser = userService.login(username, password,role);
            session.setAttribute("uid",loggedInUser.getUid());//将用户的uid和role保存到session
            session.setAttribute("role",loggedInUser.getRole());
            response.put("state", 200);
            response.put("message", "登陆成功");
            response.put("data", loggedInUser);
        } catch (UsernameNotFoundException e) {
            response.put("state", 400);
            response.put("message", "用户名未找到");
            response.put("data", null);
        } catch (PasswordNotMatchException e) {
            response.put("state", 401);
            response.put("message", "密码不正确");
            response.put("data", null);
        } catch (RoleNotMatchException e){
            response.put("state",402);
            response.put("message","角色不正确");
            response.put("data",null);
        }
        return response;
    }
}

3.前后对接

对接这里其实就只是一个跨域问题,这个就是在后端工程的config/WebMvcConfig类里面加入设置,这里我前端的地址是http://localhost:5173

package com.hckj.dormitory_b.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:5173")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}

4.效果

在这里插入图片描述

3.界面

这里用element的menu做界面,我就把顶部底部和左侧都放在MenuView.vue里面,然后右侧内容给里面放个路由出口。
1.然后在router里面配路由,这里面二级路由嵌套时就用children属性,就会把children里面的component插入到父的<router-view></router-view>,当第一个子的path为空时,就会和父用一个路由。

import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '@/components/LoginView.vue'
import MenuView from '@/components/MenuView.vue'
import AddManagement from '@/components/Right/AddManagement.vue'
...
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/login',
      component: LoginView,
      name:'登陆'
    },
    {
      path: '/addmanagement',
      component:MenuView,
      children:[{
        path:'',
        component:AddManagement,
        name:'添加宿管'
      },
      ...
    ]
    }
  ]
})
export default router

2.登陆成功后让跳转到首页来,用到了useRouter:

import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/addmanagement');

3.在menuview里面拿到cookie里面保存的username显示到主页的右上角:

<template #title>{{ username }}</template>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const username = ref('');
const cookies = document.cookie.split('; ');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].split('=');
            if (cookie[0] === 'username') {
                username.value = decodeURIComponent(cookie[1]);
                break;
            }
        }
return {username};

4.右侧退出登录使用表单来二次确认:

<el-menu-item @click="showLogoutConfirmDialog">退出登录</el-menu-item>
const showLogoutConfirmDialog = () => {
            ElMessageBox.confirm('确定要退出登录吗?', '退出登录', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            })
                .then(() => {
                    // 用户点击确认,执行退出操作,删除 Cookie
                    document.cookie = 'username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
                    // 跳转到登录页面
                    router.push('/login');
                })
                .catch(() => {
                    // 用户点击取消,不执行退出操作
                });
        };
        return {
            showLogoutConfirmDialog,
        };

5.左侧菜单点击后跳转到固定路由并且面包屑显示name:

<el-menu-item @click="navigateToLockregist"><el-icon><Calendar /></el-icon><span>学生缺寝记录</span></el-menu-item>
//1.面包屑显示name,使用$route.name 
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ $route.name }}</el-breadcrumb-item>
//2.点击跳转路由,使用router
import { useRouter } from 'vue-router';
const router = useRouter();
const navigateToLockregist = (event) => {router.push('/lockregist');};
return {navigateToLockregist}

效果如下
在这里插入图片描述

4.宿管模块

这几个模块儿大同小异。

1.添加

这里首先是前端,用到了表单校验(rules,prop),数据绑定(model,v-model),和后端ajax请求(json数据请求,所以后端要添加注解@RequestBody才能拿到数据;上面登陆功能用的是user对象请求),请求成功后会跳到宿管管理页面:

<template>
    <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize" status-icon style="width: 300px; margin: 0 auto;">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="ruleForm.username" />
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input v-model="ruleForm.password" />
      </el-form-item>
      <el-form-item label="姓名" prop="name">
        <el-input v-model="ruleForm.name" />
      </el-form-item>
      <el-form-item label="性别" prop="gender">
        <el-radio-group v-model="ruleForm.gender">
          <el-radio label="男" />
          <el-radio label="女" />
        </el-radio-group>
      </el-form-item>
      <el-form-item label="联系电话" prop="telephone">
        <el-input v-model="ruleForm.telephone" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm(ruleFormRef)">
          立即创建
        </el-button>
        <el-button @click="resetForm(ruleFormRef)">重置</el-button>
      </el-form-item>
    </el-form>
  </template>
  
  <script lang="ts" setup>
  import axios from 'axios';
  import { reactive, ref } from 'vue'
  import type { FormInstance, FormRules } from 'element-plus'
  import { useRouter } from 'vue-router';
  interface RuleForm {
    username:string
    password:string
    name: string
    gender:string
    telephone:string
  }
  const router = useRouter();
  const formSize = ref('default')
  const ruleFormRef = ref<FormInstance>()
  const ruleForm = reactive<RuleForm>({
    username:"jack",
    password:"111",
    name: "老王",
    gender:"女",
    telephone:'1008611',
    
  })
  
  const rules = reactive<FormRules<RuleForm>>({
    username: [
      { required: true, message: '请输入用户名', trigger: 'blur' },
      { min: 2, max: 9, message: '用户名长度2-9', trigger: 'blur' },
    ],
    password: [
      { required: true, message: '请输入用户密码', trigger: 'blur' },
    ],
    name: [
      { required: true, message: '请输入用户名', trigger: 'blur' },
    ],
    telephone: [
      { required: true, message: '请输入用户手机号', trigger: 'blur' },
    ],
  })
  const genderMap = {
    '男': 0,
    '女': 1,
    };
    const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate((valid, fields) => {
    if (valid) {
      ruleForm.gender = genderMap[ruleForm.gender];
      console.log(ruleForm);
      axios.post('http://localhost:8080/users/addmanagement', ruleForm)
        .then(response => {
          const data = response.data;
          if (data.state === 200) {
            alert(ruleForm.username+'添加成功');
            router.push('/managemanagement');
          } else if (data.state === 403) {
            alert('用户名已存在');
          }else if (data.state === 404) {
            alert('插入数据发生未知错误');
          }
        })
        .catch(error => {
          console.error('请求错误:', error.message);
        });
    } else {
      console.log('error submit!', fields);
    }
  });
}
  const resetForm = (formEl: FormInstance | undefined) => {
    if (!formEl) return
    formEl.resetFields()
  }
  </script>

然后后端就按步骤,没啥知识点。

#1.mapper
Integer insertManagement(User user);
<insert id="insertManagement" useGeneratedKeys="true" keyProperty="uid">
        insert into t_user(username,password,role,name,gender,telephone) values (
        #{username},#{password},#{role},#{name},#{gender},#{telephone})
    </insert>
#2.service
void insertManagement(String username,String password,String name,Integer gender ,String telephone);
@Override
    public void insertManagement(String username,String password,String name,Integer gender ,String telephone) {
        User result = userMapper.findByUsername(username);
        if (result != null) {
            throw new UsernameDuplicateException("用户名重复");
        }
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setName(name);
        user.setRole(1);
        user.setGender(gender);
        user.setTelephone(telephone);
        Integer rows = userMapper.insertManagement(user);
        if (rows != 1) {
            throw new InsertException("在用户注册过程中产生了未知的异常");
        }
    }
#3.controller
 @PostMapping("addmanagement")
    public Map<String, Object> insertManagement(@RequestBody User user){
        Map<String, Object> response = new HashMap<>();
        try {
		userService.insertManagement(user.getUsername(),user.getPassword(),user.getName(),user.getGender(), user.getTelephone());
            response.put("state", 200);
            response.put("message", "添加宿管成功");
            response.put("data", null);
        }catch (UsernameDuplicateException e){
            response.put("state", 403);
            response.put("message", "用户名已存在");
            response.put("data", null);
        }catch (InsertException e){
            response.put("state", 404);
            response.put("message", "插入数据时发生位置错误");
            response.put("data", null);
        }
        return response;
    }

效果如下:
在这里插入图片描述

2.分页查询

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是小z呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值