24

一、uni-app简介

uni-app 是一个使用 Vue.js 开发所有前端应用的框架,是一种终极的跨平台解决方案,这里的平台,主要指的是App平台(android、ios)小程序平台H5平台。开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。
uni-app将常用的组件和api进行了跨平台封装,并且保持与微信小程序的组件和api一致,同时兼容以微信的方式去调用api,即在uni-app中除了可以使用全局的uni对象去调用微信小程序中同名的api还可以使用全局的wx对象去调用api。uni-app使用vue的语法以及微信小程序api,所以学习成本非常低。
<script>
    export default {
        onLoad() {
            uni.showLoading({
                title: '加载中-uni'
            });
            // 二者是等价的
            wx.showLoading({
                title: '加载中-uni'
            })
        }
    }
</script>

uni-app框架图

二、uni-app初始目录及文件介绍

① pages.json : 该文件是用来对 uni-app 进行全局配置,决定页面文件的路径(pages)窗口样式(globalStyle)原生的导航栏(globalStyle)底部的原生tabbar(tabBar) 等。
它类似微信小程序中app.json的页面管理部分。需要注意的是,微信小程序权限配置也是在app.json文件中通过permission进行配置,而uni-app则将权限配置移到了manifest.json文件中

② manifest.json: 该文件是应用的配置文件,主要用于指定应用的名称图标权限等。

③ App.vue : 该文件是应用的主组件,所有页面都是在App.vue下进行切换的,是页面入口文件,在这个文件里,你可以初始化一些通用的组件调用一些应用生命周期函数,应用生命周期(onLaunch、onShow、onHide)仅可在App.vue中监听,在其它页面监听无效,以及设置一些全局的样式,即在App.vue中的<style></style>内置设置全局样式。

④ main.js: 该文件是应用的入口文件,主要作用是初始化App.vue主组件并安装需要的插件,如vuex,需要注意的是,uni-app中是不能使用vue-router插件的,因为路由须在pages.json中进行配置

// main.js

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

App.mpType = 'app'

const app = new Vue({ // ①官方写法
    ...App
})
// const app = new Vue(App); // ②等价于上面的写法

// const app = new Vue({ // ③可正常渲染出页面,但onLaunch、onShow、onHide应用生命周期失效
//      render: h => h(App)
// });
app.$mount()
main.js中官方写法是在创建Vue实例的时候传入一个对象,并且对根组件App.vue进行解构后传入,其写法和直接传入根组件是一样的,但是如果通过写法③虽然可以正常渲染出页面,但是根组件上的应用生命周期函数将失效

⑤ pages文件夹: 主要用于存放应用中的页面,应用中的页面就是.vue组件。

⑥ static文件夹: 主要用于存放应用中的图片等静态资源

⑦ uni.scss: 主要存放uni-app内置的常用样式变量

⑧ unpackage文件夹: 主要存放uni-app编译运行后生成的打包相关输出文件

三、开始开发一个小视频应用

① 初始化项目

打开HBuilderX IDE,新建一个名称为mini-video的初始化uni-app项目,这里勾选uni-app即可创建,项目创建完成后,打开pages/index/index.vue,将<template>中的模板内容content部分清空,将uni-app初始项目中与应用无关的东西进行清空、修改即可。

mini-video

② 创建底部导航栏组件

首先要弄清楚我们的uni-app已经提供了tabBar的配置,即提供了底部导航栏的,那为什么还需要自定义底部导航栏呢 ?因为uni-app提供的默认底部导航栏tabBar的背景颜色只支持十六进制,所以无法设置为透明。同时我们又需要将底部导航栏中的页面设置为tabBar页面,所以我们还是要进行tarBar的配置,而一配置tabBar,那么就会自动出现uni-app提供的默认导航栏,所以我们必须在应用启动onLaunch的时候将默认tabBar进行隐藏,那么没有了默认导航栏,我们怎么进行tabBar页面的切换呢?我们可以通过<navigator>组件设置不同的跳转方式,实现应用内各种页面之间的跳转。记住APP和微信小程序是不支持<a>标签跳转的

底部导航栏有五个页面: 首页(index.vue)关注(follow.vue)加号(添加好友friend.vue)消息(news.vue)我(personal.vue)。所以需要在pages中模仿index新建出剩余的四个页面,页面新建完成后,需要配置到pages.json中的tarBar中,只需要配置list即可,如:

