第2章 uni-app 基础
2-1 回顾小程序
小程序页面生命周期:
onLoad 页面加载
onShow 页面显示
onReady 页面渲染完成
onHide 页面隐藏
onUnload 卸载页面
2-3 uni-app 核心知识点概览
uni-app 规范:
1.页面文件遵循Vue单文件组件(SFC)规范.
vue文件是一个自定义文件的类型, 用类HTML的语法来描述一个vue组件, 每个vue组件包含三个类型的语言块: <template> 模板块; <script>脚本块; <style>样式块.
2.组件标签靠近小程序规范.
3.接口能力(JS API)靠近微信小程序规范.
onLoad() {
//获取网络类型
uni.getNetworkType()
4.数据绑定及事件处理同Vue.js规范.
5.为兼容多端运行, 建议使用flex布局进行开发.
uni-app特色:
1.条件编译
2.App端的Nvue开发
3.HTML5+ (无法在h5和小程序中使用)
$cd 创建项目安装目录
$vue create -p dcloudio/uni-preset-vue test-uniapp #安装uni-app工程
$请选择 uni-app 模板 : ✅默认模板
$cd test-uniapp #进入到项目
$npm run serve #运行项目到h5
2-5 语法速通-模板语法与数据绑定
<template>
<!-- v-bind:class 动态的修改标签上的属性 (v-bind:可以省略)-->
<!-- v-on:click 自定义事件 (v-on:可以用@替换) -->
<view class="content" v-bind:class="className" v-on:click="open">
{{title}}
</view>
<!-- 简写: -->
<view class="content" :class="className" @click="open">
{{title}}
</view>
</template>
<script>
export default {
// 初始化数据方法
// 不建议这样写(以对象的形式初始化), 因为会保留上次的变量值, 不会被初始化
// data:{ },
// 写成一个方法(推荐)
data() {
return {
title: 'Hello uni-app',
className:'active'
onLoad() {
// 动态数据绑定
//需求: 两秒后更新title内容
setTimeout(()=>{
// this指向当前view的实例
this.title = '更新后的内容'
},2000)
// 在这里写自定义的所有方法
methods: {
open(){
console.log('我被点击了')
this.title = '我被点击了'
</script>
2-6 语法速通-条件判断
<template>
<!-- template内只能有一个元素 -->
<view>这是一个一直显示的内容</view>
<!-- v-if 如果表达式返回值为true, 就在页面显示内容 -->
<view v-if="show">我是动态内容</view>
<view v-if="showing === 'uni-app'">条件显示</view>
<button type="default" @click="open">点击</button>
<!-- 根据属性, 改变显示文字 -->
<view>{{show?'uni-app':'vue'}}</view>
<!-- if else if else -->
<view v-if="showing==='uni-app'">if</view>
<view v-else-if="showing==='vue'">else if</view>
<view v-else>else</view>
</view>
</template>
<script>
export default {
data() {
return {
show: true,
showing: 'uni-app'
onLoad() {
methods: {
open(){
console.log(this.show)
this.show = !this.show
</script>
2-7 语法速通-列表渲染
// 通过数组来渲染列表
<template>
<!-- v-for -->
<!-- item: 被迭代的数组元素的别名 -->
<view v-for="item in arr">{{item}}</view>
<!-- index: 下标别名 -->
<view v-for="(item, index) in arr">{{index}}{{item}}</view>
<view v-for="(value, key) in obj">{{key}}{{value}}</view>
</view>
</template>
<script>
export default {
data() {
return {
arr:['uni-app','vue','html'] ,
obj:{
name:'LiMing',
age:18,
type:'eat'
onLoad() {
methods: {
open(){
</script>
2-8 语法速通-基础组件
组件查看网址: https://uniapp.dcloud.io/component/README
<template>
<!-- 基础组件 -->
<view>视图容器(块级元素)</view>
<text>文本组件</text>
<!-- 使用竖向滚动时, 需要给scrollView一个高度 -->
<scroll-view class="height" scroll-y="true" @scroll="scorll">
<view v-for="item in 100">{{item}}</view>
</scroll-view>
<button type="default" @click="input">点击</button>
<!-- v-bind单向数据绑定 -->
<input type="text" :value="value" />
<!-- v-model实现双向数据绑定 -->
<input type="text" v-model="value" />
当前的值是: {{value}}
</view>
</view>
</template>
<script>
export default {
data() {
return {
value:'hello-uniapp'
onLoad() {
methods: {
scorll(e){
console.log(e);
input(){
this.value = 'hello 重置吧'
</script>
<style>
.height{
height: 500rpx;
border: 1px #4CD964 solid;
</style>
2-9 语法速通-自定义组件
//sp1. 实现组件
// 新建 -> 目录 -> 主目录下新建"components"文件夹 -> 右键 -> 新建组件 -> 勾选"创建同名目录"
//在页面index.vue中:
<template>
<!-- 3.使用组件 -->
<!-- 3-1.向组件内传值 -->
<!-- 4.在页面接收组件的自定义事件 -->
<!-- 5.在组件里接收页面传过去的内容, 使用slot插槽 -->
<btn color="red" @change="change">页面要传的内容</btn>
</view>
</template>
<script>
// 1.引入btn组件, @表示根目录.
import btn from '@/components/btn/btn.vue'
export default {
//2.注册组件
components:{
data() {
return {
onLoad() {
methods: {
// 4-1
change(e){
console.log('我是页面的事件, 我返回了:' + e);
</script>
//在组件btn.vue中:
<template>
<!-- 3-3. 使用外部传入的参数 -->
<!-- :style 动态的设置行内样式 -->
<view class="btn-box" :style="{color: color}" @click="onClick">
<!-- 5-1. 外部的内容会插到slot里面-->
<slot></slot>
</view>
</template>
<script>
export default {
name:"btn",
// 3-2.props 接收外部传入的属性
props:{
//color属性, 接收一个对象
color: {
type: String, //设置接收的类型
default: '#000' //设置默认值
data() {
return {
methods:{
onClick(){
console.log('(组件)我被点击了')
// vue自定义事件注册: this.$emit()
// 参数一: 给外部使用的事件名; 参数二: 事件的参数
// 4-2. 把组件内的事件发送到页面上面
this.$emit('change', this.color)
</script>
<style>
.btn-box{
width: 200px;
height: 100px;
text-align: center;
line-height: 100px;
border: 1px #4CD964 solid;
</style>
2-10 语法速通-api与条件编译
在页面index.vue中:
<template>
<!-- 条件编译: 只会对选中的平台进行编译-->
<!-- #ifdef APP-PLUS || H5 -->
<!-- #endif -->
<!-- 条件编译: 会对选中的平台不进行编译-->
<!-- #ifndef H5 -->
<!-- #endif -->
<!-- 1-2.使用外部样式 -->
<view class="content color">uni-app</view>
</view>
</template>
<script>
export default {
data() {
return {
onLoad() {
// 返回系统信息的api
uni.getSystemInfo({
success(res) {
console.log('success🌺',res);
fail(err) {
console.log('fail💣',err);
complete(res) {
console.log('不管成功失败都会返回: ', res);
methods: {
</script>
<style>
/* 1-1.引入外部样式文件 */
@import url("./index.css");
/* 设置页面的样式 */
page{
background-color: #4CD964;
/* 尺寸单位 */
.content{
/* px rpx(动态单位) rem vh(屏幕高) vw(屏幕宽)*/
font-size: 12px;
</style>
在外部样式类,index.css中:
.color {
border: 1px red solid;
2-11 生命周期概述(上)
分为: 应用生命周期、页面生命周期、组件生命周期
// 在App.vue中:
<script>
// 应用生命周期 : 只能在app.vue中执行
export default {
// 应用初始化完成触发一次, 全局只触发一次
onLaunch: function() {
// 使用场景:登录 全局变量
console.log('App Launch')
// 当uniapp启动的时候, 或从后台进入前台的时候触发
onShow: function() {
console.log('App Show')
// 应用从前台进入后台触发
onHide: function() {
console.log('App Hide')
</script>
// 在页面index.vue中:
<script>
// 页面生命周期
export default {
data() {
return {
// 监听页面加载
onLoad() {
console.log('页面开始加载')
// 监听页面的初次渲染完成
onReady() {
// 如果渲染速度快, 会在页面进入动画完成前触发
console.log('页面渲染完成')
// 监听页面显示
onShow() {
console.log('页面每次展示在屏幕上都会触发页面的onShow')
// 监听页面隐藏
onHide() {
console.log('页面每次隐藏在屏幕上都会触发页面的onHide')
// 监听页面卸载
onUnload() {
console.log('页面卸载')
methods: {
open(){
// uni.navigateTo 跳转某页面
uni.navigateTo({
url: '../test-a/test-a'
// uni.redirectTo 卸载本页面, 打开某页面
uni.redirectTo({
url: '../test-a/test-a'
</script>
2-12 生命周期概述(下)
在组件test.vue中:
<script>
export default {
data() {
return {
// 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
beforeCreate() {
console.log('实例创建之前调用')
// 实例创建完成之后立即调用, 挂载阶段还没开始
created() {
console.log('实例创建之后调用')
// 挂载到实例上去之后调用
mounted() {
console.log('挂载到实例上去之后调用')
// Vue实例销毁之后调用
destroyed() {
console.log('组件已销毁')
</script>
项目整体执行顺序:
App Launch -> App Show -> component beforeCreate -> component created -> page onload -> page onshow -> component mounted -> page onready
第3章 基础配置
3-1 uni-app 项目配置
1.对微信开发者工具进行配置:
"HBuild X" --> 偏好设置 --> 运行配置 --> 小程序运行配置, 选择微信开发者工具路径(一般不需要手动配置, 除非发生路径错误)
运行项目在微信开发者工具: (注意: 微信开发者工具要下载稳定版)
点击"运行" --> 微信开发者工具-[xxx]
2.app真机、模拟器配置:
安卓设备:
手机 -> 关于手机 -> 版本号 -> 点击5次, 打开开发者模式-> 打开USB调试 -> USB连接方式(选"传输文件")
HBuilder -> 运行 -> 运行手机或模拟器 -> 选择"连接的安卓设备"
3-2 目录结构概述
components - 自定义组件的目录
pages - 页面存放目录
static - 静态文件资源目录 (放图片、字体等)
unpackage - 编译后的文件存放目录
utils - 公用的工具类
common - 公用的文件 (可选)
app.vue - 类似小程序的app.js(项目初始化操作、登录、公共样式等)
main.js - 应用入口 (注册vue实例、绑定全局变量、引用全局自定义组件、引用三方库)
manifest.json - 项目配置 (启动图、APP图标等)
pages.json - 页面配置(页面注册、下拉刷新、是否显示导航栏、所有页面默认样式配置等)
uni.scss - 变量全局注册
3-3 配置项目底部选项卡 - tabbar 配置
/// 在页面配置文件page.json中:
"pages": [ //注册文件、控制页面窗口表现形式
"path": "pages/index/index", //页面地址
"style": {
"navigationBarTitleText": "uni-app", //页面名称
"app-plus": { // 配置特有平台样式: APP
"mp-weixin": { //配置特有平台样式: 微信
"h5": { //配置特有平台样式: H5
"path": "pages/about/about",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
"path": "pages/my/my",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
"globalStyle": { //所有页面默认样式配置
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
"tabBar": { //底部导航栏
"color": "#666", //tabbar 标题文字颜色
"selectedColor": "#ff5a5f", //tabbar 标题选中文字颜色
"borderStyle": "black", // tabbar 顶部线颜色
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/logo.png", // 必须是本地图片, 大小最大不能超过40kb, 尺寸建议81*81px
"selectedIconPath": "static/logo.png"
"pagePath": "pages/about/about",
"text": "关于",
"iconPath": "static/logo.png",
"selectedIconPath": "static/logo.png"
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "static/logo.png",
"selectedIconPath": "static/logo.png"
/// 在页面index.vue中:
<script>
export default {
data() {
return {
// tabbar 点击触发
onTabItemTap(e) {
console.log(e);
methods: {
</script>
3-4 在uni-app中如何使用sass
// 在页面中使用scss:
sp1. 安装插件
HBuildX -> 工具-> 插件安装 -> 找到"scss/sass编译",点击"安装"
sp2. 在样式中引入scss
<style lang="scss"></style>
// 👇下面是使用示例:
<template>
<view class="content box">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">哈哈</text>
</view>
</view>
</template>
<style lang="scss">
// 2.可以使用变量、函数、表达式
$width : 200rpx;
// 1.可以嵌套设置样式
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
// &表示父级, 用做.content同层级设置. 同级选择器
&.box {
border: 1px red solid;
.logo{
height: $width;
width: $width;
margin-top: $width;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
.text-area{
display: flex;
justify-content: center;
.title{
font-size: 36rpx;
color: #8f8f94;
</style>
第4章 uniCloud 的基础用法
4-1 认识 uniCloud开发
uniCloud的价值:
用JavaScript开发前后台整体业务
开发成本大幅下降
只需要专注你的业务
非H5, 免域名使用服务器
对于敏捷性业务, 完整不需要前后端分离
uniCloud的开发流程:
创建一个uni-app项目 --> 选择一个对应的云开发环境 --> 编写代码(写在云函数中) --> 将云函数上传部署到阿里云/腾讯云的serverless --> uni-app中调用云函数
uniCloud的构成:
云函数: 运行在我们定制过的node.js中, 有良好的性能和功能. 云函数中可以执行js运算, 读写noSQL的云函数数据库, 读写存储然后操作网络,给前端返回一个数据. 示例代码如下:👇
'use strict';
exports.main = async(evnet, context) => {
//event为客户端上传的参数
consle.log('event:' + event)
//返回数据给客户端
return event
云数据库: 在云函数中读写基于noSQL的JSON数据库, 这种数据库对于前端工程师更自然, 不需要学习SQL、不需要理解关系型, 方便前端做云数据库开发.
云存储和CDN: 不管是前端还是云函数中, 都可以直接去操作和存储CDN, 在uniCloud的安全机制下, 可以实现应用前端直传CDN, 避免服务器中转的耗时和带宽占用, 而且还不会发生倒传, 我们还可以在我们的web控制台, 对我们的文件进行上传、删除和查看.
4-2 HBuilderX 中配置 uniCloud 环境
sp1. 新建含uniCloud项目:
文件-> 新建 -> 项目 -> 选择"uni-app", 选中"启用uniCloud"并在右侧选择"阿里云"或"腾讯云" -> 新建完成后, 保证自己是登录状态.
sp2. 项目设置, 创建"云服务空间":
在manifest.json文件 --> 基础配置 --> 要确保uniapp的应用标识属于获取状态, 如果没有,点击"重新获取", 再点击"继续".
在cloudfunction文件夹, 右键选择"创建云服务空间" --> 跳转到web控制台, 填写服务空间的名称(可以使用默认生成名称), 点击"创建".
回到项目, 在cloudfunction文件夹, 右键选择"关联云服务空间或项目..." --> 选择刚刚创建好的云服务空间, 这样就选择了我们的云服务空间, 就可以使用uniCloud功能.
sp3. 创建云函数:
在cloudfunction文件夹下, 右键"新建云函数", 创建完成后可以通过右键云函数文件"上传所有云函数、公共模块及actions", 这样云开发环境就配置完成了
4-3 使用 uniCloud web 控制台
打开uniCloud控制台:
右击"uniCloud", 选择"打开uniCloud Web控制台..."
云函数: 所有上传的云函数都会显示在这里.
云数据库: 在项目中云函数可以对云数据库进行增删改查. 也可以在控制台手动创建数据.
云存储: 放一些项目中需要的图片、视频、音频等文件.
uniCloud.uploadFile({
//上传图片等操作
跨域配置: 解决在h5运行或者发行跨域的问题
比如: 比如直接请求网址没问题, 如果请求ip地址就会报错. 这时需要配置跨域
uniCloud.callFunction({
name:"login",
success(res) {
console.log('云函数调用成功:',res)
4-4 开始使用云函数
/// sp1.在云函数get_list中:
'use strict';
// 云函数: 运行在云端(服务器端)的函数
// 运行在云端node.js中,当客户端调用时, 定义的代码会被放在node.js运行环境中去执行
// context 包含了调用信息和运行状态, 用它来了解服务器运行的情况. uniCloud会自动将客户端的操作系统、运行平台、应用信息等等这些信息 注入到context中, 我们可以通过context获取到
exports.main = async (event, context) => {
//event为客户端上传的参数
console.log('event : ' + event)
//返回数据给客户端
return {
"code": 200,
"msg": event.name + '的年龄是' + event.age
// 修改完云函数之后, 右键点击"上传部署"才能生效
/// sp2.调用云函数, 在页面index.vue中:
<template>
<view class="content">
<button type="default" @click="open">执行云函数</button>
</view>
</template>
<script>
export default {
methods: {
open(){
// 跟平时项目中请求接口一样
// 使用这个API去调用我们的云函数
uniCloud.callFunction({
name:"get_list",//要调用的云函数的名字
data:{ //传递参数
name:"Liming",
age:18
success(res) {//调用成功的回调
console.log('正确返回🌺 ', res)
},fail(res) {//失败的回调
console.log('错误返回💣 ', res)
</script>
4-5 云数据库的添加和删除
在云函数中对云数据进行增删改查, uniCloud云数据库给我们提供了一个JSON格式的文档型数据库, 数据库中的每条记录都是JSON格式的对象. 一个数据库可以有多个集合, 也就是有多个数据表. 相对于关系型数据中的表呢, 集合可以看做是一个JSON数组, 数组中的每一条对象就是一条记录, 记录的格式就是我们的JSON对象.
介于安全原因, 我们的数据库调用只能在云函数中, 不能在客户端直接对数据库增删改查.
在云函数get_list中:
'use strict';
// sp1.获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
//sp2.获取集合的引用
const collection = db.collection('test')//传递的参数为集合的名字
// // (增)新增一条记录
// let res = await collection.add({
// name:'uni-app'
// })
// // (增)批量添加数据
// let res = await collection.add([
// {
// name:'vue'
// },
// {
// name:'ag',
// type:'哈哈'
// }
// ])
// console.log("数据插入:")
// console.log(JSON.stringify(res))//JSON转字符串
// //返回字段说明: inserted表示新增几条; result里是实际存放的id;
// (删)删除一条记录.
const res = await collection.doc('60827b0619a4150001b43354').remove()//参数传id
console.log("数据删除:")
console.log(JSON.stringify(res))//JSON转字符串
// 返回字段说明: affectedDocs表示影响了几条记录; deleted表示删除了几条记录;
return {}
// 快速测试云函数代码: 右键云函数文件 --> "上传并运行". 这样就可以直接在控制台看到结果
4-6 数据库的更新和查找
'use strict';
const db = uniCloud.database()
exports.main = async (event, context) => {
const collection = db.collection('test')//传递的参数为集合的名字
// (改)更新一条记录.
// const res = await collection.doc('60827dbde01ac80001dfbbf4').update({
// type:'前端'
// })
const res = await collection.doc('60827dbde01ac80001dfbbf4').set({
type:'大前端'
// update和set都可以更新数据, update 和 set的区别: 如果id不存在, 使用update后, 没有任何变化; 使用set后, 会新增一条记录.
// update只能更新存在的记录; set 如果记录存在就更新, 如果记录不存在就添加.
console.log("数据更新:")
console.log(JSON.stringify(res))//JSON转字符串
// 返回字段说明: affectedDocs表示影响了几条记录; updated表示更新了几条记录;
// (查)查找某一条数据 (指定id为某一项的记录)
const res1 = await collection.doc('60827dbde01ac80001dfbbf4').get()
// (查)查找某一条数据 (根据条件查询记录)
const res2 = await collection.where({
type:'前端'
}).get()
// (查)查找某一条数据 (根据外部传入参数, 按条件查询记录)
const res3 = await collection.where({
type:event.name
}).get()
console.log("数据查找:")
console.log(JSON.stringify(res3))//JSON转字符串
return {
code:200,
msg:'查询成功',
data: res3.data
//聚合:一个更精细化的查找, 返回给客户端
4-7 使用云储存上传文件
/// 图片的上传和删除, 在页面idex.vue中:
<template>
<view class="content">
<image class="logo" :src="src"></image>
<button type="default" @click="open">执行云函数</button>
</view>
</template>
<script>
export default {
data() {
return {
src: ''
onLoad() {
methods: {
open(){
let self = this //防止👇因function的方式, this的指向错误
//云存储添加图片 -- 选择图片
uni.chooseImage({
count:1, //只允许传入一张图片
success(res) {
console.log(res)
//字段说明: errMsg: "chooseImage:ok"表示上传成功
const tempFilePath = res.tempFilePaths[0]
console.log('上传地址:🐯',tempFilePath)//上传成功后, 需要用这个地址, 上传到云存储中
//上传图片到云存储
uniCloud.uploadFile({
filePath:tempFilePath,
cloudPath:'哈哈哈',//clouPath:“要上传图片的名称”,必须要写,而且还要带后缀!!
success(res) {
console.log(res)
self.src = res.fileID
},fail(err) {
console.log(err)
},fail(err) {
console.log(err)
// 云存储删除图片
uniCloud.deleteFile({
fileList:['https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2c0e7ea5-a545-4fa2-b0b9-8bc692fcbe8b/534d33fe-3654-440a-827f-f624027d3327.'],//云存储中图片地址
success() {
console.log(res)
},fail() {
console.log(err)
</script>
第5章 首页功能模块
5-1 项目初始化
sp1.常规流程创建项目 --> 勾选"启用uniCloud".
sp2.选择云服务空间
sp3. "cloudfunctions"右键"创建db_init.json", db_init.json是配置云数据库初始化的.在db_init.json中:
// 在本文件中可配置云数据库初始化, 数据格式见: https://uniapp.dcloud.io/uniCloud/quickstart?id=db-init
// ✅编写完毕后对本文件点右键, 可按配置规则创建表和添加数据
"list": { // 集合(表名)
"data": [//数据
"name": "tom"
"name": "LiMing"
sp4. 在pages文件夹下, 新建tabbar目录, 把index文件夹拖进tabbar文件夹. 在page.json配置tabbar信息
5-2 自定义导航栏
//sp1. 首页隐藏导航栏, 在pages.json中:
"path": "pages/tabbar/index/index",
"style": {
"navigationStyle":"custom", //隐藏导航栏
"navigationBarTitleText": "uni-app"
//sp2. 自定义navbar组件
新建components目录 --> 在目录下新建组件"navbar", 勾选"创建同名目录", 在navba.vue组件中:
<template>
<view class="navbar">
<view class="navbar-fixed">
<view class="navbar-search">
<view class="navbar-search-icon"></view>
<view class="navbar-search-text">uni-app、vue</view>
</view>
</view>
<!-- 占位作用, 为了把内容挤下去, 防止导航栏遮挡内容 -->
<view style="height: 45px;"></view>
</view>
</template>
<script>
export default {
name: "navbar",
data() {
return {
</script>
<style lang="scss">
.navbar {
.navbar-fixed {
position: fixed; //设置固定且在最上层, 这样就不会因为滚动影响导航栏了
top: 0;
left: 0;
z-index: 99;
display: flex;
justify-content: center;
align-items: center;
padding: 0 15px; //上下0, 左右15px
width: 100%;
height: 45px;
background-color: $mk-base-color; //使用在uni.scss中自定义的颜色
box-sizing: border-box; //让其在盒子内显示.🐂
.navbar-search {
display: flex;
align-items: center;
padding: 0 10px;
width: 100%;
height: 30px;
border-radius: 30px; //圆角
background-color: #FFFFFF;
.navbar-search-icon {
width: 10px;
height: 10px;
border: 1px red solid;
margin-right: 10px;
.navbar-search-text {
font-size: 12px;
color: #999;
</style>
//sp3. 在页面中使用, 在index.vue中:
<template>
<view class="content">
<!-- 自定义导航栏 -->
<navbar></navbar>
<view v-for="(item,index) in 100" :key="index">
{{item}}测试导航栏是否会遮挡内容
</view>
</view>
</template>
<script>
// easyCom : 当我们的组件项目名是 components/组件名/组件名.vue 这个样子的(组件名和项目名一致), 就可以直接去使用组件, 不需要import和注册. 这样引入的组件是局部引入的,不是全局引入的
// import navbar from '@/components/navbar/navbar.vue'//组件的引用
export default {
// comments:{
// navbar//组件的注册
// },
data() {
return {
src: ''
onLoad() {
methods: {
open(){
</script>
5-3 导航栏适配小程序
//sp1.导航栏适配小程序胶囊空间, 在组件navbar.vue中:
<template>
<view class="navbar">
<view class="navbar-fixed">
<!-- 状态栏 -->
<!-- :style 设置动态属性样式 -->
<view :style="{height:statusBarHeight+'px'}"></view>
<!-- 导航栏内容 -->
<view class="navbar-content" :style="{height:navBarHeight+'px',width:windowWidth+'px'}">
<view class="navbar-search">
<view class="navbar-search-icon"></view>
<view class="navbar-search-text">uni-app、vue</view>
</view>
</view>
</view>
<view :style="{height: statusBarHeight+navBarHeight+'px'}"></view>
</view>
</template>
<script>
export default {
name: "navbar",
data() {
return {
statusBarHeight: 20,
navBarHeight: 45,
windowWidth: 375
created() {
// 获取手机系统信息
const info = uni.getSystemInfoSync()
// 设置状态栏高度
this.statusBarHeight = info.statusBarHeight
console.log(info);
this.windowWidth = info.windowWidth
// 下面这些API, h5、app、mp-alipay都是不支持的
// #ifndef H5 || APP-PLUS || MP-ALIPAY
// 获取胶囊的位置
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
console.log(menuButtonInfo);
// 导航栏的高度 = (胶囊底部高度 - 状态栏高度) + (胶囊顶部高度 - 状态栏内的高度)
this.navBarHeight = (menuButtonInfo.bottom - info.statusBarHeight) + (menuButtonInfo.top - info.statusBarHeight)
console.log(this.navBarHeight)
// 导航栏内容宽度 = 胶囊左侧距离
this.windowWidth = menuButtonInfo.left;
console.log(this.windowWidth)
// #endif
</script>
//sp2. 设置状态栏的字体颜色, 在pages.json中:
"pages": [
"path": "pages/tabbar/index/index",
"style": {
"navigationStyle":"custom",
"navigationBarTextStyle":"white",//设置状态栏颜色
"navigationBarTitleText": "uni-app"
5-4 使用字体图标
/// 使用字体图标, 方案一:
//sp1.创建字体图标:
登录阿里图标库--> 选择图标后,加入购物车,点击购物车"添加到项目",打开"项目"-->在"Font class"下, 点击"查看在线链接", 点击"暂无代码, 点此生成" --> 把生成的代码(添加前缀https:)复制到浏览器中, 把代码复制下来 --> 项目中,新建目录"common",在目录下新建目录"css",在"css"目录下新建文件xxx.css(比如icons.css), 把复制的内容粘贴过来. --> 删掉不用的部分, 只留base64的字符串(此步骤可选)
//sp2.使用字体图标, 在组件navbar.vue中:
<template>
<view class="navbar">
<view class="navbar-search-icon">
<!-- sp2.使用字体图标 -->
<!-- iconfont 是一个基础的字体图标类 -->
<!-- icon-search是字体图标的名字 -->
<text class="iconfont icon-search"></text>
</view>
</view>
</template>
<style lang="scss">
//sp1.使用字体图标-- 导入图标样式
@import '../../common/css/icons.css';
.navbar-search-icon {
margin-right: 10px;
</style>
/// 使用字体图标, 方案二: 使用HBuildX插件
//插件市场地址: https://ext.dcloud.net.cn/
sp1.安装"Icons 图标"插件: 插件市场搜索-->Icons图标 -->点击"使用HBuilderX导入插件". 点击"插件预览"可以查看到所有图标
sp2.使用字体图标:
<template>
<view class="navbar">
<view class="navbar-search-icon">
<uni-icons type="search" size="16" color="#999"></uni-icons>
</view>
</view>
</template>
5-5 选项卡展示
/// 在组件tab.vue中:
<template>
<view class="tab">
<scroll-view class="tab-scoll" scroll-x>
<view class="tab-scoll__box">
<view v-for="item in list" class="tab-scroll__item">{{item.name}}</view>
</view>
</scroll-view>
<!-- "设置"图标 -->
<view class="tab-icons">
<uni-icons type="gear" size="26" color="#666"></uni-icons>
</view>
</view>
</template>
<script>
export default {
name:"tab",
data() {
return {
list: [{
name: 'uni-app'
name: 'vue'
</script>
<style lang="scss">
.tab {
display: flex;
width: 100%;
border-bottom: 1px #f5f5f5 solid;
background-color: #FFFFFF;
box-sizing: border-box;//设置成为盒内元素
.tab-scoll{
flex: 1;//表示让tab撑满整个元素
overflow: hidden;//溢出隐藏
box-sizing: border-box;
.tab-scoll__box {
display: flex;
align-items: center;
flex-wrap: nowrap;
height: 45px;
box-sizing: border-box;
.tab-scroll__item{
flex-shrink: 0;//不让元素进行挤压
padding: 0 10px;
color: #333;
font-size: 14px;
.tab-icons {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 45px;
&::after{
content: '';
position: absolute;
top: 12px;
bottom: 12px;
left: 0;
width: 1px;
background-color: #ddd;
</style>
5-6 选项卡数据初始化
///¥ 请求数据, 在页面index.vue中:
<template>
<view class="content">
<!-- 自定义导航栏 -->
<navbar></navbar>
<!-- 自定义选项卡 -->
<tab :list="tabList"></tab>
</view>
</template>
<script>
export default {
data() {
return {
tabList: []
onLoad() {
this.getLabel()
methods: {
getLabel(){
//sp1. 调取云函数方法
uniCloud.callFunction({
name:'get_label'
}).then((res)=>{
const {result} = res
this.tabList = result.data
console.log(this.tabList)
</script>
///¥ 获取数据, 在云函数get_label的index.js中:
'use strict';
// 获取数据库引用
const db = uniCloud.database()
exports.main = async (event, context) => {
// sp2.获取label表的数据
let label = await db.collection('label').get()
//返回数据给客户端
return {
code: 200,
msg: '数据请求成功',
data: label.data
///¥ 将数据传入组件, 在组件tabbar.vue中:
<template>
<view class="tab">
<scroll-view class="tab-scoll" scroll-x>
<view class="tab-scoll__box">
<view v-for="(item, index) in list" :key="index" class="tab-scroll__item">{{item.name}}</view>
</view>
</scroll-view>
<!-- "设置"图标 -->
<view class="tab-icons">
<uni-icons type="gear" size="26" color="#666"></uni-icons>
</view>
</view>
</template>
<script>
export default {
name:"tab",
props:{
list:{
type: Array,
default(){
return []
data() {
return {
</script>
5-7 封装数据请求
在common目录中, 新建目录"api", "api"中存放所有调取云函数的方法.
///¥ 封装一:
//1.封装函数调用, 在目录api的index.js文件中:
const get_label = (data)=>{
return new Promise((reslove, reject)=>{
uniCloud.callFunction({
name:'get_label',
data //传入参数
}).then((res)=>{
if(res.result.code === 200){
reslove(res.result) // .then
}else{
reject(res.result) // catch
}).catch((err)=>{
reject(err)
export default {
get_label
//2.设置全局可调用, 在main.js中:
import Vue from 'vue'
import App from './App'
import api from './common/api'//sp1.导入自定义的api
Vue.config.productionTip = false
Vue.prototype.$api = api//sp2.绑定到vue实例上
App.mpType = 'app'
const app = new Vue({
...App
app.$mount()
//3.调用使用封装好的接口, 在页面index.vue中:
<script>
export default {
data() {
return {
tabList: []
onLoad() {
this.getLabel()
methods: {
getLabel(){
this.$api.get_label({
name: 'get_label'
}).then(res=>{
const {data} = res
this.tabList = data
console.log(this.tabList)
</script>
优化原因: 如果新添加很多接口调用, 就要在index.js中新添加几个相应的封装方法并导出, 比较麻烦.
///¥ 封装方案二: 将api进行分类
//1.在目录api中, 新建list.js文件(放所有跟列表有关的接口调用内容),在list.js中:
export const get_label = (data)=>{
return new Promise((reslove, reject)=>{
uniCloud.callFunction({
name:'get_label',
data //传入参数
}).then((res)=>{
if(res.result.code === 200){
reslove(res.result) // .then
}else{
reject(res.result) // catch
}).catch((err)=>{
reject(err)
//2. 在index.js中:
import {get_label} from './list.js'
export default {
get_label
再次优化原因: 同样如果新添加很多接口调用, 就要在list.js中新添加几个相应的封装方法并导出, 且要在index.js中导入, 比较麻烦.
///¥ 封装方案三: 统一处理Promise
//1.在目录common下, 新建文件http.js(用于封装网络请求), 在http.js中:
export default function $http(options) {//导出一个http的方法
const {
url, //传入的云函数的名字
data //传入参数
} = options
return new Promise((reslove, reject) => {
uniCloud.callFunction({
name: url,
}).then((res) => {
if (res.result.code === 200) {
reslove(res.result) // .then
} else {
reject(res.result) // catch
}).catch((err) => {
reject(err)
//2. 在list.js中:
import $http from '../http.js';
export const get_label = (data)=>{
return $http({
url:'get_label',
//3. 在index.js中:
import {get_label} from './list.js'
export default {
get_label
再次优化原因: 同样多次创建接口请求api, 如果list.js中添加导出方法,对应index.js也会添加导出方法,会很麻烦.
///¥ 封装方案四: 自动批量导出文件
在index.js中:
// 批量导出文件
const requireApi = require.context(
// api 目录的相对路径
// 是否查询子目录
false,
// 查询文件的一个后缀
/.js$/ //用正则匹配, 只匹配.js的文件
console.log(requireApi.keys);
let module = {} //创建空对象
requireApi.keys().forEach((key,index)=>{
if(key === './index.js') return
console.log(key);
Object.assign(module,requireApi(key))//Object.assign()对象合并
console.log(module);
export default module
5-8 选项卡切换
// 1.切换逻辑, 在组件tab.vue中:
<template>
<view class="tab">
<!-- 动态设置高亮 : :class="{active: xxx}"-->
<view v-for="(item, index) in list" :key="index" class="tab-scroll__item" :class="{active:activeIndex === index}" @click="clickTab(item, index)">{{item.name}}</view>
</view>
</template>
<script>
export default {
data() {
return {
activeIndex: 0 //定义当前选中的下标索引(默认是0)
methods:{
clickTab(item, index){
this.activeIndex = index;
this.$emit('tab', { //时间发送到外部
data: item, //当前点击的内容
index: index
</script>
<style lang="scss">
.tab-scroll__item {
flex-shrink: 0;
padding: 0 10px;
color: #333;
font-size: 14px;
&.active { //设置高亮选中的样式
color: $mk-base-color;
</style>
//2. 页面同步响应, 在页面index.vue中:
<template>
<view class="content">
<tab :list="tabList" @tab="tab"></tab>
</view>
</template>
<script>
export default {
methods: {
//tab切换事件
tab({data, index}){
console.log(data,index)
</script>
5-9 基础卡片视图实现
// 内容切换视图
<swiper>
<swiper-item>
// 放封装的组件tableview
<list-item>
<list-scroll>
// 放封装的组件cell
</list-scroll>
</list-item>
</swiper-item>
<swiper-item></swiper-item>
</swiper>
5-12 选项卡与内容联动
1.将组件list.vue的值回传给调用页面index.vue, 在list.vue中:
<template>
<!-- @change内置函数: 监听swiper的滑动 -->
<!-- :current内置函数: 表示把swiper切换到第几项 -->
<swiper class="home-swiper" :current="activeIndex" @change='change'>
<swiper-item v-for="(item, index) in tab" :key="index" class="swiper">
<list-item></list-item>
</swiper-item>
</swiper>
</template>
<script>
export default {
methods: {
change(e){// swiper切换事件
const {current} = e.detail
console.log(e)
this.$emit('change', current)
</script>
2.协调两个组件, 达到联动效果, 在页面index.vue中:
<template>
<view class="home">
<!-- 自定义选项卡 -->
<!-- :tabIndex 将tabIndex的值传入组件中 -->
<tab :list="tabList" :tabIndex="tabIndex" @tab="tab"></tab>
<view class="home-list">
<!-- @change 接收组件传递过来的事件和参数 -->
<list :tab="tabList" :activeIndex="activeIndex" @change="change"></list>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabList: [],
tabIndex: 0,
activeIndex: 0
methods: {
change(current){
console.log('当前swiper的值: ',current);
//tab切换事件
tab({data, index}){
console.log(data,index)
//实现点击tab,滑动到对应的页面
this.activeIndex = index
</script>
3.在tab.vue中:
<template>
<view class="tab">
<scroll-view class="tab-scroll" scroll-x>
<view class="tab-scroll__box">
<!-- 动态设置高亮 : :class="{active: xxx}"-->
<view v-for="(item, index) in list" :key="index" class="tab-scroll__item" :class="{active:activeIndex === index}" @click="clickTab(item, index)">{{item.name}}</view>
</view>
</scroll-view>
<view class="tab-icons">
<uni-icons type="gear" size="26" color="#666"></uni-icons>
</view>
</view>
</template>
<script>
export default {
name:"tab",
props:{
list:{
type: Array,
default(){
return []
// 接收外部传入的值.
tabIndex:{//为了滑动页面时, tab的title也对应高亮
type: Number,
default: 0
// watch 可以监听data或props中值的变化
watch:{
tabIndex(newVal, oldVal){
console.log(newVal, oldVal);
this.activeIndex = newVal
data() {
return {
activeIndex: 0 //定义当前选中的下标索引(默认是0)
methods:{
clickTab(item, index){
this.activeIndex = index;
this.$emit('tab', { //时间发送到外部
data: item, //当前点击的内容
index: index
</script>
5-13 内容卡片数据初始化
//1.在cloudfunctions中新建云函数get_list, 在云函数get_list中:
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
const list = await db.collection('article')
.field({ //.field()指定返回的字段
content: false, //false 表示过滤掉这个字段, 不返回; 如果是true, 表示只返回这个字段
}).get()
//返回数据给客户端
return {
code: 200,
msg: '数据请求成功',
data: list.data
//2.添加请求, 在list.js中:
import $http from '../http.js';
export const get_list = (data)=>{
return $http({
url:'get_list',
//3. onLoad是页面中的生命周期函数; created是组件的生命周期函数. 在组件list.vue中:
<script>
export default {
// 组件初始化时调用
created() {
this.getList()
methods: {
getList(){
this.$api.get_list({
name: 'get_list'
}).then(res=>{
console.log(res)
</script>
5-14 切换选项卡懒加载数据
聚合操作: 更精细化的去处理数据, 对返回的数据进行求和、分组、指定返回哪些字段.
///¥ 1.需求: 通过聚合操作, 动态筛选'aritcle'表, 找出分类为'name'的那一项数据并返回.
在云函数get_list的index.js中:
'use strict';
const db = uniCloud.database()// 获取数据库的引用
exports.main = async (event, context) => {
const {
} = event
const list = await db.collection('article')// 获取数据表
.aggregate()//获取聚合操作的实例
.match({//对筛选的字段进行处理
classify: name //当我们的分类是name值的时候,返回这个条件成立的数据, 然后把获取到的数据传递给下一个流水线
.project({//指定某些字段返回到我们的数据中,哪些字段不去返回到我们的数据中. 与.field()类似
content: false
.end()//标志聚合操作定义完成,发起实际聚合操作
//返回数据给客户端
return {
code: 200,
msg: '数据请求成功',
data: list.data
///¥ 2.实现切换tab加载不同的数据, 在list.vue中:
<template>
<!-- @change内置函数: 监听swiper的滑动 -->
<!-- :current内置函数: 表示把swiper切换到第几项 -->
<swiper class="home-swiper" :current="activeIndex" @change='change'>
<swiper-item v-for="(item, index) in tab" :key="index" class="swiper">
<!-- 3.2🐯 加载缓存数据 -->
<list-item :list="listCatchData[index]"></list-item>
</swiper-item>
</swiper>
</template>
<script>
export default {
props: {
activeIndex:{//为了点击tab, 下面页面跟随滑动
type: Number,
default: 0
load: {
type: Object,
default () {
return {
loading: "loading"
tab: {
type:Array,
default() {
return []
data(){
return {
list: [],
//3.1🐯 由于js的限制, 使用listCatchData[index] = data这种方式赋值是不会触发vue的渲染中心的. 因为数组是引用类型, 更改后, vue不认为你发生变化了, 所以并不会渲染. 解决方法使用this.$set(), 告诉vue数组发生了变化, 让其刷新一下.
listCatchData:{
watch:{
//🔥2. 防止初始化时, tab还没有赋值, 就被拿去做请求, 报错.
tab(newVal){
if(newVal.length === 0)return
// TODO 当数据不存在 或者长度为0的时候, 才会请求数据
if(!this.listCatchData[current] || this.listCatchData[current].length === 0){
this.getList(this.activeIndex)
created() {
// 🔥1. TODO 组件初始化时, tab 还没有被赋值
// this.getList()
methods: {
loadmore() {
this.$emit('loadmore')
change(e){
const {current} = e.detail
console.log(this.tab[current])//获取当前切换的那项数据
this.getList(current) //获取当前name值对应的数据
this.$emit('change', current)
getList(current){
this.$api.get_list({
name: this.tab[current].name
}).then(res=>{
console.log(res)
const {
} = res
// 1🐯. this.list = data//会导致切换页面时闪一下, 因为每次this.list都会赋值一个新值, 赋值完后会重新刷新页面. 处理这个问题: 做一个数据缓存对象来接收每个data, 并通过我们点击的下标来对数据做缓存.
// 2🐯. this.listCatchData[current] = data
// 3🐯.this.$set()通知vue数据更新了, 来渲染UI
//参数一: 要改变的数组; 参数二: 修改的第几项; 参数三: 实际要改成的内容
// 数据的懒加载, 只有需要哪部分数据的时候, 才去加载对应的数据. 我们把它存到不同的对象中,根据需要来去渲染对应的数据
this.$set(this.listCatchData, current, data)
this.$forceUpdate()// 强制渲染页面
</script>
5-15 -1 上拉加载更多(上)
//1.下载组件:
插件市场 --> 搜索"loadmore" --> 选择"LoadMore 加载更多" --> 点击"下载插件ZIP" --> 下载解压,将"uni-load-more"文件夹拖入项目中的"components"目录下, 这样组件就引用成功了.
//2.使用组件, 在list-item.vue中:
<template>
<list-scroll class="list-scroll">
<list-card mode="base" :item="item" v-for="item in list" :key="item._id"></list-card>
<!-- iconType指loading的样式 -->
<!-- 最初加载数据时status='loadding', 当没有更多时status='noMore' -->
<uni-load-more iconType="snow" status="loading"></uni-load-more>
</list-scroll>
</template>
5-16 -2 上拉加载更多(下)
// 需求: 云函数中设置按页,返回数据
在云函数get_list的index.js中:
'use strict';
// 获取数据库的引用
const db = uniCloud.database()
exports.main = async (event, context) => {
// 获取数据表
const {
name,
pageSize,
} = event
let matchObj = {}
if(name !=== '全部'){
matchObj = {
classify: name
const list = await db.collection('article')
.aggregate()
.match(matchObj)
.project({
content: false
.skip(pageSize*(page-1))//要跳过多少数据
.limit(pageSize)//限定每次输出多少条数据
.end()
//返回数据给客户端
return {
code: 200,