Browse Source

feat: 商品列表接口及详情接口调试

liangan 3 tuần trước cách đây
mục cha
commit
7026644428

+ 9 - 0
src/api/product.ts

@@ -9,3 +9,12 @@ export function getList(data: any) {
   const { extract, retained } = extractAndRetained(data, ['page', 'size'])
   return http.post<any>(`/mall/product/list?${qs(extract)}`, retained)
 }
+
+/**
+ * 获取商品详情
+ * @returns data.list[]
+ */
+export function getDetail(data: any) {
+  const { extract } = extractAndRetained(data, ['id'])
+  return http.post<any>(`/mall/product/detail?${qs(extract)}`)
+}

+ 12 - 6
src/components/product/product.vue

@@ -1,4 +1,6 @@
 <script lang="ts" setup>
+import { formatNumber } from '@/utils/index'
+
 defineOptions({
   name: 'Product', // 商品组件
 })
@@ -11,6 +13,10 @@ const props = defineProps({
     type: [Number, String],
     default: 260,
   },
+  flex: {
+    type: Boolean,
+    default: false,
+  },
   height: {
     type: [Number, String],
     default: 260,
@@ -26,11 +32,11 @@ function handleClick() {
 
 <template>
   <view
-    class="flex flex-[0.5] flex-col items-center border-1 border-#E0E0E0 border-solid pb-8rpx"
-    :style="{ width: Number.isFinite(width) ? `${width}rpx` : width }"
+    class="flex flex-col items-center border-1 border-#E0E0E0 border-solid pb-8rpx"
+    :class="{ 'flex-[0.5]': flex }"
     @click="handleClick"
   >
-    <view :style="{ width: Number.isFinite(width) ? `${width}rpx` : width, height: Number.isFinite(height) ? `${height}rpx` : height }">
+    <view class="mb-10rpx" :style="{ maxWidth: Number.isFinite(width) ? `${width}rpx` : width, width: Number.isFinite(width) ? `${width}rpx` : width, height: Number.isFinite(height) ? `${height}rpx` : height }">
       <image
         :src="item.image"
         class="h-full w-full"
@@ -39,14 +45,14 @@ function handleClick() {
     </view>
     <view class="box-border w-full px-8rpx text-24rpx">
       <view class="truncate">
-        {{ item.title }}
+        {{ item.storeName }}
       </view>
       <view class="flex items-center justify-between">
         <view class="text-24rpx text-#FF334A font-bold">
-          ৳ {{ item.price }}
+          ৳ {{ formatNumber(item.price) }}
         </view>
         <view class="text-24rpx text-#898989">
-          {{ item.sold }} Sold
+          {{ item.sales }} Sold
         </view>
       </view>
     </view>

+ 18 - 60
src/pages/index/index.vue

@@ -14,6 +14,7 @@ import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
 // 必须导入需要用到的页面生命周期(即使在当前页面上没有直接使用到)(使用页面滚动)
 
 import { getList } from '@/api/product'
+import { toPage } from '@/utils/page'
 
 defineOptions({
   name: 'Index', // 首页
@@ -84,47 +85,14 @@ const navIcons = ref([
   },
 ])
 
-function toPage(url: string) {
-  if (url) {
-    uni.navigateTo({
-      url,
-    })
-  }
-}
-
 // 新品列表
-const newProducts = ref([
-  {
-    image: 'https://picsum.photos/260?random=1',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
-  {
-    image: 'https://picsum.photos/260?random=2',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
-  {
-    image: 'https://picsum.photos/260?random=3',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
-  {
-    image: 'https://picsum.photos/260?random=4',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
-  {
-    image: 'https://picsum.photos/260?random=5',
-    title: 'Chanel Rouge Coco Shine…',
-    price: '123',
-    sold: 100,
-  },
-])
+const newProducts = ref<any>([])
+
+async function getNewList() {
+  const res = await getList({ page: 1, size: 10, isNew: true })
+  console.log(res)
+  newProducts.value = res.data.list
+}
 
 //  商品列表
 const priceTab = ref<number>(0)
@@ -149,29 +117,20 @@ const priceTabList = ref([
   },
 ])
 
-const dataList = ref([])
+const dataList = ref<any>([])
 async function queryList(pageNo, pageSize) {
   const data = { page: pageNo, size: pageSize }
   const res = await getList(data)
-  console.log(res)
-  paging.value.complete(dataList.value)
+  paging.value.complete(res.data.list)
 }
 
-function toProductDetail(item) {
-  const data = JSON.stringify({ id: item.id })
-  uni.navigateTo({
-    url: `/pages/productDetail/productDetail?data=${encodeURIComponent(data)}`,
-  })
-}
-function toNotifications() {
-  uni.navigateTo({
-    url: '/pages/notifications/notifications',
-  })
-}
+onLoad(() => {
+  getNewList()
+})
 </script>
 
 <template>
-  <z-paging ref="paging" use-page-scroll @query="queryList">
+  <z-paging ref="paging" v-model="dataList" use-page-scroll @query="queryList">
     <template #top>
       <view
         class="flex items-center justify-between bg-white pb-40rpx pl-42rpx pr-34rpx pt-26rpx"
@@ -180,7 +139,7 @@ function toNotifications() {
         <image src="/static/header-logo.png" class="h-54rpx w-250rpx" />
         <view class="flex items-center">
           <wd-icon custom-class="mr-20rpx" name="search1" size="38rpx" @click="toSearch" />
-          <wd-icon name="notification" size="38rpx" @click="toNotifications" />
+          <wd-icon name="notification" size="38rpx" @click="toPage('/pages/notifications/notifications')" />
         </view>
       </view>
     </template>
@@ -207,7 +166,7 @@ function toNotifications() {
         </view>
         <scroll-view scroll-x>
           <view class="flex items-center gap-22rpx pb-24rpx">
-            <product v-for="(item, index) in newProducts" :key="index" :item="item" @item-click="toProductDetail" />
+            <product v-for="(item, index) in newProducts" :key="index" :item="item" @item-click="toPage('/pages/productDetail/productDetail', { id: item.id })" />
           </view>
         </scroll-view>
       </view>
@@ -217,9 +176,8 @@ function toNotifications() {
             <wd-tab :title="item.title" />
           </block>
         </wd-tabs>
-        <view class="flex flex-wrap gap-22rpx">
-          <product v-for="(item, index) in newProducts" :key="index" :height="340" :item="item" @item-click="toProductDetail" />
-          <view />
+        <view class="grid grid-cols-2 gap-22rpx">
+          <product v-for="(item, index) in newProducts" :key="index" flex width="100%" :height="340" :item="item" @item-click="toPage('/pages/productDetail/productDetail', { id: item.id })" />
         </view>
       </view>
     </view>

+ 26 - 21
src/pages/productDetail/productDetail.vue

@@ -12,6 +12,9 @@
 // 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 { getDetail } from '@/api/product'
+import { formatNumber } from '@/utils/index'
+import { getPageParams, goBack } from '@/utils/page'
 import CustomTooltip from './components/CustomTooltip.vue'
 import NotificationCarousel from './components/NotificationCarousel.vue'
 
@@ -101,35 +104,37 @@ function toPage() {
     url: '/pages/productDetail/checkOut',
   })
 }
-
-const dataList = ref([])
-function queryList(pageNo, pageSize) {
-  // 此处请求仅为演示,请替换为自己项目中的请求
-  setTimeout(() => {
-    dataList.value = [
-      { title: '123' },
-      { title: '123' },
-      { title: '123' },
-      { title: '12345' },
-    ]
-    paging.value.complete(dataList.value)
-  }, 1000)
+const detail = ref<any>({
+  sliderImage: '',
+})
+async function queryDetail(id) {
+  const res = await getDetail({ id })
+  console.log(res)
+  detail.value = res.data
+  paging.value.complete()
 }
+const id = ref('')
+onLoad((options) => {
+  console.log(options)
+  const params = getPageParams(options)
+  id.value = params.id
+  queryDetail(id.value)
+})
 </script>
 
 <template>
-  <z-paging ref="paging" refresher-only use-page-scroll @query="queryList" @click="handlePageClick">
+  <z-paging ref="paging" use-page-scroll refresher-only @on-refresh="queryDetail(id)" @click="handlePageClick">
     <wd-navbar :bordered="false" safe-area-inset-top fixed :left-arrow="false" :custom-style="`background: ${navBgColor}; transition: background 0.3s;`" custom-class="h-auto!">
       <template #title>
         <view class="box-border h-full flex items-center justify-between p-24rpx">
-          <image :src="`/static/icons/left-icon${navBgColor === '#ffffff' ? '-tr' : ''}.png`" class="h-56rpx w-56rpx" />
-          <image :src="`/static/icons/menu-icon${navBgColor === '#ffffff' ? '-tr' : ''}.png`" class="h-56rpx w-56rpx" />
+          <image :src="`/static/icons/left-icon${navBgColor === '#ffffff' ? '-tr' : ''}.png`" class="h-56rpx w-56rpx" @click="goBack" />
+          <image :src="`/static/icons/menu-icon${navBgColor === '#ffffff' ? '-tr' : ''}.png`" class="h-56rpx w-56rpx" @click="goBack" />
         </view>
       </template>
     </wd-navbar>
     <view class="relative">
       <wd-swiper
-        v-model:current="current" :list="swiperList" autoplay height="750rpx"
+        v-model:current="current" :list="detail.sliderImage.split(',')" autoplay height="750rpx"
         custom-indicator-class="bottom-40rpx!" :indicator="{ type: 'fraction' }" indicator-position="bottom-right"
         image-mode="aspectFit" @click="handleClick" @change="onChange"
       />
@@ -155,20 +160,20 @@ function queryList(pageNo, pageSize) {
             <text class="text-48rpx">
               <text class="text-28rpx">
-              </text>1000.00
+              </text>{{ formatNumber(detail.price) }}
             </text>
             <text class="ml-22rpx text-28rpx line-through">
-              ৳1999.00
+              ৳{{ formatNumber(detail.otPrice) }}
             </text>
           </view>
         </view>
         <text class="text-28rpx">
-          1.8k sold
+          {{ detail.sales }} sold
         </text>
       </view>
       <view class="bg-white px-24rpx pb-24rpx pt-20rpx text-32rpx">
         <view class="line-clamp-2font-bold mb-16rpx">
-          BOLON Classic Aviator Polarized Sunglasses, Exclusive Eyewear Brand
+          {{ detail.storeName }}
         </view>
         <view class="flex items-center justify-between">
           <view>

+ 1 - 1
src/typings.d.ts

@@ -3,7 +3,7 @@
 declare global {
   interface IResData<T> {
     code: number
-    msg: string
+    message: string
     data: T
   }
 

+ 13 - 3
src/utils/http.ts

@@ -11,11 +11,21 @@ export function http<T>(options: CustomRequestOptions) {
       responseType: 'json',
       // #endif
       // 响应成功
-      success(res) {
+      success(res: any) {
         // 状态码 2xx,参考 axios 的设计
         if (res.statusCode >= 200 && res.statusCode < 300) {
           // 2.1 提取核心数据 res.data
-          resolve(res.data as IResData<T>)
+          if (res.data.code === '200') {
+            resolve(res.data as IResData<T>)
+          }
+          else if (res.data.code === '401') {
+            reject(res.data)
+            toast.error((res.data as IResData<T>).message || '未登录')
+          }
+          else {
+            reject(res.data)
+            toast.error((res.data as IResData<T>).message || '请求错误')
+          }
         }
         else if (res.statusCode === 401) {
           // 401错误  -> 清理用户信息,跳转到登录页
@@ -26,7 +36,7 @@ export function http<T>(options: CustomRequestOptions) {
         else {
           // 其他错误 -> 根据后端错误信息轻提示
           !options.hideErrorToast
-          && toast.info((res.data as IResData<T>).msg || '请求错误')
+          && toast.info((res.data as IResData<T>).message || '请求错误')
           reject(res)
         }
       },

+ 8 - 0
src/utils/index.ts

@@ -228,3 +228,11 @@ export function parseQs(queryString) {
       return acc
     }, {})
 }
+
+// 数字格式化,加逗号 保留两位小数 增加判空处理
+export function formatNumber(num) {
+  if (num === undefined || num === null) {
+    return '0.00'
+  }
+  return num.toFixed(2).replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
+}

+ 31 - 0
src/utils/page.ts

@@ -0,0 +1,31 @@
+//  uniapp 返回 入参为堆栈
+export function goBack(delta = 1) {
+  uni.navigateBack({ delta })
+}
+
+// uniapp 跳转页面 可携带参数
+// 示例: toPage('/pages/productDetail/productDetail', { id: 123 })
+export function toPage(url: string, params?: Record<string, any>) {
+  let targetUrl = url
+
+  if (params) {
+    const data = JSON.stringify(params)
+    targetUrl = `${url}?params=${encodeURIComponent(data)}`
+  }
+
+  uni.navigateTo({ url: targetUrl })
+}
+
+// page参数解析方法
+export function getPageParams(options: any) {
+  if (options && options.params) {
+    try {
+      return JSON.parse(decodeURIComponent(options.params))
+    }
+    catch (e) {
+      console.error('解析页面参数失败:', e)
+      return {}
+    }
+  }
+  return {}
+}