{
    "tabBar": { // 在pages.json中添加上tabBar配置,如下
        "list": [
            {"pagePath":"pages/index/index"},
            {"pagePath":"pages/follow/follow"},
            {"pagePath":"pages/friend/friend"},
            {"pagePath":"pages/news/news"},
            {"pagePath":"pages/personal/personal"}
        ]
    }
}

// App.vue中onLaunch的时候隐藏掉uni-app自带的tabBar

<script>
    export default {
        setTimeout(() => {
            uni.hideTabBar(); // 隐藏tabBar
        }, 1000);
    }
</script>
在ios和安卓App平台上运行时,会出现tabBar隐藏失败的情况,解决办法就是隐藏的时候需要添加一个1000ms左右的延迟

// 项目根目录下新建一个components目录,并在其中新建一个tab-bar.vue即自定义底部导航栏组件

<template>
    <view class="tab">
        <navigator open-type="switchTab" url="/pages/index/index" class="tab-box">
            首页
        </navigator>
        <navigator open-type="switchTab" url="/pages/follow/follow" class="tab-box">
            关注
        </navigator>
        <view class="tab-box">
            + <!--暂时用加号代替,后面会替换成字体图标-->
        </view>
        <navigator open-type="switchTab" url="/pages/news/news" class="tab-box">
            消息
        </navigator>
        <navigator open-type="switchTab" url="/pages/personal/personal" class="tab-box">
            我
        </navigator>
    </view>
</template>

<style>
.tab{
    height:50px;
    width:100%;
    position:fixed;
    bottom: 0;
    left: 0;
    z-index: 20;
}
.tab-box{
    float: left;
    width: 20%;
    color: #FFFFFF;
    text-align: center;
    height: 50px;
    line-height: 50px;
    font-size:20px
}
.icon-box{
    width: 60%;
    height: 30px;
    background: #FFFFFF;
    color: #000000;
    margin: 10px 20%;
    line-height:30px;
    border-radius: 5px;
    font-size: 15px;
}
</style>

③ 添加图标字体

添加图标字体非常简单,就是登录iconfont网站,然后创建一个图标项目,然后搜索自己需要的图标,比如加号搜索返回,将它们加入到项目中,然后点击下载即可,下载完成后解压,找到iconfont.css这个文件,这个就是我们要用到的图标字体的css样式,直接引入到项目中即可,为了方便使用,我们将图标字体css文件作为一个全局样式引入到App.vue组件中。使用的时候,我们只需要在需要添加图标字体的标签上,添加上"iconfont 具体的图标样式名"即可,如:

// App.vue

<style>
    /*每个页面公共css */
    @import url("./static/iconfont.css");
</style>

// components/tab-bar.vue

<view class="tab-box">
    <view class="iconfont icon-jiahao icon-box" ><!--添加一个加号图标字体样式,注意是两个样式名哦-->
    </view>
</view>

④ 创建首页头部导航栏

首页头部导航栏,最左侧是一个搜索图标,中间是推荐和同城,右侧无内容。同样,我们的uni-app是有一个默认头部导航栏的,所以我们首先要隐藏掉默认的头部导航栏,要隐藏默认头部导航栏,我们需要在pages.json文件中设置其navigationStyle属性值为custom即自定义,如:
{
    "globalStyle": {
        "navigationStyle":"custom" // 设置头部导航栏为自定义模式,头部导航栏会自动消失
    }
}

// /components/index-header.vue

<template>
    <view class="index-header"><!--固定定位到首页顶部-->
        <view class="iconfont icon-icon-- icon"></view> <!--绝对定位到左侧-->
        <view class="middle"> <!--搜索图标绝对定位后,middle将会上移动到顶部,在搜索图标下面,里面内容居中显示即可-->
            <view class="text">推荐</view>|
            <view class="text">同城</view><!--变成行内元素-->
        </view>
    </view>
</template>

<style scoped>
.index-header {
    height: 35px;
    line-height: 35px;
    width: 100%;
    position: fixed;
    top: 25px;
    left: 0;
    margin: 0 auto;
    background: #000000;
    z-index: 20;
}
.icon {
    position: absolute;
    left: 0;
    top: 0;
    color: white;
    width: 20%;
    text-align: center;
}
.middle {
    text-align: center;
    color: white;
}
.text {
    display: inline;
    margin: 0 10px;
}

</style>

⑤ 创建视频播放组件

