springboot+vue练手小项目[前台搭建+后台编写](非常详细)

[ springboot+vue练手小项目 ]

  • 技术栈: springboot+vue3+element-plus +Mybaties-plus+hutool
    +mysql8
  • 项目介绍 :最近刚学了springboot+vue,就想着做一个小的前后端分离的练手项目,简单的后台管理页面,有基本的登陆注册+增删改查,后面具体的模块等需要的时候的再进行完善,这只是一个练手项目,如果大家运行不出来或者有疑问,欢迎交流。
  • 我是个菜鸟,也不太会写文章,若有不足欢迎指正。

1.环境搭建

1.安装node环境,npm,cli,这里不再赘述

2.在指定文件夹使用cmd指令创建项目:vue create springboot-vue-demo

3.选择Manually select features在这里插入图片描述

4.选择路由和vuex,这里未选择了eslint语法检测[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

5.选择3.x版本[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-H4bjGfpT-1647170194103)(C:UserserhangAppDataRoamingTypora ypora-user-imagesimage-20220309152419903.png)]](https://img-blog.csdnimg.cn/01313a7a07824b64820fb1c22a09aa4f.png)

6.输入y (路由信息为history,)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-K8XKVnNm-1647170194104)(C:UserserhangAppDataRoamingTypora ypora-user-imagesimage-20220309152546319.png)]](https://img-blog.csdnimg.cn/616317c8beea4a0fa731a390d18c8dbc.png)

7.选择In package.json

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgXKp4wx-1647170194105)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309152708971.png)]

8.是否保存配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kToZAsdH-1647170194105)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309152756027.png)]

9.创建,启动项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SbmAjbi-1647170194106)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309153156801.png)]

10.启动成功,浏览器输入8080端口进行访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xyJSTE4P-1647170194107)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309153341418.png)]

11.在idea打开项目,配置vue启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECaYZ44b-1647170194108)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309153839751.png)]

选择npm,在npm中Script选项中输入serve

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0yj8MHT-1647170194108)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309154020540.png)]

之后就可以点击启动键,快捷启动项目了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ts8xxuQl-1647170194110)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309154109032.png)]

安装vue插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BoOodoxL-1647170194111)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309155231754.png)]

2.项目基本布局

1.引入Element-plus(基于vue3.x版本)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdfSdwaW-1647170194111)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309161033385.png)]

在idea终端输入指令引入Element-ui依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpot5kCY-1647170194112)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309161155066.png)]

项目中引入Element-ui,main.js文件中引入Element-plus

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJK0eYQE-1647170194112)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220309162320799.png)]

2.删除HelloWorld组件,删除App.vue的多余部分

在components中创建Header组件 在App.js中引入

创建css文件夹,创建global.css文件,在main.js中引入全局css样式

在components中创建Aside(侧边栏),app.vue引入组件

Aside中引入emement导航栏样式

Home中引入四个区域 功能 搜索 表格 分页作为主体区域

3.项目目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56llqBrZ-1647170194113)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310133522560.png)]

4.代码

global.css

*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

Aside.vue

<template>
  <el-row class="tac">
    <el-col :span="24">
      <el-menu
          active-text-color="#ffd04b"
          background-color="#545c64"
          class="el-menu-vertical-demo"
          default-active="2"
          style="width: 200px;min-height:100vh"
          text-color="#fff"
      >
        <el-sub-menu index="1">
          <template #title>
            <el-icon><location /></el-icon>
            <span>选项1</span>
          </template>
          <el-menu-item index="1-1">
            <el-icon><setting /></el-icon>
            <span>选项1-1</span>
          </el-menu-item>
        </el-sub-menu>


        <el-sub-menu index="2">
          <template #title>
            <el-icon><location /></el-icon>
            <span>选项2</span>
          </template>
          <el-menu-item index="2-1">
            <el-icon><setting /></el-icon>
            <span>选项2-1</span>
          </el-menu-item>
        </el-sub-menu>

        <el-menu-item index="3">
          <el-icon><icon-menu /></el-icon>
          <span>3</span>
        </el-menu-item>
      </el-menu>
    </el-col>
  </el-row>
