概述
在实际工作中,App既需要支持Android手机,又要支持IOS手机,还要支持微信小程序,公众号等,面对这样的需求,是否劳心费力,苦不堪言,还要考虑各平台的兼容性。现在uni-app以“开发一次,多端覆盖”的理念,海纳百川,求同存异,受到大多数开发者的青睐。uni-app是采用vue.js作为开发前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。本文以一个天气预报的小例子,简述uni-app的开发步骤。
为什么选择uni-app ?
- uni-app实现了一套代码,同时运行到多个平台。
- uni-app在开发者数量、案例、跨端抹平度、扩展灵活性、性能体验、周边生态、学习成本、开发成本等8大关键指标上拥有更强的优势。
- DCloud为uni-app开发提供了开发利器HBuilderX,以其轻巧极速,强大的语法提示,清爽护眼,和专为vue量身打造的优势,吸引了大多数的开发者。
uni-app目录结构
一个uni-app工程,默认包含如下目录及文件,如下图所示:
uni-app应用生命周期
uni-app
支持如下应用生命周期函数:
注意:应用生命周期仅可在App.vue
中监听,在其它页面监听无效。
uni-app页面生命周期
uni-app
支持如下页面生命周期函数:
关于其他uni-app介绍,可以参考uni-app官网
示例效果图
本次开发是一个天气预报的小例子,在Chrome浏览器里面如下图所示:
在Android手机上如下所示:
源码分析
在uni-app开发小例子时,为了代码的复用,自定义三个组件,包括,风车组件,圆形进度球组件,天气组件。源码分别如下:
(一)圆形进度球组件
组件包含三部分,分别是页面(template),脚本(JavaScript),样式(CSS),源码如下:
页面(template)代码如下:
<template>
<view class="content">
<view class="progress">
<view class="progress_outer">
<view class="progress_inner"></view>
<view class="progress_masker" :style="maskStyle"></view>
</view>
<view class="progress_value">{{cvalue}}%</view>
</view>
</view>
</template>
脚本(JavaScript)代码如下:
<script>
export default {
props: {
cvalue: {
// 进度条百分比
type: Number,
default: 10,
},
},
data() {
return {
};
},
computed: {
maskStyle(){
var top=100-this.cvalue;
return {marginTop : top + '%'};
},
}
}
</script>
样式(CSS)代码如下:
<style>
.content{
width: 200rpx;
height: 200rpx;
display: block;
box-sizing: border-box;
}
.progress {
position: relative;
width: 200rpx;
height: 200rpx;
padding: 0;
box-sizing: border-box;
}
.progress_outer
{
height:100%;
width:100%;
background-color:gray;
border-radius:calc(100%/2);
border:5px solid rgba(0,0,0, 0.1);
padding:0;
box-shadow: 0px 2px 4px #555555;
-webkit-box-shadow: 0px 2px 4px #555555;
-moz-box-shadow: 0px 2px 4px #555555;
position: absolute;
box-sizing: border-box;
overflow: hidden;
}
.progress_inner
{
height:100%;
width:100%;
border:1px solid yellow;
border-radius:calc(100%/2);
position:absolute;
background-color:white;
text-align:center;
box-sizing: border-box;
}
.progress_masker
{
height:100%;
width:100%;
background: -webkit-gradient(linear,center top,center bottom,from(#0ff), to(#0f0));
background: -moz-linear-gradient( top,#fff,#0f0);
background: -o-linear-gradient( top,#fff,#0f0);
position: absolute;
box-sizing: border-box;
}
.progress_value
{
width:100%;
color:black;
font-weight:bolder;
background-color:transparent;
text-align:center;
position: absolute;
margin-top: 90rpx;
}
</style>
(二)风车组件
风车组件包含两部分,分别是页面(template),样式(CSS),源码如下:
页面(template)代码如下:
<template>
<view>
<view class="wind_mill">
<view class="cicle"></view>
<view class="vane">
<view class="vane1"></view>
<view class="vane2"></view>
<view class="vane3"></view>
</view>
<view class="blade">
</view>
</view>
</view>
</template>
样式(CSS)代码如下:
<style>
.wind_mill{
width: 200rpx;
height: 220rpx;
/* background-color: rgba(25, 83, 157, 0.5); */
position: relative;
}
@keyframes vanflash{
from{transform: rotate(0deg); transform-origin: center;}
to{transform: rotate(360deg);transform-origin: center;}
}
.vane{
width: 200rpx;
height: 200rpx;
position: relative;
animation-name: vanflash;
animation-duration: 5s;
animation-iteration-count: infinite;
-webkit-animation-name: vanflash;
-webkit-animation-duration: 5s;
-webkit-animation-iteration-count: infinite;
}
.vane1{
display: block;
width: 80rpx;
height: 4rpx;
background-color: #FFFFFF;
border-radius: 5rpx;
position: absolute;
left: 100rpx;
top:100rpx;
transform: rotate(0deg);
transform-origin:left;
-webkit-transform:rotate(0deg);
}
.vane2{
width: 80rpx;
height: 4rpx;
background-color: #FFFFFF;
position: absolute;
left: 100rpx;
top:100rpx;
border-radius: 5rpx;
transform: rotate(120deg);
transform-origin:left;
-webkit-transform:rotate(120deg);
}
.vane3{
width: 80rpx;
height: 4rpx;
background-color: #FFFFFF;
position: absolute;
left: 100rpx;
top:100rpx;
border-radius: 5rpx;
transform: rotate(240deg);
transform-origin:left;
-webkit-transform:rotate(240deg);
}
.cicle{
position: absolute;
left: 90rpx;
top:90rpx;
background-color: #FFFFFF;
width: 20rpx;
height: 20rpx;
border-radius: 16rpx;
}
.blade{
width: 120rpx;
height: 10rpx;
background-color: #FFFFFF;
position: absolute;
left: 100rpx;
top:100rpx;
border-radius: 5rpx;
transform: rotate(90deg);
transform-origin:left;
-webkit-transform:rotate(90deg);
}
</style>
(三)天气组件
天气组件,引用了前面两个组件,并有自定义数据内容,如下所示:
页面(template)代码如下:
<template>
<scroll-view scroll-y="true" class="main">
<view class="current">
<view class="district">{{district}}</view>
<view class="temp">{{temp}}°C</view>
<view class="temp_range">{{temprange}}</view>
<view class="temp_desc">{{tempdesc}}</view>
<br>
<view class="temp_src">
<view class="temp_src_left">中国气象</view>
<view class="temp_src_right">上次更新时间:{{updatetime}}</view>
</view>
</view>
<scroll-view scroll-x="true">
<view class="hour" enable-flex="true">
<view class="each_hour" v-for="item in timelist">
<view class="each_hour_time">{{item.time}}</view>
<image :src="item.img" mode="scaleToFill" class="each_hour_img" ></image>
<view class="each_hour_temp">{{item.temp}}</view>
</view>
</view>
</scroll-view>
<view class="sevenday">
<view class="each_day" v-for="item in daylist">
<view class="each_day_text">
{{item.day}} {{item.week}}
</view>
<image class="each_day_img" :src="item.img" mode=""></image>
<view class="each_day_temp">{{item.temp}}</view>
</view>
</view>
<view class="air">
<view class="air_title">
<view class="" style="flex: 1;">空气质量</view>
<view class="" style="text-align: right;flex: 1;">更多></view>
</view>
<view class="air_body">
<view class="air_left">
<airprogress class="airprogress" :cvalue="airvalue"></airprogress>
</view>
<view class="air_right">
<view class="air_content" v-for="item in airlist">
<view class="air_content_name">{{item.name}}</view>
<view class="air_content_value">{{item.value}}</view>
</view>
</view>
</view>
</view>
<view class="wind">
<view class="wind_title">
<view class="" style="flex: 1;">风向风力</view>
<view class="" style="text-align: right;flex: 1;">更多></view>
</view>
<view class="wind_body">
<view class="wind_left">
<windmill class="wind01"></windmill>
<windmill class="wind02"></windmill>
</view>
<view class="wind_right">
<view class="wind_right_direction">
<view style="flex: 1;text-align: left;">风向</view>
<view style="flex: 1;text-align: left;">{{winddirection}}</view>
</view>
<view class="wind_right_power">
<view style="flex: 1;text-align: left;">风力</view>
<view style="flex: 1;text-align: left;">{{windpower}}</view>
</view>
</view>
</view>
</view>
<view class="provider">provide by Alan.hsiang</view>
</scroll-view>
</template>
脚本(JavaScript)代码如下:
<script>
import windmill from "../windmill/windmill.vue"
import airprogress from "../airprogress/airprogress.vue"
export default {
components: {
windmill,
airprogress
},
props:{
district:{
type:String,
required:true,
},
temp:{
type:Number,
default:0
},
temprange:{
type:String,
},
tempdesc:{
type:String,
},
updatetime:{
type:String,
},
timelist:{
type:Array,
},
daylist:{
type:Array,
},
airvalue:{
type:Number,
default:10,
},
airlist:{
type:Array,
},
winddirection:{
type:String,
},
windpower:{
type:String,
}
},
data() {
return {
};
},
}
</script>
样式(CSS)代码如下:
<style>
view {
/* border: #007AFF 1rpx solid; */
font-family: Arial, Helvetica, sans-serif;
font-size: 28rpx;
padding: 2rpx;
}
.main {
width: 100%;
height: 100%;
padding: 4rpx;
background-color: rgba(25, 83, 157, 0.5);
color: #FFFFFF;
}
.current {
display: flex;
flex-direction: column;
vertical-align: middle;
justify-content: center;
height: 400rpx;
border-bottom: #F1F1F1 2rpx solid;
}
.current view {
margin-bottom: 4rpx;
}
.district {
height: 60rpx;
font-size: 45rpx;
text-align: center;
}
.temp {
height: 90rpx;
font-size: 70rpx;
text-align: center;
line-height: 1.5;
}
.temp_range {
height: 60rpx;
font-size: 40rpx;
text-align: center;
line-height: 1.5;
}
.temp_desc {
height: 50rpx;
font-size: 30rpx;
text-align: center;
line-height: 1.5;
}
.temp_src {
display: flex;
flex-direction: row;
text-align: justify;
vertical-align: bottom;
}
.temp_src_left {}
.temp_src_right {
flex: 1;
text-align: right;
}
.top {
display: flex;
flex-direction: column;
}
.hour {
display: flex;
flex-direction: row;
text-align: center;
font-size: small;
margin-top: 4rpx;
margin-bottom: 4rpx;
border-bottom: #F1F1F1 2rpx solid;
}
.each_hour{
margin-left: 6rpx;
}
.each_hour_img{
width: 50rpx;
height: 50rpx;
}
.sevenday {
display: flex;
flex-direction: column;
}
.each_day {
display: flex;
flex-direction: row;
text-align: center;
margin-bottom: 2rpx;
border-bottom: #F1F1F1 2rpx solid;
}
.each_day_text {
flex: 1;
text-align: left;
line-height: 2;
}
.each_day_img {
width: 70rpx;
height: 70rpx;
}
.each_day_temp {
flex: 1;
text-align: right;
line-height: 2;
}
.air {
display: flex;
flex-direction: column;
margin: 6rpx;
}
.air_title {
display: flex;
flex-direction: row;
font-size: small;
}
.air_body {
display: flex;
flex-direction: row;
}
.air_left {
flex: 1;
display: inline-block;
text-align: center;
margin-top: 6rpx;
}
.airprogress{
position: absolute;
left: 40rpx;
}
.air_right {
flex: 1;
display: flex;
flex-direction: column;
}
.air_content {
display: flex;
flex-direction: row;
}
.air_content_name {
flex: 1;
font-size: 20rpx;
}
.air_content_value {
flex: 1;
font-size: 20rpx;
}
.wind{
display: flex;
flex-direction: column;
height: 260rpx;
margin: 6rpx;
}
.wind_title{
display: flex;
flex-direction: row;
}
.wind_body{
display: flex;
flex-direction: row;
}
.wind_left{
flex: 1;
position: relative;
height: 150rpx;
}
.wind_right{
flex: 1;
display: flex;
flex-direction: column;
}
.wind_right_direction{
flex: 0.5;
display: flex;
flex-direction: row;
}
.wind_right_power{
flex: 1;
display: flex;
flex-direction: row;
}
.wind_left_img{
width: 140rpx;
height: 140rpx;
}
.wind01{
position: absolute;
top: 10rpx;
left: 0rpx;
}
.wind02{
position: absolute;
top: -20rpx;
left: 90rpx;
}
.provider{
text-align: center;
}
</style>
(四)页面调用组件
当组件定义完成,就可以在页面引用组件,如下所示:
本例采用swiper来实现左右滑动功能,页面(template)源码如下:
<template>
<view class="content">
<swiper :indicator-dots="showIndicatorDots" indicator-color="#FFFFFF" indicator-active-color="#FF0000" :autoplay="isAutoPlay">
<swiper-item v-for="(item,index) in weather_content">
<weather :id="index"
:district="item.district"
:temp="item.temp"
:tempdesc="item.tempdesc"
:temprange="item.temprange"
:updatetime="item.updatetime"
:timelist="item.time_list"
:daylist="item.day_list"
:airvalue="item.air_value"
:airlist="item.air_list"
:winddirection="item.winddirection"
:windpower="item.windpower"
class="weather">
</weather>
</swiper-item>
</swiper>
</view>
</template>
本例通过脚本造了一些数据,没有进行接口调用,脚本(JavaScript)代码如下:
<script>
import weather from "@/components/weather/weather.vue"
export default {
components: {
weather
},
data() {
return {
title: 'Hello',
showIndicatorDots:true,
isAutoPlay:false,
weather_content:[{
district:"龙岗区",
temp:23,
temprange:"-2°C / 10°C",
tempdesc:"晴 空气良",
updatetime:"22:10",
time_list: [{
time: "00:00",
img: "../../static/day/00.png",
temp: "0°C"
},
{
time: "01:00",
img: "../../static/day/01.png",
temp: "1°C"
}, {
time: "02:00",
img: "../../static/day/02.png",
temp: "2°C"
},
{
time: "03:00",
img: "../../static/day/03.png",
temp: "3°C"
}, {
time: "04:00",
img: "../../static/day/04.png",
temp: "4°C"
},
{
time: "05:00",
img: "../../static/day/05.png",
temp: "5°C"
}, {
time: "06:00",
img: "../../static/day/06.png",
temp: "6°C"
},
{
time: "07:00",
img: "../../static/day/07.png",
temp: "7°C"
}, {
time: "08:00",
img: "../../static/day/08.png",
temp: "8°C"
},
{
time: "09:00",
img: "../../static/day/09.png",
temp: "9°C"
}, {
time: "10:00",
img: "../../static/day/10.png",
temp: "10°C"
},
{
time: "11:00",
img: "../../static/day/11.png",
temp: "11°C"
}, {
time: "12:00",
img: "../../static/day/12.png",
temp: "12°C"
},
{
time: "13:00",
img: "../../static/day/13.png",
temp: "13°C"
}, {
time: "14:00",
img: "../../static/day/14.png",
temp: "14°C"
},
{
time: "15:00",
img: "../../static/day/15.png",
temp: "15°C"
}, {
time: "16:00",
img: "../../static/day/16.png",
temp: "16°C"
},
{
time: "17:00",
img: "../../static/day/17.png",
temp: "17°C"
}, {
time: "18:00",
img: "../../static/day/18.png",
temp: "18°C"
},
{
time: "19:00",
img: "../../static/day/19.png",
temp: "19°C"
}, {
time: "20:00",
img: "../../static/day/20.png",
temp: "20°C"
},
{
time: "21:00",
img: "../../static/day/21.png",
temp: "21°C"
}, {
time: "22:00",
img: "../../static/day/22.png",
temp: "22°C"
},
{
time: "23:00",
img: "../../static/day/23.png",
temp: "23°C"
}
],
day_list: [{
day: "10月31日",
week: "昨天",
img: "../../static/night/00.png",
temp: "26°C/21°C"
},
{
day: "11月01日",
week: "今天",
img: "../../static/night/01.png",
temp: "22°C/11°C"
},
{
day: "11月02日",
week: "明天",
img: "../../static/night/03.png",
temp: "12°C/11°C"
},
{
day: "11月03日",
week: "星期二",
img: "../../static/night/04.png",
temp: "18°C/01°C"
},
{
day: "11月04日",
week: "星期三",
img: "../../static/night/06.png",
temp: "22°C/02°C"
},
{
day: "11月05日",
week: "星期四",
img: "../../static/night/07.png",
temp: "12°C/02°C"
},
{
day: "11月07日",
week: "星期五",
img: "../../static/night/09.png",
temp: "06°C/02°C"
}
],
air_value:30,
air_list: [{
name: "PM10",
value: 23
},
{
name: "PM2.5",
value: 25
},
{
name: "NO2",
value: 28
},
{
name: "SO2",
value: 5
},
{
name: "O3",
value: 35
},
{
name: "CO",
value: 0.91
}
],
winddirection:"北风",
windpower:"3~4级"
},{
district:"东城区",
temp:13,
temprange:"12°C / 20°C",
tempdesc:"阴 空气很好",
updatetime:"22:00",
time_list: [{
time: "00:00",
img: "../../static/night/00.png",
temp: "0°C"
},
{
time: "01:00",
img: "../../static/night/01.png",
temp: "1°C"
}, {
time: "02:00",
img: "../../static/night/02.png",
temp: "2°C"
},
{
time: "03:00",
img: "../../static/night/03.png",
temp: "3°C"
}, {
time: "04:00",
img: "../../static/night/04.png",
temp: "4°C"
},
{
time: "05:00",
img: "../../static/night/05.png",
temp: "5°C"
}, {
time: "06:00",
img: "../../static/night/06.png",
temp: "6°C"
},
{
time: "07:00",
img: "../../static/night/07.png",
temp: "7°C"
}, {
time: "08:00",
img: "../../static/night/08.png",
temp: "8°C"
},
{
time: "09:00",
img: "../../static/night/09.png",
temp: "9°C"
}, {
time: "10:00",
img: "../../static/night/10.png",
temp: "10°C"
},
{
time: "11:00",
img: "../../static/night/11.png",
temp: "11°C"
}, {
time: "12:00",
img: "../../static/night/12.png",
temp: "12°C"
},
{
time: "13:00",
img: "../../static/night/13.png",
temp: "13°C"
}, {
time: "14:00",
img: "../../static/night/14.png",
temp: "14°C"
},
{
time: "15:00",
img: "../../static/night/15.png",
temp: "15°C"
}, {
time: "16:00",
img: "../../static/night/16.png",
temp: "16°C"
},
{
time: "17:00",
img: "../../static/night/17.png",
temp: "17°C"
}, {
time: "18:00",
img: "../../static/night/18.png",
temp: "18°C"
},
{
time: "19:00",
img: "../../static/night/19.png",
temp: "19°C"
}, {
time: "20:00",
img: "../../static/night/20.png",
temp: "20°C"
},
{
time: "21:00",
img: "../../static/night/21.png",
temp: "21°C"
}, {
time: "22:00",
img: "../../static/night/22.png",
temp: "22°C"
},
{
time: "23:00",
img: "../../static/night/23.png",
temp: "23°C"
}
],
day_list: [{
day: "10月31日",
week: "昨天",
img: "../../static/day/00.png",
temp: "6°C/21°C"
},
{
day: "11月01日",
week: "今天",
img: "../../static/day/01.png",
temp: "12°C/11°C"
},
{
day: "11月02日",
week: "明天",
img: "../../static/day/03.png",
temp: "22°C/09°C"
},
{
day: "11月03日",
week: "星期二",
img: "../../static/day/04.png",
temp: "28°C/11°C"
},
{
day: "11月04日",
week: "星期三",
img: "../../static/day/06.png",
temp: "12°C/02°C"
},
{
day: "11月05日",
week: "星期四",
img: "../../static/day/07.png",
temp: "22°C/12°C"
},
{
day: "11月07日",
week: "星期五",
img: "../../static/night/09.png",
temp: "16°C/12°C"
}
],
air_value:67,
air_list: [{
name: "PM10",
value: 63
},
{
name: "PM2.5",
value: 39
},
{
name: "NO2",
value: 23
},
{
name: "SO2",
value: 5
},
{
name: "O3",
value: 65
},
{
name: "CO",
value: 0.71
}
],
winddirection:"东南风",
windpower:"1~4级"
}]
}
},
onLoad() {
console.log("测试加载页面")
},
onShow(){
console.log("页面onshow....")
},
methods: {
}
}
</script>
样式(CSS)代码如下:
<style>
.content {
width: 100%;
height: 100%;
color: #007AFF;
}
swiper{
width: 100%;
height: 100%;
}
.swiper-item{
border: #007AFF 1rpx solid;
}
.weather{
height: 100%;
}
</style>
(五)注意事项
例子虽小,开发时也会踩坑,具体事项如下:
1. 开发过程中,在运行到Chrome浏览器,一切正常,但是当运行到Android真机时,页面除了导航条显示,其他一片空白,后来发现,需要在App.vue中定义页面的高度,才可以正常显示。App.vue源码如下所示:
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
uni-page-body,#app {width:100%;height: 100%;}
/* #ifdef APP-PLUS */
/* 以下样式用于 hello uni-app 演示所需 */
page {
/* background-color: #F4F5F6; */
height: 100%;
/* font-size: 28rpx; */
/* line-height: 1.8; */
}
/* #endif*/
</style>
2. 在开发过程中,最初圆形进度条是采用svg进行开发,在Chrome浏览器显示正常,但是在手机App上显示不出来,并造成页面显示一大片空白,后来不得已采用css实现。
备注
次北固山下
【作者】王湾 【朝代】唐
客路青山外,行舟绿水前。
潮平两岸阔,风正一帆悬。
海日生残夜,江春入旧年。
乡书何处达?归雁洛阳边。