uni-app入门到实战总结(上)

第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,