05-uniapp 开发 ImoocBlog

1)开篇

经过前面四个章节,我们已经完成了 微信小程序 的学习。那么从这一章开始我们就进入 uniapp 的项目开发之中。

整个 uniapp 阶段我们会完成一个正式的项目 慕课热搜 ,以这个项目来作为 uniapp 学习阶段的的最终产出,同时通过这个项目来贯穿所有的 uniapp 知识点,可以让大家在学习的过程中不至于那么枯燥。

目前我们的项目已经上线了:

  1. 微信小程序:微信 -> 发现 -> 小程序 -> 搜索《慕课热搜》
  2. H5https://imooc.blog.lgdsunday.club/#/

那么现在:

  • html、css、js、微信小程序,等基础知识
  • 接口文档、最终效果、等各种项目需求

各种前置条件已经全部准备就绪,项目开发即可开始!

2)uniapp 难吗?

1、引言

《慕课热搜》基于 uniapp 进行开发,关于 uniapp 的优点 -> 在【课程导学】阶段已经描述过了,如果你忘了,那么你可以回过头去看一下。

这一小节我们来点实在的,光知道它好,不行。因为不是你的,它再好,对你而言也是没啥用的……

那么怎么才能学会它呢?或者说它难学吗?这才是这一小节我们需要说明的内容。

2、内容

点击这里直接进入 uniapp 官网open in new window

想要学习 uniapp 那么需要有三个前置条件:

具备了这 3 个条件,学习 uni-app ,对你而言就是小菜一碟

  1. html + css + js: 这个相信大家都没有问题
  2. 微信小程序:这个我们已经在前面的章节非常详细的为大家讲解过了 -> uni-app 借鉴了很多小程序的内容
  3. vue:可能有很多同学一看这个,心就凉了一截。我不会 vue 咋办啊。.. 没有关系! 我敢把这个列出来,肯定就已经为大家想到了这么一点。 vue 的理念和 微信小程序 的理念有非常多相同的地方,在我们后面进行项目开发的过程中,遇到一些个别的语法时,我会为大家进行介绍的。

总之,对于大家来说,这三个条件,如果你全部具备,那自然是最好的。

如果你只具备前两个条件,也不要担心,甚至可以说是更加幸运 -> 为啥这么说? -> 因为接下来你将会在学会 uniapp 的同时,也会掌握 vue 的核心使用!

学会 uni-app 难吗? -> uni-app 的语法可以简单理解成「vue 语法」和「小程序语法」的结合体 -> 这显然不难!

3)配置 uniapp 开发环境

之前开发微信小程序之前,需要:

  1. 申请小程序账号
  2. 下载微信小程序开发工具

同样,在进行 uniapp 开发之前,我们也需要做类似这样的事情,也就是需要配置项目的开发环境

uniapp 项目的开发环境主要有两点:

  • 下载并安装开发者工具 HBuilder X
  • 安装 sass 依赖 -> 项目需要用到 sass 语法

1、下载并安装开发工具

  1. uniapp 同样提供了一个专门的开发工具 HBuilder X -> HBuilderX 下载页面open in new window
  2. 点击 DOWNLOAD
  3. 选择 App 开发版本(因为我们要实现是慕课热搜) -> 推荐使用正式版
  4. Windows 版本下载完成之后会得到一个 zip 的压缩包文件,解压完成即可使用 -> 是一个便携版
  5. MacOS 版本下来完成会得到一个 dmg 的安装包,直接安装即可

2、安装 sass 依赖

因为我们的项目开发会使用 sass,所以需要为 HBuilder X 安装 sass 编译器

  1. 打开 HBuilder X
  2. 打开插件地址:https://ext.dcloud.net.cn/plugin?id=2046open in new window -> 要登录账号,没有账号,那就注册
    1. 点击【使用 HBuilderX 导入插件】 -> 一定要去掉你浏览器的广告插件,不然,这是不会出现这个按钮的
    2. 在弹出框中点击【打开 HBuilderX
  3. 点击【是】
  4. 此时会在 HBuilderX 右下角,提示你【正在下载】
  5. 等待完成即可

安装插件

4)创建 imooc-blog

本小节做两件事:

  1. 创建项目 -> 开发工具会自动帮我们生成目录结构,就像微信小程序开发者工具一样
  2. 了解项目基本组成结构

1、创建 uni-app 项目

打开 HBuilderX -> 文件 -> 新建 -> 项目:

创建项目

效果:

效果

2、项目目录介绍

