一个前后端分离的实现。后端使用SpringBoot,前端使用Vue,后端标准的四层结构,前端是用的Element。
一、环境准备
1.安装node.js
Node.js 是一个开源的、跨平台的 JavaScript 运行时环境。
成功后cmd中运行 node -v npm -v 出现 对应的版本号
2.安装Vue脚手架
Vue脚手架指的是vue-cli,它是一个快速构建**单页面应用程序(SPA)**环境配置的工具,cli是(command-line-interface )命令行界面。
成功后cmd中运行 vue -V 出现 对应的版本号
3.安装webpack前端打包工具
webpack是现代javascript应用程序的静态模块打包器,当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
成功后cmd中运行 webpack -V 出现 对应的版本号
二、创建项目
一.创建Vue项目
因为在IDEA中创建项目,不会自动的创建出所需要的router文件,所以我个人感觉还是在命令行界面里创建比较好一点,因为这个会自动加进入我们需要的router,不需要我们再手动创建了(尤其是手动创建在某些时候还会报错)
-
在我们要创建的目录下打开cmd,输入命令
vue int webpack 项目名
-
一些初始化配置
-
根据命令运行项目
-
成功界面
会出现一个链接。然后在浏览器里打开这个链接,出现初始化页面,表示已经创建成功
二.创建SpringBoot项目
1.在Idea里使用Spring初始化快速建立一个空的项目。注意语言要使用java,类型要选择Maven, JDK和 java版本要对应。
2.创建成功后的默认目录
3.在main里面创建controller、pojo、mapper、service,以及service下的impl实现层,以及resorces下的mapper
三.数据库中创建表
- 设计
- 内容
恭喜!到目前为止我们已经把程序在电脑运行的环境搭建好了,并且创建了前后端和数据库的项目,下一步就是在项目里进行配置,并且写代码和调试了。
三、项目里面的环境配置
后端
Springboot中修改pom.xml 和 applicatiom.yml
- pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置spring boot的parent,这样当前的项目就是Spring Boot项目了,使用它之后,常用的包依赖可以省去version标签-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<!-- 设置项目编码格式及声明JDK版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--依赖-->
<dependencies>
<!--对全栈web开发的支持, 包括Tomcat和spring-webmvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 对Thymeleaf模板引擎的支持,包括和Spring的集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入mybatis-plus,MyBatis的增强工具-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!--引入mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--引入thymeleaf页面模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--比较喜欢这个,创建bean的时候不用写getter/setter/toString-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入fastjson,JSON转换时使用 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--Spring boot的插件,导出成jar、war包时需要-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- applicatiom.yml
#服务器配置
server:
#默认端口号
port: 8088
#tomcat服务器传输参数时的字符编码,不注意的话会导致中文乱码
tomcat.uri-encoding: UTF-8
#配置spring数据源
spring:
# 配置连接信息默认连接池
datasource:
#数据库连接地址,解释为:JDBC以Mysql的方式进行连接,IP地址为本地,端口号为3306,数据库为test,使用Unicode字符集,字符集编码为utf-8,不进行SSL连接
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
#数据库用户名
username: root
#数据库用户密码
password: Admin123@123
#数据库驱动
driver-class-name: com.mysql.jdbc.Driver
#配置hikari连接池
hikari:
#从连接池中获取的连接是否默认处于只读模式:否
read-only: false
#等待来自连接池的连接的毫秒数为60000,即60秒
connection-timeout: 60000
#连接允许在池中闲置的毫秒数为60000,即60秒
idle-timeout: 60000
#连接池中最长生命周期
max-lifetime: 3000
#指定连接数据库的超时时间
login-timeout: 500
#连接池中最大连接数
maximum-pool-size: 60
#连接池中维护的最小连接数
minimum-idle: 10
# 配置静态资源地址
resources:
#静态资源默认目录,此出配置指向resources文件夹
static-locations: classpath:/
# 配置模板引擎
thymeleaf:
# 指定文件默认目录
prefix: classpath:/templates/
# 指定文件尾缀
suffix: .html
# 是否开启缓存
cache: false
# 字符集编码
encoding: UTF-8
#mybatis plus映射文件的地址
mybatis-plus:
#设置扫描.xml文件的存放目录
mapper-locations: classpath*:/mapper/**/*.xml
#设置扫描实体bean目录
typeAliasesPackage: com.test.demo.pojo
四层结构功能
controller 控制层:负责调用 service 层接口
entity实体层:实体类是属性对象,用于供给其他层引用,该层的属性往往和数据库的表结构各个字段相同
mapper 数据层:mapper层所定义的接口要实现对数据的操作
service 业务逻辑层: 负责功能的编写,将所需要的功能都定义在一个 service 层的接口当中
前端
- 安装Element 组件库
cnpm i element-ui -S
- 在 main.js 中添加语句
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
四 编写后端页面
0.跨域问题
因为前后端使用的端口号不同,涉及到跨域。
需要使用注解符号@CrossOrigin(origins = “http://localhost:8080”)
1. 代码编写
- TUser.java
package com.test.demo.pojo;
import lombok.Data;
/**
* t_user表使用了lombok的插件,而导致序列化出现错误
*/
@Data
public class TUser {
private static final long serialVersionUID = 1L;
public TUser() {
}
/**
* ID序号
*/
private Integer id;
/**
* 用户名
*/
private String name;
/**
* 性别0男1女
*/
private String gender;
/**
* 年龄
*/
private Integer age;
private String email;
}
- TUserMapper.java
package com.test.demo.mapper;
import com.test.demo.pojo.TUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
/**
* TUser的Dao层
* 加入Mapper注解后,Mybatis的拦截器会把其的接口生成动态代理类,让spring对mapper接口的bean进行管理,
* 也可通过注解将需要使用的sql写在这
*/
@Mapper
public interface TUserMapper {
//根据实体bean返回集合
List<TUser> getAllBeanInfo();
TUser selectTUserById(Integer id);
int insertTUser(TUser tUser);
int deleteTuserById(Integer id);
int updateTUser(TUser tUser);
}
- TUserMapper.xml
<?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.test.demo.mapper.TUserMapper">
<select id="getAllBeanInfo" resultType="TUser">
select * from t_user
</select>
<select id="selectTUserById" resultType="TUser">
select * from t_user where `id` = #{id};
</select>
<insert id="insertTUser" parameterType="TUser">
insert into t_user(id,name,age,gender,email) values (#{id}, #{name}, #{age},#{gender}, #{email});
</insert>
<delete id="deleteTuserById" parameterType="integer">
delete from t_user where `id` = #{id}
</delete>
<update id="updateTUser" parameterType="TUser">
update t_user set name = #{name},age = #{age},gender = #{gender},email = #{email}
where id = #{id}
</update>
</mapper>
- TUserService
package com.test.demo.service;
import com.test.demo.mapper.TUserMapper;
import com.test.demo.pojo.TUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* TUser逻辑层
* Service注解是声明这个类是业务层,用来写复杂逻辑的
*/
public interface TUserService {
//根据实体bean返回集合
List<TUser> getAllBeanInfo();
TUser selectTUserById(Integer id);
int insertTUser(TUser tUser);
int deleteTuserById(Integer id);
int updateTUser(TUser tUser);
}
- TUserServiceImpl
package com.test.demo.service.Impl;
import com.test.demo.mapper.TUserMapper;
import com.test.demo.pojo.TUser;
import com.test.demo.service.TUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TUserServiceImpl implements TUserService {
/**
* Autowired是当Spring发现该注解时,将动在代码上下文中找到与其匹配(默认是类型匹配)的Bean,并自动注入到相应的地方去。简称自动注入.
*/
@Autowired
TUserMapper userMapper;
@Override
public List<TUser> getAllBeanInfo() {
return userMapper.getAllBeanInfo();
}
@Override
public TUser selectTUserById(Integer id) {
return userMapper.selectTUserById(id);
}
@Override
public int insertTUser(TUser tuser) {
return userMapper.insertTUser(tuser);
}
@Override
public int deleteTuserById(Integer id) {
return userMapper.deleteTuserById(id);
}
@Override
public int updateTUser(TUser tUser) {
return userMapper.updateTUser(tUser);
}
}
2. 前端展示
出现这种页面表示接口没有问题
五 编写前端页面
1.登录页面
- 新创建一个login.vue,增加一个登录页面
<template>
<div class="login-container">
<el-form :model="ruleForm2" :rules="rules2"
status-icon
ref="ruleForm2"
label-position="left"
label-width="0px"
class="demo-ruleForm login-page">
<h3 class="title">系统登录</h3>
<el-form-item prop="username">
<el-input type="text"
v-model="ruleForm2.username"
auto-complete="off"
placeholder="用户名"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password"
v-model="ruleForm2.password"
auto-complete="off"
placeholder="密码"
></el-input>
</el-form-item>
<el-checkbox
v-model="checked"
class="rememberme"
>记住密码
</el-checkbox>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:100%;" @click="handleSubmit" :loading="logining">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "login",
data(){
return {
logining: false,
ruleForm2: {
username: 'admin',
password: '123456',
},
rules2: {
username: [{required: true, message: 'please enter your account', trigger: 'blur'}],
password: [{required: true, message: 'enter your password', trigger: 'blur'}]
},
checked: false
}
},
methods: {
handleSubmit(event){
this.$refs.ruleForm2.validate((valid) => {
if(valid){
this.logining = true;
if(this.ruleForm2.username === 'admin' &&
this.ruleForm2.password === '123456'){
this.logining = false;
sessionStorage.setItem('user', this.ruleForm2.username);
this.$router.push({path: '/home'});
}else{
this.logining = false;
this.$alert('username or password wrong!', 'info', {
confirmButtonText: 'ok'
})
}
}else{
console.log('error submit!');
return false;
}
})
}
}
}
</script>
<style scoped>
.login-container {
width: 100%;
height: 100%;
}
.login-page {
-webkit-border-radius: 5px;
border-radius: 5px;
width: 350px;
padding: 35px 35px 15px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
margin: 0;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
label.el-checkbox.rememberme {
margin: 0px 0px 15px;
text-align: left;
}
.title{
text-align: center;
}
</style>
- 在 router 目录下 index.js 文件中添加 login 路径
- npm run dev ,前端运行页面如图所示
2.(跳转)后台页面
- 创建一个登陆后跳转的页面 index.vue,引入 element 布局容器 和 element 侧边栏
<template>
<el-container>
<!-- width的宽度跟collapse一样动态控制 -->
<el-aside width="collapse">
<div class="logo" v-show="open"><h3><i class="el-icon-eleme"></i>xxx管理系统</h3></div>
<div class="logo" v-show="close"><h3><i class="el-icon-eleme"></i></h3></div>
<!-- :collapse="isCollapse" class="el-menu-vertical" 动态控制导航菜单的收起与展开 router:让index作为 path 进行路由跳转 -->
<el-menu default-active="$route.path"
router
:default-openeds="openeds"
:collapse="isCollapse"
class="el-menu-vertical">
<el-submenu index="1">
<!-- 一级标题 -->
<template slot="title">
<i class="el-icon-s-tools"></i>
<span slot="title">后台管理</span>
</template>
<!-- 二级标题 -->
<el-menu-item index="/console">
<i class="el-icon-setting"></i>
<span slot="title">控制台</span>
</el-menu-item>
<el-menu-item index="/student">
<i class="el-icon-setting"></i>
<span slot="title">学生管理</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header>
<div class="trigger" @click="isShow">
<!-- 点击展开收起导航和切换对应图标 -->
<i class="el-icon-s-fold" v-show="open"></i>
<i class="el-icon-s-unfold" v-show="close"></i>
</div>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>Footer</el-footer>
</el-container>
</el-container>
</template>
<script>
export default {
name: "index",
data() {
return {
openeds: ["1"],
isCollapse: false, //导航栏默认为展开
close: false, //第二个图标默认隐藏
open: true, //默认显示第一个图标
}
},
methods: {
isShow() {
this.isCollapse = !this.isCollapse;
this.open = !this.open;
this.close = !this.close;
},
}
}
</script>
<style scoped>
.el-header, .el-footer {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
height: 100%;
padding: 0 !important;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
height: 100vh;
}
.el-main {
background-color: #E9EEF3;
color: #333;
}
body > .el-container {
margin-bottom: 40px;
}
.logo {
height: 60px;
line-height: 60px;
background-color: antiquewhite;
text-align: center;
}
.logo h3 {
margin: 0;
height: 60px;
}
.el-menu {
border-right-width: 0;
}
.el-menu-vertical:not(.el-menu--collapse) {
width: 240px;
}
.trigger {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
width: 54px;
}
.trigger i {
font-size: 20px;
}
.trigger:hover {
background-color: rgb(203, 215, 230);
}
</style>
main部分添加,表示一会要将控制台和学生管理里的内容显示在main部分。(起到一个占位符的作用)
- index.js 中添加新的import,并在routes下的home里添加children
index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import login from '@/views/login'
import home from '@/views/home'
import console from '@/views/console'
import student from '@/views/student'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},{
path: '/login',
name: 'login',
component: login
},{
path: '/home',
name: 'home',
component: home,
children:[
{
path: '/console',
name: 'console',
component: console
}, {
path: '/student',
name: 'student',
component: student
}
]
}
]
})
-
页面
-
成功后的页面
3.挂载页面
- 修改 student 页面,引入 element 表格
- 对话框控件 要考虑如何跟后台做交互,vue 推荐的是 axios。
axios是什么:axios是基于promise(诺言)用于 node.js 和浏览器中。
axios的作用:axios用于向后台发起请求,并在请求中做更多的可控功能。
- 安装
cnpm install axios --save-dev
- 在main.js 完成挂载
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import login from '@/views/login.vue'
import home from '@/views/home.vue'
import axios from 'axios'
Vue.prototype.$axios = axios
Vue.config.productionTip = false
Vue.use(ElementUI);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
axios,
components: { App },
template: '<App/>'
})
4.修改student.vue
<template>
<el-card class="box-card">
<!-- Dialog 对话框 弹出新增和修改表单 -->
<el-row>
<el-button size="mini" type="primary" @click="add">新增</el-button>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form">
<el-form-item label="id:" hidden>
<el-input v-model="form.id"></el-input>
</el-form-item>
<el-form-item label="姓名:" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名" style="width:80%"></el-input>
</el-form-item>
<el-form-item label="年龄:" prop="age">
<el-input v-model.number="form.age" placeholder="请输入年龄" style="width:80%"></el-input>
</el-form-item>
<el-form-item label="性别:" prop="gender">
<el-select v-model="form.gender" placeholder="请选择性别" style="width:80%">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<el-form-item label="邮箱:" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" style="width:80%"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="submit()">提 交</el-button>
</div>
</el-dialog>
</el-row>
<!-- 表格 -->
<el-table
ref="singleTable"
:data="tableData"
style="width: 100%">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
property="id"
label="ID"
width="50"
align="center">
</el-table-column>
<el-table-column
property="name"
label="姓名"
width="120"
align="center">
</el-table-column>
<el-table-column
property="age"
label="年龄"
width="120"
align="center">
</el-table-column>
<el-table-column
property="gender"
label="性别"
width="120"
align="center">
</el-table-column>
<el-table-column
property="email"
label="邮箱"
align="center">
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="edit(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="remove(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
export default {
name: "student",
data() {
return {
title: '',
currentRow: null,
dialogFormVisible: false,
form: {},
tableData: [],
rules: {
name: [{required: true, message: '请输入姓名', trigger: 'blur'}],
age: [{required: true, message: '请输入年龄', trigger: 'blur'},
{type: 'number', message: '年龄必须为数字值', trigger: 'blur'},
{pattern: /^(0|[1-9]\d?|200)$/, message: '范围在0-200', trigger: 'blur'}],
gender: [{required: true, message: '请选择性别', trigger: 'change'}],
email: [{required: true, message: '请输入邮箱', trigger: 'blur'}]
}
}
},
methods: {
// 表单重置初始化
reset() {
this.form = {
id: null,
name: null,
age: null,
gender: null,
email: null
}
},
// 增
add() {
this.reset()
this.dialogFormVisible = true
this.title = "新增学生数据"
},
// 删
remove(index, row) {
console.log(row.id)
this.$axios({
method: 'post',
url: 'http://localhost:8088/user/remove/' + row.id,
}).then((response) => {
this.$message({
message: '删除成功!',
type: 'success'
});
this.getList();
}).catch((error) => {
})
},
// 改
edit(index, row) {
this.reset()
this.form = JSON.parse(JSON.stringify(row));
this.dialogFormVisible = true
this.title = "修改学生数据"
},
//查
getList() {
this.$axios({
method: 'get',
url: 'http://localhost:8088/user/userBean',
}).then((response) => {
this.tableData = response.data
}).catch((error) => {
})
},
//提交按钮
submit() {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.form.id == null) {
this.$axios({
method: 'post',
data: this.form,
url: 'http://localhost:8088/user/add',
}).then((response) => {
this.$message({
message: '新增成功!',
type: 'success'
});
this.dialogFormVisible = false
this.getList();
}).catch((error) => {
})
} else {
this.$axios({
method: 'post',
data: this.form,
url: 'http://localhost:8088/user/edit',
}).then((response) => {
this.$message({
message: '修改成功!',
type: 'success'
});
this.getList();
this.dialogFormVisible = false
}).catch((error) => {
})
}
} else {
return false;
}
})
}
},
mounted() {
this.getList();
}
}
</script>
<style scoped>
</style>
到此为止,我们的功能应该是都完成了。恭喜
六 功能展示
1.增加
2.删除
3.修改
七 参考资料
SpringBoot + Vue + Element 前后端分离的管理后台项目简单入门
八 总结&踩坑
- 创建Vue项目的时候建议还是用cmd创建吧…IDEA创建很方便也很快,但是没有router文件…要手动创建还会报错…直接使用cmd会好很多,虽然他时间长它原始但它好用啊
- 在编写前端的student展示页面中,注意url地址 和 后端接口要对应起来才能拿到数据!不然会出现表格,但表格里没有数据,报错:找不到数据
- 在前后端连接的时候有一堆功能增删改查的,建议一个一个的连接,就比如展示所有的数据,先把后端那一套连接起来,再连前端,一个个功能的在各种页面里转换,然后换不同的功能再这么走一遍(因为一下子全走完了可能会报错,但很难排查错误出在哪里)。
- 前后端跨域问题记得注意一下。
- 很不完善的一个小页面,建议大家可以找教程亲自动手试一下,会发现很多看的时候发现不出来的问题。比如可能实际过程没有教程里那么顺利,会出现各种各样奇怪问题,这很正常,先办法解决掉就好。总之搞出来还是有那么一点成就感的
- 就到这里,希望这篇过程记录文能给大家一点帮助?非常感谢您能看到这里!希望大家玩得开心~