mobile/pages/product/list/list.vue
@@ -4,8 +4,9 @@
         <view class="index-search-box index-search-box_re d-b-c" id="searchBox">
            <view class="index-search index-search_re t-c flex-1">
               <span class="icon iconfont icon-sousuo"></span>
               <input type="text" v-model="search" class="flex-1 ml10 f26 gray3" value="" placeholder-class="f26 gray9"
                placeholder="搜索商品" confirm-type="search" @confirm="searchFunc()" />
               <input type="text" v-model="search" class="flex-1 ml10 f26 gray3" value=""
                  placeholder-class="f26 gray9" placeholder="搜索商品" confirm-type="search"
                  @confirm="searchFunc()" />
            </view>
         </view>
         <view class="inner-tab">
@@ -36,14 +37,15 @@
         </view>
      </view>
      <view class="prodcut-list-wrap">
         <scroll-view scroll-y="true" class="scroll-Y" :style="'height:' + scrollviewHigh + 'px;'" lower-threshold="50"
          @scrolltolower="scrolltolowerFunc">
         <scroll-view scroll-y="true" class="scroll-Y" :style="'height:' + scrollviewHigh + 'px;'"
            lower-threshold="50" @scrolltolower="scrolltolowerFunc">
            <view :class="topRefresh?'top-refresh open':'top-refresh'">
               <view class="circle" v-for="(circle,n) in 3" :key="n"></view>
            </view>
            <view class="shop_body" v-if="isLieBiao ==true">
               <view class="shop_body_l_item" :class="index==listData.length-1?'noborder':''" v-for="(item,index) in listData"
                :key="index" @click="gotoList(item.product_id)" v-if="index_open_city==0 || (index_open_city==1 && isInArray2(city_supplier_ids,item.shop_supplier_id))">
               <view class="shop_body_l_item" :class="index==listData.length-1?'noborder':''"
                  v-for="(item,index) in listData" :key="index" @click="gotoList(item.product_id)"
                  v-if="index_open_city==0 || (index_open_city==1 && isInArray2(city_supplier_ids,item.shop_supplier_id))">
                  <view>
                     <image :src="item.product_image" mode=""></image>
                  </view>
@@ -51,29 +53,61 @@
                     <view class="shop_body_l_item_info_title gray3 f32">{{item.product_name}}</view>
                     <view class="d-b-c pb10">
                        <view class="shop_body_l_item_info_price">
                           <view class="f24 shop_red">¥<text class="f32 fb">{{item.product_price}}</text></view>
                           <view class="f24 shop_red">¥<text class="f32 fb">{{item.product_price}}</text>
                           </view>
                        </view>
                        <view class="shop_body_l_item_info_others f22">
                        <!-- <view class="shop_body_l_item_info_others f22">
                           <view class="shop_body_l_item_info_others_sales">累计成交:{{item.product_sales}}笔</view>
                        </view>
                        </view> -->
                     </view>
                  </view>
               </view>
            </view>
            <view class="shop_body2" v-if="isLieBiao ==false">
               <view class="shop_body_t_item" :class="index % 2 == 0?'ml20 mr20':' mr20'" v-for="(item,index) in listData" :key="index"
                @click="gotoList(item.product_id)" v-if="index_open_city==0 || (index_open_city==1 && isInArray2(city_supplier_ids,item.shop_supplier_id))">
               <view class="shop_body_t_item" :class="index % 2 == 0?'ml20 mr20':' mr20'"
                  v-for="(item,index) in listData" :key="index" @click="gotoList(item.product_id)"
                  v-if="index_open_city==0 || (index_open_city==1 && isInArray2(city_supplier_ids,item.shop_supplier_id))">
                  <image :src="item.product_image" mode=""></image>
                  <view class="shop_body_t_item_info">
                     <view class="shop_body_t_item_info_title f26">{{item.product_name}}</view>
                     <view class="shop_body_t_item_info_others f24 gray9 mt">
                     <!-- <view class="shop_body_t_item_info_others f24 gray9 mt">
                        <view class="shop_body_t_item_info_others_sales">累计成交:{{item.product_sales}}笔</view>
                     </view>
                     </view> -->
                     <view class="shop_body_t_item_info_price">
                        <view class="f20 redF6">¥<text class="f32">{{item.product_price}}</text></view>
                        <view class="f20 huaxianjia">¥<text class="24">{{item.line_price}}</text></view>
                        <view class="f20 redF6" v-if="item.is_price_negotiable">价格面议</view>
                        <template v-else>
                           <view class="f20 redF6">¥<text class="f32">{{item.product_price}}</text></view>
                           <view class="f20 huaxianjia">¥<text class="24">{{item.line_price}}</text></view>
                        </template>
                     </view>
                     <!-- 购物车操作组件 -->
                     <view class="cart-action">
                        <!-- 多规格商品显示选择规格按钮 -->
                        <view class="spec-select-btn" v-if="item.spec_type === 20"
                           @click.stop="showSpecPopup(item,index)">
                           <text>选择规格</text>
                           <!-- 购物车数量徽章 -->
                           <view class="cart-badge" v-if="(item.cart && item.cart.total_num > 0)">
                              <text class="cart-count">{{ item.cart.total_num || 0 }}</text>
                           </view>
                        </view>
                        <!-- 单规格商品显示购物车操作 -->
                        <template v-else>
                           <view class="cart-btn-add" v-if="!item.cart.total_num || item.cart.total_num <= 0"
                              @click.stop="addToCart(item,index)">
                              <text class="icon iconfont icon-jia"></text>
                           </view>
                           <view class="cart-number-controller" v-else>
                              <view class="cart-btn-sub" @click.stop="decreaseCart(item,index)">
                                 <text class="icon iconfont icon-jian"></text>
                              </view>
                              <view class="cart-number">{{ item.cart.total_num }}</view>
                              <view class="cart-btn-add" @click.stop="increaseCart(item,index)">
                                 <text class="icon iconfont icon-jia"></text>
                              </view>
                           </view>
                        </template>
                     </view>
                  </view>
               </view>
            </view>