</template>

<script>
export default {
  name: "Aside"
}
</script>

<style scoped>

</style>

Header.vue

<template>
  <div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc;display: flex">
    <div style="width: 200px;font-weight: bold;padding-left: 20px;color: blue">后天管理</div>
    <div style="flex: 1"></div>
    <div style="width: 100px;margin-right: 20px;margin-top: 20px">
      <el-dropdown>
        <span class="el-dropdown-link">
          张三
          <el-icon class="el-icon--right">
            <arrow-down/>
          </el-icon>
        </span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item>个人信息</el-dropdown-item>
            <el-dropdown-item>退出系统</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
export default {
  name: "Header"
}
</script>

<style scoped>

</style>

router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },

]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

HomeView.vue

<template>
  <div class="home" style="padding: 10px">
    <!--    功能区域-->
    <div style="margin: 10px 0">
      <el-button type="primary">新增</el-button>
      <el-button type="primary">导入</el-button>
      <el-button type="primary">导出</el-button>
    </div>
    <!--    搜索区域-->
    <div style="margin: 10px 0">
      <el-input v-model="search" style="width: 20%;" placeholder="请输入关键字"></el-input>
      <el-button type="primary" style="margin-left: 5px">查询</el-button>
    </div>
    <!--    表格区域-->
    <el-table :data="tableData" border stripe style="width: 100%">
      <el-table-column prop="id" label="ID" sortable/>
      <el-table-column prop="username" label="用户名"/>
      <el-table-column prop="nickName" label="昵称"/>
      <el-table-column prop="age" label="年龄"/>
      <el-table-column prop="sex" label="性别"/>

      <el-table-column label="操作">
        <template #default="scope">
          <el-button size="small" @click="handleEdit(scope.$index, scope.row)"
          >编辑
          </el-button>

          <el-popconfirm title="确定删除吗?">
            <template #reference>
              <el-button
                  size="small"
                  type="danger"
                  @click="handleDelete(scope.$index, scope.row)"
              >删除
              </el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <!--    分页-->
    <div style="margin: 10px 0">
  		<el-pagination
          :currentPage="currentPage"
          :page-sizes="[5,10,20]"
          :page-size="pageSize"
          :small="small"
          :disabled="disabled"
          :background="background"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      >
      </el-pagination>
    </div>
  </div>
</template>

<script>

export default {
  name: 'HomeView',
  components: {},
  data() {
    return {
      form:{},
      dialogVisible: false,
      serch: '',
      currentPage: 1,
      pageSize:"10",
      total:  0,
      tableData: [
        {
          id: '1',
          name: 'Tom',
          nickName: '189号',
          age: 18,
          sex: "男",

        },
        {
          id: '1',
          name: 'Tom',
          nickName: '189号',
          age: 18,
          sex: "男",

        },
        {
          id: '1',
          name: 'Tom',
          nickName: '189号',
          age: 18,
          sex: "男",

        },
        {
          id: '1',
          name: 'Tom',
          nickName: '189号',
          age: 18,
          sex: "男",

        },
      ]
    }
  },
  methods: {
    handleEdit() {
    },
    handleDelete() {
    },
    handleSizeChange(){},
    handleCurrentChange(){}
  }
}
</script>

App.vue

<template>
  <nav>
    <!--头部-->
    <Header></Header>
    <!--主题-->
    <div style="display: flex">
      <!-- 侧边栏-->
      <Aside></Aside>
      <!--内容区域-->
      <router-view style="flex: 1;"></router-view>
    </div>
  </nav>1

</template>

<script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";

export default {
  name: "App",
  components: {
    Header,
    Aside
  }
}
</script>

main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import '@/assets/css/global.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'


