四、12306系统会员基础功能的实现
乘车人表的设计
新增/修改时间保存到毫秒
drop table if exists `passenger`; create table `passenger` ( `id` bigint not null comment 'id', `member_id` bigint not null comment '会员id', `name` varchar(20) not null comment '姓名', `id_card` varchar(18) not null comment '身份证', `type` char(1) not null comment '旅客类型|枚举[PassengerTypeEnum]', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), index `member_id_index` (`member_id`) ) engine=innodb default charset=utf8mb4 comment='乘车人';
用generator-config-member.xml生成持久层代码
<table tableName="passenger" domainObjectName="Passenger"/>
打开方式Maven--generator--Plugins--mybatis-generator--双击
增加乘车人类型枚举
1 package com.zihans.train.member.enums; 2 3 import java.util.ArrayList; 4 import java.util.EnumSet; 5 import java.util.HashMap; 6 import java.util.List; 7 8 public enum PassengerTypeEnum { 9 10 ADULT("1", "成人"), 11 CHILD("2", "儿童"), 12 STUDENT("3", "学生"); 13 14 private String code; 15 16 private String desc; 17 18 PassengerTypeEnum(String code, String desc) { 19 this.code = code; 20 this.desc = desc; 21 } 22 23 public String getCode() { 24 return code; 25 } 26 27 public void setCode(String code) { 28 this.code = code; 29 } 30 31 public void setDesc(String desc) { 32 this.desc = desc; 33 } 34 35 public String getDesc() { 36 return desc; 37 } 38 39 public static List<HashMap<String,String>> getEnumList() { 40 List<HashMap<String, String>> list = new ArrayList<>(); 41 for (PassengerTypeEnum anEnum : EnumSet.allOf(PassengerTypeEnum.class)) { 42 HashMap<String, String> map = new HashMap<>(); 43 map.put("code",anEnum.code); 44 map.put("desc",anEnum.desc); 45 list.add(map); 46 } 47 return list; 48 } 49 }
乘车人新增接口开发
暂时与Passager相同,设置属性不为空。
1 package com.zihans.train.member.req; 2 3 import jakarta.validation.constraints.NotBlank; 4 5 import java.util.Date; 6 7 public class PassengerSaveReq { 8 private Long id; 9 10 //@NotNull(message = "【会员ID】不能为空") 11 private Long memberId; 12 13 @NotBlank(message = "【名字】不能为空") 14 private String name; 15 16 @NotBlank(message = "【身份证】不能为空") 17 private String idCard; 18 19 @NotBlank(message = "【旅客类型】不能为空") 20 private String type; 21 22 private Date createTime; 23 24 private Date updateTime; 25 26 public Long getId() { 27 return id; 28 } 29 30 public void setId(Long id) { 31 this.id = id; 32 } 33 34 public Long getMemberId() { 35 return memberId; 36 } 37 38 public void setMemberId(Long memberId) { 39 this.memberId = memberId; 40 } 41 42 public String getName() { 43 return name; 44 } 45 46 public void setName(String name) { 47 this.name = name; 48 } 49 50 public String getIdCard() { 51 return idCard; 52 } 53 54 public void setIdCard(String idCard) { 55 this.idCard = idCard; 56 } 57 58 public String getType() { 59 return type; 60 } 61 62 public void setType(String type) { 63 this.type = type; 64 } 65 66 public Date getCreateTime() { 67 return createTime; 68 } 69 70 public void setCreateTime(Date createTime) { 71 this.createTime = createTime; 72 } 73 74 public Date getUpdateTime() { 75 return updateTime; 76 } 77 78 public void setUpdateTime(Date updateTime) { 79 this.updateTime = updateTime; 80 } 81 82 @Override 83 public String toString() { 84 StringBuilder sb = new StringBuilder(); 85 sb.append(getClass().getSimpleName()); 86 sb.append(" ["); 87 sb.append("Hash = ").append(hashCode()); 88 sb.append(", id=").append(id); 89 sb.append(", memberId=").append(memberId); 90 sb.append(", name=").append(name); 91 sb.append(", idCard=").append(idCard); 92 sb.append(", type=").append(type); 93 sb.append(", createTime=").append(createTime); 94 sb.append(", updateTime=").append(updateTime); 95 sb.append("]"); 96 return sb.toString(); 97 } 98 }
在service层新建save保存方法,用于在数据库保存Passenger信息。
1 package com.zihans.train.member.service; 2 3 import cn.hutool.core.bean.BeanUtil; 4 import cn.hutool.core.date.DateTime; 5 import com.zihans.train.common.util.SnowUtil; 6 import com.zihans.train.member.domain.Passenger; 7 import com.zihans.train.member.mapper.PassengerMapper; 8 import com.zihans.train.member.req.PassengerSaveReq; 9 import jakarta.annotation.Resource; 10 import org.springframework.stereotype.Service; 11 12 @Service 13 public class PassengerService { 14 15 @Resource 16 private PassengerMapper passengerMapper; 17 18 public void save(PassengerSaveReq req) { 19 DateTime now = DateTime.now(); 20 Passenger passenger = BeanUtil.copyProperties(req, Passenger.class); 21 passenger.setId(SnowUtil.getSnowflakeNextId()); 22 passenger.setCreateTime(now); 23 passenger.setUpdateTime(now); 24 passengerMapper.insert(passenger); 25 } 26 }
controller
1 package com.zihans.train.member.controller; 2 3 4 import com.zihans.train.common.resp.CommonResp; 5 import com.zihans.train.member.req.PassengerSaveReq; 6 import com.zihans.train.member.service.PassengerService; 7 import jakarta.annotation.Resource; 8 import jakarta.validation.Valid; 9 import org.springframework.web.bind.annotation.PostMapping; 10 import org.springframework.web.bind.annotation.RequestBody; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RestController; 13 14 @RestController 15 @RequestMapping("/passenger") 16 public class PassengerController { 17 18 @Resource 19 private PassengerService passengerService; 20 21 @PostMapping("/save") 22 public CommonResp<Object> save(@Valid @RequestBody PassengerSaveReq req) { 23 passengerService.save(req); 24 return new CommonResp<>(); 25 } 26 27 }
使用HTTPClient保存用户登录信息
如果使用gateway拦截器登录,每次请求都需要手动增加token,很不方便,可以用HTTPClient内置的全局变量返回token。
login请求时添加将token保存在全局变量。
> {% client.log(JSON.stringify(response.body)); client.log(JSON.stringify(response.body.content.token)); client.global.set("token", response.body.content.token); %}
读取
POST http://localhost:8001/member/passenger/save Content-Type: application/json token: {{token}} { "memberId": "1", "name": "test", "idCard": "123321", "type": "1" }
使用线程本地变量存储会员信息
功能实现流程:
- 使用spring拦截器,获得登录会员信息
- 增加专用的日志拦截器,统—设置日志流水号
- 使用ThreadLocal保存登录信息
ThreadLocal可以方便线程内,多个方法传递参数。
本节方案:在接口入口获取会员信息,并放到线程本地变量,则在controller、service都可直接从线程本地变量获取会员信息
之前保存passenger需要前端传入member_id,这个不应该直接传递,要从token中拿,可以使用拦截器来实现。
gateway的拦截器只在gateway有效,而且gateway只是校验,并没有取东西,需要在member中添加拦截器,在controller处进行拦截。
拦截器是通用的,很多地方都要member_id,所以放在common模块。
common中新增拦截器MemberInterceptor拦截token。
1 package com.zihans.train.common.interceptor; 2 3 4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.json.JSONObject; 6 import cn.hutool.json.JSONUtil; 7 import com.zihans.train.common.context.LoginMemberContext; 8 import com.zihans.train.common.resp.MemberLoginResp; 9 import com.zihans.train.common.util.JwtUtil; 10 import jakarta.servlet.http.HttpServletRequest; 11 import jakarta.servlet.http.HttpServletResponse; 12 import org.slf4j.Logger; 13 import org.slf4j.LoggerFactory; 14 import org.springframework.stereotype.Component; 15 import org.springframework.web.servlet.HandlerInterceptor; 16 17 /** 18 * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 19 */ 20 @Component 21 public class MemberInterceptor implements HandlerInterceptor { 22 23 private static final Logger LOG = LoggerFactory.getLogger(MemberInterceptor.class); 24 25 @Override 26 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 27 //获取header的token参数 28 String token = request.getHeader("token"); 29 if (StrUtil.isNotBlank(token)) { 30 LOG.info("获取会员登录token:{}", token); 31 JSONObject loginMember = JwtUtil.getJSONObject(token); 32 LOG.info("当前登录会员:{}", loginMember); 33 MemberLoginResp member = JSONUtil.toBean(loginMember, MemberLoginResp.class); 34 LoginMemberContext.setMember(member); 35 } 36 return true; 37 } 38 39 }
LoginMemberContext.java上下文来保存和使用本地变量。
1 package com.zihans.train.common.context; 2 3 4 import com.zihans.train.common.resp.MemberLoginResp; 5 import org.slf4j.Logger; 6 import org.slf4j.LoggerFactory; 7 8 public class LoginMemberContext { 9 private static final Logger LOG = LoggerFactory.getLogger(LoginMemberContext.class); 10 11 private static ThreadLocal<MemberLoginResp> member = new ThreadLocal<>(); 12 13 public static MemberLoginResp getMember() { 14 return member.get(); 15 } 16 17 public static void setMember(MemberLoginResp member) { 18 LoginMemberContext.member.set(member); 19 } 20 21 public static Long getId() { 22 try { 23 return member.get().getId(); 24 } catch (Exception e) { 25 LOG.error("获取登录会员信息异常", e); 26 throw e; 27 } 28 } 29 30 }
需要配置让拦截器生效。member模块新增SpringMvcConfig实现WebMvcConfigurer接口,使拦截器生效。
1 package com.zihans.train.member.config; 2 3 import com.zihans.train.common.interceptor.MemberInterceptor; 4 import jakarta.annotation.Resource; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 9 @Configuration 10 public class SpringMvcConfig implements WebMvcConfigurer { 11 12 @Resource 13 MemberInterceptor memberInterceptor; 14 15 @Override 16 public void addInterceptors(InterceptorRegistry registry) { 17 registry.addInterceptor(memberInterceptor) 18 .addPathPatterns("/**") 19 .excludePathPatterns( 20 "/member/hello", 21 "/member/member/send-code", 22 "/member/member/login" 23 ); 24 } 25 }
1 package com.zihans.train.member.resp; 2 3 public class MemberLoginResp { 4 private Long id; 5 6 private String mobile; 7 8 private String token; 9 10 public String getToken() { 11 return token; 12 } 13 14 public void setToken(String token) { 15 this.token = token; 16 } 17 18 public Long getId() { 19 return id; 20 } 21 22 public void setId(Long id) { 23 this.id = id; 24 } 25 26 public String getMobile() { 27 return mobile; 28 } 29 30 public void setMobile(String mobile) { 31 this.mobile = mobile; 32 } 33 34 @Override 35 public String toString() { 36 final StringBuffer sb = new StringBuffer("MemberLoginResp{"); 37 sb.append("id=").append(id); 38 sb.append(", mobile='").append(mobile).append('\''); 39 sb.append(", token='").append(token).append('\''); 40 sb.append('}'); 41 return sb.toString(); 42 } 43 }
这时,就可以在PassengerService中通过拦截器获得member_id。
1 package com.zihans.train.member.service; 2 3 import cn.hutool.core.bean.BeanUtil; 4 import cn.hutool.core.date.DateTime; 5 import com.zihans.train.common.context.LoginMemberContext; 6 import com.zihans.train.common.util.SnowUtil; 7 import com.zihans.train.member.domain.Passenger; 8 import com.zihans.train.member.mapper.PassengerMapper; 9 import com.zihans.train.member.req.PassengerSaveReq; 10 import jakarta.annotation.Resource; 11 import org.springframework.stereotype.Service; 12 13 @Service 14 public class PassengerService { 15 16 @Resource 17 private PassengerMapper passengerMapper; 18 19 public void save(PassengerSaveReq req) { 20 DateTime now = DateTime.now(); 21 Passenger passenger = BeanUtil.copyProperties(req, Passenger.class); 22 passenger.setMemberId(LoginMemberContext.getId()); 23 passenger.setId(SnowUtil.getSnowflakeNextId()); 24 passenger.setCreateTime(now); 25 passenger.setUpdateTime(now); 26 passengerMapper.insert(passenger); 27 } 28 }
测试
POST http://localhost:8001/member/passenger/save Content-Type: application/json token: {{token}} { "name": "test", "idCard": "123321", "type": "1" }
此时拦截器的日志并没有打印,因为拦截器发生在AOP之前,新建一个日志拦截器类,专门用来打印日志,并将该拦截器注入相应模块。
1 package com.zihans.train.common.interceptor; 2 3 import cn.hutool.core.util.RandomUtil; 4 import jakarta.servlet.http.HttpServletRequest; 5 import jakarta.servlet.http.HttpServletResponse; 6 import org.slf4j.MDC; 7 import org.springframework.stereotype.Component; 8 import org.springframework.web.servlet.HandlerInterceptor; 9 10 /** 11 * 日志拦截器 12 */ 13 @Component 14 public class LogInterceptor implements HandlerInterceptor { 15 16 @Override 17 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 18 // 增加日志流水号 19 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3)); 20 return true; 21 } 22 23 }
前端二级路由页面开发
新增views/main/welcome.vue制作欢迎页面。
1 <template> 2 <h1>欢迎使用模拟12306售票系统</h1> 3 </template> 4 <script> 5 import { defineComponent } from 'vue'; 6 7 export default defineComponent({ 8 setup() { 9 return { 10 }; 11 }, 12 }); 13 </script> 14 <style> 15 </style>
router/index.js中增加二级路由(children中的welcome),也就是一级路由"/"+"welcome"。
1 import { createRouter, createWebHistory } from 'vue-router' 2 import store from "@/store"; 3 import {notification} from "ant-design-vue"; 4 5 const routes = [ 6 { 7 path: '/login', 8 component: () => import('../views/login.vue') 9 }, 10 { 11 path: '/', 12 component: () => import('../views/main.vue'), 13 meta: { 14 loginRequire: true 15 }, 16 children: [{ 17 path: 'welcome', 18 component: () => import('../views/main/welcome.vue'), 19 }] 20 }, 21 { 22 path: '', 23 redirect: '/welcome' 24 }, 25 ] 26 27 const router = createRouter({ 28 history: createWebHistory(process.env.BASE_URL), 29 routes 30 }) 31 32 // 路由登录拦截 33 router.beforeEach((to, from, next) => { 34 // 要不要对meta.loginRequire属性做监控拦截 35 if (to.matched.some(function (item) { 36 console.log(item, "是否需要登录校验:", item.meta.loginRequire || false); 37 return item.meta.loginRequire 38 })) { 39 const _member = store.state.member; 40 console.log("页面登录校验开始:", _member); 41 if (!_member.token) { 42 console.log("用户未登录或登录超时!"); 43 notification.error({ description: "未登录或登录超时" }); 44 next('/login'); 45 } else { 46 next(); 47 } 48 } else { 49 next(); 50 } 51 }); 52 53 export default router
一级路由到main.vue,二级路由到welcome.vue。
1 <template> 2 <a-layout id="components-layout-demo-top-side-2"> 3 <the-header-view></the-header-view> 4 <a-layout> 5 <the-sider-view></the-sider-view> 6 <a-layout-content 7 :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }" 8 > 9 <router-view></router-view> 10 </a-layout-content> 11 </a-layout> 12 </a-layout> 13 </template> 14 <script> 15 import { defineComponent } from 'vue'; 16 import TheHeaderView from "@/components/the-header"; 17 import TheSiderView from "@/components/the-sider"; 18 19 export default defineComponent({ 20 components: { 21 TheSiderView, 22 TheHeaderView, 23 }, 24 setup() { 25 return { 26 }; 27 }, 28 }); 29 </script> 30 <style> 31 #components-layout-demo-top-side-2 .logo { 32 float: left; 33 width: 120px; 34 height: 31px; 35 margin: 16px 24px 16px 0; 36 background: rgba(255, 255, 255, 0.3); 37 } 38 39 .ant-row-rtl #components-layout-demo-top-side-2 .logo { 40 float: right; 41 margin: 16px 0 16px 24px; 42 } 43 44 .site-layout-background { 45 background: #fff; 46 } 47 </style>
login.vue登陆成功直接跳到welcome页。
1 <template> 2 <a-row class="login"> 3 <a-col :span="8" :offset="8" class="login-main"> 4 <h1 style="text-align: center"><car-two-tone /> 模拟12306售票系统</h1> 5 <a-form 6 :model="loginForm" 7 name="basic" 8 autocomplete="off" 9 > 10 <a-form-item 11 label="" 12 name="mobile" 13 :rules="[{ required: true, message: '请输入手机号!' }]" 14 > 15 <a-input v-model:value="loginForm.mobile" placeholder="手机号"/> 16 </a-form-item> 17 18 <a-form-item 19 label="" 20 name="code" 21 :rules="[{ required: true, message: '请输入验证码!' }]" 22 > 23 <a-input v-model:value="loginForm.code"> 24 <template #addonAfter> 25 <a @click="sendCode">获取验证码</a> 26 </template> 27 </a-input> 28 <!--<a-input v-model:value="loginForm.code" placeholder="验证码"/>--> 29 </a-form-item> 30 31 <a-form-item> 32 <a-button type="primary" block @click="login">登录</a-button> 33 </a-form-item> 34 35 </a-form> 36 </a-col> 37 </a-row> 38 </template> 39 40 <script> 41 import { defineComponent, reactive } from 'vue'; 42 import axios from 'axios'; 43 import { notification } from 'ant-design-vue'; 44 import { useRouter } from 'vue-router' 45 import store from "@/store"; 46 47 export default defineComponent({ 48 name: "login-view", 49 setup() { 50 const router = useRouter(); 51 52 const loginForm = reactive({ 53 mobile: '13000000000', 54 code: '', 55 }); 56 57 const sendCode = () => { 58 axios.post("/member/member/send-code", { 59 mobile: loginForm.mobile 60 }).then(response => { 61 let data = response.data; 62 if (data.success) { 63 notification.success({ description: '发送验证码成功!' }); 64 loginForm.code = "8888"; 65 } else { 66 notification.error({ description: data.message }); 67 } 68 }); 69 }; 70 71 const login = () => { 72 axios.post("/member/member/login", loginForm).then((response) => { 73 let data = response.data; 74 if (data.success) { 75 notification.success({ description: '登录成功!' }); 76 // 登录成功,跳到控台主页 77 router.push("/welcome"); 78 store.commit("setMember", data.content); 79 } else { 80 notification.error({ description: data.message }); 81 } 82 }) 83 }; 84 85 return { 86 loginForm, 87 sendCode, 88 login 89 }; 90 }, 91 }); 92 </script> 93 94 <style> 95 .login-main h1 { 96 font-size: 25px; 97 font-weight: bold; 98 } 99 .login-main { 100 margin-top: 100px; 101 padding: 30px 30px 20px; 102 border: 2px solid grey; 103 border-radius: 10px; 104 background-color: #fcfcfc; 105 } 106 </style>
新增乘车人管理页面
1 <template> 2 <h1>乘车人管理</h1> 3 </template> 4 <script> 5 import { defineComponent } from 'vue'; 6 7 export default defineComponent({ 8 setup() { 9 return { 10 }; 11 }, 12 }); 13 </script> 14 <style> 15 </style>
index.js增加passenger二级路由(目前只能手打进入)
1 import { createRouter, createWebHistory } from 'vue-router' 2 import store from "@/store"; 3 import {notification} from "ant-design-vue"; 4 5 const routes = [{ 6 path: '/login', 7 component: () => import('../views/login.vue') 8 }, { 9 path: '/', 10 component: () => import('../views/main.vue'), 11 meta: { 12 loginRequire: true 13 }, 14 children: [{ 15 path: 'welcome', 16 component: () => import('../views/main/welcome.vue'), 17 }, { 18 path: 'passenger', 19 component: () => import('../views/main/passenger.vue'), 20 }] 21 }, { 22 path: '', 23 redirect: '/welcome' 24 }]; 25 26 const router = createRouter({ 27 history: createWebHistory(process.env.BASE_URL), 28 routes 29 }) 30 31 // 路由登录拦截 32 router.beforeEach((to, from, next) => { 33 // 要不要对meta.loginRequire属性做监控拦截 34 if (to.matched.some(function (item) { 35 console.log(item, "是否需要登录校验:", item.meta.loginRequire || false); 36 return item.meta.loginRequire 37 })) { 38 const _member = store.state.member; 39 console.log("页面登录校验开始:", _member); 40 if (!_member.token) { 41 console.log("用户未登录或登录超时!"); 42 notification.error({ description: "未登录或登录超时" }); 43 next('/login'); 44 } else { 45 next(); 46 } 47 } else { 48 next(); 49 } 50 }); 51 52 export default router
修改菜单,实现页面切换,实现菜单同步激活。
1 <template> 2 <a-layout-header class="header"> 3 <div class="logo" /> 4 <div style="float: right; color: white;"> 5 您好:{{member.mobile}} 6 <router-link to="/login" style="color: white;"> 7 退出登录 8 </router-link> 9 </div> 10 <a-menu 11 v-model:selectedKeys="selectedKeys" 12 theme="dark" 13 mode="horizontal" 14 :style="{ lineHeight: '64px' }" 15 > 16 <a-menu-item key="/welcome"> 17 <router-link to="/welcome"> 18 <coffee-outlined /> 欢迎 19 </router-link> 20 </a-menu-item> 21 <a-menu-item key="/passenger"> 22 <router-link to="/passenger"> 23 <user-outlined /> 乘车人管理 24 </router-link> 25 </a-menu-item> 26 </a-menu> 27 </a-layout-header> 28 </template> 29 30 <script> 31 import {defineComponent, ref, watch} from 'vue'; 32 import store from "@/store"; 33 import router from '@/router' 34 35 export default defineComponent({ 36 name: "the-header-view", 37 setup() { 38 let member = store.state.member; 39 const selectedKeys = ref([]); 40 41 watch(() => router.currentRoute.value.path, (newValue) => { 42 console.log('watch', newValue); 43 selectedKeys.value = []; 44 selectedKeys.value.push(newValue); 45 }, {immediate: true}); 46 return { 47 member, 48 selectedKeys 49 }; 50 }, 51 }); 52 </script> 53 54 <!-- Add "scoped" attribute to limit CSS to this component only --> 55 <style scoped> 56 57 </style>
1 <template> 2 <a-layout-sider width="200" style="background: #fff"> 3 <a-menu 4 v-model:selectedKeys="selectedKeys" 5 mode="inline" 6 :style="{ height: '100%', borderRight: 0 }" 7 > 8 <a-menu-item key="/welcome"> 9 <router-link to="/welcome"> 10 <coffee-outlined /> 欢迎 11 </router-link> 12 </a-menu-item> 13 <a-menu-item key="/passenger"> 14 <router-link to="/passenger"> 15 <user-outlined /> 乘车人管理 16 </router-link> 17 </a-menu-item> 18 </a-menu> 19 </a-layout-sider> 20 </template> 21 22 <script> 23 import {defineComponent, ref, watch} from 'vue'; 24 import router from "@/router"; 25 26 export default defineComponent({ 27 name: "the-sider-view", 28 setup() { 29 const selectedKeys = ref([]); 30 31 watch(() => router.currentRoute.value.path, (newValue) => { 32 console.log('watch', newValue); 33 selectedKeys.value = []; 34 selectedKeys.value.push(newValue); 35 }, {immediate: true}); 36 return { 37 selectedKeys 38 }; 39 }, 40 }); 41 </script> 42 43 <!-- Add "scoped" attribute to limit CSS to this component only --> 44 <style scoped> 45 46 </style>
乘车人新增界面开发
乘车人新增界面开发,增加【新增】按钮,点击弹出Modal
1 <template> 2 <a-button type="primary" @click="showModal">新增</a-button> 3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"> 4 <p>Some contents...</p> 5 <p>Some contents...</p> 6 <p>Some contents...</p> 7 </a-modal> 8 </template> 9 <script> 10 import { defineComponent, ref } from 'vue'; 11 12 export default defineComponent({ 13 setup() { 14 const visible = ref(false); 15 16 const showModal = () => { 17 visible.value = true; 18 }; 19 20 const handleOk = e => { 21 console.log(e); 22 visible.value = false; 23 }; 24 return { 25 visible, 26 showModal, 27 handleOk, 28 }; 29 }, 30 }); 31 </script> 32 <style> 33 </style>
乘车人新增界面开发,增加乘车人表单
1 <template> 2 <a-button type="primary" @click="showModal">新增</a-button> 3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 4 ok-text="确认" cancel-text="取消"> 5 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 6 <a-form-item label="姓名"> 7 <a-input v-model:value="passenger.name" /> 8 </a-form-item> 9 <a-form-item label="身份证"> 10 <a-input v-model:value="passenger.idCard" /> 11 </a-form-item> 12 <a-form-item label="类型"> 13 <a-select v-model:value="passenger.type"> 14 <a-select-option value="1">成人</a-select-option> 15 <a-select-option value="2">儿童</a-select-option> 16 <a-select-option value="3">学生</a-select-option> 17 </a-select> 18 </a-form-item> 19 </a-form> 20 </a-modal> 21 </template> 22 <script> 23 import { defineComponent, ref, reactive } from 'vue'; 24 25 export default defineComponent({ 26 setup() { 27 const visible = ref(false); 28 const passenger = reactive({ 29 id: undefined, 30 memberId: undefined, 31 name: undefined, 32 idCard: undefined, 33 type: undefined, 34 createTime: undefined, 35 updateTime: undefined, 36 }); 37 38 const showModal = () => { 39 visible.value = true; 40 }; 41 42 const handleOk = e => { 43 console.log(e); 44 visible.value = false; 45 }; 46 return { 47 passenger, 48 visible, 49 showModal, 50 handleOk, 51 }; 52 }, 53 }); 54 </script> 55 <style> 56 </style>
乘车人新增界面开发,完成保存功能
1 <template> 2 <a-button type="primary" @click="showModal">新增</a-button> 3 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 4 ok-text="确认" cancel-text="取消"> 5 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 6 <a-form-item label="姓名"> 7 <a-input v-model:value="passenger.name" /> 8 </a-form-item> 9 <a-form-item label="身份证"> 10 <a-input v-model:value="passenger.idCard" /> 11 </a-form-item> 12 <a-form-item label="类型"> 13 <a-select v-model:value="passenger.type"> 14 <a-select-option value="1">成人</a-select-option> 15 <a-select-option value="2">儿童</a-select-option> 16 <a-select-option value="3">学生</a-select-option> 17 </a-select> 18 </a-form-item> 19 </a-form> 20 </a-modal> 21 </template> 22 <script> 23 import { defineComponent, ref, reactive } from 'vue'; 24 import {notification} from "ant-design-vue"; 25 import axios from "axios"; 26 27 export default defineComponent({ 28 setup() { 29 const visible = ref(false); 30 const passenger = reactive({ 31 id: undefined, 32 memberId: undefined, 33 name: undefined, 34 idCard: undefined, 35 type: undefined, 36 createTime: undefined, 37 updateTime: undefined, 38 }); 39 40 const showModal = () => { 41 visible.value = true; 42 }; 43 44 const handleOk = () => { 45 axios.post("/member/passenger/save", passenger).then((response) => { 46 let data = response.data; 47 if (data.success) { 48 notification.success({description: "保存成功!"}); 49 visible.value = false; 50 } else { 51 notification.error({description: data.message}); 52 } 53 }); 54 }; 55 56 return { 57 passenger, 58 visible, 59 showModal, 60 handleOk, 61 }; 62 }, 63 }); 64 </script> 65 <style> 66 </style>
乘车人列表查询接口开发
所有的列表查询接口,都要注意查询的数据量,不能因为查询量太大,把内存打爆,也不能因为数据量太大,而把带宽打爆,特别注意是否有大字段,如多媒体字段。
控台接口、会员接口、第三方接口等,不同的接口用不同的前缀,比如控台前面是/admin/xxx,第三方全部是/api/xxx,做接口隔离不要混用。
增加乘车人列表查询接口,只能查看当前全员自己添加的乘客
1 package com.zihans.train.member.resp; 2 3 import java.util.Date; 4 5 public class PassengerQueryResp { 6 private Long id; 7 8 private Long memberId; 9 10 private String name; 11 12 private String idCard; 13 14 private String type; 15 16 private Date createTime; 17 18 private Date updateTime; 19 20 public Long getId() { 21 return id; 22 } 23 24 public void setId(Long id) { 25 this.id = id; 26 } 27 28 public Long getMemberId() { 29 return memberId; 30 } 31 32 public void setMemberId(Long memberId) { 33 this.memberId = memberId; 34 } 35 36 public String getName() { 37 return name; 38 } 39 40 public void setName(String name) { 41 this.name = name; 42 } 43 44 public String getIdCard() { 45 return idCard; 46 } 47 48 public void setIdCard(String idCard) { 49 this.idCard = idCard; 50 } 51 52 public String getType() { 53 return type; 54 } 55 56 public void setType(String type) { 57 this.type = type; 58 } 59 60 public Date getCreateTime() { 61 return createTime; 62 } 63 64 public void setCreateTime(Date createTime) { 65 this.createTime = createTime; 66 } 67 68 public Date getUpdateTime() { 69 return updateTime; 70 } 71 72 public void setUpdateTime(Date updateTime) { 73 this.updateTime = updateTime; 74 } 75 76 @Override 77 public String toString() { 78 StringBuilder sb = new StringBuilder(); 79 sb.append(getClass().getSimpleName()); 80 sb.append(" ["); 81 sb.append("Hash = ").append(hashCode()); 82 sb.append(", id=").append(id); 83 sb.append(", memberId=").append(memberId); 84 sb.append(", name=").append(name); 85 sb.append(", idCard=").append(idCard); 86 sb.append(", type=").append(type); 87 sb.append(", createTime=").append(createTime); 88 sb.append(", updateTime=").append(updateTime); 89 sb.append("]"); 90 return sb.toString(); 91 } 92 }
1 package com.zihans.train.member.resp; 2 3 import java.util.Date; 4 5 public class PassengerQueryResp { 6 private Long id; 7 8 private Long memberId; 9 10 private String name; 11 12 private String idCard; 13 14 private String type; 15 16 private Date createTime; 17 18 private Date updateTime; 19 20 public Long getId() { 21 return id; 22 } 23 24 public void setId(Long id) { 25 this.id = id; 26 } 27 28 public Long getMemberId() { 29 return memberId; 30 } 31 32 public void setMemberId(Long memberId) { 33 this.memberId = memberId; 34 } 35 36 public String getName() { 37 return name; 38 } 39 40 public void setName(String name) { 41 this.name = name; 42 } 43 44 public String getIdCard() { 45 return idCard; 46 } 47 48 public void setIdCard(String idCard) { 49 this.idCard = idCard; 50 } 51 52 public String getType() { 53 return type; 54 } 55 56 public void setType(String type) { 57 this.type = type; 58 } 59 60 public Date getCreateTime() { 61 return createTime; 62 } 63 64 public void setCreateTime(Date createTime) { 65 this.createTime = createTime; 66 } 67 68 public Date getUpdateTime() { 69 return updateTime; 70 } 71 72 public void setUpdateTime(Date updateTime) { 73 this.updateTime = updateTime; 74 } 75 76 @Override 77 public String toString() { 78 StringBuilder sb = new StringBuilder(); 79 sb.append(getClass().getSimpleName()); 80 sb.append(" ["); 81 sb.append("Hash = ").append(hashCode()); 82 sb.append(", id=").append(id); 83 sb.append(", memberId=").append(memberId); 84 sb.append(", name=").append(name); 85 sb.append(", idCard=").append(idCard); 86 sb.append(", type=").append(type); 87 sb.append(", createTime=").append(createTime); 88 sb.append(", updateTime=").append(updateTime); 89 sb.append("]"); 90 return sb.toString(); 91 } 92 }
PassengerService新增查询方法
public List<PassengerQueryResp> queryList(PassengerQueryReq req) { PassengerExample passengerExample = new PassengerExample(); PassengerExample.Criteria criteria = passengerExample.createCriteria(); if (ObjectUtil.isNotNull(req.getMemberId())) { criteria.andMemberIdEqualTo(req.getMemberId()); } List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample); return BeanUtil.copyToList(passengerList, PassengerQueryResp.class); }
PassengerController也新增查询方法
@GetMapping("/query-list") public CommonResp<List<PassengerQueryResp>> queryList(@Valid PassengerQueryReq req) { req.setMemberId(LoginMemberContext.getId()); List<PassengerQueryResp> list = passengerService.queryList(req); return new CommonResp<>(list); }
测试
GET http://localhost:8000/member/passenger/query-list Accept: application/json token: {{token}}
集成PageHelper实现后端分页
列表接口要做分页,否则容易因为数据量大而把内存打爆或把带宽打爆。
PageHelper.startPage只对第—条遇到的sql起作用,使用时,要紧跟着查询语句。
父pom新增插件,用来mybatis分页,common模块也使用。
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>
在Passenger中执行sql的地方上方插入分页代码(第一页查两条)
PageHelper.startPage(1,2); List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample);
封装分页参数请求
1 package com.zihans.train.common.req; 2 3 import jakarta.validation.constraints.Max; 4 import jakarta.validation.constraints.NotNull; 5 6 public class PageReq { 7 8 @NotNull(message = "【页码】不能为空") 9 private Integer page; 10 11 @NotNull(message = "【每页条数】不能为空") 12 @Max(value = 100, message = "【每页条数】不能超过100") 13 private Integer size; 14 15 public Integer getPage() { 16 return page; 17 } 18 19 public void setPage(Integer page) { 20 this.page = page; 21 } 22 23 public Integer getSize() { 24 return size; 25 } 26 27 public void setSize(Integer size) { 28 this.size = size; 29 } 30 31 @Override 32 public String toString() { 33 return "PageReq{" + 34 "page=" + page + 35 ", size=" + size + 36 '}'; 37 } 38 }
PassengerQueryReq继承PageReq
public class PassengerQueryReq extends PageReq{
PassengerService修改分页请求参数,跟随url传入
- PageHelper.startPage(2, 2); + PageHelper.startPage(req.getPage(), req.getSize());
查询
GET http://localhost:8000/member/passenger/query-list?page=2&size=200
封装分页返回结果
1 import java.io.Serializable; 2 import java.util.List; 3 4 public class PageResp<T> implements Serializable { 5 6 /** 7 * 总条数 8 */ 9 private Long total; 10 11 /** 12 * 当前页的列表 13 */ 14 private List<T> list; 15 16 public Long getTotal() { 17 return total; 18 } 19 20 public void setTotal(Long total) { 21 this.total = total; 22 } 23 24 public List<T> getList() { 25 return list; 26 } 27 28 public void setList(List<T> list) { 29 this.list = list; 30 } 31 32 @Override 33 public String toString() { 34 return "PageResp{" + 35 "total=" + total + 36 ", list=" + list + 37 '}'; 38 } 39 }
service层
1 public PageResp<PassengerQueryResp> queryList(PassengerQueryReq req) { 2 PassengerExample passengerExample = new PassengerExample(); 3 PassengerExample.Criteria criteria = passengerExample.createCriteria(); 4 if (ObjectUtil.isNotNull(req.getMemberId())) { 5 criteria.andMemberIdEqualTo(req.getMemberId()); 6 } 7 8 LOG.info("查询页码:{}", req.getPage()); 9 LOG.info("每页条数:{}", req.getSize()); 10 PageHelper.startPage(req.getPage(), req.getSize()); 11 List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample); 12 13 PageInfo<Passenger> pageInfo = new PageInfo<>(passengerList); 14 LOG.info("总行数:{}", pageInfo.getTotal()); 15 LOG.info("总页数:{}", pageInfo.getPages()); 16 17 List<PassengerQueryResp> list = BeanUtil.copyToList(passengerList, PassengerQueryResp.class); 18 19 PageResp<PassengerQueryResp> pageResp = new PageResp<>(); 20 pageResp.setTotal(pageInfo.getTotal()); 21 pageResp.setList(list); 22 return pageResp; 23 }
controller层
1 @GetMapping("/query-list") 2 public CommonResp<PageResp<PassengerQueryResp>> queryList(@Valid PassengerQueryReq req) { 3 req.setMemberId(LoginMemberContext.getId()); 4 PageResp<PassengerQueryResp> list = passengerService.queryList(req); 5 return new CommonResp<>(list); 6 }
格式化PassengerQueryResp.java中的日期字段
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date updateTime;
乘车人列表前端开发
引入表格组件
1 <template> 2 <p> 3 <a-button type="primary" @click="showModal">新增</a-button> 4 </p> 5 <a-table :dataSource="dataSource" :columns="columns" /> 6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 7 ok-text="确认" cancel-text="取消"> 8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 9 <a-form-item label="姓名"> 10 <a-input v-model:value="passenger.name" /> 11 </a-form-item> 12 <a-form-item label="身份证"> 13 <a-input v-model:value="passenger.idCard" /> 14 </a-form-item> 15 <a-form-item label="类型"> 16 <a-select v-model:value="passenger.type"> 17 <a-select-option value="1">成人</a-select-option> 18 <a-select-option value="2">儿童</a-select-option> 19 <a-select-option value="3">学生</a-select-option> 20 </a-select> 21 </a-form-item> 22 </a-form> 23 </a-modal> 24 </template> 25 <script> 26 import { defineComponent, ref, reactive } from 'vue'; 27 import {notification} from "ant-design-vue"; 28 import axios from "axios"; 29 30 export default defineComponent({ 31 setup() { 32 const visible = ref(false); 33 const passenger = reactive({ 34 id: undefined, 35 memberId: undefined, 36 name: undefined, 37 idCard: undefined, 38 type: undefined, 39 createTime: undefined, 40 updateTime: undefined, 41 }); 42 const dataSource = [{ 43 key: '1', 44 name: '胡彦斌', 45 age: 32, 46 address: '西湖区湖底公园1号', 47 }, { 48 key: '2', 49 name: '胡彦祖', 50 age: 42, 51 address: '西湖区湖底公园1号', 52 }]; 53 const columns = [{ 54 title: '姓名', 55 dataIndex: 'name', 56 key: 'name', 57 }, { 58 title: '年龄', 59 dataIndex: 'age', 60 key: 'age', 61 }, { 62 title: '住址', 63 dataIndex: 'address', 64 key: 'address', 65 }]; 66 67 const showModal = () => { 68 visible.value = true; 69 }; 70 71 const handleOk = () => { 72 axios.post("/member/passenger/save", passenger).then((response) => { 73 let data = response.data; 74 if (data.success) { 75 notification.success({description: "保存成功!"}); 76 visible.value = false; 77 } else { 78 notification.error({description: data.message}); 79 } 80 }); 81 }; 82 83 return { 84 passenger, 85 visible, 86 showModal, 87 handleOk, 88 dataSource, 89 columns 90 }; 91 }, 92 }); 93 </script> 94 <style> 95 </style>
使用axios调用后端查询接口
1 <template> 2 <p> 3 <a-button type="primary" @click="showModal">新增</a-button> 4 </p> 5 <a-table :dataSource="passengers" :columns="columns" /> 6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 7 ok-text="确认" cancel-text="取消"> 8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 9 <a-form-item label="姓名"> 10 <a-input v-model:value="passenger.name" /> 11 </a-form-item> 12 <a-form-item label="身份证"> 13 <a-input v-model:value="passenger.idCard" /> 14 </a-form-item> 15 <a-form-item label="类型"> 16 <a-select v-model:value="passenger.type"> 17 <a-select-option value="1">成人</a-select-option> 18 <a-select-option value="2">儿童</a-select-option> 19 <a-select-option value="3">学生</a-select-option> 20 </a-select> 21 </a-form-item> 22 </a-form> 23 </a-modal> 24 </template> 25 <script> 26 import { defineComponent, ref, reactive, onMounted } from 'vue'; 27 import {notification} from "ant-design-vue"; 28 import axios from "axios"; 29 30 export default defineComponent({ 31 setup() { 32 const visible = ref(false); 33 const passenger = reactive({ 34 id: undefined, 35 memberId: undefined, 36 name: undefined, 37 idCard: undefined, 38 type: undefined, 39 createTime: undefined, 40 updateTime: undefined, 41 }); 42 const passengers = ref([]); 43 const columns = [{ 44 title: '姓名', 45 dataIndex: 'name', 46 key: 'name', 47 }, { 48 title: '身份证', 49 dataIndex: 'idCard', 50 key: 'idCard', 51 }, { 52 title: '类型', 53 dataIndex: 'type', 54 key: 'type', 55 }]; 56 57 const showModal = () => { 58 visible.value = true; 59 }; 60 61 const handleOk = () => { 62 axios.post("/member/passenger/save", passenger).then((response) => { 63 let data = response.data; 64 if (data.success) { 65 notification.success({description: "保存成功!"}); 66 visible.value = false; 67 } else { 68 notification.error({description: data.message}); 69 } 70 }); 71 }; 72 73 const handleQuery = (param) => { 74 axios.get("/member/passenger/query-list", { 75 params: { 76 page: param.page, 77 size: param.size 78 } 79 }).then((response) => { 80 let data = response.data; 81 if (data.success) { 82 passengers.value = data.content.list; 83 } else { 84 notification.error({description: data.message}); 85 } 86 }); 87 }; 88 89 onMounted(() => { 90 handleQuery({ 91 page: 1, 92 size: 2 93 }); 94 }); 95 96 return { 97 passenger, 98 visible, 99 showModal, 100 handleOk, 101 passengers, 102 columns 103 }; 104 }, 105 }); 106 </script> 107 <style> 108 </style>
显示分页页码
1 <template> 2 <p> 3 <a-button type="primary" @click="showModal">新增</a-button> 4 </p> 5 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination"/> 6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 7 ok-text="确认" cancel-text="取消"> 8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 9 <a-form-item label="姓名"> 10 <a-input v-model:value="passenger.name" /> 11 </a-form-item> 12 <a-form-item label="身份证"> 13 <a-input v-model:value="passenger.idCard" /> 14 </a-form-item> 15 <a-form-item label="类型"> 16 <a-select v-model:value="passenger.type"> 17 <a-select-option value="1">成人</a-select-option> 18 <a-select-option value="2">儿童</a-select-option> 19 <a-select-option value="3">学生</a-select-option> 20 </a-select> 21 </a-form-item> 22 </a-form> 23 </a-modal> 24 </template> 25 <script> 26 import { defineComponent, ref, reactive, onMounted } from 'vue'; 27 import {notification} from "ant-design-vue"; 28 import axios from "axios"; 29 30 export default defineComponent({ 31 setup() { 32 const visible = ref(false); 33 const passenger = reactive({ 34 id: undefined, 35 memberId: undefined, 36 name: undefined, 37 idCard: undefined, 38 type: undefined, 39 createTime: undefined, 40 updateTime: undefined, 41 }); 42 const passengers = ref([]); 43 // 分页的三个属性名是固定的 44 const pagination = reactive({ 45 total: 0, 46 current: 1, 47 pageSize: 2, 48 }); 49 const columns = [{ 50 title: '姓名', 51 dataIndex: 'name', 52 key: 'name', 53 }, { 54 title: '身份证', 55 dataIndex: 'idCard', 56 key: 'idCard', 57 }, { 58 title: '类型', 59 dataIndex: 'type', 60 key: 'type', 61 }]; 62 63 const showModal = () => { 64 visible.value = true; 65 }; 66 67 const handleOk = () => { 68 axios.post("/member/passenger/save", passenger).then((response) => { 69 let data = response.data; 70 if (data.success) { 71 notification.success({description: "保存成功!"}); 72 visible.value = false; 73 } else { 74 notification.error({description: data.message}); 75 } 76 }); 77 }; 78 79 const handleQuery = (param) => { 80 axios.get("/member/passenger/query-list", { 81 params: { 82 page: param.page, 83 size: param.size 84 } 85 }).then((response) => { 86 let data = response.data; 87 if (data.success) { 88 passengers.value = data.content.list; 89 pagination.total = data.content.total; 90 } else { 91 notification.error({description: data.message}); 92 } 93 }); 94 }; 95 96 onMounted(() => { 97 handleQuery({ 98 page: 1, 99 size: 2 100 }); 101 }); 102 103 return { 104 passenger, 105 visible, 106 showModal, 107 handleOk, 108 passengers, 109 pagination, 110 columns 111 }; 112 }, 113 }); 114 </script> 115 <style> 116 </style>
增加表格分页点击事件
1 <template> 2 <p> 3 <a-button type="primary" @click="showModal">新增</a-button> 4 </p> 5 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination" @change="handleTableChange"/> 6 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 7 ok-text="确认" cancel-text="取消"> 8 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 9 <a-form-item label="姓名"> 10 <a-input v-model:value="passenger.name" /> 11 </a-form-item> 12 <a-form-item label="身份证"> 13 <a-input v-model:value="passenger.idCard" /> 14 </a-form-item> 15 <a-form-item label="类型"> 16 <a-select v-model:value="passenger.type"> 17 <a-select-option value="1">成人</a-select-option> 18 <a-select-option value="2">儿童</a-select-option> 19 <a-select-option value="3">学生</a-select-option> 20 </a-select> 21 </a-form-item> 22 </a-form> 23 </a-modal> 24 </template> 25 <script> 26 import { defineComponent, ref, reactive, onMounted } from 'vue'; 27 import {notification} from "ant-design-vue"; 28 import axios from "axios"; 29 30 export default defineComponent({ 31 setup() { 32 const visible = ref(false); 33 const passenger = reactive({ 34 id: undefined, 35 memberId: undefined, 36 name: undefined, 37 idCard: undefined, 38 type: undefined, 39 createTime: undefined, 40 updateTime: undefined, 41 }); 42 const passengers = ref([]); 43 // 分页的三个属性名是固定的 44 const pagination = reactive({ 45 total: 0, 46 current: 1, 47 pageSize: 2, 48 }); 49 const columns = [{ 50 title: '姓名', 51 dataIndex: 'name', 52 key: 'name', 53 }, { 54 title: '身份证', 55 dataIndex: 'idCard', 56 key: 'idCard', 57 }, { 58 title: '类型', 59 dataIndex: 'type', 60 key: 'type', 61 }]; 62 63 const showModal = () => { 64 visible.value = true; 65 }; 66 67 const handleOk = () => { 68 axios.post("/member/passenger/save", passenger).then((response) => { 69 let data = response.data; 70 if (data.success) { 71 notification.success({description: "保存成功!"}); 72 visible.value = false; 73 } else { 74 notification.error({description: data.message}); 75 } 76 }); 77 }; 78 79 const handleQuery = (param) => { 80 axios.get("/member/passenger/query-list", { 81 params: { 82 page: param.page, 83 size: param.size 84 } 85 }).then((response) => { 86 let data = response.data; 87 if (data.success) { 88 passengers.value = data.content.list; 89 // 设置分页控件的值 90 pagination.current = param.page; 91 pagination.total = data.content.total; 92 } else { 93 notification.error({description: data.message}); 94 } 95 }); 96 }; 97 98 const handleTableChange = (pagination) => { 99 // console.log("看看自带的分页参数都有啥:" + pagination); 100 handleQuery({ 101 page: pagination.current, 102 size: pagination.pageSize 103 }); 104 }; 105 106 onMounted(() => { 107 handleQuery({ 108 page: 1, 109 size: pagination.pageSize 110 }); 111 }); 112 113 return { 114 passenger, 115 visible, 116 showModal, 117 handleOk, 118 passengers, 119 pagination, 120 columns, 121 handleTableChange 122 }; 123 }, 124 }); 125 </script> 126 <style> 127 </style>
增加刷新按钮,点击查询第1页
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="showModal">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" :columns="columns" :pagination="pagination" @change="handleTableChange"/> 9 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 10 ok-text="确认" cancel-text="取消"> 11 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 12 <a-form-item label="姓名"> 13 <a-input v-model:value="passenger.name" /> 14 </a-form-item> 15 <a-form-item label="身份证"> 16 <a-input v-model:value="passenger.idCard" /> 17 </a-form-item> 18 <a-form-item label="类型"> 19 <a-select v-model:value="passenger.type"> 20 <a-select-option value="1">成人</a-select-option> 21 <a-select-option value="2">儿童</a-select-option> 22 <a-select-option value="3">学生</a-select-option> 23 </a-select> 24 </a-form-item> 25 </a-form> 26 </a-modal> 27 </template> 28 <script> 29 import { defineComponent, ref, reactive, onMounted } from 'vue'; 30 import {notification} from "ant-design-vue"; 31 import axios from "axios"; 32 33 export default defineComponent({ 34 setup() { 35 const visible = ref(false); 36 const passenger = reactive({ 37 id: undefined, 38 memberId: undefined, 39 name: undefined, 40 idCard: undefined, 41 type: undefined, 42 createTime: undefined, 43 updateTime: undefined, 44 }); 45 const passengers = ref([]); 46 // 分页的三个属性名是固定的 47 const pagination = reactive({ 48 total: 0, 49 current: 1, 50 pageSize: 2, 51 }); 52 const columns = [{ 53 title: '姓名', 54 dataIndex: 'name', 55 key: 'name', 56 }, { 57 title: '身份证', 58 dataIndex: 'idCard', 59 key: 'idCard', 60 }, { 61 title: '类型', 62 dataIndex: 'type', 63 key: 'type', 64 }]; 65 66 const showModal = () => { 67 visible.value = true; 68 }; 69 70 const handleOk = () => { 71 axios.post("/member/passenger/save", passenger).then((response) => { 72 let data = response.data; 73 if (data.success) { 74 notification.success({description: "保存成功!"}); 75 visible.value = false; 76 } else { 77 notification.error({description: data.message}); 78 } 79 }); 80 }; 81 82 const handleQuery = (param) => { 83 if (!param) { 84 param = { 85 page: 1, 86 size: pagination.pageSize 87 }; 88 } 89 axios.get("/member/passenger/query-list", { 90 params: { 91 page: param.page, 92 size: param.size 93 } 94 }).then((response) => { 95 let data = response.data; 96 if (data.success) { 97 passengers.value = data.content.list; 98 // 设置分页控件的值 99 pagination.current = param.page; 100 pagination.total = data.content.total; 101 } else { 102 notification.error({description: data.message}); 103 } 104 }); 105 }; 106 107 const handleTableChange = (pagination) => { 108 // console.log("看看自带的分页参数都有啥:" + pagination); 109 handleQuery({ 110 page: pagination.current, 111 size: pagination.pageSize 112 }); 113 }; 114 115 onMounted(() => { 116 handleQuery({ 117 page: 1, 118 size: pagination.pageSize 119 }); 120 }); 121 122 return { 123 passenger, 124 visible, 125 showModal, 126 handleOk, 127 passengers, 128 pagination, 129 columns, 130 handleTableChange, 131 handleQuery 132 }; 133 }, 134 }); 135 </script> 136 <style> 137 </style>
增加loading效果
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="showModal">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"/> 13 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 14 ok-text="确认" cancel-text="取消"> 15 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 16 <a-form-item label="姓名"> 17 <a-input v-model:value="passenger.name" /> 18 </a-form-item> 19 <a-form-item label="身份证"> 20 <a-input v-model:value="passenger.idCard" /> 21 </a-form-item> 22 <a-form-item label="类型"> 23 <a-select v-model:value="passenger.type"> 24 <a-select-option value="1">成人</a-select-option> 25 <a-select-option value="2">儿童</a-select-option> 26 <a-select-option value="3">学生</a-select-option> 27 </a-select> 28 </a-form-item> 29 </a-form> 30 </a-modal> 31 </template> 32 <script> 33 import { defineComponent, ref, reactive, onMounted } from 'vue'; 34 import {notification} from "ant-design-vue"; 35 import axios from "axios"; 36 37 export default defineComponent({ 38 setup() { 39 const visible = ref(false); 40 const passenger = reactive({ 41 id: undefined, 42 memberId: undefined, 43 name: undefined, 44 idCard: undefined, 45 type: undefined, 46 createTime: undefined, 47 updateTime: undefined, 48 }); 49 const passengers = ref([]); 50 // 分页的三个属性名是固定的 51 const pagination = reactive({ 52 total: 0, 53 current: 1, 54 pageSize: 2, 55 }); 56 let loading = ref(false); 57 const columns = [{ 58 title: '姓名', 59 dataIndex: 'name', 60 key: 'name', 61 }, { 62 title: '身份证', 63 dataIndex: 'idCard', 64 key: 'idCard', 65 }, { 66 title: '类型', 67 dataIndex: 'type', 68 key: 'type', 69 }]; 70 71 const showModal = () => { 72 visible.value = true; 73 }; 74 75 const handleOk = () => { 76 axios.post("/member/passenger/save", passenger).then((response) => { 77 let data = response.data; 78 if (data.success) { 79 notification.success({description: "保存成功!"}); 80 visible.value = false; 81 } else { 82 notification.error({description: data.message}); 83 } 84 }); 85 }; 86 87 const handleQuery = (param) => { 88 if (!param) { 89 param = { 90 page: 1, 91 size: pagination.pageSize 92 }; 93 } 94 loading.value = true; 95 axios.get("/member/passenger/query-list", { 96 params: { 97 page: param.page, 98 size: param.size 99 } 100 }).then((response) => { 101 loading.value = false; 102 let data = response.data; 103 if (data.success) { 104 passengers.value = data.content.list; 105 // 设置分页控件的值 106 pagination.current = param.page; 107 pagination.total = data.content.total; 108 } else { 109 notification.error({description: data.message}); 110 } 111 }); 112 }; 113 114 const handleTableChange = (pagination) => { 115 // console.log("看看自带的分页参数都有啥:" + pagination); 116 handleQuery({ 117 page: pagination.current, 118 size: pagination.pageSize 119 }); 120 }; 121 122 onMounted(() => { 123 handleQuery({ 124 page: 1, 125 size: pagination.pageSize 126 }); 127 }); 128 129 return { 130 passenger, 131 visible, 132 showModal, 133 handleOk, 134 passengers, 135 pagination, 136 columns, 137 handleTableChange, 138 handleQuery, 139 loading 140 }; 141 }, 142 }); 143 </script> 144 <style> 145 </style>
保存成功后刷新列表
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="showModal">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"/> 13 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 14 ok-text="确认" cancel-text="取消"> 15 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 16 <a-form-item label="姓名"> 17 <a-input v-model:value="passenger.name" /> 18 </a-form-item> 19 <a-form-item label="身份证"> 20 <a-input v-model:value="passenger.idCard" /> 21 </a-form-item> 22 <a-form-item label="类型"> 23 <a-select v-model:value="passenger.type"> 24 <a-select-option value="1">成人</a-select-option> 25 <a-select-option value="2">儿童</a-select-option> 26 <a-select-option value="3">学生</a-select-option> 27 </a-select> 28 </a-form-item> 29 </a-form> 30 </a-modal> 31 </template> 32 <script> 33 import { defineComponent, ref, reactive, onMounted } from 'vue'; 34 import {notification} from "ant-design-vue"; 35 import axios from "axios"; 36 37 export default defineComponent({ 38 setup() { 39 const visible = ref(false); 40 const passenger = reactive({ 41 id: undefined, 42 memberId: undefined, 43 name: undefined, 44 idCard: undefined, 45 type: undefined, 46 createTime: undefined, 47 updateTime: undefined, 48 }); 49 const passengers = ref([]); 50 // 分页的三个属性名是固定的 51 const pagination = reactive({ 52 total: 0, 53 current: 1, 54 pageSize: 2, 55 }); 56 let loading = ref(false); 57 const columns = [{ 58 title: '姓名', 59 dataIndex: 'name', 60 key: 'name', 61 }, { 62 title: '身份证', 63 dataIndex: 'idCard', 64 key: 'idCard', 65 }, { 66 title: '类型', 67 dataIndex: 'type', 68 key: 'type', 69 }]; 70 71 const showModal = () => { 72 visible.value = true; 73 }; 74 75 const handleOk = () => { 76 axios.post("/member/passenger/save", passenger).then((response) => { 77 let data = response.data; 78 if (data.success) { 79 notification.success({description: "保存成功!"}); 80 visible.value = false; 81 handleQuery({ 82 page: pagination.current, 83 size: pagination.pageSize 84 }); 85 } else { 86 notification.error({description: data.message}); 87 } 88 }); 89 }; 90 91 const handleQuery = (param) => { 92 if (!param) { 93 param = { 94 page: 1, 95 size: pagination.pageSize 96 }; 97 } 98 loading.value = true; 99 axios.get("/member/passenger/query-list", { 100 params: { 101 page: param.page, 102 size: param.size 103 } 104 }).then((response) => { 105 loading.value = false; 106 let data = response.data; 107 if (data.success) { 108 passengers.value = data.content.list; 109 // 设置分页控件的值 110 pagination.current = param.page; 111 pagination.total = data.content.total; 112 } else { 113 notification.error({description: data.message}); 114 } 115 }); 116 }; 117 118 const handleTableChange = (pagination) => { 119 // console.log("看看自带的分页参数都有啥:" + pagination); 120 handleQuery({ 121 page: pagination.current, 122 size: pagination.pageSize 123 }); 124 }; 125 126 onMounted(() => { 127 handleQuery({ 128 page: 1, 129 size: pagination.pageSize 130 }); 131 }); 132 133 return { 134 passenger, 135 visible, 136 showModal, 137 handleOk, 138 passengers, 139 pagination, 140 columns, 141 handleTableChange, 142 handleQuery, 143 loading 144 }; 145 }, 146 }); 147 </script> 148 <style> 149 </style>
PassengerService.java新增
+ PassengerExample passengerExample = new PassengerExample(); passengerExample.setOrderByClause("id desc");
解决Long类型精度丢失问题
不同的语言,虽然都有int long等类型,但他们的精度不太一样,在数据传递时需要特别注意精度丢失。
解决方法:将long传成string
两种方法
第一种是统一注解。把许多小的long也转换了,不是很好。会多很多警告。
1 package com.zihans.train.common.config; 2 3 import com.fasterxml.jackson.databind.ObjectMapper; 4 import com.fasterxml.jackson.databind.module.SimpleModule; 5 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 9 10 /** 11 * 统一注解,解决前后端交互Long类型精度丢失的问题 12 */ 13 @Configuration 14 public class JacksonConfig { 15 @Bean 16 public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { 17 ObjectMapper objectMapper = builder.createXmlMapper(false).build(); 18 SimpleModule simpleModule = new SimpleModule(); 19 simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 20 objectMapper.registerModule(simpleModule); 21 return objectMapper; 22 } 23 }
第二种在返回结果加注解,用雪花算法生成的大long都加。
public class PassengerQueryResp { @JsonSerialize(using= ToStringSerializer.class) private Long id; @JsonSerialize(using= ToStringSerializer.class) private Long memberId;
乘车人编辑接口开发
编辑接口和新增接口相同,只要修改新增接口的内容,因为前端调用同一个接口。
修改PassengerService的save方法。
如果id是空,则保存,否则是编辑。
1 public void save(PassengerSaveReq req) { 2 DateTime now = DateTime.now(); 3 Passenger passenger = BeanUtil.copyProperties(req, Passenger.class); 4 if (ObjectUtil.isNull(passenger.getId())) { 5 passenger.setMemberId(LoginMemberContext.getId()); 6 passenger.setId(SnowUtil.getSnowflakeNextId()); 7 passenger.setCreateTime(now); 8 passenger.setUpdateTime(now); 9 passengerMapper.insert(passenger); 10 } else { 11 passenger.setUpdateTime(now); 12 passengerMapper.updateByPrimaryKey(passenger); 13 } 14 }
测试方法
### POST http://localhost:8000/member/passenger/save Content-Type: application/json token: {{token}} { "id": 1628627322414436352, "memberId": 1624581077500825600, "name": "test1", "idCard": "123321", "type": "1" }
乘车人编辑界面开发
reactive改为ref,reactive需要再封装一层属性才能实现双向绑定
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a @click="onEdit(record)">编辑</a> 17 </a-space> 18 </template> 19 </template> 20 </a-table> 21 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 22 ok-text="确认" cancel-text="取消"> 23 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 24 <a-form-item label="姓名"> 25 <a-input v-model:value="passenger.name" /> 26 </a-form-item> 27 <a-form-item label="身份证"> 28 <a-input v-model:value="passenger.idCard" /> 29 </a-form-item> 30 <a-form-item label="类型"> 31 <a-select v-model:value="passenger.type"> 32 <a-select-option value="1">成人</a-select-option> 33 <a-select-option value="2">儿童</a-select-option> 34 <a-select-option value="3">学生</a-select-option> 35 </a-select> 36 </a-form-item> 37 </a-form> 38 </a-modal> 39 </template> 40 <script> 41 import { defineComponent, ref, onMounted } from 'vue'; 42 import {notification} from "ant-design-vue"; 43 import axios from "axios"; 44 45 export default defineComponent({ 46 setup() { 47 const visible = ref(false); 48 let passenger = ref({ 49 id: undefined, 50 memberId: undefined, 51 name: undefined, 52 idCard: undefined, 53 type: undefined, 54 createTime: undefined, 55 updateTime: undefined, 56 }); 57 const passengers = ref([]); 58 // 分页的三个属性名是固定的 59 const pagination = ref({ 60 total: 0, 61 current: 1, 62 pageSize: 2, 63 }); 64 let loading = ref(false); 65 const columns = [{ 66 title: '姓名', 67 dataIndex: 'name', 68 key: 'name', 69 }, { 70 title: '身份证', 71 dataIndex: 'idCard', 72 key: 'idCard', 73 }, { 74 title: '类型', 75 dataIndex: 'type', 76 key: 'type', 77 }, { 78 title: '操作', 79 dataIndex: 'operation' 80 }]; 81 82 const onAdd = () => { 83 visible.value = true; 84 }; 85 86 const onEdit = (record) => { 87 passenger.value = record; 88 visible.value = true; 89 }; 90 91 const handleOk = () => { 92 axios.post("/member/passenger/save", passenger.value).then((response) => { 93 let data = response.data; 94 if (data.success) { 95 notification.success({description: "保存成功!"}); 96 visible.value = false; 97 handleQuery({ 98 page: pagination.value.current, 99 size: pagination.value.pageSize 100 }); 101 } else { 102 notification.error({description: data.message}); 103 } 104 }); 105 }; 106 107 const handleQuery = (param) => { 108 if (!param) { 109 param = { 110 page: 1, 111 size: pagination.value.pageSize 112 }; 113 } 114 loading.value = true; 115 axios.get("/member/passenger/query-list", { 116 params: { 117 page: param.page, 118 size: param.size 119 } 120 }).then((response) => { 121 loading.value = false; 122 let data = response.data; 123 if (data.success) { 124 passengers.value = data.content.list; 125 // 设置分页控件的值 126 pagination.value.current = param.page; 127 pagination.value.total = data.content.total; 128 } else { 129 notification.error({description: data.message}); 130 } 131 }); 132 }; 133 134 const handleTableChange = (pagination) => { 135 // console.log("看看自带的分页参数都有啥:" + pagination); 136 handleQuery({ 137 page: pagination.current, 138 size: pagination.pageSize 139 }); 140 }; 141 142 onMounted(() => { 143 handleQuery({ 144 page: 1, 145 size: pagination.value.pageSize 146 }); 147 }); 148 149 return { 150 passenger, 151 visible, 152 onAdd, 153 handleOk, 154 passengers, 155 pagination, 156 columns, 157 handleTableChange, 158 handleQuery, 159 loading, 160 onEdit 161 }; 162 }, 163 }); 164 </script> 165 <style> 166 </style>
解决没保存也会同步修改列表的问题
public/js新增tool.js
1 Tool = { 2 /** 3 * 空校验 null或""都返回true 4 */ 5 isEmpty: (obj) => { 6 if ((typeof obj === 'string')) { 7 return !obj || obj.replace(/\s+/g, "") === "" 8 } else { 9 return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0); 10 } 11 }, 12 13 /** 14 * 非空校验 15 */ 16 isNotEmpty: (obj) => { 17 return !Tool.isEmpty(obj); 18 }, 19 20 /** 21 * 对象复制 22 * @param obj 23 */ 24 copy: (obj) => { 25 if (Tool.isNotEmpty(obj)) { 26 return JSON.parse(JSON.stringify(obj)); 27 } 28 }, 29 30 /** 31 * 使用递归将数组转为树形结构 32 * 父ID属性为parent 33 */ 34 array2Tree: (array, parentId) => { 35 if (Tool.isEmpty(array)) { 36 return []; 37 } 38 39 const result = []; 40 for (let i = 0; i < array.length; i++) { 41 const c = array[i]; 42 // console.log(Number(c.parent), Number(parentId)); 43 if (Number(c.parent) === Number(parentId)) { 44 result.push(c); 45 46 // 递归查看当前节点对应的子节点 47 const children = Tool.array2Tree(array, c.id); 48 if (Tool.isNotEmpty(children)) { 49 c.children = children; 50 } 51 } 52 } 53 return result; 54 }, 55 56 /** 57 * 随机生成[len]长度的[radix]进制数 58 * @param len 59 * @param radix 默认62 60 * @returns {string} 61 */ 62 uuid: (len, radix = 62) => { 63 const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 64 const uuid = []; 65 radix = radix || chars.length; 66 67 for (let i = 0; i < len; i++) { 68 uuid[i] = chars[0 | Math.random() * radix]; 69 } 70 71 return uuid.join(''); 72 } 73 };
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a @click="onEdit(record)">编辑</a> 17 </a-space> 18 </template> 19 </template> 20 </a-table> 21 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 22 ok-text="确认" cancel-text="取消"> 23 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 24 <a-form-item label="姓名"> 25 <a-input v-model:value="passenger.name" /> 26 </a-form-item> 27 <a-form-item label="身份证"> 28 <a-input v-model:value="passenger.idCard" /> 29 </a-form-item> 30 <a-form-item label="类型"> 31 <a-select v-model:value="passenger.type"> 32 <a-select-option value="1">成人</a-select-option> 33 <a-select-option value="2">儿童</a-select-option> 34 <a-select-option value="3">学生</a-select-option> 35 </a-select> 36 </a-form-item> 37 </a-form> 38 </a-modal> 39 </template> 40 <script> 41 import { defineComponent, ref, onMounted } from 'vue'; 42 import {notification} from "ant-design-vue"; 43 import axios from "axios"; 44 45 export default defineComponent({ 46 setup() { 47 const visible = ref(false); 48 let passenger = ref({ 49 id: undefined, 50 memberId: undefined, 51 name: undefined, 52 idCard: undefined, 53 type: undefined, 54 createTime: undefined, 55 updateTime: undefined, 56 }); 57 const passengers = ref([]); 58 // 分页的三个属性名是固定的 59 const pagination = ref({ 60 total: 0, 61 current: 1, 62 pageSize: 2, 63 }); 64 let loading = ref(false); 65 const columns = [{ 66 title: '姓名', 67 dataIndex: 'name', 68 key: 'name', 69 }, { 70 title: '身份证', 71 dataIndex: 'idCard', 72 key: 'idCard', 73 }, { 74 title: '类型', 75 dataIndex: 'type', 76 key: 'type', 77 }, { 78 title: '操作', 79 dataIndex: 'operation' 80 }]; 81 82 const onAdd = () => { 83 passenger.value = {}; 84 visible.value = true; 85 }; 86 87 const onEdit = (record) => { 88 passenger.value = window.Tool.copy(record); 89 visible.value = true; 90 }; 91 92 const handleOk = () => { 93 axios.post("/member/passenger/save", passenger.value).then((response) => { 94 let data = response.data; 95 if (data.success) { 96 notification.success({description: "保存成功!"}); 97 visible.value = false; 98 handleQuery({ 99 page: pagination.value.current, 100 size: pagination.value.pageSize 101 }); 102 } else { 103 notification.error({description: data.message}); 104 } 105 }); 106 }; 107 108 const handleQuery = (param) => { 109 if (!param) { 110 param = { 111 page: 1, 112 size: pagination.value.pageSize 113 }; 114 } 115 loading.value = true; 116 axios.get("/member/passenger/query-list", { 117 params: { 118 page: param.page, 119 size: param.size 120 } 121 }).then((response) => { 122 loading.value = false; 123 let data = response.data; 124 if (data.success) { 125 passengers.value = data.content.list; 126 // 设置分页控件的值 127 pagination.value.current = param.page; 128 pagination.value.total = data.content.total; 129 } else { 130 notification.error({description: data.message}); 131 } 132 }); 133 }; 134 135 const handleTableChange = (pagination) => { 136 // console.log("看看自带的分页参数都有啥:" + pagination); 137 handleQuery({ 138 page: pagination.current, 139 size: pagination.pageSize 140 }); 141 }; 142 143 onMounted(() => { 144 handleQuery({ 145 page: 1, 146 size: pagination.value.pageSize 147 }); 148 }); 149 150 return { 151 passenger, 152 visible, 153 onAdd, 154 handleOk, 155 passengers, 156 pagination, 157 columns, 158 handleTableChange, 159 handleQuery, 160 loading, 161 onEdit 162 }; 163 }, 164 }); 165 </script> 166 <style> 167 </style>
index.html新增
<script src="<%= BASE_URL %>js/tool.js"></script>
乘车人删除接口开发
service层,按id删除,id不必要封装成一个类。
public void delete(Long id) { passengerMapper.deleteByPrimaryKey(id); }
controller层,@PathVariable注解为路径映射,将路径上的id映射下来。
@DeleteMapping("/delete/{id}") public CommonResp<Object> delete(@PathVariable Long id) { passengerService.delete(id); return new CommonResp<>(); }
测试。
### DELETE http://localhost:8000/member/passenger/delete/1628627401883914240 Accept: application/json token: {{token}}
乘车人删除界面开发
前端常用设计,对于重要的操作,如删除,需要增加确认,防止误点击,有些特别重要的,还要有二次确认
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a-popconfirm 17 title="删除后不可恢复,确认删除?" 18 @confirm="onDelete(record)" 19 ok-text="确认" cancel-text="取消"> 20 <a style="color: red">删除</a> 21 </a-popconfirm> 22 <a @click="onEdit(record)">编辑</a> 23 </a-space> 24 </template> 25 </template> 26 </a-table> 27 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 28 ok-text="确认" cancel-text="取消"> 29 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 30 <a-form-item label="姓名"> 31 <a-input v-model:value="passenger.name" /> 32 </a-form-item> 33 <a-form-item label="身份证"> 34 <a-input v-model:value="passenger.idCard" /> 35 </a-form-item> 36 <a-form-item label="类型"> 37 <a-select v-model:value="passenger.type"> 38 <a-select-option value="1">成人</a-select-option> 39 <a-select-option value="2">儿童</a-select-option> 40 <a-select-option value="3">学生</a-select-option> 41 </a-select> 42 </a-form-item> 43 </a-form> 44 </a-modal> 45 </template> 46 <script> 47 import { defineComponent, ref, onMounted } from 'vue'; 48 import {notification} from "ant-design-vue"; 49 import axios from "axios"; 50 51 export default defineComponent({ 52 setup() { 53 const visible = ref(false); 54 let passenger = ref({ 55 id: undefined, 56 memberId: undefined, 57 name: undefined, 58 idCard: undefined, 59 type: undefined, 60 createTime: undefined, 61 updateTime: undefined, 62 }); 63 const passengers = ref([]); 64 // 分页的三个属性名是固定的 65 const pagination = ref({ 66 total: 0, 67 current: 1, 68 pageSize: 2, 69 }); 70 let loading = ref(false); 71 const columns = [{ 72 title: '姓名', 73 dataIndex: 'name', 74 key: 'name', 75 }, { 76 title: '身份证', 77 dataIndex: 'idCard', 78 key: 'idCard', 79 }, { 80 title: '类型', 81 dataIndex: 'type', 82 key: 'type', 83 }, { 84 title: '操作', 85 dataIndex: 'operation' 86 }]; 87 88 const onAdd = () => { 89 passenger.value = {}; 90 visible.value = true; 91 }; 92 93 const onEdit = (record) => { 94 passenger.value = window.Tool.copy(record); 95 visible.value = true; 96 }; 97 98 const onDelete = (record) => { 99 axios.delete("/member/passenger/delete/" + record.id).then((response) => { 100 const data = response.data; 101 if (data.success) { 102 notification.success({description: "删除成功!"}); 103 handleQuery({ 104 page: pagination.value.current, 105 size: pagination.value.pageSize, 106 }); 107 } else { 108 notification.error({description: data.message}); 109 } 110 }); 111 }; 112 113 const handleOk = () => { 114 axios.post("/member/passenger/save", passenger.value).then((response) => { 115 let data = response.data; 116 if (data.success) { 117 notification.success({description: "保存成功!"}); 118 visible.value = false; 119 handleQuery({ 120 page: pagination.value.current, 121 size: pagination.value.pageSize 122 }); 123 } else { 124 notification.error({description: data.message}); 125 } 126 }); 127 }; 128 129 const handleQuery = (param) => { 130 if (!param) { 131 param = { 132 page: 1, 133 size: pagination.value.pageSize 134 }; 135 } 136 loading.value = true; 137 axios.get("/member/passenger/query-list", { 138 params: { 139 page: param.page, 140 size: param.size 141 } 142 }).then((response) => { 143 loading.value = false; 144 let data = response.data; 145 if (data.success) { 146 passengers.value = data.content.list; 147 // 设置分页控件的值 148 pagination.value.current = param.page; 149 pagination.value.total = data.content.total; 150 } else { 151 notification.error({description: data.message}); 152 } 153 }); 154 }; 155 156 const handleTableChange = (pagination) => { 157 // console.log("看看自带的分页参数都有啥:" + pagination); 158 handleQuery({ 159 page: pagination.current, 160 size: pagination.pageSize 161 }); 162 }; 163 164 onMounted(() => { 165 handleQuery({ 166 page: 1, 167 size: pagination.value.pageSize 168 }); 169 }); 170 171 return { 172 passenger, 173 visible, 174 onAdd, 175 handleOk, 176 passengers, 177 pagination, 178 columns, 179 handleTableChange, 180 handleQuery, 181 loading, 182 onEdit, 183 onDelete 184 }; 185 }, 186 }); 187 </script> 188 <style> 189 </style>
前端枚举的解决方案
将下拉框的值提取成常量数组
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a-popconfirm 17 title="删除后不可恢复,确认删除?" 18 @confirm="onDelete(record)" 19 ok-text="确认" cancel-text="取消"> 20 <a style="color: red">删除</a> 21 </a-popconfirm> 22 <a @click="onEdit(record)">编辑</a> 23 </a-space> 24 </template> 25 </template> 26 </a-table> 27 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 28 ok-text="确认" cancel-text="取消"> 29 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 30 <a-form-item label="姓名"> 31 <a-input v-model:value="passenger.name" /> 32 </a-form-item> 33 <a-form-item label="身份证"> 34 <a-input v-model:value="passenger.idCard" /> 35 </a-form-item> 36 <a-form-item label="类型"> 37 <a-select v-model:value="passenger.type"> 38 <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.key" :value="item.key">{{item.value}}</a-select-option> 39 </a-select> 40 </a-form-item> 41 </a-form> 42 </a-modal> 43 </template> 44 <script> 45 import { defineComponent, ref, onMounted } from 'vue'; 46 import {notification} from "ant-design-vue"; 47 import axios from "axios"; 48 49 export default defineComponent({ 50 setup() { 51 const PASSENGER_TYPE_ARRAY = [{key: "1", value: "成人1"}, {key: "2", value: "儿童"}, {key: "3", value: "学生"}]; 52 const visible = ref(false); 53 let passenger = ref({ 54 id: undefined, 55 memberId: undefined, 56 name: undefined, 57 idCard: undefined, 58 type: undefined, 59 createTime: undefined, 60 updateTime: undefined, 61 }); 62 const passengers = ref([]); 63 // 分页的三个属性名是固定的 64 const pagination = ref({ 65 total: 0, 66 current: 1, 67 pageSize: 2, 68 }); 69 let loading = ref(false); 70 const columns = [{ 71 title: '姓名', 72 dataIndex: 'name', 73 key: 'name', 74 }, { 75 title: '身份证', 76 dataIndex: 'idCard', 77 key: 'idCard', 78 }, { 79 title: '类型', 80 dataIndex: 'type', 81 key: 'type', 82 }, { 83 title: '操作', 84 dataIndex: 'operation' 85 }]; 86 87 const onAdd = () => { 88 passenger.value = {}; 89 visible.value = true; 90 }; 91 92 const onEdit = (record) => { 93 passenger.value = window.Tool.copy(record); 94 visible.value = true; 95 }; 96 97 const onDelete = (record) => { 98 axios.delete("/member/passenger/delete/" + record.id).then((response) => { 99 const data = response.data; 100 if (data.success) { 101 notification.success({description: "删除成功!"}); 102 handleQuery({ 103 page: pagination.value.current, 104 size: pagination.value.pageSize, 105 }); 106 } else { 107 notification.error({description: data.message}); 108 } 109 }); 110 }; 111 112 const handleOk = () => { 113 axios.post("/member/passenger/save", passenger.value).then((response) => { 114 let data = response.data; 115 if (data.success) { 116 notification.success({description: "保存成功!"}); 117 visible.value = false; 118 handleQuery({ 119 page: pagination.value.current, 120 size: pagination.value.pageSize 121 }); 122 } else { 123 notification.error({description: data.message}); 124 } 125 }); 126 }; 127 128 const handleQuery = (param) => { 129 if (!param) { 130 param = { 131 page: 1, 132 size: pagination.value.pageSize 133 }; 134 } 135 loading.value = true; 136 axios.get("/member/passenger/query-list", { 137 params: { 138 page: param.page, 139 size: param.size 140 } 141 }).then((response) => { 142 loading.value = false; 143 let data = response.data; 144 if (data.success) { 145 passengers.value = data.content.list; 146 // 设置分页控件的值 147 pagination.value.current = param.page; 148 pagination.value.total = data.content.total; 149 } else { 150 notification.error({description: data.message}); 151 } 152 }); 153 }; 154 155 const handleTableChange = (pagination) => { 156 // console.log("看看自带的分页参数都有啥:" + pagination); 157 handleQuery({ 158 page: pagination.current, 159 size: pagination.pageSize 160 }); 161 }; 162 163 onMounted(() => { 164 handleQuery({ 165 page: 1, 166 size: pagination.value.pageSize 167 }); 168 }); 169 170 return { 171 PASSENGER_TYPE_ARRAY, 172 passenger, 173 visible, 174 onAdd, 175 handleOk, 176 passengers, 177 pagination, 178 columns, 179 handleTableChange, 180 handleQuery, 181 loading, 182 onEdit, 183 onDelete 184 }; 185 }, 186 }); 187 </script> 188 <style> 189 </style>
解决表格中枚举字段的显示
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a-popconfirm 17 title="删除后不可恢复,确认删除?" 18 @confirm="onDelete(record)" 19 ok-text="确认" cancel-text="取消"> 20 <a style="color: red">删除</a> 21 </a-popconfirm> 22 <a @click="onEdit(record)">编辑</a> 23 </a-space> 24 </template> 25 <template v-else-if="column.dataIndex === 'type'"> 26 <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.key"> 27 <span v-if="item.key === record.type"> 28 {{item.value}} 29 </span> 30 </span> 31 </template> 32 </template> 33 </a-table> 34 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 35 ok-text="确认" cancel-text="取消"> 36 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 37 <a-form-item label="姓名"> 38 <a-input v-model:value="passenger.name" /> 39 </a-form-item> 40 <a-form-item label="身份证"> 41 <a-input v-model:value="passenger.idCard" /> 42 </a-form-item> 43 <a-form-item label="类型"> 44 <a-select v-model:value="passenger.type"> 45 <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.key" :value="item.key">{{item.value}}</a-select-option> 46 </a-select> 47 </a-form-item> 48 </a-form> 49 </a-modal> 50 </template> 51 <script> 52 import { defineComponent, ref, onMounted } from 'vue'; 53 import {notification} from "ant-design-vue"; 54 import axios from "axios"; 55 56 export default defineComponent({ 57 setup() { 58 const PASSENGER_TYPE_ARRAY = [{key: "1", value: "成人"}, {key: "2", value: "儿童"}, {key: "3", value: "学生"}]; 59 const visible = ref(false); 60 let passenger = ref({ 61 id: undefined, 62 memberId: undefined, 63 name: undefined, 64 idCard: undefined, 65 type: undefined, 66 createTime: undefined, 67 updateTime: undefined, 68 }); 69 const passengers = ref([]); 70 // 分页的三个属性名是固定的 71 const pagination = ref({ 72 total: 0, 73 current: 1, 74 pageSize: 2, 75 }); 76 let loading = ref(false); 77 const columns = [{ 78 title: '姓名', 79 dataIndex: 'name', 80 key: 'name', 81 }, { 82 title: '身份证', 83 dataIndex: 'idCard', 84 key: 'idCard', 85 }, { 86 title: '类型', 87 dataIndex: 'type', 88 key: 'type', 89 }, { 90 title: '操作', 91 dataIndex: 'operation' 92 }]; 93 94 const onAdd = () => { 95 passenger.value = {}; 96 visible.value = true; 97 }; 98 99 const onEdit = (record) => { 100 passenger.value = window.Tool.copy(record); 101 visible.value = true; 102 }; 103 104 const onDelete = (record) => { 105 axios.delete("/member/passenger/delete/" + record.id).then((response) => { 106 const data = response.data; 107 if (data.success) { 108 notification.success({description: "删除成功!"}); 109 handleQuery({ 110 page: pagination.value.current, 111 size: pagination.value.pageSize, 112 }); 113 } else { 114 notification.error({description: data.message}); 115 } 116 }); 117 }; 118 119 const handleOk = () => { 120 axios.post("/member/passenger/save", passenger.value).then((response) => { 121 let data = response.data; 122 if (data.success) { 123 notification.success({description: "保存成功!"}); 124 visible.value = false; 125 handleQuery({ 126 page: pagination.value.current, 127 size: pagination.value.pageSize 128 }); 129 } else { 130 notification.error({description: data.message}); 131 } 132 }); 133 }; 134 135 const handleQuery = (param) => { 136 if (!param) { 137 param = { 138 page: 1, 139 size: pagination.value.pageSize 140 }; 141 } 142 loading.value = true; 143 axios.get("/member/passenger/query-list", { 144 params: { 145 page: param.page, 146 size: param.size 147 } 148 }).then((response) => { 149 loading.value = false; 150 let data = response.data; 151 if (data.success) { 152 passengers.value = data.content.list; 153 // 设置分页控件的值 154 pagination.value.current = param.page; 155 pagination.value.total = data.content.total; 156 } else { 157 notification.error({description: data.message}); 158 } 159 }); 160 }; 161 162 const handleTableChange = (pagination) => { 163 // console.log("看看自带的分页参数都有啥:" + pagination); 164 handleQuery({ 165 page: pagination.current, 166 size: pagination.pageSize 167 }); 168 }; 169 170 onMounted(() => { 171 handleQuery({ 172 page: 1, 173 size: pagination.value.pageSize 174 }); 175 }); 176 177 return { 178 PASSENGER_TYPE_ARRAY, 179 passenger, 180 visible, 181 onAdd, 182 handleOk, 183 passengers, 184 pagination, 185 columns, 186 handleTableChange, 187 handleQuery, 188 loading, 189 onEdit, 190 onDelete 191 }; 192 }, 193 }); 194 </script> 195 <style> 196 </style>
将枚举数组放入单独的文件中
1 PASSENGER_TYPE_ARRAY = [{key: "1", value: "成人"}, {key: "2", value: "儿童"}, {key: "3", value: "学生"}];
1 <template> 2 <p> 3 <a-space> 4 <a-button type="primary" @click="handleQuery()">刷新</a-button> 5 <a-button type="primary" @click="onAdd">新增</a-button> 6 </a-space> 7 </p> 8 <a-table :dataSource="passengers" 9 :columns="columns" 10 :pagination="pagination" 11 @change="handleTableChange" 12 :loading="loading"> 13 <template #bodyCell="{ column, record }"> 14 <template v-if="column.dataIndex === 'operation'"> 15 <a-space> 16 <a-popconfirm 17 title="删除后不可恢复,确认删除?" 18 @confirm="onDelete(record)" 19 ok-text="确认" cancel-text="取消"> 20 <a style="color: red">删除</a> 21 </a-popconfirm> 22 <a @click="onEdit(record)">编辑</a> 23 </a-space> 24 </template> 25 <template v-else-if="column.dataIndex === 'type'"> 26 <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.key"> 27 <span v-if="item.key === record.type"> 28 {{item.value}} 29 </span> 30 </span> 31 </template> 32 </template> 33 </a-table> 34 <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk" 35 ok-text="确认" cancel-text="取消"> 36 <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> 37 <a-form-item label="姓名"> 38 <a-input v-model:value="passenger.name" /> 39 </a-form-item> 40 <a-form-item label="身份证"> 41 <a-input v-model:value="passenger.idCard" /> 42 </a-form-item> 43 <a-form-item label="类型"> 44 <a-select v-model:value="passenger.type"> 45 <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.key" :value="item.key">{{item.value}}</a-select-option> 46 </a-select> 47 </a-form-item> 48 </a-form> 49 </a-modal> 50 </template> 51 <script> 52 import { defineComponent, ref, onMounted } from 'vue'; 53 import {notification} from "ant-design-vue"; 54 import axios from "axios"; 55 56 export default defineComponent({ 57 setup() { 58 const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY; 59 const visible = ref(false); 60 let passenger = ref({ 61 id: undefined, 62 memberId: undefined, 63 name: undefined, 64 idCard: undefined, 65 type: undefined, 66 createTime: undefined, 67 updateTime: undefined, 68 }); 69 const passengers = ref([]); 70 // 分页的三个属性名是固定的 71 const pagination = ref({ 72 total: 0, 73 current: 1, 74 pageSize: 2, 75 }); 76 let loading = ref(false); 77 const columns = [{ 78 title: '姓名', 79 dataIndex: 'name', 80 key: 'name', 81 }, { 82 title: '身份证', 83 dataIndex: 'idCard', 84 key: 'idCard', 85 }, { 86 title: '类型', 87 dataIndex: 'type', 88 key: 'type', 89 }, { 90 title: '操作', 91 dataIndex: 'operation' 92 }]; 93 94 const onAdd = () => { 95 passenger.value = {}; 96 visible.value = true; 97 }; 98 99 const onEdit = (record) => { 100 passenger.value = window.Tool.copy(record); 101 visible.value = true; 102 }; 103 104 const onDelete = (record) => { 105 axios.delete("/member/passenger/delete/" + record.id).then((response) => { 106 const data = response.data; 107 if (data.success) { 108 notification.success({description: "删除成功!"}); 109 handleQuery({ 110 page: pagination.value.current, 111 size: pagination.value.pageSize, 112 }); 113 } else { 114 notification.error({description: data.message}); 115 } 116 }); 117 }; 118 119 const handleOk = () => { 120 axios.post("/member/passenger/save", passenger.value).then((response) => { 121 let data = response.data; 122 if (data.success) { 123 notification.success({description: "保存成功!"}); 124 visible.value = false; 125 handleQuery({ 126 page: pagination.value.current, 127 size: pagination.value.pageSize 128 }); 129 } else { 130 notification.error({description: data.message}); 131 } 132 }); 133 }; 134 135 const handleQuery = (param) => { 136 if (!param) { 137 param = { 138 page: 1, 139 size: pagination.value.pageSize 140 }; 141 } 142 loading.value = true; 143 axios.get("/member/passenger/query-list", { 144 params: { 145 page: param.page, 146 size: param.size 147 } 148 }).then((response) => { 149 loading.value = false; 150 let data = response.data; 151 if (data.success) { 152 passengers.value = data.content.list; 153 // 设置分页控件的值 154 pagination.value.current = param.page; 155 pagination.value.total = data.content.total; 156 } else { 157 notification.error({description: data.message}); 158 } 159 }); 160 }; 161 162 const handleTableChange = (pagination) => { 163 // console.log("看看自带的分页参数都有啥:" + pagination); 164 handleQuery({ 165 page: pagination.current, 166 size: pagination.pageSize 167 }); 168 }; 169 170 onMounted(() => { 171 handleQuery({ 172 page: 1, 173 size: pagination.value.pageSize 174 }); 175 }); 176 177 return { 178 PASSENGER_TYPE_ARRAY, 179 passenger, 180 visible, 181 onAdd, 182 handleOk, 183 passengers, 184 pagination, 185 columns, 186 handleTableChange, 187 handleQuery, 188 loading, 189 onEdit, 190 onDelete 191 }; 192 }, 193 }); 194 </script> 195 <style> 196 </style>
main.js import
import './assets/js/enums';
package.json添加规则
"no-undef": 0