vue前台(十一)
一,我的订单获取数据进行展示
在center订单中心组建中,创建两个子路由组件,myorder组件, 我的订单, grouporder组件, 团购组件
<dd> <router-link to="/center/myorder">我的订单</router-link> </dd> <dd> <router-link to="/center/grouporder">团购订单</router-link> </dd>
</div> <!-- 右侧内容 --> <router-view></router-view> </div>
配置路由
{
path:'/center',
component:Center,
children:[
{
path:'myorder',
component:Myorder
},
{
path:'grouporder',
component:Grouporder
},
{
path:'',
redirect:'myorder'
}
]
},
点击路由连接router-link,自带的类,配置颜色
//左边
.order-left {
float: left;
width: 16.67%;
.router-link-active {
color: hotpink;
/* background-color: hotpink; */
}
1.封装请求获取我的订单分页信息
//请求获取我的订单分页信息 /api/order/auth/{page}/{limit} get
export const reqMyOrder = (page,limit) => Ajax.get(`/order/auth/${page}/${limit}`)
2.myorder组件获取数据进行展示,需要传入page和limit参数,可在data中初始化,因为myorder中有分页器,需要用到该参数
data() {
return {
page: 1,
limit: 5,
myOrderInfo: {},
};
},
mounted() {
this.getMyOrder();
},
methods: {
//这个MyOrder组件是一个路由组件
//路由组件点击切换才会创建组件对象,父组件传递才有可能
//可以选择路由传参,但是非常复杂并且不适合(数据有可能很复杂)
//所以请求数据只能在子路由组件
async getMyOrder(page = 1) {
this.page = page;
const result = await this.$API.reqMyOrder(this.page, this.limit);
if (result.code === 200) {
this.myOrderInfo = result.data;
}
},
3.返回的响应数据
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"id": 70,
"consignee": "admin",
"consigneeTel": "15011111111",
"totalAmount": 29495,
"orderStatus": "UNPAID",
"userId": 2,
"paymentWay": "ONLINE",
"deliveryAddress": "北京市昌平区2",
"orderComment": "",
"outTradeNo": "ATGUIGU1584247289311481",
"tradeBody": "Apple iPhone 11 (A2223) 128GB手机 双卡双待 A",
"createTime": "2020-03-15 12:41:29",
"expireTime": "2020-03-16 12:41:29",
"processStatus": "UNPAID",
"trackingNo": null,
"parentOrderId": null,
"imgUrl": null,
"orderDetailList": [
{
"id": 81,
"orderId": 70,
"skuId": 2,
"skuName": "Apple iPhone 11 (A2223) 64GB 红色",
"imgUrl": "http://192.168.200.128:8080/xxx.jpg",
"orderPrice": 5499,
"skuNum": 1,
"hasStock": null
},
…
],
"orderStatusName": "未支付",
"wareId": null
},
…
],
"total": 41,
"size": 2,
"current": 1,
"pages": 21
},
"ok": true
}
4.在html中填充数据
<tr v-for="(goods, index) in order.orderDetailList" :key="goods.id"> <td width="60%"> <div class="typographic"> <!-- hasStock:null id:4252 imgUrl:"http://182.92.128.115:8080/group1/M00/00/0D/rBFUDF7G-ZKADQhWAAJsvyuFaiE144.jpg" orderId:1939 orderPrice:4500 skuId:118 skuName:"华为P40--22" skuNum:2 --> <img :src="goods.imgUrl" style="width:80px;height:80px" /> <a href="#" class="block-text">{{goods.skuName}}</a> <span>x{{goods.skuNum}}</span> <a href="#" class="service">售后申请</a> </div> </td> <!--template是一个内置的标签,这个标签不会影响样式,相当于一个包裹器和div类似,但是div影响样式 --> <template v-if="index === 0"> <td :rowspan="order.orderDetailList.length" width="8%" class="center" >{{order.consignee}}</td> <td :rowspan="order.orderDetailList.length" width="13%" class="center"> <ul class="unstyled"> <li>总金额¥{{order.totalAmount}}</li> <li>{{ order.paymentWay === "ONLINE" ? '在线支付': '货到付款'}}</li> </ul> </td> <td :rowspan="order.orderDetailList.length" width="8%" class="center"> <a href="#" class="btn">{{order.orderStatus === "UNPAID"?"未支付":"已完成"}}</a> </td> <td :rowspan="order.orderDetailList.length" width="13%" class="center"> <ul class="unstyled"> <li> <a href="mycomment.html" target="_blank">评价|晒单</a> </li> </ul> </td> </template> </tr>
注;1.此时需要对单元格进行合并,首先,只对第一行数据展示,然后对td单元格标签中的 :rowspan属性进行行数占据,可计算order.orderDetailList.length长度
二,使用element-ui的分页器
1.在html中使用分页器paginaton
在入口文件main.js中导入分液器pagainaton
import { MessageBox, Message, Pagination } from 'element-ui';
Vue.use(Pagination)
<!-- @size-change="changeSize" 修改每页的数量回调函数 选择了新条数,就会触发这个事件,把选择的条数传给这个事件 --> <!-- @current-change="getMyOrder" 修改当前页 点击了哪一页,就会触发这个事件 把点击的页码传给这个事件 --> <el-pagination background :current-page="page" :page-size="limit" layout=" prev, pager, next, jumper,->,total" :total="myOrderInfo.total" :pager-count="5" @current-change="getMyOrder($event)" @size-change="changeSize" ></el-pagination>
2.@current-change="getMyOrder" 和 @size-change="changeSize" 两个事件函数
methods: {
//这个MyOrder组件是一个路由组件
//路由组件点击切换才会创建组件对象,父组件传递才有可能
//可以选择路由传参,但是非常复杂并且不适合(数据有可能很复杂)
//所以请求数据只能在子路由组件
async getMyOrder(page = 1) { //默认当前页为第一页
this.page = page; //修改当前页
const result = await this.$API.reqMyOrder(this.page, this.limit);
if (result.code === 200) {
this.myOrderInfo = result.data;
}
},
//改变当前页的条数
changeSize(size){
this.limit = size
this.getMyOrder()
},
注,对分页器的属性详解
<el-pagination //当前页的颜色 background //修改每页的条数回调函数 选择了新条数,就会触发这个事件,把选择的条数传给这个事件 @size-change="handleSizeChange" //修改当前页 点击了哪一页,就会触发这个事件 把点击的页码传给这个事件 @current-change="handleCurrentChange" //当前页 :current-page="currentPage4" 首尾项 //连续页码数,包括首 :pager-count="5" :page-sizes="[100, 200, 300, 400]" //每页显示条目个数 :page-size="100" //total放在最后面 layout=" prev, pager, next, jumper,->,total" //总条目数 :total="400"> </el-pagination>
三,全局前置路由守卫的使用
1.在router-index.js,
2.引入vuex
import routes from '@/router/routes'
const router = new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
//添加全局前置路由导航守卫
// 必须登录后才能访问的多个界面使用全局守卫(交易相关、支付相关、用户中心相关)
// 自动跳转前面想而没到的页面
router.beforeEach((to, from, next) => {
//to:代表路由对象,目标(想去哪)
//from: 代表路由对象,起始(从哪来)
//netx:是一个函数,选择放行或者不放行的意思还可以去重定向到一个新的地方
//next()就是放行
//next(false)不放行
//next(路径)重定向
let targerPath = to.path
if(targerPath.startsWith('/pay') || targerPath.startsWith('/trade') || targerPath.startsWith('/center')){
//看看用户是否登录了
if(store.state.user.userInfo.name){
next()
}else{
//在登录的路径后面添加上之前想要去的路径
//配合登录逻辑可以让我们去到之前想去而没有去的地方
next('/login?redirect='+targerPath)
}
}else{
next()
}
})
export default router
注; 1.如果用户需要去交易页面,订单页面,需要判断一下
2.判断用户是否登录了,从vuex中的user.js找到用户名信息,判断是否登录了
3.
如果用户没有登录,点击我的订单,跳转到登录页面,然后登录后,应该直接跳转到我的订单页面
此时需要配置一个重定向的路径,在登录路径后添加一个query参数(去哪里的路径)
此时在登录组件需要判断一下是否有query参数,有的话,去需要去的路径,没有的话,去home路径
//点击登录按钮,发送请求
methods: {
async login() {
let { mobile, password } = this;
if (mobile && password) {
let userInfo = { mobile, password };
try {
await this.$store.dispatch("userLogin", userInfo);
let redirectPath = this.$route.query.redirect;
//如果存在需要去的路径
if (redirectPath) {
//代表是从导航守卫进来的登录逻辑
this.$router.push(redirectPath);
} else {
//代表不是从导航守卫来的登录逻辑
this.$router.push("/home");
}
} catch (error) {
alert(error.message);
}
}
},
},
有个bug, 如果用户登录了,进入登录页面输入http://localhost:8080/#/login, 还是会跳转到登录页面,应该到home页面
此时需要设置路由独享守卫,没有登录时,放行,有登录时,跳转到home页面
2.引入vuex, import store from '@/store'
在路由对象中 router--routers.js, 设置login路由对象中设置路由独享守卫
{
path:'/login',
component:Login,
// 用来判定底部是否隐藏
meta:{
isHide:true
},
//路由独享守卫
beforeEnter: (to, from, next) => {
//to:代表路由对象,目标(想去哪),此时代表login路由对象
if(!store.state.user.userInfo.name){
//没登录,放行
next()
}else{
//登录了,跳转到home页面
next('/home')
}
}
},
五,组件内的守卫,一般不用,一般用路由独享守卫
和路由独享守卫的第二种方法,组件内的守卫,在login组件中设置
引入vuex
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
// 如果内部需要用到this,那么就得用下面的那个写法
if (!store.state.user.userInfo.name) {
//没有登录,放行
next();
} else {
//登录了,跳转到home页面
next("/home");
}
},
// beforeRouteEnter(to, from, next) {
// next((vm) => {
// // 通过 `vm` 访问组件实例 vm就是你之前想要的this
// });
// },
六,只有携带了skuNum和sessionStorage内部有skuInfo数据 才能看到添加购物车成功的界面,路由独享守卫
{
path:'/addCartSuccess',
component:AddCartSuccess,
name:"addcartsuccess",
//只有携带了skuNum和sessionStorage内部有skuInfo数据 才能看到添加购物车成功的界面
beforeEnter: (to, from, next) => {
let skuNum= to.query.skuNum
let skuInfo = JSON.parse(sessionStorage.getItem('SKUINFO'))
//判断
if(skuNum && skuInfo){
//携带了skuNum 和skuInfo,放行
next()
}else{
next('/')
}
}
},
只有从购物车界面/shopcart才能跳转到交易页面(创建订单)/trade
{
path:'/trade',
component:Trade,
beforeEnter: (to, from, next) => {
if(from.path === '/shopcart'){
next()
}else{
next('/')
}
}
},
只有从交易页面(创建订单)页面/trade才能跳转到支付页面/pay
{
path:'/pay',
component:Pay,
beforeEnter: (to, from, next) => {
if(from.path === '/trade'){
next()
}else{
next('/')
}
}
},
只有从支付页面/pay才能跳转到支付成功页面/paysuccess
{
path:'/paysuccess',
component:PaySuccess,
beforeEnter: (to, from, next) => {
if(from.path === '/pay'){
next()
}else{
next('/')
}
}
},
七,图片懒加载
1. 图片懒加载特点说明 (1) 还没有加载得到目标图片时, 先显示loading图片 (2) 在<img>进入可视范围才加载请求目标图片 2. 下载依赖 npm install vue-lazyload 3. 引入并配置loading图片 import VueLazyload from 'vue-lazyload' import loading from '@/assets/images/loading.gif' // 在图片界面没有进入到可视范围前不加载, 在没有得到图片前先显示loading图片 Vue.use(VueLazyload, { // 内部自定义了一个指令lazy loading, // 指定未加载得到图片之前的loading图片 }) 4. 对异步获取的图片实现懒加载 <img v-lazy="goods.defaultImg" />
1.在入口文件main.js中配置
// 引入图片懒加载插件
import VueLazyload from 'vue-lazyload'
import loading from '@/assets/images/loading.gif'
Vue.use(VueLazyload, { // 内部自定义了一个指令lazy
loading, // 指定未加载得到图片之前的loading图片
})
2.在search组件的img标签中实行v-lazy指令
<router-link :to="`/detail/${goods.id}`" target="_blank"> <img v-lazy="goods.defaultImg" /> </router-link>
八,路由懒加载
路由懒加载
调用import函数把一次性打包的所有路由组件分开去打包
然后访问哪一个再去加载哪一个
(1) 当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,
然后当路由被访问的时候才加载对应组件,这样就更加高效了
(2) 本质就是Vue 的异步组件在路由组件上的应用
(3) 需要使用动态import语法, 也就是import()函数
(4) import('模块路径'): webpack会对被引入的模块单独打包
(5) 当第一次访问某个路径对应的组件时,此时才会调用import函数去加载对应的js打包文件
1. 理解
(1) 当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
(2) 本质就是Vue 的异步组件在路由组件上的应用
(3) 需要使用动态import语法, 也就是import()函数
2. 编码
// import Home from '@/pages/Home'
// import Search from '@/pages/Search'
// import Detail from '@/pages/Detail'
/*
1. import('模块路径'): webpack会对被引入的模块单独打包
2. 路由函数只在第一次请求时才执行, 也就是第一次请求访问对应路由路径时才会请求后台加载对应的js打包文件
*/
const Home = () => import('@/pages/Home')
const Search = () => import('@/pages/Search')
const Detail = () => import('@/pages/Detail')
没有使用路由懒加载时,是整体的组件打包
使用路由懒加载后,加载一个路由组件,打包一个路由组件, 0.js,是组件打包的文件,路由的懒加载组件按照顺序加载组件
vee-validate表单验证
注;
required: true,是必须的验证,
name="phone" 是每项的输入框的名称标识,
invalid类,非法的类名,不匹配这个验证规则,提示报错
class="error-msg" 报错的类,局部验证
协议选项需要打钩,需要自定义一个规则agree
# 1. 说明 vee-validate是专门用来做表单验证的vue插件 我们当前用的是2.x的版本, 最新的3.x版本使用比较麻烦 github地址: https://github.com/logaretm/vee-validate 内置校验规则: https://github.com/logaretm/vee-validate/tree/v2/src/rules 中文messages: https://github.com/logaretm/vee-validate/blob/v2/locale/zh_CN.js # 2. 使用 ## 1). 引入 下载: npm install -S vee-validate@2.2.15 引入插件: import Vue from 'vue' import VeeValidate from 'vee-validate' Vue.use(VeeValidate) ## 2). 基本使用 <input v-model="mobile" name="phone" v-validate="{required: true,regex: /^1\d{10}$/}" :class="{invalid: errors.has('phone')}"> <span class="error-msg">{{ errors.first('phone') }}</span> const success = await this.$validator.validateAll() // 对所有表单项进行验证 问题: 提示文本默认都是英文的 ## 3). 提示文本信息本地化 import VeeValidate from 'vee-validate' import zh_CN from 'vee-validate/dist/locale/zh_CN' // 引入中文message VeeValidate.Validator.localize('zh_CN', { messages: { ...zh_CN.messages, is: (field) => `${field}必须与密码相同` // 修改内置规则的message }, attributes: { // 给校验的field属性名映射中文名称 phone: '手机号', code: '验证码', } }) 完整中文message源码: https://github.com/logaretm/vee-validate/blob/v2/locale/zh_CN.js ## 4). 自定义验证规则 VeeValidate.Validator.extend('agree', { validate: value => { return value }, getMessage: field => field + '必须同意' })
1.在src新建一个文件,validate.js,
import Vue from 'vue'
import VeeValidate from 'vee-validate'
import zh_CN from 'vee-validate/dist/locale/zh_CN' // 引入中文message
Vue.use(VeeValidate)
VeeValidate.Validator.localize('zh_CN', {
messages: {
...zh_CN.messages,
is: (field) => `${field}必须与密码相同` // 修改内置规则的message
},
attributes: { // 给校验的field属性名映射中文名称
phone: '手机号',
code: '验证码',
password:'密码',
password2:'确认密码',
isCheck:'协议'
}
})
VeeValidate.Validator.extend('agree', {
validate: value => {
return value
},
getMessage: field => field + '必须同意'
})
在入口文件main.js引入该模块
在Register注册组件中,使用规则
<div class="content"> <label>手机号:</label> <input placeholder="请输入你的手机号" v-model="mobile" name="phone" v-validate="{ required: true, regex: /^1\d{10}$/ }" :class="{ invalid: errors.has('phone') }" /> <span class="error-msg">{{ errors.first("phone") }}</span> <!-- <input type="text" placeholder="请输入你的手机号" v-model="mobile" /> <span class="error-msg">错误提示信息</span> --> </div>
<div class="content"> <label>验证码:</label> <input placeholder="请输入验证码" v-model="code" name="code" v-validate="{ required: true, regex: /^\d{4}$/ }" :class="{ invalid: errors.has('code') }" /> <img ref="code" src="/api/user/passport/code" @click="changecode" alt="code" /> <span class="error-msg">{{ errors.first("code") }}</span> <!-- <input type="text" placeholder="请输入验证码" v-model="code" /> <img ref="code" src="/api/user/passport/code" @click="changecode" alt="code" /> <span class="error-msg">错误提示信息</span> --> </div>
<div class="content"> <label>登录密码:</label> <input type="text" placeholder="请输入你的登录密码" v-model="password" name="password" v-validate="{ required: true, regex: /^1\d{10}$/ }" :class="{ invalid: errors.has('password') }" /> <span class="error-msg">{{ errors.first("password") }}</span> <!-- <input type="text" placeholder="请输入你的登录密码" v-model="password" /> <span class="error-msg">错误提示信息</span> --> </div>
确认密码要和密码一致
<div class="content"> <label>确认密码:</label> <input placeholder="请输入确认密码" v-model="password2" name="password2" v-validate="{ required: true, is: (password) }" :class="{ invalid: errors.has('password2') }" /> <span class="error-msg">{{ errors.first("password2") }}</span> <!-- <input type="text" placeholder="请输入确认密码" v-model="password2" /> <span class="error-msg">错误提示信息</span> v-model="isCheck" type="checkbox"--> </div>
<div class="controls"> <input type="checkbox" v-model="isCheck" name="isCheck"
//自定义的agree规则
v-validate="{ agree: true }" :class="{ invalid: errors.has('isCheck') }" /> <span>同意协议并注册《尚品汇用户协议》</span> <span class="error-msg">{{ errors.first("isCheck") }}</span> <!-- <input name="m1" type="checkbox" /> <span>同意协议并注册《尚品汇用户协议》</span> <span class="error-msg">错误提示信息</span> --> </div>
,此时,点击注册按钮,需要统一验证
//点击完成注册,发送请求
async wczc() {
const success = await this.$validator.validateAll(); // 对所有表单项进行验证
if (success) {
let { mobile, password, code, password2 } = this;
try {
// if (mobile && code && password && password === password2) {
let userInfo = {
mobile,
password,
code,
};
await this.$store.dispatch("getreqRegister", userInfo);
alert("注册成功,跳转到登录");
this.$router.push("/login");
// }
} catch (error) {
alert(error);
}
}
},