mui开发过程遇到的问题和解决方案
- 最近接触了一个老项目是使用的mui(webapp),一段时间下来的有一些感受,这里大致说一下项目开发过程中的体会
- 上手非常容易----html5+js+css没问题的话,就等于直接入门了
- 坑很多----在开发的过程中,mui本身提供了很多的例子,很多组件都是可以拿过来使用的,但是在一些情况下,这些组件之间会有冲突,导致各种报错,但是知道解决方案之后,也就很顺畅了
- 开发效率高----熟练之后,开发效率很高
- 丰富的api----提供很多api,可以直接拿过来使用,非常方便
- 跨平台----一套代码,android和ios两个环境运行
- 界面UI----ui框架的界面效果一般般
问题和解决方案
1.页面传值
一个项目避免不了页面之间的交互,在mui中页面之间的传值是通过事件监听的方式来实现的(这里使用的是mui.fire,另外一种为mui.openWindow(extras:{id:this.id},)),例如A页面跳转到B页面,并且给B页面传一个ID值,代码实现如下
A.html:
<!-- 点击按钮触发跳转事件 -->
<button id="edit" type="button" class="mui-btn mui-btn-blue mui-btn-link" v-on:tap="edit(item.ID)">编辑</button>
A.js:
var vm = new Vue({
el: '#content',
created() {
},
methods: {
edit(id) {
const editShipLanding = plus.webview.getWebviewById('B');
// 在B页面监听 shipLanding
mui.fire(editShipLanding , 'shipLanding', {
id: id // 这个id值会传递到B页面
});
mui.openWindow({
id: 'B',// 这里是B页面的页面id,一般是页面的名称
});
}
}
})
mui.init({
preloadPages: [{
id: 'B',// 这里是B页面的页面id,一般是页面的名称
url: 'B.html'// 页面于当前页面的相对位置,这里是在同一目录下
}
],
});
B.html
<h2>{{id}}</h2>
B.js
var vm = new Vue({
el: '#content',
data: {
id:''
},
created() {
},
methods: {
}
});
(function($, doc) {
$.init();
window.addEventListener('shipLandingForm', function(event) {
vm.id = event.detail.id; // event.detail.id就是A页面传过来的id值 -- event.detail['属性键值'],这就获取到了A页面的值
});
})(mui, document);
2.http请求
做项目避免不了前后台的交互,mui封装了常用的函数,用于与发送http请求
2.1 get请求
mui.ajax('${this.url}',{
data: {
id: this.id
},
dataType: 'json',//服务器返回json格式数据
type: 'get',//HTTP请求类型
success: function (data) {
console.log(data);// 获取请求成功的结果
},
error: function (error) {
console.log(error);// 获取的错误信息
}
});
2.2 post请求
mui.ajax('${this.url}',{
data: this.userInfo,
dataType: 'json',//服务器返回json格式数据
type: 'post',//HTTP请求类型
headers: {'Content-Type':'application/json'},
success: function (data) {
console.log(data);// 获取请求成功的结果
},
error: function (error) {
console.log(error);// 获取的错误信息
}
});
2.3 put请求
mui.ajax('${this.url}',{
data: this.userInfo,
dataType: 'json',//服务器返回json格式数据
type: 'put',//HTTP请求类型
headers: {'Content-Type':'application/json'},
success: function (data) {
console.log(data);// 获取请求成功的结果
},
error: function (error) {
console.log(error);// 获取的错误信息
}
});
2.4 delete请求
mui.ajax('${this.url}id=${this.id}',{
dataType: 'json',//服务器返回json格式数据
type: 'put',//HTTP请求类型
headers: {'Content-Type':'application/json'},
success: function (data) {
console.log(data);// 获取请求成功的结果
},
error: function (error) {
console.log(error);// 获取的错误信息
}
});
3.文件下载
有需求要求需要支持移动端可以下载PDF文件,文件下载完成后,会使用手机端的wps打开文件,代码如下
var wt = plus.nativeUI.showWaiting();
var dtask = plus.downloader.createDownload(`${this.url}?id=${event.ID}&inline=true`, {}, function(d, status) {
if (status == 200) {
var fileUrl = d.filename;
plus.runtime.openFile(fileUrl, {}, function(e) {
alert('打开失败');
});
wt.close();
} else {
alert('Download failed: ' + status);
wt.close();
}
});
dtask.start();
4下拉刷新和上拉加载
当数据很多的时候,我们需要去做分页,这个时候就会要用到上拉刷新和下来加载
var vm = new Vue({
el: '#content',
data: {
params: { // 分页参数
top: 10,
skip: 0,
orderby: 'CreateDate desc',// 默认排序条件
},
violationList: [],
listUrl: getApiUrl('PunishSheet'),
httpUrl: 'http://47.99.116.65:8193/FiveS.Web/Fives.Reports/PunishSheet/Index?punishtype=xq&id',
},
created() {
this.getViolationList();
},
methods: {
getViolationList() {
gets(this.listUrl, this.params, (data) => {
this.violationList = data.Results; // 初始化列表
})
}
}
})
mui.init({
pullRefresh: {
swipeBack: true,
container: '#refreshContainer', //待刷新区域标识,querySelector能定位的css选择器均可,比如:id、.class等
down: { // 下拉刷新
style: 'circle', //必选,下拉刷新样式,目前支持原生5+ ‘circle’ 样式
color: '#2BD009', //可选,默认“#2BD009” 下拉刷新控件颜色
height: '50px', //可选,默认50px.下拉刷新控件的高度,
range: '100px', //可选 默认100px,控件可下拉拖拽的范围
offset: '50%', //可选 默认0px,下拉刷新控件的起始位置
callback: function() {
vm.params.skip = 0;
const params = vm.params;
gets(vm.listUrl, params, (data) => {
vm.violationList = data.Results;
mui('#refreshContainer').pullRefresh().endPulldown();
mui('#refreshContainer').pullRefresh().refresh(true);
})
}
},
up: { //上拉加载
height: 50, //可选.默认50.触发上拉加载拖动距离
contentrefresh: "正在加载...", //可选,正在加载状态时,上拉加载控件上显示的标题内容
contentnomore: '没有更多数据了', //可选,请求完毕若没有更多数据时显示的提醒内容;
callback: function() {
vm.params.skip += 10;
const params = vm.params;
gets(vm.listUrl, params, (data) => {
vm.violationList = vm.violationList.concat(data.Results);
vm.params.skip >= data.Count ? this.endPullupToRefresh(true) : this.endPullupToRefresh(false);
})
}
}
}
});
// 封装的get
//按条件获取多条记录
function gets (url, params, success, fail) {
return _gets (url, params, false, success, fail);
}
function _gets (url, params, countOnly, success, fail) {
if (params) {
if (params.filter) {
url = url + '?$filter=' + params.filter;
} else {
url = url + '?$filter=(1 eq 1)';
}
if (params.skip) {
url = url + '&$skip=' + params.skip;
}
if (params.top) {
url = url + '&$top=' + params.top;
}
if (params.orderby) {
url = url + '&$orderby=' + params.orderby;
}
}
var headers = {
'Content-Type': 'application/json'
};
if (app && app.getState() && app.getState().token) {
headers['token'] = app.getState().token;
}
if (countOnly) {
headers['CountOnly'] = 'CountOnly';
}
url = encodeURI(url);
mui.ajax(url, {
type: 'get',
dataType: 'json',
headers: headers,
success: function(data) {
success(data);
},
error: function(xhr, type, errorThrown) {
//console.log(xhr, type, errorThrown);
if (fail) {
fail(xhr, type, errorThrown);
}
}
});
}
mui.init();
5.开启左滑返回
mui.init({
pullRefresh: {
swipeBack: true //配置开启左滑返回
}
});
// 实现左滑返回
mui.plusReady(function() {
const _self = plus.webview.currentWebview();
_self.addEventListener('swipeleft', function() {
mui.back()
}, false);
});
6.图片上传
在表单中需要将手机中相册中的图片上传(下次使用直接拷贝,修改url以及属性名称即可)
html:
<form class="mui-input-group">
<div class="form-group">
<div v-if="upload.imgList.length!==0" v-for="(item, index) in upload.imgList" class="upload">
<img :src="item.thumbpreView" alt="" data-preview-src="" :data-preview-group="index" />
<span class="iconfont icon-download" style="background: seagreen;" v-on:tap="operation('download',item)"></span>
<span class="iconfont icon-shanchu" style="background: indianred;" v-on:tap="operation('delete',item)"></span>
</div>
<div class="files" v-on:tap="upLoadImg()"></div>
</div>
</form>
js:
var vm = new Vue({
el: '#content',
data: {
uploadUrl: getApiUrl('Base_FileInfor/upload'),
imgUrl: getApiUrl('Base_FileInfor'),
isDelete: false,
id: '',
files: [],
upload: {
imgList: [],
mataData: []
},
imgHttp: 'http://47.99.116.65:8193/fives.web/',
ids:[],
},
created() {},
methods: {
upLoadImg() {
const _self = this;
// 点击获取选项----排照\从手机相册中选择
if (mui.os.plus) {
const a = [{
title: '拍照'
},
{
title: '从手机相册选择'
}
];
// 添加取消按钮
plus.nativeUI.actionSheet({
cancel: '取消',
buttons: a
},
b => {
/*actionSheet 按钮点击事件*/
switch (b.index) {
case 0:
break;
case 1:
_self.getImage(); /*拍照*/
break;
case 2:
_self.galleryImg(); /*打开相册*/
break;
}
}
);
}
},
getImage() {// 拍照
var c = plus.camera.getCamera();
const _self = this;
c.captureImage(
function(e) {
plus.io.resolveLocalFileSystemURL(
e,
function(entry) {
const imgSrc = entry.toLocalURL() + '?version=' + new Date().getTime(); //拿到图片路径
const ID = new Date().getTime() * Math.random();
_self.files.push({
path: imgSrc,
name: new Date().getTime()
});
_self.upload.imgList.push({
name: 'images' + new Date().getTime(),
thumbpreView: imgSrc,
ID: ID,
isNew: true
});
},
function(e) {
console.log('读取拍照文件错误:' + e.message);
}
);
},
function(s) {
console.log('error' + s);
}, {
filename: '_doc/camera/'
}
);
},
galleryImg() {//打开相册
const _self = this;
plus.gallery.pick(
function(e) {
for (var i in e.files) {
const file = e.files[i];
const ID = new Date().getTime() * Math.random();
const name = file.substr(file.lastIndexOf('/') + 1);
_self.files.push({
name: 'images' + name,
path: file
});
_self.upload.imgList.push({
name: 'images' + name,
thumbpreView: file,
ID: ID,
isNew: true
});
}
},
function(e) {}, {
multiple: true
}
);
},
fileUpload() {// 点击上传将选择的图片上传
const _self = this;
const files = this.files;
const content = mui.extend({}, {
RelateID: String(_self.id),
RelateType: 'EntryShipBerthing',
TreeID: '-1',
OrigenName: 'photo'
});
var wt = plus.nativeUI.showWaiting();
if (_self.isDelete === true) {
_self.fileUploadDelete();// 编辑的时候,涉及到删除,点击上传之后,若是操作的删除.将图片从数据库中删除
}
// 图片上传
var task = plus.uploader.createUpload(
_self.uploadUrl, {
method: 'POST'
},
function(t, status) {
//上传完成
if (status == 200) {
wt.close();
mui.toast('上传完成');
const shipIslanding = plus.webview.getWebviewById('shipIslanding');
shipIslanding.reload();
mui.back();
_self.upload = {
imgList: [],
mataData: []
};
} else {
console.log('上传失败:' + status);
wt.close();
}
}
);
// 数据
mui.each(content, function(index, element) {
task.addData(index, element);
});
//添加上传文件
mui.each(_self.files, function(index, element) {
var f = _self.files[index];
task.addFile(f.path, {
key: f.path
});
});
//加用户信息
task.setRequestHeader('token', app.getState().token);
//开始上传任务
task.start();
},
operation(event, item) {
const _self = this;
switch (event) {
case 'download':
const download = plus.downloader.createDownload(item.url, {}, function(dl, status) {
if (status == 200) {
plus.gallery.save(
dl.filename,
function(event) {
mui.toast('图片已保存到相册');
},
function(error) {
mui.toast('保存失败');
}
);
} else {
mui.toast('保存失败');
}
});
download.start();
break;
case 'delete':
mui.confirm('是否要删除?', '警告', ['是', '否'], function(e) {
if (e.index === 0) {
_self.isDelete = true;
// 删除一个图片,先从数组中删除,点击上传才将图片真正的删除
_self.upload.imgList = _self.upload.imgList.filter(x => x.ID !== item.ID);
if (_self.files.some(x => x.ID === item.ID)) {
_self.files = _self.files.filter(x => x.ID !== item.ID);
}
}
});
break;
}
},
fileUploadDelete() {
// 删除图片
const mata = this.upload.mataData.map(x => x.ID);
const list = this.upload.imgList.map(x => x.ID);
const difference = mata.filter(v => !list.includes(v)).map(x => x);
const url = `${getApiUrl('Base_FileInfor/DeleteInBatches')}?IDs=${difference}`;
remove(url, data => {
});
},
}
});
(function($, doc) {
$.init({
preloadPages: [{
id: 'shipIslanding',
url: 'shipIslanding.html'
}]
});
// 获取列表页面的某条数据的id
window.addEventListener('shipIslandingUpFile', function(event) {
vm.id = event.detail.id;
vm.isDelete = false;
gets(
vm.imgUrl, {
filter: `RelateID eq ${vm.id} and RelateType eq 'EntryShipBerthing'`
},
res => {
// 若是没有上传的图片
if (res.Results === null) {
vm.upload.imgList = [];
return;
}
// 将图片放到upload,在页面显示
vm.upload.imgList = res.Results.map(x => {
return {
url: vm.imgHttp + GetPicStaticSrc(x.NewName),
thumbpreView: vm.imgHttp + 'app_data/' + GetPicThumbnailAddress(x.NewName),
ID: x.ID,
name: x.OrigenName
};
});
vm.upload.mataData = res.Results.map(x => {
return {
url: vm.imgHttp + GetPicStaticSrc(x.NewName),
thumbpreView: vm.imgHttp + 'app_data/' + GetPicThumbnailAddress(x.NewName),
ID: x.ID,
name: x.OrigenName
};
});
}
);
});
})(mui, document);
// /***获取图片资源的缩略图地址**/
function GetPicThumbnailAddress(srcOriginal) {
if (srcOriginal.indexOf('?') !== -1) {
return '';
}
let straddress = GetFileNameNoExt(srcOriginal);
straddress += '.THUMBPREVIEW';
straddress += GetFileExt(srcOriginal);
return straddress;
}
// /***获取图片资源的静态资源地址**/
function GetPicStaticSrc(src) {
return 'app_data/' + src;
}
// 取文件后缀名
function GetFileExt(filepath) {
if (filepath != '') {
var pos = '.' + filepath.replace(/.+\./, '');
return pos;
}
}
// 取文件名不带后缀
function GetFileNameNoExt(filepath) {
if (filepath != '') {
var names = filepath.split('\\');
var pos = names[names.length - 1].lastIndexOf('.');
return names[names.length - 1].substring(0, pos);
}
}
mui.plusReady(function() {
const _self = plus.webview.currentWebview();
_self.addEventListener('swipeleft', function() {
mui.back()
}, false);
});
7.实现时间组件
表单中常常会用到时间组件,在开发过程中,在这里遇到一个小坑,mui的mui.back于时间组件冲突了,在我开发的过程中,发现只要我将时间组件在页面加载的过程中就初始化,mui顶部自带的返回按钮就会不起作用,这块确实坑了我一下,以下是解决方案:
html:
<div class="mui-input-row">
<label>开始时间<span class="violation-form-text"> *</span>:</label>
<input type="text" class="mui-input-clear mui-input"
v-model="data.StartTime" v-on:tap="getStartTime()" id="shipIslandingStartTime"
data-options='{"type":"datetime"}' />
</div>
js:
var vm = new Vue({
el: '#content',
data: {
data: {
StartTime: ''
},
},
created() {
},
methods: {
getStartTime () {
const _this = this;
let dtPicker = new mui.DtPicker();
const dateElement = document.getElementsByClassName('mui-dtpicker')
if (dateElement[0].className === 'mui-dtpicker mui-active') {
return false;
} else {
dtPicker.show(function(selectItems) {
return _this.data.StartTime = selectItems.text;
})
}
},
}
});
mui.init();
8.实现选择器
在web中我们一般使用下拉框来实现的功能,在app中会使用选择器来实现,这个项目值有一级的选择,所以这里也只记录最简单的一级选择器,这个功能挺简单的,仔细看下示例就没啥问题了,代码实现
html:
<div class="mui-input-row">
<label>申请部门<span class="violation-form-text"> *</span>:</label>
<input type="text"
class="mui-input-clear mui-input"
v-model="data.ApplicationDept"
placeholder="请选择申请部门"
readonly="readonly"
v-on:tap="getDeptInfo()"
/>
<div class="ui-alert" v-model="deptTextValue"></div>
</div>
js:
var vm = new Vue({
el: '#content',
data: {
getDeptListUrl: getApiUrl('Base_TreeItems'),
data: {
ApplicationDept: '',
},
selectDept: '',
deptList: []
},
created() {
this.getStaffDeptList();
},
methods: {
getStaffDeptList() {
gets(`${this.getDeptListUrl}?$filter=startswith(InCode, 'O')`, null, (res) => {
res.Results.forEach((item) => {
this.deptList.push({
value: item.CNName, // 这里的键value和text是确定的,text是显示的值,value可以写成对应的需要传到后台的值
text: item.CNName
});
})
}, (error) => {
console.log(error);
});
},
getDeptInfo() {
const _this = this;
const userPicker = new mui.PopPicker();
userPicker.setData(this.deptList);
userPicker.show(function(items) {
_this.ApplicationDept= JSON.stringify(items[0]);
_this.data.ApplicationDept = items[0].value;
});
},
});
mui.init();
9.唤醒默认浏览器打开html网页
html:
<button type="button" class="mui-btn mui-btn-blue mui-btn-link" v-on:tap="detail(item.ID)">详情</button>
js:
var vm = new Vue({
el: '#content',
data: {
httpUrl: 'http:/xxxx.xxxx.com?id',
},
created() {
},
methods: {
detail(id) {
// 点击打开该网页
plus.runtime.openURL(`${this.httpUrl}=${id}`);
}
}
})
mui.init();