├─pages // 页面存放文件夹,等同于 微信小程序中的 pages │ └─index.vue //
默认生成的页面 ├─static // 静态资源存放文件夹 └─uni_modules // uni-app 组件目录
│ └─uni-xxx // uni-app 所提供的业务组件,等同于 微信小程序中的组件 ├─App.vue //
应用配置文件,用来配置全局样式、生命周期函数等,等同于 微信小程序中的 app.js
└─main.js // 项目入口文件 -> 初始化 Vue ├─mainfest.json //
配置应用名称、appid、logo、版本等打包信息, └─pages.json //
配置页面路径、窗口样式、tabBar 等页面类信息,等同于 微信小程序中的 app.json
└─uni.scss // uni-app 内置的常用样式变量

5)运行项目到 微信开发者工具

uniapp 支持 10 个平台,我们以 微信小程序 和 h5 平台为例子,进行演示。

为啥只适配这两个端? -> 因为其它平台很少使用,比如 360 小程序

1、运行到 微信小程序

  1. 配置【微信开发工具】路径:工具 -> 设置
  2. 设置【微信开发工具路径】:运行配置 -> 微信开发者工具路径 -> 输入微信开发者工具的安装路径,如「D:/微信 web 开发者工具
  3. 切记: 一定要在 HBuilder X 中双击打开你项目中的某一个文件(比如:App.vue
  4. 运行到微信小程序:运行 -> 运行到小程序模拟器 -> 微信开发者工具
  5. 底部会提示编译
  6. 编译成功,微信小程序自动启动

第一个问题:

问题

在小程序开发者工具里边,把安全设置的服务端口给开启了:

开启端口

【报 Bug】无法打开微信小程序开发者工具 - DCloud 问答open in new window

打开 IDE 成功:

IDE

效果:

效果

2、运行到浏览器

  1. 切记: 一定要在 HBuilder X 中双击打开你项目中的某一个文件(比如:App.vue) -> 不然浏览器是不认识的
  2. 无需配置,直接运行:运行 -> 运行到浏览器 -> Chrome
  3. 编译完成,浏览器自动打开,运行成功

运行到浏览器

一般切换到手机调试模式


在实际项目开发中,微信小程序占据了绝大多数的用户,而 H5 端是我们 web 前端常见的一种形式,所以,在以后的正常开发之中,我们项目多以微信小程序和 H5 端的适配为主

6)使用 VSCode 开发 uniapp

虽说 HBuilder X 开发体验还算不错,但是有时候金窝银窝不如自己的狗窝,当我们习惯了 VSCode 之后,有时候不太愿意换开发工具。

那么怎么使用 VSCode 来开发 uniapp 呢? 其实是有办法的。

  1. 使用 HBuilder X 运行项目(运行方式,参考上一小节)
  2. 使用 VSCode 打开项目
  3. VSCode 中安装插件:
    1. uni-helperopen in new window - 让开发者在 VSCode 中开发 uni-* 的体验尽可能好。
    2. uni-app-snippetsopen in new window - 支持 uni-app 基本能力的代码片段,包括组件和 API
    3. uni-app-schemasopen in new window - 支持 uni-app pages.jsonmanifest.json 简单的格式校验
    4. uni-ui-snippetsopen in new window - 支持 uni-ui 组件代码片段
  4. VSCode 中修改代码,运行结果自动发生变化

HBuilder X 作为中介,我们在 VSCode 中写代码 -> 为了提高自己的开发体验(比如 API 提示等),可以安装插件 -> 安装一个uni-helper插件,其它三个插件自动安装

💡:uniapp 的 uni_modules 目录需要提交到 Git 吗?

需要提交!

7)创建与配置 tabBar

目前,项目的运行以及开发环境都搞定了,就下来我们要做的就是开发这个项目!

先搞定这个项目的结构 -> 也就是 tabBar 切换 -> uniapp 模仿微信小程序,也就是创建姿势,跟微信小程序一样

1、创建页面

把默认的页面删了,由于安装了插件,VSCode 也可以右键目录名创建页面 -> 但建议还是用 HBuilder 创建页面

  1. 删除 pages 下的 index 文件夹
  2. pages 文件夹处,右键 -> 选择新建页面
  3. 确认新建页面的信息
  4. 点击创建按钮完成新建
  5. 循环以上顺序,依次完成 hothot-videomy 三个页面的创建

新建页面

效果:

创建页面

