Переглянути джерело

feat: 搜索、分享、收藏页面 轮播接口调试

liangan 1 тиждень тому
батько
коміт
580ee55a4f

+ 7 - 0
src/api/common.ts

@@ -15,3 +15,10 @@ export function getEnum(data: any) {
 export function divisionsTreeList(data: any) {
   return http.post<any>(`/operating/divisions/treeList?${qs(data)}`)
 }
+/**
+ * 获取banner
+ * @returns
+ */
+export function bannerList(data: any) {
+  return http.post<any>(`/mall/banner/list?${qs(data)}`, {})
+}

+ 9 - 1
src/api/product.ts

@@ -14,8 +14,16 @@ export function getList(data: any) {
  * @returns data.list[]
  */
 export function getRankList(data: any) {
+  return http.get<any>(`/mall/combination/rank`, data)
+}
+
+/**
+ * 获取商品分类
+ * @returns data.list[]
+ */
+export function categoryList(data: any) {
   const { extract, retained } = extractAndRetained(data, ['page', 'size'])
-  return http.post<any>(`/mall/combination/rank?${qs(extract)}`, { ...retained })
+  return http.post<any>(`/mall/category/list?${qs(extract)}`, retained)
 }
 
 /**

+ 1 - 0
src/locale/bn.json

@@ -5,6 +5,7 @@
   "home.bestSellers": "সেরা\nবিক্রেতা",
   "home.topChampions": "শীর্ষ\nচ্যাম্পিয়ন",
   "home.news": "নতুন",
+  "home.priceTab.allPrice": "All Price",
   "home.priceTab.300spot": "৩০০স্পট",
   "home.priceTab.500spot": "৫০০স্পট",
   "home.priceTab.1000spot": "১০০০স্পট",

+ 1 - 0
src/locale/en.json

@@ -5,6 +5,7 @@
   "home.bestSellers": "Best\nSellers",
   "home.topChampions": "Top\nChampions",
   "home.news": "News",
+  "home.priceTab.allPrice": "All Price",
   "home.priceTab.300spot": "300Spot",
   "home.priceTab.500spot": "500Spot",
   "home.priceTab.1000spot": "1000Spot",

+ 1 - 0
src/locale/zh-Hans.json

@@ -5,6 +5,7 @@
   "home.bestSellers": "热销商品",
   "home.topChampions": "顶级冠军",
   "home.news": "新品",
+  "home.priceTab.allPrice": "全部价格",
   "home.priceTab.300spot": "300积分",
   "home.priceTab.500spot": "500积分",
   "home.priceTab.1000spot": "1000积分",

+ 17 - 4
src/pages.json

@@ -115,6 +115,15 @@
         "navigationStyle": "custom"
       }
     },
+    {
+      "path": "pages/mine/myFavorite",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "My Favorite",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
     {
       "path": "pages/mine/myProfile",
       "type": "page",
@@ -133,6 +142,14 @@
         "navigationBarBackgroundColor": "#fff"
       }
     },
+    {
+      "path": "pages/mine/share",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationStyle": "custom"
+      }
+    },
     {
       "path": "pages/missionCenter/missionCenter",
       "type": "page",
@@ -211,10 +228,6 @@
         "navigationStyle": "custom"
       }
     },
-    {
-      "path": "pages/share/share",
-      "type": "page"
-    },
     {
       "path": "pages/topChampions/topChampions",
       "type": "page",

+ 15 - 14
src/pages/index/index.vue

@@ -15,6 +15,7 @@
 import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
 import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
 
+import { bannerList } from '@/api/common'
 import { getList } from '@/api/product'
 import { toPage } from '@/utils/page'
 
@@ -33,17 +34,9 @@ useZPaging(paging)
 
 // 轮播图
 const current = ref<number>(0)
-const swiperList = ref([
-  'https://picsum.photos/750/420?random=1',
-  'https://picsum.photos/750/420?random=2',
-  'https://picsum.photos/750/420?random=3',
-  'https://picsum.photos/750/420?random=4',
-])
-function handleClick(e) {
-  // console.log(e)
-}
-function onChange(e) {
-  // console.log(e)
+const swiperList = ref([])
+function handleSwiperClick(e: any) {
+  console.log(e)
 }
 
 // 导航图标
@@ -137,6 +130,12 @@ async function queryList(pageNo: number, pageSize: number) {
   paging.value.complete(res.data.list)
 }
 
+async function getBannerList() {
+  const res = await bannerList({ page: 1, size: 20 })
+  console.log(res)
+  swiperList.value = res.data.list
+}
+
 // 价格tab切换事件
 function onPriceTabChange() {
   paging.value.reload()
@@ -144,6 +143,7 @@ function onPriceTabChange() {
 
 onLoad(() => {
   getNewList()
+  getBannerList()
 })
 </script>
 
@@ -163,11 +163,12 @@ onLoad(() => {
     </template>
     <wd-swiper
       v-model:current="current"
-      :list="swiperList" autoplay
+      :list="swiperList"
+      value-key="image"
+      autoplay
       :indicator="{ type: 'fraction' }"
       indicator-position="bottom-right"
-      @click="handleClick"
-      @change="onChange"
+      @click="handleSwiperClick"
     />
     <view class="px-24rpx pb-24rpx">
       <view class="flex items-end justify-between pb-22rpx pt-24rpx">

+ 2 - 2
src/pages/mine/mine.vue

@@ -45,8 +45,8 @@ const groupList = ref([
 const menuList = ref([
   { name: 'My Profile', url: '/pages/mine/myProfile', icon: '/static/icons/my-profile.png' },
   { name: 'Address Book', url: '/pages/mine/addressBook', icon: '/static/icons/address-book.png' },
-  { name: 'Share', url: '', icon: '/static/icons/share.png' },
-  { name: 'My Favorite', url: '', icon: '/static/icons/my-favorite.png' },
+  { name: 'Share', url: '/pages/mine/share', icon: '/static/icons/share.png' },
+  { name: 'My Favorite', url: '/pages/mine/myFavorite', icon: '/static/icons/my-favorite.png' },
   { name: 'Live Chat', url: '', icon: '/static/icons/live-chat.png' },
   { name: 'Activity Group', url: '', icon: '/static/icons/activity-group.png' },
 ])

+ 106 - 0
src/pages/mine/myFavorite.vue

@@ -0,0 +1,106 @@
+<route lang="json5">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: 'My Favorite',
+    navigationBarBackgroundColor: '#fff',
+  },
+}
+</route>
+
+<script lang="ts" setup>
+// 必须导入需要用到的页面生命周期(即使在当前页面上没有直接使用到)
+// eslint-disable-next-line unused-imports/no-unused-imports
+import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
+import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
+import { getList } from '@/api/product'
+
+defineOptions({
+  name: 'MyFavorite', // 我的收藏
+})
+// 获取屏幕边界到安全区域距离
+const systemInfo = uni.getSystemInfoSync()
+const safeAreaInsets = systemInfo.safeAreaInsets
+// z-paging
+const paging = ref(null)
+// 类似mixins,如果是页面滚动务必要写这一行,并传入当前ref绑定的paging,注意此处是paging,而非paging.value
+useZPaging(paging)
+
+// 搜索结果
+const dataList = ref([])
+async function queryList(pageNo: number, pageSize: number) {
+  try {
+    const params = {
+      page: pageNo,
+      size: pageSize,
+    }
+    const res = await getList(params)
+    paging.value.complete(res.data.list)
+  }
+  catch {
+    paging.value.complete(false)
+  }
+}
+</script>
+
+<template>
+  <z-paging ref="paging" v-model="dataList" use-page-scroll @query="queryList" @click="closeOutside">
+    <view class="px-24rpx py-24rpx">
+      <view class="grid grid-cols-2 gap-22rpx">
+        <product v-for="(item, index) in dataList" :key="index" :height="340" :item="item" />
+      </view>
+    </view>
+  </z-paging>
+</template>
+
+<style>
+page {
+  background: #fff;
+}
+</style>
+
+<style lang="scss" scoped>
+:deep() {
+  .wd-navbar__title {
+    margin: 0;
+    max-width: 100%;
+    .content {
+      box-sizing: border-box;
+      position: relative;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      padding: 19rpx 25rpx 19rpx 0;
+      .back {
+        padding: 0 25rpx;
+      }
+      .search-input {
+        flex: 1;
+        height: 100%;
+        text-align: left;
+        background: rgba(245, 245, 245, 0.6);
+        border-radius: 8rpx;
+        border: 1px solid #efefef;
+        font-size: 28rpx;
+        padding: 16rpx 64rpx 16rpx 22rpx;
+        font-weight: normal;
+        &:active {
+          border-color: rgba(230, 27, 40, 0.65);
+        }
+        &:focus-within {
+          border-color: rgba(230, 27, 40, 0.65) !important;
+        }
+        &:focus-visible {
+          border-color: rgba(230, 27, 40, 0.65) !important;
+        }
+      }
+      .search-icon {
+        position: absolute;
+        right: 42rpx;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+  }
+}
+</style>

+ 40 - 48
src/pages/share/share.vue → src/pages/mine/share.vue

@@ -1,52 +1,52 @@
+<route lang="json5">
+{
+  layout: 'default',
+  style: {
+    navigationStyle: 'custom',
+  },
+}
+</route>
+
 <script setup lang="ts">
-import { onLoad } from '@dcloudio/uni-app'
-import { ref } from 'vue'
+import { goBack } from '@/utils/page'
 
-// 获取安全区域
-const { safeAreaInsets } = uni.getSystemInfoSync()
+// 获取屏幕边界到安全区域距离
+const systemInfo = uni.getSystemInfoSync()
+const safeAreaInsets = systemInfo.safeAreaInsets
 
 // 推荐码
-const referrerCode = ref('BAN678')
+const userInfo = computed(() => {
+  return getUserInfoHook()
+})
 
 // 社交平台配置
 const socialPlatforms = ref([
   {
     name: 'facebook',
     label: 'Facebook',
-    icon: '/static/social/facebook.png', // 占位图片路径
+    icon: '/static/icons/facebook.png', // 占位图片路径
   },
   {
     name: 'whatsapp',
     label: 'Whatsapp',
-    icon: '/static/social/whatsapp.png', // 占位图片路径
+    icon: '/static/icons/whatsapp.png', // 占位图片路径
   },
   {
     name: 'instagram',
     label: 'Instagram',
-    icon: '/static/social/instagram.png', // 占位图片路径
+    icon: '/static/icons/instagram.png', // 占位图片路径
   },
   {
     name: 'twitter',
     label: 'Twitter',
-    icon: '/static/social/twitter.png', // 占位图片路径
+    icon: '/static/icons/twitter.png', // 占位图片路径
   },
 ])
 
-// 页面加载
-onLoad(() => {
-  // 可以从用户信息中获取推荐码
-  // referrerCode.value = getUserReferrerCode()
-})
-
-// 返回上一页
-function goBack() {
-  uni.navigateBack()
-}
-
 // 复制推荐码
 function copyReferrerCode() {
   uni.setClipboardData({
-    data: referrerCode.value,
+    data: userInfo.value.userNo,
     success: () => {
       uni.showToast({
         title: 'Copied to clipboard',
@@ -79,63 +79,55 @@ function handleShare(platform: string) {
       <!-- 自定义导航栏 -->
       <view :style="{ paddingTop: `${safeAreaInsets?.top}px` }">
         <view class="h-88rpx flex items-center px-24rpx">
-          <wd-icon name="arrow-left" size="48rpx" color="#333" @click="goBack" />
-        </view>
-      </view>
-
-      <!-- Logo和标语 -->
-      <view class="pb-40rpx pt-60rpx text-center">
-        <view class="mb-20rpx flex items-center justify-center">
-          <image src="/static/logo.png" class="mr-16rpx h-60rpx w-60rpx" />
+          <wd-icon name="thin-arrow-left" size="32rpx" @click="goBack" />
         </view>
       </view>
     </view>
 
     <!-- 主要内容区域 -->
-    <view class="flex flex-col px-20rpx">
-      <view class="mb-40rpx" />
-
+    <view class="flex flex-col px-24rpx">
       <!-- 推荐码标题 -->
-      <view class="mb-10rpx text-center">
+      <view class="mb-10rpx mt-40rpx text-center">
         <text class="text-28rpx text-#333 font-medium">
           My Referrer Code
         </text>
       </view>
 
       <!-- 推荐码显示 -->
-      <view class="mb-60rpx ml-40rpx flex items-center justify-center">
+      <view class="mb-44rpx flex items-center justify-center">
         <text class="mr-20rpx text-64rpx font-bold tracking-wider">
-          {{ referrerCode }}
+          {{ userInfo.userNo }}
         </text>
         <wd-icon name="file-copy" size="30rpx" color="#757575" @click="copyReferrerCode" />
       </view>
 
       <!-- 二维码区域 -->
-      <view class="mb-80rpx flex justify-center">
-        <view class="rounded-20rpx bg-white p-40rpx" style="box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.1);">
-          <!-- 二维码占位图 -->
-          <view class="h-480rpx w-480rpx flex items-center justify-center border-2rpx border-#e5e5e5 rounded-12rpx bg-#f8f8f8">
-            <view class="text-center">
-              <view class="mb-20rpx text-80rpx">
-                📱
-              </view>
-              <text class="text-28rpx text-#999">
-                QR Code
-              </text>
+      <view class="mb-60rpx flex justify-center">
+        <!-- 二维码占位图 -->
+        <view
+          style="box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.1);"
+          class="h-500rpx w-500rpx flex items-center justify-center border-#e5e5e5 rounded-12rpx bg-#f8f8f8"
+        >
+          <view class="text-center">
+            <view class="mb-20rpx text-80rpx">
+              📱
             </view>
+            <text class="text-28rpx text-#999">
+              QR Code
+            </text>
           </view>
         </view>
       </view>
 
       <!-- 分享说明 -->
-      <view class="mb-60rpx px-40rpx text-center">
+      <view class="mb-60rpx text-center">
         <text class="text-28rpx text-#797979 leading-relaxed">
           Share your aR code with your friends, they can scan it with their camera to register as your downline.
         </text>
       </view>
 
       <!-- 社交分享按钮 -->
-      <view class="mb-80rpx flex justify-center gap-50rpx">
+      <view class="flex justify-center gap-50rpx">
         <view
           v-for="item in socialPlatforms"
           :key="item.name"

+ 74 - 66
src/pages/search/search.vue

@@ -13,6 +13,8 @@
 import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
 import { useQueue } from 'wot-design-uni'
 import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
+import { categoryList, getList } from '@/api/product'
+import { t } from '@/locale'
 import { goBack } from '@/utils/page'
 
 defineOptions({
@@ -27,83 +29,84 @@ const paging = ref(null)
 useZPaging(paging)
 
 // 搜索关键词
-const keyword = ref('')
+const formData = ref<any>({
+  storeName: '',
+  prices: 0,
+  cateId: 0,
+  sortWay: 0, // 3=销量升序,4=销量降序
+})
 
 const { closeOutside } = useQueue()
-const value1 = ref<number>(0)
-const value2 = ref<number>(0)
-const value3 = ref<number>(0)
 
 const option1 = ref<Record<string, any>[]>([
-  { label: 'All Price', value: 0 },
-])
-const option2 = ref<Record<string, any>[]>([
-  { label: 'Category', value: 0 },
-])
-const option3 = ref<Record<string, any>[]>([
-  { label: 'Best Sellers', value: 0 },
-])
-
-function handleChange1({ value }) {
-  console.log(value)
-}
-function handleChange2({ value }) {
-  console.log(value)
-}
-function handleChange3({ value }) {
-  console.log(value)
-}
-// 新品列表
-const newProducts = ref([
-  {
-    image: 'https://picsum.photos/260?random=1',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
+  { label: t('home.priceTab.allPrice'), value: 0 },
   {
-    image: 'https://picsum.photos/260?random=2',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
+    label: t('home.priceTab.300spot'),
+    value: 300,
+    minPrice: 0,
+    maxPrice: 300,
   },
   {
-    image: 'https://picsum.photos/260?random=3',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
+    label: t('home.priceTab.500spot'),
+    value: 500,
+    minPrice: 300,
+    maxPrice: 500,
   },
   {
-    image: 'https://picsum.photos/260?random=4',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
+    label: t('home.priceTab.1000spot'),
+    value: 1000,
+    minPrice: 500,
+    maxPrice: 1000,
   },
   {
-    image: 'https://picsum.photos/260?random=5',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
+    label: t('home.priceTab.2000spot'),
+    value: 2000,
+    minPrice: 1000,
+    maxPrice: 2000,
   },
 ])
+
+const option2 = ref<Record<string, any>[]>([
+  { label: 'All Category', value: 0 },
+])
+async function getCategoryList() {
+  const res = await categoryList({ page: 1, size: 20 })
+  console.log(res)
+  option2.value = [...option2.value, ...res.data.list.map((i: any) => ({ label: i.name, value: i.id }))]
+}
+
 // 搜索结果
 const dataList = ref([])
-function queryList(pageNo, pageSize) {
-  // 此处请求仅为演示,请替换为自己项目中的请求
-  setTimeout(() => {
-    dataList.value = [
-      { title: '123' },
-      { title: '123' },
-      { title: '123' },
-      { title: '12345' },
-    ]
-    paging.value.complete(dataList.value)
-  }, 1000)
+async function queryList(pageNo: number, pageSize: number) {
+  try {
+    const currentTab = option1.value.find((i: any) => i.value === formData.value.prices) || option1.value[0]
+    console.log(currentTab)
+    const minPrice = currentTab?.minPrice ?? undefined
+    const maxPrice = currentTab?.maxPrice ?? undefined
+    const params = {
+      page: pageNo,
+      size: pageSize,
+      storeName: formData.value.storeName,
+      cateId: formData.value.cateId ? formData.value.cateId : undefined,
+      sortWay: formData.value.storeName.value.sortWay === 1 ? 3 : formData.value.storeName.value.sortWay === -1 ? 4 : 0,
+      minPrice,
+      maxPrice,
+    }
+    const res = await getList(params)
+    paging.value.complete(res.data.list)
+  }
+  catch {
+    paging.value.complete(false)
+  }
 }
+
+onLoad(() => {
+  getCategoryList()
+})
 </script>
 
 <template>
-  <z-paging ref="paging" refresher-only use-page-scroll @query="queryList" @click="closeOutside">
+  <z-paging ref="paging" v-model="dataList" use-page-scroll @query="queryList" @click="closeOutside">
     <template #top>
       <view class="bg-white" :style="{ paddingTop: `${safeAreaInsets?.top}px` }">
         <wd-navbar :bordered="false">
@@ -112,22 +115,27 @@ function queryList(pageNo, pageSize) {
               <view class="back">
                 <wd-icon name="thin-arrow-left" size="32rpx" @click="goBack" />
               </view>
-              <input v-model.trim="keyword" class="search-input" type="text" :placeholder="$t('search.placeholder')">
+              <input v-model.trim="formData.storeName" class="search-input" type="text" :placeholder="$t('search.placeholder')" @confirm="queryList(1, 20)">
               <wd-icon name="search" custom-class="search-icon" color="#999" size="32rpx" />
             </view>
           </template>
         </wd-navbar>
-        <wd-drop-menu>
-          <wd-drop-menu-item v-model="value1" :options="option1" @change="handleChange1" />
-          <wd-drop-menu-item v-model="value2" :options="option2" @change="handleChange2" />
-          <wd-drop-menu-item v-model="value3" :options="option3" @change="handleChange3" />
-        </wd-drop-menu>
+        <view class="flex bg-white text-center">
+          <wd-drop-menu class="flex-1">
+            <wd-drop-menu-item v-model="formData.prices" :options="option1" @change="queryList(1, 20)" />
+          </wd-drop-menu>
+          <wd-drop-menu class="flex-1">
+            <wd-drop-menu-item v-model="formData.cateId" :options="option2" @change="queryList(1, 20)" />
+          </wd-drop-menu>
+          <view class="flex-1">
+            <wd-sort-button v-model="formData.sortWay" title="Best Sellers" @change="queryList(1, 20)" />
+          </view>
+        </view>
       </view>
     </template>
     <view class="px-24rpx pb-24rpx">
       <view class="grid grid-cols-2 gap-22rpx">
-        <product v-for="(item, index) in newProducts" :key="index" :height="340" :item="item" />
-        <view />
+        <product v-for="(item, index) in dataList" :key="index" :height="340" :item="item" />
       </view>
     </view>
   </z-paging>

BIN
src/static/icons/facebook.png


BIN
src/static/icons/instagram.png


BIN
src/static/icons/twitter.png


BIN
src/static/icons/whatsapp.png


+ 5 - 4
src/utils/http.ts

@@ -1,4 +1,5 @@
 import type { CustomRequestOptions } from '@/interceptors/request'
+import { toPage } from '@/utils/page'
 import { toast } from './toast'
 
 export function http<T>(options: CustomRequestOptions) {
@@ -18,9 +19,9 @@ export function http<T>(options: CustomRequestOptions) {
           if (res.data.code === '200') {
             resolve(res.data as IResData<T>)
           }
-          else if (res.data.code === '401') {
-            toast.error((res.data as IResData<T>).message || '未登录')
-            reject(res.data)
+          else if (res.data.code === '598') {
+            toast.error((res.data as IResData<T>).message)
+            toPage('/pages/login/login')
           }
           else {
             toast.error((res.data as IResData<T>).message || '请求错误')
@@ -36,7 +37,7 @@ export function http<T>(options: CustomRequestOptions) {
         else {
           // 其他错误 -> 根据后端错误信息轻提示
           !options.hideErrorToast
-          && toast.info((res.data as IResData<T>).message || '请求错误')
+          && toast.info((res.data as IResData<T>).message)
           reject(res)
         }
       },