购物车
静态结构
src\pages\cart\index.vue
vue
<template>
<view class="main">
<!-- 内容列表 -->
<scroll-view scroll-y class="viewport">
<!-- 购物车列表 -->
<template v-if="true">
<!-- 优惠提示 -->
<view class="tips">
<text class="label">满减</text>
<text class="desc">满1件, 即可享受9折优惠</text>
</view>
<view class="blank" v-show="true">
购物车还是空的,快来挑选好货吧
</view>
<!-- 购物车商品 -->
<view class="carts">
<uni-swipe-action>
<uni-swipe-action-item
class="swipe-cell"
v-for="item in []"
:key="item.skuId"
>
<navigator
hover-class="none"
:url="'/pages/goods/index?id=' + item.id"
class="card"
>
<text
class="checkbox"
:class="[`icon-${item.selected ? 'checked' : 'ring'}`]"
></text>
<!-- 商品缩略图 -->
<image class="thumb" :src="item.picture"></image>
<view class="meta">
<!-- 商品名称 -->
<view class="name ellipsis">{{ item.name }}</view>
<!-- 商品类型 -->
<view class="type ellipsis">{{ item.attrsText }}</view>
<!-- 价格 -->
<view class="price"> ¥{{ item.price }} </view>
<!-- 商品数量,阻止冒泡 -->
<view class="quantity" @tap.stop="() => {}">
<uni-number-box
v-model="item.count"
:min="1"
:max="item.stock"
/>
</view>
</view>
</navigator>
<template v-slot:right>
<view class="swipe-cell-action">
<button class="delete-button">删除</button>
</view>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</template>
<view class="blank" v-else>
<text>登后后可查看购物车中的商品</text>
<navigator url="/pages/login/index" hover-class="none">
<button class="button">去登录</button>
</navigator>
</view>
<!-- 状态提示 -->
</scroll-view>
<!-- 底部操作栏 -->
<view class="toolbar" v-if="true">
<text class="all" :class="{ checked: true }">全选</text>
<text class="text">合计:</text>
<text class="amount">{{ 300.0 }}</text>
<!-- 操作按钮 -->
<view class="buttons">
<view class="button payment" :class="{ disabled: true }">
去结算({{ 300.0 }})
</view>
</view>
</view>
</view>
</template>
<script>
export default {
};
</script>
<style lang="scss">
page {
background-color: #f7f7f8;
height: 100%;
}
.main {
height: 100%;
display: flex;
flex-direction: column;
}
.viewport {
min-height: 400rpx;
}
/* 顶部工具栏 */
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
padding: 0 30rpx;
font-size: 26rpx;
color: #262626;
background-color: #fff;
.extra {
display: flex;
align-items: center;
height: 24rpx;
border-left: 1rpx solid #bfbfbf;
.edit {
padding: 0 30rpx;
}
.menu {
width: 9rpx;
height: 9rpx;
padding: 14rpx;
border-radius: 50%;
background-color: #262626;
background-clip: content-box;
position: relative;
&::before {
left: 0;
}
&::after {
right: 0;
}
}
}
}
.topbar .extra .menu::before,
.topbar .extra .menu::after {
position: absolute;
top: 50%;
content: "";
width: 6rpx;
height: 4rpx;
background-color: #8c8c8c;
transform: translateY(-50%);
border-radius: 4rpx;
}
/* 优惠提示 */
.tips {
display: flex;
align-items: center;
line-height: 1;
padding: 30rpx;
font-size: 26rpx;
color: #666;
.label {
color: #fff;
padding: 7rpx 15rpx 5rpx;
border-radius: 4rpx;
font-size: 24rpx;
background-color: #27ba9b;
margin-right: 10rpx;
}
}
.carts {
padding: 0 20rpx;
.card {
display: flex;
padding: 20rpx 20rpx 20rpx 80rpx;
border-radius: 10rpx;
background-color: #fff;
position: relative;
.checkbox {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 80rpx;
height: 100%;
font-size: 40rpx;
color: #444;
}
.icon-checked {
color: #27ba9b;
}
.thumb {
width: 170rpx;
height: 170rpx;
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 20rpx;
}
.name {
height: 72rpx;
font-size: 26rpx;
color: #444;
}
.type {
line-height: 1.8;
padding: 0 15rpx;
font-size: 24rpx;
align-self: flex-start;
border-radius: 4rpx;
color: #888;
background-color: #f7f7f8;
}
.price {
line-height: 1;
font-size: 26rpx;
color: #444;
margin-bottom: 2rpx;
}
.warning {
color: #cf4444;
font-size: 24rpx;
}
.quantity {
position: absolute;
bottom: 20rpx;
right: 5rpx;
display: flex;
justify-content: space-between;
align-items: center;
width: 220rpx;
height: 48rpx;
.text {
height: 100%;
padding: 0 20rpx;
font-size: 32rpx;
color: #444;
}
.input {
height: 100%;
text-align: center;
border-radius: 4rpx;
font-size: 24rpx;
color: #444;
background-color: #f6f6f6;
}
}
}
.swipe-cell {
display: block;
margin-top: 20rpx;
&:first-child {
margin-top: 0;
}
}
}
/* 购物车商品 */
/* 购物车状态提示 */
.blank {
padding: 100rpx 0 60rpx;
text-align: center;
color: #444;
font-size: 26rpx;
.button {
width: 240rpx !important;
height: 60rpx;
line-height: 60rpx;
margin-top: 25rpx;
font-size: 26rpx;
border-radius: 60rpx;
color: #fff;
background-color: #27ba9b;
&::after {
display: none;
}
}
}
/* 吸底工具栏 */
.toolbar {
position: relative;
padding: 32rpx 20rpx 28rpx;
border-top: 1rpx solid #ededed;
border-bottom: 1rpx solid #ededed;
background-color: #fff;
.all {
margin-left: 25rpx;
font-size: 14px;
color: #444;
&::before {
font-family: "erabbit" !important;
content: "\e6cd";
font-size: 36rpx;
margin-right: 8rpx;
vertical-align: -4rpx;
}
}
.checked {
&::before {
content: "\e6cc";
color: #27ba9b;
}
}
.text {
margin-right: 8rpx;
margin-left: 32rpx;
color: #444;
font-size: 14px;
}
.amount {
font-size: 20px;
color: #cf4444;
vertical-align: -1px;
&::before {
content: "¥";
font-size: 12px;
}
.decimal {
font-size: 12px;
}
}
.buttons {
position: absolute;
right: 10rpx;
top: 50%;
display: flex;
justify-content: space-between;
text-align: center;
line-height: 72rpx;
font-size: 13px;
color: #fff;
transform: translateY(-50%);
.button {
width: 240rpx;
margin: 0 10rpx;
border-radius: 72rpx;
}
}
.payment {
background-color: #27ba9b;
}
.disabled {
opacity: 0.6;
}
.delete {
display: none;
background-color: #27ba9b;
}
.collect {
display: none;
background-color: #ffa868;
}
}
.swipe-cell-action {
display: flex;
height: 100%;
button {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
padding: 6px;
line-height: 1.5;
color: #fff;
font-size: 26rpx;
border-radius: 0;
}
.collect-button {
background-color: #ffa868;
}
.delete-button {
background-color: #cf4444;
}
}
</style>
请求数据,渲染列表
1、封装API:src/api/cart.js
js
/**
* 请求列表
*/
export const getCartsAPI = () => {
return http({
url: "/member/cart",
});
};
2、封装请求函数
3、onShow触发请求
4、声明变量保存数据
src/pages/cart/index.vue
js
import { mapState } from "vuex";
export default {
data() {
return {
list: [],
};
},
methods: {
async loadData() {
const { result } = await getCartsAPI();
this.list = result;
},
},
onShow() {
this.loadData();
},
};
5、列表渲染
代码省略...
商品切换选中
1、封装API:api/cart.js
js
export const updateCartAPI = (selected, id) => {
return http({
url: `/member/cart/selected`,
method: "PUT",
data: { ids: [id], selected },
});
};
2、 封装点击事件 , 绑定点击事件
3、调用API
4、刷新列表
js
async updateCart(selected, skuId) {
await updateCartAPI(selected, skuId);
this.loadData();
},
小选影响全选
1、定义计算属性
js
computed: {
// 1. 定义计算属性,
isAll() {
return this.list.every((item) => item.selected);
},
},
2、控制全选选中
vue
<text class="all" :class="{ checked: isAll }">全选</text>
全选影响小选
1、改造API:api/cart.js
diff
export const updateCartAPI = (selected, id) => {
+ let ids;
// ids为空时不传
+ if (id) ids = [id];
return http({
url: `/member/cart/selected`,
method: "PUT",
- data: { ids: [id], selected }
+ data: { ids, selected },
});
};
2、复用更新方法
diff
<text
class="all"
:class="{ checked: isAll }"
+ @click="updateCart(!isAll)"
>
全选
</text>
统计总价格和总量
1、定义计算属性
js
computed: {
// ... 省略其它代码...
total() {
let sum = 0;
this.list.forEach((item) => {
// 💥 注意: 后台给的加个是字符串
if (item.selected) {
sum += Number(item.price);
}
});
return sum.toFixed(2);
},
amount() {
let sum = 0;
this.list.forEach((item) => {
if (item.selected) {
sum += item.count;
}
});
return sum;
},
},
2、插值显示
diff
<view class="toolbar" v-if="list.length">
<text class="all" :class="{ checked: isAll }" @click="updateCart(!isAll)"
>全选</text
>
<text class="text">合计:</text>
+ <text class="amount">{{ total }}</text>
<!-- 操作按钮 -->
<view class="buttons">
<view
class="button payment"
+ :class="{ disabled: !amount }"
>
+ 去结算({{ amount }})
</view>
<view class="button collect">移入收藏</view>
<view class="button delete">删除</view>
</view>
</view>
实现购物车加减
1、封装API
js
export const updateCartCountAPI = (skuId, { selected, count }) => {
return http({
url: `/member/cart/${skuId}`,
method: "PUT",
data: { count, selected },
});
};
删除购物车商品
1、API
js
/**
* 删除购物车 id
*/
export const delCartByIdAPI = (id) => {
return http({
url: `/member/cart`,
method: "delete",
data: { ids: [id] },
});
};
2、点击事件
vue
<template v-slot:right>
<view class="swipe-cell-action">
<!-- 💥 注意,这里传入的是item.skuId,而不是item.id -->
<button class="delete-button" @click="delById(item.skuId)">
删除
</button>
</view>
</template>
3、点击方法
js
async delById(id) {
await delCartByIdAPI(id);
this.loadData();
},
跳转支付
1、点击事件
diff
<!-- 操作按钮 -->
<view class="buttons">
<view
class="button payment"
:class="{ disabled: !amount }"
+ @click="onGoPay"
>
去结算({{ amount }})
</view>
</view>
2、绑定方法
js
onGoPay() {
if (!this.amount) {
return uni.showToast({ title: "请至少选择一项商品" });
}
// 💥💥 添加到文档上
uni.navigateTo({ url: "/pages/order/create/index" });
},