createApp(App).use(store).use(router).use(ElementPlus, {locale: zhCn,}).mount('#app')

3.后台编写

在项目名称右键new一个Moudule为springboot

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eNMuRjoZ-1647170194114)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310092435766.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U17gkgNC-1647170194114)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310092604149.png)]

在项目目录下新建vue文件夹 将原来vue的工程文件拷贝进去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7HpFHg6-1647170194115)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310093145571.png)]

重新配置serve,因为项目结构改变,要重新选择vue的package.json

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dizv0dNW-1647170194115)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310093324415.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVdVhpZM-1647170194116)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310093521214.png)]

修改项目的maven为自己的本地maven仓库

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.6.4</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.example</groupId>
   <artifactId>lesson08</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>lesson08</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>1.8</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>2.2.2</version>
      </dependency>

      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <scope>runtime</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid-spring-boot-starter</artifactId>
         <version>1.1.21</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>

</project>

检验是否配置成功

如果报错则将vue文件夹的node_modules文件夹删除 再vue项目执行命令npm install

新的目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIhuuKvi-1647170194116)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310101019845.png)]

springboot的resource目录下application.property文件修改为application.yml

输入配置(我这是mysql8版本 配置了druid数据库连接池)

server:
  port: 9090

spring:
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/springboot-vue
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

      initialSize: 10
      maxActive: 20
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      testWhileIdle: true
      testOnBorrow: true
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
      validationQuery: SELECT 1
      validation-query-timeout: 500
      filters: stat,wall

      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: 10197538

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.syb.po

启动mysql服务

再navicat里新建数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n9CS2DG9-1647170194117)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310102239367.png)]

新建user表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ly41P8jg-1647170194117)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310102550784.png)]

点击启动,启动成功则配置完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsCJaAWA-1647170194118)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220310102912554.png)]

引入mybaties-plus pom文件添加依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3.1</version>
    </dependency>

导入分页插件包

com.example.spring目录下创建mapper包

com.example.springboot包创建common包,创建MybatiesPlusCongif类

package com.example.springboot.common;


import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@MapperScan("com.example.springboot.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

}

创建Result包包装返回类型工具类,新建Result

package com.example.springboot.common;

public class Result<T>
{
    private String code;
    private String msg;
    private T data;

    public String getCode()
    {
        return code;
    }

    public void setCode(String code)
    {
        this.code = code;
    }

    public String getMsg()
    {
        return msg;
    }

    public void setMsg(String msg)
    {
        this.msg = msg;
    }

    public T getData()
    {
        return data;
    }

    public void setData(T data)
    {
        this.data = data;
    }

    public Result()
    {
    }

    public Result(T data)
    {
        this.data = data;
    }

    public static Result success(){
        Result result = new Result<>();
        result.setCode("0");
        result.setMsg("成功");
        return result;
    }
     public static <T> Result<T> success(T data){
        Result<T> result = new Result<>(data);
        result.setCode("0");
        result.setMsg("成功");
        return  result;
     }

     public static Result error(String code,String msg){
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
     }
}

com.example.spring目录下新建controller包,新建UserController类

com.example.spring目录下新建entity包,新建User类

package com.example.springboot.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

//数据库表名
@TableName("user")
@Data
public class User
{
//    设置ID自增长
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String sex;
//    private String address
}

定义UserMapper接口,继承BaseMapper,传入的泛型为User

再UserConroller中使用@Resource注解引入UserMapper(这里为了简化没有创建service)

UserMapper:

package com.example.springboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springboot.entity.User;

public interface UserMapper extends BaseMapper<User>
{
}

UserConroller:

package com.example.springboot.controller;

import com.example.springboot.common.Result;
import com.example.springboot.entity.User;
import com.example.springboot.mapper.UserMapper;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

//定义是返回json的controller
@RestController
//定义统一的路由
@RequestMapping("/user")
public class UserController
{
    //引入UserMaooer
    @Resource
    UserMapper userMapper;