2、配置 pages.json

  1. 删除 index 路径

  2. 新建 tabBar 节点

  3. 复制 资源 文件夹下 tab-icons 文件夹到 static 文件夹中 -> 删掉原先存在的图片

  4. 编写tabBar代码

  5. 如果修改完成之后,依然得到了以下错误,那么可以在 HBuilder X 中重新运行项目到微信开发者工具解决 -> 这是小程序开发者工具的 bug(无法更新已经删除了文件这种情况,似乎有缓存啊) -> 重新运行项目就可以解决这个 bug 了

    bug

完整tabBar代码:

代码

效果:

效果

8)警告与错误处理

1、解决 【sitemap 索引情况提示】的警告问题

警告问题

微信小程序默认开启了索引功能,但是因为我们没有配置索引策略,导致出现了这么一个警告的问题。具体情况可以参考:https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.htmlopen in new window

而如果想要解决这个警告也非常简单,因为一般情况项目不需要被微信索引,所以我们只需要 关闭 默认索引功能即可!

索引问题

双击打开 manifest.json -> 点击源码视图 -> 下翻找到 mp-weixin 配置节点 -> 在 settings 下新增 "checkSiteMap" : false

2、解决 Cannot read property 'forceUpdate' of undefined 的错误

forceUpdate

这个错误的原因非常简单 -> 因为我们没有为项目配置 AppID 的原因,所以只需要完成 AppID 配置即可。 -> 我们说过开发一个微信小程序项目,首先得有微信开发者工具,其次得有AppId

AppID


错误处理完成后,就可以进入到我们的页面开发了!

9)热搜页面分析

整个项目分为三大模块,我们首先去开发第一个模块——热搜模块

我们会把「热搜模块」分成四部分去进行开发:

热搜页面

10)认识 .vue 文件结构与 logo 图片展示

1、.vue 文件结构

文件结构

  1. template:定义当前页面的结构。相当于 wxml
  2. script:定义当前页面的逻辑。相当于 js
  3. style:定义当前页面的样式。相当于 wxss
    1. style 标签增加 scoped 属性:表示当前样式只在当前页面生效

2、logo 图片展示

注意点:

  • 路径以@开头 -> @表示项目的根路径,也就是从项目的根路径开始找文件 -> src="@/static/images/logo.png"
  • 可以用全局样式变量,因为我们用了scss -> 无须导入这个uni.scss -> uniapp 帮我们自动引入了,我们可以直接使用这些全局样式变量
  • logo 图片 -> 不要拉伸 -> mode="aspectFit"

代码:

代码

效果:

效果

👇:logo 处理完成,处理搜索框

11) 创建搜索框组件

  • 创建自定义组件的方式跟在微信小程序开发者工具里边所创建的姿势是一模一样的
  • 组件的结构和页面的结构是一样的 -> 都是.vue文件,里边分为三块
  • 组件定义好后,可以直接使用,无需注册 -> 微信小程序需要在页面配置里边注册,而 uni-app 则不需要

做法:

  1. 创建 components 文件夹

  2. 右键components文件夹 -> 新建组件

    创建组件

  3. 明确当前 my-search 组件的能力(暂时不需要考虑太多之后的能力)

    1. 具备输入框的样式
    2. 不可进行输入 -> 本质上是一个按钮,只是看起来像输入框罢了
    3. placeholder 内容可以在父组件定义 -> 不是在组件内写死的
  4. 代码实现

    1. 第一点和第二点的能力 -> 用 CSS 即可实现
    2. 第三点能力 -> 父向子传值 -> 使用props -> 小程序是用properties

代码:创建搜索框组件 · ppambler/imooc-uni-app@651bcb6open in new window

目前这个my-search组件的功能是非常非常简单的,后边会赋予my-search更多的一些能力,这样它就会变得非常非常的复杂了

比如:

my-search 组件

12)tabs 组件 - 创建并分析 my-tabs 组件

  1. 创建 my-tabs 组件
  2. 分析 my-tabs 组件的能力 -> 希望创建一个通用的 my-tabs 组件,可以满足各个应用中的需求 -> 既然是通用的,内容就不能写死了
    1. 可在父组件中定制 my-tabs 样式 -> 比如下划线颜色
    2. 可在父组件中设置展示数据 -> 每个 tab 的内容是啥
    3. 可在父组件中设置默认的激活项(选中项) -> 选中高亮等
    4. 由此分析,定义出以下代码:
    5. Demoopen in new window

啥叫通用的组件? -> 这个项目里边能用,其它项目里边导入该组件也能用它 -> 说白了就是「轮子」呗! -> 既然想要把my-tabs开发成一个轮子 -> 那么my-tabs的复杂端将远远超过我们之前所开发的my-search组件 -> 你可以认为my-tabs是我们这个项目中第一个比较复杂的自定义组件

