Skip to content
On this page

推荐模块

image-20221125124734660

静态结构 (复制)

src\pages\recommend\index.vue

vue
<template>
  <view class="viewport">
    <!-- 推荐封面 -->
    <view class="cover">
      <image
        src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg"
      />
    </view>
    <!-- tab栏 -->
    <view class="tabs">
      <text class="text active"> 抢先尝鲜 </text>
      <text class="text"> 新品预告 </text>
    </view>
    <!-- 列表内容 -->
  </view>
</template>
<script>

export default {};
</script>

<style lang="scss">
page {
  height: 100%;
  background-color: #f4f4f4;
}
.viewport {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 180rpx 0 0;
  position: relative;
}
.cover {
  width: 750rpx;
  height: 225rpx;
  border-radius: 0 0 40rpx 40rpx;
  overflow: hidden;
  position: absolute;
  left: 0;
  top: 0;
}
.tabs {
  display: flex;
  justify-content: space-evenly;
  height: 100rpx;
  line-height: 90rpx;
  margin: 0 20rpx;
  font-size: 28rpx;
  border-radius: 10rpx;
  box-shadow: 0 4rpx 5rpx rgba(200, 200, 200, 0.3);
  color: #333;
  background-color: #fff;
  position: relative;
  z-index: 9;
  .text {
    margin: 0 20rpx;
    position: relative;
  }
  .active {
    &::after {
      content: "";
      width: 40rpx;
      height: 4rpx;
      transform: translate(-50%);
      background-color: #27ba9b;
      position: absolute;
      left: 50%;
      bottom: 24rpx;
    }
  }
}
.scroll-view {
  flex: 1;
  overflow: hidden;
}
.goods {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 0 20rpx 20rpx;
  .navigator {
    width: 345rpx;
    padding: 20rpx;
    margin-top: 20rpx;
    border-radius: 10rpx;
    background-color: #fff;
  }
  .thumb {
    width: 305rpx;
    height: 305rpx;
  }
  .name {
    height: 88rpx;
    font-size: 26rpx;
  }
  .price {
    line-height: 1;
    color: #cf4444;
    font-size: 30rpx;
  }
}
.goods .symbol,
.goods .decimal {
  font-size: 70%;
}
</style>

动态设置页面标题

目标:实现动态设置推荐页面的标题

思路:从首页跳转到推荐页面时,传来参数type。根据type的值,显示不同的title

步骤:

  1. 声明对象urlMap,保存title与url等信息
  2. 页面加载后,获取参数type的值
  3. 根据type的值,从urlMap中取出信息
  4. 动态设置页面title
js
<script>
// 1. 声明对象`urlMap`,保存title与url等信息
const urlMap = {
  1: { title: "特惠推荐", url: "/home/preference/mutli" },
  2: { title: "爆款推荐", url: "/home/inVogue/mutli" },
  3: { title: "一站买全", url: "/home/oneStop/mutli" },
  4: { title: "新鲜好物", url: "/home/new/mutli" }, // 后端未提供,暂用新鲜好物url
  5: { title: "新鲜好物", url: "/home/new/mutli" },
};
export default {
  // 2. 页面加载后,获取参数type的值
  onLoad({ type }) {
    // 3. 根据type的值,从urlMap中取出信息
    this.currentItem = urlMap[type];
    // 4. 动态设置页面title
    uni.setNavigationBarTitle({ title: this.currentItem.title });
  },
};
</script>

请求数据-渲染界面

目标:实现加载后请求数据

image-20221126052502941

image-20221126052532048

封装API

src\api\home.js

js
// 1. 封装API
export const getRecomendsAPI = (url) => {
  return request({ url });
};

请求数据、保存数据

src\pages\recommend\index.vue

js
<script>

import { getRecomendsAPI } from "@/api/home";

export default {
  data() {
    return {
      // 3. 声明数据
      bannerPicture: "",
      goodsItems: [],
      subTypes: [],
    };
  },

  // 2. 加载后触发
  async onLoad({ type }) {
    this.currentItem = urlMap[type];
    uni.setNavigationBarTitle({ title: this.currentItem.title });

    const { result } = await getRecomendsAPI(this.currentItem.url);
    const { bannerPicture, goodsItems, subTypes } = result;
    // 4. 保存数据
    this.bannerPicture = bannerPicture;
    this.goodsItems = goodsItems;
    this.subTypes = subTypes;
  },
};
</script>

渲染界面:

vue
<template>
  <view class="viewport">
    <!-- 推荐封面 -->
    <view class="cover">
      <!-- 5.1 渲染图片 -->
      <image :src="bannerPicture" />
    </view>
    <!-- tab栏 -->
    <view class="tabs">
      <!-- 5.2 列表渲染 -->
      <text v-for="item in subTypes" :key="item.id" class="text active">
        {{ item.title }}
      </text>
    </view>
  </view>
</template>

点谁谁高亮

目标:实现点谁谁高

思路:排它判断

步骤:

  1. 声明变量activeId
  2. 动态class添加类名active
  3. 点击事件,修改activeId
  4. 加载后设置第一项id为activeId

代码:

  1. 声明变量activeId
diff
data() {
  return {
  
+   // 1. 声明变量activeId
+    activeId: "",

  };
},
  1. 动态class添加类名active
  2. 点击事件,修改activeId
diff
<view class="tabs">
  <!-- 2. 动态class添加类名 -->
  <!-- 3. 点击事件,修改activeId -->
  <text
    v-for="item in subTypes"
    :key="item.id"
    class="text"