    //  定义post接口
    @PostMapping
    //  新增 RequestBody注解把前台传来的数据转换成java对象实体
    public Result<?> save(@RequestBody User user)
    {
        //使用userMapper的insert方法新增user对象
        userMapper.insert(user);
        return Result.success();
    }
}

4.前台业务完善

1.在新增按钮中绑定add方法添加用户数据,点击后有弹窗提示

引入elementui Dialog 对话框组件,引入表单组件

点击新增按钮后弹出对话框,点击确认后使用axios将数据存入后台

    add() {
      //打开弹窗
      this.dialogVisible = true;
      //清空
      this.form = {}
    },
        //点击确认后保存到后台
    save() {
      //使用axios进行数据交互
      //两个参数,url和请求参数

      /*      注意这样写会有跨域问题
              request.post("/user",this.form).then(res => {
              console.log(res)
            })

            request.post("http://localhost:9090/user",this.form).then(res => {
              console.log(res)
            })
      */
      //如果id存在 执行更新操作  否则执行新增操作
      if (this.form.id) {//更新
        request.put("/user", this.form).then(res => {
          console.log(res)
          //根据状态码判断 0为成功 这里用了elmentui 的提示框
          if (res.code === '0') {
            this.$message({
              type: "success",
              message: "更新成功"
            })
          } else {
            this.$message({
              type: "error",
              message: res.msg
            })
          }

        })
      }

    },

5.前后台数据交互

