文章目录
- 12. Filter 和 Listener
- 12.1 Filter快速入门和执行流程
- 12.2 Filter使用细节
- 12.3 Filter案例:登录验证
- 12.4 Listener
- 13. AJAX
- 13.1 AJAX快速入门
- 13.2 AJAX案例
- 13.3 Axios异步框架
- 13.4 JSON
- 13.5 JSON案例
- SelectAllServlet.java
- brand.html
- AddServlet.java
- addBrand.html
- 14. Vue
- 14.1 概述
- 14.2 快速入门
- 14.3 Vue常用指令
- v-bind 和 v-model
- v-on
- v-if 和 v-show
- v-for
- 14.4 生命周期
- 14.5 案例
- 14.6 Element
- 快速入门
- Element布局
- Element组件 - 表格 表单
- 15. 综合案例
- 15.1 完整项目及代码
- 15.2 功能实现
- 查询所有
- 新增品牌
- * Servlet优化 - BaseServlet
- 批量删除
- 分页查询
- 条件查询
- 前端优化
12. Filter 和 Listener
概念
Filter表示过滤器,Servlet
, Filter
, Listener
是JavaWeb三大组件
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
过滤器一般完成一些通用的操作,比如:权限控制、统一编码处理、处理敏感字符等
Filter代码应写在web.filter目录下
12.1 Filter快速入门和执行流程
快速入门
- 定义类,实现Filter接口,并重写其所有方法
- 配置Filter拦截资源的路径,在类上定义
@WebFilter
注解 - 在doFilter方法中输出一句话,并放行
package org.example.web.filter;
//注意:Filter是javax.servlet.Filter
@WebFilter("/*")//拦截所有资源
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterDemo");
//放行,如果不写下面这句话,hello.jsp不显示内容,但控制台有输出
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
/* 表示拦截所有资源,即不管访问什么界面,都将来到FilterDemo.java,并执行doFilter方法
执行流程
流程:执行放行前逻辑 --> 放行 --> 访问资源 --> 执行放行后逻辑
- 放行前:对request数据进行处理
- 将携带处理后的request访问资源
- 放行后:对response数据进行处理
12.2 Filter使用细节
拦截路径配置
@WebFilter("/*")
- 拦截具体的资源:
/index.jsp
,只有访问index.jsp时才会被拦截 - 目录拦截:
/user/*
,访问/user下的所有资源,都会被拦截 - 后缀名拦截:
*.jsp
,访问后缀名为jsp的资源,都会被拦截 - 拦截所有:
/*
,访问所有资源,都会被拦截
以配置 /index.jsp 为例,当访问index.jsp时,会执行 FilterDemo里面的doFilter()方法,但如果是访问 hello.jsp,doFilter方法直接不执行
过滤器链
一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链
- 注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序先后执行
12.3 Filter案例:登录验证
需求:访问服务器资源时,需要先进行登录验证,如果没有登录,则自动转入登录界面
package org.example.web.filter;
/**
* 登录验证的过滤器
*/
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
//1. 判断session中是否有User
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
//2. 判断user是否为null
if(user != null){
//登录过了,放行
chain.doFilter(request, response);
}else{
//没有登录,存储提示信息,跳转到登录界面
req.setAttribute("login_msg", "您尚未登录");
req.getRequestDispatcher("/login.jsp").forward(req, response);
}
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
先清除浏览器的浏览数据(清除缓存)
此时访问任何资源(页面)都会跳转到登录界面,且css样式都不显示了
这是因为css样式也被拦截下来,需要对登录注册相关的资源放行
修改后的过滤器如下
package org.example.web.filter;
/**
* 登录验证的过滤器
*/
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
//判断访问资源路径是否和登录注册相关
String[] urls = {"/login.jsp", "/imgs/", "/css/", "/loginServlet", "/register.jsp", "/registerServlet", "/checkCodeServlet"};
//获取当前访问的资源路径
String url = req.getRequestURL().toString();
//循环判断
for(String u:urls){
if(url.contains(u)){
chain.doFilter(request, response);
return;
}
}
//1. 判断session中是否有User
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
//2. 判断user是否为null
if(user != null){
//登录过了,放行
chain.doFilter(request, response);
}else{
//没有登录,存储提示信息,跳转到登录界面
req.setAttribute("login_msg", "您尚未登录");
req.getRequestDispatcher("/login.jsp").forward(req, response);
}
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
12.4 Listener
现在用的不多了
概念
Listener表示监听器
监听器可以用于监听application, seesion, request三个对象创建、销毁或者往其中添加修改删除属性这些事件
Listener分类
JavaWeb中提供了8个监听器
ServletContextListener使用
- 定义类,实现ServletContextListener接口
- 在类上添加
@WebListener
注解
package org.example.web.listener;
//ServletContext代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//加载资源
System.out.println("ContextLoadListener...");//会被自动调用
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
//释放资源
}
}
13. AJAX
概念
AJAX (Asynchronous JavaScript And XML):异步的 JavaScript 和 XML
AJAX作用
- 与服务器进行数据交换:通过AJAX可以给服务器发送请求,并获取服务器响应的数据
在之前,是通过Servlet获取数据,再转发给JSP,响应给客户
使用AJAX和服务器进行通信,就可以使用HTML+AJAX来替换JSP页面了 - 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验等
异步:客户端不需要阻塞等待服务器响应
13.1 AJAX快速入门
- 编写AjaxServlet,并使用response输出字符串
- 创建XMLHttpRequest对象:用于和服务器交换数据
- 向服务器发送请求
- 获取服务器响应数据
AjaxServlet.java
package org.example.web.servlet;
@WebServlet("/ajaxServlet")
public class AjaxServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 编写AjaxServlet,并使用response输出字符串
response.getWriter().write("hello ajax");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
ajax-demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//2. 创建XMLHttpRequest对象:用于和服务器交换数据
//可以去https://www.w3school.com.cn/js/js_ajax_http.asp复制
var xhttp;
if (window.XMLHttpRequest) {
xhttp = new XMLHttpRequest();
}else{
//code for IE6, IE5
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//4. 获取服务器响应数据
//前端后端可能部署环境不一致,所以这里写全路径
xhttp.open("GET", "http://localhost:8080/tomcat-demo2/ajaxServlet")
xhttp.send()
//3. 获取响应
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200){
alert(this.responseText);
}
}
</script>
</body>
</html>
访问http://localhost:8080/tomcat-demo2/ajax-demo1.html,即可获取到服务端返回的数据
13.2 AJAX案例
需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在
SelectUserServlet.java
package org.example.web.servlet;
@WebServlet("/selectUserServlet")
public class SelectUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 接收用户名
String username = request.getParameter("username");
//2. 调用service查询User对象(模拟)
boolean flag = true;
//3. 响应标记
response.getWriter().write(""+flag);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎注册</title>
<link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
<div class="reg-content">
<h1>欢迎注册</h1>
<span>已有帐号?</span> <a href="login.html">登录</a>
</div>
<form id="reg-form" action="/tomcat-demo2/registerServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg" style="display: none">用户名已存在</span>
</td>
</tr>
<tr>
<td>密码</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 册" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
<script>
document.getElementById("username").onblur = function () {
//获取用户名的值
var username = this.value;
//1. 创建核心对象
var xhttp;
if(window.XMLHttpRequest){
xhttp = new XMLHttpRequest();
}else{
//code for IE6, IE5
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 发送请求
xhttp.open("GET", "http://localhost:8080/tomcat-demo2/selectUserServlet?username="+username)
xhttp.send();
//3. 获取响应
xhttp.onreadystatechange = function () {
if(this.readyState == 4 && this.status == 200){
if(this.responseText == "true"){
//用户名存在,显示提示信息
document.getElementById("username_err").style.display = '';
}else{
//用户名不存在,清除提示信息
document.getElementById("username_err").style.display = 'none';
}
}
}
}
</script>
</body>
</html>
13.3 Axios异步框架
Axios是对ajax的封装
-
引入axios的js文件
<script src="js/axios-0.18.0.js"></script>
-
使用axios发送请求,并获取响应结果
示例
AxiosServlet.java
package org.example.web.servlet;
@WebServlet("/axiosServlet")
public class AxiosServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("get ...");
//1. 接收请求参数
String username = request.getParameter("username");
System.out.println(username);
//2. 响应数据
response.getWriter().write("hello axios");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("post ...");
this.doGet(request, response);
}
}
axios-demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="js/axios-0.18.0.js"></script>
<script>
//1. 以get方式发送
axios({
method:"get",
url:"http://localhost:8080/tomcat-demo2/axiosServlet?username=zhangsan"
}).then(function (resp){
alert(resp.data)
})
//2. 以post方式发送
axios({
method:"post",
url:"http://localhost:8080/tomcat-demo2/axiosServlet",
data:"username=zhangsan"
}).then(function (resp){
alert(resp.data)
})
</script>
</body>
</html>
请求方式别名
为了方便,Axios已经为所有支持的请求方式提供了别名
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios-demo.html
<script src="js/axios-0.18.0.js"></script>
<script>
//1. 以get方式发送
axios.get("http://localhost:8080/tomcat-demo2/axiosServlet?username=zhangsan").then(function (resp){
alert(resp.data)
})
//2. 以post方式发送
axios.post("http://localhost:8080/tomcat-demo2/axiosServlet", "username=zhangsan").then(function (resp){
alert(resp.data)
})
</script>
13.4 JSON
概念
JSON, JavaScript Object Notatio, JavaScript对象表示法
现多用于作为数据载体,在网络中进行数据传输
JSON基础语法
<script>
var json = {
"name":"zhangsan",
"age":23,
"addr":["北京","上海","西安"]
};
alert(json.name);
</script>
JSON数据和Java对象转换
Fastjson是阿里巴巴提供的一个Java语言编写的高性能功能完善的JSON库,是目前Java语言中最快的JSON库,可以实现Java对象和JSON字符串的相互转换
-
导入坐标
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
-
Java对象转JSON
String jsonStr = JSON.toJSONString(obj);
-
JSON字符串转Java对象
User user = JSON.parseObject(jsonStr, User.class)
示例
public static void main(String[] args){
//1. 将Java对象转为JSON字符串
User user = new User();
user.setId(1);
user.setUsername("zhangsan");
user.setPassword("123");
String jsonString = JSON.toJSONString(user);
System.out.println(jsonString);
//2. 将JSON字符串转为Java对象
User u = JSON.parseObject("{\"id\":1,\"password\":\"123\",\"username\":\"zhangsan\"}", User.class);
System.out.println(u);
}
13.5 JSON案例
需求:使用Axios + JSON完成品牌列表数据查询和添加
先写完后端代码,运行查看是否能够获取数据,再写前端代码
查询所有功能
SelectAllServlet.java
package org.example.web;
@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 调用service查询
List<Brand> brands = service.selectAll();
//2. 将集合转换为JSON数据 序列化
String jsonString = JSON.toJSONString(brands);
//3. 响应数据
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(jsonString);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
brand.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="addBrand.html"><input type="button" value="新增"></a><br>
<hr>
<table id="brandTable" border="1" cellspacing="0" width="100%">
</table>
<script src="js/axios-0.18.0.js"></script>
<script>
//1. 当页面加载完成后,发送ajax请求
window.onload = function () {
//2. 发送ajax请求
axios({
method:"get",
url:"http://localhost:8080/tomcat-demo2/selectAllServlet"
}).then(function (resp) {
//获取数据
let brands = resp.data;
let tableData = " <tr>\n" +
" <th>序号</th>\n" +
" <th>品牌名称</th>\n" +
" <th>企业名称</th>\n" +
" <th>排序</th>\n" +
" <th>品牌介绍</th>\n" +
" <th>状态</th>\n" +
" <th>操作</th>\n" +
" </tr>";
for (let i = 0; i < brands.length ; i++) {
let brand = brands[i];
tableData += "\n" +
" <tr align=\"center\">\n" +
" <td>"+(i+1)+"</td>\n" +
" <td>"+brand.brandName+"</td>\n" +
" <td>"+brand.companyName+"</td>\n" +
" <td>"+brand.ordered+"</td>\n" +
" <td>"+brand.description+"</td>\n" +
" <td>"+brand.status+"</td>\n" +
"\n" +
" <td><a href=\"#\">修改</a> <a href=\"#\">删除</a></td>\n" +
" </tr>";
}
// 设置表格数据
document.getElementById("brandTable").innerHTML = tableData;
})
}
</script>
</body>
</html>
新增功能
AddServlet.java
package org.example.web;
@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 接收数据
//request.getParameter()不能接收JSON数据
//需要获取请求体数据
BufferedReader br = request.getReader();
String param = br.readLine();
//将JSON字符串转为Java对象
Brand brand = JSON.parseObject(param, Brand.class);
System.out.println(brand);
//2. 调用service添加
service.add(brand);
//3. 响应成功标识
response.getWriter().write("success");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
addBrand.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加品牌</title>
</head>
<body>
<h3>添加品牌</h3>
<!-- 不需要指定action -->
<form action="" method="post">
品牌名称:<input id="brandName" name="brandName"><br>
企业名称:<input id="companyName" name="companyName"><br>
排序:<input id="ordered" name="ordered"><br>
描述信息:<textarea rows="5" cols="20" id="description" name="description"></textarea><br>
状态:
<input type="radio" name="status" value="0">禁用
<input type="radio" name="status" value="1">启用<br>
<input type="button" id="btn" value="提交">
</form>
<script src="js/axios-0.18.0.js"></script>
<script>
//1. 给按钮绑定单击事件
document.getElementById("btn").onclick = function () {
//将表单的数据转为JSON
var formData = {
brandName:"",
companyName:"",
ordered:"",
description:"",
status:""
};
//获取表单数据
let brandName = document.getElementById("brandName").value;
formData.brandName = brandName;
let companyName = document.getElementById("companyName").value;
formData.companyName = companyName;
let ordered = document.getElementById("ordered").value;
formData.ordered = ordered;
let description = document.getElementById("description").value;
formData.description = description;
let status = document.getElementsByName("status");
for(let i=0; i<status.length; ++i){
if(status[i].checked){
formData.status = status[i].value;
}
}
console.log(formData);
//2. 发送ajax请求
axios({
method:"post",
url:"http://localhost:8080/tomcat-demo2/addServlet",
data:formData
}).then(function (resp){
//判断响应数据
if(resp.data == "success"){
location.href = "http://localhost:8080/tomcat-demo2/brand.html"
}
})
}
</script>
</body>
</html>
14. Vue
14.1 概述
-
Vue
是一套前端框架,免除原生JavaScript中的DOM操作,简化书写像这样的大量DOM操作,Vue将对其进行简化
-
基于
MVVM
(Model-View-ViewModel)思想,实现数据的双向绑定
,将编程的关注点放在数据上对于前端而言,MVC模型中,C是 js 代码,M 就是数据,而 V 是页面上展示的内容
MVC 思想是没法进行双向绑定的,只能是js到html标签的单向绑定,当html里的内容变化时,js可以获取到;但js对于值变化时,html是感知不到的
双向绑定是指当数据模型数据发生变化时,页面展示的会随之发生变化,而如果表单数据发生变化,绑定的模型数据也随之发生变化
-
MVVM思想
图中的
Model
就是数据,View
是视图,也就是页面标签,用户可以通过浏览器看到的内容;Model
和View
是通过ViewModel
对象进行双向绑定的,而ViewModel
对象是Vue
提供的
-
14.2 快速入门
引入vue.js
<script src="js/vue.js"></script>
创建Vue核心对象,进行数据绑定
<script>
new Vue({
el:"#app",
data() {
return {
username:""
}
}
//这里的data(){},实际上是data:function(){}
});
</script>
编写视图
<div id="app">
<!-- 视图 -->
<input name="username" v-model="username">
<!-- 数据模型 -->
{{username}}
</div>
示例:mvvm.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 视图 -->
<input name="username" v-model="username">
<!-- 数据模型 -->
{{username}}
</div>
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
username:""
}
}
//这里的data(){},实际上是data:function(){}
});
</script>
</body>
</html>
结果:
14.3 Vue常用指令
指令:HTML 标签上带有 v- 前缀的特殊属性,不同指令具有不同含义。例如:v-if,v-for…
常用的指令有:
指令 | 作用 |
---|---|
v-bind | 为HTML标签绑定属性值,如设置 href , css样式等 |
v-model | 在表单元素上创建双向数据绑定 |
v-on | 为HTML标签绑定事件 |
v-if | 条件性的渲染某元素,判定为true时渲染,否则不渲染 |
v-else | |
v-else-if | |
v-show | 根据条件展示某元素,区别在于切换的是display属性的值 |
v-for | 列表渲染,遍历容器的元素或者对象的属性 |
v-bind 和 v-model
-
v-bind
<a v-bind:href="url">百度一下</a>
上面的
v-bind:"
可以简化写成:
,如下:<!-- v-bind 可以省略 --> <a :href="url">百度一下</a>
注意这里的a标签后面一定要跟一个空格
示例
<div id="app"> <!-- <a v-bind:href="url">点击一下</a> --> <a :href="url">点击一下</a> <input v-model="url"> </div> ... <script src="js/vue.js"></script> <script> new Vue({ el:"#app", data() { return { username:"", url:"https://www.baidu.com" } } }); </script>
-
v-model
<input name="username" v-model="username">
v-on
<div id="app">
<input type="button" value="一个按钮" v-on:click="show()"><br>
<!--简化-->
<input type="button" value="一个按钮" @click="show">
</div>
...
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
username:"",
url:"https://www.baidu.com"
}
},
methods:{
show(){
alert("msg");
}
}
});
</script>
v-if 和 v-show
v-if
<div id="app">
<div v-if="count == 3">div1</div>
<div v-else-if="count == 4">div2</div>
<div v-else>div1</div>
<br>
<input v-model="count">
</div>
...
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
username:"",
url:"https://www.baidu.com",
count:3
}
}
});
</script>
v-show
和v-if用法一致
<div v-show="count == 3">div v-show</div>
和v-if的区别: v-show
不展示的原理是给对应的标签添加 display
css属性,并将该属性值设置为 none
,这样就达到了隐藏的效果。而 v-if
指令是条件不满足时根本就不会渲染。
v-for
<div id="app">
<div v-for="addr in addrs">
{{addr}}<br>
</div>
</div>
...
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data() {
return {
addrs:["北京","上海","西安"]
}
},
});
</script>
加索引
<!-- 从1开始 -->
<div id="app">
<div v-for="(addr,i) in addrs">
{{i+1}}--{{addr}}<br>
</div>
</div>
14.4 生命周期
生命周期的八个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法,这些生命周期方法也被称为钩子方法
mounted
:挂载完成,Vue初始化成功,HTML页面渲染成功。而以后会在该方法中发送异步请求,加载数据
<script>
new Vue({
el:"#app",
<!-- 实际上是mounted:function(){} -->
mounted(){
alert("vue挂载完毕,发送异步请求")
}
});
</script>
14.5 案例
查询所有:brand.html
<div id="app">
<a href="addBrand.html"><input type="button" value="新增"></a><br>
<hr>
<table border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
<!-- 使用v-for遍历 -->
<tr v-for="(brand,i) in brands" align="center">
<td>{{i+1}}</td>
<td>{{brand.brandName}}</td>
<td>{{brand.companyName}}</td>
<td>{{brand.ordered}}</td>
<td>{{brand.description}}</td>
<td v-if="brand.status==1">启用</td>
<td v-else>禁用</td>
<td><a href="#">修改</a> <a href="#">删除</a></td>
</tr>
</table>
</div>
<script src="js/axios-0.18.0.js"></script>
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data(){
return{
brands:[]
}
},
mounted(){
//页面加载完成之后,发送异步请求,查询数据
var _this = this;
axios({
method:"get",
url:"http://localhost:8080/tomcat-demo2/selectAllServlet"
}).then(function (resp){
_this.brands = resp.data;
//写在axios里的this指定的是windows对象,而非Vue,因此如果使用this.brands将不能成功获取
//这里需要使用_this做一个替换
})
}
})
</script>
新增:addBrand.html
<div id="app">
<form action="" method="post">
品牌名称:<input id="brandName" v-model="brand.brandName" name="brandName"><br>
企业名称:<input id="companyName" v-model="brand.companyName" name="companyName"><br>
排序:<input id="ordered" v-model="brand.ordered" name="ordered"><br>
描述信息:<textarea rows="5" cols="20" id="description" v-model="brand.description" name="description"></textarea><br>
状态:
<input type="radio" name="status" v-model="brand.status" value="0">禁用
<input type="radio" name="status" v-model="brand.status" value="1">启用<br>
<input type="button" id="btn" @click="submitForm" value="提交">
</form>
</div>
<script src="js/axios-0.18.0.js"></script>
<script src="js/vue.js"></script>
<script>
new Vue({
el:"#app",
data(){
return{
brand:{}
}
},
methods:{
submitForm(){
// 发送ajax请求,添加
var _this = this;
axios({
method:"post",
url:"http://localhost:8080/tomcat-demo2/addServlet",
data:_this.brand
}).then(function (resp) {
// 判断响应数据是否为 success
if(resp.data == "success"){
location.href = "http://localhost:8080/tomcat-demo2/brand.html";
}
})
}
}
})
</script>
14.6 Element
Element:是饿了么公司前端开发团队提供的一套基于 Vue
的网站组件库,用于快速构建网页
Element 提供了很多组件(组成网页的部件)供我们使用。例如 超链接、按钮、图片、表格等等
官网地址:https://element.eleme.cn/#/zh-CN
快速入门
1.引入Element 的css、js文件 和 Vue.js
<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">
2.创建Vue核心对象
<script>
new Vue({
el:"#app"
})
</script>
3.官网复制Element组件代码
Element布局
Element 提供了两种布局方式,分别是:
-
Layout 布局:通过基础的 24 分栏,迅速简便地创建布局
-
Container 布局容器:用于布局的容器组件,方便快速搭建页面的基本结构
Element组件 - 表格 表单
要完成的效果如下
依次完成的功能
- 表格(表格)
- 搜索表单(表单)
- 删除和新增按钮(按钮)
- 点击新增按钮弹出对话框(对话框)
- 新增对话框里的表单(表单)
- 分页工具条
element-demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
</head>
<body>
<div id="app">
<!-- 去Element官网复制,然后修改 -->
<!-- 如果要设置属性如居中,在官网中的表格-Table-column Attributes -->
<!-- 搜索表单 -->
<el-form :inline="true" :model="brand" class="demo-form-inline">
<el-form-item label="当前状态">
<el-select v-model="brand.status" placeholder="当前状态">
<el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="企业名称">
<el-input v-model="brand.companyName" placeholder="企业名称"></el-input>
</el-form-item>
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName" placeholder="品牌名称"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
</el-form-item>
</el-form>
<!-- 删除和新增按钮 -->
<el-row>
<el-button type="danger" plain>批量删除</el-button>
<el-button type="primary" @click="dialogVisible = true" plain>新增</el-button>
</el-row>
<!-- 弹出新增数据的对话框 -->
<!-- <el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button> -->
<el-dialog
title="编辑品牌"
:visible.sync="dialogVisible"
width="30%">
<!-- 新增数据对话框里面的表单 -->
<el-form ref="form" :model="brand" label-width="80px">
<el-form-item label="品牌名称">
<el-input v-model="brand.brandName"></el-input>
</el-form-item>
<el-form-item label="企业名称">
<el-input v-model="brand.companyName"></el-input>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="brand.ordered"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="brand.description"></el-input>
</el-form-item>
<!-- Switch 开关 -->
<el-form-item label="状态">
<el-switch v-model="brand.status"
active-value="1"
inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addBrand">提交</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 表格 -->
<template>
<!-- 复选框需要设置方法 @selection-change -->
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
type="index"
width="50"
label="序号">
</el-table-column>
<el-table-column
prop="brandName"
align="center"
label="品牌名称">
</el-table-column>
<el-table-column
prop="companyName"
align="center"
label="企业名称">
</el-table-column>
<el-table-column
prop="ordered"
align="center"
label="排序">
</el-table-column>
<el-table-column
prop="status"
align="center"
label="当前状态">
</el-table-column>
<el-table-column
align="center"
label="操作">
<el-row>
<el-button type="primary">修改</el-button>
<el-button type="danger">删除</el-button>
</el-row>
</el-table-column>
</el-table>
</template>
<!-- 分页工具条 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[100, 200, 300, 400]"
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
:total="400">
</el-pagination>
</div>
<script src="js/vue.js"></script>
<script src="element-ui/lib/index.js"></script>
<link rel="stylesheet" href="element-ui/lib/theme-chalk/index.css">
<script>
new Vue({
el:"#app",
methods: {
tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
},
//复选框选中后执行的方法
handleSelectionChange(val) {
this.multipleSelection = val;
// console.log(this.multipleSelection);//在控制台可以看到选中的输出
},
//查询数据方法
onSubmit() {
// console.log(this.brand);//查看是否绑定成功
//以后在这里添加和后端交互的代码
},
//添加数据
addBrand(){
console.log(this.brand);//查看是否绑定成功
},
//分页工具条函数
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
}
},
data() {
return {
//品牌模型数据
brand: {
id:'',
status: '',
brandName: '',
companyName:'',
ordered:'',
description:''
},
//复选框选中数据集合
multipleSelection: [],
//表格数据
tableData: [{
brandName: '华为',
companyName: '华为科技有限公司',
ordered: '100',
status:"1"
}, {
brandName: '华为',
companyName: '华为科技有限公司',
ordered: '100',
status:"1"
}, {
brandName: '华为',
companyName: '华为科技有限公司',
ordered: '100',
status:"1"
}, {
brandName: '华为',
companyName: '华为科技有限公司',
ordered: '100',
status:"1"
}],
//新增按钮的弹出对话框默认显示为false
dialogVisible: false,
//当前页数
currentPage: 4
}
}
})
</script>
</body>
</html>
15. 综合案例
功能列表
- 查询所有
- 新增品牌
- 修改品牌(略)
- 删除品牌(略)
- 批量删除
- 分页查询
- 条件查询
准备工作
-
创建brand-case项目,设置JavaWeb相关配置
-
创建tb_brand表
-
导入一些基础文件,如element-ui,vue.js,以及上一小节编写的element-demo.html页面,更名为brand.html
-
在BrandMapper.xml记得做列名和属性名的转换
<mapper namespace="org.example.mapper.BrandMapper"> <resultMap id="brandResultMap" type="org.example.pojo.Brand"> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap> </mapper>
注意,这里路径有问题会报找不到ResultMap,可以去classes里看BrandMapper.class和BrandMapper.xml是否在同一路径
15.1 完整项目及代码
链接:https://pan.baidu.com/s/1cU2eXbEOInovl4B-aL8BQw
提取码:744t
15.2 功能实现
和第10节案例brand_demo相同的后端代码,这里省略
查询所有
后端:SelectAllServlet.java
和之前一致,需要注意的的是,这里sevice分为了接口和实现包
前端:brand.html
brand.html就是上一节的element-demo.html,下面是需要修改的部分
<script src="js/axios-0.18.0.js"></script>
...
mounted() {
//当页面加载完成后,发送异步请求,获取数据
this.selectAll();
},
methods: {
//因为selectAll()经常用到,于是单独抽取一个函数
selectAll() {
var _this = this;
axios({
method: "get",
url: "http://localhost:8080/brand-case/selectAllServlet"
}).then(function (resp) {
_this.tableData = resp.data;
})
},
}
新增品牌
后端:AddServlet.java
package org.example.web;
@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {
private BrandService service = new BrandServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求体数据
BufferedReader br = request.getReader();
String param = br.readLine();
//JSON转为Brand对象
Brand brand = JSON.parseObject(param, Brand.class);
//添加
service.add(brand);
//成功标识
response.getWriter().write("success");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
前端:brand.html
el-button type="primary" @click="addBrand">提交</el-button>
...
data() {
return {
//品牌模型数据
brand: {
id: '',
status: '',
brandName: '',
companyName: '',
ordered: '',
description: ''
},
}
},
methods: {
addBrand() {
// console.log(this.brand);//查看是否绑定成功
var _this = this;
axios({
method: "post",
url: "http://localhost:8080/brand-case/addServlet",
data: this.brand
}).then(function (resp) {
if (resp.data == "success") {
// location.href = "http://localhost:8080/brand-case/brand.html"
//关闭对话框窗口
_this.dialogVisible = false;
//重新查询数据
_this.selectAll();
//弹出消息提示
_this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
}
})
},
}
* Servlet优化 - BaseServlet
问题:Servlet太多了,每一个功能都需要写一个Servlet
解决:Servlet归类
需求:希望像BrandService那样,同一类的Servlet写在一个文件里,如
@WebServlet("/brand/*")
public class BrandServlet {
//查询所有
public void selectAll(...) {}
//添加数据
public void add(...) {}
//修改数据
public void update(...) {}
//删除删除
public void delete(...) {}
}
之前的Servlet都是继承自HttpServlet,然后实现doPost方法,显然归类后不能再这么写,书写方法如下:
-
BaseServlet.java
!详细理解下面的代码
package org.example.web.servlet; /** * 替换HttpServlet,根据请求的最后一段路径来进行分发 */ public class BaseServlet extends HttpServlet { //根据请求的最后一段路径来进行分发,需要重写service方法 @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 获取请求路径 String uri = req.getRequestURI();//获取的路径形如 "/brand-case/brand/selectAll" //2. 获取最后一段路径,方法名 int index = uri.lastIndexOf('/'); String methodName = uri.substring(index+1); //3. 执行对象 //this:谁调用我(this所在的方法,service),我(this)代表谁 //以后使用BrandServlet extends BaseServlet,那么BrandServlet将调用父类的service,因此是BrandServlet进行调用,this代表BrandServlet //System.out.println(this);//这里的this以后代表的是BrandServlet //3.1 获取 BrandServlet / UserServlet 字节码对象 Class Class<? extends BaseServlet> cls = this.getClass(); //3.2 获取方法Method对象,根据方法名和所有参数的class获取 try { Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); //3.3 执行方法 //假设访问的是/brand/*,那么this指代的是BrandServlet,下面语句代表执行BrandServlet里面的method method.invoke(this, req, resp); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } }
-
BrandServlet.java
package org.example.web.servlet; @WebServlet("/brand/*") public class BrandServlet extends BaseServlet { private BrandService service = new BrandServiceImpl(); public void selectAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 获取数据 List<Brand> brands = service.selectAll(); //2. 封装JSON String jsonStr = JSON.toJSONString(brands); //3. 响应数据 response.setContentType("text/html;charset=utf-8"); response.getWriter().write(jsonStr); } public void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取请求体数据 BufferedReader br = request.getReader(); String param = br.readLine(); //JSON转为Brand对象 Brand brand = JSON.parseObject(param, Brand.class); //添加 service.add(brand); //成功标识 response.getWriter().write("success"); } }
这里的service没有调用doGet或者doPost方法,个人理解为:一是这里没有用到get传递参数,二是通过axois进行数据交互时,一般会使用post,这样后端只需要使用getReader()即可
-
brand.html
前端代码的地址要修改一下
url: "http://localhost:8080/brand-case/brand/add"
批量删除
后端
-
BrandMapper.java
/** * 批量删除 * 操作比较复杂,不再使用注解,而使用xml * @param ids */ void deleteByIds(@Param("ids") int[] ids);
-
BrandMapper.xml
<delete id="deleteByIds"> delete from tb_brand where id in <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach>; </delete>
-
BrandService接口和BrandServiceImpl.java
/** * 批量删除 * @param ids */ void deleteByIds(int[] ids);
@Override public void deleteByIds(int[] ids) { SqlSession session = factory.openSession(); BrandMapper mapper = session.getMapper(BrandMapper.class); mapper.deleteByIds(ids); session.commit(); session.close(); }
-
在BrandServlet.java里面添加deleteByIds方法
public void deleteByIds(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取请求体数据 BufferedReader br = request.getReader(); String param = br.readLine(); //JSON转为int[] int[] ids = JSON.parseObject(param, int[].class); //批量删除 service.deleteByIds(ids); //成功标识 response.getWriter().write("success"); }
前端:brand.html
<el-button type="danger" @click="deleteByIds" plain>批量删除</el-button>
...
data() {
return {
//复选框选中数据集合
multipleSelection: [],
//被选中的ids,用于批量删除
selectedIds:[],
}
},
methods: {
deleteByIds(){
//弹出确认提示框,显示是否删除(官网代码)
this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//用户点击确认按钮
//1. 创建id数组,并从this.multipleSelection中获取数据
for(let i=0; i<this.multipleSelection.length; i++){
let selectionElement = this.multipleSelection[i];
this.selectedIds[i] = selectionElement.id;
}
console.log(this.selectedIds);
//2. 发送ajax请求
var _this = this;
axios({
method: "post",
url: "http://localhost:8080/brand-case/brand/deleteByIds",
data: _this.selectedIds
}).then(function (resp) {
if (resp.data == "success") {
//重新查询数据
_this.selectAll();
//弹出消息提示
_this.$message({
message: '恭喜你,删除成功',
type: 'success'
});
}
})
}).catch(() => {
//用户取消按钮
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
}
注意:这里删除时获取的id和页面中展示的序号不一样
分页查询
后端
-
PageBean.java
package org.example.pojo; //使用泛型,以后更加通用 public class PageBean<T> { private int totalCount; private List<T> rows; //省略getter, setter, toString }
-
BrandMapper.java
/** * 分页查询 * @param begin * @param size * @return */ @Select("select * from tb_brand limit #{begin}, #{size}") @ResultMap("brandResultMap") List<Brand> selectByPage(@Param("begin") int begin, @Param("size") int size); /** * 查询总数 * @return */ @Select("select count(*) from tb_brand") int selectTotalCount();
-
BrandService接口
/** * 分页查询,根据当前页数和每页大小,返回当页数据和总记录数 * @param currentPage * @param pageSize * @return */ public PageBean selectByPage(int currentPage, int pageSize);
-
BrandServiceImpl.java
@Override public PageBean selectByPage(int currentPage, int pageSize) { SqlSession session = factory.openSession(); BrandMapper mapper = session.getMapper(BrandMapper.class); //计算开始索引 int begin = (currentPage-1) * pageSize; //计算查询条目数 int size = pageSize; //查询当前页数据 List<Brand> rows = mapper.selectByPage(begin, size); //查询总记录数 int totalCount = mapper.selectTotalCount(); PageBean<Brand> pageBean = new PageBean<>(); pageBean.setRows(rows); pageBean.setTotalCount(totalCount); session.close(); return pageBean; }
-
BrandServlet.java
public void selectByPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收参数 当前页码 和 每页展示条数 url?currentPage=1&pageSize=5 int currentPage = Integer.parseInt(request.getParameter("currentPage")); int pageSize = Integer.parseInt(request.getParameter("pageSize")); //2. 查询数据 PageBean<Brand> pageBean = service.selectByPage(currentPage, pageSize); //3. 返回数据 String jsonStr = JSON.toJSONString(pageBean); response.setContentType("text/html;charset=utf-8"); response.getWriter().write(jsonStr); }
前端:brand.html
<!-- 分页工具条 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20, 30, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>
data() {
return {
//分页数据:当前页数和每页展示量
currentPage: 1,
pageSize: 5,
totalCount: 100,
}
},
methods: {
//这里selectAll也要改变一下
selectAll() {
var _this = this;
axios({
method: "get",
url: "http://localhost:8080/brand-case/brand/selectByPage?currentPage="+_this.currentPage+"&pageSize="+_this.pageSize
}).then(function (resp) {
_this.tableData = resp.data.rows;
_this.totalCount = resp.data.totalCount;
})
},
handleSizeChange(val) {
// console.log(`每页 ${val} 条`);
this.pageSize = val;
this.selectAll();
},
handleCurrentChange(val) {
// console.log(`当前页: ${val}`);
this.currentPage = val;
this.selectAll();
},
}
条件查询
后端
-
BrandMapper.java
/** * 条件分页查询 * @param begin * @param size * @param brand * @return */ List<Brand> selectByPageCondition(@Param("begin") int begin, @Param("size") int size, @Param("brand") Brand brand); /** * 条件分页查询出来的总数 * @param brand * @return */ int selectTotalCountByCondition(Brand brand);
有了条件分页查询之后,之前的普通分页查询其实已经用不上了
-
BrandMapper.xml
<select id="selectByPageAndCondition" resultMap="brandResultMap"> select * from tb_brand <where> <if test="brand.brandName != null and brand.brandName != ''">and brand_name like #{brand.brandName}</if> <if test="brand.companyName != null and brand.companyName != ''">and company_name like #{brand.companyName}</if> <if test="brand.status != null">and status = #{brand.status}</if> </where> limit #{begin}, #{size} </select> <select id="selectTotalCountByCondition" resultType="java.lang.Integer"> select count(*) from tb_brand <where> <if test="brandName != null and brandName != ''">and brand_name like #{brandName}</if> <if test="companyName != null and companyName != ''">and company_name like #{companyName}</if> <if test="status != null">and status = #{status}</if> </where> </select>
-
BrandService接口
/** * 分页条件查询 * @param currentPage * @param pageSize * @param brand * @return */ public PageBean selectByPageAndCondition(int currentPage, int pageSize, Brand brand);
-
BrandServiceImpl.java
@Override public PageBean selectByPageAndCondition(int currentPage, int pageSize, Brand brand) { SqlSession session = factory.openSession(); BrandMapper mapper = session.getMapper(BrandMapper.class); //计算开始索引 int begin = (currentPage-1) * pageSize; //计算查询条目数 int size = pageSize; //处理brand条件,模糊表达式 String brandName = brand.getBrandName(); if(brandName != null && brandName.length()>0){ brand.setBrandName("%"+brandName+"%"); } String companyName = brand.getCompanyName(); if(companyName != null && companyName.length()>0){ brand.setCompanyName("%"+companyName+"%"); } //查询当前页数据 List<Brand> rows = mapper.selectByPageAndCondition(begin, size, brand); //查询总记录数 int totalCount = mapper.selectTotalCountByCondition(brand) PageBean<Brand> pageBean = new PageBean<>(); pageBean.setRows(rows); pageBean.setTotalCount(totalCount); session.close(); return pageBean; }
-
BrandServlet.java
public void selectByPageAndCondition(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收参数 当前页码 和 每页展示条数 url?currentPage=1&pageSize=5 int currentPage = Integer.parseInt(request.getParameter("currentPage")); int pageSize = Integer.parseInt(request.getParameter("pageSize")); //2. 获取查询条件对象 BufferedReader br = request.getReader(); String param = br.readLine(); Brand brand = JSON.parseObject(param, Brand.class); //2. 查询数据 PageBean<Brand> pageBean = service.selectByPageAndCondition(currentPage, pageSize, brand); //3. 返回数据 String jsonStr = JSON.toJSONString(pageBean); response.setContentType("text/html;charset=utf-8"); response.getWriter().write(jsonStr); }
前端:brand.html
修改selectAll方法
<el-button type="primary" @click="onSubmit">查询</el-button>
...
methods:{
//和之前的分页查询合并
selectAll() {
var _this = this;
axios({
method: "post",
url:"http://localhost:8080/brand-case/brand/selectByPageAndCondition?currentPage=" + _this.currentPage + "&pageSize=" + _this.pageSize,
data:this.brand
//then外面可以直接用this,then里面用+this
}).then(function (resp) {
_this.tableData = resp.data.rows;
_this.totalCount = resp.data.totalCount;
})
},
//条件分页查询
onSubmit() {
// console.log(this.brand);//查看是否绑定成功
this.selectAll();
},
}
前端优化
selectAll() {
axios({
method: "post",
url:"http://localhost:8080/brand-case/brand/selectByPageAndCondition?currentPage=" + _this.currentPage + "&pageSize=" + _this.pageSize,
data:this.brand
//then外面可以直接用this,then里面用+this
}).then(resp=>{
//新的写法
this.tableData = resp.data.rows;
this.totalCount = resp.data.totalCount;
})
},
之后可以练习,将之前的登录注册、用户操作、brand操作合并一起