Skip to content
On this page

购物车

image-20221201221830325

image-20221201224319660

静态结构

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>

请求数据,渲染列表

image-20221201223659923

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" });
    },

Released under the MIT License.