1.封装axios接口

  • 安装axios依赖,在vue目录下执行npm i axios -S

  • 在vue项目scr目录下新建utils文件夹,新建request.js文件,在request.js里引入axios,request.js用来请求数据

    //引入axios包
    import axios from ‘axios’
    //创建request对象
    const request = axios.create({
    baseURL: ‘/api’, // 注意!! 这里是全局统一加上了 ‘/api’ 前缀,也就是说所有接口都会加上’/api’前缀在,页面里面写接口的时候就不要加 ‘/api’了,否则会出现2个’/api’,类似 '/api/api/user’这样的报错,切记!!!
    timeout: 5000
    })

    // request 拦截器
    // 可以自请求发送前对请求做一些处理
    // 比如统一加token,对请求参数统一加密
    request.interceptors.request.use(config => {
    //设置请求头
    config.headers[‘Content-Type’] = ‘application/json;charset=utf-8’;

    // config.headers['token'] = user.token;  // 设置请求头
    return config
    

    }, error => {
    return Promise.reject(error)
    });

    // response 拦截器
    // 可以在接口响应后统一处理结果
    request.interceptors.response.use(
    response => {
    //获取返回结果的data
    let res = response.data;
    // 如果是返回的文件
    if (response.config.responseType === ‘blob’) {
    return res
    }
    // 兼容服务端返回的字符串数据
    if (typeof res === ‘string’) {
    res = res ? JSON.parse(res) : res
    }
    return res;
    },
    error => {
    console.log(‘err’ + error) // for debug
    return Promise.reject(error)
    }
    )

    export default request

  • 解决跨域问题,vue文件夹下新建vue.config.js文件,重启项目(这里将端口设置为9876)

    // 跨域配置
    module.exports = {
    devServer: { //记住,别写错了devServer//设置本地默认端口 选填
    port: 9876,
    proxy: { //设置代理,必须填
    ‘/api’: { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定
    target: ‘http://localhost:9090’, //代理的目标地址
    changeOrigin: true, //是否设置同源,输入是的
    pathRewrite: { //路径重写
    ‘^/api’: ‘’ //选择忽略拦截器里面的内容 把api解析为空字符串
    }
    }
    }
    }
    }

  • HomeView.vue中的save方法使用request.post方法访问后台的接口

    save(){
    request.post("/user",this.form).then(res => {
      console.log(res)
    }
    

2.实现了添加功能,前台点击确认,数据库有数据。接下来要实现前台的数据渲染

  • 导入hutool工具类,引入依赖

    cn.hutool hutool-all 5.7.3
  • 在UserController中新增findpage方法

    //  定义get接口
    @GetMapping
    /**
     * @pageNum 当前页 默认为1
     * @pageSize 分页大小 默认为10
     * @search 查询关键字 默认为空字符串
     */
    public Result<?> findPage(@RequestParam(defaultValue = "1") Integer pageNum,
                              @RequestParam(defaultValue = "10") Integer pageSize,
                              @RequestParam(defaultValue = "") String search)
    {
        //Page是MybatiesPlus提供的类
        //分页对象   Wrappers 条件构造器, 通过搜索框绑定的search值,在用户列表中拆模糊查询
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
        //当search为空时查询全部
        if (StrUtil.isNotBlank(search)){
            wrapper.like(User::getNickName,search);
        }
        Page<User> userPage = userMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
        return Result.success(userPage);
    }
    
  • 将查到的数据在渲染到前端tableData变量中

HomeView.vue中定义load方法,加载后台数据。在生命周期created(页面加载完成后) 调用load方法,即可在前端拿到后台的数据。

在load().then()内将拿到对象的具体data赋值给tableData,即可将后台数据渲染至前端页面

HomeView.vue

//生命周期函数
created() {
  //调用load()
  this.load()
},
methods: {
  load() {
      //传入三个参数 get请求不能传对象
      request.get("/user", {
        params: {
          pageNum: this.currentPage,
          pageSize: this.pageSize,
          search: this.search
        }
      }).then(res => {
        this.tableData = res.data.records;
        //实现前端显示总条数
        this.total = res.data.total;
        // console.log(res)
      })
    },

6.crud

1.查询

给查询按钮点击事件绑定load方法,点击按钮即可查询

<el-input v-model="search" @input="onInput" style="width: 20%;" clearable placeholder="请输入关键字"  />
<el-button type="primary" @click="load" style="margin-left: 5px">查询</el-button>

//注意我这里遇到了el-input输入框无法实时刷新的问题,在网上寻找后发现这应该是element-ui的一个bug,解决方案是:在el-input标签绑定@input事件  @input="onInput"

定义事件
onInput(){
      this.$forceUpdate();
    }

2.编辑功能

点击编辑按钮,出现弹窗,并且弹窗里有原本的数据

为了避免点击取消后数据依然会有变化,这里选择深拷贝

现在在弹窗点击确定时,后台会报错,因为之前post只有一个新增方法,数据的主键ID重复,所以会报错,这里我们需要改写之前的save方法,并在UserController新增一个update方法

编辑按钮绑定handleEdit方法

 <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>

handleEdit(row) {
  this.form = JSON.parse(JSON.stringify(row));
  this.dialogVisible = true
},

改写save方法

save() {
  //使用axios进行数据交互
  //两个参数,url和请求参数

  /*      注意这样写会有跨域问题
          request.post("/user",this.form).then(res => {
          console.log(res)
        })

        request.post("http://localhost:9090/user",this.form).then(res => {
          console.log(res)
        })
  */
  //如果id存在 执行更新操作  否则执行新增操作
  if (this.form.id) {//更新
    request.put("/user", this.form).then(res => {
      console.log(res)
      //根据状态码判断 0为成功 这里用了elmentui 的提示框
      if (res.code === '0') {
        this.$message({
          type: "success",
          message: "更新成功"
        })
      } else {
        this.$message({
          type: "error",
          message: res.msg
        })
      }

      this.load()//刷新表格数据
      this.dialogVisible = false //关闭弹窗
    })
  } else {//新增
    request.post("/user", this.form).then(res => {
      console.log(res)
      if (res.code === '0'){
        this.$message({
          type: "success",
          message:"新增成功"
        })
      }else {
        this.$message({
          type: "error",
          message:res.msg
        })
      }

      this.load()//刷新表格数据
      this.dialogVisible = false //关闭弹窗
    })
  }

},

UserController添加update方法

//    一般put进行更新
@PutMapping
public Result<?> update(@RequestBody User user)
{
    userMapper.updateById(user);
    return Result.success();
}

3.删除:

确认删除按钮,confirm事件绑定handleDelete方法,根据主键id删除

          <el-popconfirm title="确定删除吗?" @confirm="handleDelete(scope.row.id)">
            <template #reference>
              <el-button
                  size="small"
                  type="danger"
              >删除</el-button>
            </template>
          </el-popconfirm>

拿到id后,在后台写一个删除接口

UserController

   //占位符方式传入参数id  必须用@PathVariable接收
    @DeleteMapping("/{id}")
    public Result<?> delete(@PathVariable Long id)
    {
        userMapper.deleteById(id);
        return Result.success();
    }

分页功能:

HomeView中有两个方法:

handleSizeChange(),改变当前每页个数触发和handleCurrentChange(),改变当前页数触发

handleSizeChange(pageSize) {
  this.pageSize = pageSize
  this.load();
},
handleCurrentChange(currentPage) {
  this.currentPage = currentPage
  this.load();
}

7.登陆注册

因为之前实在App.vue作为页面的框架,所有的页面都是在App.vue里,App在main.js作为根节点,因此应该将App.vue作为访问所有界面的结点,根据具体的路由进行页面展示

重构App.vue

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: "App",
  components: {}
}
</script>

在src目录下新建目录Layout,新建Layout.vue作为框架页面

<template>
  <div>
    <!--头部-->
    <Header></Header>
    <!--主题-->
    <div style="display: flex">
      <!-- 侧边栏-->
      <Aside></Aside>
      <!--内容区域-->
      <router-view style="flex: 1;"></router-view>
    </div>
  </div>
</template>

<script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";

export default {
  name: "Layout",
  components:{
    Header,
    Aside
  }
}
</script>

<style scoped>

</style>

修改HomeView.vue为Home.vue

配置路由

router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Layout from '../Layout/Layout.vue'
import Login from '../views/Login.vue'

const routes = [
  {
    //默认路由地址为Layout
    path: '/',
    name: 'Layout',
    component: Layout,
    //重定向设置路由跳转,当访问'/'时自动跳转到'/home'
    redirect:"/home",
    //嵌套子路由 主体区域展示
    children:[
      {
        path:"home",
        name:"Home",
        component:() => import("@/views/Home")
      }
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import("@/views/Login")
  },

]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

编写登陆页面

vies/Login.vue

<template>
  <div style="width: 100%;height: 100vh;overflow:hidden ;background-color: darkcyan;">
    <div style="width: 400px;margin: 150px auto">
      <div style="color:#cccccc; padding: 30px 0; font-size: 30px;text-align: center">欢迎登陆</div>
      <el-form ref="formRef" :model="form" size="normal" :rules="rules">
        <el-form-item prop="username">
          <el-input placeholder="请输入用户名" v-model="form.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input placeholder="请输入密码" v-model="form.password" style=""></el-input>
        </el-form-item>
        <el-form-item>
          <el-button style="width: 100%;" @click="login" type="primary">登录</el-button>
        </el-form-item>
        <el-form-item>
          <el-button style="width: 100%;" @click="toRegister" type="primary">注册</el-button>
        </el-form-item>

      </el-form>
    </div>
  </div>
</template>

<script>
import request from "@/utils/request";

export default {
  name: "Login",
  components: {},
  data() {
    return {
      form: {},
      rules: {
        username: [
          {
            required: true,
            message: '请输入用户名',
            trigger: 'blur',
          },
        ],
        password: [
          {
            required: true,
            message: '请输入密码',
            trigger: 'blur',
          },
        ],
      }
    }
  },
  methods: {
  toRegister(){
  	this.$router.push("/login")
  },
    login() {
      //满足校验规则才传给后台数据
      this.$refs['formRef'].validate((valid) => {
        if (valid) {
          //将登录信息传给后台
          request.post("/user/login", this.form).then(res => {
            if (res.code === '0') {
              this.$message({
                type: "success",
                message: "登陆成功"
              })
               //登陆成功后将用户信息存到sessionStorage
              sessionStorage.setItem("user",JSON.stringify(res.data))
              this.$router.push("/") //登录成功后进行页面跳转 跳转到主页
            } else {
              this.$message({
                type: "error",
                message: res.msg
              })
            }
          })
        }
      })

    }
  }
}

</script>

<style scoped>

</style>

UserCOntroller添加登录接口

//登录接口
@PostMapping("/login")
public Result<?> login(@RequestBody User user)
{
    User res = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername,user.getUsername()).eq(User::getPassword,user.getPassword()));
    //判断查询是否存在
    if (res == null){
        return Result.error("-1","用户名或密码错误");
    }
    return Result.success(res);
}

登陆后点击退出系统,重新返回登录界面

在Header.vue的添加路由跳转

<el-dropdown-menu>
  <el-dropdown-item>个人信息</el-dropdown-item>
  <el-dropdown-item @click="$router.push('/login')">退出系统</el-dropdown-item>
</el-dropdown-menu>

注册界面

在views目录下新建Register.vue

<template>
  <div style="width: 100%;height: 100vh;overflow:hidden ;background-color: darkcyan;">
    <div style="width: 400px;margin: 150px auto">
      <div style="color:#cccccc; padding: 30px 0; font-size: 30px;text-align: center">欢迎注册</div>
      <el-form ref="formRef" :model="form" size="normal" :rules="rules">
        <el-form-item prop="username">
          <el-input placeholder="请输入用户名" v-model="form.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input placeholder="请输入密码" v-model="form.password" style=""></el-input>
        </el-form-item>
        <el-form-item prop="confirm">
          <el-input placeholder="确认密码" v-model="form.confirm" style=""></el-input>
        </el-form-item>
        <el-form-item>
          <el-button style="width: 100%;" @click="register" type="primary">注册</el-button>
        </el-form-item>
        <el-form-item>
          <el-button style="width: 100%;" @click="toLogin" type="primary">已有帐号</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
import request from "@/utils/request";

export default {
  name: "Register",
  components: {},
  data() {
    return {
      form: {},
      //表单校验规则
      rules: {
        username: [
          {
            required: true,
            message: '请输入用户名',
            trigger: 'blur',
          },
        ],
        password: [
          {
            required: true,
            message: '请输入密码',
            trigger: 'blur',
          },
        ],
        confirm: [
          {
            required: true,
            message: '请确认密码',
            trigger: 'blur',
          },
        ],
      }
    }
  },
  methods: {
    toLogin(){
      this.$router.push("/login")
    },
    register() {
      //判断两次密码是否一致
      if (this.form.password !== this.form.confirm) {
        this.$message({
          type: "error",
          message: "2次密码输入不一致!"
        })
        return
      }

      //满足校验规则才传给后台数据
      this.$refs['formRef'].validate((valid) => {
        if (valid) {
          //将登录信息传给后台
          request.post("/user/register", this.form).then(res => {
            if (res.code === '0') {
              this.$message({
                type: "success",
                message: "注册成功"
              })
              this.$router.push("/login") //Register成功后进行页面跳转 跳转到登录
            } else {
              this.$message({
                type: "error",
                message: res.msg
              })
            }
          })
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

router/index.js新增路由

{
  path: '/register',
  name: 'Register',
  component: () => import("@/views/Register")
},

UserController新增注册接口

//注册接口
@PostMapping("/register")
public Result<?> register(@RequestBody User user)
{
    //注册之前先验证是否有重名

    User res = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername,user.getUsername()));
    //判断查询是否存在
    if (res != null){
        return Result.error("-1","用户名重复!");
    }
    //默认密码
    if(user.getPassword() == null){
        user.setPassword("123456");
    }
    userMapper.insert(user);
    return Result.success();
}

8.项目路由

我们发现默认打开的是用户界面,因此Home.vue命名不太合适

更名为User.vue,同时将router/index.js中的home都改为user

在Aside.vue的el-menu标签里写入router,可以直接根据el-menu-item的index进行路由跳转

为了实现效果,在views下新建Book.vue,同时写入路由

<template>
  <div>
    我是book
  </div>
</template>

<script>
export default {
  name: "book"
}
</script>

<style scoped>

</style>


import { createRouter, createWebHistory } from 'vue-router'
import Layout from '../Layout/Layout.vue'
import Login from '../views/Login.vue'

const routes = [
  {
    //默认路由地址为Layout
    path: '/',
    name: 'Layout',
    component: Layout,
    //重定向设置路由跳转,当访问'/'时自动跳转到'/home'
    redirect:"/user",
    //嵌套子路由 主体区域展示
    children:[
      {
        path:"user",
        name:"User",
        component:() => import("@/views/User")
      },
      {
        path:"book",
        name:"Book",
        component:() => import("@/views/Book")
      },

    ]
  },

  {
    path: '/login',
    name: 'Login',
    component: () => import("@/views/Login")
  },
  {
    path: '/register',
    name: 'Register',
    component: () => import("@/views/Register")
  },

]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

点击菜单栏可以发现实现了路由的跳转

这里有一个bug,每次重新进入项目都会自动进去user,无论我们有没有登录。因此我们可以在request.js里设置拦截器,登陆成功则将用户信息存储在sessionStorage中命名为user变量,若sessionStoragem没有用户信息,则重启项目强制跳转到登陆界面

request.js

//引入axios包
import axios from 'axios'
import router from "@/router";
//创建request对象
const request = axios.create({
    baseURL: '/api',  // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
    timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';

    // config.headers['token'] = user.token;  // 设置请求头
    //取出sessionStorage里面的缓存的用户信息
    let userJson = sessionStorage.getItem("user");
    if (!userJson){
        //如果没有该用户信息则跳转到登陆页面
        router.push("login")
    }

    return config
}, error => {
    return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        //获取返回结果的data
        let res = response.data;
        // 如果是返回的文件
        if (response.config.responseType === 'blob') {
            return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        return res;
    },
    error => {
        console.log('err' + error) // for debug
        return Promise.reject(error)
    }
)


export default request

可以看到,登陆成功后,sessionStorage存储了用户信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIoagdBA-1647173109764)(C:UserserhangAppDataRoamingTypora	ypora-user-imagesimage-20220311162146379.png)]

有了sessionStorage对象,我们可以页面右上角的用户实时更新为用户名

在Header.vue添加数据user:JSON.parse(sessionStorage.getItem(“user”))(这里是将JSON字符串转化为JSON对象),

<template>
  <div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc;display: flex">
    <div style="width: 200px;font-weight: bold;padding-left: 20px;color: blue">后台管理</div>
    <div style="flex: 1"></div>
    <div style="width: 100px;margin-right: 20px;margin-top: 20px">
      <el-dropdown>
        <span class="el-dropdown-link">
        <!--绑定user的username属性-->
          管理员:{{ user.username }}
          <el-icon class="el-icon--right">
            <arrow-down/>
          </el-icon>
        </span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item @click="$router.push('/person')">个人信息</el-dropdown-item>
            <el-dropdown-item @click="$router.push('/login')">退出系统</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
export default {
  name: "Header",
  data() {
    return {
      //注意这里是将JSON字符串转化为JSON对象 m
      user: sessionStorage.getItem("user")?JSON.parse(sessionStorage.getItem("user")):""
    }
  }
}
</script>

<style scoped>

</style>

我们新建Person.vue

运行项目

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页
评论

打赏作者

普通网友

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值