uni-app开发总结文档
项目结构
access(权限)
api(请求后台接口)
components(项目中公用组件)
filters(过滤器)
store(vuex)
static(项目中引用的静态文件如图片等)
common
App.vue
config.js
main.js
manifest.json
package.json
pages.json(页面配置)
pages(页面)
|
|- control 自控
| |-index (tab)
| |
| |_detail 空调详情
|
|_ device 设备
|-list 设备列表
|
|_detail 设备详情
特殊需求功能实现
一、富文本展示
项目:app Android端
使用uniapp官方提供的组件【rich-text】,官方文档地址:uniapp.dcloud.io/component/r…
官方示例:
<!-- 本示例未包含完整css,获取外链css请参考上文,在hello uni-app项目中查看 -->
<template>
<view class="content">
<page-head :title="title"></page-head>
<view class="uni-padding-wrap">
<view class="uni-title uni-common-mt">
数组类型
<text>\nnodes属性为Array</text>
</view>
<view class="uni-common-mt" style="background:#FFF; padding:20rpx;">
<rich-text :nodes="nodes"></rich-text>
</view>
<view class="uni-title uni-common-mt">
字符串类型
<text>\nnodes属性为String</text>
</view>
<view class="uni-common-mt" style="background:#FFF; padding:20rpx;">
<rich-text :nodes="strings"></rich-text>
</view>
</view>
</view>
</template>
本项目中使用了节点(content)是字符串类型,因为本项目中的富文本中有图片,直接将content用组件渲染到页面上时,图片的宽高会不适应手机端的屏幕导致图片展示不全(如下图),因此本项目对html片段字符串content加工了一下
<!-- typeof content === string 是一段html片段-->
<rich-text :nodes="content"></rich-text>
//获取公告内容
getContent(id) {
this.$api.businessList({
id
}).then(res => {
if (res && res.code === 0 && res.data) {
this.info = res.data[0]
if (this.info.content) {//this.info.content是从后台获取到的需要渲染到页面的内容节点
const regex = new RegExp('<img', 'gi');
this.content = this.info.content.replace(regex, '<img style = "max-width: 100%;"')
}
}
}).catch(err => console.log(err))
},
参考文档:ask.dcloud.net.cn/article/357… ask.dcloud.net.cn/article/364…
二、原生标题栏按钮配置
项目:app Android端
官方文档:uniapp.dcloud.io/collocation…
//pages.json中配置
{
“pages”:[
...,
{
"path": "pages/meeting/roomList",
"style": {
"app-plus": {
"titleNView": {
"buttons": [ //原生标题栏按钮配置,
{
"fontSize": "17px",
"color": "#3695FF",
"text": "预约" //原生标题栏增加按钮,点击事件可通过页面的 onNavigationBarButtonTap 函数进行监听
}
],
"titleText": "会议室"
}
}
}
},
]
}
//相应页面中监听原生导航栏按钮点击事件 pages/meeting/roomList页面
//监听原生导航栏 右键【预约】
onNavigationBarButtonTap(e) {
console.log(e);
if (e.text === '预约') {
if(!this.$useAccess('10')){
uni.showToast({
title: '对不起,您没有该权限!',
icon: 'none',
})
return
}
uni.navigateTo({
url: './addMeeting'
})
}
},
onShow(){
...
},
data(){
return{
...
}
}
三、实现自定义导航栏
项目:app Android端
官方文档:uniapp.dcloud.io/collocation…
本项目中 首页(pages/home/index) 因为导航栏右侧需要自定义图标按钮,所以没有用原生导航栏,而是使用了uniapp插件市场里uni官方出品的插件
自定义导航栏【uni-nav-bar】 地址:ext.dcloud.net.cn/plugin?id=5…
//需要pages.json中将首页的原生导航栏禁掉
{
"pages":[
{
"path": "pages/home/index",
"style": {
"enablePullDownRefresh": true,//页面是否可以下拉刷新
"app-plus": {
"titleNView": false//禁掉原生导航栏
},
"navigationBarTitleText": "首页"
}
},
]
}
<!--对应的页面中 使用uni-nav-bar-->
<!-- 顶部导航条-->
<uni-nav-bar :fixed="true" :statusBar='true'>
<view class="tab-bar">首页</view>
<view slot="right" @click="goMessage" style="position: relative;">
<view class="flex align-items-c">
<image src="../../static/ic_warn.png" style="width: 46rpx;height: 46rpx;"></image>
<view v-if="hasNewMessage" class="nav-bar-num"></view>
</view>
</view>
</uni-nav-bar>
四、统计图绘制
1)项目:app Android端
本项目中使用 uni插件市场中的【ucharts echarts】秋云 ucharts echarts 高性能跨全端图表组件,插件地址:ext.dcloud.net.cn/plugin?id=2…
此插件兼容app、小程序、h5。ucharts有在线配置地址,可以配置好后把配置代码移植到项目中,比较方便。但是相较于echarts的可配置项要少,算是一个简易版的统计图绘制库。
2)项目:微信小程序
【微信小程序】中的统计图绘制,使用的是uni插件市场的【echarts-for-wx】,插件地址:ext.dcloud.net.cn/plugin?id=1…
这个插件是基于 echarts官方提供的【echarts-for-wx】二次封装,专门给uniapp开发的 微信小程序 使用,配置项和echart相差无几(除了部分echart官方的echarts-for-wx不支持的功能)
3)未实践,官网可参考demo
从HBuilderX 2.6起,App端新增了renderjs,这是一种运行在视图层的js,vue页面通过renderjs可以操作浏览器对象,进而可以让基于浏览器的库直接在uni-app的App端运行,诸如echart、threejs,详见:renderjs
五、即时通讯 WebSocket
项目:app Android端
本项目使用:
本项目中使用了第三方插件【socket.io ui-app版】 ^1.x版本,适用于uni-app的socket.io封装,可用于uni-app、微信小程序。
插件地址:ext.dcloud.net.cn/plugin?id=1…
npm地址:www.npmjs.com/package/@hy…
安装方式:使用npm安装相应版本!!!
实现socket连接探索过程:
尝试一:使用uni官方提供的连接WebSocket方式。 官方文档地址:uniapp.dcloud.io/api/request…。
结果失败。使用了一个测试的ws协议地址可以连接成功,但是连接本项目服务端的socket不成功,失败原因分析——本项目服务端的socket版本太低了,导致连接失败。
尝试二:使用uni插件市场第三方插件【GoEasy IM聊天和即时通讯】 。插件地址:ext.dcloud.net.cn/plugin?id=5…
没有尝试,放弃理由:这个是收费的,不同服务类目每月/年 要收取服务费。但是看这个插件介绍很牛叉的样子,提供的功能很多(GoEasy IM聊天即时通讯、客服,支持私聊、群聊、会话列表和历史消息,发送图片/视频/语音/表情,支持通知栏厂商推送)以后如果有这种需求和足够的项目预算可以尝试,能不能用有没有坑我也不知道。
尝试三:使用socket.io
结果失败。在浏览器上可以正常连接、接收消息,但是本项目是app,在真机上运行的时候直接报错(报错原因推断:socket.io封装的一些东西在app中不适用)
尝试四:使用uni插件市场的第三方插件【socket.io ui-app版】。
初次的时候直接npm 安装没有指定版本,安装的是最新版,连接失败,原因是本项目服务端的socket版本太低了。
第二次尝试时,问了一下后端开发人员服务端使用的socket服务端版本——2.x ,于是使用npm 安装【uni-socket 】 1.x 版本,最后成功。
插件地址:ext.dcloud.net.cn/plugin?id=1…
npm地址:www.npmjs.com/package/@hy…
npm i @hyoga/uni-socket.io @1.x --save
// 封装socket 连接监听、关闭 方法
// common/utils/socketTool.js
import io from '@hyoga/uni-socket.io'
import config from '../../config.js'
const {
socket_url//socket连接地址
} = config
// 初始化socket连接
export function socketInit(vm) {
const socket = io.connect(socket_url, {
query: {},
transports: ['websocket'],
timeout: 5000,
})
socket.on('connect', () => {
// ws连接已建立,此时可以进行socket.io的事件监听或者数据发送操作
console.log('ws 已连接', socket_url);
getApp().globalData.socket = socket
// 监听告警
socket.on('device_alarm_data', (data) => {
if (!vm.$store.state.realAlarmList.length && !data.length) return
console.log('device_alarm_data==', data, typeof data)
vm.$store.dispatch('updateRealAlarmList', data)
})
// 监听待审批会议
socket.on('new_meeting_examine', (data) => {
console.log('new_meeting_examine==', data)
vm.$store.dispatch('changeHasNewNewApprMeet', true)
})
// 监听待审批工单
socket.on('new_order_examine', (data) => {
console.log('new_order_examine==', data)
vm.$store.dispatch('changeHasNewApprOrder', true)
})
// 监听工单审批结果
socket.on('order_apply_result', (data) => {
const obj = JSON.parse(data)
if (obj.contactUserId == uni.getStorageSync('userId')) {
console.log('order_apply_result 需要通知')
this.getMessageList(1)
}
})
// 监听会议审批结果
socket.on('meeting_apply_result', (data) => {
const obj = JSON.parse(data)
if (obj.userId == uni.getStorageSync('userId')) {
console.log('meeting_apply_result 需要通知')
this.getMessageList(2)
}
})
// 监听待接工单
socket.on('new_order', (data) => {
console.log('new_order==', data)
vm.$store.dispatch('changeHasNewReceOrder', true)
})
});
socket.on('connect_error', (msg) => {
console.log('ws error', msg);
});
}
// 关闭socket连接
export function socketClose() {
let socket = getApp().globalData.socket
if (socket) {
socket.close()
socket = undefined
}
}
//页面中使用
//App.vue
import {socketInit,socketClose} from '@/common/utils/socketTool.js'
...
onShow: function() {
console.log('App Show', this)
if (uni.getStorageSync('userId')) {
...
// 连接socket.io
socketInit(this, getApp().globalData.socket)
}
},
onHide: function() {
console.log('App Hide')
socketClose() //关闭socket
},
...
六、权限
// 在pages文件夹的同路径下新建文件夹access,access下新建index.js文件
// 权限查找
/**
* 判断是否有这个权限
* @param {String|Array[String]} 需要查询的权限
* @return {Boolean} 返回判断结果
*/
export default function(authorize) {
let result = false
if (authorize !== undefined) {
let authorizeList = Array.isArray(authorize) ? authorize : [authorize]
const AUTH_LIST = JSON.parse(uni.getStorageSync('pageAuth'))
// console.log('AUTH_LIST==',AUTH_LIST) AUTH_LIST是该登录账户下有的权限id数组 ['1','4','30']
for (let v in authorizeList) {
if (AUTH_LIST.indexOf(authorizeList[v]) < 0) {
return result = false
} else {
result = true
}
}
} else {
throw new Error('$useAccess参数不能为空!')
}
return result
}
// access/authList.js
// 没啥用,就是为了记录app中功能对应的权限
export default [
'2',//事务处理
'3',//已接工单
'4',//待接工单
'5',//工单审批
'6',//会议审批
'7',//自控
'8',//照明控制
'9',//空调控制
'10',//会议预约
'11',//事件上报
'12',//告警消息通知
]
// main.js 中 将useAccess挂到Vue原型下
import useAccess from '@/access/index.js'
...
Vue.prototype.$useAccess = useAccess //权限
...
// 项目页面中使用案例 pages/home/index.vue
<!-- 会议审批 -->
<view class="card5" style="background-image: url('../../static/img_meeting@2x.png');"
@click="goMeetApproveList" v-if="$useAccess('6')">
<view class="text" style="margin-right:18rpx">
会议审批
</view>
<view class="card5_num" v-if="hasUnApprMeet || $store.state.hasNewApprMeet"></view>
</view>
...
methods:{
// 查询是否有未接工单
getUnReceOrder() {
if (!this.$useAccess('4')) return //未接工单对应的权限id是'4',这里判断该用户是否有此权限
this.$api.orderMissedList({
pageNum: 1,
pageSize: 1,
}).then(res => {
if (res && res.code === 0 && res.data && res.data.length) {
this.hasUnReceOrder = true
} else {
this.hasUnReceOrder = false
}
}).catch(err => {
console.log(err)
this.hasUnReceOrder = false
})
},
...
}
登录时调用登录接口,将登录成功后接口返回的数据中的pageAuth(该账户有的权限)放入storage中。
调用获取用户详情成功后接口返回的数据中的pageAuth(该账户有的权限)放入storage中
pageAuth:['1','4','10']
七、版本检测更新
1)小程序
// App.vue文件
<script>
export default {
onLaunch: function() {
console.log('App Launch')
if (uni.canIUse('getUpdateManager')) {
const updateManager = uni.getUpdateManager()
//检测版本更新
updateManager.onCheckForUpdate(function(res) {
// 请求完新版本信息的回调
if (res.hasUpdate) {
//监听小程序有版本更新事件
updateManager.onUpdateReady(function() {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
}
})
})
updateManager.onUpdateFailed(function() {
// 新版本下载失败
uni.showModal({
title: '已经有新版本咯~',
content: '请您删除当前小程序,到微信 “发现-小程序” 页,重新搜索打开呦~',
})
})
}
})
}
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style lang="scss">
/*每个页面公共css */
@import '@/common/scss/common.scss';
</style>
2)app
uniapp 支持原生APP整包升级(ask.dcloud.net.cn/article/349… 资源在线升级/热更新 (ask.dcloud.net.cn/article/356…
可参考博文:www.cnblogs.com/goloving/p/…
举例:整包更新(应用项目:app)
// App.vue
/*
在onLaunch中,请求后台接口拿到最新版本号,与 plus.runtime.version(当前运行版本号——即当前运行的apk包在打包时manifest.json中配置的versionName) 对比,若不一样则调用【plus.runtime.openURL(最新apk的下载地址)】下载最新包。
注意:一定要写条件编译 #ifdef APP-PLUS 仅在 App 平台执行此升级逻辑。
*/
// #ifdef APP-PLUS
API.getVersion()
.then(res => {
if (res && res.code === 0) {
const {
data
} = res
if (data.version && data.version != plus.runtime.version && data.downloadPath) {
uni.showModal({ //提醒用户更新
title: "发现新版本",
content: "确认下载更新",
success: (result) => {
if (result.confirm) {
plus.runtime.openURL(data.downloadPath);//下载最新包
}
}
})
}
}
})
.catch(err => console.log(err))
// #endif
自开发项目基础框架CLI(基于Vue2)
git地址:gitee.com/zf-zfzn/zf-…
该CLI 集成/引入了:Vuex、filters过滤器;
对request 请求进行了二次封装:【common】—【http】模块,包括了请求、响应拦截器,可完成统一请求错误处理、请求头token设置 等;
App.vue中加入了【检测版本更新】功能;
在此基础上可继续进行业务代码的开发,可拓展性高;
开发中遇到的坑
1、app真机调试(运行到手机)
1)调试准备
连接手机数据线 =>手机打开 【开发者选项】=> 允许【USB调试模式】=>允许【USB安装】、允许【USB调试】=> 关闭【停用adb授权超时功能】=>【默认USB配置】选择【MIDI】
2)真机运行失败,失败原因:手机与HBuilder连接失败,请拔掉手机重新插入或重开USB调试模式,并重新运
行真机调试
①拔掉数据线重开USB调试模式
② 步骤①还没有解决的话,可参考ask.dcloud.net.cn/question/13…
3)真机运行失败,失败原因:手机上没有信任本计算机的授权,请在手机上信任该授权
① 撤销USB调试授权,拔掉连接的数据线,
重开USB调试,连接数据线,手机会弹出授权窗口,授权后即可正确调试。
②若步骤①做完后,手机没有弹出授权窗口,可以参考:zhuanlan.zhihu.com/p/427117697…
手机开发者选项中,关闭【停用adb授权超时功能】;
计算机中删除【adbkey】【adbkey.pub】后;
重新连接usb,手机弹出了授权窗口,授权后,就可以调试了。