@@ -84,6 +118,57 @@
            </view>
            <uni-load-more v-else :loadingType="loadingType"></uni-load-more>
         </scroll-view>
      </view><!-- 规格选择弹窗 -->
      <view class="spec-popup" :class="specPopupVisible ? 'visible' : ''" @touchmove.stop.prevent="">
         <view class="popup-mask" @click="closeSpecPopup"></view>
         <view class="popup-content" v-if="selectedProduct">
            <view class="popup-header">
               <image :src="selectedProduct.product_image" mode="aspectFit"></image>
               <view class="popup-header-info">
                  <view class="price">¥{{ currentPrice }}</view>
                  <view class="stock">库存:{{ currentStock }}</view>
                  <view class="selected-spec">{{ selectedSpecText }}</view>
               </view>
               <view class="popup-header-right">
                  <view class="close-btn" @click="closeSpecPopup">
                     <text class="icon iconfont icon-guanbi"></text>
                  </view>
               </view>
            </view>
            <view class="spec-section" v-if="selectedProduct.spec_type === 20 && selectedProduct.specData">
               <view class="spec-group" v-for="(specGroup, groupIndex) in selectedProduct.specData.spec_attr"
                  :key="groupIndex">
                  <view class="spec-group-name">{{ specGroup.group_name }}</view>
                  <view class="spec-options">
                     <view class="spec-option"
                        :class="{ active: selectedSpecs[groupIndex] === specItem.item_id }"
                        v-for="(specItem, itemIndex) in specGroup.spec_items" :key="itemIndex"
                        @click="selectSpec(groupIndex, specItem.item_id)">
                        {{ specItem.spec_value }}
                     </view>
                  </view>
               </view>
            </view>
            <view class="quantity-section">
               <view class="quantity-label">数量</view>
               <view class="quantity-controller">
                  <view class="quantity-btn" :class="{ disabled: quantity <= 1 }" @click="decreaseQuantity">
                     <text class="icon iconfont icon-jian"></text>
                  </view>
                  <view class="quantity-display">{{ quantity }}</view>
                  <view class="quantity-btn" :class="{ disabled: quantity >= currentStock }"
                     @click="increaseQuantity">
                     <text class="icon iconfont icon-jia"></text>
                  </view>
               </view>
            </view>
            <view class="action-buttons">
               <button class="add-cart-btn" @click="confirmAddToCart">加入购物车</button>
            </view>
         </view>
      </view>
   </view>
