Springboot+Vue实现简单的前端后分离数据交互
目录
一,前后端分离介绍
二,与传统单体结构的比较
2.1,传统单体结构开发
2.2,前后端分离结构开发
三,简单实现前后端数据交互
1,准备的环境及工具
2,开发步骤
2.1,后端部分
2.2,前端部分
2.3,项目运行及实现结果
编辑
四,简单总结
一,前后端分离介绍
所谓的前后端分离,就是将一个应用开发的前后端编码分开来写。后端负责处理,存储数据,而前端,则主要通过后端提供的数据接口,对数据进行渲染展示。通过这样前后端分工合作,使得项目的分工更加明确,大大降低了前后端代码的之间的耦合度,提高了开发效率。
二,与传统单体结构的比较
2.1,传统单体结构开发
传统的开发主要是,前端通过编写HTML静态界面,然后通过将界面内嵌到JSP中,或者使用Thymeleaf模板解析HTML静态界面。后端通过MVC架构中的DispatcherServlet将处理请求中的数据渲染ModelAndView或者Model到指定的静态界面中,从而达到前后端的整合。
整体的结构如图所示:
在这样一体化的构造中,如果后端业务逻辑需要更改或者数据获取出现问题,前端就要跟后端协调沟通,或者业务功能更加复杂时,这样一体化的弊端就会愈发明显。耦合度高,开发麻烦,严重影响开发效率。
2.2,前后端分离结构开发
采用前后端分离的方式进行开发时,前端只需要独立去编写客户端代码,后端专心于编写服务端代码,然后提供数据接口即可。前端开发人员与后端人员通过约定好接口文档(URL,数据类型,参数)就可以分别进行独立开发,并且,前端还可以进行伪数据构造进行数据展示测试。不需要完全依赖于后端,最后集成一下就能实现相应的业务功能了,从而达到前后端的解耦,大大提高开发效率。
整体结构如图所示:
这样开发以后,前后端开发人员可以更专注于自己擅长的领域,实现职责分离。
- 前后端仅仅通过异步接口(AJAX/JSONP)来编程
- 前后端都各自有自己的开发流程,构建工具,测试集合
- 关注点分离,前后端变得相对独立并解耦合
前端 | 后端 |
接受,展示数据 | 提供数据 |
处理渲染逻辑 | 处理业务逻辑 |
MVVM架构 | MVC架构 |
专注于客户端代码构造 | 专注于服务器代码构造 |
三,简单实现前后端数据交互
1,准备的环境及工具
开发准备 | 前端 | 后端 |
环境 | node.js | jdk1.8,tomcat9,mysql8 |
技术集成 | vue,axios,element-plus | Springboot,MyBatis-plus |
开发工具 | Visual Studio Code | IntelliJ IDEA 2022.1,Navicat Premium ,(ApiPost6) |
2,开发步骤
2.1,后端部分
2.1.1,构造一个数据库,准备一张用于数据展示的数据表
create table test_user
(id int(20) auto_increment comment '用户id'primary key,name varchar(30) null comment '用户姓名',sex tinyint(1) null comment '性别(1为男,0为女)',address varchar(45) null comment '用户地址',createTime datetime null,constraint id_indexunique (id) comment 'id为唯一索引'
);
构建存储过程快速插入100条数据,详情方法查看如何使用存储过程快速插入数据
2.1.2,在IDEA里创建一个SpringBoot项目,并导入相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- MP代码生成器依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version></dependency><!-- 3.0版本的swagger依赖--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.5</version></dependency>
2.1.3,配置yml文件里面的数据库配置
server:port: 8090
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverpassword: 123456username: rooturl: jdbc:mysql://localhost:3306/db_user?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTCmvc:format:date-time: yyyy-MM-dd HH:mmmybatis-plus:mapper-locations: classPath*:/mapper/*.xmlconfiguration:map-underscore-to-camel-case: false # 禁止大写变小写时自动添加下划线log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.1.4,通过MyBatis-plus代码生成器,直接构建基础的项目结构(pojo,service,dao,serviceImpl,controller),通过MyBatis-Plus自动生成代码后,我们基本的一些代码全部省去了,包括业务需要的简单增删改查也全部轻松自动搞定。需要注意的是,自动生成的代码并不能完全适用于我们所有业务,如果业务需求有变,还是需要我们自己手动编写动态SQL,不要过于依赖框架哦~~~~这样后端基本的框架就搭建成功了
2.1.5, 考虑到在前后端数据对介绍会涉及到跨域问题,接口文档对接问题,因此需要简单编写一下跨域,Swagger的配置类。
package com.yy.Config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;/*** @author young* @date 2022/8/23 17:33* @description: 跨域配置*/
@Configuration
public class CorsConfig {/*** 最大有效时长*/private static final long MAX_TIMEOUT=24*60*60;@Beanpublic CorsConfiguration corsConfiguration(){CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setMaxAge(MAX_TIMEOUT);//设置访问源请求头corsConfiguration.addAllowedHeader("*");//设置访问源地址corsConfiguration.addAllowedOrigin("*");//设置访问源请求方法corsConfiguration.addAllowedMethod("*");return corsConfiguration;}/*** 设置跨域配置* @return*/@Beanpublic CorsFilter corsFilter(){UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",corsConfiguration());return new CorsFilter(source);}
}
package com.yy.Config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;@Configuration
@EnableSwagger2//开启Swagger2
public class SwaggerConfig {//添加分组@Beanpublic Docket docket1(){return new Docket(DocumentationType.SWAGGER_2).groupName("黎治跃");}//配置Swagger的Docket的bean实例@Beanpublic Docket docket(Environment environment){//设置要显示的Swagger环境Profiles profiles = Profiles.of("dev", "test");//获取项目环境boolean b = environment.acceptsProfiles(profiles);System.out.println(b);return new Docket(DocumentationType.SWAGGER_2).groupName("YY").apiInfo(apiInfo()).enable(b) //是否启动Swagger,false,则浏览器无法访问Swagger.select()//RequestHandlerSelectors,配置要扫描的接口方式//basePackage指定要扫描的包//any : 扫描全部//none :不扫描//withClassAnnotation : 扫描类上的注解.apis(RequestHandlerSelectors.basePackage("com.yy.controller"))//path :过滤路径//.paths(PathSelectors.ant("/yy/**")).build();
}//配置Swagger信息=apiinfoprivate ApiInfo apiInfo(){Contact contact = new Contact("YY", "https://www.4399.com", "2463252763@qq.com");return new ApiInfo("YY的SwaggerAPI文档", "黎治跃失恋了,2022/8/19","1.0v","urn:tos",contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0",new ArrayList<>());}
}
为了更好地清晰的让我们获取的信息与前端统一,也可以编写一个统一返回值的类,让返回结果以自定义的JSON格式展示出来,方便我们阅读。
定义一个方便获取常量的枚举类
/*** @author young* @date 2022/8/19 21:36* @description: 响应结果枚举类*/@AllArgsConstructor
@Getter
public enum ResponseEnum {/**响应成功**/SUCCESS(200, "操作成功"),/**操作失败*/FAIL(201,"获取数据失败"),/**错误请求**/ERROR(400,"错误请求"),/**页面未找到**/NOT_FOUND(404,"页面未找到"),/**系统异常**/SYS_ERROR(-1,"系统异常"),/*信息已存在*/MSG_ERROR(409,"信息已存在");/**响应码**/private final Integer code;/** 结果 **/private final String resultMessage;public static ResponseEnum getResultCode(Integer code){for (ResponseEnum value : ResponseEnum.values()) {if (code.equals(value.getCode())){return value;}}return ResponseEnum.ERROR;}
/*
简单测试一下*/public static void main(String[] args) {ResponseEnum resultCode = ResponseEnum.getResultCode(100);System.out.println(resultCode);}
}
定义统一返回值的类
package com.yy.utils;
import com.yy.enums.ResponseEnum;
import lombok.Data;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;/*** @author young* @date 2022/8/19 21:52* @description: 统一返回结果的类*/@Data
public class R<T> implements Serializable {private static final long serialVersionUID = 56665257248936049L;/**响应码**/private Integer code;/**返回消息**/private String message;/**返回数据**/private T data;private R(){}/*** 操作成功ok方法*/public static <T> R<T> ok(T data) {R<T> response = new R<>();response.setCode(ResponseEnum.SUCCESS.getCode());response.setMessage(ResponseEnum.SUCCESS.getResultMessage());response.setData(data);return response;}/*** 编译失败方法*/public static <T> R<T> buildFailure(Integer errCode, String errMessage){R<T> response = new R<>();response.setCode(errCode);response.setMessage(errMessage);return response;}}
如果需要在后端对获取的响应数据用mybatis-plus进行分页呢。还需要配置一下mybatis-plus的配置类
/*** @author young* @date 2022/8/29 21:27* @description: MyBatis-Plus分页配置*/
@Configuration
@MapperScan("com.yy.dao")
public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false// paginationInnerInterceptor.setOverflow(false);// 设置最大单页限制数量,默认 500 条,-1 不受限制// paginationInnerInterceptor.setMaxLimit(500L);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}
}
当然,这个并不是必须的,后面在前端通过element-plus也能对数据自动进行分页展示,个人认为更加方便,没有必要在后端进行分页。如果是为了更好的按条件查询或其他之用,也可以考虑 。
2.1.6,编写controller去提取前端页面需要的数据
/*** @author young* @date 2022/8/26 9:36* @description:*/
@Slf4j
@RestController
@RequestMapping("/mysql")
public class TestUserController {private static final DateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Resourceprivate TestUserServiceImpl testUserService;
/* 前后端分离测试 *//*** 添加用户** @param request* @return*/@PostMapping("/addUserTo")public R<Object> addUser(HttpServletRequest request) {JSONObject object = new JSONObject();String name = request.getParameter("name").trim();String sex = request.getParameter("sex").trim();String address = request.getParameter("address").trim();String createTime = request.getParameter("createTime").trim();if ("".equals(name)) {object.put("code", 0);object.put("msg", "用户不能为空");return R.ok(object);}TestUser user = new TestUser();Date date = new Date();try {date = dataFormat.parse(createTime);} catch (Exception e) {e.printStackTrace();}user.setName(name);user.setSex(Boolean.valueOf(sex));user.setAddress(address);user.setCreateTime(date);try {boolean add = testUserService.save(user);if (add) {object.put("code", 1);object.put("success", true);object.put("msg", "添加成功");object.put("type", "success");return R.ok(object);} else {object.put("code", 0);object.put("success", false);object.put("msg", "添加失败");object.put("type", "error");return R.buildFailure(ResponseEnum.FAIL.getCode(), ResponseEnum.FAIL.getResultMessage());}} catch (DuplicateKeyException e) {object.put("code", 2);object.put("success", false);object.put("msg", "用户已存在");object.put("type", "error");return R.buildFailure(ResponseEnum.MSG_ERROR.getCode(), ResponseEnum.MSG_ERROR.getResultMessage());}}/*** 前端获取所有数据** @return*/@GetMapping("/getAllTo")public R<List<TestUser>> allUser() {return R.ok(testUserService.list());}/*** 返回指定id的用户** @param request* @return*/@GetMapping("/getById")public R<TestUser> getById(HttpServletRequest request) {String id = request.getParameter("id");TestUser user = testUserService.getById(id);return R.ok(user);}/*** 删除用户** @param request* @return*/@DeleteMapping("deleteUserTo")public R<Boolean> deleteUserTo(HttpServletRequest request) {String id = request.getParameter("id");return R.ok(testUserService.removeById(id));}/*** 更新用户信息** @param request* @return*/@PostMapping("/updateUserTo")public R<Object> updateUserTo(HttpServletRequest request) {JSONObject jsonObject = new JSONObject();String id = request.getParameter("id").trim();String name = request.getParameter("name").trim();String sex = request.getParameter("sex").trim();String address = request.getParameter("address").trim();String createTime = request.getParameter("createTime").trim();TestUser testUser = new TestUser();Date date = new Date();try {date = dataFormat.parse(createTime);} catch (Exception e) {e.printStackTrace();}testUser.setId(Integer.parseInt(id));testUser.setName(name);testUser.setSex(Boolean.valueOf(sex));testUser.setAddress(address);testUser.setCreateTime(date);boolean res = testUserService.updateById(testUser);if (res) {jsonObject.put("code", 1);jsonObject.put("msg", "修改成功!");R.ok(jsonObject).toString();return R.ok(jsonObject);} else {jsonObject.put("code", 0);jsonObject.put("msg", "修改失败");return R.buildFailure(ResponseEnum.FAIL.getCode(), ResponseEnum.FAIL.getResultMessage());}}
}
最后,通过接口测试工具(Swagger,ApiPost,Postman都可)对我们写的数据接口测试一下,数据返回值符合预期的话,那么后端代码就该一段落了!
2.2,前端部分
前端主要通Vue框架构建项目,主要是对客户端界面进行构造。由于笔者对于前端基础不怎么好,因此主要用Element-Plus进行界面构造,axios解决前后端交互。vue使用的是vue3,但是函数方法上仍旧采用的vue2的形式,主要实现过程如下:
2.2.1,在main.js上全局配置需要使用到的插件
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import * as ELIcons from '@element-plus/icons-vue'
// 配置路由器
import router from './router'import './assets/global.css'
createApp(App).use(router).use(ELIcons).use(ElementPlus,{size:'small'}).mount('#app')
2.2.2,封装aixos请求后端的请求方式request.js
import axios from 'axios'
import {BASE_URL} from '../util/name'axios.defaults.timeout = 5000 // 超时时间设置
axios.defaults.baseURL = BASE_URL
// Content-Type 响应头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'// response 拦截器
// 可以在接口响应后统一处理结果
axios.interceptors.response.use(response => {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 debugreturn Promise.reject(error)}
)/*** 封装get方法* @param url* @param data* @returns {Promise}*/export function get (url, params = {}, responseType = 'json') {return new Promise((resolve, reject) => {axios.get(url, {params: params,responseType}).then(response => {resolve(response.data)}).catch(err => {reject(err)})})}/*** 封装post请求* @param url* @param data* @returns {Promise}*/export function post (url, data = {}) {return new Promise((resolve, reject) => {axios.post(url, data).then(response => {resolve(response.data)}, err => {reject(err)})})}/*** 封装delete请求* @param url* @param data* @returns {Promise}*/export function deletes (url, data = {}) {return new Promise((resolve, reject) => {axios.delete(url, data).then(response => {resolve(response.data)}, err => {reject(err)})})}
2.2.3,封装前后端api对应的请求接口index.js
import {get,post,deletes} from '../util/request'
const HttpManager={// 前端用到的函数 后端对应的接口//返回所有用户 getAllUser: () => get(`mysql/getAllTo`),// 返回指定ID的用户getUserOfId: (id) => get(`mysql/getById?id=${id}`),// 添加用户addUser: (params) => post(`mysql/addUserTo`, params),// 更新用户信息updateUserMsg: (params) => post(`mysql/updateUserTo`, params),// 删除用户deleteUser: (id) => deletes(`mysql/deleteUserTo?id=${id}`),//模糊查询likeSelect:(params)=>get(`mysql/likeSelect`,params)
}
export {HttpManager}
2.2.4,提取公共的methos,放在mixins中,这样就能将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来,这样就可以提高代码的重用性,使代码保持干净和易于维护。
<!--mixins/index.js-->export const mixin = {methods: {// 获取要删除列表的idhandleDelete (id) {this.idx = idthis.delVisible = true},// 获取批量要删除的列表handleSelectionChange (val) {this.multipleSelection = val},// 批量删除delAll () {console.log("执行该方法")for (let item of this.multipleSelection) {this.handleDelete(item.id)this.deleteRow(item.id)}this.multipleSelection = []},getTime (val) {let time = String(val).match(/[0-9-]+(?=\s)/)return Array.isArray(time) ? time[0] : time},changeSex (value) {if (value === false) {return '女'} else if (value === true) {return '男'}},toggleSex (value) {if (value === '女') {return false} else if (value === '男') {return true}},// 更新图片handleAvatarSuccess (res, file) {if (res.code === 1) {this.imageUrl = URL.createObjectURL(file.raw)this.getData()this.$notify({title: '上传成功',type: 'success'})} else {this.$notify({title: '上传失败',type: 'error'})}},beforeAvatarUpload (file) {const isJPG = (file.type === 'image/jpeg') || (file.type === 'image/png')const isLt2M = file.size / 1024 / 1024 < 2if (!isJPG) {this.$message.error('上传头像图片只能是 JPG 格式!')}if (!isLt2M) {this.$message.error('上传头像图片大小不能超过 2MB!')}return isJPG && isLt2M},}
}
2.2.5,抽离出侧边栏组件AsiderBody,头部组件HeaderBody,直接从官网提取,并进行简单的样式更改
<!--HeaderBody.vue -->
<template><!-- 收缩 --><div style="font-size: 14px;line-height: 60px; display: flex"><div style="flex: 1;font-size: 20px"><el-icon style="cursor: pointer" @click="collapse"><Fold /></el-icon></div><div class="toolbar" style="width: 70px"><el-dropdown style="cursor: pointer"><el-icon style="margin-right: 8px; margin-top: 24px"><ArrowDown/></el-icon><template #dropdown><el-dropdown-menu><el-dropdown-item>个人信息</el-dropdown-item><el-dropdown-item>退出</el-dropdown-item></el-dropdown-menu></template></el-dropdown><span>张三</span></div></div>
</template><script>
import {Fold,ArrowDown} from '@element-plus/icons-vue'
export default {
name:'HeaderBody',
components:{
Fold,
ArrowDown
},
props:['collapse'],
}
</script><style></style>
<!--AsiderBody.vue -->
<template><el-scrollbar style="background-color: #304156"><el-menu:default-openeds="['1', '3']"background-color="#304156"text-color="rgb(91, 119, 211)"active-text-color="#ffd04b"overflow="hidden":collapse="isCollapse":collapse-transition="false"router><!-- 添加头部log --><div style="height: 60px; line-height: 60px; text-align: center"><imgsrc="../assets/yp.png"style="width: 20px; posotion: relative; top: 5px; margin-right: 5px"/><b style="color: white" v-show="logoTestShow">后台管理系统</b></div><el-sub-menu index="/"><template #title><el-icon><Menu /></el-icon><span>导航 主页</span></template><el-menu-item-group><template #title>分组 1</template><el-menu-item index="/user">用户管理 1</el-menu-item><el-menu-item index="/about">关于-登录</el-menu-item></el-menu-item-group><el-menu-item-group title="分组 2"><el-menu-item index="1-3">选项 3</el-menu-item></el-menu-item-group><el-sub-menu index="1-4"><template #title>选项4</template><el-menu-item index="1-4-1">选项 4-1</el-menu-item></el-sub-menu></el-sub-menu><el-sub-menu insdex="2"><template #title><el-icon><Message /></el-icon><span>导航 Two</span></template><el-menu-item-group><template #title>分组 1</template><el-menu-item index="2-1">选项 1</el-menu-item><el-menu-item index="2-2">选项 2</el-menu-item></el-menu-item-group><el-menu-item-group title="Group 2"><el-menu-item index="2-3">选项 3</el-menu-item></el-menu-item-group><el-sub-menu index="2-4"><template #title>选项 4</template><el-menu-item index="2-4-1">选项 4-1</el-menu-item></el-sub-menu></el-sub-menu><el-sub-menu index="3"><template #title><el-icon><House /></el-icon><span>导航 Three</span></template><el-menu-item-group><template #title>分组 1</template><el-menu-item index="3-1">选项 1</el-menu-item><el-menu-item index="3-2">选项 2</el-menu-item></el-menu-item-group><el-menu-item-group title="Group 2"><el-menu-item index="3-3">选项 3</el-menu-item></el-menu-item-group><el-sub-menu index="3-4"><template #title>选项 4</template><el-menu-item index="3-4-1">选项 4-1</el-menu-item></el-sub-menu></el-sub-menu></el-menu></el-scrollbar>
</template><script>
import { Menu, Message, House } from "@element-plus/icons-vue";
export default {name: "AsiderBody",props: {isCollapse: Boolean,logoTestShow: Boolean,},components: {Menu,Message,House,},
};
</script><style>
</style>
2.2.6,构建主要的信息展示界面,这样只有el-main里面的内容会随路由的改变而切换,而侧边栏AsiderBody,头部栏HeaderBody不会改动,从而实现组件复用。
<!--HomePage.vue-->
<template><el-container class="layout-container-demo" style="height: 100vh"><el-aside :width="sideWidth + 'px'" background-color="rgb(238,241,246)"><asider-body :isCollapse="isCollapse" :logoTestShow="logoTestShow" :collapse="collapse"/></el-aside><el-container><el-header style="border-bottom:1px solid #ccc"><HeaderBody :collapse="isCollapse"/></el-header><el-main style="margin-left: 40px"><router-view/></el-main></el-container></el-container>
</template><script>import { mixin } from "../mixins/index";
import AsiderBody from "@/components/AsiderBody.vue"
import HeaderBody from "@/components/HeaderBody.vue"
export default {name: "HomePage",mixins: [mixin],components: {AsiderBody,HeaderBody},data() {return {isCollapse: false,sideWidth: 200,logoTestShow: true,}},methods:{// 收缩侧边栏collapse() {this.isCollapse = !this.isCollapse;if (this.isCollapse) {(this.sideWidth = 64), (this.logoTestShow = false);} else {(this.sideWidth = 200), (this.logoTestShow = true);}},
}
}
</script><style scoped>
.layout-container-demo .el-header {position: relative;background-color: var(--el-color-primary-light-7);color: var(--el-text-color-primary);
}
.layout-container-demo .el-aside {color: var(--el-text-color-primary);background: var(--el-color-primary-light-8);
}
.layout-container-demo .el-menu {border-right: none;
}
.layout-container-demo .el-main {padding: 0;
}
.layout-container-demo .toolbar {display: inline-flex;align-items: center;justify-content: center;height: 100%;right: 20px;
}</style>
2.2.7,用户信息展示界面构造,以及相关函数编写调用
<!--UserMsg.vue-->
<template><div><div style="margin-top: 20px; text-size: 20px"><el-breadcrumb separator="/"><el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item><el-breadcrumb-item><a href="/user">用户信息</a></el-breadcrumb-item></el-breadcrumb></div><div style="margin-top: 10px"><el-inputv-model="select_word"placeholder="筛选相关用户"style="width: 200px"><template #suffix><el-icon><Search /></el-icon></template></el-input><el-buttonroundcolor="#db6b2b"class="ml-5"plain@click="reSet">重置</el-button></div><div style="padding: 10px 0; display: inline-flex"><el-button type="primary" @click="delAll">批量删除<el-icon><DeleteFilled /></el-icon></el-button><el-buttontype="primary"class="mr-5"@click="centerDialogVisible = true">添加用户<el-icon><Plus /></el-icon></el-button><el-uploadref="upload"action="#"accept=".xlsx, .xls":auto-upload="false":on-change="uploadFile":show-file-list="false"><el-button type="primary" class="ml-5">导入数据<el-icon><Download /></el-icon></el-button></el-upload><el-buttontype="primary"style="margin-left: 10px"@click="exportE()">导出数据<el-icon><Upload /></el-icon></el-button></div><el-table:data="datas"style="text-align: center"borderstripe@selection-change="handleSelectionChange"><el-table-columntype="selection"width="40"align="center"></el-table-column><el-table-column prop="id" label="ID" width="80px" align="center" /><el-table-columnprop="createTime"label="日期"width="240px"align="center"><!-- <template v-slot="scope"><div>{{ getTime(scope.row.createTime) }}</div></template> --></el-table-column><el-table-columnprop="name"label="姓名"width="200px"align="center"/><el-table-column prop="sex" label="性别" width="200px" align="center"><!-- eslint-disable-next-line --><template v-slot="scope"><div>{{ changeSex(scope.row.sex) }}</div></template></el-table-column><el-table-columnprop="address"label="地址"width="200px"align="center"/><el-table-column label="操作" align="center"><!-- 解决scope没被使用的问题 --><!-- eslint-disable-next-line --><template v-slot="scope"><el-buttontype="success"class="mr-5"@click="handleEdit(scope.row)">编辑<el-icon><Edit /></el-icon></el-button><el-buttontype="danger"class="mr-5"@click="handleDelete(scope.row.id)">删除<el-icon><DeleteFilled /></el-icon></el-button><el-button type="warning" class="mr-5">权限<el-icon><ElementPlus /></el-icon></el-button></template></el-table-column></el-table><div style="padding: 10px 0"><el-pagination@current-change="handleCurrentChange"backgroundlayout="total, prev, pager, next":current-page="currentPage":page-size="pageSize":total="tableData.length"></el-pagination></div><!--添加新用户--><el-dialogtitle="添加用户"v-model="centerDialogVisible"width="400px"center><el-form:model="registerForm"status-icon:rules="rules"ref="registerForm"label-width="70px"class="demo-ruleForm"><el-form-item label="用户名" prop="name" size="small"><el-inputv-model="registerForm.name"placeholder="用户名"></el-input></el-form-item><el-form-item label="性别" size="small"><el-radio-group v-model="registerForm.sex"><el-radio :label="false">女</el-radio><el-radio :label="true">男</el-radio></el-radio-group></el-form-item><el-form-item label="创建时间" prop="createTime" size="small"><el-date-pickertype="date"placeholder="选择日期"v-model="registerForm.createTime"style="width: 100%"></el-date-picker></el-form-item><el-form-item label="地址" prop="address" size="small"><el-inputv-model="registerForm.address"placeholder="地址"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button size="small" @click="centerDialogVisible = false">取 消</el-button><el-button type="primary" size="small" @click="addPeople">确 定</el-button></span></template></el-dialog><!-- 编辑弹出框 --><el-dialog title="编辑" v-model="editVisible" width="400px"><el-form ref="form" :model="form" label-width="60px"><el-form-item label="用户名" size="small"><el-input v-model="form.name" :disabled="true"></el-input></el-form-item><el-form-item label="性别" size="small"><el-radio-group v-model="form.sex"><el-radio :label="false">女</el-radio><el-radio :label="true">男</el-radio></el-radio-group></el-form-item><el-form-item label="创建日期" prop="createTime" size="small"><el-date-pickertype="date"placeholder="选择日期"v-model="form.createTime"style="width: 100%"></el-date-picker></el-form-item><el-form-item label="地址" size="small"><el-input v-model="form.address"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button size="small" @click="editVisible = false">取 消</el-button><el-button type="primary" size="small" @click="saveEdit">确 定</el-button></span></template><!-- 删除提示框 --></el-dialog><DelDialog:delVisible="delVisible"@deleteRow="deleteRow"@cancelRow="delVisible = $event"></DelDialog></div>
</template><script>
import { HttpManager } from "../api/index";
import { getDateTime } from "../util/DataUtil";
import DelDialog from "@/components/DelDialog.vue";
import { mixin } from "../mixins/index";
import {Plus,Upload,Download,Search,Edit,DeleteFilled,ElementPlus,
} from "@element-plus/icons-vue";
export default {
name:'UserMsg',
mixins: [mixin],
components:{Plus,Upload,Download,Search,Edit,DeleteFilled,ElementPlus,DelDialog,
},data() {return {tableData: [],multipleSelection: [], // 记录要删除的用户信息centerDialogVisible: false,editVisible: false, // 显示编辑框delVisible: false, // 显示删除框select_word: "", // 记录输入框输入的内容pageSize: 10, // 页数currentPage: 1, // 当前页idx: -1, // 记录当前点中的行tempDate: [],is_Search: false,excelVisible: false,registerForm: {// 添加用户name: "",sex: "",createTime: "",address: "",},form: {// 记录编辑的信息id: "",name: "",sex: "",createTime: "",address: "",//updateTime: ''},};},computed: {// 计算当前表格中的数据datas() {return this.tableData.slice((this.currentPage - 1) * this.pageSize,this.currentPage * this.pageSize);},},//模糊查询watch: {select_word() {if (this.select_word === "") {this.tableData = this.tempDate;} else {this.tableData = [];for (let item of this.tempDate) {if (item.name.includes(this.select_word) ) {this.tableData.push(item);}}}},},created() {//获取分页数据信息this.getData();},methods: {// 获取用户信息getData() {this.tableData = [];this.tempDate = [];HttpManager.getAllUser().then((res) => {this.tableData = res;this.tempDate = res;this.currentPage = 1;console.log(this.tempDate)});},//重置搜索信息reSet() {this.select_word = "";this.getData();},// 添加用户addPeople() {let createTime = getDateTime(this.registerForm.createTime);let params = new URLSearchParams();params.append("name", this.registerForm.name);params.append("sex", this.registerForm.sex);params.append("createTime", createTime);params.append("address", this.registerForm.address);HttpManager.addUser(params).then((res) => {if (res.code === 1) {this.getData();this.registerForm = {};this.$notify({title: "添加成功",type: "success",});} else {this.$notify({title: "添加失败",type: "error",});}}).catch((err) => {console.error(err);});this.centerDialogVisible = false;},// 分页// handleSizeChange(pageSize) {// console.log(`每页 ${pageSize} 条`);// this.pageSize = pageSize;// // this.getData();// },handleCurrentChange(val) {this.currentPage = val;},// 编辑handleEdit(row) {this.idx = row.id;this.form = {id: row.id,name: row.name,sex: row.sex,createTime: row.createTime,address: row.address,};this.editVisible = true;},// 保存编辑saveEdit() {let datetime = getDateTime(new Date(this.form.createTime));let params = new URLSearchParams();params.append("id", this.form.id);params.append("name", this.form.name);params.append("sex", this.form.sex);params.append("createTime", datetime);params.append("address", this.form.address);HttpManager.updateUserMsg(params).then((res) => {if (res.code === 1) {this.getData();this.$notify({title: "修改成功",type: "success",});} else {this.$notify({title: "修改失败",type: "error",});}}).catch((err) => {console.error(err);});this.editVisible = false;},// 确定删除deleteRow() {HttpManager.deleteUser(this.idx).then((res) => {if (res) {this.getData();this.$notify({title: "删除成功",type: "success",});} else {this.$notify({title: "删除失败",type: "error",});}}).catch((error) => {console.error(error);});this.delVisible = false;},},
};
</script><style>
/* 分页 */
.demo-pagination-block + .demo-pagination-block {margin-top: 10px;
}
.demo-pagination-block .demonstration {margin-bottom: 16px;
}
</style>
2.2.8,简单配置一下router,这样运行项目后,localhst:8080访问进入到“/home”请求对应的组件界面(随便写一个即可)上,点击侧边栏上的用户管理就能跳转到对应的信息展示界面。
import { createRouter, createWebHistory } from 'vue-router'
import HomePage from '@/view/HomePage'const routes = [{path: '/',name: 'HomePage',component: HomePage,redirect: '/home',children: [{path: 'home',name: 'HomeTest',component: () => import('../components/HomeTest.vue')},{path: 'user',name: 'UserMsg',component: () => import('../view/UserMsg.vue')},{path: 'about',name: 'About',component: () => import('../components/AboutTest.vue')},]},
]const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes
})
export default router
2.2.9,在vue.config.js上通过chainWebpack配置后端代理地址,这样由于后端已经配置了跨域,前端就可以通过对后端的请求访问到后端对应的接口了。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,chainWebpack: config => {config.plugin('define').tap(definitions => {Object.assign(definitions[0]['process.env'], {NODE_HOST: '"http://localhost:8090"',});return definitions;});}
})
2.3,项目运行及实现结果
前端通过 npm install 安装项目所需的依赖,然后npm run serve运行项目即可得到初始的HomePage界面。因为前端学的并不扎实,页面配色布局可能一言难尽……
展开导航主页,点击用户展示界面,得到相应界面,此时并没有数据展示,只有初始的一些界面 ,因为后端服务没有开启,数据获取不到。
运行后端项目,重新刷新一下前端界面,数据通过element-plus中的el-pagination分页插件已经实现了分页效果。
并且该能实现一些基本增删改查操作,后端数据库的信息也会相应进行更改!
至此,简单的数据交互就实现了。
四,简单总结
这个测试项目还有一些小BUG正在完善中,还有一些实现的功能并没有完全放在博客中,这篇文章仅限于展示一些数据,实现前后端数据之间的交互。后期会继续完善成为一个简易的后台管理系统,供学习练习之用,项目会陆续上传到GitHub/Gitee上……有问题的地方希望大家指正交流,共同进步。
补充:
gitee项目地址
由于项目中涉及一些mysql以及mongodb的数据库,但是考虑比较简单,只是作为数据模拟使用的,因此没有放在项目中,前端也比较较简单没有放在gitee中,大家自行构建。
Springboot+Vue实现简单的前端后分离数据交互相关推荐
- Springboot+vue 社团管理系统(前后端分离)
Springboot+vue 社团管理系统(前后端分离) zero.项目功能设计图 一.数据库设计(项目准备) 1.建表 2.表目录 二.前端编写(vue) 1.搭建Vue框架 2.放入静态资源(as ...
- springboot+vue搭建简单的聊天网站,从0到上线(腾讯云)
springboot+vue搭建简单的聊天网站,从0到上线 整体架构简单梳理 云服务器 nginx的基础配置 springboot-eureka简单梳理 聊天功能实现的基础流程 ws的实现 整体架构简 ...
- python的前端和后端_python前端和后端数据交互,tornado框架入门,初学小试牛刀!...
Python前端和后端是如何交互的,怎么用tornado框架快速搭建前端和后端数据交互? 前端与后端的数据交互,最常用的就是GET.POST,比较常用的用法是:提交表单数据到后端,后端返回json 前 ...
- 开源前端 可视化大数据交互前端动态模板
介绍: 如今老板都很在乎公司实力形象 往往会在大厅投放展示大数据巨屏 你是否也想实现这样大数据效果展示 本次带来一套开源的前端可视化大数据交互动态模板网页前端模板,是HTML网页模板 只要稍微懂点前端 ...
- 前端后分离深入分析 ——浏览器渲染和服务器渲染区别
1.为什么会有服务器渲染与客户端渲染? 首先理解服务器和浏览器客户端之间传递的是什么--HTML,CSS,JavaScript的文件以及数据载体json(xml)等文件,而文件都是静态,之所以动态是应 ...
- Java基于springboot+vue的电子相册管理系统 前后端分离node
智能电子相册是一个可以永久保留记忆的东西,用户可以讲自己美好的一面展示在网络上,人更多的人了解到自己的生活,为此我们通过Java语言并结合springboot+vue开发了本次的电子相册管理系统,希望 ...
- 【EasyExcel】在SpringBoot+VUE项目中引入EasyExcel实现对数据的导出(封装工具类)
在SpringBoot+VUE项目中引入EasyExcel实现导入导出 一.引入EasyExcel 通过maven引入,坐标如下: <dependency><groupId>c ...
- web前端与后台数据交互
1.前端请求数据URL由谁来写? 在开发中,URL主要是由后台来写的,写好了给前端开发者.如果后台在查询数据,需要借助查询条件才能查询到前端需要的数据时,这时后台会要求前端提供相关的查询参数,这里的查 ...
- 前端和后端 数据交互的基本知识
一.首先了解前端.后端.数据三者的关系 1) 前端通常是html,css,js三者构成的页面的总称.运行在客户端.以浏览器为例. 2) 后端是指后端程序.比如java,php等编写的一些服务.用来操作 ...
最新文章
- 对Linux文件中的多行进行注释
- Firefox Quantum支持跨浏览器插件架构
- LeetCode 92反转链表Ⅱ93复制ip地址94二叉树的中序遍历
- Linux基础第一章 概述
- zabbix server is not running the information displayed may not be current
- 【scala初学】scala 语法 声明
- gb28181的sip通信
- 铭瑄显卡不支持Linux,铭瑄主板bios设置显卡方法
- 战舰少女服务器不显示,战舰少女进不去 闪退及连接不上解决方法
- 一位区域销售经理百条经验手记
- 关于一些桌面、移动应用开发的平台
- axure8 Mac破解版+汉化包
- 目标检测_CVPR2020
- .NET跨平台:在CentOS上编译dnx并运行ASP.NET 5示例程序
- 去中心化应用程序 (dApps) 发展现状及趋势回顾
- WebMeeting
- vue-seamless-scroll公告组件的使用
- Leetcode 977.有序数组的平方
- RT5350 openwrt添加Reset按键,实现短按重启系统,长按复位系统
- 桌面壁纸被计算机管理员禁用,更改桌面背景时显示已经被系统管理员禁用,这种情况要怎么处理...
热门文章
- 七月算法深度学习笔记4 -- CNN与常用框架
- 帕斯卡三角形html,数学之美:杨辉三角(帕斯卡三角)的奇特性质
- 一刀工具箱- 在线AI智能写诗工具
- Android安装apk应用的时候出现INSTALL_FAILED_SHARED_USER_INCOMPATIBLE如何解决
- 阿里孤尽:Code Review 是一场苦涩但有意思的修行
- 【H5+ Quick-cocos2dx整合】之iOS 三 集成Quick-Cocos2dx SDK
- mysql导出表结构及数据的三种方法
- 谢国忠:下一场金融风暴将在6月左右开始
- openGauss数据库源码解析系列文章——openGauss开发快速入门(二)
- Linux内核之vmlinux与vmlinuz