文章目录
- 八、加入购物车
- 路由跳转之前发请求
- api—请求接口
- 将数据存储到服务器里
- 判断加入成功 或 失败
- 成功后进行路由跳转
- 开始进行路由跳转,并进行路由传参
- 开始渲染页面
- 购物车静态组件与修改
- 九、完成ShopCart购物车模块业务
- 向服务器发起请求,获取购物车数据
- 向服务器发请求
- 操作vuex 将数据存储到仓库中
- 组件获取数据展示数据
- UUID临时游客身份
- 动态展示购物车
- 修改购物车产品的数量
- 删除购物车产品
- 修改产品的勾选状态
- 删除选中全部产品的操作
- 修改全部商品的勾选状态
- 十、完成登录与注册页面
- 登录与注册的静态组件完成
- 注册业务的完成
- 向手机发送验证码
- 登录和确认密码
- 点击 完成注册
- 表单验证
- 登录业务的完成
- 十一、登录过后
- 首页用户信息的展示
- Header组件显示用户名与退出登录
- 持久化存储token
- 退出登录
- 导航守卫
- 用户登录后的页面
- 用户未登录后的页面
- 十二、完成trade交易业务
- 完成交易页面的静态组件
- 获取交易页数据
- 动态渲染数据
- 渲染用户地址信息
- 渲染商品清单信息
- 收集买家的留言信息
- 渲染底部总信息
- 十三、完成提交订单业务
- 完成支付页面的静态组件
- 提交订单发起请求
- 组件存储数据
- 在支付页面展示数据
- 展示订单号
- 展示应付金额
- 点击立即支付-elementUI
- 微信支付业务
- 二维码生成 qrcode
- 支付后
- 支付成功
- 没有支付
- 完整代码
- 十四、个人中心
- 完成静态组件
- 个人中心二级路由搭建
- 我的订单
- 发请求存储数据
- 动态渲染数据
- 分页器
- 十五、鉴权判断
- 未登录的导航守卫判断
- 路由独享守卫
- 组件内守卫
- 十六、图片懒加载
- 十七、路由懒加载
- 十八、项目上线
- 打包上线
- 购买云服务器
- 安全组设置
- xshell链接服务器与linux指令
- nginx反向代理
八、加入购物车
点击 加入购物车 的时候,先发请求,把数据存储到服务器,然后再进行路由跳转
- 当点击 加入购物车 的时候,会跳转到 加入购物车成功的页面,到时候需要配置路由,进行路由跳转
路由跳转之前发请求
api—请求接口
// src/api/index.js// 将产品添加到购物车中(或者 更新某一个产品个数)// /api/cart/addToCart/{ skuId }/{ skuNum } POST 带参数export const reqAddOrUpdateShopCart = (skuId,skuNum)=>{ return requests({url:`/cart/addToCart/${skuId}/${skuNum}`,method:'POST'})}
将数据存储到服务器里
派发actions,发请求
// src / pages / detail /index.vue<div class="add"> <a @click="addShopcar">加入购物车</a></div>
// 加入购物车的回调函数addShopcar() { // 1. 发请求--将产品加入到数据库(通知服务器) // 服务器存储成功----进行路由跳转 // 失败,给用户提示 this.$store.dispatch( "detail/addOrUpdateShopCart",{skuId:this.$route.params.skuId,skuNum:this.skuNum});},
加入购物车以后(发请求),前台将参数带给服务器
服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功
⚠ !!!因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据
将参数带给服务器
const actions = { // 将产品添加到购物车中 async addOrUpdateShopCart({commit},{skuId,skuNum}){ // 加入购物车返回的结果 let result = await reqAddOrUpdateShopCart(skuId,skuNum); console.log('购物车',result); // 加入购物车以后(发请求),前台将参数带给服务器 // 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功 // 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据 },};
判断加入成功 或 失败
async
:只要有async返回的就是Promise,Promise返回的不是成功就是失败
发请求–将产品加入到数据库(通知服务器)
服务器存储成功—-进行路由跳转
服务器存储失败——给用户提示
// src/pages/detail/index.vue// 加入购物车的回调函数 async addShopcar() { // 1. 发请求--将产品加入到数据库(通知服务器) // 服务器存储成功----进行路由跳转 // 失败------给用户提示 try { await this.$store.dispatch("detail/addOrUpdateShopCart", {skuId: this.$route.params.skuId,skuNum: this.skuNum,}); // 成功了进行路由跳转 ..... } catch (error) { alert(error.message); } },
this.$store.dispatch("detail/addOrUpdateShopCart", {skuId: this.$route.params.skuId,skuNum: this.skuNum,});
表示调用了addOrUpdateShopCart
这个函数,调用这个函数,这个函数有async,说明这个函数的返回值是Promise函数await返回的是promise成功的值,但是我们要有成功做什么以及失败做什么…如果await的promise失败了,就会抛出异常,我们需要通过try…catch捕获处理
所以在detail.vue中,给 加入购物车的回调函数加上 async
// src/store/detail/index.js// 将产品添加到购物车中 async addOrUpdateShopCart({ commit }, { skuId, skuNum }) { // 加入购物车返回的结果 // 加入购物车以后(发请求),前台将参数带给服务器 // 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功 // 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据 let result = await reqAddOrUpdateShopCart(skuId, skuNum); // 当前的这个函数,如果执行,返回promise // 代表服务器加入购物车成功 if(result.code==200){ return 'ok'; // 返回的只要是非空字符串就是成功 }else{ // 代表加入购物车失败 return Promise.reject(new Error('false')); } },
成功后进行路由跳转
注意:路由不是组件!!!??????❓
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。使用组件的步骤:创建-注册-引用-使用
创建路由,写到专门放路由的文件夹下
编写路由配置项:src/router/index.js
// 配置路由的地方import Vue from 'vue';import VueRouter from 'vue-router';// 使用插件Vue.use(VueRouter);// 引入路由组件import AddCartSuccess from '../pages/AddCartSuccess'// 先把VueRouter原型对象的push,先保存一份const originalPush = VueRouter.prototype.pushconst originalReplace = VueRouter.prototype.replace// 重写push | replace// 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数)VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err)}VueRouter.prototype.replace = function replace(location) { return originalReplace.call(this, location).catch(err => err)}// 配置路由export default new VueRouter({ // 配置路由 routes: [ ... { name: 'addcartsuccess', path: '/addcartsuccess', component: AddCartSuccess, meta: { showFooter: true } }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 }})
开始进行路由跳转,并进行路由传参
路由传参的话数据过多,在skuInfo里面,是个对象,以及还要带参数skuNum,地址栏会有点乱
所以这里我们只带skuNum参数传过去,其余复杂数据用会话存储—不持久化,会话结束数据消失
浏览器存储功能:HTML5中新增的,分为本地存储和会话存储
本地存储:持久化的,具有上限—–5M
会话存储:不是持久化的(浏览器关闭等),
// 加入购物车的回调函数 async addShopcar() { // 1. 发请求--将产品加入到数据库(通知服务器) // 服务器存储成功----进行路由跳转 // 失败------给用户提示 try { await this.$store.dispatch("detail/addOrUpdateShopCart", { skuId: this.$route.params.skuId, skuNum: this.skuNum, }); // 成功了进行路由跳转,并将产品的信息带给下一级的路由组件 // 会话存储 | 本地存储,一般存储的是字符串,所以将对象转换为字符串 sessionStorage.setItem('SKUINFO',JSON.stringify(this.skuInfo)) this.$router.push({ name: "addcartsuccess", query:{skuNum:this.skuNum}, }); } catch (error) { alert(error.message); } },
本地存储 里面只能存储字符串格式 ,因此需要把对象转换为字符串
JSON.stringify()
获取本地存储数据,需要把里面的字符串转换为对象格式
JSON.parse()
我们才能使用里面的数据。
获取本地存储数据
开始渲染页面
<div class="left-good"> <div class="left-pic"> <img :src="skuInfo.skuDefaultImg" /> </div> <div class="right-info"> <p class="title">{{ skuInfo.skuName }}</p> <p class="attr"> <span v-for="attrName in skuInfo.skuSaleAttrValueList" :key="attrName.id">{{ attrName.saleAttrName }} {{attrName.saleAttrValueName}} </span> <span>数量:{{$route.query.skuNum}}</span> </p> </div> </div> <div class="right-gocart">
购物车静态组件与修改
跳转到detail商品详情页,携带参数
<div class="right-gocart"> <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link> <a href="javascript:">去购物车结算 > </a> </div>
跳转到购物车页面ShopCart
九、完成ShopCart购物车模块业务
引入和配置路由
// 配置路由的地方import Vue from 'vue';import VueRouter from 'vue-router';// 使用插件Vue.use(VueRouter);// 引入路由组件import ShopCart from '../pages/ShopCart'// 先把VueRouter原型对象的push,先保存一份const originalPush = VueRouter.prototype.pushconst originalReplace = VueRouter.prototype.replace// 重写push | replace// 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数)VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err)}VueRouter.prototype.replace = function replace(location) { return originalReplace.call(this, location).catch(err => err)}// 配置路由export default new VueRouter({ // 配置路由 routes: [ { name: 'shopcart', path: '/shopcart', component: ShopCart, meta: { showFooter: true }, }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 }})
路由跳转
<div class="right-gocart"> <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link> <router-link to="/shopcart">去购物车结算 > </router-link> </div>
向服务器发起请求,获取购物车数据
向服务器发请求
/api/cart/cartList GET 无参数
// src/api/index.js// 获取购物车列表数据 /api/cart/cartList GET 无参数export const reqCartList = ()=>{ return requests({url:'/cart/cartList',method:'GET'})}
操作vuex 将数据存储到仓库中
- 新建一个仓库,用来存储购物车的数据
// src/store/shopcart/index.jsimport { reqCartList } from '@/api/index'const state = {};const mutations = {};const actions = {};const getters = {};export default { namespaced: true, state, mutations, actions, getters}
- 在大仓库中引用小仓库
// src/store/index.jsimport Vue from 'vue';import Vuex from 'vuex';// 需要使用插件Vue.use(Vuex);// 引入小仓库import home from './home'import search from './search'import detail from './detail'import shopcart from './shopcart';// 对外暴露Store类的一个实例export default new Vuex.Store({ // 实现vuex仓库模块式开发存储数据 modules:{ home, search, detail, shopcart }})
vuex三连环
const actions = { // 获取购物车列表的数据 async getCartList({ commit }) { let result = await reqCartList(); console.log('购物车列表',result); },};
export default { name: 'ShopCart', mounted(){ // 获取服务器数据 this.getData(); }, methods:{ // 获取个人购物车数据 getData(){ this.$store.dispatch('shopcart/getCartList'); } } }
注意:发请求的时候,获取不到你购物车里的数据,因为这里不知道购物车获取谁的数据,需要给用户加一个身份!所以需要 UUID临时游客身份
开始真正的vuex三连环!!
这个是shopcart仓库里面的数据,有点复杂,所以我们要简化
import { reqCartList } from '@/api/index'const state = { cartList:[],};const mutations = { GETCARTLIST(state, cartList) { state.cartList = cartList; }};const actions = { // 获取购物车列表的数据 async getCartList({ commit }) { let result = await reqCartList(); if (result.code == 200) { commit('GETCARTLIST', result.data); // result.data 是个数组 } },};const getters = { cartList(state){ // state.cartList[0] 如果没有返回,至少是个数组 return state.cartList[0]|| []; },};export default { namespaced: true, state, mutations, actions, getters}
现在仓库里面有了数据
组件获取数据展示数据
...mapGetters("shopcart", ["cartList"])
, 组件开始获取数据
遍历用every
<script>import { mapGetters } from "vuex";export default { name: "ShopCart", mounted() { // 获取服务器数据 this.getData(); }, methods: { // 获取个人购物车数据 getData() { this.$store.dispatch("shopcart/getCartList"); }, }, computed: { ...mapGetters("shopcart", ["cartList"]),// 并不是真正的购物车列表数据 // 真正的购物车列表数据 cartInfoList() { // 至少是个空数组 return this.cartList.cartInfoList || []; }, // 计算购买产品的总价 totalPrice() { let sum = 0; this.cartInfoList.forEach((item) => { // item是购物车列表的每一行数据 sum += item.skuNum * item.cartPrice; }); return sum; }, // 判断底部的复选框是否勾选 isAllChecked(){ // 遍历每一个产品的isChecked,只要全部元素isChecked属性都为1,返回为真 return this.cartInfoList.every(item=>item.isChecked==1) }, },};</script>
开始渲染数据
UUID临时游客身份
在点击 加入购物车 的时候,告诉服务器你是谁
utils :放一些常用的功能模块,比如正则,uuid
将游客身份用会话存储(sessionStorage)保存,放到detail仓库里
uuid是一个随机的字符串,且每次执行不能发生变化,且持久存储,
所以每次调用getUUID函数的时候,先从本地存储获取uuid,看会话存储是否有,
如果有的话就返回会话存储里面的uuid,
如果没有的话,就生成uuid
// src/utils/uuid_token.jsimport {v4 as uuidv4} from 'uuid'// 要生成一个随机的字符串,且每次执行不能发生变化,游客身份持久存储export const getUUID = ()=>{ // 先从会话存储获取uuid,看一下本地存储是否有 let uuid_token = sessionStorage.getItem('UUIDTOKEN'); // 如果没有,我就生成UUID if(!uuid_token){ uuid_token=uuidv4(); // 会话存储 存储一次 sessionStorage.setItem('UUIDTOKEN',uuid_token); } // 切记要有返回值! return uuid_token;}
在detail仓库里面存储uuid
// src/store/detail/index.js// 封装游客身份模块uuid---生成一个随机的字符串(不能再变了)import {getUUID} from '@/utils/uuid_token'const state = { goodsInfo: {}, // 看api文档,result.data返回的是一个对象 // 游客临时身份 uuid_token:getUUID(),};
现在游客身份在仓库里,我们要把数据带给服务器,可以利用请求头把数据带给服务器
找到请求拦截器,在请求拦截器捞到仓库里的数据
// src/api/request.js// 在当前模块引入store仓库import store from '@/store'.....// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情requests.interceptors.request.use((config) => { // config:配置对象,对象里面有一个属性很重要:headers请求头 if (store.state.detail.uuid_token) { // 给请求头添加一个字段(userTempId):和后台老师商量好! config.headers.userTempId = store.state.detail.uuid_token } // 进度条开始动 nprogress.start(); return config;})
动态展示购物车
修改购物车产品的数量
点击 + 或 – 或 修改input框里的数字,input框里发生变化
问:这个时候向服务器发请求吗?
发请求,如果不发请求的话,服务器里的数据还是原来的,那么该页面的关于数量的数据和原来一样,
———–所以我们每当修改的时候,就要发请求给服务器,然后再从服务器捞到数据,进行渲染页面
这个之前写过api(在将产品添加到购物车那里,detail详情页,也有仓库了)所以我们可以把数据存到detail仓库,就现在直接派发action就行了,然后再重新捞数据渲染
这里的skuNum是 现在状态的数字 与 起始状态的数字 的差值。比如:现在商品数量是5,我们要买12,这个skuNum是12-5 = 7
找到产品数量的结构位置
<li class="cart-list-con5"> <a href="javascript:void(0)" class="mins">-</a> <input autocomplete="off" type="text" minnum="1" class="itxt" :value="cart.skuNum" /> <a href="javascript:void(0)" class="plus">+</a> </li>
这三个节点 要派发同一个action,也就是说这三个节点调用同一个回调函数
问:但是如何判断这三个节点?
通过传参。三个不同的参数,用形参接收,来区分这三个节点
要传三个参数
第一个参数type 是用来区分它们三个节点
第二个参数是disNum,他们的变化量,+号是1,-号是-1,input框暂定不是变化量,是修改后的值
第三个参数是cart 当前他们点击的产品的信息cart,然后得知他们的id,因为要发请求需要skuID
<li class="cart-list-con5"> <a href="javascript:void(0)" class="mins" @click="handler('mins',-1,cart)">-</a> <input autocomplete="off" type="text" minnum="1" class="itxt" :value="cart.skuNum" @change="handler('change',$event.target.value*1,cart)" /> <a href="javascript:void(0)" class="plus" @click="handler('plus',1,cart)">+</a> </li>
先写 + 号 和 – 号
加号:直接带给服务器变化的量
减号:判断产品的当前数量是否大于1,大于1才能减,传递给服务器-1
然后就是派发action发请求,发给服务器后再 捞数据 渲染页面
// 购物车里 修改某一个产品的个数 handler(type, disNum, cart) { //disNum 代表 现在状态的数字 与 起始状态的数字 的差值 // 目前disNum 形参: + 号变化量(1),- 号 变化量 (-1),input 最终的量(并不是变化量) // 向服务器发请求,修改数量 switch (type) { // 加号 case "plus": { // 带给服务器变化的量 disNum = 1; break; } // 减号 case "mins": { // 判断产品的数量是否大于1,大于1才能减,传递给服务器-1 if (cart.skuNum > 1) { disNum = -1; } else { // 产品的个数小于等于1,传递给服务器的个数为 0 disNum = 0; } break; } } // 派发action 发请求 try { // 代表的是修改购物车中产品数量修改成功 await this.$store.dispatch("detail/addOrUpdateShopCart", { skuId: cart.skuId, skuNum: disNum, }); // 再一次获取服务器的最新数据进行展示 this.getData(); } catch (error) { // 修改失败 alert(error.message) } },
解决 input 框里面输入数字
如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0
如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum
// input框 case "change": { // 如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0 if (isNaN(disNum) || disNum < 1) { disNum = 0; } else { // 如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum disNum = parseInt(disNum) - cart.skuNum; } break; }
对修改购物车产品的数量 进行节流操作
当你刚到购物车页面的时候,快速点击 – 号,商品的数量会变成负数、
这是因为 用户操作太频繁,请求服务器跟不上————–所以我们用到【节流】,限制用户的操作
// 原来写法async handler(type, disNum, cart) { //disNum 代表 现在状态的数字 与 起始状态的数字 的差值 // 目前disNum 形参: + 号变化量(1),- 号 变化量 (-1),input 最终的量(并不是变化量) // 向服务器发请求,修改数量 switch (type) { // 加号 case "plus": { // 带给服务器变化的量 disNum = 1; break; } // 减号 case "mins": { // 判断产品的数量是否大于1,大于1才能减,传递给服务器-1 if (cart.skuNum > 1) { disNum = -1; } else { // 产品的个数小于等于1,传递给服务器的个数为 0 disNum = 0; } break; } // input框 case "change": { // 如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0 if (isNaN(disNum) || disNum < 1) { disNum = 0; } else { // 如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum disNum = parseInt(disNum) - cart.skuNum; } break; } } // 派发action 发请求 try { // 代表的是修改购物车中产品数量修改成功 await this.$store.dispatch("detail/addOrUpdateShopCart", { skuId: cart.skuId, skuNum: disNum, }); // 再一次获取服务器的最新数据进行展示 this.getData(); } catch (error) { // 修改失败 alert(error.message); } },
引入节流
// 最好的引入方式:按需加载import throttle from "lodash/throttle";
整理代码
// 购物车里 修改某一个产品的个数,使用【节流】 handler: throttle(async function (type, disNum, cart) {// 原来代码 }, 500),
删除购物车产品
写api接口
// src/api/index.js// 删除购物车商品数据 /api/cart/deleteCart/{skuId} DELETE 带参数skuIdexport const reqDeleteCartById=(skuId)=>{ return requests({url:`/cart/deleteCart/${skuId}`,method:'DELETE'})}
写仓库vuex,去购物车的仓库里写
这里就不用拿数据了,因为是删除数据!!!
const actions = { // 删除购物车商品的数据 async deleteCartListBySkuId({ commit }, skuId) { let result = await reqDeleteCartById(skuId); if (result.code == 200) { return 'ok' } else { return Promise.reject(new Error('删除购物车商品失败')) } }};
点击 删除 的时候 派发action,需要传参,当前商品的skuId
<li class="cart-list-con7"> <a class="sindelet" @click="deleteCartById(cart)">删除</a> <br /> <a href="#none">移到收藏</a> </li>
删除购物车商品的操作
如果删数据成功,则发请求显示新的数据,如果失败弹窗失败信息
// 删除购物车商品的操作 async deleteCartById(cart) { // 如果删数据成功,则发请求显示新的数据,如果失败弹窗失败信息 try { await this.$store.dispatch( "shopcart/deleteCartListBySkuId", cart.skuId ); // 再次发请求显示新的数据 this.getData(); } catch (error) { alert(error.message); } },
修改产品的勾选状态
每次修改完状态也是要向服务器发请求的
写api接口
// 切换商品的选中状态 /api/cart/checkCart/{skuId}/{isChecked} GET请求 带参数export const reqUpdateCheckedById = (skuId, isChecked) => { return requests({ url: `/cart/checkCart/${skuId}/${isChecked}`, method: 'GET' })}
vuex—-派发actions 与 仓库捞到数据 (不返回数据)
多个参数的话,多个参数用
{}
对象包裹!!注意!不能只传个参数 cart ,因为isChecked不变的,它获取的是后台的数据,一直是1
可以用
event.target.checked
获取当前元素的checked属性// src/pages/ShopCart/index.vue <li class="cart-list-con1"> <input type="checkbox" name="chk_list" :checked="cart.isChecked == 1" @change="updateChecked(cart, $event)" /> </li>.....<script> // 修改购物车是否勾选状态 async updateChecked(cart, event) { try { let checked = event.target.checked " />
-
我们现在解决了 当上面的勾选框全都勾选上了,下面的全选按钮全都勾选上
-
但是没有解决 当下面全选按钮勾选上的时候,上面的也都要勾选上
1. 修改产品的勾选状态 这个请求 一次只能勾选一个,而我们是要全部产品勾选上,所以我们可以多发几次请求。
2. 当点击 【全选】的时候,派发一个actions,去调用一个函数【这个函数可以多次发请求修改产品的勾选状态】,这个函数 我们要执行【遍历上面所有产品,如果勾选框勾上了–不动,如果勾选框没勾上—发请求修改产品的勾选状态】
-
给【全选】绑定事件
<div class="select-all"> <input class="chooseAll" type="checkbox" :checked="isAllChecked" @change="updateAllCartChecked" /> <span>全选</span></div>
派发actions
问:需要传参数吗
答:需要,因为我们发请求的时候需要商品的ID和isChecked,而我们对商品的isChecked修改是根据 【全选】的checked来修改。如果全选的checked为1,那么商品的isChecked为1,否则就是0。所以我们需要拿到当前状态该【全选】的checked值
// 修改全部产品的勾选状态 updateAllCartChecked(event){ // console.log(event.target.checked); // 返回的是布尔值 let isChecked = event.target.checked ? "1" : "0"; // 我们发请求需要的是布尔值 // 派发actions this.$store.dispatch('shopcart/updateAllCartIsChecked',isChecked) }
vuex,多调用几次修改产品勾选状态的函数
我们拿state里面的数据
state.cartList.cartInfoList
里面才是我们购物车里商品的数据// 修改全部商品的勾选状态 updateAllCartIsChecked({ dispatch, getters }, isChecked) { let promiseAll = []; // 遍历购物车里面的所有商品 getters.cartList.cartInfoList.forEach(item => { // 向修改产品的勾选状态发请求,带两个参数,第二个参数是【全选】的勾选状态 // 目的:让购物车里面的商品勾选状态与全选一致 let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked: isChecked }); promiseAll.push(promise); // 将每一次返回的promise添加到数组promiseAll当中 }); // 最终返回的结果 Promise.all只要全部的商品都修改成功,返回结果就是成功 return Promise.all(promiseAll); },
最后我们用
try...catch
,如果成功,则再次发请求获取购物车列表,如果失败,报错// 修改全部商品的勾选状态 updateAllCartIsChecked({ dispatch, getters }, isChecked) { let promiseAll = []; // 遍历购物车里面的所有商品 getters.cartList.cartInfoList.forEach(item => { // 向修改产品的勾选状态发请求,带两个参数,第二个参数是【全选】的勾选状态 // 目的:让购物车里面的商品勾选状态与全选一致 let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked: isChecked }); promiseAll.push(promise); // 将每一次返回的promise添加到数组promiseAll当中 }); // 最终返回的结果 Promise.all只要全部的商品都修改成功,返回结果就是成功 return Promise.all(promiseAll); },
解决小问题:当购物车里面商品没有的时候,全选框不可点击且不勾选上
cartInfoList.length > 0
当购物车商品数组长度>0才勾选上:disabled="cartInfoList == 0"
当购物车商品数组长度=0,不能被点击<div class="select-all"> <input class="chooseAll" type="checkbox" :checked="isAllChecked && cartInfoList.length > 0" :disabled="cartInfoList == 0" @change="updateAllCartChecked" /> <span>全选</span></div>
十、完成登录与注册页面
- 登录与注册的静态组件完成
- 注册业务的完成
- 登录业务的完成
登录与注册的静态组件完成
assets文件夹-----放置全部组件共用的静态资源
在样式当中也可以使用@符号【src别名】,切记在前面加上
~
注册业务的完成
- 主页业务 | 登录业务中表单验证先不处理【最后一天统一处理】
业务步骤:
- 看到注册页面,你会 【输入手机号】,然后点击【获取验证码】,验证码会发到你的手机上,然后你【输入验证码】---------向手机发送验证码
- 需要知道你输入的手机号,所以给手机号输入框双向绑定
v-model
- 点击【获取验证码】,会向服务器发请求,给你的手机发验证码,需要写api接口发请求
- 需要知道你输入的验证码是否正确,所以给验证码输入框双向绑定
- 需要知道你输入的手机号,所以给手机号输入框双向绑定
向手机发送验证码
/api/user/passport/sendCode/{phone} 获取验证码接口,GET
写api接口—获取验证码
// 获取验证码接口 /api/user/passport/sendCode/{phone} GET export const reqGetCode = (phone) =>{ return requests({url:`/user/passport/sendCode/${phone}`,method:'GET'})}
vuex 写仓库 --------我们把登录和注册的仓库写一起
写仓库
引入仓库与注册仓库
派发action 来获取验证码
// src/pages/Register/index.vue <input type="text" placeholder="请输入验证码" v-model="code" /> <button style="width: 100px; height: 38px" @click="getCode">获取验证码</button>... methods: { // 获取验证码 async getCode() { try { // 简单判断一下---至少有数据 this.phone && (await this.$store.dispatch("user/getCode", this.phone)); // 如果获取到验证码,让验证码自动填写,将组件中的code变为我们的验证码 this.code = this.$store.state.user.code; } catch (error) { alert(error.message); } }, },
VUEX来存储验证码
业务:获取验证码的接口,把验证码返回存储到state中,然后将【接收到的验证码】返回到组件里的验证码,这样点击【获取验证码】的时候,就可以自动返回并填写验证码了
------------正常情况是,后台把验证码发到用户手机上【省钱】,但我们后台弄不了
const state = { // 验证码 code:'', // 验证码起始状态是字符串}const mutations = { GETCODE(state,code){ state.code = code; }}const actions = { // 获取验证码 async getCode({ commit }, phone) { // 获取验证码的接口,把验证码返回,但是正常情况,后台把验证码发到用户手机上【省钱】 let result = await reqGetCode(phone) // result.data是验证码 if(result.code==200){ // 存储验证码,因为后台不能把验证码发到用户手机上 commit('GETCODE',result.data) return 'ok' }else{ return Promise.reject(new Error('发送验证码失败咯')) } },}
登录和确认密码
业务步骤
- 写登录密码和确认密码,这两个密码要一致,然后勾选同意协议,才能进行注册
- 要知道密码,所以我们给密码的input框进行【双向绑定】
- 要确保勾选框是勾上的,所以data里面新加
agree:true
<div class="content"> <label>登录密码:</label> <input type="password" placeholder="请输入你的登录密码" v-model="password"/> <span class="error-msg">错误提示信息</span> </div> <div class="content"> <label>确认密码:</label> <input type="password" placeholder="请输入确认密码" v-model="password1"/> <span class="error-msg">错误提示信息</span> </div> <div class="controls"> <input name="m1" type="checkbox" :checked="agree"/> <span>同意协议并注册《尚品汇用户协议》</span> <span class="error-msg">错误提示信息</span> </div>
data() { return { // 收集表单数据---手机号 phone: "", // 验证码 code: "", // 密码 password:'', // 确认密码 password1:'', // 是否同意协议 agree:true };
点击 完成注册
点击 完成注册,发请求进行注册,注册成功后 跳转到登录页面
写api请求 /api/user/passport/register POST 带参数
// 注册用户接口 /api/user/passport/register POST 带参数export const reqUserRegister = (data) => { return requests({ url: '/user/passport/register', method: 'POST', data })}
vuex写仓库
变量的解构赋值
const {phone} = this;
解构赋值后面必须有个;
这样就不用每次
this.phone
,可以直接phone
派发action
<div class="btn"> <button @click="userRegister">完成注册</button> </div>
// 用户注册--发请求async userRegister() { try { // 如果成功----路由跳转 // 变量的解构赋值 const { phone, code, password, password1 } = this; // 判断phone,code存不存在,以及密码和确认密码是否相等 //前面都为真的话就开始派发action phone && code && password == password1 && (await this.$store.dispatch("user/userRegister", { phone, code, password, })); // 如果成功就跳转路由---登陆页面 this.$router.push("/login"); } catch (error) { alert(error.message); }},
VUEX
// 完成用户注册async userRegister({ commit }, user) { let result = await reqUserRegister(user); if(result.code==200){ return 'ok' }else{ return Promise.reject(new Error('用户注册失败咯')) }},
表单验证
拿【登陆密码】为例子
<div class="content"> <label>登录密码:</label> <input type="password" placeholder="请输入你的登录密码" v-model="password" @change="testPassword" /> <span class="error-msg" style="display: none" ref="passwordError">错误提示信息</span></div>
登陆密码是 6-16位的数字或字母或 - 或 _
用正则
reg.test(value)
reg是规则,value 是我们要验证的如果验证成功返回的true,就让错误提示信息隐藏
验证错误,就要错误提示信息显示
// 表单验证登陆密码 testPassword(){ let regPassword = /^[a-zA-Z0-9-_]{6,16}$/; //6-16位的数字字母-和_ if(!regPassword.test(this.password)){ // 表单验证失败 this.$refs.passwordError.style.display = "block"; this.$refs.passwordError.innerHTML = "密码格式不正确,请输入6-16位的数字字母-和_"; }else{ this.$refs.passwordError.style.display = "none"; } },
手机号码:
/^1[3|4|5|7|8][0-9]{9}$/
QQ:[1-9][0-9]{4,}
(腾讯QQ号从10000开始)
昵称是中文:^[\u4e00-\u9fa5]{2,8}$
从第一个汉字到第二个汉字 2-8位
短信验证码:^\d{6}$
六位数字
密码:^[a-zA-Z0-9-_]{6,16}$
6-16位的数字字母-和_具体的正则
写api /api/user/passport/login POST 带参数
// 登录接口 /api/user/passport/login POST 带参数export const reqUserLogin = (data) =>{ return requests({url:'/user/passport/login',method:'POST',data})}
VUEX
const state = { code: '', // 验证码起始状态是字符串 token:'', // 用户的唯一标识}const mutations = { USERLOGIN(state,token){ state.token = token; }}const actions = {... // 完成用户登录 async userLogin({ commit }, data) { let result = await reqUserLogin(data); // 服务器下发token,用户唯一标识(相当于uuid) // 将来经常带着token找服务器要用户的信息 进行展示 if(result.code==200){ // 存储token commit('USERLOGIN',result.data.token); return 'ok'; }else{ return Promise.reject(new Error('用户登录失败咯')) } },}
派发action
// 登录的回调函数 async userLogin() { try { const { phone, password } = this; // 派发action phone && password && await this.$store.dispatch("user/userLogin", { phone, password }); // 进行路由跳转---home主页 phone && password && this.$router.push('/home') } catch (error) { alert(error.message) } },
token -------- 是用户的唯一标识
注册-----通过数据库存储用户信息(名字,密码)
登录-----登录成功的时候,后台为了区分你这个用户是谁--------服务器下发 token【令牌:唯一标识符】
前台会持久化 存储token【带者token找服务器要用户信息进行展示】
vuex仓库存储数据------不是持久化
十一、登录过后
首页用户信息的展示
- 用户注册完成-----用户点击登录【用户名+密码】向服务器发请求(组件派发action:userLogin),登陆成功获取到token,存储到仓库当中(非持久化),然后路由跳转到home首页
- 跳转到home首页,home首页在挂载的时候就向服务器发请求(组件派发action:getUserInfo),用token校验获取用户登录信息,然后动态的将用户信息展示到Header中
- 一刷新home首页,首页获取不到用户信息(token:vuex非持久化存储)
Header组件显示用户名与退出登录
写api
添加了token校验获取用户登录信息,用户登录只保存用户的token
token校验 http://182.92.128.115/api/user/passport/auth/getUserInfo
// 获取用户的信息【需要带着用户的token向服务器要用户信息】api/user/passport/auth/getUserInfo// 用请求头带过去参数export const reqUserInfo = () =>{ return requests({url:'/user/passport/auth/getUserInfo',method:'GET'})}
vuex三连环,存储数据
// src/store/user/index.jsconst state = { code: '', // 验证码起始状态是字符串 token: '', // 用户的唯一标识 userInfo:{}, // 用户的信息}const mutations = { GETUSERINFO(start,userInfo){ state.userInfo = userInfo; }}const actions = { // 获取用户信息【token】 async getUserInfo({ commit }) { let result = await reqUserInfo(); // 开始存储用户信息 if(result.code==200){ commit('GETUSERINFO',result.data) return 'ok' } }}
请求拦截器—需要携带token带给服务器
// src/api/requests.js// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情requests.interceptors.request.use((config) => { // config:配置对象,对象里面有一个属性很重要:headers请求头 if (store.state.detail.uuid_token) { // 给请求头添加一个字段(userTempId):和后台老师商量好 config.headers.userTempId = store.state.detail.uuid_token } // 需要携带token携带给服务器 if(store.state.user.token){ config.headers.token = store.state.user.token; } // 进度条开始动 nprogress.start(); return config;})
将仓库里的数据进行展示
Header组件
如果登陆成功,有用户信息的话,将用户信息展示
this.$store.state.user.userInfo.name
// 原形式 <div class="loginList"> <p>尚品汇欢迎您!</p> <p> <span>请</span> <router-link to="/login">登录</router-link> <router-link to="/register" class="register">免费注册</router-link> </p> </div>
现在形式
<div class="loginList"> <p>尚品汇欢迎您!</p> <p v-if="!userName"> <span>请</span> <router-link to="/login">登录</router-link> <router-link to="/register" class="register">免费注册</router-link> </p> <p v-else> <a>{{userName}}</a> <a class="register">退出登录</a> </p> </div>....<script>computed:{ // 用户名信息 userName(){ return this.$store.state.user.userInfo.name; }}</script>
注意!一刷新的话vuex存储的信息就没了,vuex非持久化存储,一刷新仓库里的 token就没有了
持久化存储token
在用户完成登录的时候,服务器发给我们token,我们将token本地存储起来,然后将state里面token,读取本地存储里的token
// src/utils/token.js// 本地存储token// 存储tokenexport const setToken = (token) => { localStorage.setItem('TOKEN', token)}// 获取tokenexport const getToken = () =>{ return localStorage.getItem('TOKEN')}
// src/store/user/index.jsimport { setToken,getToken } from '@/utils/token'const state = { code: '', // 验证码起始状态是字符串 token: getToken(), // 用户的唯一标识 userInfo: {}, // 用户的信息}...// 完成用户登录 async userLogin({ commit }, data) { let result = await reqUserLogin(data); // 服务器下发token,用户唯一标识(相当于uuid) // 将来经常带着token找服务器要用户的信息 进行展示 if (result.code == 200) { // 存储token commit('USERLOGIN', result.data.token); // 持久化存储token setToken(result.data.token); return 'ok'; } else { return Promise.reject(new Error('用户登录失败咯')) } },
这样就持久化存储token了
但是存在问题:
- 多个组件要展示用户信息,需要在每一个组件的mounted中 触发获取用户信息进行展示
this.$store.dispatch('user/getUserInfo')
,要不然进入到其他组件刷新会不展示用户信息 - 用户已经登陆了,用户再点击 登录 不能跳转到登陆页面
退出登录
向服务器发请求清空用户信息,清除后台token和前台的一些关于用户的数据,然后退出成功后跳转到首页
点击【退出登录】
<p v-else> <a>{{userName}}</a> <a class="register" @click="logout">退出登录</a> </p>
发请求通知服务器要退出登录【清除一些数据:token】
/api/user/passport/logout GET 无参数
写api
// 退出登录 /api/user/passport/logout GET 无参数export const reqLogout = () => { return requests({ url: '/user/passport/logout', method: 'GET' })}
vuex
// src/store/user/index.jsconst state = { code: '', // 验证码起始状态是字符串 token: getToken(), // 用户的唯一标识 userInfo: {}, // 用户的信息}const mutations = { // 清除本地的数据 CLEAR(state) { // 把仓库中相关用户信息清空 以及 本地存储数据清空 state.token = ''; state.userInfo = {}; removeToken(); }}const actions = { // 退出登录,向服务器发请求,通知服务器清除服务器的token async userLogout({ commit }) { let result = await reqLogout(); // 发请求成功,清除前台的数据 if (result.code == 200) { commit('CLEAR'); return 'ok' }else{ return Promise.reject(new Error('退出登录失败')) } }}
// src/utils/token.js// 本地存储token// 存储tokenexport const setToken = (token) => { localStorage.setItem('TOKEN', token)}// 获取tokenexport const getToken = () =>{ return localStorage.getItem('TOKEN')}// 清除本地存储的tokenexport const removeToken = ()=>{ localStorage.removeItem('TOKEN')}
退出登录后 跳转到home首页
// src/components/Header/index.vuemethods:{ // 退出登录 async logout() { try { // 退出登录 清空数据 派发action await this.$store.dispatch("user/userLogout"); // 退出成功,回到首页 this.$router.push('/home') } catch (error) { alert(error.message) } },}
导航守卫
现在的问题:
- 用户已经登录,用户不应该可以去到login登录页面
- 用户没有登陆,用户不能去购物车页面
-
配置路由
// src/router/index.js// 引入路由组件import Trade from '../pages/Trade'...// 配置路由let router = new VueRouter({ // 配置路由 routes: [ .... { name: 'trade', path: '/trade', component: Trade, meta: { showFooter: true }, }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 }})
获取交易页数据
获取用户地址信息–写api
/api/user/userAddress/auth/findUserAddressList 获取用户地址信息 GET
// 获取用户地址信息 /api/user/userAddress/auth/findUserAddressList GETexport const reqAddressInfo = () =>{ return requests({url:'/user/userAddress/auth/findUserAddressList',method:'GET'})}
获取商品清单信息–写api
/api/order/auth/trade 获取订单交易页【商品清单信息】 GET
// 获取订单交易页 商品清单信息 /api/order/auth/trade GET 无参数export const reqOrderInfo = () =>{ return requests({url:'/order/auth/trade',method:'GET'})}
建仓库trade
// src/store/trade/index.js// 交易页面的仓库import { reqAddressInfo } from '@/api/index'const state = {};const mutations = {};const actions = {};const getters = {};export default { // 开启命名空间 namespaced: true, state, mutations, actions, getters,}
在大仓库引入小仓库
vuex存储数据
// 交易页面的仓库import { reqAddressInfo, reqOrderInfo } from '@/api/index'const state = { address: [], // address至少是个空数组 orderInfo: {}, // 至少是个空对象};const mutations = { GETUSERADDRESS(state, address) { state.address = address; }, GETORDERINFO(state, orderInfo) { state.orderInfo = orderInfo; }};const actions = { // 获取用户地址信息 async getUserAddress({ commit }) { let result = await reqAddressInfo(); if (result.code == 200) { commit('GETUSERADDRESS', result.data) } }, // 获取商品清单信息 async getOrderInfo({ commit }) { let result = await reqOrderInfo(); console.log(result); if (result.code == 200) { commit('GETORDERINFO', result.data) } }};
当交易页面挂载完毕的时候就开始派发action请求用户地址信息
<script> export default { name: 'Trade', mounted(){ // 组件挂载完毕,就要获取用户的地址信息 this.$store.dispatch('trade/getUserAddress') // 获取商品清单列表 this.$store.dispatch('trade/getOrderInfo') }, }</script>
动态渲染数据
渲染用户地址信息
当数据中的
isDefault
为1 是,才是默认地址,所以收件人会有边框以及地址后面跟着【默认地址】动态绑定【类名】以及【默认地址的出现】
:class="{ selected: addressInfo.isDefault == 1 }"
<div class="address clearFix" v-for="addressInfo in address" :key="addressInfo.id"> <span class="username" :class="{ selected: addressInfo.isDefault == 1 }" >{{ addressInfo.consignee }}</span> <p @click="changeDefault(addressInfo,address)"> <span class="s1">{{ addressInfo.fullAddress }}</span> <span class="s2">{{ addressInfo.phoneNum }}</span> <span class="s3" v-show="addressInfo.isDefault == 1">默认地址</span> </p></div>
当你点击谁 谁就是默认地址------【排他思想】先让全部的地址的isDefault为0,然后让你点击的为1
@click="changeDefault(addressInfo,address)"
要传两个参数,一个是当前点击的地址,一个是所有的地址列表
// 修改默认地址 changeDefault(addressInfo,address){ // addressInfo你点击的--address全部地址列表 // 排他思想----先让全部的地址的isDefault为0,然后让你点击的为1 address.forEach(item => { item.isDefault = 0; }); addressInfo.isDefault = 1 },
当上面修改了默认地址后,将来提交订单最终选中的地址也要改变—所以我们计算出最终选中的地址
这里计算最终地址我们用
find
方法,可以直接将符合条件的元素返回
import { mapState } from "vuex";... computed: { ...mapState("trade", ["address"]), // state仓库下的 trade小仓库里面的address数据 // 计算出将来最终要提交的选中的地址 userDefaultAddress() { // find:查找数组当中符合条件的元素返回 return this.address.find(item => item.isDefault==1) || {};// 至少是个空对象 }, },
将计算出的地址进行渲染
<div class="trade"> <div class="price">应付金额: <span>¥5399.00</span></div> <div class="receiveInfo"> 寄送至: <span>{{userDefaultAddress.fullAddress}}</span> 收货人:<span>{{userDefaultAddress.consignee}}</span> <span>{{userDefaultAddress.phoneNum}}</span> </div> </div>
渲染商品清单信息
将仓库里的数据捞到组件里面
// src/pages/Trade/index.vue import { mapState } from "vuex"; computed: { ...mapState("trade", ["address","orderInfo"]), // state仓库下的 trade小仓库里面的address和orderInfo数据 }
开始渲染数据
<ul class="list clearFix" v-for="order in orderInfo.detailArrayList" :key=order.skuId> <li> <img :src="order.imgUrl" style="width:100px;height:100px"/> </li> <li> <p>{{order.skuName}}</p> <h4>7天无理由退货</h4> </li> <li> <h3>¥{{order.orderPrice}}</h3> </li> <li>X{{order.skuNum}}</li> <li>有货</li> </ul>
收集买家的留言信息
买家留言是要收集然后并上传的,所以动态绑定 买家留言
<div class="bbs"> <h5>买家留言:</h5> <textarea placeholder="建议留言前先与商家沟通确认" class="remarks-cont" v-model="msg"></textarea> </div>...<script>data(){ return { // 收集买家留言的信息 msg:'', }},</script>
渲染底部总信息
购物车里的总商品数量和金额
<li> <b><i>{{orderInfo.totalNum}}</i>件商品,总商品金额</b> <span>¥{{orderInfo.totalAmount}}</span></li>...<div class="price">应付金额: <span>¥{{orderInfo.totalAmount}}</span></div>
十三、完成提交订单业务
点击 【交易页面】中的【提交订单】,向服务器发请求【把支付的一些信息传递给服务器】,然后跳转到【支付页面】
<div class="sub clearFix"> <a class="subBtn" to="/pay" @click="submitOrder">提交订单</a> </div>
完成支付页面的静态组件
引入静态组件
配置路由
// src/router/index.jsimport Pay from '../pages/Pay' ...// 配置路由let router = new VueRouter({ // 配置路由 routes: [ { name: 'pay', path: '/pay', component: Pay, meta: { showFooter: true }, }, // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!! { path: '*', redirect: '/home', } ], // 控制滚动条的滚动行为 scrollBehavior(to, from, savedPosition) { // return 期望滚动到哪个的位置 return { y: 0 }; // 始终滚动到顶部 }})
提交订单发起请求
当你点击【提交订单】的时候,向服务器发送你的一些交易页面的数据,获取到你的订单号
提交订单发请求—写api
这里tradeNo 当query参数拼接在路径中,其余参数都写在data对象里面
// src/api/index.js// 提交订单的接口 /api/order/auth/submitOrder" /> // src/main.js // 统一接收api文件夹里面全部请求函数import *as API from '@/api'new Vue({ render: h => h(App), // 注册路由信息:当这里书写router的时候,组件身上都拥有$route,$router属性 router, // 注册仓库:组件实例的身上会多一个属性$store属性 store, // 配置全局事件总线$bus beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm Vue.prototype.$API = API; // 以后你使用请求接口的时候可以 this.$API.xxx },}).$mount('#app')
当点击【提交订单】开始发请求
<div class="sub clearFix"> <a class="subBtn" to="/pay" @click="submitOrder">提交订单</a> </div>
开始发请求
返回 成功后的
{ // 返回result "code": 200, "message": "成功", "data": 71, // orderId 订单号 "ok": true}
// 提交订单发请求 async submitOrder() { // 发请求,需要带参数tradeNo let { tradeNo } = this.orderInfo; // 解构赋值!!!可以直接用this.orderInfo的tradeNo // 其余的六个参数 let data = { consignee: this.userDefaultAddress.consignee, // 收件人姓名 consigneeTel: this.userDefaultAddress.phoneNum, // 收件人电话 deliveryAddress: this.userDefaultAddress.fullAddress, // 收件人地址 paymentWay: "ONLINE", // 支付方式 orderComment: this.msg, // 订单备注 // 存储多个商品对象的数组 orderDetailList: this.orderInfo.detailArrayList, }; // 开始发请求 let result = await this.$API.reqSubmitOrder(tradeNo, data); console.log(result); },
请求成功后 将数据存储到组件的data里面
// 开始发请求 let result = await this.$API.reqSubmitOrder(tradeNo, data); console.log("提交订单", result); if (result.code == 200) { // 提交订单成功 // 存储数据 this.orderId = result.data; // 进行跳转,把订单号传参过去 this.$router.push("/pay?orderId=" + this.orderId); } else { alert(result.data); }
data() { return { // 收集买家留言的信息 msg: "", // 订单号 orderId: "", };
在支付页面展示数据
展示订单号
<span class="fl">请您在提交订单<em class="orange time">4小时</em>之内完成支付,超时订单会自动取消。订单号:<em>{{orderId}}</em></span>
computed:{ // 订单号 orderId(){ return this.$route.query.orderId; } },
展示应付金额
要拿到应付金额,要拿着订单号向服务器发请求,捞到你这次支付的信息
// 写api 发请求// 获取订单支付信息 /api/payment/weixin/createNative/{orderId} 带参数 GETexport const reqPayInfo = (orderId) => { return requests({ url: `/payment/weixin/createNative/${orderId}`, method: 'GET' })}
当你路由跳转到【支付页面】的时候,你会拿到【订单号】,然后你就要拿着【订单号】去发请求捞到【支付金额】,所以是你在【支付页面挂载】的时候就要开始发请求获取订单支付信息
在组件里发请求完后 就要等待你的数据await
,正常情况是async
,
但是!生命周期函数中不要使用async
!!!!!!!!!!!!!!!!!!!!!!!!
// src/pages/Pay/index.vue data() { return { payInfo: {}, // 至少是个对象 }; mounted() { // 发请求 获取订单支付信息 this.getPayInfo(); }, methods: { async getPayInfo() { let result = await this.$API.reqPayInfo(this.orderId); if (result.code == 200) { // 存储支付信息 this.payInfo = result.data; } }, },
// 开始展示数据 <span class="fr"> <em class="lead">应付金额:</em> <em class="orange money">¥{{payInfo.totalFee}}</em> </span>
点击立即支付-elementUI
点击【立即支付】会弹出一个【遮罩层】
- 我们这里用elementUI
安装elementUI【我们按需引入】
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ]}
我们这里用这个
注册组件
// main.js// 引入elementUI--按需引入import { MessageBox,xxxx } from 'element-ui'// 引入样式import 'element-ui/lib/theme-chalk/index.css';// 注册组件的时候---可以挂载在原型上// Vue.use(xxxxx)Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;
点击【立即支付】以后
<div class="submit"> <a class="btn" @click="open">立即支付</a> </div>
// 遮罩层/弹出框 open() { this.$alert("二维码", "HTML 片段", { dangerouslyUseHTMLString: true, // 是否将 message 属性作为 HTML 片段处理 center:true, // 中间布局 showCancelButton:true, // 是否显示取消按钮 cancelButtonText:'支付遇见问题', // 取消按钮的文本内容 confirmButtonText:'已支付成功', // 确定按钮的文本内容 showClose:false, // MessageBox 是否显示右上角关闭按钮 }); },
现在样式是这样
微信支付业务
二维码生成 qrcode
qrcode - npm npm官网
安装QRCODE
npm i qrcode
// 遮罩层/弹出框 async open() { // 生成二维码地址 let url = await QRCode.toDataURL(this.payInfo.codeUrl); this.$alert(` 支付后
我们要知道他是支付成功 | 失败
- 支付成功,进行路由的跳转
- 支付失败,提示信息
怎么知道他现在处于什么支付状态呢?
当 弹出框一弹出来,就开始一直发请求 知道你的订单状态
查询支付订单的状态 /api/payment/weixin/queryPayStatus/{orderId} GET 带参数
// 写api// 查询支付订单的状态 /api/payment/weixin/queryPayStatus/{orderId} GET 带参数export const reqPayStatus = (orderId) => { return requests({ url: `/payment/weixin/queryPayStatus/${orderId}`, method: 'GET' })}
支付成功
支付成功—code==200
- 要清除定时器
- 保存支付成功返回的code,之后点击【支付成功】按钮时 要判断你的code==200,然后进行路由跳转
- 关闭支付弹出框
- 如果不点击【支付成功】按钮的话,你支付成功了,可以直接跳转到下一页路由
// 你需要知道支付成功 | 失败 // 支付成功,路由的跳转;如果支付失败,提示信息 // 定时器没有,开启一个新的的定时器 if (!this.timer) { this.timer = setInterval(async () => { // 发请求获取 用户支付状态 let result = await this.$API.reqPayStatus(this.orderId); // 支付成功 if(result.code == 200){ // 清除定时器 clearInterval(this.timer); this.timer = null; // 保存支付成功返回的code ,然后之后 点击支付成功 判断你的code==200,然后进行路由跳转 this.code = result.code; // 关闭弹出框 this.$msgbox.close(); // 跳转到下一页路由 this.$router.push('/paysuccess'); } }, 1000); }
下一页路由-----支付成功页面
- 静态组件
- 配置路由
没有支付
没有支付,点击了【已支付成功】,弹出框不要消失,
beforeClose
MessageBox 关闭前的回调,会暂停实例的关闭,有三个参数
// type:可以区分 取消 | 确定 按钮
// instance:当前组件实例
// done 关闭弹出框的方法
- 如果你点击了【支付遇见问题】,会弹框 让你联系管理员,清除定时器,关闭弹出框
- 如果你点击【已支付成功】,要判断是否真的支付成功了
- code==200,支付成功,关闭定时器和弹出框,跳转到下一路由
- code !==200,未支付成功,弹出 【未支付成功】,关闭定时器和弹出框
beforeClose: (type, instance, done) => { // type:可以区分 取消 | 确定 按钮 // instance:当前组件实例 // done 关闭弹出框的方法 // 关闭弹出框之前的配置 // 如果你点击了[支付遇见问题] if (type == "cancel") { alert("请联系管理员东方青苍"); // 清除定时器 clearInterval(this.timer); this.timer = null; // 关闭弹出框 done(); } else { // 判断是否真的支付了 if (this.code == 200) { // 关闭定时器 clearInterval(this.timer); this.timer = null; // 关闭弹出框 done(); // 跳转到下一页路由 this.$router.push("/paysuccess"); } else { alert("未支付成功"); // 清除定时器 clearInterval(this.timer); this.timer = null; // 关闭弹出框 done(); } } },
完整代码
src/pages/Pay/index.vue
<script>import QRCode from "qrcode";export default { name: "Pay", data() { return { payInfo: {}, // 至少是个对象 timer: null, code: "", // 支付的状态码 }; }, computed: { // 订单号 orderId() { return this.$route.query.orderId; }, }, mounted() { // 发请求 获取订单支付信息 this.getPayInfo(); }, methods: { // 获取支付的信息 async getPayInfo() { let result = await this.$API.reqPayInfo(this.orderId); if (result.code == 200) { // 存储支付信息 this.payInfo = result.data; } }, // 遮罩层/弹出框 async open() { // 生成二维码地址 let url = await QRCode.toDataURL(this.payInfo.codeUrl); this.$alert(` 完成静态组件
-
将组件引入到文件夹下
-
配置路由
// src/router/index.js// 引入组件import Center from '../pages/Center'// 配置路由组件let router = new VueRouter({ // 配置路由 routes: [ { name:'center', path:'/center', component:Center, meta: { showFooter: true }, }, ]})
个人中心二级路由搭建
想给Center组件加上二级路由 :
当你点击 【我的订单】显示我的订单相关内容,.
当你点击【团购订单】 显示团购订单相关内容
步骤:
将子组件【myOrder】和【groupOrder】的静态组件放到【Center】文件夹下
配置二级路由组件
// src/router/index.js// 引入二级路由组件import MyOrder from '../pages/Center/myOrder'import GroupOrder from '../pages/Center/groupOrder'{ name:'center', path:'/center', component:Center, meta: { showFooter: true }, // 二级路由组件 children:[ { name:'myorder', path:'myorder', component:MyOrder, }, { name:'grouporder', path:'grouporder', component:GroupOrder }, // 重定向 { path:'/center', redirect:'/center/myorder' } ] },
在父组件中引入–注册子组件
// src/pages/Center/index.vue<script> // 引入子组件 import myOrder from './myOrder' import groupOrder from './groupOrder' export default { name: 'Center', // 注册局部组件 components:{ myOrder, groupOrder } }</script>
展示子路由组件 出口的位置
// src/pages/Center/index.vue <dd><router-link to="/center/myorder">我的订单</router-link></dd> <dd><router-link to="/center/grouporder">团购订单</router-link></dd><router-view></router-view>
我的订单
我们要展示动态数据
发请求-捞数据-动态展示数据
发请求存储数据
/api/order/auth/{page}/{limit} GET 带参数
// 获取我的订单列表 /api/order/auth/{page}/{limit} GET 带参数export const reqMyOrderList = (page, limit) => { return requests({ url: `/order/auth/${page}/${limit}`, method: 'GET' })}
当【我的订单】开始挂载的时候,就要发次请求捞到数据,然后每点击下面的页数也要发次请求,所以封装成一个函数
<script>export default { name: "myOrder", data() { return { // 初始化一些参数 page: 1, // 当前第几页 limit: 3, // 每一页展示的数据个数 myOrder:{}, // 存储我的订单的数据 }; }, mounted() { // 获取我的订单的数据的方法 this.getData(); }, methods: { // 获取我的订单的方法 async getData() { // 解构出参数 const { page, limit } = this; let result = await this.$API.reqMyOrderList(page, limit); if(result.code ==200){ // 存数据到组件身上 this.myOrder = result.data; } }, },};</script>
动态渲染数据
<div class="orders"> <table class="order-item" v-for="order in myOrder.records" :key=order.id> <thead> <tr> <th colspan="5"> <span class="ordertitle">{{order.createTime}} 订单编号:{{order.outTradeNo}} <span class="pull-right delete"><img src="../images/delete.png" /></span> </span> </th> </tr> </thead> <tbody> <tr v-for="(cart,index) in order.orderDetailList" :key="cart.id"> <td width="60%"> <div class="typographic"> <img :src="cart.imgUrl" style="width:100px;height:100px"/> <a href="#" class="block-text">{{cart.skuName}}</a> <span>x{{cart.skuNum}}</span> <a href="#" class="service">售后申请</a> </div> </td> <td :rowspan="order.orderDetailList.length" v-if="index==0" width="8%" class="center">{{order.consignee}}</td> <td :rowspan="order.orderDetailList.length" v-if="index==0" width="13%" class="center"> <ul class="unstyled"> <li>总金额¥{{order.totalAmount}}</li> <li>在线支付</li> </ul> </td> <td :rowspan="order.orderDetailList.length" v-if="index==0" width="8%" class="center"> <a href="#" class="btn">{{order.orderStatusName}} </a> </td> <td :rowspan="order.orderDetailList.length" v-if="index==0" width="13%" class="center"> <ul class="unstyled"> <li> <a href="mycomment.html" target="_blank">评价|晒单</a> </li> </ul> </td> </tr> </tbody> </table></div>
tbody里面的tr是每一行,每一行就是一个产品,td是每个单元格,我们要合并单元格,是根据该订单的数量来合并的
每一个tr是一个产品,但是一个订单可能会有多个产品,我们只需要遍历左边的,右边的只显示第一个就可以了
最后的结果:
分页器
咱们之前写过分页器组件,作为了全局组件,我们可以直接使用
<div class="choose-order"> <Pagination :pageNo="page" :pageSize="limit" :total="myOrder.total" :continues="5" @getPageNo="getPageNo" ></Pagination> </div>
pageNo :是当前在第几页
pageSize:是一页有几条数据
total:是一共有多少条数据
continues:是连续页数是几页
@getPageNo 是自定义事件,当你点击的时候,就跳转到第几页,把你点击的页数传给该函数
// 获取当前点击的那一页 getPageNo(page){ // 修改组件响应式数据 this.page = page; // 再次发请求 this.getData(); },
十五、鉴权判断
未登录的导航守卫判断
跳转到登陆过后—>导航守卫---->用户未登录后的页面
路由独享守卫
想要去交易trade页面,不能去从地址栏进行跳转,只能从【购物车页面】才能跳转到【交易页面】
同理,只能从【交易页面】跳转到【支付页面】
只能从【支付页面】跳转到【支付成功页面】
路由独享守卫,要写在路由配置项里面,写在你要【鉴权】的路由里面
next(false)
中断当前路由,停留在当前页面
只能从【购物车页面】才能跳转到【交易页面】,就要看他来的路径是不是购物车,是的话就放行,不是的话就停留在当前页面
只能从【交易页面】跳转到【支付页面】 同理
{ name: 'trade', path: '/trade', component: Trade, meta: { showFooter: true }, // 路由独享守卫 beforeEnter: (to, from, next) => { if (from.path == '/shopcart') { next(); } else { next(false); // 停留在当前页面 } } }, { name: 'pay', path: '/pay', component: Pay, meta: { showFooter: true }, // 路由独享守卫 beforeEnter: (to, from, next) => { if (from.path == '/trade') { next(); } else { next(false); // 停留在当前页面 } } }, { name: 'paysuccess', path: '/paysuccess', component: PaySuccess, meta: { showFooter: true }, beforeEnter: (to, from, next) => { if (from.path == '/pay') { next(); } else { next(false); // 停留在当前页面 } } },
组件内守卫
在组件里面写该守卫【.vue】
你可以为路由组件添加以下配置:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染该组件的对应路由被验证前调用 // 不能获取组件实例 `this` ! // 因为当守卫执行时,组件实例还没被创建! }, beforeRouteUpdate(to, from) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候, // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this` }, beforeRouteLeave(to, from) { // 在导航离开渲染该组件的对应路由时调用 // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this` },}
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:
beforeRouteEnter (to, from, next) { next(vm => { // 通过 `vm` 访问组件实例 })}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持 传递回调,因为没有必要了:
beforeRouteUpdate (to, from) { // just use `this` this.name = to.params.name}
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false
来取消。
beforeRouteLeave (to, from) { const answer = window.confirm('Do you really want to leave" />dist就是我们最终的文件夹
项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确的知道是哪里的代码报错
有了map文件就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
所以,该文件 如果项目不需要 是可以去除掉
如何在打包前就去除掉?
在 vue.config.js 配置里面,加上 productionSourceMap:false
购买云服务器
项目要让别人访问,就放在服务器上
阿里云 腾讯云 可以购买
安全组设置
让服务器一些端口号打开
xshell链接服务器与linux指令
利用xshell工具登录服务器【需要用户名和密码】
nginx反向代理
麻了…到时候去看【尚硅谷VUE项目】P111的视频吧
这个需要深学!!