</template>
@@ -121,8 +206,16 @@
            sortPrice: 0,
            list_rows: 10,
            last_page: 0,
            index_open_city:0,
            city_supplier_ids:[],
            index_open_city: 0,
            city_supplier_ids: [],
            // 购物车相关数据
            cartData: {}, // 存储各商品在购物车中的数量
            // 规格弹窗相关数据
            specPopupVisible: false,
            selectedProduct: null,
            selectedSpecs: [],
            quantity: 1,
         };
      },
      computed: {
@@ -185,13 +278,13 @@
               }
            });
         },
         isInArray2(arr,value){
            value=parseInt(value);
             var index = arr.indexOf(value);
             if(index >= 0){
                 return true;
             }
             return false;
         isInArray2(arr, value) {
            value = parseInt(value);
            var index = arr.indexOf(value);
            if (index >= 0) {
               return true;
            }
            return false;
         },
         /*还原初始化*/
         restoreData() {
@@ -229,12 +322,12 @@
         getData() {
            let self = this;
            var city_supplier_ids = '';
            if(uni.getStorageSync('citySupplierRes')){
               let resData=uni.getStorageSync('citySupplierRes');
               self.city_supplier_ids=resData.supplier_ids;
            if (uni.getStorageSync('citySupplierRes')) {
               let resData = uni.getStorageSync('citySupplierRes');
               self.city_supplier_ids = resData.supplier_ids;
               city_supplier_ids = self.city_supplier_ids.join(",")
            }
            let page = self.page;
            let list_rows = self.list_rows;
            let category_id = self.category_id;
@@ -249,12 +342,12 @@
               sortType: sortType,
               sortPrice: sortPrice,
               list_rows: list_rows,
               city_supplier_ids:city_supplier_ids
               city_supplier_ids: city_supplier_ids
            }, function(res) {
               self.loading = false;
               self.listData = self.listData.concat(res.data.list.data);
               self.last_page = res.data.list.last_page;
               self.index_open_city=res.data.store.index_open_city;
               self.index_open_city = res.data.store.index_open_city;
               if (res.data.list.last_page <= 1) {
                  self.no_more = true;
               }
@@ -298,6 +391,256 @@
               path: "/pages/product/category?" + this.getShareUrlParams()
            };
         },
         // 添加到购物车或显示规格选择
         addToCartOrShowSpec(product, index) {
            if (product.spec_type === 20) {
               // 多规格商品,显示规格选择弹窗
               this.showSpecPopup(product);
            } else {
               // 单规格商品,直接添加到购物车
               this.directlyAddToCart(product, index);
            }
         },
         // 处理列表项购物车操作
         handleListItemCartAction(product, index) {
            // 如果是价格面议商品,提示联系客服
            if (product.is_price_negotiable) {
               uni.showModal({
                  title: '温馨提示',
                  content: '该商品为价格面议商品,请联系客服咨询具体价格',
                  confirmText: '联系客服',
                  cancelText: '取消',
                  success: (res) => {
                     if (res.confirm) {
                        // 这里可以跳转到客服页面或显示联系方式
                        uni.showToast({
                           title: '请联系客服咨询',
                           icon: 'none'
                        });
                     }
                  }
               });
            } else {
               // 非价格面议商品,正常添加到购物车
               this.directlyAddToCart(product, index);
            }
         },
         // 添加到购物车(单规格商品)
         addToCart(product, index) {
            this.directlyAddToCart(product, index);
         },
         // 直接添加到购物车(单规格商品)
         directlyAddToCart(product, index) {
            this._post('order.cart/add', {
               product_id: product.product_id,
               total_num: 1,
               spec_sku_id: 0
            }, (res) => {
               if (res.code === 1) {
                  if (!product.cart) {
                     product.cart = {
                        total_num: 0
                     }
                  }
                  // 更新商品的购物车数量
                  product.cart.total_num++;
                  this.listData[index].cart.total_num = product.cart.total_num;
               } else {
                  uni.showToast({
                     title: res.msg,
                     icon: 'none'
                  });
               }
            });
         },
         // 显示规格选择弹窗
         showSpecPopup(product) {
            this.selectedProduct = product;
            this.quantity = 1;
            let url = ''
            //#ifdef H5
            if (this.isWeixin()) {
               url = window.location.href;
            }
            //#endif
            // 获取商品规格数据
            this._get('product.product/detail', {
               product_id: product.product_id,
               url: url,
               visitcode: this.getVisitcode()
            }, (res) => {
               if (res.code === 1) {
                  // 使用正确的路径获取规格数据
                  let specData = res.data.detail.product_multi_spec || res.data.detail;
                  this.$set(this.selectedProduct, 'specData', specData);
                  // 同时设置SKU数据
                  if (res.data.detail.sku) {
                     this.$set(this.selectedProduct, 'sku', res.data.detail.sku);
                  }
                  console.log(this.selectedSpecs);
                  // 初始化选中规格数组
                  if (specData && specData.spec_attr) {
                     this.selectedSpecs = specData.spec_attr.map(specGroup => {
                        return specGroup.spec_items && specGroup.spec_items.length > 0 ? specGroup
                           .spec_items[0].item_id : null;
                     });
                     this.specPopupVisible = true;
                  } else {
                     uni.showToast({
                        title: '获取商品规格失败',
                        icon: 'none'
                     });
                     return;
                  }
               } else {
                  uni.showToast({
                     title: '获取商品规格失败',
                     icon: 'none'
                  });
               }
            });
         },
         // 关闭规格选择弹窗
         closeSpecPopup() {
            this.specPopupVisible = false;
            this.selectedProduct = null;
            this.selectedSpecs = [];
         },
         // 选择规格
         selectSpec(groupIndex, itemId) {
            // 使用 Vue.set 确保响应式更新
            this.$set(this.selectedSpecs, groupIndex, itemId);
         },
         // 增加数量
         increaseQuantity() {
            if (this.quantity < this.currentStock) {
               this.quantity++;
            }
         },
         // 减少数量
         decreaseQuantity() {
            if (this.quantity > 1) {
               this.quantity--;
            }
         },
         // 确认添加到购物车
         confirmAddToCart() {
            // 检查是否选择了所有规格
            if (this.selectedSpecs.includes(null) || this.selectedSpecs.includes(undefined)) {
               uni.showToast({
                  title: '请选择完整的商品规格',
                  icon: 'none'
               });
               return;
            }
            // 构造规格SKU ID
            const specSkuId = this.selectedSpecs.join('_');
            this._post('order.cart/add', {
               product_id: this.selectedProduct.product_id,
               total_num: this.quantity,
               spec_sku_id: specSkuId ? specSkuId : 0
            }, (res) => {
               if (res.code === 1) {
                  // 更新商品的购物车数量
                  const product = this.listData.find(p => p.product_id === this.selectedProduct
                     .product_id);
                  if (product) {
                     if (!product.cart) {
                        product.cart = {
                           total_num: 0
                        }
                     }
                     product.cart.total_num++;
                  }
                  // 关闭弹窗
                  this.closeSpecPopup();
                  uni.showToast({
                     title: '已添加到购物车',
                     icon: 'success'
                  });
               } else {
                  uni.showToast({
                     title: res.msg,
                     icon: 'none'
                  });
               }
            });
         },
         // 增加购物车商品数量
         increaseCart(product, index) {
            this._post('order.cart/add', {
               product_id: product.product_id,
               total_num: 1,
               spec_sku_id: 0
            }, (res) => {
               if (res.code === 1) {
                  product.cart.total_num++;
                  this.listData[index].cart.total_num = product.cart.total_num;
               } else {
                  uni.showToast({
                     title: res.msg,
                     icon: 'none'
                  });
               }
            });
         },
         // 减少购物车商品数量
         decreaseCart(product, index) {
            if (product.cart.total_num <= 1) {
               // 如果数量为1,执行删除操作
               this._post('order.cart/delete', {
                  product_id: product.product_id,
                  cart_id: product.cart.cart_id,
                  spec_sku_id: product.product_sku.spec_sku_id
               }, (res) => {
                  if (res.code === 1) {
                     this.listData[index].cart.total_num = 0;
                  } else {
                     uni.showToast({
                        title: res.msg,
                        icon: 'none'
                     });
                  }
               });
            } else {
               // 否则减少数量
               this._post('order.cart/sub', {
                  product_id: product.product_id,
                  spec_sku_id: product.product_sku.spec_sku_id
               }, (res) => {
                  if (res.code === 1) {
                     product.cart.total_num--;
                     this.listData[index].cart.total_num = product.cart.total_num;
                  } else {
                     uni.showToast({
                        title: res.msg,
                        icon: 'none'
                     });
                  }
               });
            }
         }
      }
   };
