第5章 uniapp开发ImoocBlog

收获:
1.微信小程序
2.uni-app开发慕课热搜项目
3.构建企业级项目的编程思维
4.上线可商用的企业项目

第5章 uniapp开发ImoocBlog

uniapp开发ImoocBlog

5-1:开篇

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

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

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

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

两个版本,因为在【第一章】中已经为大家演示了项目,所以这里就不再重复演示了。

那么现在:

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

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

5-2:uniapp 难吗?

引言

《慕课热搜》基于 uniapp 进行开发,关于 uniapp 的优点,我们在【课程导学】阶段已经为大家描述过了,忘了的同学可以回过头去看一下。这就不再重复去夸它了。

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

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

内容

点击这里直接进入 uniapp 官网

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

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

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

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

5-3:配置项目开发环境

下载并安装开发工具

  1. uniapp 同样提供了一个专门的开发工具 HBuilder X ,可以点击 这里直接进入下载页面

  2. 点击 DOWNLOAD

  3. 选择 App 开发版本
    第5章 uniapp开发ImoocBlog_第1张图片

  4. windows 版本下载完成之后会得到一个 zip 的压缩包文件,解压完成即可使用

  5. macos 版本下来完成会得到一个 dmg 的安装包,直接安装即可

安装 sass 依赖

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

  1. 打开 HBuilder X

  2. 打开插件地址:https://ext.dcloud.net.cn/plugin?id=2046

  3. 点击【使用 HBuilderX 导入插件】
    第5章 uniapp开发ImoocBlog_第2张图片

  4. 在弹出框中点击【打开 HBuilderX
    第5章 uniapp开发ImoocBlog_第3张图片

  5. 点击【是】
    第5章 uniapp开发ImoocBlog_第4张图片

  6. 此时会在 HBuilderX 右下角,提示你【正在下载】
    请添加图片描述

  7. 等待完成即可
    第5章 uniapp开发ImoocBlog_第5张图片

5-4:创建 imooc-blog

创建 uni-app 项目

  1. 创建项目
    第5章 uniapp开发ImoocBlog_第6张图片

第5章 uniapp开发ImoocBlog_第7张图片

项目目录介绍

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

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

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