至此,我们指定了三个可定制的内容

本小节我们创建了my-tabs组件 -> 分析了my-tabs组件中所具备的能力

👇:my-tabs组件中的内容开发

13)tabs 组件 - 封装网络请求

要使用my-tabs,就需要把tabs的数据给展示出来 -> 想要展示tabs数据,就得调用封装的接口来获取数据 -> 接口文档:热搜 -> 热搜文章类型

发请求在微信小程序里边是wx.request,那在 uni-app 里边呢? -> uni.request

  1. 创建 utils 文件夹
  2. 创建 request.js ,封装请求对象
  3. 创建 api 文件夹 -> 放置所有的网络请求的相关方法
  4. 创建 hot.js 文件,封装 hot 相关的请求方法:getHotTabs
  5. hot.vue里边使用getHotTabs -> 在loadHotTabs方法里边发起请求
    1. 在哪里调用loadHotTabs方法? -> 在created里边:组件实例配置完成,但 DOM 未渲染,我们可以在这个钩子里边进行网络请求,配置响应式数据 -> 这跟 Vue 是一样的

Demoopen in new window

至此,一个基本的网络请求代码就已经完成了!

14)tabs 组件 - 进行基本的数据展示

文档:scroll-view - uni-app 官网open in new window

  • scroll-x:允许横向滚动 -> 默认值是false
  • scroll-with-animation:在设置滚动条位置时使用动画过渡 -> 默认值是false
  1. hot 中使用my-tabs组件 -> 父子通信 -> 传递两个参数:
    1. tabData:tabs 数据源
    2. defaultIndex:当前的切换 index
  2. my-tabs 组件中展示
    1. scroll-view
    2. <block v-for="(item, index) in tabData" :key="index"></block> -> uni-app 遵循 vue 的 v-for 指令,小程序是wx:for="🟡🟡arr🟡🟡",默认变量名是item,下标是index

效果:

效果

Demoopen in new window

15)tabs 组件 - 美化样式

这个样式的实现过程是很丑陋的…… -> 我很难理解为啥要嵌套那么多层……

tabs 组件

Demoopen in new window

头两个能力已经实现了,并且把数据给展示出来了,那么现在第三个能力「在父组件中选中项」 -> 这该如何实现呢?目前,我们已经给了这个激活项一个对应的propsdefaultIndex)了 -> 有了这个props该如何实现激活项呢?

在开始下一小节前,请先自己去实现一遍(唯有自己尝试实现一遍,你才会发现这里边所存在的问题) -> 实现失败有失败的问题,实现成功也有成功的问题 -> 带着问题去看下一小节,唯有这样你才会收获更多!

要实现的效果:

效果

16)tabs 组件 - 设置激活项

自己实现一遍:

  1. 判断defaultIndex是否和index相等 -> 相等即给item添加一个active
  2. 写样式 -> 如何才能有下划线?
  3. 点击某个 tab ,更新 defaultIndex的值 -> 如何为my-tabs组件绑定点击事件? -> 子向父传参
  4. 下划线的滑动效果如何实现呢?

注意点:

  • 父组件传递的数据,我们不应该在子组件中进行修改
  • uni-app 的点击事件用@click,微信小程序则是用bind:tap
    • 用来更新activeIndex的值
    • 并且向外界通知一个tabClick事件
  • 数据监听器用watch,微信小程序则是用observers
    • 监听defaultIndex这个数据 -> 必须添加immediate: true,表示defaultIndex第一次赋值(默认值或者父传递过来的数据)也要被监听到 -> 为了更新activeIndex的值而服务 -> 这就是「父组件传递的数据」不应该在子组件中进行修改
  • 发送事件通知用$emit ,微信小程序则是用triggerEvent

关于老师的代码实现:active的切换是在子组件内部通过修改activeIndex来完成的,而我的是在父组件通过修改defaultIndex的值来完成的

Demoopen in new window

实现效果:

效果

👇:实现激活项下边的滑块效果

17)tabs 组件 - 定义滑块

  • 使用style添加内联样式
  • 定义了一个slider数据,它旗下有个left属性,用来指定这个滑块距离左侧的距离是多少,默认是0

定义滑块

Demoopen in new window

👇:实现滑块的滚动效果

18)tabs 组件 - 实现滑块的滚动 01

想要实现滑块的滚动:

  1. 确定滚动的时机 -> 也就是滑块什么时候发生滚动 -> 监听激活项的变化 + tab 的点击事件处理时
  2. 计算滑块滚动的距离 -> 也就是this.slider.left的值

如何计算滑块滚动的距离?

滑块的滚动距离