+    :class="{ active: activeId === item.id }"
+    @click="activeId === item.id"
  >
    {{ item.title }}
  </text>
</view>
  1. 加载后设置第一项id为activeId
diff
async onLoad({ type }) {
  const currentItem = urlMap[type];
  uni.setNavigationBarTitle({ title: currentItem.title });

  const { result } = await getRecomendsAPI(currentItem.url);
  const { bannerPicture, goodsItems, subTypes } = result;
  this.bannerPicture = bannerPicture;
  this.goodsItems = goodsItems;
  this.subTypes = subTypes;
  
+  // 4. 加载后设置第一项id为activeId
+  this.activeId = subTypes[0].id;
},

渲染内容列表

实现渲染内容列表

效果:

image-20221126052626149

步骤:

  1. 准备静态结构
  2. 列表渲染
  3. 条件渲染

代码:

  1. 准备静态结构
vue
<scroll-view
      scroll-y
      class="scroll-view"
    >
      <view class="goods">
        <navigator
          hover-class="none"
          class="navigator"
          v-for="goods in []"
          :key="goods.id"
          :url="`/pages/goods/index?id=${goods.id}`"
        >
          <image class="thumb" :src="goods.picture"></image>
          <view class="name ellipsis">{{ goods.name }}</view>
          <view class="price">
            <text class="symbol">¥</text>
            <text class="number">{{ goods.price }}</text>
          </view>
        </navigator>
      </view>
      <view class="loading">正在加载...</view>
    </scroll-view>
  1. 列表渲染
  2. 条件渲染
diff
<scroll-view
  scroll-y
  class="scroll-view"
+  v-for="(item, id) in goodsItems"
+  :key="id"
+  v-show="id === activeId"
>
  <view class="goods">
    <navigator
      hover-class="none"
      class="navigator"
-      v-for="goods in []"
+     v-for="goods in item.items"
      :key="goods.id"
      :url="`/pages/goods/index?id=${goods.id}`"
    >
      <image class="thumb" :src="goods.picture"></image>
      <view class="name ellipsis">{{ goods.name }}</view>
      <view class="price">
        <text class="symbol">¥</text>
        <text class="number">{{ goods.price }}</text>
      </view>
    </navigator>
  </view>
  <view class="loading">正在加载...</view>
</scroll-view>

内容列表-无限滚动

目标:实现下拉分页,从而实现无限滚动

思路:

  1. 每次滚动到底部时,请求下一页数据。
  2. 合并每次请求的数据,到数组中。
  3. 根据当前页page与总页数pages,决定是否继续请求下一页数据

步骤:

  1. 改造API,支持传subTypesId、page等参数。
  2. 定义计算属性,根据activeIdgooodsItems获取到当前项的分页信息
  3. 监听滚动底部事件,请求下一页更多数据
  4. 改造保存数据为合并数据

代码:

1、改造API,支持传subTypesId、page等参数。

src/api/home.js

js
export const getRecomendsAPI = (
  url,
  subTypeId = "",
  page = 1,
  pageSize = 10
) => {
  return request({
    url,
    data: {
      subType: subTypeId,
      page,
      pageSize,
    },
  });
};

2、定义计算属性,根据activeIdgooodsItems获取到当前项的分页信息

src/page/recommend/index.vue

js
computed: { 
  pageInfo() {
    if (!this.activeId) return {};
    const { page, pages } = this.goodsItems[this.activeId];
    return { page, pages };
  },
},

3、监听滚动底部事件,请求下一页更多数据

4、保存数据改为合并数据

diff
<scroll-view
      scroll-y
      class="scroll-view"
      v-for="(item, id) in goodsItems"
      :key="id"
      v-show="id === activeId"
+      @scrolltolower="loadMore"
    >
    
    ... 省略其它代码
    
    </scroll-view>
js
async loadMore() {
 
  const { result } = await getRecomendsAPI(
    this.currentItem.url,
    this.activeId,
    // 请求下一页更多数据
    this.pageInfo.page + 1
  );
  const { goodsItems } = result;
  const { items, page, pages, pageSize, counts } = goodsItems[this.activeId]
   // 4. 改造保存数据为合并数据;
  this.goodsItems[this.activeId].items.push(...items);
  // 💥💥 要更新page和pages
  this.goodsItems[this.activeId].page = page;
  this.goodsItems[this.activeId].pages = pages;

},

内容列表-停止加载

目标:实现没有更多数据时,停止请求下一页数据

步骤:

  1. 根据page和pages,定义计算属性finished
  2. 分页请求触发前,判断finished,为true则阻止发请求
  3. 条件渲染-显示没有更多数据了提示信息

代码:

1、定义计算属性finished

js
computed: {
		// ... 省略其它代码 ...
    
  finished() {
    if (!this.activeId) return false;
    if (this.pageInfo.page < this.pageInfo.pages) return false;
    return true;
  },
    
},

2、分页请求触发前,判断finished,为true则阻止发请求

diff
async loadMore() {
+  //  阻止发请求
+  if (this.finished) return;

  // ... 省略其它代码 ...

},

3、 条件渲染-显示没有更多数据了提示信息

diff
+ <view class="loading" v-if="finished">没有更多多数据了</view>

内容列表-节流

目标:防止在底部频繁触发分页请求

思路:

  1. 声明标识loading为true或false,
  2. 每次分页请求前,将loading改为true,表示正在请求,未结束。
  3. 每次分页请求后,将loading改为false,表示请,已未结束。
  4. 滚动事件触发时,如果loading为true,通过return阻止代码执行

代码省略...

Released under the MIT License.