运行到 微信小程序

  1. 配置【微信开发工具】路径
    第5章 uniapp开发ImoocBlog_第8张图片

  2. 设置【微信开发工具路径】
    第5章 uniapp开发ImoocBlog_第9张图片

  3. 切记: 一定要在 HBuilder X 中双击打开你项目中的某一个文件(比如:App.vue
    第5章 uniapp开发ImoocBlog_第10张图片

  4. 运行到微信小程序
    第5章 uniapp开发ImoocBlog_第11张图片

  5. 底部会提示编译

第5章 uniapp开发ImoocBlog_第12张图片

  1. 编译成功,微信小程序自动启动
    第5章 uniapp开发ImoocBlog_第13张图片

运行到浏览器

  1. 切记: 一定要在 HBuilder X 中双击打开你项目中的某一个文件(比如:App.vue
    第5章 uniapp开发ImoocBlog_第14张图片

  2. 无需配置,直接运行
    第5章 uniapp开发ImoocBlog_第15张图片

5-6:使用 VSCode 开发 uniapp(当你想要使用 VSCode 来开发 uniapp 时,可以查看本小节)

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

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

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

5-7:创建与配置 tabbar

创建页面

  1. 删除 pages 下的 index 文件夹

  2. pages 文件夹处,右键 -> 选择新建页面

  3. 确认新建页面的信息
    第5章 uniapp开发ImoocBlog_第16张图片

  4. 点击创建按钮完成新建。

  5. 循环以上顺序,依次完成 hothot-videomy 三个页面的创建

配置 pages.json

  1. 删除 index 路径

  2. 新建 tabbar 节点

  3. 复制 资源 文件夹下 tab-icons 文件夹到 static 文件夹中

  4. 最终代码如下:

      "tabBar": {
        "selectedColor": "#f94d2a",
        "list": [
          {
            "pagePath": "pages/hot/hot",
            "text": "热榜",
            "iconPath": "static/tab-icons/hot.png",
            "selectedIconPath": "static/tab-icons/hot-active.png"
          },
          {
            "pagePath": "pages/hot-video/hot-video",
            "text": "热播",
            "iconPath": "static/tab-icons/hot-video.png",
            "selectedIconPath": "static/tab-icons/hot-video-active.png"
          },
          {
            "pagePath": "pages/my/my",
            "text": "我的",
            "iconPath": "static/tab-icons/my.png",
            "selectedIconPath": "static/tab-icons/my-active.png"
          }
        ]
      }
    
  5. 如果修改完成之后,依然得到了以下错误,那么可以在 HBuilder X 中重新运行项目到微信开发者工具解决
    请添加图片描述

5-8:警告与错误处理

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

请添加图片描述

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

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

  1. 双击打开 manifest.json

  2. 点击源码试图
    第5章 uniapp开发ImoocBlog_第17张图片

  3. 下翻找到 mp-weixin 配置节点

  4. settings 下新增 "checkSiteMap" : false

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

这个错误的原因非常简单,是因为我们没有为项目配置 appID 的原因,所以只需要完成 APPID 配置即可。

  1. 双击打开 manifest.json

  2. 找到 - 【微信小程序配置】

  3. 在此处添加 APPID 即可
    请添加图片描述

5-9:热搜页面分析

第5章 uniapp开发ImoocBlog_第18张图片

5-10:认识 .vue 文件结构与 logo 图片展示

.vue 文件结构

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

logo 图片展示

<template>
  <view class="hot-container">
    
    <image class="logo" mode="aspectFit" src="@/static/images/logo.png" />
  view>
template>

5-11:创建搜索框组件

  1. 创建 components 文件夹

  2. 右键新建组件
    第5章 uniapp开发ImoocBlog_第19张图片

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

    1. 具备输入框的样式
    2. 不可进行输入
    3. placeholder 内容可以在父组件定义
  4. 代码实现
    my-search.vue

    
    
    
    
    
    
    

    hot.vue

    
        
        
    
    
    
    

5-12:tabs组件 - 创建并分析 my-tabs 组件

  1. 创建 my-tabs 组件

  2. 分析 my-tabs 组件的能力 -> 希望创建一个通用的 my-tabs 组件,可以满足各个应用中的需求

    1. 可在父组件中定制 my-tabs 样式

    2. 可在父组件中设置展示数据

    3. 可在父组件中设置默认的激活项

    4. 由此分析,定义处以下代码:

      
      

5-13:tabs组件 - 封装网络请求

  1. 创建 utils 文件夹

  2. 创建 request.js ,封装请求对象

    const BASE_URL = 'https://api.imooc-blog.lgdsunday.club/api';
    function request({ url, data, method }) {
      return new Promise((resolve, reject) => {
        uni.request({
          url: BASE_URL + url,
          data,
          method,
          success: ({ data }) => {
            if (data.success) {
              resolve(data);
            } else {
              uni.showToast({
                title: data.message,
                icon: 'none',
                mask: true,
                duration: 3000
              });
              reject(data.message);
            }
          },
          fail: (error) => {
            reject(error);
          }
        });
      });
    }
    
    export default request;
    
    
  3. 创建 api 文件夹

  4. 创建 hot 文件,封装 hot 相关的请求方法

    import request from '../utils/request';
    
    export function getHotTabs() {
      return request({
        url: '/hot/tabs'
      });
    }
    
    

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

  1. hot 中使用

    
    
    
    
  2. tabs 中展示

    
    

5-15:tabs组件 - 美化样式

my-tabs


5-16:tabs组件 - 设置激活项

  1. 因为根据 子组件不可以直接修改父组件传递过来的数据 特性,所以可以通过定义一个 data-> activeIndex 来跟随 defaultIndex 的变化

    <script>
    export default {  
        data: () => {
            return {
              // 当前激活项的 index
              activeIndex: -1
            };
          },
            // 侦听器
          watch: {
            // 监听激活项目的变化
            defaultIndex: {
              handler(val) {
                this.activeIndex = val;
              },
              // 该回调将会在侦听开始之后被立即调用
              immediate: true
            }
          },
    	}
    </script>
    
  2. 找到 tab-itemview ,判断 active 的状态。并添加点击事件,修改 activeIndex 的值

    <view
          class="tab-item"
          :class="{ 'tab-item-active': activeIndex === index }"
          @click="tabClick(index)"
          >{{ item.label || item }}view
        >
    
      methods: {
        /**
         * tab 的点击事件处理
         */
        tabClick(index) {
          this.activeIndex = index;
          // 发送通知
          this.$emit('tabClick', index);
        }
      }
    
    &-active {
        color: $uni-text-color-hot;
        font-weight: bold;
    }
    
    $uni-text-color-hot: #f94d2a; // 热点颜色
    

5-17:tabs组件 - 定义滑块


<view
      class="underLine"
      :style="{
              transform: 'translateX(' + slider.left + 'px)'
              }"
      />
data: () => {
    return {
      // 滑块
      slider: {
        // 距离左侧的距离
        left: 0
      }
    };
  },
.underLine {
    height: 2px;
    width: 25px;
    background-color: #f01414;
    border-radius: 3px;
    transition: 0.5s;
    position: absolute;
    bottom: 0;
}

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

实现滑块滚动的功能

  1. 确定滚动的时机
  2. 计算滚动的距离

确定滚动的时机

  1. 监听激活项目的变化
  2. tab 的点击事件处理

确定滚动时机后,执行滚动的方法

/**
     * 根据当前的 activeIndex 下标,计算 【滑块】 滚动位置
     */
tabToIndex() {
    // 获取当前的 activeIndex
    const activeIndex = this.activeIndex;
    // 滑块的宽度
    const underLineWidth = this.defaultConfig.underLineWidth;
    // 配置 滚动条 的数据
    this.slider = {
        // TODO:left 如何定义呢?
        left: 0
    };
    console.log('TODO:left 如何定义呢?');
}    

data: () => {
    return {
      // 默认配置
      defaultConfig: {
        // 下划线宽度 px
        underLineWidth: 24,
        // 下划线高度 px
        underLineHeight: 2,
        // 下划线颜色
        underLineColor: '#f94d2a'
      }
    };

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

实现滑块滚动的功能

  1. 确定滚动的时机
  2. 计算滚动的距离
    1. 维护一个单独的数据对象 tabList
    2. tabListitem 中为一个 _slider 属性
    3. 该属性保存了 【当前 item 下 的滑块位置】
      1. 计算公式:滑块左侧位置 = item.left + (item.width - slider.width) / 2
data: () => {
    return {
      // 内部维护的数据对象,为每个 item 单独额外维护一个 slider 的滑块对象
      tabList: []
    };
  },
      
 // 侦听器
  watch: {
    // 侦听数据的变化
    tabData: {
      handler(val) {
        this.tabList = val;
        setTimeout(() => {
          this.updateTabWidth();
        }, 0);
      },
      // 该回调将会在侦听开始之后被立即调用
      immediate: true
    },
  },
      
      /**
     * 更新 tab item 的宽度
     */
      updateTabWidth() {
          /**
       * 为 tabList 的每个 item 单独额外维护一个 slider 的滑块对象
       */
          let data = this.tabList;
          if (data.length == 0) return false;

          // 获取 dom 的固定写法
          const query = uni.createSelectorQuery().in(this);
          // 循环数据源
          data.forEach((item, index) => {
              // 获取 dom 的固定写法
              query
                  .select('#_tab_' + index)
                  .boundingClientRect((res) => {
                  // 为数据对象中每一个 item 都维护一个 _slider(滑动条) 对象
                  item._slider = {
                      // 当前的 tab 距离左侧的距离
                      left: res.left + (res.width - this.defaultConfig.underLineWidth) / 2
                  };
                  // 运算完成之后,执行一次 【滑块】位置运算
                  if (data.length - 1 === index) {
                      this.tabToIndex();
                  }
              })
                  .exec();
          });
      },
    /**
     * 根据当前的 activeIndex 下标,计算 【滑块】 滚动位置
     */
    tabToIndex() {
      if (this.tabList.length === 0) return;
      // 获取当前的 activeIndex
      const activeIndex = this.activeIndex;
      // 滑块的宽度
      const underLineWidth = this.defaultConfig.underLineWidth;
      // 配置 滚动条 的数据
      this.slider = {
        // TODO:left 如何定义呢?
        // 1. 维护一个单独的数据对象 `tabList`
        // 2. 在 `tabList`  的 `item` 中为一个 `_slider` 属性
        // 3. 该属性保存了 【当前 `item` 下 的滑块位置】
        //    3.1. 计算公式:`滑块左侧位置 = item.left + (item.width - slider.width) / 2`
        left: this.tabList[activeIndex]._slider.left
      };
    }
  

5-20:tabs组件 - scrollView 的点击位移

当 【选中项】发生变化时,希望 scrollView 也进行对应的位移。

  

// scrollView 的横向滚动条位置
scrollLeft: 0,

简单的算法:

this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;

5-21:tabs组件 - 增加可配置项

<view
      class="tab-item"
      :id="'_tab_' + index"
      :class="{ 'tab-item-active': activeIndex === index }"
      @click="tabClick(index)"
      :style="{
              color:
              activeIndex === index ? defaultConfig.activeTextColor : defaultConfig.textColor
              }"
      >{{ item.label || item }}view>
  props: {
    // 配置对象
    config: {
      type: Object,
      default: () => {
        return {};
      }
    }
  },
  data: () => {
    return {
      // 默认配置
      defaultConfig: {
        // 默认的字体颜色
        textColor: '#333333',
        // 高亮字体颜色
        activeTextColor: '#f94d2a',
        // 下划线宽度 px
        underLineWidth: 24,
        // 下划线高度 px
        underLineHeight: 2,
        // 下划线颜色
        underLineColor: '#f94d2a'
      }
    };
  },
  // 侦听器
  watch: {
    // 监听 config
    config: {
      handler(val) {
        this.defaultConfig = { ...this.defaultConfig, ...val };
      },
      // 该回调将会在侦听开始之后被立即调用
      immediate: true
    }
  },

5-22:List 组件 - 分析 List 组件

  1. 使用 mock 数据,构建 List 的基本结构
  2. 美化 item 样式
  3. 根据 tab 的切换,获取真实数据
  4. 渲染真实数据
  5. 通过 swiper 改造List
  6. 完成 swiper 和 tabs 的联动效果

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

hot.vue

<template>
 	...
    
    <view>
      <hot-list-item v-for="(item, index) in 50" :key="index">hot-list-item>
    view>
  view>
template>

hot-list-item.vue







hot-ranking.vue







5-24: List 组件 - 美化 item 样式

hot-list-item.vue



hot-ranking.vue



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

hot.js

/**
 * 热搜文章列表
 */
export function getHotListFromTabType(type) {
  return request({
    url: '/hot/list',
    data: {
      type
    }
  });
}

hot.vue





5-26:List 组件 - 渲染真实数据

hot.vue

<hot-list-item
    v-for="(item, index) in listData[currentIndex]"
    :key="index"
    :data="item"
    :ranking="index + 1"
>hot-list-item>

hot-list-item.vue




hot-ranking.vue




style/global.scss

/**
 * 这里是共用样式的定义位置
 */
// 最多展示2行
.line-clamp-2 {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

main.js

// 通用样式
import './styles/global.scss';

5-27:List 组件 - 通过 swiper 改造List

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

 <!-- 基于 swiper 的 list 列表 -->
    <swiper class="swiper" :current="currentIndex">
      <swiper-item class="swiper-item" v-for="(tabItem, tabIndex) in tabData" :key="tabIndex">
        <view>
          <!-- 加载动画 -->
          <uni-load-more status="loading" v-if="isLoading"></uni-load-more>
          <!-- 列表 -->
          <block v-else>
            <!-- 列表循环数据更改为 listData[tabIndex] -->
            <hot-list-item
              v-for="(item, index) in listData[tabIndex]"
              :key="index"
              :data="item"
              :ranking="index + 1"
            ></hot-list-item>
          </block>
        </view>
      </swiper-item>
    </swiper>

当前的问题:

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

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

原因:

没有给 swiper 指定高度。

解决方案:

指定高度即可。





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

原因:

动画未执行完成,DOM 未渲染完成,即获取数据,执行了新的渲染逻辑。

解决方案:

等待 动画执行完成, DOM 渲染完成。之后再获取数据,渲染列表。





问题: swiper 滚动时,tabs 无法产生联动

5-30:List 组件 - swipertabs 联动

目前状态:

tabs 切换时,swiper 可以联动。

swiper 切换时,tabs 无法联动。

解决:

swiper 切换时,tabs 进行联动。



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

watch: {
        // 监听激活项目的变化
    defaultIndex: {
      handler(val) {
        this.activeIndex = val;
        // 定义滑块的位置
        this.tabToIndex();
      },
      // 该回调将会在侦听开始之后被立即调用
      immediate: true
    },
    
}

tabToIndex() {
      if (this.tabList.length === 0) return;
}

问题: tabs 吸顶

5-32:完成 tabs 的吸顶效果





5-33:控制列表滚动位置

// 当前的滚动距离
currentPageScrollTop: 0 

/**
   * 监听页面的滚动
   */
  onPageScroll(res) {
    this.currentPageScrollTop = res.scrollTop;
  },   

	// 监听 swiper 的切换事件
    onSwiperChange(e) {
      if (this.currentPageScrollTop > 130) {
        // 控制列表滚动位置
        uni.pageScrollTo({
          scrollTop: 130
        });
      }	
      this.currentIndex = e.detail.current;
    },

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

通过 filters 过滤器 进行处理:创建 filters 文件夹,创建 index.js 文件:

filters/index.js

/**
 * 将字符转化为以千为单位的字符
 * @param {*} val 待转换字符
 * @returns
 */
export function hotNumber(val) {
  const num = parseInt(val);
  if (num < 1000) return val;

  // 将 val 转为字符串
  val = val + '';
  // 获取以 千 为单位的值
  return val.substring(0, val.length - 3) + 'k';
}

main.js

import * as filters from './filters';

// 注册过滤器
Object.keys(filters).forEach((key) => {
  Vue.filter(key, filters[key]);
});

hot-list-item.vue

 <text class="hot-text">{{ data.views | hotNumber }} 热度text>

5-35:总结

  1. 首页内容完成
    1. uniapp 进行了基础的了解
    2. 创建 imooc-blog 的项目
    3. 完成了 tabbar 的搭建
    4. 了解了 .vue 的单文件组件
    5. 分析了首页的模块组成
    6. 封装了 request API 请求模块
    7. 复杂组件 tabs
    8. tabs 和 基于 swiper 的列表联动

你可能感兴趣的:(uni-app从入门到进阶,系统完成项目实战,uni-app,微信小程序,小程序)