我们要知道tabItem的宽度,tabItemleft,slider 的width

一个标准的数学公式:

left = tabItem.left + (tabItem.width - slider.width) / 2

slider left

这个公式似曾相识

19)tabs 组件 - 实现滑块的滚动 - 02

  1. slider 的width很容易确定,因为这是固定的 -> 添加默认配置数据:defaultConfig
    1. 配置下划线的宽高以及颜色 -> 添加到 view.underLinestyle
  2. 创建一个内部维护的数据对象tabList,相较于父组件传过来的tabData,每个item多了一个_slider属性,该属性有个left属性,存储的是滑块距离左侧的距离值
  3. 监听tabData的变化,再次强调,设置了immediate:true,第一次的初始值,也是会执行它的handler的,所以在updateTabWidthtabToIndex会有一个判断tabList是否为空数组的语句
    1. 第一次执行updateTabWidth没啥反应 -> 因为此时tabData为空,所以tabList也为空
    2. 第二次执行updateTabWidth,获取每个 tabItem DOM 元素的信息,计算每个 tabItem 距离左侧的距离,也就是给tabList里边的每个item添加一个_slider.left -> 因为父组件传递了tabData,所以tabData不为空 -> 默认会有一次计算「滑块」的位置,比如初始的第一次
  4. 切换tabItem -> 点击事件触发 -> 更新this.slider.left的值,好让view.underLinetransform根据这个this.slider.left产生水平位移

一些小技巧:

  • 什么时候可以获取 DOM 元素? -> 可以在handler里边用setTimeout -> $nextTick()兼容性不好
  • 获取 DOM 元素时:给每个tabItem添加了:id="'_tab_' + index"
  • 获取 DOM 的固定写法:
    • const query = uni.createSelectorQuery().in(this);
    • query.select("#_tab_" + index).boundingClientRect((res) => { // 获取每个元素在这个页面的空间信息 }).exec()

tab Item


效果:

效果

Demoopen in new window

20)tabs 组件 - scrollView 的点击位移

需求:当【选中项】发生变化时,希望 scrollView 也进行对应的位移 -> 说白了,想选最后一个,不让用自己拖动这个 scroll 视图容器

用代码模拟我们用鼠标滑动的滚动视图容器的效果

位移

关键代码:

  • :scroll-left="scrollLeft"
  • this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;

Demoopen in new window

21)tabs 组件 - 增加可配置项

需求:可在父组件中定制 my-tabs 样式

指定 my-tabs 样式

做法:

  1. datadefaultConfig追加几项有关样式的配置
  2. 监听props——config -> 让内部维护的数据对象defaultConfig跟父组件传递过来的数据合并一下 -> 说白了,子组件内部默认的样式配置作为兜底值
  3. style属性指定每个tabItem的样式

Demoopen in new window


至此,我们就已经完成了tabs组件的开发了 -> 整个tabs组件从创建开始到最后完成,总共经历了 9 小节内容

tabs 组件是我们这个项目中第一个复杂的组件,并且我们希望把这个tabs组件做成一个轮子,可以满足更多应用的需求 -> 所以我们不光要实现功能就完事儿了,我们还要提供定制化的一些特性(比如样式),这样的话,才能让我们这个组件来满足各个项目的各个场景里边去

22)List 组件 - 分析 List 组件

这是首页的最后一个功能!

分析完成品里边 List 组件的效果,得出我们要做以下几个步骤才能实现它:

  1. 使用 mock 数据(假数据),构建 List 的基本结构 -> 此时后端那边还没有接口
  2. 美化 item 样式
  3. 根据 tab 的切换,获取真实数据
  4. 渲染真实数据
  5. 让 List 具备左右切换的能力 -> 通过 swiper 改造 List
  6. 完成 list 与 tabs 的联动的能力 -> 也就是完成 swiper 和 tabs 的联动效果 -> tabs 切换,list 视图也切换,list 视图左右滑,tabs 也会自动跟着切换

23)List 组件 - 使用 mock 数据,构建 List 的基本结构

组件的根元素 -> 控制同类盒子之间的间隙;盒子容器 -> 控制盒子的外观;内容容器 -> 控制元素

有多个子元素就用一个容器把它们给包裹了

把每个item项抽离成单独的组件——hot-list-item

使用假数据把组件的基本结构给渲染出来

hot-list-item

item的结构划分:

结构划分

左侧展示的索引图标,而且每个item都有,并且都不一样,所以我们可以把它封装成一个组件——hot-ranking

注意,这个图标只是图片,这个图片是包含数字内容的


结构

