共计 6870 个字符,预计需要花费 18 分钟才能阅读完成。
这篇我们来实现商品详情页面:
在首页点击某一商品会展示商品的详细信息。如下图所示:
创建 good 组件
创建一个组件 good
- transition 来定义页面展示的形式;
- 使用 v -show 与变量 showFlag 来控制显示与隐藏,ref 配合 BScroll 控制页面的滑动;
-
现在结构内出现的数据的引用我们在 script 内进行处理。
<template>
<transition name=”food-detail”><div class="food" v-show="showFlag" ref="foodView"> <div class="food-wrapper"> <div class="food-content"> <div class="img-wrapper"> <img class="food-img" :src="food.picture"> <span class="close-bt icon-close" @click="closeView"></span> <img class="share-bt" src="./share.png"> <img class="more-bt" src="./more.png"> </div> <div class="content-wrapper"> <h3 class="name">{{food.name}}</h3> <p class="saled">{{food.month_saled_content}}</p> <img class="product" v-show="food.product_label_picture" :src="food.product_label_picture" > <p class="price"> <span class="text">¥{{food.min_price}}</span> <span class="unit">/{{food.unit}}</span> </p> <div class="cartcontrol-wrapper"> <Cartcontrol :food="food"></Cartcontrol> </div> <div class="buy" v-show="!food.count || food.count==0" @click="addFirst"> 选规格 </div> </div> </div> <div class="rating-wrapper"> <div class="rating-title"> <div class="like-ratio" v-if="food.rating"> <span class="title">{{food.rating.title}}</span> <span class="ratio"> ({{food.rating.like_ratio_desc}} <i>{{food.rating.like_ratio}}</i> ) </span> </div> <div class="snd-title" v-if="food.rating"> <span class="text">{{food.rating.snd_title}}</span> <span class="icon icon-keyboard_arrow_right"></span> </div> </div> <ul class="rating-content" v-if="food.rating"> <li v-for="comment in food.rating.comment_list" class="comment-item"> <div class="comment-header"> <img :src="comment.user_icon" v-if="comment.user_icon"> <img src="./anonymity.png" v-if="!comment.user_icon"> </div> <div class="comment-main"> <div class="user">{{comment.user_name}}</div> <div class="time">{{comment.comment_time}}</div> <div class="content">{{comment.comment_content}}</div> </div> </li> </ul> </div> </div> </div>
</transition>
</template>
处理数据,行为。
- 通过 data 监听 showFlag 的状态;
- 通过 props 传入父组件循环 Foods 而得到的 food 数据;
- 方法 showView()可以被父组件调用,稍后说明调用形式;
- 方法 closeView()绑定在模板退出图标上,用于关闭页面,重置变量 showFlag 为 false;
-
方法 addFirst()用来添加一个商品数量;
<script>
// 导入 BScroll
import BScroll from “better-scroll”;
// 导入 Cartcontrol
import Cartcontrol from “components/Cartcontrol/Cartcontrol”;
// 导入 Vue
import Vue from “vue”;export default {
data() {return {showFlag: false};
},
props: {food: {type: Object}
},
methods: {// showView 方法可以被父组件调用 showView() { this.showFlag = true; // BScroll 初始化 this.$nextTick(() => {if (!this.scroll) { this.scroll = new BScroll(this.$refs.foodView, {click: true}); } else {this.scroll.refresh(); } }); }, closeView() {this.showFlag = false;}, addFirst() {Vue.set(this.food, "count", 1); }
},
components: {Cartcontrol, BScroll
}
};
</script>
food 组件样式
<style>
.food {
position: fixed;
left: 0;
top: 0;
bottom: 51px;
background: white;
width: 100%;
z-index: 90;
}
.food-detail-enter-active,
.food-detail-leave-active {transition: 1s;}
.food-detail-enter,
.food-detail-leave-to {transform: translateX(100%);
}
.food .food-wrapper .food-content .img-wrapper {
position: relative;
width: 100%;
height: 0;
/* 高度如何撑开?在定位中,使用 padding-top 或 padding-bottom 设置为 100%,其实盒子高度会根据盒子的宽度进行计算
*/
padding-top: 100%;
}
.food .food-wrapper .food-content .img-wrapper .food-img {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.food .food-wrapper .food-content .img-wrapper .close-bt {
width: 30px;
height: 30px;
position: absolute;
left: 10px;
top: 10px;
text-align: center;
font-size: 30px;
color: white;
background: #7f7f7f;
border-radius: 50%;
}
.food .food-wrapper .food-content .img-wrapper .share-bt,
.food .food-wrapper .food-content .img-wrapper .more-bt {
width: 30px;
height: 30px;
position: absolute;
top: 10px;
}
.food .food-wrapper .food-content .img-wrapper .share-bt {right: 50px;}
.food .food-wrapper .food-content .img-wrapper .more-bt {right: 10px;}
.food .food-wrapper .food-content .content-wrapper {
padding: 0 0 16px 16px;
position: relative;
}
.food .food-wrapper .food-content .content-wrapper .name {
font-size: 15px;
color: #333333;
line-height: 30px;
font-weight: bold;
}
.food .food-wrapper .food-content .content-wrapper .saled {
font-size: 11px;
color: #9d9d9d;
margin-bottom: 6px;
}
.food .food-wrapper .food-content .content-wrapper .product {
height: 15px;
margin-bottom: 16px;
}
.food .food-wrapper .food-content .content-wrapper .price {font-size: 0;}
.food .food-wrapper .food-content .content-wrapper .price .text {
font-size: 17px;
color: #fb4e44;
}
.food .food-wrapper .food-content .content-wrapper .price .unit {
font-size: 11px;
color: #9d9d9d;
}
.food .food-wrapper .food-content .cartcontrol-wrapper {
position: absolute;
right: 12px;
bottom: 10px;
padding: 2px;
}
.food .food-wrapper .food-content .buy {
width: 64px;
height: 30px;
font-size: 12px;
line-height: 30px;
text-align: center;
background: #ffd161;
border-radius: 30px;
position: absolute;
right: 12px;
bottom: 10px;
}
.food .food-wrapper .rating-wrapper {padding-left: 16px;}
.food .food-wrapper .rating-wrapper .rating-title {padding: 16px 16px 16px 0;}
.food .food-wrapper .rating-wrapper .rating-title .like-ratio {
float: left;
font-size: 0;
}
.food .food-wrapper .rating-wrapper .rating-title .like-ratio .title {font-size: 13px;}
.food .food-wrapper .rating-wrapper .rating-title .like-ratio .ratio {font-size: 11px;}
.food .food-wrapper .rating-wrapper .rating-title .like-ratio .ratio i {
color: #fb4e44;
font-size: 11px;
}
.food .food-wrapper .rating-wrapper .rating-title .snd-title {
float: right;
font-size: 0;
}
.food .food-wrapper .rating-wrapper .rating-title .snd-title .text,
.food .food-wrapper .rating-wrapper .rating-title .snd-title .icon {
font-size: 13px;
color: #9d9d9d;
display: inline-block;
}
.food .food-wrapper .rating-wrapper .rating-title .snd-title .icon {margin-left: 12px;}
.food .food-wrapper .rating-wrapper .comment-item {
padding: 15px 14px 17px 0;
border-bottom: 1px solid #f4f4f4;
width: 100%;
box-sizing: border-box;
display: flex;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-header {
flex: 0 0 41px;
margin-right: 10px;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-header img {
width: 41px;
height: 41px;
border-radius: 50%;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-main {
flex: 1;
margin-top: 6px;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-main .user {
width: 50%;
float: left;
font-size: 12px;
color: #333333;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-main .time {
width: 50%;
float: right;
text-align: right;
font-size: 10px;
color: #666666;
}
.food .food-wrapper .rating-wrapper .comment-item .comment-main .content {
margin-top: 17px;
font-size: 13px;
line-height: 19px;
}
</style>
在 foods 父组件内引用 food 组件
父组件中,selectedFood 变量 默认没有选中任何商品
当我们通过 showDetail() 点击商品时候将所选择的 food 传入 selectedFood;
通过父组件模板中 ref=”foodView 与 script 标签内 this.$refs.foodView.showView(),调用子组件的方法显示详情页面。
注意变量名称不要与事件名称重复。方法名称与 data 一样的话,都可以通过 this 访问,会有覆盖风险。所以不要重复。
<template>
<!--foods 组件其他内容 -->
<!-- 商品详情 -->
<Food :food="selectedFood" ref="foodView"></Food>
</template>
<script>
// 导入 Food
import Food from "components/Food/Food";
// 导入 Cartcontrol
import Cartcontrol from "components/Cartcontrol/Cartcontrol";
export default {data() {
return {selectedFood: {}
};
},
methods:{showDetail(food) {
// 传值
this.selectedFood = food;
// 显示详情页
this.$refs.foodView.showView();}
}
}
</script>
总结
我们先创建了 good 组件,定义了所依赖的数据,方法,
注意子组件的方法可以被父组件调用,我们了解父组件中调用子组件中的方法的方式。但是子组件不可以调用父组件的方法。
然后我们在父组件 foods 中引用了子组件。有个细节 data 中的变量名不要与方法名字重复,避免被 this.xx 访问到,从而覆盖掉,产生错误。我们也了解父组件调用子组件方法的方式。
如上图所示,我们实现了商品详情页。
下一篇我们来实现商品详情页面中的评价列表。