视频播放组件即一个全屏的页面,然后里面嵌入一个<video>组件即可实现。这里需要特别说一下如何让页面全屏显示,我们设置页面全屏通常会让需要全屏的元素设置上width: 100%; height: 100%;可是当我们给视频播放组件根元素标签设置上width为100%,height为100%后,它并没有全屏显示,因为当样式属性值为百分数的时候,其是相对于父元素的,即是父元素宽高的100%,而此时视频播放组件的父元素是htmlbody它们并没有设置宽高,所以我们需要在App.vue中设置一下全局样式,将html和body的宽高设置为100%,此后其中的子元素设置百分数的时候才会其作用

// App.vue

html,body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}

// /components/video-player.vue

<template>
    <view class="video-player">
        <video class="video" 
               :src= "video.src" 
               :controls="false"
               :loop="true">
        </video>
    </view>
</template>
<script>
    export default {
        props: ["video"]
    }
</script>
<style>
    .video-player {
        width: 100%;
        height: 100%;
    }
    .video {
        width: 100%;
        height: 100%;
        z-index: 19;
    }
</style>

⑥ 创建视频列表组件

视频列表组件,我们使用的是<swiper>组件,里面<swiper-item>部分则为上面的视频播放组件。

// /components/video-list.vue

<template>
    <view class="video-list">
        <view class="swiper-box">
            <swiper class="swiper" :vertical="true">
                <swiper-item v-for="(item,index) in videos" :key="index">
                    <view class="swiper-item">
                        <video-player                         
                             :video="item"                        
                            :index="index">
                        </video-player>
                    </view>
                </swiper-item>
            </swiper>
        </view>
    </view>
</template>

<script>
    import VideoPlayer from "./video-player.vue";
    export default {
        components: {
            "video-player": VideoPlayer
        },
        props:['list'],
        data() {
            return {
                videos:[],
            }
        },
        watch:{            
            list(){                
                this.videos=this.list;            
            }        
        }
    }
</script>

<style scoped>
    .video-list {
        width: 100%;
        height: 100%;
    }
    .swiper-box{    
        height:100%;    
        width: 100%;
    }
    .swiper{    
        height:100%;    
        width: 100%;
    }
    .swiper-item {
        width: 100%;
        height: 100%;
        background: red;
    }
</style>

⑦ 向视频列表组件传入列表数据

视频列表组件和视频播放组件都已经完成后,就可以在首页onLoad的时候获取视频数据,然后传递给视频列表组件,视频列表组件在遍历传递过来的视频列表将视频地址传入对应的视频播放组件中即可,这里采用mock数据的方式提供视频列表。

// pages/index/index.vue

<template>
    <view class="content">
        <index-header></index-header> <!--首页头部导航栏组件-->
        <video-list :list="list"></video-list> <!--视频列表组件-->
        <tab-bar></tab-bar> <!--首页底部导航栏组件-->
    </view>
</template>
<script>
    import TabBar from "../../components/tab-bar.vue";
    import IndexHeader from "../../components/index-header.vue";
    import VideoList from "../../components/video-list.vue";
    export default {
        components: {
            "tab-bar": TabBar,
            "index-header": IndexHeader,
            "video-list": VideoList
        },
        data() {
            return {
                list: []
            }
        },
        onLoad() {
            this.getVideos();
        },
        methods: {
            getVideos() {
                const res = [
                    {
                        id: 0,
                        src: "http://alimov2.a.yximgs.com/bs2/gdtPostRoll/postRoll-MTA3MDY0NDY3Mzk.mp4",
                        autho: "张三",
                        title: "仙娜美",
                        loveNumber: 10000,
                        commentNumber: 2000,
                        shareNumber: 30000
                    },
                    {
                        id: 1,
                        src: "http://upmov.a.yximgs.com/upic/2019/02/13/22/BMjAxOTAyMTMyMjUxMTlfNDc4ODM2MzlfMTA3Mjc5ODU2MjhfMV8z_b_B1fbc185eaca8cc06efa2d4f713e13e8c.mp4",
                        autho: "李四",
                        title: "【搞笑】最强猜歌王",
                        loveNumber: 40000,
                        commentNumber: 5000,
                        shareNumber: 60000
                    },
                    {
                        id: 2,
                        src: "http://bdmov.a.yximgs.com/bs2/gdtPostRoll/postRoll-MTA3MDk5Mjc5OTg.mp4",
                        autho: "王五",
                        title: "特制流泪芥末酱",
                        loveNumber: 70000,
                        commentNumber: 8000,
                        shareNumber: 90000
                    }
                ];
                this.list = res;
            }
        }
    }
</script>

<style>
    .content {
        width: 100%;
        height: 100%;
    }
</style>

JS_Even_JS
2.6k 声望3.7k 粉丝

前端工程师