</script>
@@ -602,4 +945,261 @@
   .noborder {
      border: none;
   }
</style>
   //购物车
   .cart-action {
      display: flex;
      justify-content: flex-end;
   }
   .cart-btn-add,
   .cart-btn-sub {
      width: 48rpx;
      height: 48rpx;
      border-radius: 50%;
      background: #ff6b6b;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-size: 24rpx;
   }
   .spec-select-btn {
      padding: 2rpx 4rpx;
      background: #ff6b6b;
      color: white;
      border-radius: 24rpx;
      font-size: 24rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 120rpx;
      height: 48rpx;
      position: relative;
   }
   .cart-btn-sub {
      background: #f0f0f0;
      color: #666;
   }
   .cart-number {
      margin: 0 10rpx;
      font-size: 28rpx;
   }
   .cart-number-controller {
      display: flex;
      align-items: center;
   }
   /* 规格选择弹窗样式 */
   .spec-popup {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 1000;
      visibility: hidden;
      opacity: 0;
      transition: all 0.3s ease;
   }
   .spec-popup.visible {
      visibility: visible;
      opacity: 1;
   }
   .popup-mask {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.6);
   }
   .popup-content {
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      background: white;
      border-top-left-radius: 20rpx;
      border-top-right-radius: 20rpx;
      padding: 30rpx;
      transform: translateY(100%);
      transition: transform 0.3s ease;
      max-height: 80%;
      overflow-y: auto;
   }
   .spec-popup.visible .popup-content {
      transform: translateY(0);
   }
   .popup-header {
      display: flex;
      position: relative;
      padding-right: 60rpx;
      margin-bottom: 30rpx;
   }
   .popup-header image {
      width: 160rpx;
      height: 160rpx;
      border-radius: 10rpx;
   }
   .popup-header-info {
      margin-left: 20rpx;
      flex: 1;
   }
   .price {
      font-size: 36rpx;
      color: #ff6b6b;
      font-weight: bold;
   }
   .stock {
      font-size: 24rpx;
      color: #999;
      margin-top: 10rpx;
   }
   .selected-spec {
      font-size: 24rpx;
      color: #666;
      margin-top: 10rpx;
   }
   .popup-header-right {
      position: absolute;
      right: 0;
      top: 0;
      display: flex;
      align-items: center;
      gap: 20rpx;
   }
   .cart-badge {
      position: absolute;
      top: -12rpx;
      right: -12rpx;
      background: #ff4757;
      color: white;
      border-radius: 50%;
      min-width: 32rpx;
      height: 32rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20rpx;
      line-height: 1;
      padding: 0 6rpx;
      z-index: 10;
   }
   .cart-count {
      color: white;
      font-size: 20rpx;
      line-height: 1;
   }
   .close-btn {
      width: 50rpx;
      height: 50rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #999;
   }
   .spec-section {
      margin-bottom: 30rpx;
   }
   .spec-group {
      margin-bottom: 30rpx;
   }
   .spec-group-name {
      font-size: 28rpx;
      font-weight: bold;
      margin-bottom: 20rpx;
   }
   .spec-options {
      display: flex;
      flex-wrap: wrap;
   }
   .spec-option {
      padding: 10rpx 20rpx;
      border: 1rpx solid #ddd;
      border-radius: 10rpx;
      margin-right: 20rpx;
      margin-bottom: 20rpx;
      font-size: 26rpx;
   }
   .spec-option.active {
      border-color: #ff6b6b;
      color: #ff6b6b;
   }
   .quantity-section {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 30rpx;
   }
   .quantity-label {
      font-size: 28rpx;
   }
   .quantity-controller {
      display: flex;
      align-items: center;
   }
   .quantity-btn {
      width: 50rpx;
      height: 50rpx;
      border: 1rpx solid #ddd;
      border-radius: 10rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 24rpx;
   }
   .quantity-btn.disabled {
      opacity: 0.5;
   }
   .quantity-display {
      margin: 0 20rpx;
      font-size: 28rpx;
      min-width: 60rpx;
      text-align: center;
   }
   .action-buttons {
      text-align: center;
   }
   .add-cart-btn {
      width: 100%;
      height: 80rpx;
      background: linear-gradient(90deg, #ff6b6b, #ff8e8e);
      border-radius: 40rpx;
      color: white;
      font-size: 32rpx;
      border: none;
   }
</style>