| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- <template>
- <view class="cart-container">
- <!-- 添加动画小球 -->
- <view class="cart-ball" :class="{ active: ballShow }" :style="ballStyle" v-show="ballShow">
- <view class="inner"></view>
- </view>
- <!-- 顶部导航栏 -->
- <view class="cart-header">
- <text class="header-title"></text>
- <text class="manage-btn" @tap="toggleManageMode">{{isManageMode ? '完成' : '管理'}}</text>
- </view>
- <!-- 购物车为空时显示 -->
- <view class="empty-cart" v-if="list.length === 0">
- <image src="/static/empty-cart.png" mode="aspectFit"></image>
- <view class="flex-items-plus">
- <image src="/static/images/empty.png" class="empty"></image>
- </view>
- <view class="empty-text">购物车是空的</view>
- <button class="go-shopping-btn" @tap="goShopping">去逛逛</button>
- </view>
- <!-- 购物车有商品时显示 -->
- <view class="cart-content" v-else>
- <!-- 商品列表 -->
- <view class="cart-list">
- <view class="cart-item" :class="{ 'out-of-stock': !item.stock || item.stock <= 0 }" v-for="(item, index) in list" :key="index">
- <checkbox :checked="item.selected" @tap="toggleSelect(index)" :color="'#ff6b81'"></checkbox>
- <view class="item-content" @tap="goToProductDetail(item)">
- <image :src="item.images" mode="aspectFill"></image>
- <view class="item-info">
- <view class="title">{{item.productName}}</view>
- <view class="sku-name">
- <text>
- 规格:
- </text>
- <text>
- {{item.skuName}}
- </text>
- </view>
- <view class="stock-info">
- <text class="stock-text">库存:{{item.stock || 0}}件</text>
- <text class="stock-warning" v-if="!item.stock || item.stock <= 0">(库存不足)</text>
- </view>
- <view class="price-quantity">
- <text class="price">¥{{item.price}}</text>
- <view class="quantity-control" v-if="!isManageMode" @tap.stop="">
- <text class="minus" @tap="decreaseQuantity(index)">-</text>
- <input type="number" v-model="item.count" @blur="updateQuantity(index)" />
- <text class="plus"
- :class="{ 'disabled': !item.stock || item.stock <= 0 || item.count >= item.stock }"
- @tap="increaseQuantity(index)">+</text>
- </view>
- </view>
- </view>
- </view>
- <view class="delete-icon" @tap="deleteItem(index)" v-if="!isManageMode">
- <uni-icons type="close" size="28"></uni-icons>
- </view>
- </view>
- </view>
- <!-- 底部结算栏 -->
- <view class="cart-footer">
- <view class="select-all">
- <checkbox :checked="isAllSelected" @tap="toggleSelectAll" :color="'#ff6b81'"></checkbox>
- <text>全选</text>
- </view>
- <view class="right-section" v-if="!isManageMode">
- <view class="total-info">
- <text>合计:</text>
- <text class="total-price">¥{{totalPrice}}</text>
- </view>
- <button class="checkout-btn" @tap="checkout">结算({{selectedCount}})</button>
- </view>
- <view class="right-section" v-else>
- <view class="total-info">
- <text>已选:</text>
- <text class="selected-count">{{selectedCount}}件</text>
- </view>
- <button class="delete-btn delete-btn-outline" @tap="batchDelete">删除</button>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import {
- productCartList,
- productCartCount,
- productCartSave,
- productCartRemoveIds,
- saveCartOrder,
- productCartTotalPrice
- } from '../../../config/api.js';
- import {
- mapGetters
- } from 'vuex';
- export default {
- computed: {
- ...mapGetters(['isLogin'])
- },
- data() {
- return {
- list: [], // 购物车商品列表
- isLoading: false, // 是否正在加载
- noMoreData: false, // 是否没有更多数据
- isManageMode: false, // 是否为管理模式
- params: {
- current: 1,
- size: 10
- },
- totalPrice: '0.00', // 购物车总价
- // 添加小球动画相关数据
- ballShow: false,
- ballStyle: {
- left: '0px',
- top: '0px'
- }
- }
- },
- computed: {
- // 是否全选
- isAllSelected() {
- return this.list.length > 0 && this.list.every(item => item.selected);
- },
- // 已选商品数量
- selectedCount() {
- return this.list.filter(item => item.selected).length;
- },
- // 计算当前总价
- currentTotalPrice() {
- return this.list.reduce((total, item) => {
- if (item.selected) {
- return total + (Number(item.price) * Number(item.count));
- }
- return total;
- }, 0).toFixed(2);
- }
- },
- onShow() {
- // // 页面显示时获取购物车数据
- // if (this.isLogin) {
- this.getList()
- // } else {
- // this.$Router.push({
- // path: '/pages/user/login'
- // })
- // }
- },
- onReachBottom() {
- if (!this.isLoading && !this.noMoreData) {
- this.getList();
- }
- },
- methods: {
- // 获取购物车列表
- async getList() {
- if (this.isLoading) return;
- this.isLoading = true;
- try {
- const res = await productCartList(this.params);
- if (res.code === 200) {
- const records = res.data.records || [];
- records.forEach(item => {
- item.selected = false; // 添加选中状态属性
- });
- if (this.params.current === 1) {
- this.list = records;
- } else {
- this.list = [...this.list, ...records];
- }
- this.noMoreData = records.length < this.params.size;
- this.params.current++;
- // 获取购物车总价
- this.getTotalPrice();
- }
- } catch (error) {
- console.error('获取购物车列表失败:', error);
- uni.$u.toast('获取购物车列表失败');
- } finally {
- this.isLoading = false;
- }
- },
- // 获取购物车总价
- async getTotalPrice() {
- try {
- // 由于我们现在基于选中状态计算价格,直接使用计算属性
- this.totalPrice = this.currentTotalPrice;
- } catch (error) {
- console.error('获取购物车总价失败:', error);
- }
- },
- // Toggle manage mode
- toggleManageMode() {
- this.isManageMode = !this.isManageMode;
- // Reset selection when exiting manage mode
- if (!this.isManageMode) {
- this.list.forEach(item => item.selected = false);
- }
- },
- // Batch delete selected items
- async batchDelete() {
- const selectedItems = this.list.filter(item => item.selected);
- if (selectedItems.length === 0) {
- uni.showToast({
- title: '请选择要删除的商品',
- icon: 'none'
- });
- return;
- }
- uni.showModal({
- title: '提示',
- content: '确定要删除选中的商品吗?',
- success: async (res) => {
- if (res.confirm) {
- try {
- const ids = selectedItems.map(item => item.id).join(',');
- const res = await productCartRemoveIds(ids);
- if (res.code === 200) {
- // Remove deleted items from list
- this.list = this.list.filter(item => !item.selected);
- // 更新前端总价
- this.totalPrice = this.currentTotalPrice;
- uni.$u.toast('删除成功');
- // Exit manage mode if no items left
- if (this.list.length === 0) {
- this.isManageMode = false;
- }
- }
- } catch (error) {
- console.error('批量删除失败:', error);
- uni.$u.toast('删除失败');
- }
- }
- }
- });
- },
- // Toggle select item
- toggleSelect(index) {
- this.list[index].selected = !this.list[index].selected;
- this.totalPrice = this.currentTotalPrice;
- },
- // Toggle select all
- toggleSelectAll() {
- const newStatus = !this.isAllSelected;
- this.list.forEach(item => {
- item.selected = newStatus;
- });
- this.totalPrice = this.currentTotalPrice;
- },
- // Delete single item
- async deleteItem(index) {
- uni.showModal({
- title: '提示',
- content: '确定要删除这个商品吗?',
- success: async (res) => {
- if (res.confirm) {
- try {
- const response = await productCartRemoveIds(this.list[index].id);
- if (response.code === 200) {
- // Remove item from list
- this.list.splice(index, 1);
- // 更新前端总价
- this.totalPrice = this.currentTotalPrice;
- uni.$u.toast('删除成功');
- }
- } catch (error) {
- console.error('删除失败:', error);
- uni.$u.toast('删除失败');
- }
- }
- }
- });
- },
- // Increase quantity
- async increaseQuantity(index) {
- const item = this.list[index];
- const newCount = Number(item.count) + 1;
-
- // 检查库存
- if (!item.stock || item.stock <= 0) {
- uni.$u.toast('该商品库存不足');
- return;
- }
-
- if (newCount > item.stock) {
- uni.$u.toast(`该商品库存仅剩${item.stock}件`);
- return;
- }
-
- await this.updateItemQuantity(index, newCount);
- },
- // Decrease quantity
- async decreaseQuantity(index) {
- const item = this.list[index];
- if (Number(item.count) > 1) {
- const newCount = Number(item.count) - 1;
- await this.updateItemQuantity(index, newCount);
- }
- },
- // Update quantity
- async updateQuantity(index) {
- const item = this.list[index];
- const newCount = Number(item.count);
-
- if (newCount < 1) {
- item.count = 1;
- return;
- }
-
- // 检查库存
- if (!item.stock || item.stock <= 0) {
- uni.$u.toast('该商品库存不足');
- item.count = 0;
- return;
- }
-
- if (newCount > item.stock) {
- uni.$u.toast(`该商品库存仅剩${item.stock}件,已调整为最大可购买数量`);
- item.count = item.stock;
- await this.updateItemQuantity(index, item.stock);
- return;
- }
-
- await this.updateItemQuantity(index, newCount);
- },
- // Update item quantity in backend
- async updateItemQuantity(index, newCount) {
- const item = this.list[index];
- try {
- const res = await productCartSave({
- id: item.id,
- count: newCount
- });
- if (res.code === 200) {
- // Update item count
- item.count = newCount;
- // Update total price
- this.getTotalPrice();
- }
- } catch (error) {
- console.error('更新数量失败:', error);
- uni.$u.toast('更新数量失败');
- }
- },
- // Go shopping
- goShopping() {
- uni.switchTab({
- url: '/pages/shop/product-type-list'
- })
- },
- // 跳转到商品详情页
- goToProductDetail(item) {
- if (item.productId) {
- uni.navigateTo({
- url: `/packageShop/pages/detail/index?id=${item.productId}`
- });
- }
- },
- // Checkout
- checkout() {
- const selectedItems = this.list.filter(item => item.selected);
- if (selectedItems.length === 0) {
- uni.showToast({
- title: '请选择要结算的商品',
- icon: 'none'
- });
- return;
- }
- // 检查选中商品的库存
- for (let item of selectedItems) {
- if (!item.stock || item.stock <= 0) {
- uni.showToast({
- title: `商品"${item.productName}"库存不足,请先删除或更换`,
- icon: 'none',
- duration: 3000
- });
- return;
- }
-
- if (Number(item.count) > item.stock) {
- uni.showToast({
- title: `商品"${item.productName}"数量超出库存,库存仅剩${item.stock}件`,
- icon: 'none',
- duration: 3000
- });
- return;
- }
- }
- // 跳转到结算页面
- uni.navigateTo({
- url: `/packageShop/pages/settle/index?items=${encodeURIComponent(JSON.stringify(selectedItems))}`
- });
- }
- }
- }
- </script>
- <style>
- .cart-container {
- min-height: 100vh;
- background-color: #f8f8f8;
- padding-bottom: 120rpx;
- }
- .cart-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20rpx 30rpx;
- background-color: #fff;
- }
- .header-title {
- font-size: 32rpx;
- font-weight: bold;
- }
- .manage-btn {
- font-size: 28rpx;
- color: #666;
- }
- .empty-cart {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding-top: 200rpx;
- }
- .empty-cart image {
- width: 200rpx;
- height: 200rpx;
- margin-bottom: 30rpx;
- }
- .empty-text {
- font-size: 28rpx;
- color: #999;
- margin-bottom: 40rpx;
- }
- .go-shopping-btn {
- width: 240rpx;
- height: 80rpx;
- line-height: 80rpx;
- text-align: center;
- background-color: #F95B5B;
- color: #fff;
- border-radius: 40rpx;
- font-size: 28rpx;
- }
- .cart-list {
- padding: 20rpx;
- }
- .cart-item {
- display: flex;
- align-items: center;
- background-color: #fff;
- padding: 20rpx;
- margin-bottom: 20rpx;
- border-radius: 12rpx;
- }
- .cart-item.out-of-stock {
- background-color: #f9f9f9;
- opacity: 0.8;
- }
- .cart-item.out-of-stock .item-content image {
- opacity: 0.6;
- }
- .item-content {
- display: flex;
- flex: 1;
- margin-left: 20rpx;
- cursor: pointer;
- }
- .item-content:active {
- opacity: 0.8;
- }
- .item-content image {
- width: 160rpx;
- height: 160rpx;
- border-radius: 8rpx;
- }
- .item-info {
- flex: 1;
- margin-left: 20rpx;
- }
- .title {
- font-size: 28rpx;
- color: #333;
- margin-bottom: 10rpx;
- }
- .sku-name {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 10rpx;
- }
- .stock-info {
- font-size: 24rpx;
- margin-bottom: 20rpx;
- }
- .stock-text {
- color: #666;
- }
- .stock-warning {
- color: #ff6b81;
- font-weight: bold;
- }
- .price-quantity {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .price {
- font-size: 32rpx;
- color: #ff6b81;
- font-weight: bold;
- }
- .quantity-control {
- display: flex;
- align-items: center;
- border: 1rpx solid #eee;
- border-radius: 6rpx;
- }
- .minus,
- .plus {
- width: 60rpx;
- height: 60rpx;
- line-height: 60rpx;
- text-align: center;
- font-size: 36rpx;
- color: #666;
- }
- .plus.disabled {
- color: #ccc;
- background-color: #f5f5f5;
- }
- .quantity-control input {
- width: 80rpx;
- height: 60rpx;
- text-align: center;
- font-size: 28rpx;
- border-left: 1rpx solid #eee;
- border-right: 1rpx solid #eee;
- }
- .delete-icon {
- padding: 20rpx;
- }
- .cart-footer {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100rpx;
- background-color: #fff;
- display: flex;
- align-items: center;
- padding: 0 30rpx;
- box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
- }
- .select-all {
- display: flex;
- align-items: center;
- }
- .select-all text {
- font-size: 28rpx;
- color: #333;
- margin-left: 10rpx;
- }
- .right-section {
- flex: 1;
- display: flex;
- justify-content: flex-end;
- align-items: center;
- }
- .total-info {
- margin-right: 30rpx;
- }
- .total-price {
- font-size: 36rpx;
- color: #ff6b81;
- font-weight: bold;
- }
- .checkout-btn {
- width: 200rpx;
- height: 80rpx;
- line-height: 80rpx;
- text-align: center;
- background-color: #F95B5B;
- color: #fff;
- border-radius: 40rpx;
- font-size: 28rpx;
- }
- .delete-btn {
- width: 200rpx;
- height: 80rpx;
- line-height: 80rpx;
- text-align: center;
- background-color: #ff6b81;
- color: #fff;
- border-radius: 40rpx;
- font-size: 28rpx;
- }
- .delete-btn-outline {
- background-color: #fff;
- color: #ff6b81;
- border: 1rpx solid #ff6b81;
- }
- /* 购物车动画小球样式 */
- .cart-ball {
- position: fixed;
- left: -18px;
- top: 0;
- z-index: 999;
- transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41);
- opacity: 0;
- }
- .cart-ball.active {
- opacity: 1;
- }
- .cart-ball .inner {
- width: 16px;
- height: 16px;
- border-radius: 50%;
- background-color: #ff6b81;
- transition: all 0.4s linear;
- }
- </style>
|