Explorar o código

feat(api): 添加购物车和分类相关API类型定义和接口

- 添加channelTopId字段到userInfo接口
- 添加latitude和longitude字段到用户信息接口
- 添加购物车选中商品相关字段到myShoppingCart接口
- 新增AppletOrderSkuVo和SkuConfirmVo接口定义
- 新增smqjhCategoryList和CategoryListVO分类相关接口
- 添加skuOrderConfirm和getCategoryList API接口定义

feat(cart): 实现购物车多店铺商品选择功能

- 添加useSmqjhCartStore购物车状态管理
- 实现全选和部分选择商品功能
- 添加商品增减和删除操作
- 支持多店铺商品管理
- 添加商品售罄和删除状态显示

feat(classify): 实现分类页面动态数据加载

- 替换静态分类数据为API获取
- 添加分类层级展示功能
- 实现左右联动分类导航
- 支持分类图片和名称显示

fix(store): 重构购物车存储和用户信息更新

- 将useXsbCartStore重命名为useSmqjhCartStore
- 添加isAddingCart状态防止重复操作
- 更新channelId为channelTopId字段
- 优化购物车列表初始化逻辑

chore(config): 更新测试环境配置地址

- 修改trial环境API地址为新测试服务器
- 注释旧测试环境地址配置
zhangtao hai 3 días
pai
achega
9a2f4fc0bf

+ 136 - 0
src/api/api.type.d.ts

