收获:
1.微信小程序
2.uni-app开发慕课热搜项目
3.构建企业级项目的编程思维
4.上线可商用的企业项目
经过前面四个章节,我们已经完成了 微信小程序 的学习。那么从这一章开始我们就进入 uniapp
的项目开发之中。
整个 uniapp
阶段我们会完成一个正式的项目 慕课热搜 , 以这个项目来作为 uniapp
学习阶段的的最终产出,同时通过这个项目来贯穿所有的 uniapp
知识点,可以让大家在学习的过程中不至于那么枯燥。
目前我们的项目已经上线了:
H5
:https://imooc.blog.lgdsunday.club/#/
两个版本,因为在【第一章】中已经为大家演示了项目,所以这里就不再重复演示了。
那么现在:
各种前置条件已经全部准备就绪,项目开发即可开始!
《慕课热搜》基于 uniapp
进行开发,关于 uniapp
的优点,我们在【课程导学】阶段已经为大家描述过了,忘了的同学可以回过头去看一下。这就不再重复去夸它了。
我们这一小节来点实在的,光知道它好,不行。因为不是你的没啥用对吧。
那么怎么才能学会它呢?它难学吗?这才是这一小节我们需要说明的内容。
点击这里直接进入 uniapp 官网
想要学习 uniapp
那么需要有三个前置条件:
html + css + js
: 这个相信大家都没有问题vue
:可能有很多同学一看这个,心就凉了一截。我不会 vue
咋办啊…vue
的理念和 微信小程序 的理念有非常多相同的地方,在我们后面进行项目开发的过程中,遇到一些个别的语法时,我会为大家进行介绍的。总之,对于大家来说,这三个条件,如果你全部具备,那自然是最好的。
如果你只具备前两个条件,也不要担心,甚至可以说是更加幸运。因为接下来你将会在学会 uniapp
的同时,也掌握 vue
的核心使用!
uniapp
同样提供了一个专门的开发工具 HBuilder X
,可以点击 这里直接进入下载页面
点击 DOWNLOAD
windows
版本下载完成之后会得到一个 zip
的压缩包文件,解压完成即可使用
macos
版本下来完成会得到一个 dmg
的安装包,直接安装即可
sass
依赖因为我们的项目开发会使用 sass
,所以需要为 HBuilder X
安装 sass 编译器
。
打开 HBuilder X
打开插件地址:https://ext.dcloud.net.cn/plugin?id=2046
├─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内置的常用样式变量
uniapp
支持10个平台,我们已 微信小程序 和 h5
平台为例子,进行演示。
VSCode
来开发 uniapp
时,可以查看本小节)虽说 HBuilder X
开发体验还算不错,但是有时候金窝银窝不如自己的狗窝,当我们习惯了 VSCode
之后,有时候不太愿意换开发工具。
那么怎么使用 VSCode
来开发 uniapp
呢? 其实是有办法的。
HBuilder X
运行项目(运行方式,参考上一小节)VSCode
打开项目VSCode
中安装插件:
pages.json
和 manifest.json
简单的格式校验VSCode
中修改代码,运行结果自动发生变化删除 pages
下的 index
文件夹
在 pages
文件夹处,右键 -> 选择新建页面
点击创建按钮完成新建。
循环以上顺序,依次完成 hot
、hot-video
、my
三个页面的创建
pages.json
删除 index
路径
新建 tabbar
节点
复制 资源 文件夹下 tab-icons
文件夹到 static
文件夹中
最终代码如下:
"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"
}
]
}
如果修改完成之后,依然得到了以下错误,那么可以在 HBuilder X
中重新运行项目到微信开发者工具解决
微信小程序默认开启了索引功能,但是因为我们没有配置索引策略,导致出现了这么一个警告的问题。具体情况可以参考:https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html
而如果想要解决这个警告也非常简单,因为一般情况项目不需要被微信索引,所以我们只需要 关闭 默认索引功能即可!
Cannot read property 'forceUpdate' of undefined
的错误这个错误的原因非常简单,是因为我们没有为项目配置 appID
的原因,所以只需要完成 APPID
配置即可。
template
:定义当前页面的结构。相当于 wxml
script
:定义当前页面的逻辑。相当于 js
style
:定义当前页面的样式。相当于 wxss
style
标签增加 scoped
属性:表示当前样式只在当前页面生效<template>
<view class="hot-container">
<image class="logo" mode="aspectFit" src="@/static/images/logo.png" />
view>
template>
创建 components
文件夹
明确当前 my-search
组件的能力(暂时不需要考虑太多之后的能力)
placeholder
内容可以在父组件定义代码实现
my-search.vue
{{ placeholderText }}
hot.vue
my-tabs
组件创建 my-tabs
组件
分析 my-tabs
组件的能力 -> 希望创建一个通用的 my-tabs
组件,可以满足各个应用中的需求
可在父组件中定制 my-tabs
样式
可在父组件中设置展示数据
可在父组件中设置默认的激活项
由此分析,定义处以下代码:
创建 utils
文件夹
创建 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;
创建 api
文件夹
创建 hot
文件,封装 hot
相关的请求方法
import request from '../utils/request';
export function getHotTabs() {
return request({
url: '/hot/tabs'
});
}
在 hot
中使用
...
在 tabs
中展示
{{ item.label || item }}
my-tabs
因为根据 子组件不可以直接修改父组件传递过来的数据 特性,所以可以通过定义一个 data-> activeIndex
来跟随 defaultIndex
的变化
<script>
export default {
data: () => {
return {
// 当前激活项的 index
activeIndex: -1
};
},
// 侦听器
watch: {
// 监听激活项目的变化
defaultIndex: {
handler(val) {
this.activeIndex = val;
},
// 该回调将会在侦听开始之后被立即调用
immediate: true
}
},
}
</script>
找到 tab-item
的 view
,判断 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; // 热点颜色
<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;
}
确定滚动的时机
确定滚动时机后,执行滚动的方法
/**
* 根据当前的 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'
}
};
tabList
tabList
的 item
中为一个 _slider
属性item
下 的滑块位置】
滑块左侧位置 = 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
};
}
当 【选中项】发生变化时,希望 scrollView
也进行对应的位移。
// scrollView 的横向滚动条位置
scrollLeft: 0,
简单的算法:
this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;
<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
}
},
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
标题
简介
作者
1000 热度
hot-ranking.vue
1
hot-list-item.vue
hot-ranking.vue
hot.js
/**
* 热搜文章列表
*/
export function getHotListFromTabType(type) {
return request({
url: '/hot/list',
data: {
type
}
});
}
hot.vue
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
{{ data.title }}
{{ data.desc }}
{{ data.nickname }}
{{ data.views }} 热度
hot-ranking.vue
{{ ranking }}
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';
想要让 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>
当前的问题:
tab
时的 list
的卡顿问题原因:
没有给 swiper
指定高度。
解决方案:
指定高度即可。
...
...
tab
时的 list
的卡顿问题原因:
动画未执行完成,DOM
未渲染完成,即获取数据,执行了新的渲染逻辑。
解决方案:
等待 动画执行完成, DOM
渲染完成。之后再获取数据,渲染列表。
问题: swiper
滚动时,tabs
无法产生联动
swiper
和 tabs
联动目前状态:
tabs
切换时,swiper
可以联动。
swiper
切换时,tabs
无法联动。
解决:
让swiper
切换时,tabs
进行联动。
watch: {
// 监听激活项目的变化
defaultIndex: {
handler(val) {
this.activeIndex = val;
// 定义滑块的位置
this.tabToIndex();
},
// 该回调将会在侦听开始之后被立即调用
immediate: true
},
}
tabToIndex() {
if (this.tabList.length === 0) return;
}
问题: tabs
吸顶
tabs
的吸顶效果
// 当前的滚动距离
currentPageScrollTop: 0
/**
* 监听页面的滚动
*/
onPageScroll(res) {
this.currentPageScrollTop = res.scrollTop;
},
// 监听 swiper 的切换事件
onSwiperChange(e) {
if (this.currentPageScrollTop > 130) {
// 控制列表滚动位置
uni.pageScrollTo({
scrollTop: 130
});
}
this.currentIndex = e.detail.current;
},
通过 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>
uniapp
进行了基础的了解imooc-blog
的项目tabbar
的搭建.vue
的单文件组件request
API
请求模块tabs
tabs
和 基于 swiper
的列表联动