1. 首图轮播图
1.1 引入子组件
1.1.1 导入子组件
import swiper from '../subComponents/swiper.vue';
1.1.2 新建components节点
components:{ swiper }
1.1.3 界面引入组件
在goodsInfo.vue中轮播图区域,class=mui-card-content-inner的div中加入
<swiper :lunbotuList="lunbotu"></swiper>
1.2 获取轮播图数据
定义方法获取轮播图数据
getLunBoTu(){ // 获取轮播图数据
this.$http.get("getlunbotu").then(response => {
//console.log("xxx = " + JSON.stringify(response.data.message));
if (response.data.status == '0'){
this.lunbotu = response.data.message;
} else { // 失败处理
Toast("加载轮播图失败!");
}
});
}
至此,商品详情轮播图完成,运行之后,发现,图片好丑。
1.3 解决轮播图宽度问题
我们首先来分析一下,为什么首页轮播图宽度没问题,但是到了商品详情中宽度会出现问题了?
1. 首页中,图片的宽和高都是100%,在商品详情中,如果宽高也是100%的话,就不好看了
2. 商品详情轮播图,期望高度是100%宽度自适应
3. 所以问题的关键原因是宽度是否自适应问题。
4. 所以我们可以定义一个属性,谁调用谁制定宽度。
1.3.1 在swiper.vue中的props中定义isFull属性
props:[ "lunbotuList","isFull" ]
1.3.2 在图片上绑定样式class
<img :src="item.img" :class="{'full':isFull}">
1.3.3 定义full类的样式
.full {
width:100%;
}
注意:定义full样式之后,需要将之前的img标签的宽度样式注释掉
1.3.4 在调用者身上加上属性绑定设置相应的isFull的值
<swiper :lunbotuList="lunbotu" isFull="false"></swiper>
至此,宽度问题解决了,我们又发现一个问题,图片宽度不够的时候,没有居中显示
1.4 解决图片不居中的问题
我们发现图片在.mint-swipe-item类下,所以我们只需要在该类上加一个样式即可
text-align:center;
2. 绘制商品购买区的样式
2.1 界面结构
<!-- 数据区域 -->
<div class="mui-card">
<div class="mui-card-header">商品标题</div>
<div class="mui-card-content">
<div class="mui-card-content-inner">
<p>
市场价:<del>¥2499</del> 销售价:<span class="now_price">¥1999</span>
</p>
<p>
购买数量:
</p>
<p>
<mt-button type="primary" size="small">立即购买</mt-button>
<mt-button type="danger" size="small">加入购物车</mt-button>
</p>
</div>
</div>
</div>
2.2 优化购买数量
上面的结构,我们给购买数量预留了位置,这里用于显示购买数量组件
2.2.1 引入xample
打开numbox.html,选择合适的numbox样式
<div class="mui-numbox" data-numbox-min='1' data-numbox-max='9'>
<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
<input id="test" class="mui-input-numbox" type="number" value="5" />
<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
</div>
2.2.2 新建goodsinfo_numbox.vue模板
新建购买数量模板,并将numbox粘贴进去
<template>
<div class="mui-numbox" data-numbox-min='1'>
<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
<input id="test" class="mui-input-numbox" type="number" value="1" ref="numbox" />
<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
</div>
</template>
2.2.3 在goodsInfo.vue中引入numbox组件
1. 导入组件
import goodsinfo_numbox from '../subComponents/goodsinfo_numbox.vue';
2. 添加节点
components:{ swiper,goodsinfo_numbox }
3. 界面引用
<p>
购买数量:<goodsinfo_numbox></goodsinfo_numbox>
</p>
刷新界面,点击加减无反应,原因是没有初始化numbox组件
2.2.4 初始化numbox组件
通过查看官方文档,我们知道初始化numbox组件,可以在mounted中调用
// 初始化数字选择框
mui('.mui-numbox').numbox();
因为这里用到了mui,所以,我们需要引入mui
import mui from "../../lib/mui/js/mui.min.js";
至此,numbox组件完成。
3. 绘制商品详情区域的样式
3.1 界面结构
<div class="mui-card">
<div class="mui-card-header">商品参数</div>
<div class="mui-card-content">
<div class="mui-card-content-inner">
<p>商品货号:1001</p>
<p>库存情况:1000</p>
<p>上架时间:2019-09-03</p>
</div>
</div>
<div class="mui-card-footer">
<mt-button type="primary" size="large">图文介绍</mt-button>
<mt-button type="danger" size="large">商品评论</mt-button>
</div>
</div>
3.2 样式
.now_price {
color:red;
font-size: 16px;
font-weight: bold;
}
.mui-card-footer {
display:block;
button {
margin:10px 0;
}
}
注意:第二个样式是因为.mui-card-footer类中用的是flex布局,我们需要换行,所以将布局改成了block
4. 使用编程式导航实现详情介绍与商品评论跳转
4.1 实现图文介绍
4.1.1 新建详情组件
<template>
<div class="goodsInfo-container">
商品详情---{{id}}
</div>
</template>
<script>
export default {
data(){
return {
id:this.$route.params.id
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
</style>
4.1.2 在route.js中配置
// 导入商品详情描述组件
import goodsdesc from './components/goods/goodsdesc.vue';
{path:'/home/goodsdesc/:id',component:goodsdesc,name:'goodsdesc'}
4.1.3 导航式跳转
toDesc(){
this.$router.push({name:'goodsdesc',params: { id: this.id }});
},
注意:
1. toDesc是图文介绍的点击事件
2. name为路由跳转名称
3. params对象是参数
4.2 实现商品评论跳转
4.2.1 新建商品评论组件
<template>
<div>
商品评论---{{id}}<br/>
</div>
</template>
<script>
export default {
data(){
return {
id:this.$route.params.id
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
</style>
4.2.2 配置route
// 导入商品评论组件
import goodscomments from './components/goods/goodscomment.vue';
{path:'/home/goodscomments/:id',component:goodscomments,name:'goodscomments'} // 商品评论
4.2.3 实现导航式跳转
toComment(){
this.$router.push({name:'goodscomments',params:{id:this.id}});
}
4.2.4 在商品评论组件中添加评论组件
在商品详情评论组件中
1. 导入评论组件
import comments from '../subComponents/comment.vue';
2. 引入组件
components:{
comments
}
3. 标签的形式引入评论组件
<comments :id="id"></comments>
5. 实现购物车效果
5.1 实现小球动画效果
5.1.1 定义小球div
<div class="ball"></div>
5.1.2 定义小球样式
.ball {
width: 15px;
height: 15px;
border-radius: 50%;
background-color:red;
top:390px;
left:148px;
position:absolute;
z-index: 999;
}
5.1.3 控制小球的显示与隐藏
我们在data中定义一个Boolean的变量,用于控制小球的显示与隐藏
ballFlag:false // 控制小球的隐藏和显示的标识
将变量添加到小球div上
<div class="ball" v-show="ballFlag"></div>
5.1.4 给小球加动画效果
因为加购物车的动画属于半场动画,所以用钩子函数来实现比较恰当
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div class="ball" v-show="ballFlag"></div>
</transition>
5.1.5 定义动画钩子函数
1. 定义动画开始之前的钩子
beforeEnter(el){
// 定义动画开始时候的位置
el.style.transform = "translate(0,0)";
},
2. 定义动画开始之后的钩子
enter(el,done){
// 设置显示动画
el.offsetWidth;
// 定义动画终点位置
el.style.transform = "translate(80px, 416px)";
// 定义过度的动效 1s 表示1秒 ease表示easy简单的意思
//el.style.transition = 'all 1s ease';
// 换一个动画效果 (https://cubic-bezier.com/)
el.style.transition = 'all 1s cubic-bezier(.4,-0.3,1,.68)';
// done代表afterEnter还未引用
done();
}
注意:
2.1 动画过度效果的定义方式easy表示简单的效果
2.2 我们要实现一些更加炫酷的效果可以使用bezier工具来实现,网址是https://cubic-bezier.com/
3. 定义动画结束钩子
afterEnter(el){
this.ballFlag = !this.ballFlag;
}
5.1.6 解决小球动画问题
当我们改动画面大小的时候,小球的动画消失点会出现改变,出现这个问题的原因是我们在代码将位移的代码写死了,所以只要分辨率或者滚动画面之后就会和测试的时候不一样,所以,我们不能将位移横纵坐标写死,应该动态的计算坐标值,怎么计算了,我们可以根据徽标的横纵坐标与小球的横纵坐标求差,得到位移的距离,现在问题来了,怎么得到徽标和小球的横纵坐标了,可以使用domObject.getBoundingClientRect()来计算某个dom对象与顶部和底部的距离,该方法返回一个矩形的对象,包含四个属性,left、top、right和bottom,分别表示元素各边与页面上边和左边的距离。
注意:
1. getBoundingClientRect是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。
2. 该函数返回一个Object对象,该对象有6个属性:top,lef,right,bottom,width,height;
3. top、left和css中的理解很相似,分别表示元素左上顶点到页面最左边与最上面的距离。
4. width、height是元素自身的宽高
5. right是指元素右边界距窗口最左边的距离
6. bottom是指元素下边界距窗口最上面的距离
示例:
// 获取元素
var stove=document.getElementById('stove');
// 元素上边距离页面上边的距离
alert(stove.getBoundingClientRect().top);
// 元素右边距离页面左边的距离
alert(stove.getBoundingClientRect().right);
// 元素下边距离页面上边的距离
alert(stove.getBoundingClientRect().bottom);
// 元素左边距离页面左边的距离
alert(stove.getBoundingClientRect().left);
有了上面的知识储备,我们现在来解决小球动画问题。
首先,小球属于当前组件的,所以我们在组件内部可以直接通过ref属性来获取小球
<div class="ball" v-show="ballFlag" ref="ball"></div>
然后在下面,通过this.$refs.ball来获取小球对象。
注意:this.$refs只能获取组件内部的元素
那么购物车的位置了,怎么获取了,购物车不属于商品详情组件,所以我们不能通过this.$refs来获取,又因为购物车属于父组件的内容,所以这里又回到了父子组件传值的问题了,但是,我们只是想很简单的获取到购物车的位置,通过父组件向子组件传值稍显麻烦,我们可以操作DOM来获取购物车的位置。
在enter方法中执行如下操作获取位置
// 获取小球在页面中的位置
const ballPosition = this.$refs.ball.getBoundingClientRect();
// 获取购物车徽标的位置
const badgePosition = document.getElementById("badge").getBoundingClientRect();
// 获取左边距离
const xDist = badgePosition.left - ballPosition.left;
const yDist = badgePosition.top - ballPosition.top;
// 定义动画终点位置 使用ES6的模板字符串语法
el.style.transform = `translate(${xDist}px, ${yDist}px)`;
注意:最后定义动画的终点位置,我们使用了ES6的木模板字符串的语法。
5.1.7 购物车中购买数量的改变
我们希望实现的效果是,当我们点击购物车的时候,能够将numberbox中的数值传递给购物车,但是,按钮属于goodsInfo组件,数值属于numberbox组件,购物车又属于APP组件,这里涉及到了嵌套父子组件了,所以,无法直接在goodsinfo页面中获取选中的商品数量值。
那么,我们该怎么办了?我们知道子组件向父组件传值是通过事件调用机制来实现的,事件调用的本质是父组件向子组件传递方法,子组件调用这个方法,同时把数据当做参数传递给这个方法。所以,我们可以在goodsInfo组件中定义一个方法getSelectCount,该方法又子组件调用,所以子组件肯定会传递一个参数过来,而且,我们需要预先规定这个值的传递方式。
我们将方法传递给子组件之后,最后,我们需要在子组件中定义一下方法,通过this.$emit来出发这个事件,但是,子组件什么时候将数据传递给父组件了?我们希望不管是点击加号还是减号,都能将值传递给父组件
所以,我们需要为加减号分别绑定一个点击事件,同时我们修改文本框的值的时候,也需要一个事件,所以操作这个数据需要绑定三个事件,有点麻烦,我们经过分析,知道不管是哪个事件,最终的结果都是文本框的值发生了变化,所以,我们可以只给文本框一个onchange事件即可。
操作步骤:
1. 在data区域定义一个变量selectCount用于接收子组件传递过来的值,默认值为1,表示进入页面,默认让用户购买1个商品
selectCount:1,
2. 在购买数量组件numbox上绑定函数getCount,用于让子组件来调用的,其值为一个函数名
<p>
购买数量:<goodsinfo_numbox @getCount="getSelectCount"></goodsinfo_numbox>
</p>
3. 然后定义函数getSelectCount
getSelectCount(count) {
// 当子组件把选中的数量,传递给父组件的时候,将选中的值保存到data上
this.selectCount = count;
}
4. 在子组件numberBox组件中定义change事件
<input id="test" class="mui-input-numbox" type="number" value="1" @change="countChanged" />
我们需要在change事件函数中获取到value的值,怎么获取了?我们可以给input标签一个ref属性,通过ref拿到一个原生的DOM,然后在获取值
<input id="test" class="mui-input-numbox" type="number" value="1" ref="numbox"
@change="countChanged" />
5. 定义change事件函数,并通过emit调用父组件的方法
countChanged(){
this.$emit('getCount',parseInt(this.$refs.numbox.value));
}
注意:
5.1. 每当文本框的数据被修改的时候,立即把最新的数据通过事件调用传递给父组件
5.2 通过emit来调用父组件的事件,将值传递给父组件
5.3 this.$emit函数,第一个参数就是父组件的事件名,第二个参数是子组件向父组件传递的值
5.1.8 使用JS的API来设置numbox的最大值
现在我们默认的numbox的最大值为9,而我们实际的库存量是60,怎么将numbox的最大值设为60了,60在父组件,numbox是子组件,所以这里又涉及到了子组件向父组件传值,用属性绑定
操作步骤:
1. 在购买数量上绑定一个属性max,其值为最大值
<p>
购买数量:<goodsinfo_numbox @getCount="getSelectCount" :max="getGoodsInfo.stock_quantity"></goodsinfo_numbox>
</p>
2. 在numberbox组件中通过props属性数组拿到父组件的值
props:['max']
3. 将max的值赋给numbox
<div class="mui-numbox" data-numbox-min='1' :data-numbox-max='max'>
<button class="mui-btn mui-btn-numbox-minus" type="button">-</button>
<input id="test" class="mui-input-numbox" type="number" value="1" ref="numbox"
@change="countChanged" />
<button class="mui-btn mui-btn-numbox-plus" type="button">+</button>
</div>
运行结果,我们发现,这里并没有真正的控制住,我们打印一下max的值,发现其为undefined,这是为什么呢?我们逐层分析,首先,子组件的max来自于哪里,来自于props属性数组,props中的max来自于父组件的getGoodsInfo.stock_quantity,getGoodsInfo.stock_quantity又来自于getGoodsInfo,getGoodsInfo又是来自于data域,data域中的getGoodsInfo又来自于$.http.get(url).then,看到这里,我们大概明白了问题可能出在.then是一个异步操作的原因,所以导致numberbox组件已经被渲染了,但是我们的getGoodsInfo的值还没拿到,但是不管多慢,总会在某一个时刻,会拿到getGoodsInfo.stock_quantity的值给max,所以我们可以使用watch属性监听,来监听父组件传递过来的max的值,不管watch会被触发多少次,但是最后一次肯定是一个合法的max值。
代码如下:
首先删除掉标签中的属性绑定 :data-numbox-max='max'
在与methods平级的地方定义watch监听器,然后监听max值的改变
watch:{
'max':function(newVal,oldVal){
mui(".mui-numbox").numbox().setOption('max',newVal);
}
}