ソースを参照

首页修改、分类页面修改

chengjunhui 1 ヶ月 前
コミット
6443bb50eb

+ 6 - 0
api/index.js

@@ -123,3 +123,9 @@ export const appVersionNewest_Api = (params) => uni.$uv.http.get(`/version/appVe
 
 // 余额兑换积分
 export const balanceToIntegral_Api = (data) => uni.$uv.http.post(`/business/integralVerifier/balanceToIntegral`, data);
+
+// 获取广告位广告
+export const getAdList_Api = (locationCode) => uni.$uv.http.get(`/ad/list/${locationCode}`, {});
+
+// 根据位置标识码查询金刚区列表
+export const getQuickLinkList_Api = (locationCode) => uni.$uv.http.get(`/quick/link/list/${locationCode}`, {});

+ 9 - 0
api/shop.js

@@ -36,3 +36,12 @@ export const userShoppingCartDel_Api = (ids) => uni.$uv.http.post(`/userShopping
 export const oneClickClear_Api = (data) => uni.$uv.http.post(`/userShoppingCart/oneClickClear`, data);
 // 查询会员某个店铺的购物车列表
 export const cartListBusinessList_Api = (businessId) => uni.$uv.http.get(`/userShoppingCart/businessList/${businessId}`, {  });
+
+// 推荐或新或热销产品列表
+export const shopProductList_Api = (params) => uni.$uv.http.get(`/shop/product/list`, { params });
+
+// 商城分页查询产品列表
+export const shopProductPage_Api = (params) => uni.$uv.http.get(`/shop/product/page`, { params });
+
+// 查询平台分类列表
+export const merchantCategoryTree_Api = (params) => uni.$uv.http.get(`/merchantCategory/tree`, { params });

+ 1 - 1
config/global.config.js

@@ -4,7 +4,7 @@ const CONFIG = {
     // baseUrl: 'http://192.168.0.152:8300/api', // 后台接、口请求地址
     baseUrl: 'http://192.168.0.70:8300/api', // 后台接、口请求地址
 		
-    baseUrl: 'https://shop.xiaocaituan.com/hd-api/api', // 正式接口请求地址
+    // baseUrl: 'https://shop.xiaocaituan.com/hd-api/api', // 正式接口请求地址
     telRegex: /^1[3-9]\d{9}$/, //手机正则
     telIdentity: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))((0[1-9])|([1-2][0-9])|30|31)\d{3}[\dXx]$/, //身份证的正则表达式
     twoMinNum: /^(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2})))$/,

+ 10 - 15
pages/goodsType/index.vue

@@ -8,6 +8,7 @@
         <text class="searchBox_btn">搜索</text>
       </view>
     </view>
+    <view class="" style="height: 13rpx;background-color: rgba(230, 230, 230, 0.4);"></view>
     <view class="goodsBox">
       <scroll-view scroll-y="true" class="goodsBox_lScroll">
         <view class="goodsBox_l">
@@ -28,7 +29,7 @@
                 <view class="item_r_title">{{v.title}}</view>
                 <view class="item_r_price">
                   <text v-if="v.productPaymentMode==1" style="font-size:28rpx;">{{v.minPoints}}积分</text>
-                  <rich-text v-else :nodes="mUtil.priceBigSmall(v.salePrice||v.minSalePrice)"></rich-text>
+                  <rich-text v-else :nodes="$mUtil.priceBigSmall(v.salePrice||v.minSalePrice)"></rich-text>
                 </view>
               </view>
             </view>
@@ -42,23 +43,20 @@
 </template>
 
 <script setup>
-import { productCategoryList, productList } from '@/api/shop.js'
+import { merchantCategoryTree_Api, shopProductPage_Api } from '@/api/shop.js'
 import { ref, getCurrentInstance } from 'vue'
 const { proxy } = getCurrentInstance();
 import { onShow, onLoad } from '@dcloudio/uni-app'
-const mUtil = proxy.$mUtil;
 const status = ref('nomore');//loadmore - 加载前,loading - 加载中,nomore - 没有数据
 const tabTop = ref(0);
 const firstList = ref([]);
 const secondList = ref([]);