目前这个结构有了,就是样式长得太丑了:

样式太丑

Demoopen in new window

24)List 组件 - 美化 item 样式

效果:

效果

Demoopen in new window

25)List 组件 - 根据 tab 的切换,获取真实数据

所谓的真实数据指的是后端的接口已经写好了!你可以根据这个接口的定义去获取数据,之前的假数据只是为了完成这个List组件的结构以及样式!

  1. 监听tab切换
    1. my-tabs组件 -> 发送了一个tabClick通知
    2. 父组件接收这个通知 -> 修改激活项 -> 让子组件此刻激活的index和父组件的currentIndex绑定
  2. 发送数据请求 -> 获取热搜文章列表
    1. hot.js封装请求接口
    2. 父组件定义一个方法loadHotListFromTab -> 该方法的作用是「获取 List 列表数据」 -> 什么时候调用这个方法 -> 在调用loadHotTabs的时候,因为,我们获取 list 数据时,需要 tab 中对应的 id -> 这是初始化列表的第一次数据请求
  3. 缓存请求得到的数据 -> 第一次请求,那就把这次请求得来的数据给缓存下来 -> 切换旧的,不会再次发送请求
    1. 这个 tab 从未获取过列表数据 -> loading
    2. 这个 tab 已经获取过列表数据 -> 直接渲染

关键点:

  • 缓存数据源的定义:listData: {} -> keytabItemidvalue是这个tabItem所对应的list数据

注意点:

  • 父组件要监听子组件发布过来的事件 -> 微信小程序通过bind监听事件,uniapp 遵循 vue 规则,通过 @ 监听事件
  • 加载动画 -> uni-app 提供了一个uni-load-more组件 -> 用来专门展示加载动画的组件

效果:

效果

Demoopen in new window

👇:渲染真实数据

26)List 组件 - 渲染真实数据

  1. 替换掉假的数据源:v-for="(item, index) in 50" -> v-for="(item, index) in listData[currentIndex]"
  2. 把每个list数据传给 List 组件 -> 给 List 组件定义两个propsdata(循环列表的 item 数据)、ranking(排名次序数据)
  3. 父组件hot传递数据给了子组件list -> 子组件得把数据给显示出来 -> 根据接口文档来确定有哪些数据需要被展示:比如titlenickname
  4. 样式美化 -> 简介最多展示两行 -> 这个样式经常被用到 -> 抽离出一个styles文件夹,在这个文件夹下边创建一个global.scss -> 定义公共样式的地方 -> 展示两行的样式是固定的 CSS 写法 -> 需要做兼容处理 -> 在main.js引入公共样式 -> 谁要添加这个类?
    1. 标题
    2. 简介
  5. 处理排名,也就是给hot-ranking组件添加props
    1. 根据不同的排名,显示不同的背景图片,图片分为:1、2、3、其它,这四种
      1. 使用计算属性 -> 根据传递过来的排名数据,来决定渲染的是哪张背景图 -> JS 加载图片用require
    2. 处理排名的文本颜色 -> 用:class
      1. 前三名是白色
      2. 非前三名是黑色

效果:

效果

Demoopen in new window

前四步已经完成:

  1. 使用 mock 数据(假数据),构建 List 的基本结构 -> 此时后端那边还没有接口
  2. 美化 item 样式
  3. 根据 tab 的切换,获取真实数据
  4. 渲染真实数据

接下来实现:

  1. 让 list 拥有左右切换的能力
  2. 让 list 和 tabs 有联动效果

💡:关于文本的截断

27)List 组件 - 通过 swiper 改造 List

想要让 list 具备【横向翻页】的效果,那么可以使用 swiper 对其进行改造!

  1. 定义一个swiper组件 -> 具备滚动能力:
    1. 每一个swiper-item下边都是一个遍历完成的一个个hot-list-item -> 循环次数由listData决定 -> 此时用的是tabIndex属性(tabData0~6),而不是之前的currentIndex属性,因为swiper滑动的时候,也是需要变化的 -> 需要给swiper组件指定current属性(值是currentIndex,也就是我们点击某个tab可以切换列表数据),它决定当前展示哪个 swiperItem,不然这个tabIndex永远为0
    2. swiper-item的数量取决于my-tabs里边的tabItem数量 -> tabData决定 -> v-for处理一下

swiper

current属性:默认值是0,表示当前所在滑块的 index

效果:

swiper-item默认展示第一个 list 的数据,其它 6 个 swiper-item,你在滑动的时候,你会发现这是空的:

空的

当你第一次点击其它tab时,会加载这个tab对应的list数据,这也就意味着,这个「空」就被填上了这个list数据!

