//rating.js
const ALL = 2
export default {
data() {
return {
hideEmpty: false,
isActive: ALL
}
},
computed: {
},
methods: {
showItem(item) {
if (this.hideEmpty && item.text.length == 0) {
return false;
}
if (this.isActive === ALL) {
return true;
} else {
return item.rateType === this.isActive;
}
}
}
}
//Grade.vue
<script type="text/javascript">
import mixins from "mixins/rating.js";
export default {
mixins: [mixins],
...
}
<div class="products-detail-img">
<img :src="food.image" />
</div>
.products-detail-img {
position: relative;
width: 100%;
height: 0;
overflow: hidden;
padding-bottom: 100%; //比例自适应布局---正方形
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
为响应式对象添加一个属性,确保新属性也是响应式的,并且能够触发视图更新。
Vue 无法检测到对象属性的添加或删除,但是属性的更改不受影响,能够触发视图更新
// VButton.vue
export default {
props: {
food: Object
},
methods: {
plus(event) {
if (!this.food.count) {
// this.food.count = 1; 引用类型的属性赋值操作不会触发视图的更新,使用$set
this.$set(this.food, "count", 1);
} else {
this.food.count++; // 属性的更改不受影响,能够触发视图更新
}
},
minus(event) {
if (this.food.count > 0) {
this.food.count--;
}
}
}
};
-
一定要确保在得到数据且DOM渲染完成的情况下初始化better-scroll
-
better-scroll初始化的几个时机 mounted、mounted(nextTick)、 watch watch(nextTick)、 带条件的watch 、带条件的watch(nextTick)
-
当一个组件内使用了better-scroll时,每当操作引起该组件DOM的结构变化时,一定要调用better-scroll的refresh方法重新计算宽高,保证better-scroll的正确渲染
//ProductDetail.vue
export default {
...
methods: {
refresh() {
this.$nextTick(function() {
this.productDetail.refresh();
});
},
/* 当一个组件内使用了better-scroll时,每当操作引起该组件DOM的结构变化时,一定要调用better-scroll的refresh方法重新计算宽高,保证better-scroll的正确渲染*/
changeRatings(isActive) {
this.isActive = isActive;
this.refresh();
},
toggleEmptyComment() {
this.hideEmpty = !this.hideEmpty;
this.refresh();
},
showItem(item) {
if (this.hideEmpty && item.text.length == 0) {
return false;
}
if (this.isActive === ALL) {
return true;
} else {
return item.rateType === this.isActive;
}
this.refresh();
},
_initScroll() {
this.$nextTick(function() {
if (!this.productDetail) {
this.productDetail = new BScroll(this.$refs.detail, {
click: true
});
} else {
this.productDetail.refresh();
}
});
}
},
watch: {
isProductDetail() {
if (this.isProductDetail) {
/* 一定要确保在得到数据且DOM渲染完成的情况下初始化better-scroll */
this._initScroll();
}
}
}
};
过滤器可以在两种场景中使用:双花括号插值(mustache interpolation)和 v-bind 表达式
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
//ProductDetail.vue
<template>
<div class="time">{{ item.rateTime | formatDate}}</div>
</template>
<script type="text/javascript">
import { format } from 'date-fns'
export default {
...
methods: {
...
/* date-fns + 过滤器 */
filters: {
formatDate(time) {
return format(time, 'yyyy-MM-dd HH:mm:ss')
}
}
...
};
</script>
Product组件内频繁的对goods对象进行操作,有别于传统开发模式,在VUE中更关注数据的操作。
首先,Button组件为操作数据的最小和核心单位,因为组件之间的相互独立的,所以在初始化goods的时候,不要通过遍历的方式在其中添加count属性,而是通过组件独立的特性来判断传入的food是否有count属性,然后Button此组件中初始化count属性。并且所有组件的数据的更新都靠此组件count的数据变化来驱动
其次,利用computed特性,根据count属性的变化响应式更新选择列表selectFoods。将该数据作为动态属性传递给ShopCart组件
//Product.vue
computed: {
selectFoods() {
let foods = [];
this.goods.forEach(good => {
good.foods.forEach(food => {
if (food.count) {
foods.push(food);
}
});
});
return foods;
}
},
ShopCart组件通过对selectFoods的监听和计算来来更新视图,这样就形成了对Button组件count属性的更改导致selectFoods变化,通过selectFoods的监听更新视图的数据驱动策略。
<script type="text/javascript">
import ShopDetail from "./pages/ShopDetail";
export default {
props: {
seller: Object,
selectFoods: Array,
isAllShow: Boolean
},
components: {
ShopDetail
},
methods: {
...
clearAll(data) {
if (data) {
this.selectFoods.forEach(food => {
food.count = 0;
});
this.$store.commit("toggleShow", false);
}
this.isConfirmShow = false;
}
},
computed: {
totalPrice() {
let total = 0;
this.selectFoods.forEach(food => {
total += food.price * food.count;
});
return total;
},
totalCount() {
let count = 0;
this.selectFoods.forEach(food => {
count += food.count;
});
return count;
},
cartContent() {
let diff = this.seller.minPrice - this.totalPrice;
if (this.totalPrice === 0) {
this.isActive = false;
return `¥${this.seller.minPrice}元起送`;
} else if (diff > 0) {
this.isActive = false;
return `还差¥${diff}元起送`;
} else {
this.isActive = true;
return `结算`;
}
}
},
watch: {
totalCount() {
if (!this.totalCount) {
this.$emit("toggle-show", true);
}
}
}
};
</script>
相比传统开发最大的优势在于告别了对原始入口数据频繁更改,通过MVVM双向数据绑定、computed属性响应式的对视图更新
div的css优先级远大于动画中css属性的优先级,加上!important后,过渡才会生
// ShopCart.vue
<template>
<transition name="move">
<div class="detail-wrapper" v-show="isAllShow">
<shop-detail
:selectFoods="selectFoods"
:isAllShow="isAllShow"
@confirm-show="confirmShow"
></shop-detail>
</div>
</transition>
</template>
<style lang="scss">
.move-enter,
.move-leave-to {
transform: translateY(0) !important; //div的css优先级远大于动画中css属性的优先级,加上!important后,过渡才会生效
}
.move-enter-active,
.move-leave-active {
transition: all 1s ease;
}
.detail-wrapper {
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -1;
transform: translateY(-100%); //div的css优先级远大于动画中css属性的优先级
}
</style>
<div class="left">定宽</div>
<div class="right">定宽</div>
<div class="middle">自适应</div>
.left{
float: left;
}
.right{
float: right; //html结构中right要在middle块前
}
.middle{
// overflow: hidden; // 触发BFC,让.middle元素像flex:1 一样撑满改行
}
<div class="middle-box">
<div class="middle-inner">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Qui eum magni cupiditate enim voluptatum numquam, voluptates, dolores, ipsam repudiandae autem hic aut laudantium alias excepturi distinctio temporibus. Error, rerum vel.
</div>
</div>
.middle-box{
display: table; /*重点*/
}
/*重点:table-cell布局*/
.middle-inner{
display: table-cell;
vertical-align:middle;
}
<template>
<div id="app">
<div class="nav-tab" ref="tab">
<router-link class="tab-item" active-class="active" to="/">商品</router-link>
<router-link class="tab-item" active-class="active" to="/grade">评价</router-link>
<router-link class="tab-item" active-class="active" to="/seller">商家</router-link>
<div class="nav-line" ref="line"></div>
</div>
<transition :name="transitionName">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
</div>
</template>
<script type="text/javascript">
export default {
data() {
return {
transitionName: "slide-left",
routerDepth: ["products", "grade", "seller"],
};
},
...
watch: {
$route(to, from) {
let toDepth = to.path.split("/")[1];
let fromDepth = from.path.split("/")[1];
let toIndex = this.routerDepth.indexOf(toDepth);
let fromIndex = this.routerDepth.indexOf(fromDepth);
this.transitionName =
toIndex < fromIndex ? "slide-right" : "slide-left";
if (toIndex === 1) {
this.$refs.line.style.transform = "translate3d(100%,0,0)";
} else if (toIndex === 2) {
this.$refs.line.style.transform = "translate3d(200%,0,0)";
} else {
this.$refs.line.style.transform = "translate3d(0,0,0)";
}
}
}
};
</script>
<style lang="scss" scoped>
/* 路由切换动画 */
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.3s ease;
}
.slide-left-enter,
.slide-left-leave-active,
.slide-right-enter,
.slide-left-leave-active {
opacity: 0;
}
.slide-left-leave-active,
.slide-right-enter {
transform: translate3d(-100%, 0, 0);
}
.slide-left-enter,
.slide-right-leave-active {
transform: translate3d(100%, 0, 0);
}
.nav-tab {
...
font-size: 0;
position: relative;
border-bottom: 1px solid $vborder;
.nav-line {
transition: all 0.3s ease;
transform: translate3d(0, 0, 0);
position: absolute;
left: 0;
bottom: -1px;
width: 33.33333333333%;
height: 1px;
background-color: rgb(240, 20, 20);
}
.tab-item {
display: inline-block;
width: 33.33333333333%;
...
}
}
</style>