商品详情
1.前页点击商品,通过 uni.navigateTo({url: “/pages/list/index?query=”+this.keyword}),api,传递商品数据,进入详情组件,依然是onLoad()接受数据,发送请求,将请求的信息渲染出来。
2.点击客服,弹出客服聊天框,这里用按钮设置open-type="contact"即可。
3.点击购物车正常思路:1:未登陆就跳到登陆。2:未登陆就将数据存到本地,到登陆时从本地读取。
4.点击加入购物车,从当前商品信息解构出商品的信息,商品id.for循环判断购物车数组是否存在该商品id.存在则商品数量加一,而且自定义状态为选择goods—checked=true, uni.setStorageSync(“carts”, this.carts);不存在就将商品信息添加进购物车数组并数量设置为1.说白了就是本地存储前判断有无该商品。
5.点击购物车,跳转到购物车页面,用uni.getStorageSync取出本地存储的商品,循环本地存储的商品列表。渲染到购物车页面,点击增减,传递购物车列表的index和step.直接改变数量,用 @blur="changecount($event, index)"监听解决。
6.并重新给商品列表索引为index的项的goods-number赋值;点击选择框,状态改变,选中框颜色也改变。通过计算属性过滤出选中的商品。
7.计算属性判断选中的和购物车列表数组长度是否相等,如果相等,则全选按钮的:color颜色为选中,全选控制单选:for循环,将商品的按钮设置为true;
8.金额计算通过计算属性,数量乘以单价。数据持久化:数量变化或者选中状态变化时,更新本地存储,重新将购物车列表存一次。
9.收货地址调用api, uni.chooseAddress({ success: (info) => {this.address = info;}, });如果成功,赋值该地址给data里的地址。渲染到地址上。
10.结算前,判断地址有无,购物车列表选中状态的数组长度不为0,登陆的token,三者缺一不可,未登陆跳到登陆页面
代码:
<template>
<view class="wrapper" v-if="shop.length">
<!-- 收货信息 -->
<view class="shipment">
<block v-if="address">
<view class="dt">收货人: </view>
<view class="dd meta">
<text class="name">{{ address.userName }}</text>
<text class="phone">{{ address.telNumber }}</text>
</view>
<view class="dt">收货地址:</view>
<view class="dd">{{ detailAddress }}</view>
</block>
<button v-else @click="chooseAddress" type="primary">收货地址</button>
</view>
<!-- 购物车 -->
<view class="carts">
<view class="item">
<!-- 店铺名称 -->
<view class="shopname">阿毛购物</view>
<view :key="index" v-for="(cart, index) in shop" class="goods">
<!-- 商品图片 -->
<image class="pic" :src="cart.goods_small_logo"></image>
<!-- 商品信息 -->
<view class="meta">
<view class="name">{{ cart.goods_name }}</view>
<view class="price">
<text>¥</text>{{ cart.goods_price }}<text>.00</text>
</view>
<!-- 加减 -->
<view class="amount">
<text class="reduce" @click="changenumber(index, -1)">-</text>
<input
type="number"
pattern="[0-9]*"
@blur="changecount($event, index)"
:value="cart.goods_number"
class="number"
/>
<text class="plus" @click="changenumber(index, 1)">+</text>
</view>
</view>
<!-- 选框 -->
<view class="checkbox">
<icon
type="success"
@click="toggle(index)"
:color="cart.goods_checked ? '#ea4451' : '#ccc'"
size="20"
></icon>
</view>
</view>
</view>
</view>
<!-- 其它 -->
<view class="extra">
<!-- uni-app 存在 bug 属性绑定中计算属性的值获取 -->
<label class="checkall">
<icon
@click="checkAll(all)"
type="success"
size="20"
:color="all ? '#ea4451' : '#ccc'"
></icon>
全选
</label>
<view class="total">
合计: <text>¥</text><label>{{ amount }}</label
><text>.00</text>
</view>
<view class="pay" @click="creatOrder"
>结算({{ checkedgoods.length }})</view
>
</view>
</view>
<view class="tips" v-else>空空如也 </view>
</template>
<script>
export default {
data() {
return {
shop: [],
params: null,
address: null,
};
},
computed: {
// 拼凑详细地址
detailAddress() {
return (
this.address &&
[
this.address.provinceName,
this.address.cityName,
this.address.countyName,
this.address.detailInfo,
].join("")
);
},
// 选中状态的数组
checkedgoods() {
return this.shop.filter((goods) => {
return goods.goods_checked;
});
},
all() {
return this.shop.length === this.checkedgoods.length;
},
amount() {
let total = 0;
this.checkedgoods.forEach((goods) => {
total += goods.goods_price * goods.goods_number;
});
return total;
},
},
methods: {
// 订单结算
async creatOrder() {
// 地址不能为空
if (!this.address) return uni.showToast({ title: "地址不能为空" });
// 至少一件商品
if (!this.checkedgoods.length)
return uni.showToast({ title: "至少一件商品" });
// 是否登陆
if (!uni.getStorageSync("token"))
return uni.navigateTo({ url: "/pages/list/login" });
// 生成订单
const res = await this.http({
url: "/api/public/v1/my/orders/create",
method: "post",
header: { Authorization: uni.getStorageSync("token") },
data: {
order_price: this.amount,
consignee_addr: this.detailAddress,
goods: this.checkedgoods,
},
});
// console.log(res);
if (res[1].data.meta !== 999) {
// 跳转到订单列表
uni.navigateTo({
// url: "/pages/list/order",
});
}
},
// 直接改变商品数量
changecount(e, i) {
// console.log(e, i);
this.shop[i].goods_number = e.detail.value;
},
chooseAddress() {
uni.chooseAddress({
success: (info) => {
// console.log(info);
this.address = info;
},
});
},
// 反选
checkAll(all) {
this.shop.forEach((goods) => {
goods.goods_checked = true;
});
if (all) {
this.shop.forEach((goods) => {
goods.goods_checked = false;
});
}
uni.setStorageSync("carts", this.shop);
},
// 改变选中状态
toggle(index) {
this.shop[index].goods_checked = !this.shop[index].goods_checked;
uni.setStorageSync("carts", this.shop);
},
getCates() {
this.shop = uni.getStorageSync("carts") || [];
// console.log(this.shop, 11);
},
// 改变数量
changenumber(index, step) {
// console.log(index, step);
// 商品数量
let num = this.shop[index].goods_number;
// 检查边界
// 最小买1件,最多不超过库存,假设库存10
if (step === -1 && num <= 1) return;
if (step === 1 && num >= 10) return;
this.shop[index].goods_number += step;
uni.setStorageSync("carts", this.shop);
},
},
onLoad(e) {
this.getCates();
},
};
</script>
<style scoped lang="less">
.shipment {
height: 100rpx;
line-height: 2;
padding: 30rpx 30rpx 40rpx 30rpx;
font-size: 27rpx;
color: #333;
background-color: #fff;
background-image: url(http://static.botue.com/ugo/images/cart_border%402x.png);
background-size: contain;
background-repeat: no-repeat;
background-position: bottom;
.dt {
width: 140rpx;
float: left;
clear: both;
}
.dd {
padding-left: 160rpx;
}
.meta {
padding-right: 50rpx;
}
text.phone {
float: right;
}
}
.carts {
background-color: #f4f4f4;
padding-bottom: 110rpx;
overflow: hidden;
.shopname {
padding: 30rpx;
margin-top: 20rpx;
font-size: 30rpx;
color: #333;
background-color: #fff;
border-top: 1rpx solid #eee;
border-bottom: 1rpx solid #eee;
}
.goods {
display: flex;
padding: 30rpx 20rpx 30rpx 0;
margin-left: 100rpx;
border-bottom: 1rpx solid #eee;
background-color: #fff;
position: relative;
.checkbox {
width: 101rpx;
height: 100%;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: -100rpx;
top: 0;
}
&:last-child {
border-bottom: none;
}
.pic {
width: 200rpx;
height: 200rpx;
margin-right: 30rpx;
}
.meta {
flex: 1;
font-size: 27rpx;
color: #333;
position: relative;
}
.name {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.price {
position: absolute;
bottom: 0;
color: #ea4451;
font-size: 33rpx;
text {
font-size: 22rpx;
}
}
.amount {
position: absolute;
bottom: 0;
right: 20rpx;
height: 48rpx;
text-align: center;
border: 1rpx solid #ddd;
border-radius: 8rpx;
display: flex;
align-items: center;
text {
display: block;
width: 60rpx;
line-height: 48rpx;
font-size: 36rpx;
color: #ddd;
text-align: center;
}
input {
width: 60rpx;
height: 48rpx;
min-height: 48rpx;
font-size: 27rpx;
border-left: 1rpx solid #ddd;
border-right: 1rpx solid #ddd;
}
}
}
}
.extra {
position: fixed;
bottom: 0;
/* #ifdef H5 */
bottom: 50px;
/* #endif */
left: 0;
z-index: 9;
width: 750rpx;
height: 96rpx;
text-align: center;
line-height: 96rpx;
font-size: 36rpx;
border-top: 1rpx solid #eee;
background-color: #fff;
color: #333;
display: flex;
.checkall {
width: 140rpx;
line-height: 1;
margin-left: 25rpx;
display: flex;
align-items: center;
icon {
margin-right: 20rpx;
}
}
.total {
display: flex;
justify-content: center;
flex: 1;
label,
text {
color: #ea4451;
vertical-align: bottom;
position: relative;
bottom: -2rpx;
}
text {
position: relative;
bottom: -3rpx;
font-size: 24rpx;
&:first-child {
margin-left: 10rpx;
}
}
}
.pay {
width: 200rpx;
background-color: #ea4451;
color: #fff;
}
}
</style>