有数据

要把这些「空」都填完,你得把把其余的没有点过的tab都给点一遍……这样左右滑的时候都会看到有数据存在了! -> 为啥这样点就会有数据? -> 因为我们点击tab,就是在请求这个tab所对应的list数据啊,而这写数据会被缓存到listData

Demoopen in new window

目前存在的问题:

  1. list列表的高度展示错误 -> 也就是列表数据没有展示全
  2. 切换 tab 时,list 的渲染出现卡顿问题

问题

应该是 list 的数据渲染完再滑?还是滑完后再渲染? -> 等大风小了再走,还是不管大风直接走呢?

28)List 组件 - 解决列表高度展示错误的问题

  • 原因:没有给 swiper 指定高度
  • 解决方案:指定高度即可

可这个高度该给多少呢?1000px2000px

固定高度

剩余数据

你要知道每个tabs所对应的list数据的高度是不一样的! -> 这个接口给出来的每个tab都是20条数据,不过我们假设都不一样!

最终的解决方案:计算出每个 listItem 的高度,然后叠加到一起,就可以得到 swiper 的高度了!

这个方案的实现逻辑:

  1. 定义数据
    1. currentSwiperHeight:当前 swiper 的高度
    2. swiperHeightData:缓存高度的计算结果:以 indexkey,对应的 swiper 的高度 为 val -> 每次计算太耗性能了,你得把计算结果给缓存下来
  2. 定义一个方法:getCurrentSwiperHeight -> 用来帮我们计算当前 swiper 的高度
    1. 拿到所有的 item -> 这是一个异步操作(节点信息 - uni-app 官网open in new window
      1. 获取节点信息有固定的代码写法
    2. 拿到所有 item 的高度 -> 来自一个个的节点信息
    3. 把所有的高度累加
  3. 什么时候调用这个方法? -> 当渲染完成数据之后,再去计算高度
    1. this.$nextTick 存在一定的兼容性问题,所以更加推荐使用传统的方式 setTimeout
    2. getHotListFromTab里边的setTimeout的回调里调用
      1. 拿到getCurrentSwiperHeight的结果值,也就是说swiper的高度
      2. 把这个高度放入swiperHeightData缓存中 -> 当前选中的tabItem索引值作为key,而对应的value就是这个高度值
  4. 使用这个缓存高度:currentSwiperHeight

注意:

注意

效果:

效果

Demoopen in new window

29)List 组件 - 解决 切换 tab 时的 list 的卡顿问题

💡:为什么会出现这个卡顿问题?

原因:swiper 动画未完成时,就获取数据,渲染 DOM

具体来说就是:点击 tab 切换 -> 修改currentIndex -> currentIndex绑定到了swipercurrent属性 -> 也就说 tab 一切换,swiper就会发生对应的切换/滑动效果 -> 在发生切换效果时,swiper会执行一个动画效果,但是我们的切换动画和我们的数据获取渲染列表是同步进行的,也就说 swiper 动画未完成时,就获取数据,渲染 DOM -> 所以这就导致了卡顿问题?

💡:如何解决呢?

有了原因之后,这解决方案就非常简单了!

解决方案:swiper 动画完成之后,再去获取数据,渲染 DOM

💡:如何监听 swiper 的动画完成?

  1. swiper绑定一个animationfinish事件 -> 表示我们当前动画完成之后的回调 -> onSwiperEnd
  2. onSwiperEnd这个方法的逻辑:
    1. 在这个方法里边请求列表数据,原先在onTabClick里边的获取列表数据的方式就不需要了
    2. 判断是否有缓存再去获取列表数据 -> loadHostListFromTab里边的判断缓存不需要了,直接获取数据即可
    3. 没有缓存:获取数据,直接return -> e.detail.current也可以拿到当前切换的currentIndex
    4. 有缓存:未return,则证明存在数据缓存,存在数据缓存,则同时存在 height 的缓存数据 -> 设置currentSwiperHeight这个高度值

效果:

效果

Demoopen in new window

至此:

  • 问题 1:list 列表的高度展示错误
  • 问题 2:切换 tab 时,list 的卡顿问题

这两个问题就已经全部搞定了

这也就意味着让 list 具备左右切换的能力也已经全部搞定了

六个步骤已经完成了其中的五个步骤 -> 我们需要完成第六步:「list 与 tabs 联动的能力」,也就是完成它们俩之间的联动效果

30)List 组件 - swipertabs 联动

