vue.js三十九—— 项目实战5商品详情页面改造

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>&nbsp;&nbsp;&nbsp;销售价:<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);
    }
  }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Github上有很多可以学习的Vue实战项目。其中一些比较受欢迎的项目包括: 1. Vue.js HackerNews:这是一个基于Vue.js的HackerNews客户端,可以帮助你学习Vue.js的基本用法和组件化开发。 2. Vue.js TodoMVC:这是一个使用Vue.js实现的经典Todo应用程序,可以帮助你学习Vue.js的状态管理和组件通信。 3. Vue.js Shopping Cart:这是一个使用Vue.jsVuex实现的购物车应用程序,可以帮助你学习Vue.js的状态管理和数据流。 4. Vue.js Weather App:这是一个使用Vue.js和第三方API实现的天气应用程序,可以帮助你学习Vue.js的异步请求和数据展示。 5. Vue.js Blog:这是一个使用Vue.jsVue Router实现的博客应用程序,可以帮助你学习Vue.js的路由和页面导航。 你可以根据自己的实际情况选择适合你的项目进行学习。如果你对Vue.js还不太熟悉,可以先学习一些基础知识,比如Vue的基本语法、组件化开发和状态管理。你可以参考我最近开源的springboot-guide项目,该项目提供了一些关于Spring Boot的学习资源,可以帮助你入门。\[2\] #### 引用[.reference_title] - *1* *3* [Vue项目实战——实现GitHub搜索案例(学以致用,两小时带你巩固和强化Vue知识点)](https://blog.csdn.net/qq_45902692/article/details/126751629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Github 上热门的 Spring Boot 项目实战推荐](https://blog.csdn.net/hollis_chuang/article/details/102597814)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值