-const businessId = ref(null);
 const activeFirst = ref({});
 const activeSecond = ref({});
 const list = ref([]);
 const params = ref({
   pageSize: 10,
   pageNum: 1,
-  businessId: undefined,
   productCategoryId: undefined
 })
 onLoad((options) => {
@@ -67,7 +65,6 @@ onLoad((options) => {
       tabTop.value = res.statusBarHeight + 44;
     },
   });
-  params.value.businessId = uni.getStorageSync('businessId');
   getTypeList()
 })
 const goBack = () => {
@@ -76,15 +73,15 @@ const goBack = () => {
 const clickFirst = (item) => {
   activeFirst.value = item;
   // getTypeList(item.categoryId);
-  secondList.value = item.children
+  secondList.value = item.childrenList || [];
   activeSecond.value = {};
-  params.value.productCategoryId = item.categoryId;
+  params.value.merchantCategoryId = item.categoryId;
   params.value.pageNum = 1;
   getList();
 }
 const clickSecond = (item) => {
   activeSecond.value = item;
-  params.value.productCategoryId = item.categoryId;
+  params.value.merchantCategoryId = item.categoryId;
   getList();
 }
 const goDetails = (item) => {
@@ -101,15 +98,13 @@ const loadMore = () => {
 const getTypeList = (parentId) => {
   if (!parentId) parentId = 0;
   let param = {
-    businessId: params.value.businessId,
     parentId: parentId
   }
-  productCategoryList(param).then(res => {
+  merchantCategoryTree_Api(param).then(res => {
     // 把一级list根据parentId处理成tree结构
-    res.data = mUtil.buildTree(res.data, 'categoryId', 'parentId', 'children')
     firstList.value = res.data;
-    params.value.productCategoryId = res.data[0].categoryId;
-    secondList.value = res.data[0].children
+    params.value.merchantCategoryId = res.data[0].categoryId;
+    secondList.value = res.data[0].childrenList || [];
     activeFirst.value = res.data[0];
     params.value.pageNum = 1;
     getList();
@@ -118,7 +113,7 @@ const getTypeList = (parentId) => {
 // 获取商品列表
 const getList = () => {
   status.value = 'loading';
-  productList(params.value).then(res => {
+  shopProductPage_Api(params.value).then(res => {
     if (params.value.pageNum == 1) {
       list.value = res.rows
     } else {

+ 0 - 6
pages/groupBuying/myGbOrder/details.vue

@@ -818,12 +818,6 @@ const openPopup = () => {
   // topUniPopup.value.open();
 };
 
-const goProductDetails = (id, shopid) => {
-  uni.navigateTo({
-    url: "/pages/product/goods/goods?id=" + id + "&shopid=" + shopid,
-  });
-};
-
 const copy = (code) => {
   uni.setClipboardData({
     data: code,

+ 21 - 24
pages/shop/goodsDetails.vue

@@ -56,8 +56,8 @@
       </view>
     </view>
 
-    <view class="shopBox">
-      <view class="shopBox_l" @click="goShop">
+    <view class="shopBox" @click.stop="goShop">
+      <view class="shopBox_l">
         <view class="shopBox_l_img">
           <image :src="shopDetails.image"></image>
         </view>
@@ -73,7 +73,9 @@
           </view>
         </view>
       </view>
-      <view class="iconfont" @click="goShop">&#xe6c7;</view>
+      <view>
+        <uv-icon name="arrow-right" size="24rpx" color="#000"></uv-icon>
+      </view>
     </view>
     <uv-sticky :offsetTop="tabTop" z-index="100">
       <view class="tab">
@@ -158,11 +160,7 @@ import {
 } from "@/api/shop.js";
 import { ref, getCurrentInstance } from "vue";
 import comment from "@/components/ld-comment/ld-comment.vue";
-import {
-  onLoad,
-  onPullDownRefresh,
-  onReachBottom,
-} from "@dcloudio/uni-app";
+import { onLoad, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
 const { proxy } = getCurrentInstance();
 const mUtil = proxy.$mUtil;
 const list = ref([]);
@@ -201,9 +199,6 @@ onLoad((options) => {
     getDetails(options.id);
     getCommentList();
   }
-  if (options.businessId) {
-    getShopDetails(options.businessId);
-  }
   uni.getSystemInfo({
     success: (res) => {
       tabTop.value = res.statusBarHeight + 44;
@@ -214,7 +209,6 @@ onLoad((options) => {
 onPullDownRefresh(() => {
   queryParams.value.pageNum = 1;
   getDetails(queryParams.value.productId);
-  getShopDetails(queryParams.value.businessId);
   getCommentList();
 });
 // 上拉加载更多
@@ -298,18 +292,21 @@ const getCommentList = () => {
     });
 };
 const getDetails = (id) => {
-  productInfo(id).then((res) => {
-    if (res.data.videoUrl) {
-      res.data.images = [...res.data.images, res.data.videoUrl];
-    }
-    details.value = res.data;
-    getCollectStatus();
-  }).catch((err) => {
-    // console.log(err);
-    setTimeout(() => {
-      uni.navigateBack();
-    }, 500);
-  });
+  productInfo(id)
+    .then((res) => {
+      if (res.data.videoUrl) {
+        res.data.images = [...res.data.images, res.data.videoUrl];
+      }
+      details.value = res.data;
+      getCollectStatus();
+      getShopDetails(res.data.businessId);
+    })
+    .catch((err) => {
+      // console.log(err);
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 500);
+    });
 };
 const getShopDetails = (businessId) => {
   // let id = uni.getStorageSync("businessId");

+ 81 - 0
pages/tabtar/components/ChoiceZone.vue

@@ -0,0 +1,81 @@
+<template>
+  <view
+    class="hxbox u-skeleton-fillet"
+    v-if="hxCateList.length || skeletonShow"
+  >
+    <view class="title">惠选专区</view>
+    <view class="hxClassify">
+      <view
+        class="item"
+        v-for="(item) in skeletonShow ? 1 : hxCateList.slice(0, 4)"
+        @click="jumpHxCate(item)"
+        :key="item.id"
+      >
+        <image
+          mode="widthFix"
+          :src="`${item.cover_image}?x-oss-process=style/w_350`"
+        ></image>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+const props = defineProps({
+  hxCateList: {
+    type: Array,
+    default: () => []
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false
+  }
+});
+
+const emit = defineEmits(['jumpHxCate']);
+
+const jumpHxCate = (item) => {
+  emit('jumpHxCate', item);
+};
+</script>
+
+<style scoped lang="scss">
+.hxbox {
+  padding: 30rpx 30rpx 0;
+  margin: 0rpx 30rpx 30rpx;
+  background: linear-gradient(139deg, #ffe9c4 1%, #edffe7 100%);
+  border-radius: 10rpx;
+
+  .title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #1a1a1a;
+  }
+
+  .hxClassify {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    margin-top: 30rpx;
+    color: #373737;
+
+    .item {
+      text-align: center;
+      margin-bottom: 20rpx;
+
+      image {
+        width: 300rpx !important;
+        height: 210rpx !important;
+      }
+    }
+
+    .item:first-child {
+      margin-left: 0;
+    }
+
+    .item:nth-child(5n + 1) {
+      margin-left: 0;
+    }
+  }
+}
+</style>

+ 251 - 0
pages/tabtar/components/GroupBuy.vue

@@ -0,0 +1,251 @@
+<template>
+  <view class="group-work u-skeleton-fillet">
+    <view
+      class="head-item"
+      @click="goDetail('/pages/research/homepage/groupList')"
+      :style="{ backgroundImage: `url(${bgImage || ''})` }"
+    >
+      <view class="top-left">
+        <view class="top-left-title">拼团购</view>
+        <view class="top-left-tip">精选好物推荐</view>
+      </view>
+      <view class="top-right" style="color: #75c27a">
+        <text>查看更多</text>
+        <!-- <text class="iconfont u-font24">&#xe6c7;</text> -->
+        <uv-icon name="arrow-right" size="24rpx" color="#75c27a"></uv-icon>
+      </view>
+    </view>
+    <view
+      class="group-work-main"
+      v-if="(groupList && groupList.length > 0) || skeletonShow"
+    >
+      <view
+        class="middle_item"
+        v-for="item in skeletonShow ? 1 : groupList"
+        :key="item.id"
+      >
+        <view
+          class="middle"
+          @click="
+            goProductDetails('/pages/product/goods/puzzleGoods?id=' + item.id)
+          "
+        >
+          <view class="middle-left">
+            <image
+              v-if="item && item.cover"
+              :src="`${item.cover}?x-oss-process=style/w_350`"
+              mode="aspectFill"
+            >
+            </image>
+          </view>
+          <view class="middle-right">
+            <view class="middle-title u-text2">{{ item.title }}</view>
+            <view class="division">
+              <view class="middle-num">
+                <view class="contain">
+                  <view class="large">
+                    <rich-text
+                      :nodes="$mUtil.priceBigSmall(item.min_price)"
+                    ></rich-text>
+                  </view>
+                  <view class="small">¥ {{ item.min_sale_price }}</view>
+                </view>
+                <view class="three"
+                  >{{ item.group_people_num }}人团,已拼
+                  <text style="color: red"> {{ item.result_sale_num }}</text>
+                  件</view
+                >
+              </view>
+              <view class="button">去拼团</view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <noData v-else :config="{ top: 1, content: '暂无商品~' }"></noData>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from "vue";
+const { proxy } = getCurrentInstance();
+const $mUtil = proxy.$mUtil;
+
+const props = defineProps({
+  groupList: {
+    type: Array,
+    default: () => [],
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false,
+  },
+  bgImage: {
+    type: String,
+    default: "",
+  },
+});
+
+const emit = defineEmits(["goDetail", "goProductDetails"]);
+
+const goDetail = (url) => {
+  emit("goDetail", url);
+};
+
+const goProductDetails = (url) => {
+  emit("goProductDetails", url);
+};
+</script>
+
+<style scoped lang="scss">
+.group-work {
+  margin: 30rpx 0;
+  background: #d4f2e8;
+  padding-bottom: 30rpx;
+  border-radius: 10rpx;
+  box-sizing: border-box;
+  overflow: hidden;
+
+  .group-work-main {
+    padding: 30rpx;
+    margin: 20rpx 30rpx 0 30rpx;
+    box-sizing: border-box;
+    background-color: #ffffff;
+    border-radius: 10rpx;
+  }
+}
+
+.head-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 27rpx 30rpx;
+  box-sizing: border-box;
+  overflow: hidden;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+
+  .top-left {
+    .top-left-title {
+      font-size: 36rpx;
+      font-family: PingFang SC, PingFang SC-Bold;
+      font-weight: 700;
+    }
+    .top-left-tip {
+      font-size: 24rpx;
+      font-family: PingFang SC, PingFang SC-Regular;
+      font-weight: 400;
+      margin-top: 5rpx;
+    }
+  }
+
+  .top-right {
+    width: 165rpx;
+    height: 67rpx;
+    line-height: 67rpx;
+    background: #ffffff;
+    border-radius: 34rpx;
+    box-sizing: border-box;
+    font-size: 24rpx;
+    font-family: PingFang SC, PingFang SC-Regular;
+    font-weight: 400;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+
+.middle_item {
+  padding: 0 30rpx;
+}
+
+.middle_item:last-child {
+  border: none;
+}
+
+.middle {
+  display: flex;
+  border-bottom: 1rpx solid #f7f7f7;
+  padding-bottom: 40rpx;
+  padding-top: 42rpx;
+
+  .middle-left {
+    margin-right: 20rpx;
+
+    image {
+      width: 226rpx;
+      height: 226rpx;
+      border-radius: 10rpx;
+    }
+  }
+
+  .middle-right {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    margin-top: 10rpx;
+    padding-bottom: 10rpx;
+
+    .middle-title {
+      font-size: 28rpx;
+      margin-top: 10rpx;
+      color: #181818;
+      font-weight: 400;
+      line-height: 36rpx;
+    }
+
+    .division {
+      display: flex;
+      width: 100%;
+      padding-bottom: 10rpx;
+      justify-content: space-between;
+    }
+
+    .middle-num {
+      flex: 1;
+
+      .contain {
+        display: flex;
+        align-items: flex-end;
+      }
+
+      .three {
+        font-size: 24rpx;
+        font-weight: Regular;
+        color: #333333;
+        line-height: 24rpx;
+        margin-top: 8rpx;
+      }
+
+      .large {
+        color: #00bf5a;
+        font-size: 36rpx;
+        font-weight: Bold;
+        line-height: 24rpx;
+      }
+
+      .small {
+        margin-left: 16rpx;
+        font-size: 22rpx;
+        font-weight: 500;
+        color: #cccccc;
+        line-height: 24rpx;
+        text-decoration: line-through;
+      }
+    }
+
+    .button {
+      width: 130rpx;
+      height: 60rpx;
+      text-align: center;
+      line-height: 60rpx;
+      color: #ffffff;
+      font-size: 28rpx;
+      background: #069d4d;
+      border-radius: 32rpx;
+    }
+  }
+}
+</style>

+ 197 - 0
pages/tabtar/components/GuessYouLike.vue

@@ -0,0 +1,197 @@
+<template>
+  <view class="guess">
+    <view
+      class="head-item"
+      @click="goDetail('/pages/goodsType/index', false, true)"
+      :style="{ backgroundImage: `url(${bgImage || ''})` }"
+    >
+      <view class="top-left">
+        <view class="top-left-title">猜你喜欢</view>
+        <view class="top-left-tip">精选好物推荐</view>
+      </view>
+      <view class="top-right" style="color: #75c27a">
+        查看更多
+        <!-- <text class="iconfont u-font24">&#xe6c7;</text> -->
+        <uv-icon name="arrow-right" size="24rpx" color="#75c27a"></uv-icon>
+
+      </view>
+    </view>
+
+    <view
+      class="guess-item"
+      v-if="(goodsList && goodsList.length > 0) || skeletonShow"
+    >
+      <view
+        class="item u-skeleton-fillet"
+        @click="
+          goProductDetails('/pages/shop/goodsDetails?id=' + item.productId)
+        "
+        v-for="(item, index) in goodsList.length > 0 ? goodsList : 10"
+        :key="index"
+      >
+        <image
+          v-if="item && item.cover"
+          :src="`${item.cover}?x-oss-process=style/w_350`"
+          mode="aspectFill"
+        ></image>
+        <view style="padding-bottom: 40rpx">
+          <view class="name" style="flex: 1">{{
+            item.title || "加载中"
+          }}</view>
+          <view class="number">
+            <view class="number-red">
+              <rich-text
+                :nodes="$mUtil.priceBigSmall(item.min_sale_price)"
+              ></rich-text>
+            </view>
+            <view class="number-grey"
+              >¥{{ item.max_market_price || "加载中" }}</view
+            >
+          </view>
+        </view>
+      </view>
+    </view>
+    <noData v-else :config="{ top: 1, content: '暂无商品~' }"></noData>
+    <loadMore v-if="goodsList.length > 0" :status="status"></loadMore>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from "vue";
+const { proxy } = getCurrentInstance();
+const $mUtil = proxy.$mUtil;
+
+const props = defineProps({
+  goodsList: {
+    type: Array,
+    default: () => []
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false
+  },
+  bgImage: {
+    type: String,
+    default: ''
+  },
+  status: {
+    type: String,
+    default: 'more'
+  }
+});
+
+const emit = defineEmits(['goDetail', 'goProductDetails']);
+
+const goDetail = (url, isNeedLogin, tabShow) => {
+  emit('goDetail', url, isNeedLogin, tabShow);
+};
+
+const goProductDetails = (url) => {
+  emit('goProductDetails', url);
+};
+</script>
+
+<style scoped lang="scss">
+.guess {
+  background-color: #dcffd5;
+  box-sizing: border-box;
+  border-radius: 10rpx;
+  overflow: hidden;
+
+  .guess-item {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    padding: 20rpx 30rpx;
+
+    .item {
+      margin-bottom: 36rpx;
+      background-color: #ffffff;
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+
+      image {
+        width: 336rpx;
+        height: 336rpx;
+        border-radius: 18rpx 18rpx 0 0;
+      }
+
+      .name {
+        margin: 14rpx 20rpx 6rpx 20rpx;
+        width: 296rpx;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        -webkit-box-orient: vertical;
+        word-wrap: break-word;
+        word-break: break-all;
+        white-space: normal !important;
+      }
+
+      .number {
+        display: flex;
+        align-items: flex-end;
+        margin: 8rpx 20rpx 0 20rpx;
+
+        .number-red {
+          color: #00bf5a;
+          font-size: 36rpx;
+          font-weight: 700;
+        }
+
+        .number-grey {
+          color: #999999;
+          font-size: 22rpx;
+          font-weight: 400;
+          text-decoration: line-through;
+          margin-left: 16rpx;
+          padding-bottom: 4rpx;
+        }
+      }
+    }
+  }
+}
+
+.head-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 27rpx 30rpx;
+  box-sizing: border-box;
+  overflow: hidden;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+
+  .top-left {
+    .top-left-title {
+      font-size: 36rpx;
+      font-family: PingFang SC, PingFang SC-Bold;
+      font-weight: 700;
+    }
+    .top-left-tip {
+      font-size: 24rpx;
+      font-family: PingFang SC, PingFang SC-Regular;
+      font-weight: 400;
+      margin-top: 5rpx;
+    }
+  }
+
+  .top-right {
+    width: 165rpx;
+    height: 67rpx;
+    line-height: 67rpx;
+    background: #ffffff;
+    border-radius: 34rpx;
+    box-sizing: border-box;
+    font-size: 24rpx;
+    font-family: PingFang SC, PingFang SC-Regular;
+    font-weight: 400;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 201 - 0
pages/tabtar/components/HotRecommend.vue

@@ -0,0 +1,201 @@
+<template>
+  <view class="hot">
+    <view
+      class="head-item"
+      @click="goDetail('/pages/goodsType/index', false, true)"
+      :style="{ backgroundImage: `url(${bgImage || ''})` }"
+    >
+      <view class="top-left">
+        <view class="top-left-title">热门推荐</view>
+        <view class="top-left-tip">精选好物推荐</view>
+      </view>
+      <view class="top-right" style="color: #db7d75">
+        查看更多
+        <!-- <text class="iconfont u-font24">&#xe6c7;</text> -->
+        <uv-icon name="arrow-right" size="24rpx" color="#db7d75"></uv-icon>
+      </view>
+    </view>
+    <view
+      class="hot-item u-skeleton-fillet"
+      v-if="(recommendList && recommendList.length > 0) || skeletonShow"
+    >
+      <view
+        class="item"
+        v-for="item in recommendList.length > 0 ? recommendList : 2"
+        :key="item.id"
+        @click="
+          goProductDetails('/pages/shop/goodsDetails?id=' + item.productId)
+        "
+      >
+        <!-- ?x-oss-process=style/w_350 -->
+        <image
+          v-if="item && item.coverImage"
+          :src="`${item.coverImage}`"
+          mode="aspectFill"
+        ></image>
+        <div class="title-price">
+          <view class="hot-introduce" v-if="item && item.title">{{
+            item.title
+          }}</view>
+          <view class="hot-number">
+            <view class="hot-large">
+              <rich-text
+                :nodes="$mUtil.priceBigSmall(item.minSalePrice)"
+              ></rich-text>
+            </view>
+            <view class="hot-small">¥{{ item.maxSalePrice }}</view>
+          </view>
+        </div>
+      </view>
+    </view>
+    <noData v-else :config="{ top: 1, content: '暂无商品~' }"></noData>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from "vue";
+
+
+const props = defineProps({
+  recommendList: {
+    type: Array,
+    default: () => []
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false
+  },
+  bgImage: {
+    type: String,
+    default: ''
+  }
+});
+
+const emit = defineEmits(['goDetail', 'goProductDetails']);
+
+const goDetail = (url, isNeedLogin, tabShow) => {
+  emit('goDetail', url, isNeedLogin, tabShow);
+};
+
+const goProductDetails = (url) => {
+  emit('goProductDetails', url);
+};
+</script>
+
+<style scoped lang="scss">
+.hot {
+  background: #ffe7e7;
+  box-sizing: border-box;
+  border-radius: 10rpx;
+  overflow: hidden;
+
+  .hot-item {
+    display: flex;
+    overflow: scroll;
+    white-space: nowrap;
+    padding: 0 30rpx 40rpx 30rpx;
+    box-sizing: border-box;
+    margin-top: 27rpx;
+
+    .hot-number {
+      display: flex;
+      align-items: flex-end;
+      margin-top: 20rpx;
+    }
+
+    .hot-large {
+      color: #00bf5a;
+      font-size: 36rpx;
+      font-weight: Bold;
+      line-height: 24rpx;
+    }
+
+    .hot-small {
+      font-size: 22rpx;
+      font-weight: Medium;
+      line-height: 24rpx;
+      color: #cccccc;
+      margin-left: 16rpx;
+      text-decoration: line-through;
+    }
+
+    .title-price {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      height: 140rpx;
+    }
+
+    .hot-introduce {
+      overflow: hidden;
+      word-break: break-all;
+      margin-top: 24rpx;
+      font-size: 24rpx;
+      font-weight: Regular;
+      line-height: 34rpx;
+      color: #333333;
+      text-overflow: ellipsis;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      word-wrap: break-word;
+      white-space: normal !important;
+      width: 252rpx;
+    }
+
+    .item {
+      padding: 20rpx;
+      background-color: #ffffff;
+      border-radius: 16rpx;
+      margin-right: 20rpx;
+
+      image {
+        width: 252rpx;
+        height: 252rpx;
+        border-radius: 16rpx;
+      }
+    }
+  }
+}
+
+.head-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 27rpx 30rpx;
+  box-sizing: border-box;
+  overflow: hidden;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+
+  .top-left {
+    .top-left-title {
+      font-size: 36rpx;
+      font-family: PingFang SC, PingFang SC-Bold;
+      font-weight: 700;
+    }
+    .top-left-tip {
+      font-size: 24rpx;
+      font-family: PingFang SC, PingFang SC-Regular;
+      font-weight: 400;
+      margin-top: 5rpx;
+    }
+  }
+
+  .top-right {
+    width: 165rpx;
+    height: 67rpx;
+    line-height: 67rpx;
+    background: #ffffff;
+    border-radius: 34rpx;
+    box-sizing: border-box;
+    font-size: 24rpx;
+    font-family: PingFang SC, PingFang SC-Regular;
+    font-weight: 400;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 238 - 0
pages/tabtar/components/MonthlyBestSeller.vue

@@ -0,0 +1,238 @@
+<template>
+  <view class="new-products u-skeleton-fillet">
+    <view
+      class="head-item"
+      @click="goDetail('/pages/goodsType/index', false, true)"
+      :style="{ backgroundImage: `url(${bgImage || ''})` }"
+    >
+      <view class="top-left">
+        <view class="top-left-title">热销产品</view>
+        <view class="top-left-tip">新品上市优惠热销</view>
+      </view>
+      <view class="top-right" style="color: #e2ac40">
+        查看更多
+        <!-- <text class="iconfont u-font24">&#xe6c7;</text> -->
+        <uv-icon name="arrow-right" size="24rpx" color="#e2ac40"></uv-icon>
+
+      </view>
+    </view>
+    <view
+      class="new-products-main"
+      v-if="(hotList && hotList.length > 0) || skeletonShow"
+    >
+      <swiper
+        class="month-list"
+        :indicator-dots="false"
+        :autoplay="false"
+        :display-multiple-items="itemsDis"
+        @change="imgActiveFun"
+        :circular="false"
+      >
+        <swiper-item
+          class="item"
+          @click="
+            goProductDetails('/pages/shop/goodsDetails?id=' + item.productId)
+          "
+          v-for="item in hotList.length > 0 ? hotList : 1"
+          :key="item.id"
+        >
+          <!-- ?x-oss-process=style/w_350 -->
+          <image
+            v-if="item && item.coverImage"
+            :src="`${item.coverImage}`"
+            mode="aspectFill"
+          >
+          </image>
+          <view class="list-num">
+            <rich-text
+              :nodes="$mUtil.priceBigSmall(item.minSalePrice)"
+            ></rich-text>
+          </view>
+        </swiper-item>
+      </swiper>
+      <view class="slide-box">
+        <view class="dotBox" v-if="hotList.length > 0">
+          <view
+            class="dotBox_item"
+            v-for="(v, i) in lineList"
+            :key="i"
+            :class="{ active_dotBox: activeBannerIndex == i }"
+          ></view>
+        </view>
+      </view>
+    </view>
+    <noData v-else :config="{ top: 1, content: '暂无商品~' }"></noData>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from "vue";
+const { proxy } = getCurrentInstance();
+const $mUtil = proxy.$mUtil;
+
+const props = defineProps({
+  hotList: {
+    type: Array,
+    default: () => []
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false
+  },
+  bgImage: {
+    type: String,
+    default: ''
+  },
+  itemsDis: {
+    type: Number,
+    default: 1
+  },
+  lineList: {
+    type: Number,
+    default: 0
+  },
+  activeBannerIndex: {
+    type: Number,
+    default: 0
+  }
+});
+
+const emit = defineEmits(['goDetail', 'goProductDetails', 'imgActiveFun']);
+
+const goDetail = (url, isNeedLogin, tabShow) => {
+  emit('goDetail', url, isNeedLogin, tabShow);
+};
+
+const goProductDetails = (url) => {
+  emit('goProductDetails', url);
+};
+
+const imgActiveFun = (e) => {
+  emit('imgActiveFun', e);
+};
+</script>
+
+<style scoped lang="scss">
+.new-products {
+  margin: 30rpx 0;
+  background: rgba(249, 244, 225, 0.7);
+  padding-bottom: 30rpx;
+  box-sizing: border-box;
+  border-radius: 10rpx;
+  overflow: hidden;
+
+  .new-products-main {
+    box-sizing: border-box;
+    margin: 20rpx 30rpx;
+    background-color: #ffffff;
+    padding-bottom: 20rpx;
+    border-radius: 10rpx;
+  }
+}
+
+.head-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 27rpx 30rpx;
+  box-sizing: border-box;
+  overflow: hidden;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+
+  .top-left {
+    .top-left-title {
+      font-size: 36rpx;
+      font-family: PingFang SC, PingFang SC-Bold;
+      font-weight: 700;
+    }
+    .top-left-tip {
+      font-size: 24rpx;
+      font-family: PingFang SC, PingFang SC-Regular;
+      font-weight: 400;
+      margin-top: 5rpx;
+    }
+  }
+
+  .top-right {
+    width: 165rpx;
+    height: 67rpx;
+    line-height: 67rpx;
+    background: #ffffff;
+    border-radius: 34rpx;
+    box-sizing: border-box;
+    font-size: 24rpx;
+    font-family: PingFang SC, PingFang SC-Regular;
+    font-weight: 400;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+
+.month-list {
+  position: relative;
+  padding-bottom: 20rpx;
+  height: 350rpx;
+
+  ::v-deep uni-swiper {
+    height: 350rpx;
+  }
+
+  .item {
+    margin: 28rpx 0;
+    text-align: center;
+    height: 350rpx;
+    width: 217rpx;
+
+    image {
+      width: 217rpx;
+      margin: 0 auto;
+      height: 217rpx;
+      border-radius: 10rpx;
+    }
+
+    .list-num {
+      margin-top: 16rpx;
+      font-size: 36rpx;
+      line-height: 24rpx;
+      color: #00bf5a;
+      font-weight: bold;
+      text-align: center;
+    }
+  }
+
+  ::v-deep swiper-item {
+    width: 33.333%;
+  }
+}
+
+.slide-box {
+  display: flex;
+  justify-content: center;
+}
+
+.dotBox {
+  height: 14rpx;
+  overflow: hidden;
+  border-radius: 14rpx;
+  background-color: #ededed;
+  display: flex;
+  justify-content: center;
+
+  .dotBox_item {
+    width: 45rpx;
+    height: 14rpx;
+    border-radius: 2rpx;
+    background-color: #ededed;
+    transition: all 0.5s;
+  }
+
+  .active_dotBox {
+    width: 45rpx;
+    background: linear-gradient(109deg, #fb6b3e 20%, #feaf6b 85%);
+    transition: all 0.5s;
+  }
+}
+</style>

+ 246 - 0
pages/tabtar/components/Seckill.vue

@@ -0,0 +1,246 @@
+<template>
+  <view
+    class="seckill u-skeleton-fillet"
+    v-if="(currSeckill && currSeckill.length > 0) || skeletonShow"
+  >
+    <view class="seckill-item">
+      <view
+        class="seckill-top"
+        @click="goDetail('/pages/research/nearby/timeLimit')"
+      >
+        <view class="left">限时秒杀</view>
+        <view class="line" v-if="currTime"></view>
+        <view class="time" v-if="currTime">{{ currTime }}点场 剩余</view>
+        <view class="reciprocal" v-if="currTime">
+          <uni-countdown
+            :backgroundColor="'none'"
+            @timeup="overDown2"
+            :color="'#fff'"
+            :splitorColor="'#fff'"
+            :show-day="time2[0] > 0"
+            :day="time2[0]"
+            :hour="time2[1]"
+            :minute="time2[2]"
+            :second="time2[3]"
+          >
+          </uni-countdown>
+        </view>
+      </view>
+      <view
+        class="option"
+        v-if="(currSeckill && currSeckill.length > 0) || skeletonShow"
+      >
+        <swiper
+          :indicator-dots="false"
+          :autoplay="false"
+          :display-multiple-items="itemsDis2"
+          @change="imgActiveFun2"
+          style="height: 220rpx"
+          circular="true"
+        >
+          <swiper-item v-for="(item, key) in currSeckill" :key="key">
+            <div class="fa-item">
+              <view
+                class="option-item"
+                v-for="itemSon in item"
+                @click="
+                  goProductDetails(
+                    '/pages/product/goods/seckillGoods?id=' + itemSon.id
+                  )
+                "
+                :key="itemSon.id"
+              >
+                <image
+                  v-if="itemSon && itemSon.cover"
+                  :src="`${itemSon.cover}?x-oss-process=style/w_350`"
+                  mode="aspectFill"
+                >
+                </image>
+                <view class="number">
+                  <rich-text
+                    :nodes="$mUtil.priceBigSmall(itemSon.min_price)"
+                  ></rich-text>
+                </view>
+              </view>
+            </div>
+          </swiper-item>
+        </swiper>
+        <view class="slide-box">
+          <view class="dotBox" v-if="currSeckill.length > 1">
+            <view
+              class="dotBox_item"
+              v-for="(v, i) in currSeckill"
+              :key="i"
+              :class="{ active_dotBox: activeBannerIndex2 == i }"
+            ></view>
+          </view>
+        </view>
+      </view>
+      <noData v-else :config="{ top: 1, content: '暂无商品~' }"></noData>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance } from "vue";
+const { proxy } = getCurrentInstance();
+const $mUtil = proxy.$mUtil;
+
+const props = defineProps({
+  currSeckill: {
+    type: Array,
+    default: () => []
+  },
+  skeletonShow: {
+    type: Boolean,
+    default: false
+  },
+  currTime: {
+    type: [Number, String],
+    default: null
+  },
+  time2: {
+    type: Array,
+    default: () => [0, 0, 0, 0]
+  },
+  itemsDis2: {
+    type: Number,
+    default: 1
+  },
+  activeBannerIndex2: {
+    type: Number,
+    default: 0
+  }
+});
+
+const emit = defineEmits(['goDetail', 'overDown2', 'imgActiveFun2', 'goProductDetails']);
+
+const goDetail = (url) => {
+  emit('goDetail', url);
+};
+
+const overDown2 = () => {
+  emit('overDown2');
+};
+
+const imgActiveFun2 = (e) => {
+  emit('imgActiveFun2', e);
+};
+
+const goProductDetails = (url) => {
+  emit('goProductDetails', url);
+};
+</script>
+
+<style scoped lang="scss">
+.seckill {
+  margin: 30rpx 30rpx 50rpx;
+  border-radius: 10rpx;
+  overflow: hidden;
+
+  .seckill-item {
+    border-radius: 16rpx;
+    background-color: #ffffff;
+    padding-bottom: 26rpx;
+
+    .option {
+      position: relative;
+      margin-top: 20rpx;
+      padding: 0 29rpx;
+
+      .fa-item {
+        display: flex;
+      }
+
+      .option-item:first-child {
+        margin-left: 0;
+      }
+
+      .option-item {
+        margin-left: 24rpx;
+
+        image {
+          width: 140rpx;
+          height: 140rpx;
+        }
+
+        .number {
+          margin-top: 4rpx;
+          text-align: center;
+          font-size: 36rpx;
+          font-weight: Bold;
+          line-height: 24rpx;
+          color: #333333;
+          width: 140rpx;
+        }
+      }
+    }
+
+    .seckill-top {
+      padding: 24rpx 26rpx;
+      display: flex;
+      align-items: center;
+      border-bottom: 1rpx solid #f7f7f7;
+      background: linear-gradient(180deg, #d4ecdf, rgba(204, 235, 217, 0));
+
+      .left {
+        font-size: 36rpx;
+        font-weight: Bold;
+        color: #1a1a1a;
+        margin-right: 14rpx;
+      }
+
+      .line {
+        width: 1rpx;
+        height: 32rpx;
+        background-color: #707070;
+      }
+
+      .time {
+        margin-left: 16rpx;
+        color: #666666;
+        font-size: 24rpx;
+        font-weight: Regular;
+      }
+
+      .reciprocal {
+        color: #ffffff;
+        padding: 0rpx 14rpx;
+        background-color: #ff0000;
+        font-size: 24rpx;
+        font-weight: Bold;
+        border-radius: 30rpx;
+        margin-left: 30rpx;
+      }
+    }
+  }
+}
+
+.slide-box {
+  display: flex;
+  justify-content: center;
+}
+
+.dotBox {
+  height: 14rpx;
+  overflow: hidden;
+  border-radius: 14rpx;
+  background-color: #ededed;
+  display: flex;
+  justify-content: center;
+
+  .dotBox_item {
+    width: 45rpx;
+    height: 14rpx;
+    border-radius: 2rpx;
+    background-color: #ededed;
+    transition: all 0.5s;
+  }
+
+  .active_dotBox {
+    width: 45rpx;
+    background: linear-gradient(109deg, #fb6b3e 20%, #feaf6b 85%);
+    transition: all 0.5s;
+  }
+}
+</style>

+ 171 - 0
pages/tabtar/home copy.vue

@@ -0,0 +1,171 @@
+<template>
+  <view :class="['home-box', isBindShop ? 'pages-bg-2' : '']">
+    <template v-if="isGetBindShopLoading">
+      <!-- 初次进入,不知道有没有绑定店铺 -->
+      <HomeHint />
+    </template>
+    <template v-else-if="isBindShop">
+      <!-- 绑定店铺 -->
+      <uv-navbar
+        title="商城"
+        placeholder
+        leftIcon=""
+        :title-style="NavTitleStyle"
+        :bg-color="NavBgColor"
+      />
+      <template v-if="BannerList && BannerList.length">
+        <SwitchBanner :banner-list="BannerList" />
+      </template>
+      <ShopCard :shop-vo="BusinessShopVo" isSwitchShop isBindMore @SwitchStore="SwitchStore"/>
+      <view style="width: 100%; height: 30rpx; flex-shrink: 0" />
+      <view class="shop-content-box">
+        <shopContent ref="shopContentRef" :height="ContentHeight" :shop-vo="BusinessShopVo" />
+      </view>
+    </template>
+    <template v-else>
+      <!-- 未绑定店铺 -->
+      <HomeHint />
+    </template>
+
+    <!-- #ifdef APP-PLUS -->
+    <!-- 版本更新 -->
+    <VersionUpdate />
+    <!-- #endif -->
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, getCurrentInstance, nextTick, watch } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+import shopContent from "./components/shopContent.vue";
+import SwitchBanner from "./components/SwitchBanner.vue";
+import HomeHint from "./components/HomeHint.vue";
+import { getUserBusinessRoleApi } from "@/api/home.ts";
+import { getBusinessInfoId_Api } from "@/api/shop";
+import { usePages } from "@/hooks/usePages.ts";
+const { NavBgColor, NavTitleStyle } = usePages(1);
+// import notJoin from './notJoin.vue'
+// import goodsType from './goodsType.vue'
+const instance = getCurrentInstance();
+const ContentHeight = ref(0);
+const isBindShop = ref(false); // 是否绑定店铺
+const isGetBindShopLoading = ref(false);
+const BusinessShopVo = ref(null); // 店铺信息
+const isBindMore = ref(false); // 是否绑定多个店铺
+const BannerList = ref(null);
+const shopContentRef = ref(null);
+const apiToken = ref("");
+const originalBusinessId = ref(null);  // 原店铺id,切换店铺时使用,防止重复调用购物车数据
+
+watch(
+  () => isGetBindShopLoading.value,
+  (newStatus) => {
+    if (newStatus) {
+      uni.showLoading({
+        mask: true,
+      });
+    } else {
+      uni.hideLoading();
+    }
+  }
+);
+// 获取会员在店铺详细信息
+const getUserBusinessRole = async (businessId = '') => {
+//   let businessId = uni.getStorageSync("businessId") || "";
+  try {
+    isGetBindShopLoading.value = true;
+    let url = businessId ? getBusinessInfoId_Api : getUserBusinessRoleApi;
+    const BindInfo = await url(businessId);
+    if (!BindInfo) {
+      // 未绑定店铺
+      isBindShop.value = false;
+    } else {
+      const { businessVo, isBoundMore } = BindInfo.data;
+      let objData = businessId ? BindInfo.data : businessVo;
+      BusinessShopVo.value = objData;
+      originalBusinessId.value = objData.businessId;
+      let list = objData.innerImages || [];
+      BannerList.value = list.map((item) => item + '?x-oss-process=style/w_700');
+      isBindMore.value = businessId ? true : isBoundMore;
+      // 已绑定店铺
+      isBindShop.value = true;
+      nextTick(() => {
+        initShopContentHeight();
+      });
+    }
+  } catch (error) {
+    //TODO handle the exception
+  } finally {
+    isGetBindShopLoading.value = false;
+  }
+};
+
+const initShopContentHeight = (num = 1) => {
+  nextTick(() => {
+    try {
+      const query = uni.createSelectorQuery().in(instance);
+      query
+        .select(".shop-content-box")
+        .boundingClientRect((data) => {
+          ContentHeight.value = data.height;
+        })
+        .exec();
+    } catch (error) {
+      if (num >= 10) {
+        return;
+      } else {
+        const delayTimeout = setTimeout(() => {
+          clearTimeout(delayTimeout);
+          initShopContentHeight((num += 1));
+        }, 100);
+      }
+    }
+  });
+};
+// 切换店铺
+const SwitchStore = (businessId) => {
+  originalBusinessId.value = null;
+	getUserBusinessRole(businessId);
+};
+
+onShow(() => {
+  let originalApiToken = uni.getStorageSync("apiToken") || "";
+  if (apiToken.value != originalApiToken) {
+    apiToken.value = originalApiToken;
+    isBindShop.value = false;
+    if (originalApiToken) {
+      getUserBusinessRole();
+      return;
+    } else {
+      isBindShop.value = false;
+    }
+  }
+  if (shopContentRef.value && originalApiToken && BusinessShopVo.value && originalBusinessId.value) {
+    shopContentRef.value.cartListBusinessList(BusinessShopVo.value.businessId);
+  }
+});
+// onMounted(() => {
+//   getUserBusinessRole();
+// });
+</script>
+
+<style lang="scss" scoped>
+.home-box {
+  background-color: #f2f3f5;
+  width: 100%;
+  height: 100vh;
+  /* #ifdef H5 */
+
+  height: calc(100vh - 55px);
+  /* #endif */
+  display: flex;
+  flex-direction: column;
+
+  .shop-content-box {
+    flex: 1;
+    width: 100%;
+    border-radius: 24rpx 24rpx 0rpx 0rpx;
+    overflow: hidden;
+  }
+}
+</style>

File diff suppressed because it is too large
+ 912 - 145
pages/tabtar/home.vue


+ 1 - 1
util/index.js

@@ -686,7 +686,7 @@ export default {
     let shopid = item.shop_id
     if (type == "goods") {
       uni.navigateTo({
-        url: "/pages/product/goods/goods?id=" + id + "&shopid=" + shopid,
+        url: "/pages/shop/goodsDetails?id=" + id + "&shopid=" + shopid,
       });
     } else if (type == "service") {
       uni.navigateTo({