所谓的联动能力:

  • tabs 切换时,swiper 联动切换 -> 这一步已经完成了,点击某个tabswiper会自动滑动到相应的list
  • swiper 切换时,tabs 联动切换 -> 这一步未完成

实现逻辑:

  1. 监听swiper切换的事件 -> @change -> 通过e.detail.current获取切换完成后此时的swiper下标 -> onSwiperChange
  2. onSwiperChange:更新currentIndex的值

效果:

效果

Demoopen in new window

目前的问题:激活的tabItem,其底部没有那个滑块跟随着

31)List 组件 - tabs 中滑块跟随滚动

为啥不跟着滚动? -> 分析一下这个原因

我们知道滑块的滚动依赖my-tabs组件里边的tabToIndex这个方法来进行计算的

activeIndex发生切换的时候 -> 我们需要重新调用这个tabToIndex

实现逻辑:

  • hot.vue -> currentIndex变化,defaultIndex这个传给my-tabs组件的props更新了
  • my-tabs -> watch -> defaultIndex -> handler -> tabToIndex()(注意,第一次tabList为空时,直接返回就好了)

效果:

效果

Demoopen in new window

至此,我们的第六步代码就已经完成了,整个list组件的功能就已经全部搞定了

不过,目前还有一些小问题,比如:

  • 滚动 list 到底部,希望有吸顶的效果
  • 热度希望展示成4k这样的效果,而不是4000

问题

我发现一个问题,list的数据都缓存了,swiperanimationfinish事件还是会被触发 -> 我突然明白这个动画指的是swiperItem左右切换的动画,而不是loading动画

32)完成 tabs 的吸顶效果

目前,hot这首页的主要功能就已经完成了,剩下的都是一些边边角角的修复

💡:功能补充:让 tabs 具备吸顶的效果

  1. my-tabs标签包装一层view.tab-sticky
  2. tab-sticky这个类添加样式

Demoopen in new window

💡:功能补充:控制列表滚动位置

这是啥功能?

控制列表滚动位置

33)控制列表滚动位置

  1. 当用户滚动页面之后
  2. 切换 tabs 时,让页面的滚动位置返回为 tabs 吸顶的位置

这个交互我不是很认可啊,我对比华为的应用市场 App 软件,和极客时间的 App,都发现每个tabItem所对应的list列表的滚动位置是会保存的,也就是第一个tabItem,你在1000px高度处,你切换到第二个tabItem,此时你滚动到2000px高度处,你再切回来,这第一个tabItem还是在1000px高度处 -> 不过,那个打卡小程序,也没有做这样的效果!

实现逻辑:

  1. 定义一个currentPageScrollTop数据:表示当前的滚动距离
  2. 监听页面的滚动:onPageScroll -> 这是关于 uni-app 的页面生命周期
    1. 130px这个位置正好是触发吸顶的位置
    2. 通过uni.pageScrollTo控制列表的滚动位置

文档:生命周期 - uni-app 官网open in new window

Demoopen in new window

👇:搞定这两个补充功能后,接下来处理热度显示的问题

34)List 组件 - 处理热度的显示

用过滤器

实现逻辑:

  1. 创建一个filters目录 -> 包含所有的过滤器
    1. 创建一个index.js -> 暴露一个hotNumber方法 -> 用来把大于1000的字符串数字转化成以k结尾的字符串数字
  2. 注册过滤器的最佳实践 -> main.js -> 会把所有过滤器都给注册了
  3. 使用过滤器 -> hot-list-item.vue

Demoopen in new window

效果:

效果

至此,我们这个首页的功能开发就基本完成了!

35)总结

用了 36 节,4 个多小时!

这个首页内容是我们接触 uni-app 后所完成的第一个比较复杂的页面 -> 这个页面的复杂度只要体现在tabslist这两个位置

复杂页面

首页内容完成:

  1. uniapp 进行了基础的了解 -> uni-app 结合了微信小程序语法和 Vue 语法的一个结合体
  2. 创建 imooc-blog 的项目
  3. 完成了 tabbar 的搭建 -> 小程序里边的搭建规则
  4. 了解了 .vue 的单文件组件 -> 由哪几部分组成? -> 在这种文件里边,我们实现了很多的页面以及对应的组件
  5. 分析了首页的模块组成
  6. 封装了 request API 请求模块 -> 使用 Promise 自己封装,而不是网络上现成的库
  7. 实现了复杂组件 tabslist组件 -> 在实现的过程中,抛出了很多问题,通过思考这些问题,然后解决这些问题,这会让你收获很多!
  8. 完成了tabs 和 基于 swiper 的列表联动

👇:完成新的页面:慕课搜索页面