一、分页器
(一)理解分页器
什么时候使用分页器?
当大量的数据不能同时展示到同一个页面时
做分页器需要哪些数据?
总数(total)、页码(pageNo)、页大小(pageSize)、连续页数(continues)、总页数(totalPage)=total/pageSize(注意要向上取整)
(二)分页器的静态页面
1.copy简单的分页器页面的静态组件,创建Pagination组件,记得在main.js中引入并注册Pagination组件
import Pagination from './components/Pagination'
//全局注册
Vue.component('Pagination',Pagination)
2.去Search组件指定位置,直接编写组件标签<Pagination/>
(三)实现分页器功能
<!-- 分页器 -->
<Pagination
:total="total"
:pageSize="searchParams.pageSize"
:pageNo="searchParams.pageNo"
:continues="5"
:sendPageNo="changePageNo"
/>
Pagination组件中:
<template>
<div class="pagination" v-show="total">
<div>
<button :disabled="pageNo === 1" @click="sendPageNo(pageNo-1)">上一页</button>
<button v-show="startEnd.start !== 1" @click="sendPageNo(1)">1</button>
<span v-show="startEnd.start > 2">...</span>
<button
v-for="(number,index) in (startEnd.end - startEnd.start + 1)"
:key="index"
:class="{active:(index + startEnd.start) === pageNo}"
@click="sendPageNo(index + startEnd.start)"
>
{{index + startEnd.start}}
</button>
<span v-show="startEnd.end < totalPage - 1">...</span>
<button v-show="startEnd.end !== totalPage" @click="sendPageNo(totalPage)">{{totalPage}}</button>
<button :disabled="pageNo === totalPage" @click="sendPageNo(pageNo+1)">下一页</button>
<span>共 {{total}} 条</span>
</div>
</div>
</template>
<script>
export default {
name: "Pagination",
props:["total", "pageSize", "pageNo", "continues","sendPageNo"],
computed:{
totalPage(){
return Math.ceil(this.total / this.pageSize)
},
startEnd(){
let start = 0
let end = 0
const {pageNo,continues,totalPage} = this
// 特殊情况一
if (continues > totalPage) {
start = 1;
end = totalPage
}else{
// 开始计算
start = pageNo - (continues-1) / 2
end = pageNo + (continues-1) / 2
}
// 特殊情况二 左边推多了
if (start<1) {
start = 1
end = continues
}
// 特殊情况三 右边推多了
if (end>totalPage) {
start = totalPage - continues + 1
end = totalPage
}
return {start,end}
}
}
};
</script>
二、商品排序
1.明确实现两种排序:综合排序、价格排序
2.依赖的是数据中的order属性,属性值格式为 1:desc
1是综合排序,2是价格排序
asc是升序(箭头往上指),desc是降序(箭头往下指)
3.思路:谁有红色背景,看order的第一位;谁有箭头,看order的第一位;箭头是上还是下,看order的第二位
三、商品详情页面
(一)静态组件
ctrlCV=>Detail静态组件到页面当中;
到routes.js文件中配置路由,注意别忘了带上id!
import Detail from '@/pages/Detail'
{
name:'detail',
path:'/detail:id',
component:Detail
}
(二)获取数据
先用路由配置项scrollBehavior来解决跳转Detail组件时出现的一个滚动条问题
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
scrollBehavior函数:每次路由跳转时都会执行
to:要跳到哪个路由
from:来自哪个路由
savedPosition:跳转前滚动条的位置
编写api/index.js文件,写相对应的接口函数
//该函数专门用于:获取商品详情,注意:此接口需要参数
export const reqGoodDetailInfo = (id) => ajax.get('/item/'+id)
将详细信息存入data中
data() {
return {
goodNum: 1,
info: {
categoryView: {},
price: {},
skuInfo: {},
spuSaleAttrList: [],
},
};
},
引入reqGoodDetailInfo
import { reqGoodDetailInfo } from "@/api";
Detail组件一挂载就获取商品详情
![](https://i-blog.csdnimg.cn/blog_migrate/a22646335fee9b03203896a9a3dd1e89.png)
写一个getGoodDetailInfo方法回调,便于获取商品详情的数据
![](https://i-blog.csdnimg.cn/blog_migrate/83102869f952df288e2d40c2f8a5431f.png)
(三)放大镜效果呈现
在npm网页里搜索vue-photo-zoom-pro,在控制台输入npmivue-photo-zoom-pro@2.x,引入放大镜库。
编写zoom组件中的代码。
![](https://i-blog.csdnimg.cn/blog_migrate/0a273494d120b6bc5b7b10762f8bd9d5.png)
到父组件当中,编写代码,使其动态获取数据
<Zoom :imgurl="info.skuInfo.skuDefaultImg"/>
(四)商品图片组
Detail组件给ImageList传递图片数组
![](https://i-blog.csdnimg.cn/blog_migrate/8ac3c54c4993285db0c2da4dbfea65e6.png)
ImageList接收skuDefaultImg
![](https://i-blog.csdnimg.cn/blog_migrate/1b0f9b62a3331ab5ba56c5e837e077b8.png)
到ImageList组件中使用轮播图展示数据(一定要将id改为ref,否则轮播效果不实现)
![](https://i-blog.csdnimg.cn/blog_migrate/44b03012305548a616d7410faaf74977.png)
绑定watch中的监听事件——$nextTick来实现轮播效果,要记得引入Swiper和它的样式
![](https://i-blog.csdnimg.cn/blog_migrate/a88a6881a2b0e4b43612dcbd919064bd.png)
切换图片
在ImageList组件中点击图片,让放大镜区域实现。在ImageList组件中利用$bus(组件之间通信)传递点击图片的url给放大镜图片。
![](https://i-blog.csdnimg.cn/blog_migrate/6b550bf60332ec570c4f719da85c092c.png)
Zoom组件中:
![](https://i-blog.csdnimg.cn/blog_migrate/6b69c4c1416257a9ac45b0919c8bbdbb.png)
在Zoom组件中写个data用来存储后期的url,并用mounted来挂载,最后实现效果
![](https://i-blog.csdnimg.cn/blog_migrate/bed656959bc7285a0489f2b7c00dcc44.png)
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
(五)商品购买数量
经过分析,还有和京东等电商网站的对比学习,商品购买的最小数量是1,最大数量是200,要控制在这个范围内。
正则表达式计算商品购买数量
去Detail组件中定义
![](https://i-blog.csdnimg.cn/blog_migrate/b1be4848deeb293baf00f266b329912a.png)
data(){
return{
goodNum:1
}
},
methods:{
changeGoodNum(e){
console.log('输出的是:',e.target.value)
}
}
控制台输出:
![](https://i-blog.csdnimg.cn/blog_migrate/dce48bae1b8213a4b57aecf30adc1496.png)
创建utils/reg.js文件,以后项目当中用到的正则表达式在这里统一管理(别忘了引入)
export const goodNumReg = /^([1-9]|[1-9]\d|1\d{2}|200)$/
限制购买数量
完成changeGoodNum逻辑
![](https://i-blog.csdnimg.cn/blog_migrate/4b5ea142e1ee65de879e03548e417f48.png)
在+-按钮上添加点击事件,完善业务逻辑
![](https://i-blog.csdnimg.cn/blog_migrate/e01a93fd0753b7bb2047506dcb07b57f.png)
最终完成+-业务逻辑,记得做判断,不要低于最小值还有超过最大值
![](https://i-blog.csdnimg.cn/blog_migrate/e4b27cf93733c8e191e7a091f485ae71.png)
(六)添加商品到购物车
找到接口文档,写对应的函数
export const reqAddGood2Cart = (goodId,goodNum) => ajax.post(`cart/addToCart/${ goodId }/${ goodNum }`)
到Detail中找到加入购物车的位置,添加点击事件,在methodes中配置handleAddCart的回调,在此之前引入reqAddGood2Cart
<a @click="handleAddCart">加入购物车</a>
![](https://i-blog.csdnimg.cn/blog_migrate/089e03e71a9253d69ace2e55f5d6ed84.png)
四、AddCartSuccess页面
(一)静态页面
1.直接ctrlCV写好的页面
2.到routes.js文件中配置路由
import AddCartSuccess form '@/pages/AddCartSuccess'
{
name:'addcart_success',
path:'/addcart_success',
components:AddCartSuccess
}
3.回到Detail文件中写判断,完成跳转
![](https://i-blog.csdnimg.cn/blog_migrate/81e8973f4f7ae973bf2ee5fe8fefff23.png)
(二)收集商品属性并呈现到页面上
1.加入购物车后,要在页面添加商品属性,呈现在页面上,这是需要给handleAddCart方法追加逻辑
handleAddCart(){
//获取路由传过来的商品id
const {id} = this.$route.params
//获取组件辛苦维护的商品数据
const {goodNum} = this
//请求添加购物车
const result = reqAddGood2Cart(id,goodNum)
//判断业务逻辑是否成功
if(result.code === 200){
//第一步:获取商品属性
//获取商品名称和商品图片
const {skuName,skuDefaultImg} = this.info.skuInfo
//获取商品单价
const {price} = this.info
//获取商品的数量
const {goodNum} = this
//获取商品的所有属性,并形成一个数组
const arr = []
//遍历所有可选的属性,找到用户所选择的,收集到数组当中
this.info.spuSaleAttrList.forEach((s1) => {
const result = s1.spuSaleAttrValueList.find(s2=>s2.isChecked === '1')
arr.push(result.saleAttrName + ':' +result.saleAttrValueList)
})
// 准备一个对象,收集到所有对象,一会儿想办法传给addcart_success
const selectedInfo = {
name:skuName,
url:skuDefaultImg,
price,
goodNum,
arr
}
//第二步:将selectedGoodInfo存入sessionStorage
sessionStorage.setItem('selectedGoodInfo',JSON.stringfy(selectGoodInfo))
//第三步:跳转到/addcart_success,同时携带要呈现的信息
this.$router.push('/addcart_success')
}else{
alert(result.message)
}
}
2.到AddCartSuccess组件中编写代码呈现信息
![](https://i-blog.csdnimg.cn/blog_migrate/f65ecdb996f15de4a7c7d40774f2baca.png)
利用data返回定义selectedGoodInfo
data() {
return {
selectedGoodInfo:JSON.parse(sessionStorage.getItem('selectedGoodInfo'))
}
},
五、购物车ShopCart页面
(一)静态组件
还是ctrlCV静态组件到项目当中
到router/routes.js文件中配置路由,别忘了引入
import ShopCart from '@/pages/ShopCart'
{
name:'shopcart',
path:'/shopcart'
components:ShopCart
}
实现”我的购物车“和“去购物车结算”两个按钮的跳转功能
<router-link to="/shopcart">我的购物车</router-link>
<router-link to="/shopcart">去购物车结算</router-link>
(二)获取购物车数据
找到接口文档,开始编写接口文件
//该函数专门用于:获取购物车列表信息
export const reqShopCartList = () => ajax.get('/cart/cartList')
用mounted进行挂载获取购物车数据
mounted:{
//组件一挂载就获取购物车数据
this.getShopCartList()
}
编写methods方法发送请求
methods: {
// 获取购物车列表数据的函数
async getShopCartList(){
// 发请求,获取购物车数据
let result = await reqShopCartList()
console.log(result)
}
},
(三)实现用户临时标识
为什么不登陆就能看到购物车,为了引导用户消费,而有的网站必须要登录才能查看,是因为网站的用户量大,逻辑复杂;
当第一次发送ajax请求时,会为其生成一个临时标识,生成之后,要在本次请求头上携带这个标识,存入local当中,避免在关闭浏览器之后,丢失数据
utils中新建auth.js文件,该文件统一管理用户的身份验证,控制台npm i uuid(uuid是生成的随机字符串)
![](https://i-blog.csdnimg.cn/blog_migrate/26c4ce8634be6e3ce0e7b747f9c351f2.png)
ajax文件中引入getUserTempId,编写config,使得获得用户临时标识,Network里得到数据,如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/709b49c6a1c98d820d85af3f1d618555.png)
(四)购物车初始化列表
1.ShopCart组件一挂载就请求购物车数据,随后数据存入data,要将cartInfoList的初始值置为空。
import {reqShopCartList} from '@/api'
export default {
name: 'ShopCart',
data() {
return {
cartInfoList:[],
}
},
methods: {
// 获取购物车列表数据的函数
async getShopCartList(){
// 发请求,获取购物车数据
let result = await reqShopCartList()
if (result.code === 200) {
this.cartInfoList = result.data[0].cartInfoList
}else{
alert(result.message)
}
}
},
mounted() {
// 组件一挂载就调用getShopCartList,获取购物车数据
this.getShopCartList()
},
}
2.利用v-for遍历,将页面数据动态化
![](https://i-blog.csdnimg.cn/blog_migrate/32ec8bcfd402297cd638c47827cccb02.png)
(五)购物车勾选商品的状态
单选商品
找到接口文档,编写api接口
//该函数专门用于:获取商品单选时的状态
export const reqCheckOneCart = (skuId,isChecked) => ajax.get(`/cart/checkCart/${skuId}/${isChecked}`)
找到单选按钮,编写绑定事件handleCheckOne,并传递参数cartInfo、skuId
![](https://i-blog.csdnimg.cn/blog_migrate/3be78b56f8263e367af08490d83c0146.png)
到methods中编写获取购物车列表单选的回调函数
![](https://i-blog.csdnimg.cn/blog_migrate/2215288e6492174448fde111f9d7f83c.png)
记得在import中引入reqCheckOneCart,@click后面添加prevent,阻止默认行为,这里的单选行为需要联系服务器,要保持一致,要让所要实现的内容完全受到数据的控制。
全选
找到接口文档编写api接口
//该函数专门用于:获取商品全选时的状态
export const reqCheckAllCart = (idList,isChecked) => ajax.post(`/cart/batchCheckCart/${isChecked}`,idList)
找到全选按钮,编写绑定事件handleCheckAll,并传递参数cartInfoList,isAll
![](https://i-blog.csdnimg.cn/blog_migrate/b49ef48cd2d8843092b01dff1ebd1eeb.png)
到methods中编写获取购物车列表全选的回调函数
![](https://i-blog.csdnimg.cn/blog_migrate/61111ec1a8f0ac9e8d7d067337e330d0.png)
利用计算属性isAll的全选状态,当单选的值都为1时,全选功能生效,这里要用到数组的every方法
computed:{
return this.cartInfoList.every(cartInfo => cartInfo.isChecked === 1)
}
记得在import中引入reqCheckAllCart,@click后面添加prevent,阻止默认行为,这里的全选行为也需要联系服务器,要保持一致,要让所要实现的内容完全受到数据的控制。
补充:JavaScript数组中的every方法
作用:判断数组当中所有的元素是否都满足条件
方法特点:1.循环次数不等于数组长度
2.函数内部的return=>return true: 循环继续,当元素满足条件,继续判断,如果循环执 行完毕还是true,则every的返回值就是true
return false:循环结束,当前元素不满足条件,every的返回值也 是false
3.every方法的返回值=>return true:全部元素都满足条件
return false:有元素不满足条件
注意点:1.every()方法不会对空数组进行检测
2.every()方法不会改变原始数组
(六)购物车商品的总数与总金额
利用计算属性计算total和totalPrice
![](https://i-blog.csdnimg.cn/blog_migrate/639bc84bdf412e7ab64028a7dda79ea1.png)
(七)购物车删除商品
找到接口文档编写删除商品的api接口
//该函数专门用于:获取商品被删除时的状态
export const reqDeleteOneCart = (id) => ajax.delete(`/cart/deleteCart/${id}`)
到methods里编写handleDeleteOne的方法回调,先找到按钮绑定点击事件handleDeleteOne,传递参数cartInfo.skuId
![](https://i-blog.csdnimg.cn/blog_migrate/6fe139977dd318e624cee4521b5db5e6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5325e9c368233a7aa3b552fe8f8b2324.png)
补充:findindex方法
findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
findIndex() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 -1
注意: findIndex() 对于空数组,函数是不会执行的。
注意: findIndex() 并没有改变数组的原始值。
(八)数据为空的处理
every的坑,当数组里的内容为空时,控制台报true;空数组调用every,返回值永远为真;我们设置的初始值为空,every身后写什么已经是不重要了,所以返回为真,isAll就被勾选。
result.data[0]返回undefined,控制台会报错,所以需要再写得缜密一些,往外层包一个if判断。
![](https://i-blog.csdnimg.cn/blog_migrate/cc4c38273194a90f089af9a0c8d146fa.png)
写一个empty来展示购物车为空时的状态
![](https://i-blog.csdnimg.cn/blog_migrate/3517c129a06ea08f524121fa05a087bb.png)
实现效果如下:这样就能改善下,数组为空时,every返回true的问题了。
![](https://i-blog.csdnimg.cn/blog_migrate/aa19ae489653636f343e09fed1893c91.png)