vue五十二:Vue美团项目之商家详情-购物车实现
实现效果
cart.vue
<style scoped lang='scss'>
.cart-container {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
.mask-bg {
background-color: rgba(0, 0, 0, 0.7);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.cart-group {
position: absolute;
left: 0;
right: 0;
bottom: 50px;
background-color: #fff;
.satify-group {
background-color: #fff1d0;
font-size: 12px;
text-align: center;
padding: 4px 0;
}
.cart-list-group {
.cart-title-group {
padding: 10px;
display: flex;
justify-content: space-between;
.clear-btn {
padding-left: 16px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaBAMAAABbZFH9AAAAGFBMVEUAAABmZmZnZ2dtbW1mZmZnZ2dmZmZmZmbBwsy3AAAAB3RSTlMA54gcS6a2Y+y94QAAAGJJREFUGNNjAAPFciEGOGAqLy9XgPMYSxjcBSDi4uUwUKjAoF6OAEUM4g5wDSyFqDxUlWimMLAXgJVBGJi8chAcJDwklzEWQ3nmoKBhLTMAc5jTA0CkOMx7YFFTcQgnmIEBABeDPTvg/cSzAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-size: 14px;
}
}
.goods-list {
li {
padding: 10px;
height: 30px;
display: flex;
align-items: center;
.goods-info {
display: flex;
flex: 1;
justify-content: space-between;
.title {
font-size: 18px;
}
.price {
color: #FB4E44;
font-size: 14px;
}
}
.stepper {
min-width: 82px;
}
}
}
}
}
.settle-group {
width: 100%;
height: 50px;
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: flex;
.hot-area {
background-color: #3b3b3c;
flex: 1;
.shop-icon {
width: 50px;
height: 50px;
background-image: url("http://s3plus.meituan.net/v1/mss_e2821d7f0cfe4ac1bf9202ecf9590e67/cdn-prod/file:9096d347/03098cb323a0263fdbbb102c696433c5.png");
background-repeat: no-repeat;
background-position: center;
background-size: 50px;
left: 10px;
bottom: 10px;
position: absolute;
.badage {
width: 16px;
height: 16px;
background-color: red;
border-radius: 50%;
color: #fff;
text-align: center;
line-height: 16px;
position: absolute;
right: 0;
top: 0;
}
}
.total-price {
font-size: 20px;
color: #fff;
margin-left: 70px;
line-height: 50px;
.unit {
font-size: 14px;
}
}
}
.settle-btn {
background-color: #f8c74e;
padding: 0 30px;
font-size: 16px;
display: flex;
align-items: center;
}
}
}
</style>
<template>
<div class="cart-container" v-show="show">
<div class="mask-bg" v-show="showGoods" @click="showGoods=false"></div>
<div class="cart-group">
<div class="satify-group">已满足起送价</div>
<div class="cart-list-group" v-show="showGoods">
<div class="cart-title-group">
<span class="title">购物车</span>
<span class="clear-btn" @click="clearCart">清空购物车</span>
</div>
<ul class="goods-list">
<li v-for="goods in goodsList" :key="goods.name">
<div class="goods-info">
<span class="title">{{goods.name}}</span>
<span class="price">¥{{goods.price}}</span>
</div>
<stepper class="stepper" v-model="goods.count"></stepper>
</li>
</ul>
</div>
</div>
<div class="settle-group">
<div class="hot-area" @click="showGoods=!showGoods">
<div class="shop-icon">
<div class="badage">{{totalCount}}</div>
</div>
<div class="total-price">
<span class="unit">¥</span>{{totalPrice}}
</div>
</div>
<div class="settle-btn" @click="gotoSettle">去结算</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import Stepper from "./Stepper"
export default {
name: "cart",
props: ["categories"],
data() {
return {
showGoods: false
}
},
computed: {
show() {
if (this.goodsList.length > 0) {
return true;
} else {
// this.showGoods = false;
return false;
}
},
goodsList() {
const theGoodsList = [];
for (let category of this.categories) {
for (let goods of category.goods_list) {
if (goods.count > 0) {
theGoodsList.push(goods);
}
}
}
return theGoodsList
},
totalPrice() {
let total = 0;
for (let goods of this.goodsList) {
total += parseFloat(goods.price) * goods.count;
}
return total
},
totalCount() {
let count = 0;
for (let goods of this.goodsList) {
count += goods.count;
}
return count;
}
},
components: {
[Stepper.name]: Stepper
},
methods: {
clearCart() {
for (let category of this.categories) {
for (let goods of category.goods_list) {
goods.count = 0;
}
}
},
gotoSettle() {
this.$store.commit("setCart", this.goodsList);
this.$router.push("/submitorder");
}
}
}
</script>
Stepper.vue
<style scoped lang="scss">
.stepper-container {
display: flex;
align-items: center;
.substract {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAMAAADypuvZAAAAM1BMVEX////Z296wsLD6+vvo6evi5Obe3+Lr7O709fb5+fr3+Pjb3eDw8fLu7/Hl5+nk5ui5ubmS1nTyAAABG0lEQVRIx52WWXLEIAxEeUGSGeNl7n/aME6lUmQWm+5f+5l2A5LSC+XFPCrUcFtyuqDbFHSK6XaCzE7TvpZ8LFnWnSafPyB5g2oldSpWYXvrcgKmkQdNDpbfWDDwVwEE8cH6HMRTIAX8Y7rZofxbByydyKBfKx7MORV9Bn4CPL82EfkKlOMv+QxzuqQZfr++YemijK3Dz9Sb8mb0sib8Z4tqGlDldrA2AtnhKygjUCGOX0tDesS2sI9BO0vzuI5BK5acMgYVPAV5DMpEy71P9P71pHu/J9REDzXmBdVDSJBkTwpCilzaXOkYSQdWuRrSJZSuu1JYpBKmFEupLCsNQGk1SlOT2qfSqJWRQBg+lDFHG6j00U0fEqVx9BvALAkNabHL3QAAAABJRU5ErkJggg==");
}
.add {
background-image: url("http://s3plus.meituan.net/v1/mss_e2821d7f0cfe4ac1bf9202ecf9590e67/cdn-prod/file:9096d347/318c525df8eba9c557594305c44b2ddf.png");
}
.step-btn {
width: 45px;
height: 45px;
border-radius: 50%;
background-position: center;
background-repeat: no-repeat;
background-size: 40%;
display: inline-block;
}
}
</style>
<template>
<div class="stepper-container">
<span class="substract step-btn" v-show="value>0" @click.stop="substractClick"></span>
<span class="text" v-show="value>0">{{value}}</span>
<span class="add step-btn" @click.stop="addClick"></span>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: "stepper",
props: {
value: {
type: Number,
default: 0
}
},
model: {
prop: "value",
event: "value-changed"
},
data() {
return {
// value: 0
}
},
methods: {
substractClick() {
// this.value -= 1;
this.$emit("value-changed", this.value - 1)
},
addClick() {
// this.value += 1;
this.$emit("value-changed", this.value + 1)
}
},
components: {}
};
</script>
merchant.vue
<style scoped>
.merchant-container >>> .van-nav-bar {
background: none;
}
/*去掉横线*/
.merchant-container >>> .van-hairline--bottom::after {
border: none;
}
/*修改箭头颜色*/
.merchant-container >>> .van-icon {
color: #fff;
}
</style>
<style scoped lang="scss">
.header-group {
background-color: black;
padding: 10px;
display: flex;
margin-top: -46px;
padding-top: 46px;
.logo {
width: 85px;
height: 75px;
}
.merchant-info {
flex: 1;
margin-left: 10px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: space-around;
overflow: hidden;
}
.notice {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.tab-group {
.menu-group {
display: flex;
.category-group {
width: 80px;
text-align: center;
/**/
height: 100%;
overflow: hidden;
.category-list {
overflow: hidden;
li {
height: 50px;
line-height: 50px;
&.active {
background-color: #ccc;
}
}
}
}
.goods-group {
flex: 1;
/**/
margin-left: 10px;
overflow: hidden;
height: 100%;
.category-name {
font-size: 12px;
height: 32px;
line-height: 32px;
}
.goods-item {
display: flex;
/*margin-bottom: 10px;*/
padding-bottom: 10px;
height: 75px;
.thumbnail {
width: 75px;
height: 75px;
}
.goods-info {
flex: 1;
margin-left: 10px;
display: flex;
flex-direction: column;
justify-content: space-around;
.goods-name {
font-size: 16px;
font-weight: 700;
}
.goods-info-bottom {
display: flex;
justify-content: space-between;
align-items: center;
.price {
color: #fb4e44;
font-size: 16px;
font-weight: 700;
}
}
}
}
}
}
}
</style>
<template>
<div class="merchant-container">
<van-nav-bar left-arrow @click-left="onClickLeft"></van-nav-bar>
<div class="header-group">
<img src="https://tse3-mm.cn.bing.net/th/id/OIP.17S875eLr_HDQD8FGwagqQAAAA?w=151&h=201&c=7&o=5&dpr=1.25&pid=1.7"
alt="" class="logo">
<div class="merchant-info">
<div class="delivery-info">
<span>20分钟</span> | <span>3KM</span>
</div>
<div class="notice">
公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告公告
</div>
</div>
</div>
<van-tabs class="tab-group" v-model="active">
<van-tab title="点菜">
<div class="menu-group" :style="menuHeightStyle">
<div class="category-group" ref="category">
<ul class="category-list">
<li v-for="(category, index) in categories" :key="category.name"
@click="categoryClick(index)" :class="index==currentIndex?'active':''">
{{ category.name }}
</li>
</ul>
</div>
<div class="goods-group" ref="goods">
<div class="goods-list">
<dl v-for="(category, category_index) in categories" :key="category.name" class="goods-dl">
<dt class="category-name">{{category.name}}</dt>
<dd class="goods-item" v-for="(goods, goods_index) in category.goods_list"
:key="goods.id" @click="goodsClick(category_index, goods_index)">
<img :src="goods.picture" alt="" class="thumbnail">
<div class="goods-info">
<div class="goods-name">{{goods.name}}</div>
<div class="month-sale">月售1份</div>
<div class="goods-info-bottom">
<div class="price">¥{{goods.price}}</div>
<stepper v-model="goods.count"></stepper>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
</van-tab>
<van-tab title="评价">
评价页面
</van-tab>
<van-tab title="商家">
商家页面
</van-tab>
</van-tabs>
<goods-detail :goods="detailGoods"></goods-detail>
<cart :categories="categories"></cart>
</div>
</template>
<script type="text/ecmascript-6">
import {NavBar, Tab, Tabs} from 'vant';
import BScroll from "better-scroll"; //先 npm install better-scroll
import kfc from "../../data/kfc" // 导入测试数据
import GoodsDetail from "./GoodsDetail";
import Stepper from "./Stepper";
import Cart from "./Cart";
export default {
name: "",
data() {
return {
active: 0,
categories: [],
positions: [],
currentIndex: 0,
detailGoods: null
}
},
components: {
[NavBar.name]: NavBar,
[Tab.name]: Tab,
[Tabs.name]: Tabs,
[GoodsDetail.name]: GoodsDetail,
[Stepper.name]: Stepper,
[Cart.name]: Cart
},
computed: {
// 定义商家产品页内容体的高度
menuHeightStyle() {
const height = window.innerHeight - 164;
return {
height: height + "px"
}
}
},
mounted() {
const categories = kfc['categories']; // 获取测试数据的categories字段的值
for (let category of categories){
for (let goods of category.goods_list){
goods.count = 0;
}
}
this.categories = categories
// // 获取每条数据的id和name字段
// for (let index = 0; index < categories.length; index++) {
// const category = categories[index];
// this.categories.push({id: category.id, name: category.name})
// }
// 菜单滚动
this.$nextTick(() => {
var menuScroll = new BScroll(this.$refs.category, { // eslint-disable-line no-unused-vars
scrollY: true, // Y,垂直
click: true // 可点击
})
var goodsScroll = new BScroll(this.$refs.goods, {
scrollY: true, // Y,垂直
click: true, // 可点击
probeType: 2 // 根据better-scroll官方文档,需设置probeType为2,才能监听scroll事件
})
goodsScroll.on('scroll', (pos) => {
const y = Math.abs(pos.y); // 取绝对值
console.log(y);
const positions = this.positions;
for (let index = positions.length - 1; index >= 0; index--) {
const position = positions[index];
if (y >= position) {
// 6
// [1, 3, 6, 7] <--往前推
this.currentIndex = index;
break
}
}
})
this.menuScroll = menuScroll;
this.goodsScroll = goodsScroll;
// 获取上平距离顶部的高度,方便跳转
const positions = [0];
let offset = 0;
const dlList = document.getElementsByClassName("goods-dl") // 获取商品列表
// for...in...循环对象
// for...of...循环数组
for (let dl of dlList) {
const height = dl.clientHeight; // 获取商品的高度
offset += height;
positions.push(offset)
}
this.positions = positions;
})
},
methods: {
categoryClick(index) {
const position = this.positions[index];
// 往上滚动,y的值应该为负数
this.goodsScroll.scrollTo(1, -position, 500) // scrollTo滚动到具体位置,具体参见better-scroll官方文档
this.currentIndex = index; // 更新选中的索引
},
onClickLeft() {
this.$router.back();
},
// 商品点击事件
goodsClick(category_index, goods_index) {
let category = this.categories[category_index];
let goods = category.goods_list[goods_index];
this.detailGoods = JSON.parse(JSON.stringify(goods));
}
}
}
</script>
讨论群:249728408