@@ -1,5 +1,9 @@
 namespace Api {
   interface userInfo {
+    /**
+     * 顶级id
+     */
+    channelTopId?: number
     /**
      * 运费(单位:分)
      */
@@ -44,6 +48,8 @@ namespace Api {
     id: number
     memberId?: number
     province?: string
+    latitude?: number
+    longitude?: number
     [property: string]: any
   }
 
@@ -744,6 +750,14 @@ namespace Api {
     [property: string]: any
   }
   interface myShoppingCart {
+    /**
+     * 选中的商品
+     */
+    allGoods: number[]
+    /**
+     *  是否全选
+     */
+    AllShopGoods: boolean
     /**
      * 平台
      */
@@ -836,5 +850,127 @@ namespace Api {
     skuList: CartSkuVo[]
     [property: string]: any
   }
+  interface AppletOrderSkuVo {
+    /**
+     * offsetPoints
+     */
+    offsetPoints?: number
+    /**
+     * price
+     */
+    price?: number
+    /**
+     * shopName
+     */
+    shopName?: string
+    /**
+     * sku
+     */
+    sku?: SkuConfirmVo
+    [property: string]: any
+  }
+  interface SkuConfirmVo {
+    /**
+     * 0 正常 1 已被删除
+     */
+    isDelete?: string
+    /**
+     * pic
+     */
+    pic?: string
+    /**
+     * price
+     */
+    price?: number
+    /**
+     * 门店库存
+     */
+    shopSkuStocks?: string
+    /**
+     * sku_id
+     */
+    skuId?: number
+    /**
+     * skuName
+     */
+    skuName?: string
+    /**
+     * spec
+     */
+    spec?: string
+    [property: string]: any
+  }
+  interface smqjhCategoryList {
+    /**
+     * 跳转链接
+     */
+    adLink?: string
+    /**
+     * 广告图片
+     */
+    adPicture?: string
+    /**
+     * 子级分类列表
+     */
+    children?: CategoryListVO[]
+    /**
+     * 关联商品数
+     */
+    correlationGoodsNum?: number
+    id: number
+    /**
+     * 层级
+     */
+    level?: number
+    /**
+     * 分类名称
+     */
+    name: string
+    /**
+     * 父级id
+     */
+    parentId?: number
+    /**
+     * 分类状态
+     */
+    visible?: number
+    [property: string]: any
+  }
+  interface CategoryListVO {
+    /**
+     * 跳转链接
+     */
+    adLink?: string
+    /**
+     * 广告图片
+     */
+    adPicture?: string
+    /**
+     * 子级分类列表
+     */
+    children?: CategoryListVO[]
+    /**
+     * 关联商品数
+     */
+    correlationGoodsNum?: number
+    id: number
+    /**
+     * 层级
+     */
+    level?: number
+    /**
+     * 分类名称
+     */
+    name?: string
+    /**
+     * 父级id
+     */
+    parentId?: number
+    /**
+     * 分类状态
+     */
+    visible?: number
+    [property: string]: any
+  }
 
 }

+ 2 - 0
src/api/apiDefinitions.ts

@@ -46,6 +46,7 @@ export default {
   'xsb.orderInfo':['GET', '/smqjh-oms/api/v1/order/orderInfo'],
   'xsb.cancelOrder':['GET', '/smqjh-oms/api/v1/order/cancelOrder'],
   'xsb.deleteOrder':['GET', '/smqjh-oms/api/v1/order/deleteOrder/{ids}'],
+  'xsb.skuOrderConfirm':['POST', '/smqjh-oms/api/v1/order/skuOrderConfirm'],
 
   'common.myShoppingCart':['GET', '/smqjh-oms/app-api/v1/shoppingCart/myShoppingCart'],
   'common.addShoppingCart':['POST', '/smqjh-oms/app-api/v1/shoppingCart/addShoppingCart'],
@@ -53,4 +54,5 @@ export default {
   'common.shoppingCartOrderConfirm':['GET', '/smqjh-oms/app-api/v1/shoppingCart/shoppingCartOrderConfirm/{ids}'],
   'common.addOrder':['POST', '/smqjh-oms/api/v1/order/addOrder'],
   'common.hybridPayment':['POST', '/smqjh-oms/service/pay/hybridPayment'],
+  'smqjh.getCategoryList':['POST', '/smqjh-pms/app-api/v1/categories/getCategoryList'],
 };

+ 35 - 1
src/api/globals.d.ts

@@ -290,7 +290,7 @@ declare global {
             orderStatus?: string;
             pageNum?: number;
             pageSize?: number;
-            dvyType:number|string;
+            dvyType: number | string;
           }
         }
       >(
@@ -323,6 +323,18 @@ declare global {
       >(
         config: Config
       ): Alova2Method<any, 'xsb.deleteOrder', Config>;
+      skuOrderConfirm<
+        Config extends Alova2MethodConfig<apiResData<Api.AppletOrderSkuVo>> & {
+          data: {
+            channelId?: number;
+            num?: number;
+            shopId?: number;
+            skuId?: number;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.AppletOrderSkuVo>, 'xsb.skuOrderConfirm', Config>;
     }
     common: {
       myShoppingCart<
@@ -438,6 +450,28 @@ declare global {
         config: Config
       ): Alova2Method<apiResData<wxpay>, 'common.hybridPayment', Config>;
     }
+    smqjh: {
+      getCategoryList<
+        Config extends Alova2MethodConfig<apiResData<Api.smqjhCategoryList[]>> & {
+          data: {
+            /**
+    * 分类名称
+    */
+            name?: string;
+            /**
+             * 父级id
+             */
+            parentId?: number;
+            /**
+             * 分类状态
+             */
+            visible?: number;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.smqjhCategoryList[]>, 'smqjh.getCategoryList', Config>;
+    }
 
   }
 

+ 3 - 2
src/auto-imports.d.ts

@@ -298,6 +298,7 @@ declare global {
   const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
   const useShare: typeof import('@vueuse/core')['useShare']
   const useSlots: typeof import('vue')['useSlots']
+  const useSmqjhCartStore: typeof import('./store/cart')['useSmqjhCartStore']
   const useSorted: typeof import('@vueuse/core')['useSorted']
   const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
   const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
@@ -346,7 +347,7 @@ declare global {
   const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
   const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
   const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
-  const useXsbCartStore: typeof import('./subPack-xsb/store-xsb/cart')['useXsbCartStore']
+  const useXsbCartStore: typeof import('./store/cart')['useXsbCartStore']
   const useXsbTabbar: typeof import('./composables/useXsbTabbar')['useXsbTabbar']
   const useXsbTabbarStore: typeof import('./subPack-xsb/store-xsb/tabbar')['useXsbTabbarStore']
   const useconfirmOrderStore: typeof import('./store/confirmOrder')['useconfirmOrderStore']
@@ -657,6 +658,7 @@ declare module 'vue' {
     readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
     readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
     readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
+    readonly useSmqjhCartStore: UnwrapRef<typeof import('./store/cart')['useSmqjhCartStore']>
     readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
     readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
     readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
@@ -704,7 +706,6 @@ declare module 'vue' {
     readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
     readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
     readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
-    readonly useXsbCartStore: UnwrapRef<typeof import('./subPack-xsb/store-xsb/cart')['useXsbCartStore']>
     readonly watch: UnwrapRef<typeof import('vue')['watch']>
     readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
     readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>

+ 2 - 2
src/config/index.ts

@@ -21,8 +21,8 @@ const mapEnvVersion = {
    * 体验版
    */
   // trial: "http://192.168.1.166:8080/jeecg-boot",
-  // trial: "http://192.168.0.11:8080/jeecg-boot",
-  trial: 'http://47.109.84.152:8081',
+  trial: 'http://192.168.0.157:8080',
+  // trial: 'http://47.109.84.152:8081',
   /**
    * 正式版
    */

+ 196 - 54
src/pages/cart/index.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { StaticUrl } from '@/config'
+import router from '@/router'
 
 import selectAddressTemplate from '@/subPack-smqjh/components/selectAddress/index.vue?async'
 
@@ -14,8 +15,8 @@ definePage({
   },
 })
 const { name } = storeToRefs(useAddressStore())
+const { cartList, isAddingCart } = storeToRefs(useSmqjhCartStore())
 const isAll = ref(false)
-const selectCatrt = ref()
 const tab = ref(0)
 const selectAddress = ref(false)
 const navList = ref([
@@ -28,6 +29,123 @@ const navList = ref([
   { title: '酒店民宿', id: 7 },
   { title: '代驾', id: 8 },
 ])
+const totalProduct = ref<Api.shoppingCartOrderConfirm>()
+async function handleChangeAllShop(e: { value: boolean }, shop: Api.myShoppingCart) {
+  const shopData = cartList.value.filter(it => it.AllShopGoods)
+  if (shopData.length > 1) {
+    shop.AllShopGoods = false
+    useGlobalToast().show('不能同时选中两个店铺的商品')
+    return
+  }
+  if (e.value) {
+    shop.allGoods = shop.skuList.filter(it => it.shopSkuStocks !== '0' && it.isDelete !== '1').map(it => Number(it.id))
+  }
+  else {
+    shop.allGoods = []
+    totalProduct.value = undefined
+  }
+}
+watch(() => cartList.value, async () => {
+  const ids = cartList.value.filter(it => it.allGoods.length)
+  if (ids.length) {
+    const id = ids[0].allGoods.join(',')
+    const res = await useSmqjhCartStore().getCartAddGoodsPrice(id)
+    totalProduct.value = res
+  }
+}, {
+  deep: true,
+})
+
+async function handleChangShopGoods(e: { value: number[] }, shop: Api.myShoppingCart) {
+  const shopData = cartList.value.filter(it => it.allGoods.length)
+  if (shopData.length > 1) {
+    shop.AllShopGoods = false
+    shop.allGoods = []
+    useGlobalToast().show('不能同时选中两个店铺的商品')
+    return
+  }
+
+  if (e.value.length === shop.skuList.length) {
+    shop.AllShopGoods = true
+  }
+  else {
+    shop.AllShopGoods = false
+    totalProduct.value = undefined
+  }
+}
+function handleAllShopGoods(e: { value: boolean }) {
+  if (e.value) {
+    if (cartList.value.length > 1) {
+      useGlobalToast().show('多个店铺不能同时选中')
+      return
+    }
+    cartList.value = cartList.value.map((it) => {
+      return {
+        ...it,
+        AllShopGoods: true,
+        allGoods: it.skuList.map(it => Number(it.id)),
+      }
+    })
+  }
+  else {
+    cartList.value = cartList.value.map((it) => {
+      return {
+        ...it,
+        AllShopGoods: false,
+        allGoods: [],
+      }
+    })
+  }
+}
+async function handleDel() {
+  const delGoods = cartList.value.filter(it => it.allGoods?.length > 0)
+  if (delGoods.length) {
+    await Apis.common.deleteShoppingCart({
+      pathParams: {
+        ids: delGoods.map(it => it.allGoods).join(','),
+      },
+    })
+    await useSmqjhCartStore().getCartList('XSB')
+    useGlobalToast().show({ msg: '删除成功!' })
+  }
+  else {
+    useGlobalToast().show({ msg: '请选择要删除的商品' })
+  }
+}
+async function handleSub(item: Api.CartSkuVo) {
+  if (unref(isAddingCart)) {
+    useGlobalToast().show({ msg: '移除商品中,请稍后' })
+    return
+  }
+  if (item.num === 1) {
+    useGlobalMessage().confirm({
+      msg: '是否删除该商品?',
+      success: async () => {
+        await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
+      },
+    })
+  }
+  else {
+    await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
+  }
+}
+async function handleAdd(item: Api.CartSkuVo) {
+  if (unref(isAddingCart)) {
+    useGlobalToast().show({ msg: '添加商品中,请稍后' })
+    return
+  }
+  await useSmqjhCartStore().addCart(item.skuId, 1, item.shopId, 'XSB')
+}
+function handelPay() {
+  if (!unref(totalProduct)) {
+    useGlobalToast().show({ msg: '请选择商品' })
+    return
+  }
+  router.push({
+    name: 'xsb-confirmOrder',
+    params: { data: JSON.stringify(unref(totalProduct)) },
+  })
+}
 </script>
 
 <template>
@@ -62,50 +180,77 @@ const navList = ref([
           管理
         </view>
       </view>
-      <scroll-view scroll-y class="content box-border">
-        <view class="px24rpx">
-          <view v-for="store in 5" :key="store" class="mb24rpx rounded-16rpx bg-white px24rpx pb18rpx pt28rpx">
-            <wd-checkbox v-model="isAll" size="large">
-              <view class="text-28rpx font-semibold">
-                市民请集合官方旗舰店
+      <scroll-view scroll-y class="content box-border px24rpx">
+        <view v-for="shop in cartList" :key="shop.shopId" class="mb24rpx rounded-16rpx bg-white px24rpx pb18rpx pt28rpx">
+          <wd-checkbox v-model="shop.AllShopGoods" size="large" @change="handleChangeAllShop($event, shop)">
+            <view class="text-28rpx font-semibold">
+              {{ shop.shopName }}
+            </view>
+          </wd-checkbox>
+          <view class="mt20rpx h2rpx w-full bg-#F0F0F0" />
+          <wd-checkbox-group v-model="shop.allGoods" size="large" @change="handleChangShopGoods($event, shop)">
+            <view
+              v-for="item in shop.skuList" :key="item.id" class="relative mt20rpx flex items-center"
+            >
+              <view class="mr20rpx h32rpx w32rpx">
+                <wd-checkbox :model-value="item.id" />
               </view>
-            </wd-checkbox>
-            <view class="mt20rpx h2rpx w-full bg-#F0F0F0" />
-            <wd-checkbox-group v-model="selectCatrt" size="large">
-              <view v-for="items in 10" :key="items" class="mt20rpx flex items-center">
-                <view class="mr20rpx h32rpx w32rpx">
-                  <wd-checkbox model-value="jingmai" />
-                </view>
-                <view class="flex flex-1">
-                  <image :src="`${StaticUrl}/shu.png`" class="h200rpx w200rpx flex-shrink-0" />
-                  <view class="ml20rpx flex-1">
-                    <view class="text-left text-32rpx font-semibold">
-                      <view v-for="i in 2" :key="i" class="mr5px inline-block">
-                        <wd-tag>
+              <view class="flex flex-1">
+                <image
+                  :src="item.pic"
+                  class="h206rpx w200rpx flex-shrink-0"
+                  @click.stop="router.push({ name: 'xsb-goods', params: { id: String(item.skuId) } })"
+                />
+                <view class="ml20rpx flex-1">
+                  <view class="text-left text-28rpx font-semibold">
+                    <!-- <view v-for="i in 2" :key="i" class="mr5px inline-block">
+                        <wd-tag type="danger">
                           惊喜回馈
                         </wd-tag>
-                      </view>
-                      秋季应季水果纯甜
-                      柿子
-                    </view>
-                    <view class="mt14rpx text-24rpx text-#AAAAAA">
-                      规格:5kg,盒
+                      </view> -->
+                    {{ item.skuName }}
+                  </view>
+                  <view class="mt14rpx text-24rpx text-#AAAAAA">
+                    规格:{{ item.spec }}
+                  </view>
+                  <view class="mt14rpx flex items-center justify-between">
+                    <view class="text-36rpx text-#FF4A39 font-semibold">
+                      ¥{{ item.price }}
                     </view>
-                    <view class="mt14rpx flex items-center justify-between">
-                      <view class="text-36rpx text-#FF4A39 font-semibold">
-                        ¥13.95
+                    <!-- <wd-input-number v-model="item.num" disable-input @change="handleChangeNum($event, item)" /> -->
+                    <view class="flex items-center">
+                      <image
+                        :src="` ${StaticUrl}/sub-cart.png`"
+                        class="h44rpx w44rpx"
+                        @click.stop="handleSub(item)"
+                      />
+                      <view class="box-border h44rpx w84rpx flex items-center justify-center border border-#F0F0F0 border-solid text-24rpx text-#AAAAAA">
+                        {{ item.num }}
                       </view>
-                      <!-- <wd-input-number v-model="tabs.id" /> -->
+                      <image
+                        :src="` ${StaticUrl}/add-cart.png`"
+                        class="h44rpx w44rpx"
+                        @click.stop="handleAdd(item)"
+                      />
                     </view>
                   </view>
                 </view>
               </view>
-            </wd-checkbox-group>
-          </view>
+              <view v-if="item.shopSkuStocks == '0'" class="absolute left-0 top-0 z-1 h-full w-full flex items-center justify-center bg-[rgba(255,255,255,.6)]">
+                <view class="rounded-16rpx bg-[rgba(0,0,0,.5)] p20rpx text-white">
+                  商品已售罄
+                </view>
+              </view>
+              <view v-if="item.isDelete == '1'" class="absolute left-0 top-0 z-1 h-full w-full flex items-center justify-center bg-[rgba(255,255,255,.6)]">
+                <view class="rounded-16rpx bg-[rgba(0,0,0,.5)] p20rpx text-white">
+                  商品已删除
+                </view>
+              </view>
+            </view>
+          </wd-checkbox-group>
         </view>
-        <view class="h150rpx" />
 
-        <view v-if="false" class="box-border w-full flex items-center justify-center">
+        <view v-if="!cartList.length" class="box-border w-full flex items-center justify-center">
           <view class="mt220rpx flex flex-col items-center">
             <image :src="`${StaticUrl}/cart.png`" class="h110rpx w110rpx" />
             <view class="mb20rpx mt20rpx text-24rpx">
@@ -116,42 +261,39 @@ const navList = ref([
             </wd-button>
           </view>
         </view>
+        <view class="h140rpx" />
       </scroll-view>
     </view>
-    <view
-      class="fixedShadow fixed bottom-60rpx left-0 z-99 box-border w-full flex items-center justify-between rounded-t-16rpx bg-white px24rpx pb60rpx pt10rpx"
-    >
+    <view v-if="cartList.length" class="fixedShadow fixed bottom-60rpx left-0 z-99 box-border w-full flex items-center justify-between rounded-t-16rpx bg-white px24rpx pb60rpx pt10rpx">
       <view class="ios w-full flex items-center justify-between">
         <view class="flex items-center">
-          <image :src="`${StaticUrl}/cart-lanzi.png`" class="h100rpx w100rpx" />
+          <image
+            :src="`${StaticUrl}/cart-lanzi.png`"
+            class="h100rpx w100rpx"
+          />
           <view class="ml16rpx flex items-center">
-            <wd-checkbox v-model="isAll" size="large">
+            <wd-checkbox v-model="isAll" size="large" @change="handleAllShopGoods">
               全选
             </wd-checkbox>
-            <view class="ml10rpx text-24rpx text-#FF4A39">
+            <view class="ml10rpx text-24rpx text-#FF4A39" @click="handleDel">
               删除
             </view>
           </view>
         </view>
         <view class="flex items-center">
-          <view>
-            <view class="flex items-center font-semibold">
-              <view class="text-22rpx text-#222">
-                总计:
-              </view>
-              <view class="flex items-baseline text-24rpx text-#FF4A39">
-                ¥
-                <text class="text-36rpx">
-                  8.9
-                </text>
-              </view>
+          <view class="flex items-center font-semibold">
+            <view class="text-22rpx text-#222">
+              总计:
             </view>
-            <view class="text-22rpx text-#AAAAAA">
-              含配送费¥11
+            <view class="flex items-baseline text-24rpx text-#FF4A39">
+              ¥
+              <text class="text-36rpx">
+                {{ totalProduct?.price || '0.00' }}
+              </text>
             </view>
           </view>
           <view class="ml20rpx w160rpx">
-            <wd-button block size="large">
+            <wd-button block size="large" @click="handelPay">
               结算
             </wd-button>
           </view>

+ 21 - 40
src/pages/classfiy/index.vue

@@ -16,44 +16,16 @@ const { name } = storeToRefs(useAddressStore())
 
 const active = ref<number>(1)
 
-const subCategories = Array.from({ length: 24 }).fill({ title: '标题文字', label: '这是描述这是描述' }, 0, 24)
-const categories = ref([
-  {
-    label: '分类一',
-    title: '标题一',
-    items: subCategories,
-  },
-  {
-    label: '分类二',
-    title: '标题二',
-    items: subCategories,
-  },
-  {
-    label: '分类三',
-    title: '标题三',
-    items: subCategories.slice(0, 18),
-  },
-  {
-    label: '分类四',
-    title: '标题四',
-    items: subCategories.slice(0, 21),
-  },
-  {
-    label: '分类五',
-    title: '标题五',
-    items: subCategories,
-  },
-  {
-    label: '分类六',
-    title: '标题六',
-    items: subCategories.slice(0, 18),
-  },
-  {
-    label: '分类七',
-    title: '标题七',
-    items: subCategories,
-  },
-])
+const classfiylist = ref<Api.smqjhCategoryList[]>([])
+async function getCategories() {
+  const { data } = await Apis.smqjh.getCategoryList({ data: { parentId: 0 } })
+  classfiylist.value = data as Api.smqjhCategoryList[]
+  active.value = classfiylist.value[0].id
+}
+getCategories()
+const rightList = computed(() => {
+  return classfiylist.value.find(item => item.id === active.value)?.children
+})
 </script>
 
 <template>
@@ -73,7 +45,7 @@ const categories = ref([
     <view class="h176rpx bg-#F4FFD1" />
     <view class="wraper">
       <wd-sidebar v-model="active">
-        <wd-sidebar-item v-for="(item, index) in categories" :key="index" :value="index" :label="item.label" />
+        <wd-sidebar-item v-for="item in classfiylist" :key="item.id" :value="item.id" :label="item.name" />
       </wd-sidebar>
       <view class="content p20rpx">
         <image
@@ -86,7 +58,7 @@ const categories = ref([
           </view>
         </view>
         <view class="grid grid-cols-4 mt24rpx gap-24rpx">
-          <view v-for="item in 20" :key="item" class="flex flex-col items-center" @click="router.push({ name: 'smqjh-goodsList' })">
+          <view class="flex flex-col items-center" @click="router.push({ name: 'smqjh-goodsList' })">
             <image
               :src="`${StaticUrl}/smqjh-class-all.png`"
               class="h108rpx w108rpx"
@@ -95,6 +67,15 @@ const categories = ref([
               全部
             </view>
           </view>
+          <view v-for="item in rightList" :key="item.id" class="flex flex-col items-center">
+            <image
+              :src="item.adPicture"
+              class="h108rpx w108rpx"
+            />
+            <view class="mt16rpx text-24rpx text-#222">
+              {{ item.name }}
+            </view>
+          </view>
         </view>
       </view>
     </view>

+ 2 - 0
src/pages/index/index.vue

@@ -18,6 +18,8 @@ const { show } = useGlobalToast()
 const addressStore = useAddressStore()
 const { ScrollDown, statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
 const { name } = storeToRefs(addressStore)
+const { getTotalNum } = storeToRefs(useSmqjhCartStore())
+useTabbar().setTabbarItem('smqjh-cart', getTotalNum.value)
 const navList = ref([
   { icon: `${VITE_OSS_BASE_URL}2025/11/4dabcf9b8d794d3c99aa6b49be34f205.png`, title: '星闪豹', name: 'xsb-homeTabbar' },
   { icon: `${VITE_OSS_BASE_URL}2025/11/40cb38e287234a83885d68f30c9c39bc.png`, title: '充电', name: '' },

+ 1 - 1
src/router/index.ts

@@ -48,7 +48,7 @@ router.beforeEach((to, from, next) => {
         success() {
           redirectName.value = String(to.path)
           // console.log('✅ 用户确认访问,允许导航')
-          router.push({ name: 'smqjh-login' })
+          router.replace({ name: 'smqjh-login' })
           // resolve()
         },
         fail() {

+ 23 - 9
src/subPack-xsb/store-xsb/cart.ts → src/store/cart.ts

@@ -3,10 +3,15 @@ import router from '@/router'
 
 interface cartState {
   cartList: Api.myShoppingCart[]
+  /**
+   * 是否正在加入购物车
+   */
+  isAddingCart: boolean
 }
-export const useXsbCartStore = defineStore('xsb-cart', {
+export const useSmqjhCartStore = defineStore('smqjh-cart', {
   state: (): cartState => ({
     cartList: [],
+    isAddingCart: false,
   }),
   getters: {
     /**
@@ -23,8 +28,9 @@ export const useXsbCartStore = defineStore('xsb-cart', {
      * @param skuId 商品skuId
      * @param num 商品数量
      * @param shopId 门店id
+     * @param businessType 是否是加入购物车
      */
-    async addCart(skuId: number, num: number, shopId: number) {
+    async addCart(skuId: number, num: number, shopId: number, businessType: string) {
       return new Promise((resolve, reject) => {
         if (!skuId) {
           useGlobalToast().show({ msg: '请选择商品规格' })
@@ -38,35 +44,43 @@ export const useXsbCartStore = defineStore('xsb-cart', {
           }, 2000)
           return reject(new Error('请先登录'))
         }
+        this.isAddingCart = true
         Apis.common.addShoppingCart({
           data: {
-            businessType: 'XSB',
+            businessType,
             skuId,
             num,
             shopId,
-            channelId: userInfo.value.channelId,
+            channelId: Number(userInfo.value.channelTopId),
           },
         }).then((res) => {
-          this.getCartList()
+          this.getCartList('XSB')
           resolve(res)
+        }).finally(() => {
+          this.isAddingCart = false
         })
       })
     },
     /**
      * 星闪豹获取购物车列表
      */
-    async getCartList() {
+    async getCartList(businessType: string) {
       const { userInfo, token } = storeToRefs(useUserStore())
       if (!unref(token)) {
         return
       }
       const res = await Apis.common.myShoppingCart({
         data: {
-          channelId: unref(userInfo).channelId,
-          businessType: 'XSB',
+          channelId: unref(userInfo).channelTopId as number,
+          businessType,
         },
       })
-      this.cartList = res.data
+      this.cartList = res.data.map((it) => {
+        return {
+          ...it,
+          allGoods: [],
+        }
+      })
     },
     /**
      *

+ 3 - 0
src/store/user.ts

@@ -48,6 +48,9 @@ export const useUserStore = defineStore('user', {
         this.userInfo = data
         await this.getuserAddresslist()
         this.getSelectedAddress()
+        await useSmqjhCartStore().getCartList('XSB')
+        const { getTotalNum } = storeToRefs(useSmqjhCartStore())
+        useTabbar().setTabbarItem('smqjh-cart', getTotalNum.value)
       }
     },
     async updataUserInfo(data: Api.userInfo) {

+ 71 - 25
src/subPack-xsb/commonTab/components/cart.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import { StaticUrl } from '@/config'
 import router from '@/router'
+import { useSmqjhCartStore } from '@/store/cart'
 
 definePage({
   name: 'xsb-cart',
@@ -12,46 +13,63 @@ definePage({
   },
 })
 
-const { cartList } = storeToRefs(useXsbCartStore())
+const { cartList, isAddingCart } = storeToRefs(useSmqjhCartStore())
 const totalProduct = ref<Api.shoppingCartOrderConfirm>()
 const isAll = ref(false)
-function handleChangeAllShop(e: { value: boolean }) {
+async function handleChangeAllShop(e: { value: boolean }, shop: Api.myShoppingCart) {
+  const shopData = cartList.value.filter(it => it.AllShopGoods)
+  if (shopData.length > 1) {
+    shop.AllShopGoods = false
+    useGlobalToast().show('不能同时选中两个店铺的商品')
+    return
+  }
   if (e.value) {
-    cartList.value = cartList.value.map((it) => {
-      return {
-        ...it,
-        allGoods: it.skuList.map(it => it.id),
-      }
-    })
-    console.log(e, cartList.value)
+    shop.allGoods = shop.skuList.filter(it => it.shopSkuStocks !== '0' && it.isDelete !== '1').map(it => Number(it.id))
   }
   else {
-    cartList.value = cartList.value.map((it) => {
-      return {
-        ...it,
-        allGoods: [],
-      }
-    })
+    shop.allGoods = []
+    totalProduct.value = undefined
   }
 }
+watch(() => cartList.value, async () => {
+  const ids = cartList.value.filter(it => it.allGoods.length)
+  if (ids.length) {
+    const id = ids[0].allGoods.join(',')
+    const res = await useSmqjhCartStore().getCartAddGoodsPrice(id)
+    totalProduct.value = res
+  }
+}, {
+  deep: true,
+})
+
 async function handleChangShopGoods(e: { value: number[] }, shop: Api.myShoppingCart) {
+  const shopData = cartList.value.filter(it => it.allGoods.length)
+  if (shopData.length > 1) {
+    shop.AllShopGoods = false
+    shop.allGoods = []
+    useGlobalToast().show('不能同时选中两个店铺的商品')
+    return
+  }
+
   if (e.value.length === shop.skuList.length) {
-    const res = await useXsbCartStore().getCartAddGoodsPrice(e.value.join(','))
-    totalProduct.value = res
     shop.AllShopGoods = true
   }
   else {
-    totalProduct.value = undefined
     shop.AllShopGoods = false
+    totalProduct.value = undefined
   }
 }
 function handleAllShopGoods(e: { value: boolean }) {
   if (e.value) {
+    if (cartList.value.length > 1) {
+      useGlobalToast().show('多个店铺不能同时选中')
+      return
+    }
     cartList.value = cartList.value.map((it) => {
       return {
         ...it,
         AllShopGoods: true,
-        allGoods: it.skuList.map(it => it.id),
+        allGoods: it.skuList.map(it => Number(it.id)),
       }
     })
   }
@@ -73,7 +91,7 @@ async function handleDel() {
         ids: delGoods.map(it => it.allGoods).join(','),
       },
     })
-    await useXsbCartStore().getCartList()
+    await useSmqjhCartStore().getCartList('XSB')
     useGlobalToast().show({ msg: '删除成功!' })
   }
   else {
@@ -81,10 +99,28 @@ async function handleDel() {
   }
 }
 async function handleSub(item: Api.CartSkuVo) {
-  await useXsbCartStore().addCart(item.skuId, -1, item.shopId)
+  if (unref(isAddingCart)) {
+    useGlobalToast().show({ msg: '移除商品中,请稍后' })
+    return
+  }
+  if (item.num === 1) {
+    useGlobalMessage().confirm({
+      msg: '是否删除该商品?',
+      success: async () => {
+        await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
+      },
+    })
+  }
+  else {
+    await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
+  }
 }
 async function handleAdd(item: Api.CartSkuVo) {
-  await useXsbCartStore().addCart(item.skuId, 1, item.shopId)
+  if (unref(isAddingCart)) {
+    useGlobalToast().show({ msg: '添加商品中,请稍后' })
+    return
+  }
+  await useSmqjhCartStore().addCart(item.skuId, 1, item.shopId, 'XSB')
 }
 function handelPay() {
   if (!unref(totalProduct)) {
@@ -108,8 +144,8 @@ function handelPay() {
     <view class="-mt220rpx">
       <view class="content px24rpx">
         <scroll-view scroll-y class="content">
-          <view v-for="shop in cartList" :key="shop.shopId" class="rounded-16rpx bg-white px24rpx pb18rpx pt28rpx">
-            <wd-checkbox v-model="shop.AllShopGoods" size="large" @change="handleChangeAllShop">
+          <view v-for="shop in cartList" :key="shop.shopId" class="mb24rpx rounded-16rpx bg-white px24rpx pb18rpx pt28rpx">
+            <wd-checkbox v-model="shop.AllShopGoods" size="large" @change="handleChangeAllShop($event, shop)">
               <view class="text-28rpx font-semibold">
                 {{ shop.shopName }}
               </view>
@@ -117,7 +153,7 @@ function handelPay() {
             <view class="mt20rpx h2rpx w-full bg-#F0F0F0" />
             <wd-checkbox-group v-model="shop.allGoods" size="large" @change="handleChangShopGoods($event, shop)">
               <view
-                v-for="item in shop.skuList" :key="item.id" class="mt20rpx flex items-center"
+                v-for="item in shop.skuList" :key="item.id" class="relative mt20rpx flex items-center"
               >
                 <view class="mr20rpx h32rpx w32rpx">
                   <wd-checkbox :model-value="item.id" />
@@ -163,6 +199,16 @@ function handelPay() {
                     </view>
                   </view>
                 </view>
+                <view v-if="item.shopSkuStocks == '0'" class="absolute left-0 top-0 z-1 h-full w-full flex items-center justify-center bg-[rgba(255,255,255,.6)]">
+                  <view class="rounded-16rpx bg-[rgba(0,0,0,.5)] p20rpx text-white">
+                    商品已售罄
+                  </view>
+                </view>
+                <view v-if="item.isDelete == '1'" class="absolute left-0 top-0 z-1 h-full w-full flex items-center justify-center bg-[rgba(255,255,255,.6)]">
+                  <view class="rounded-16rpx bg-[rgba(0,0,0,.5)] p20rpx text-white">
+                    商品已删除
+                  </view>
+                </view>
               </view>
             </wd-checkbox-group>
           </view>

+ 16 - 6
src/subPack-xsb/commonTab/components/classfiy.vue

@@ -6,7 +6,8 @@ import router from '@/router'
 const props = defineProps<{ categoryList: Api.xsbCategories[], hotText: Api.xsbSearchTerm[] }>()
 const { statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
 const { topNavActive, leftActive, SelectShopInfo } = storeToRefs(useSysXsbStore())
-const { cartList } = storeToRefs(useXsbCartStore())
+const { cartList } = storeToRefs(useSmqjhCartStore())
+const { userInfo } = storeToRefs(useUserStore())
 const classfiylist = computed(() => props.categoryList)
 const sortClassBtn = ref(1)
 const show = ref(false)
@@ -77,7 +78,7 @@ async function getProductList() {
     data: {
       categoryId: Number(categoriesId.value),
       shopId: Number(SelectShopInfo.value?.shopId) || 1,
-      channelId: 1,
+      channelId: unref(userInfo).channelTopId || 1,
     },
   })
   productList.value = res.data
@@ -104,10 +105,12 @@ function handleSrollTop() {
     }
   }
 }
-function handleAddCart(event: WechatMiniprogram.TouchEvent, item: Api.xsbCategoryProductList) {
+async function handleAddCart(event: WechatMiniprogram.TouchEvent, item: Api.xsbCategoryProductList) {
   console.log(event.detail.x, item)
   if (showball.value)
     return
+
+  await useSmqjhCartStore().addCart(item.skuId, 1, Number(item.shopId), 'XSB')
   basllObj.value.left = event.detail.x
   basllObj.value.top = event.detail.y
   showball.value = true
@@ -127,7 +130,7 @@ onMounted(async () => {
       topScrollView.value = topNavActive.value
     })
   }
-  useXsbCartStore().getCartList()
+
   if (cartList.value.length) {
     const query = uni.createSelectorQuery().in(getCurrentInstance())
     query.select('.cart-box').boundingClientRect()
@@ -138,9 +141,16 @@ onMounted(async () => {
       })
     })
     const ids = cartList.value.map(it => it.skuList.map(its => its.id)).flat()
-    totalProduct.value = await useXsbCartStore().getCartAddGoodsPrice(ids.join(','))
+    totalProduct.value = await useSmqjhCartStore().getCartAddGoodsPrice(ids.join(','))
   }
 })
+function handleGo(item: Api.xsbCategoryProductList) {
+  if (item.id) {
+    useGlobalToast().show('后端数据异常!')
+    return
+  }
+  router.push({ name: 'xsb-goods', params: { id: String(item.id) } })
+}
 </script>
 
 <template>
@@ -269,7 +279,7 @@ onMounted(async () => {
         >
           <view v-if="productList.length" class="p20rpx">
             <view v-for="item in productList" :key="item.id" class="relative">
-              <view class="flex" @click="router.push({ name: 'xsb-goods', params: { id: String(item.id) } })">
+              <view class="flex" @click="handleGo(item)">
                 <view class="mr20rpx h172rpx w172rpx flex-shrink-0 overflow-hidden rounded-16rpx bg-#F6F6F6">
                   <image :src="item.pic" lazy-load class="h-full w-full" />
                 </view>

+ 3 - 7
src/subPack-xsb/commonTab/index.vue

@@ -26,7 +26,7 @@ const { ScrollDown, backTop, SelectShopInfo } = storeToRefs(useSysXsbStore())
 const { userInfo } = storeToRefs(useUserStore())
 const commonCategoryData = ref<Api.xsbCategories[]>([])
 const { data: goodsList, isLastPage, page, error, refresh } = usePagination((pageNum, pageSize) =>
-  Apis.xsb.getSearchProductList({ data: { pageNum, pageSize, salesNum: 'DESC', shopId: Number(SelectShopInfo.value?.shopId) || 1, channelId: userInfo.value.channelId || 1 } }), {
+  Apis.xsb.getSearchProductList({ data: { pageNum, pageSize, salesNum: 'DESC', shopId: Number(SelectShopInfo.value?.shopId) || 1, channelId: userInfo.value.channelTopId || 1 } }), {
   data: resp => resp.data?.list,
   initialData: [],
   initialPage: 1,
@@ -93,16 +93,12 @@ onMounted(async () => {
   refresh()
   getCategories()
 })
+onShow(() => refresh())
 
 watch(() => SelectShopInfo.value.shopId, () => {
-  refresh()
   getCategories()
 })
-onShow(() => {
-  setTimeout(() => {
-    useXsbCartStore().getCartList()
-  }, 2000)
-})
+
 onShareAppMessage(() => {
   return {
     title: '市民请集合',

+ 14 - 6
src/subPack-xsb/goods/index.vue

@@ -16,7 +16,7 @@ const SelectGoodsNum = ref(1)
 const specId = ref()
 const { userInfo } = storeToRefs(useUserStore())
 const { SelectShopInfo } = storeToRefs(useSysXsbStore())
-const { getTotalNum } = storeToRefs(useXsbCartStore())
+const { getTotalNum } = storeToRefs(useSmqjhCartStore())
 // const goodsTab = ref(0)
 const current = ref<number>(0)
 const currentDetaile = ref(0)
@@ -86,21 +86,29 @@ function handleGoCurren(id: number) {
 }
 
 async function getGoodsDetaile() {
-  const res = await Apis.xsb.getProductDetail({ data: { id: goodsId.value, shopId: Number(SelectShopInfo.value?.shopId), channelId: userInfo.value.channelId || 1 } })
+  const res = await Apis.xsb.getProductDetail({ data: { id: goodsId.value, shopId: Number(SelectShopInfo.value?.shopId), channelId: userInfo.value.channelTopId || 1 } })
   console.log(res, '请求')
   goodsInfo.value = res.data
   specId.value = res.data.skuList?.[0].skuId
 }
-function handleConfimOrder() {
+async function handleConfimOrder() {
   if (Number(unref(goodsInfo)?.spuStock) < unref(SelectGoodsNum)) {
     useGlobalToast().show('库存不足,请调整购买数量')
-    return
   }
-  router.push({ name: 'xsb-confirmOrder' })
+
+  const res = await Apis.xsb.skuOrderConfirm({
+    data: {
+      skuId: specId.value,
+      num: SelectGoodsNum.value,
+      channelId: unref(userInfo).channelId,
+      shopId: unref(goodsInfo)?.shopId,
+    },
+  })
+  console.log(res, '数据')
 }
 async function handleAddCart() {
   try {
-    await useXsbCartStore().addCart(specId.value, unref(SelectGoodsNum), unref(SelectShopInfo.value.shopId))
+    await useSmqjhCartStore().addCart(specId.value, unref(SelectGoodsNum), unref(SelectShopInfo.value.shopId), 'XSB')
     selectGoods.value = false
     useGlobalToast().show('添加成功')
   }

+ 1 - 1
src/subPack-xsb/search/index.vue

@@ -26,7 +26,7 @@ const { data, send, isLastPage, page, error } = usePagination((pageNum, pageSize
   priceSort: activeTab.value === 2 ? 'DESC' : '',
   salesNum: activeTab.value === 1 ? 'DESC' : '',
   shopId: 1,
-  channelId: userInfo.value.channelId,
+  channelId: userInfo.value.channelTopId || 1,
 } }), {
   immediate: false,
   data: resp => resp.data?.list,

+ 35 - 8
src/subPack-xsb/selectAddress/index.vue

@@ -10,9 +10,13 @@ definePage({
   },
 })
 const { SelectShopInfo, xsbShopList: shopList } = storeToRefs(useSysXsbStore())
-const { name } = storeToRefs(useAddressStore())
+const { name, Location } = storeToRefs(useAddressStore())
+const { addresses } = storeToRefs(useUserStore())
 
 const selectAddressId = ref()
+onMounted(() => {
+  selectAddressId.value = addresses.value.filter(it => it.defaulted)[0].id
+})
 
 const allShopList = computed(() => {
   return shopList.value?.filter(it => it.shopId !== SelectShopInfo.value?.shopId)
@@ -22,6 +26,29 @@ function handleClick(item: Api.xsbShopList) {
   SelectShopInfo.value = item
   router.back()
 }
+
+function handelChange() {
+  const address = addresses.value.find(it => it.id === selectAddressId.value)
+  if (!address)
+    return
+  Location.value.latitude = Number(address.latitude)
+  Location.value.longitude = Number(address.longitude)
+  let minDistance = Infinity
+  for (const shop of shopList.value) {
+    if (shop.shopLat && shop.shopLng) {
+      const distance = useSysXsbStore().getDistance(
+        Location.value.latitude,
+        Location.value.longitude,
+        Number(shop.shopLat),
+        Number(shop.shopLng),
+      )
+      if (distance < minDistance) {
+        minDistance = distance
+        SelectShopInfo.value = { ...shop, distance: Number(distance.toFixed(2)) }
+      }
+    }
+  }
+}
 </script>
 
 <template>
@@ -44,7 +71,7 @@ function handleClick(item: Api.xsbShopList) {
       </view>
     </view>
     <view class="px24rpx py20rpx">
-      <view class="rounded-16rpx bg-white px24rpx py28rpx">
+      <view v-if="addresses.length" class="rounded-16rpx bg-white px24rpx py28rpx">
         <view class="flex items-center justify-between text-24rpx">
           <view class="text-#AAAAAA">
             我的收获地址
@@ -52,19 +79,19 @@ function handleClick(item: Api.xsbShopList) {
           <view>管理</view>
         </view>
         <view class="radio mt24rpx">
-          <wd-radio-group v-model="selectAddressId" shape="dot" size="large">
-            <view v-for="item, idx in 5" :key="item" class="mb24rpx">
-              <wd-radio :value="item">
+          <wd-radio-group v-model="selectAddressId" shape="dot" size="large" @change="handelChange">
+            <view v-for="item, idx in addresses" :key="item.id" class="mb24rpx">
+              <wd-radio :value="item.id">
                 <view class="flex-1">
                   <view class="text-28rpx font-semibold">
-                    富力中心A7座 34
+                    {{ item.province }} {{ item.city }}
                   </view>
                   <view class="mt16rpx text-24rpx text-#AAAAAA">
-                    用户12345 15285654972
+                    {{ item.consigneeName }} {{ item.consigneeMobile }}
                   </view>
                 </view>
               </wd-radio>
-              <view v-if="idx < 4" class="mt20rpx h2rpx w-full bg-#F0F0F0" />
+              <view v-if="idx < addresses.length - 1" class="mt20rpx h2rpx w-full bg-#F0F0F0" />
             </view>
           </wd-radio-group>
         </view>

+ 20 - 12
src/subPack-xsb/store-xsb/sys.ts

@@ -39,7 +39,7 @@ export const useSysXsbStore = defineStore('system-xsb', {
   actions: {
     getTabbarItemValue(name: string) {
       if (name === 'xsb-cart') {
-        const { getTotalNum } = storeToRefs(useXsbCartStore())
+        const { getTotalNum } = storeToRefs(useSmqjhCartStore())
         return getTotalNum.value
       }
     },
@@ -56,20 +56,10 @@ export const useSysXsbStore = defineStore('system-xsb', {
             useAddressStore().getLocation()
             return
           }
-          const getDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
-            const R = 6371 // 地球半径(公里)
-            const dLat = (lat2 - lat1) * Math.PI / 180
-            const dLng = (lng2 - lng1) * Math.PI / 180
-            const a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
-              + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180)
-              * Math.sin(dLng / 2) * Math.sin(dLng / 2)
-            const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
-            return R * c // 距离(公里)
-          }
           let minDistance = Infinity
           for (const shop of res.data) {
             if (shop.shopLat && shop.shopLng) {
-              const distance = getDistance(
+              const distance = this.getDistance(
                 Location.value.latitude,
                 Location.value.longitude,
                 Number(shop.shopLat),
@@ -85,6 +75,24 @@ export const useSysXsbStore = defineStore('system-xsb', {
         }).catch((err) => { reject(err) })
       })
     },
+    /**
+     * 计算距离
+     * @param lat1
+     * @param lng1
+     * @param lat2
+     * @param lng2
+     * @returns 计算距离
+     */
+    getDistance(lat1: number, lng1: number, lat2: number, lng2: number) {
+      const R = 6371 // 地球半径(公里)
+      const dLat = (lat2 - lat1) * Math.PI / 180
+      const dLng = (lng2 - lng1) * Math.PI / 180
+      const a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+        + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180)
+        * Math.sin(dLng / 2) * Math.sin(dLng / 2)
+      const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
+      return R * c // 距离(公里)
+    },
 
   },
 })