HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

跨平台移动端组件库 FirstUI 1.3.0 正式发布!

组件

FirstUI(https://www.firstui.cn/)是基于uni-app开发的一款轻量、全面可靠的跨平台移动端组件库。包括框架、组件、模板、功能插件几个部分。FirstUI开发者、设计师不断精心打磨,持续发布新的组件、模板等新功能,力求为用户提供更高品质的产品,节约用户时间与成本。

一、FirstUI特性

● 多端支持。一套代码,多端适用,支持iOS(vue和Nvue)、Android(vue和Nvue)、微信小程序、支付宝小程序、QQ小程序、百度小程序、字节跳动小程序、H5平台
● 完善的组件。目前共规划118款,已上线70款,涵盖基础组件、表单组件、导航组件、布局组件、常用布局、扩展组件、操作反馈、数据组件、JS、图表、画布。
● 丰富实用的布局、模板。基于FirstUI提供的组件,针对常用场景、行业,提供丰富实用的布局和模板。
● 专属社区。我们打造了FirstU专属社区,用户可以在社区交流分享FirstUI的使用经验、提问。有其他组件、模板需求,也可以在社区中反馈。

二、本次更新内容

● 新增 Calendar 日历 组件(VIP组件)。
● 新增 Cascader 级联选择器 组件(VIP组件)。
● 新增 Slider 滑块 组件(VIP组件)。
● 新增 Rate 评分 组件(VIP组件)。
● 新增 Select 选择器 组件(VIP组件)。
● 新增 Upload 图片上传 组件(VIP组件)。
● 新增 Gallery 画廊 组件(VIP组件)。
● 新增 BubbleBox 气泡框 组件(VIP组件)。
● 新增 BottomNavbar 底部导航栏 组件(VIP组件)。
● 新增 CountDown 倒计时 组件(VIP组件)。
● 新增 CopyText 长按复制 组件(VIP组件)。
● 新增 Timer 计时器 组件(VIP组件)。
● 新增 Qrcode 二维码 组件(VIP组件)。
● 新增 Barcode 条形码 组件(VIP组件)。
● 新增 Autograph 手写签名 组件(VIP组件)。
● 修复 Textarea 多行输入框 组件回车无法换行的问题。
● 优化 Request 网络请求 组件,新增加载中提示信息配置项。
● 优化 Icon 图标 组件,新增部分图标。
● 优化若干已知问题。

三、扫码体验FirstUI

考虑快速预览,所以暂未上架App应用,后续待功能完善再进行上架。

四、开源版与商业版

FirstUI分为开源版与商业版,部分组件为商业版专属使用。

1、开源版

● github: https://github.com/FirstUI/FirstUI (欢迎star :-D)
● gitee: https://gitee.com/firstui/FirstUI (欢迎star :-D)
● 文档地址: https://doc.firstui.cn

2、VIP会员权益:

● 完整版源码
● 全部组件
● 物料商城享VIP折扣
● 专属会员群指导、答疑
● 新特性优先体验
● VIP专属文档
会员权益详情: https://www.firstui.cn/right

3、新版优惠

新版发布,优惠期内框架可5折¥150元购买(原价¥300元),2022年01月31日截止。购买框架产品即升级为VIP会员,享受VIP会员权益。
立即购买:https://www.firstui.cn/store/detail/1

继续阅读 »

FirstUI(https://www.firstui.cn/)是基于uni-app开发的一款轻量、全面可靠的跨平台移动端组件库。包括框架、组件、模板、功能插件几个部分。FirstUI开发者、设计师不断精心打磨,持续发布新的组件、模板等新功能,力求为用户提供更高品质的产品,节约用户时间与成本。

一、FirstUI特性

● 多端支持。一套代码,多端适用,支持iOS(vue和Nvue)、Android(vue和Nvue)、微信小程序、支付宝小程序、QQ小程序、百度小程序、字节跳动小程序、H5平台
● 完善的组件。目前共规划118款,已上线70款,涵盖基础组件、表单组件、导航组件、布局组件、常用布局、扩展组件、操作反馈、数据组件、JS、图表、画布。
● 丰富实用的布局、模板。基于FirstUI提供的组件,针对常用场景、行业,提供丰富实用的布局和模板。
● 专属社区。我们打造了FirstU专属社区,用户可以在社区交流分享FirstUI的使用经验、提问。有其他组件、模板需求,也可以在社区中反馈。

二、本次更新内容

● 新增 Calendar 日历 组件(VIP组件)。
● 新增 Cascader 级联选择器 组件(VIP组件)。
● 新增 Slider 滑块 组件(VIP组件)。
● 新增 Rate 评分 组件(VIP组件)。
● 新增 Select 选择器 组件(VIP组件)。
● 新增 Upload 图片上传 组件(VIP组件)。
● 新增 Gallery 画廊 组件(VIP组件)。
● 新增 BubbleBox 气泡框 组件(VIP组件)。
● 新增 BottomNavbar 底部导航栏 组件(VIP组件)。
● 新增 CountDown 倒计时 组件(VIP组件)。
● 新增 CopyText 长按复制 组件(VIP组件)。
● 新增 Timer 计时器 组件(VIP组件)。
● 新增 Qrcode 二维码 组件(VIP组件)。
● 新增 Barcode 条形码 组件(VIP组件)。
● 新增 Autograph 手写签名 组件(VIP组件)。
● 修复 Textarea 多行输入框 组件回车无法换行的问题。
● 优化 Request 网络请求 组件,新增加载中提示信息配置项。
● 优化 Icon 图标 组件,新增部分图标。
● 优化若干已知问题。

三、扫码体验FirstUI

考虑快速预览,所以暂未上架App应用,后续待功能完善再进行上架。

四、开源版与商业版

FirstUI分为开源版与商业版,部分组件为商业版专属使用。

1、开源版

● github: https://github.com/FirstUI/FirstUI (欢迎star :-D)
● gitee: https://gitee.com/firstui/FirstUI (欢迎star :-D)
● 文档地址: https://doc.firstui.cn

2、VIP会员权益:

● 完整版源码
● 全部组件
● 物料商城享VIP折扣
● 专属会员群指导、答疑
● 新特性优先体验
● VIP专属文档
会员权益详情: https://www.firstui.cn/right

3、新版优惠

新版发布,优惠期内框架可5折¥150元购买(原价¥300元),2022年01月31日截止。购买框架产品即升级为VIP会员,享受VIP会员权益。
立即购买:https://www.firstui.cn/store/detail/1

收起阅读 »

完美解决 uniapp 发布h5上传头像base64图片 又拍云

uni_app

众多app都有修改个人头像需求,最近项目需要发布H5端,却发现上传头像图片文件不行。原因是再H5端,调用uni.canvasToTempFilePath方法后,得到的是base64图片数据,而不是图片缓存路径。而uni.uploadFile需要的是图片缓存路径。至此陷入漫长的寻找解决方案的过程。。。。

网上一些资料都是说做成formData的方式上传,这个方向是OK的,但是uniapp不支持new FormData(); 和 new window.FormData() 。 new window.FormData() 虽然不报错,但是上传的时候却把formData解析成对象,服务器(又拍云)拿不到base64的图片数据。

经过三天不断地尝试,结果发现,不用new window.FormData() 也行,直接用对象存储,作为formData的参数值上传即可。下面直接上代码:

在canvasToTempFilePath的成功回调里调用下面这个方法:

        upGoodsloadImgs(imageSrc) {  
            uni.showLoading({  
                title: '图片上传中。。。',  
                mask: "true"  
            });  
            var that = this;  
            console.log("图片源路径:" + imageSrc)  
            var blobData = '';  
            var myFormdata = {};  

            // #ifdef APP-PLUS  
            var hz = imageSrc.substring(imageSrc.lastIndexOf('.'));  
            // #endif  
            // #ifdef H5  
            var arr = imageSrc.split(',');  
            var mime = arr[0].match(/:(.*?);/)[1]  
            var bstr = atob(arr[1]);//这个网上抄的,不太明白什么作用,没有它还不行,有知道的请不吝指教  
            var n = bstr.length;  
            var u8arr = new Uint8Array(n);  
            while (n--) {  
                u8arr[n] = bstr.charCodeAt(n);//  
            }  
            blobData = new Blob([u8arr], {  
                type: mime  
            });  
            myFormdata.file = blobData;  
            console.log("that.imageName = " + that.imageName);  
            var hz = mime.split('/')[1] //获得后缀  
            imageSrc = '';  
            // #endif  

            var nowd = new Date();  
            var imgName = 'avatar' + nowd.getFullYear().toString() + (nowd.getMonth() + 1) + nowd.getDate() + nowd  
                .getHours() + nowd.getMinutes() + nowd.getSeconds() + (nowd.getTime() % 1000).toString() + '.' + hz;  
            var imgUrl = '/samxiche/' + imgName;  

            that.upyun.upload({  
                localPath: imageSrc,  
                remotePath: imgUrl,  
                success: function(res) {  
                    console.log("图片上传成功:图片链接 = " + imgUrl);  
                    console.log("返回的上传结果:", res);  
                    var resImgInfo = JSON.parse(res.data);  
                    var url = that.imgBaseHost + resImgInfo.url;  
                    console.log("图片链接:", url);  

                    let pages = getCurrentPages(); //获取所有页面栈实例列表  
                    let nowPage = pages[pages.length - 1]; //当前页页面实例  
                    let prevPage = pages[pages.length - 2]; //上一页页面实例  
                    prevPage.$vm.avatarurl = url; //修改上一页datad的变量值  
                    uni.navigateBack({ //uni.navigateTo跳转的返回,默认1为返回上一级  
                        delta: 1  
                    });  
                },  

                fail: function(errMsg) {  
                    console.log("图片上传失败:", errMsg);  
                }  
            }, myFormdata ? myFormdata : '')  

        },  

下面是又拍云的上传代码:
Upyun.prototype.upload = function (options, formParams) {
var self = this
console.log("this.bucket = " + this.bucket)
console.log("this.operator = " + this.operator)
console.log("this.getSignatureUrl = " + this.getSignatureUrl)
console.log("options.remotePath = " + options.remotePath)
var date = (new Date()).toGMTString()
var opts = {
'save-key': options.remotePath,
bucket: self.bucket,
expiration: Math.round(new Date().getTime() / 1000) + 3600,
date: date
}
var policy = Base64.encode(JSON.stringify(opts))
var data = [ 'POST', '/' + self.bucket, date, policy ].join('&')
self.getSignature(data, function (err, signature) {
if (err) {
console.log("获取图片上传签名失败!!!", err)
options.fail && options.fail(err)
options.complete && options.complete(err)
return
}

// #ifdef APP-PLUS  
console.log("APP方式上传。。。。。。")  
uni.uploadFile({  
  // url: `https://v0.api.upyun.com/${self.bucket}`,  
  url: `https://v0.api.upyun.com/${self.bucket}`,  
  filePath: options.localPath,  
  name: 'file',  
  formData: {  
    authorization: `UPYUN ${self.operator}:${signature}`,  
    policy: policy,  

  },  
  success: options.success,  
  fail: options.fail,  
  complete: options.complete  
})  
// #endif  
// #ifdef H5  
console.log("H5方式上传。。。。。。")  

// formParams.append("authorization", `UPYUN ${self.operator}:${signature}`);  
// formParams.append('policy', policy);  
  //这个formParams就是一个JSON对象,上面FormData方式的不行  
formParams.authorization = `UPYUN ${self.operator}:${signature}`;  
formParams.policy = policy;  
console.log("要上传的formData:", formParams);  

//uploadFIle接口直接不要filePath参数,就放一个formData就行。
uni.uploadFile({
// url: https://v0.api.upyun.com/${self.bucket},
url: https://v0.api.upyun.com/${self.bucket},
name: 'file',
formData: formParams,
success: options.success,
fail: options.fail,
complete: options.complete
})
// #endif
})
}

附件压缩包是完整的代码,两个js文件。配置好自己的又拍云图片空间 和 获取访问密钥的接口就行。

继续阅读 »

众多app都有修改个人头像需求,最近项目需要发布H5端,却发现上传头像图片文件不行。原因是再H5端,调用uni.canvasToTempFilePath方法后,得到的是base64图片数据,而不是图片缓存路径。而uni.uploadFile需要的是图片缓存路径。至此陷入漫长的寻找解决方案的过程。。。。

网上一些资料都是说做成formData的方式上传,这个方向是OK的,但是uniapp不支持new FormData(); 和 new window.FormData() 。 new window.FormData() 虽然不报错,但是上传的时候却把formData解析成对象,服务器(又拍云)拿不到base64的图片数据。

经过三天不断地尝试,结果发现,不用new window.FormData() 也行,直接用对象存储,作为formData的参数值上传即可。下面直接上代码:

在canvasToTempFilePath的成功回调里调用下面这个方法:

        upGoodsloadImgs(imageSrc) {  
            uni.showLoading({  
                title: '图片上传中。。。',  
                mask: "true"  
            });  
            var that = this;  
            console.log("图片源路径:" + imageSrc)  
            var blobData = '';  
            var myFormdata = {};  

            // #ifdef APP-PLUS  
            var hz = imageSrc.substring(imageSrc.lastIndexOf('.'));  
            // #endif  
            // #ifdef H5  
            var arr = imageSrc.split(',');  
            var mime = arr[0].match(/:(.*?);/)[1]  
            var bstr = atob(arr[1]);//这个网上抄的,不太明白什么作用,没有它还不行,有知道的请不吝指教  
            var n = bstr.length;  
            var u8arr = new Uint8Array(n);  
            while (n--) {  
                u8arr[n] = bstr.charCodeAt(n);//  
            }  
            blobData = new Blob([u8arr], {  
                type: mime  
            });  
            myFormdata.file = blobData;  
            console.log("that.imageName = " + that.imageName);  
            var hz = mime.split('/')[1] //获得后缀  
            imageSrc = '';  
            // #endif  

            var nowd = new Date();  
            var imgName = 'avatar' + nowd.getFullYear().toString() + (nowd.getMonth() + 1) + nowd.getDate() + nowd  
                .getHours() + nowd.getMinutes() + nowd.getSeconds() + (nowd.getTime() % 1000).toString() + '.' + hz;  
            var imgUrl = '/samxiche/' + imgName;  

            that.upyun.upload({  
                localPath: imageSrc,  
                remotePath: imgUrl,  
                success: function(res) {  
                    console.log("图片上传成功:图片链接 = " + imgUrl);  
                    console.log("返回的上传结果:", res);  
                    var resImgInfo = JSON.parse(res.data);  
                    var url = that.imgBaseHost + resImgInfo.url;  
                    console.log("图片链接:", url);  

                    let pages = getCurrentPages(); //获取所有页面栈实例列表  
                    let nowPage = pages[pages.length - 1]; //当前页页面实例  
                    let prevPage = pages[pages.length - 2]; //上一页页面实例  
                    prevPage.$vm.avatarurl = url; //修改上一页datad的变量值  
                    uni.navigateBack({ //uni.navigateTo跳转的返回,默认1为返回上一级  
                        delta: 1  
                    });  
                },  

                fail: function(errMsg) {  
                    console.log("图片上传失败:", errMsg);  
                }  
            }, myFormdata ? myFormdata : '')  

        },  

下面是又拍云的上传代码:
Upyun.prototype.upload = function (options, formParams) {
var self = this
console.log("this.bucket = " + this.bucket)
console.log("this.operator = " + this.operator)
console.log("this.getSignatureUrl = " + this.getSignatureUrl)
console.log("options.remotePath = " + options.remotePath)
var date = (new Date()).toGMTString()
var opts = {
'save-key': options.remotePath,
bucket: self.bucket,
expiration: Math.round(new Date().getTime() / 1000) + 3600,
date: date
}
var policy = Base64.encode(JSON.stringify(opts))
var data = [ 'POST', '/' + self.bucket, date, policy ].join('&')
self.getSignature(data, function (err, signature) {
if (err) {
console.log("获取图片上传签名失败!!!", err)
options.fail && options.fail(err)
options.complete && options.complete(err)
return
}

// #ifdef APP-PLUS  
console.log("APP方式上传。。。。。。")  
uni.uploadFile({  
  // url: `https://v0.api.upyun.com/${self.bucket}`,  
  url: `https://v0.api.upyun.com/${self.bucket}`,  
  filePath: options.localPath,  
  name: 'file',  
  formData: {  
    authorization: `UPYUN ${self.operator}:${signature}`,  
    policy: policy,  

  },  
  success: options.success,  
  fail: options.fail,  
  complete: options.complete  
})  
// #endif  
// #ifdef H5  
console.log("H5方式上传。。。。。。")  

// formParams.append("authorization", `UPYUN ${self.operator}:${signature}`);  
// formParams.append('policy', policy);  
  //这个formParams就是一个JSON对象,上面FormData方式的不行  
formParams.authorization = `UPYUN ${self.operator}:${signature}`;  
formParams.policy = policy;  
console.log("要上传的formData:", formParams);  

//uploadFIle接口直接不要filePath参数,就放一个formData就行。
uni.uploadFile({
// url: https://v0.api.upyun.com/${self.bucket},
url: https://v0.api.upyun.com/${self.bucket},
name: 'file',
formData: formParams,
success: options.success,
fail: options.fail,
complete: options.complete
})
// #endif
})
}

附件压缩包是完整的代码,两个js文件。配置好自己的又拍云图片空间 和 获取访问密钥的接口就行。

收起阅读 »

关于uniapp双向认证https的经验分享

大佬请绕道,emmm.....小白可以看一下
我在使用过程中遇到以下问题:

  1. uni.configMTLS如何使用的问题
  2. 如何使用域名访问的问题
  3. 证书生成的问题
  4. 证书引用的问题
  5. 使用报错的问题
    首先说一下,我后台用的是springboot,因为是测试所以随便写了一个接口,直接通过uniapp调用
  6. uni.configMTLS如何使用的问题
    uni.configMTLS是配置证书用按照官方文档配置即可,可以放到App.vue文件的生命周期函数中调用一次即可,发请求还是用uni.request正常发
  7. 如何使用域名访问的问题
    我用的是夜神模拟器,修改模拟器hosts文件配置域名映射到springboot所在机器ip,我随便起了个域名www.unihttps.com
  8. 证书生成的问题
    我用的是keytool,具体怎么用请自行百度,很大程度借助了这篇文章https://www.jianshu.com/p/cabf4b759d8f
  9. 证书引用的问题
    把生成的client.p12和server.cer直接扔到uni工程下的static下,按官方demo引用的方式用就行了
  10. 使用报错的问题
    碰到的错误,走了各种弯路,遇到各种奇葩错,当遇到如下错误时我感觉自己要成功了
    "errMsg": "request:fail abort statusCode:-1 Hostname www.unihttps.com not verified:\n certificate: sha256/4cIBWyy+2BlD/ME12B4hIRVEefrl5X0nL0/3DGISfKA=\n DN: CN=www.unihttps.com\n subjectAltNames: []"

    但是还是有毛病,此时我的postman和浏览器都可以正常使用证书访问后台,但是uni工程还是不可以,本来以为是uniapp的bug,经过长时间百度“keytool DN”关键词,找到了问题所在,server证书没有配置-SAN参数,配置后即可正常访问
    为了实现uniapp的双向认证废了我这个菜鸡2个星期,一度认证官方uni.configMTLS是逗我玩的,功夫不负有心人啊,希望后来人看到此帖能够早点下班,谢谢过程中各位大佬赐教

继续阅读 »

大佬请绕道,emmm.....小白可以看一下
我在使用过程中遇到以下问题:

  1. uni.configMTLS如何使用的问题
  2. 如何使用域名访问的问题
  3. 证书生成的问题
  4. 证书引用的问题
  5. 使用报错的问题
    首先说一下,我后台用的是springboot,因为是测试所以随便写了一个接口,直接通过uniapp调用
  6. uni.configMTLS如何使用的问题
    uni.configMTLS是配置证书用按照官方文档配置即可,可以放到App.vue文件的生命周期函数中调用一次即可,发请求还是用uni.request正常发
  7. 如何使用域名访问的问题
    我用的是夜神模拟器,修改模拟器hosts文件配置域名映射到springboot所在机器ip,我随便起了个域名www.unihttps.com
  8. 证书生成的问题
    我用的是keytool,具体怎么用请自行百度,很大程度借助了这篇文章https://www.jianshu.com/p/cabf4b759d8f
  9. 证书引用的问题
    把生成的client.p12和server.cer直接扔到uni工程下的static下,按官方demo引用的方式用就行了
  10. 使用报错的问题
    碰到的错误,走了各种弯路,遇到各种奇葩错,当遇到如下错误时我感觉自己要成功了
    "errMsg": "request:fail abort statusCode:-1 Hostname www.unihttps.com not verified:\n certificate: sha256/4cIBWyy+2BlD/ME12B4hIRVEefrl5X0nL0/3DGISfKA=\n DN: CN=www.unihttps.com\n subjectAltNames: []"

    但是还是有毛病,此时我的postman和浏览器都可以正常使用证书访问后台,但是uni工程还是不可以,本来以为是uniapp的bug,经过长时间百度“keytool DN”关键词,找到了问题所在,server证书没有配置-SAN参数,配置后即可正常访问
    为了实现uniapp的双向认证废了我这个菜鸡2个星期,一度认证官方uni.configMTLS是逗我玩的,功夫不负有心人啊,希望后来人看到此帖能够早点下班,谢谢过程中各位大佬赐教

收起阅读 »

AppStore推广内购、SKPaymentTransactionObser

AppStore推广内购、SKPaymentTransactionObser:https://ext.dcloud.net.cn/plugin?id=7092

AppStore推广内购、SKPaymentTransactionObser:https://ext.dcloud.net.cn/plugin?id=7092

重写获取当前的地理位置wgs84 转 gcj02

uni.getlocation

重写获取当前的地理位置

由于不使用第三方地图SDK无法返回gcj02 ,所以在获取后加上换算,对精度要求高的还是使用地图SDK比较好

官方不建议js转换

可能部分地区偏差较大,对精度有要求还是交钱使用地图SDK吧

继续阅读 »

重写获取当前的地理位置

由于不使用第三方地图SDK无法返回gcj02 ,所以在获取后加上换算,对精度要求高的还是使用地图SDK比较好

官方不建议js转换

可能部分地区偏差较大,对精度有要求还是交钱使用地图SDK吧

收起阅读 »

mui技术点01.手机网络连接的判断

问题说明

手机未连接网络,app访问后台接口时出现错误。

解决方案

每个画面初始时,判断网络是否连接,网络如果处于断网阶段,弹出窗口信息直接退出app,或者画面显示[未联网]消息提示,提示用户刷新网络(比如:点击刷新按钮,或者下拉等操作)。

详细代码

直接退出app

  1. 检测是否连接网络

    //mui检测是否连接网络  
    function getSysInfo() {  
    //  var str = "";  
    //  str += "名称:" + plus.os.name + "\n";  
    //  str += "版本:" + plus.os.version + "\n";  
    //  str += "语言:" + plus.os.language + "\n";  
    //  str += "厂商:" + plus.os.vendor + "\n";  
    //  str += "网络类型:";  
    
    types = {};  
    types[plus.networkinfo.CONNECTION_UNKNOW] = "未知";  
    types[plus.networkinfo.CONNECTION_NONE] = "未连接网络";  
    types[plus.networkinfo.CONNECTION_ETHERNET] = "有线网络";  
    types[plus.networkinfo.CONNECTION_WIFI] = "WiFi网络";  
    types[plus.networkinfo.CONNECTION_CELL2G] = "2G蜂窝网络";  
    types[plus.networkinfo.CONNECTION_CELL3G] = "3G蜂窝网络";  
    types[plus.networkinfo.CONNECTION_CELL4G] = "4G蜂窝网络";  
    
    var str = types[plus.networkinfo.getCurrentType()];  
    if (str == '未知' || str == '未连接网络') {  
        return false;  
    } else {  
        return true;  
    }  
    }
  2. 调用及处理
    mui.plusReady(function() {  
    //如果未连接网络,退出app(针对mui框架)  
    if (!(getSysInfo())) {  
        alert('网络连接失败,请退出并重置网络!');  
        plus.runtime.quit();//退出app(针对mui框架)  
        return;  
    }  
    });

画面显示提示按钮,让用户手动刷新

  1. 点击按钮,进行画面刷新

    <!DOCTYPE html>  
    <html>  
    <head>  
    <meta charset="utf-8">  
    <meta name="viewport"  
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title>网络未连接</title>  
    <link href="css/mui.min.css" rel="stylesheet" />  
    <style>  
        .mui-btn {  
            display: block;  
            width: 60px;  
            margin: 10px auto;  
        }  
    
        #info {  
            padding: 10px 5px;  
        }  
    </style>  
    </head>  
    <body>  
    
    <header class="mui-bar mui-bar-nav">  
        <h1 class="mui-title">按钮点击刷新</h1>  
    </header>  
    
    <div id="content" class="mui-content">  
        <div class="mui-content-padded" style="margin: 50px;text-align: center;">  
            <div id="info"></div>  
            <button id="button" type="button" class="mui-btn mui-btn-outlined">刷新</button>  
        </div>  
    </div>  
    
    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  
        var info = document.getElementById("info");  
        (function($) {  
            mui.toast('画面初始化!');  
            info.innerText = '网络未连接,请连接网络后刷新!';  
        })(mui);  
    
        document.getElementById("button").addEventListener('tap', function() {  
            location.reload();  
        });  
    </script>  
    </body>  
    </html>
  2. 下划画面刷新
<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport"  
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title>网络未连接</title>  
    <link href="css/mui.min.css" rel="stylesheet" />  
    <style>  
        #info {  
            padding: 10px 5px;  
        }  
    </style>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <h1 class="mui-title">画面下拉刷新</h1>  
    </header>  

    <div id="content" class="mui-content">  
        <div class="mui-content-padded" style="margin: 50px;text-align: center;">  
            <div id="info"></div>  
        </div>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        var info = document.getElementById("info");  
        mui.init({  
        pullRefresh: {  
                container: "#content", //待刷新区域标识,querySelector能定位的css选择器均可,比如:id、.class等  
                down: { //下拉刷新  
                    style: 'circle', //必选,下拉刷新样式,目前支持原生5+ ‘circle’ 样式  
                    height: 50, //可选,默认50.触发下拉刷新拖动距离,  
                    auto: false, //可选,默认false.首次加载自动下拉刷新一次  
                    contentdown: "下拉可以刷新", //可选,在下拉可刷新状态时,下拉刷新控件上显示的标题内容  
                    contentover: "释放立即刷新", //可选,在释放可刷新状态时,下拉刷新控件上显示的标题内容  
                    contentrefresh: "正在刷新...", //可选,正在刷新状态时,下拉刷新控件上显示的标题内容  
                    callback: function() { //必选,刷新函数,根据具体业务来编写,比如通过ajax从服务器获取新数据;  
                        //模拟向服务器获取数据的等待时间  
                        sleep(1000);  
                        location.reload();  
                    }  
                }  
            }  
        });  

        (function($) {  
            info.innerText = '网络未连接,请网络连接后,下拉刷新进行页面刷新!';  
            mui.toast('画面初始化!');  
        })(mui);  

        //模拟线程等待,ms:单位毫秒  
        function sleep(ms) {  
            return new Promise(resolve => setTimeout(resolve, ms));  
        }  
    </script>  
</body>  
</html>

注意

  1. 内置浏览器技术实现,但手机上未必实现,调研的结果在手机上验证之后,在进行项目代码合并。
  2. 关于下拉刷新详细的参数,请参照官网的文档说明:https://dev.dcloud.net.cn/mui/pulldown/
继续阅读 »

问题说明

手机未连接网络,app访问后台接口时出现错误。

解决方案

每个画面初始时,判断网络是否连接,网络如果处于断网阶段,弹出窗口信息直接退出app,或者画面显示[未联网]消息提示,提示用户刷新网络(比如:点击刷新按钮,或者下拉等操作)。

详细代码

直接退出app

  1. 检测是否连接网络

    //mui检测是否连接网络  
    function getSysInfo() {  
    //  var str = "";  
    //  str += "名称:" + plus.os.name + "\n";  
    //  str += "版本:" + plus.os.version + "\n";  
    //  str += "语言:" + plus.os.language + "\n";  
    //  str += "厂商:" + plus.os.vendor + "\n";  
    //  str += "网络类型:";  
    
    types = {};  
    types[plus.networkinfo.CONNECTION_UNKNOW] = "未知";  
    types[plus.networkinfo.CONNECTION_NONE] = "未连接网络";  
    types[plus.networkinfo.CONNECTION_ETHERNET] = "有线网络";  
    types[plus.networkinfo.CONNECTION_WIFI] = "WiFi网络";  
    types[plus.networkinfo.CONNECTION_CELL2G] = "2G蜂窝网络";  
    types[plus.networkinfo.CONNECTION_CELL3G] = "3G蜂窝网络";  
    types[plus.networkinfo.CONNECTION_CELL4G] = "4G蜂窝网络";  
    
    var str = types[plus.networkinfo.getCurrentType()];  
    if (str == '未知' || str == '未连接网络') {  
        return false;  
    } else {  
        return true;  
    }  
    }
  2. 调用及处理
    mui.plusReady(function() {  
    //如果未连接网络,退出app(针对mui框架)  
    if (!(getSysInfo())) {  
        alert('网络连接失败,请退出并重置网络!');  
        plus.runtime.quit();//退出app(针对mui框架)  
        return;  
    }  
    });

画面显示提示按钮,让用户手动刷新

  1. 点击按钮,进行画面刷新

    <!DOCTYPE html>  
    <html>  
    <head>  
    <meta charset="utf-8">  
    <meta name="viewport"  
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title>网络未连接</title>  
    <link href="css/mui.min.css" rel="stylesheet" />  
    <style>  
        .mui-btn {  
            display: block;  
            width: 60px;  
            margin: 10px auto;  
        }  
    
        #info {  
            padding: 10px 5px;  
        }  
    </style>  
    </head>  
    <body>  
    
    <header class="mui-bar mui-bar-nav">  
        <h1 class="mui-title">按钮点击刷新</h1>  
    </header>  
    
    <div id="content" class="mui-content">  
        <div class="mui-content-padded" style="margin: 50px;text-align: center;">  
            <div id="info"></div>  
            <button id="button" type="button" class="mui-btn mui-btn-outlined">刷新</button>  
        </div>  
    </div>  
    
    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  
        var info = document.getElementById("info");  
        (function($) {  
            mui.toast('画面初始化!');  
            info.innerText = '网络未连接,请连接网络后刷新!';  
        })(mui);  
    
        document.getElementById("button").addEventListener('tap', function() {  
            location.reload();  
        });  
    </script>  
    </body>  
    </html>
  2. 下划画面刷新
<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport"  
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title>网络未连接</title>  
    <link href="css/mui.min.css" rel="stylesheet" />  
    <style>  
        #info {  
            padding: 10px 5px;  
        }  
    </style>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <h1 class="mui-title">画面下拉刷新</h1>  
    </header>  

    <div id="content" class="mui-content">  
        <div class="mui-content-padded" style="margin: 50px;text-align: center;">  
            <div id="info"></div>  
        </div>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        var info = document.getElementById("info");  
        mui.init({  
        pullRefresh: {  
                container: "#content", //待刷新区域标识,querySelector能定位的css选择器均可,比如:id、.class等  
                down: { //下拉刷新  
                    style: 'circle', //必选,下拉刷新样式,目前支持原生5+ ‘circle’ 样式  
                    height: 50, //可选,默认50.触发下拉刷新拖动距离,  
                    auto: false, //可选,默认false.首次加载自动下拉刷新一次  
                    contentdown: "下拉可以刷新", //可选,在下拉可刷新状态时,下拉刷新控件上显示的标题内容  
                    contentover: "释放立即刷新", //可选,在释放可刷新状态时,下拉刷新控件上显示的标题内容  
                    contentrefresh: "正在刷新...", //可选,正在刷新状态时,下拉刷新控件上显示的标题内容  
                    callback: function() { //必选,刷新函数,根据具体业务来编写,比如通过ajax从服务器获取新数据;  
                        //模拟向服务器获取数据的等待时间  
                        sleep(1000);  
                        location.reload();  
                    }  
                }  
            }  
        });  

        (function($) {  
            info.innerText = '网络未连接,请网络连接后,下拉刷新进行页面刷新!';  
            mui.toast('画面初始化!');  
        })(mui);  

        //模拟线程等待,ms:单位毫秒  
        function sleep(ms) {  
            return new Promise(resolve => setTimeout(resolve, ms));  
        }  
    </script>  
</body>  
</html>

注意

  1. 内置浏览器技术实现,但手机上未必实现,调研的结果在手机上验证之后,在进行项目代码合并。
  2. 关于下拉刷新详细的参数,请参照官网的文档说明:https://dev.dcloud.net.cn/mui/pulldown/
收起阅读 »

修复uni.preloadPage预加载tabBar页面,正式打包切换tabBar报错/出错

uni统计 预加载

修复方法如下:

修改uni统计源代码(修改后的uni-stat见附件)

打开HBuilderX安装目录,找到 HBuilderX安装目录\plugins\uniapp-cli\node_modules\@dcloudio\uni-stat\dist\index.js 这个文件。需注意package.json文件中version指定的版本,我的是uni-stat 2.0.0-32920211119001这个版本。

如果你的项目不是使用HBuilderX创建的,你可能需要修改 HBuilderX安装目录\plugins\uniapp-cli\node_modules\@dcloudio\vue-cli-plugin-uni\packages\uni-stat\dist 文件夹下的 uni-stat.cjs.js和uni-stat.es.js这两个文件,改造方法应该和下面的相同(因为我的项目没有用到这两个js代码,所以不清楚这两个代码在什么环境下起作用)。至于怎么查看用到的是哪个目录中的uni-stat,可以通过查看项目编译后的app-service.js文件,搜索关键字“@dcloudio/uni-stat@”, 可以定位到uni-stat的版本

下面开工改造:

1. 修改getRoute方法

const getRoute = () => {    
  var pages = getCurrentPages();    
  var page = pages[pages.length - 1];    
  if (!page) return ''    
  // preloadPage的nvue页面$vm为undefined    
  let _self = page.$vm;    
  // 为preloadPage的nvue页面尝试从$page获取route属性    
  let _page = page.$page;    
  if (getPlatformName() === 'bd' && _self) {    
    return _self.$mp && _self.$mp.page.is;    
  } else if (_self){    
    return (_self.$scope && _self.$scope.route) || (_self.$mp && _self.$mp.page.route);    
  } else if (_page){    
    return _page.route    
  } else {    
    console.error('未找到路径')    
    return ''    
  }    
};

2. 修改getPageRoute方法

const getPageRoute = (self) => {    
  var pages = getCurrentPages();    
  var page = pages[pages.length - 1];    
  if (!page) return ''    
  let _self = page.$vm;    
  let _page = page.$page;    
  let query = self._query;    
  let str = query && JSON.stringify(query) !== '{}' ? '?' + JSON.stringify(query) : '';    
  // clear    
  self._query = '';    
  if (getPlatformName() === 'bd' && _self) {    
    return _self.$mp && _self.$mp.page.is + str;    
  } else if (_self) {    
    return (_self.$scope && _self.$scope.route + str) || (_self.$mp && _self.$mp.page.route + str);    
  } else if (_page){    
    return _page.route    
  } else {    
    console.error('未找到路径')    
    return ''    
  }    
};

3. 修改class Stat的load方法

class Stat extends Util {    
  ....此处省略    
  load(options, self) {    
    if (!self.$scope && !self.$mp) {    
      // 如果是preloadPage nvue页面, 则这里拿不到对应的页面(也就是nvue页面不在CurrentPages页面栈中),需要使用route判断一下正确性    
      // 否则会把错误的页面赋值给self.$scope,进而引发Cannot read property '__call_hook' of undefined的错误    
      const page = getCurrentPages();    
      const route = page[page.length - 1].route;    
      if (self.$options && self.$options.route === route) {    
        self.$scope = page[page.length - 1];    
      }    
    }    
    this.self = self;    
    this._query = options;    
  }    
}

4. 在lifecycle前定义函数tryinterceptShare

const tryinterceptShare = function(ctx) {    
    if (ctx.$scope && ctx.$scope.onShareAppMessage) {    
        let oldShareAppMessage = ctx.$scope.onShareAppMessage;    
        ctx.$scope.onShareAppMessage = function(options) {    
          stat.interceptShare(false);    
          return oldShareAppMessage.call(ctx, options)    
        };    
    }    
}

5. 修改lifecycle 的 onLoad 方法

  onLoad(options) {    
    stat.load(options, this);    
    // 重写分享,获取分享上报事件    
    tryinterceptShare(this)    
    // preloadPage nvue页面,$scope可能为undefined    
    if (!this.$scope) {    
        var $scope = undefined;    
        var self = this;    
        Object.defineProperty(this, '$scope', {    
            get() {    
                return $scope    
            },    
            set(val) {    
                $scope = val;    
                setTimeout(()=>{    
                    tryinterceptShare(self)    
                }, 0)    
            }    
        })    
    }    
  }

6. try catch lifecycle 中的所有方法,防止uni-stat统计出错引发应用异常

在 lifecycle定义后,添加如下代码  

const ERROR_PREFIX = 'uni-stat@' + STAT_VERSION;    

// 把lifecycle的方法全都try catch起来,防止出什么错导致应用页面打不开    
const tryfunc = function(func, name) {    
    return function() {    
        try {    
            let args = [].slice.call(arguments);    
            func.apply(this, args)    
        } catch(e) {    
            console.error(ERROR_PREFIX + ' error in function '+ name);    
            console.error(JSON.stringify(e))    
        }    
    }    
}    
Object.keys(lifecycle).forEach(k=>{    
    lifecycle[k] = tryfunc(lifecycle[k], k)    
})  

至此改造完毕。因为我也不知道这么修改会不会在其它平台出现错误,所以大家还是在修改后调试一下再发布,不过目前我自己在安卓机子上测试没发现问题。

继续阅读 »

修复方法如下:

修改uni统计源代码(修改后的uni-stat见附件)

打开HBuilderX安装目录,找到 HBuilderX安装目录\plugins\uniapp-cli\node_modules\@dcloudio\uni-stat\dist\index.js 这个文件。需注意package.json文件中version指定的版本,我的是uni-stat 2.0.0-32920211119001这个版本。

如果你的项目不是使用HBuilderX创建的,你可能需要修改 HBuilderX安装目录\plugins\uniapp-cli\node_modules\@dcloudio\vue-cli-plugin-uni\packages\uni-stat\dist 文件夹下的 uni-stat.cjs.js和uni-stat.es.js这两个文件,改造方法应该和下面的相同(因为我的项目没有用到这两个js代码,所以不清楚这两个代码在什么环境下起作用)。至于怎么查看用到的是哪个目录中的uni-stat,可以通过查看项目编译后的app-service.js文件,搜索关键字“@dcloudio/uni-stat@”, 可以定位到uni-stat的版本

下面开工改造:

1. 修改getRoute方法

const getRoute = () => {    
  var pages = getCurrentPages();    
  var page = pages[pages.length - 1];    
  if (!page) return ''    
  // preloadPage的nvue页面$vm为undefined    
  let _self = page.$vm;    
  // 为preloadPage的nvue页面尝试从$page获取route属性    
  let _page = page.$page;    
  if (getPlatformName() === 'bd' && _self) {    
    return _self.$mp && _self.$mp.page.is;    
  } else if (_self){    
    return (_self.$scope && _self.$scope.route) || (_self.$mp && _self.$mp.page.route);    
  } else if (_page){    
    return _page.route    
  } else {    
    console.error('未找到路径')    
    return ''    
  }    
};

2. 修改getPageRoute方法

const getPageRoute = (self) => {    
  var pages = getCurrentPages();    
  var page = pages[pages.length - 1];    
  if (!page) return ''    
  let _self = page.$vm;    
  let _page = page.$page;    
  let query = self._query;    
  let str = query && JSON.stringify(query) !== '{}' ? '?' + JSON.stringify(query) : '';    
  // clear    
  self._query = '';    
  if (getPlatformName() === 'bd' && _self) {    
    return _self.$mp && _self.$mp.page.is + str;    
  } else if (_self) {    
    return (_self.$scope && _self.$scope.route + str) || (_self.$mp && _self.$mp.page.route + str);    
  } else if (_page){    
    return _page.route    
  } else {    
    console.error('未找到路径')    
    return ''    
  }    
};

3. 修改class Stat的load方法

class Stat extends Util {    
  ....此处省略    
  load(options, self) {    
    if (!self.$scope && !self.$mp) {    
      // 如果是preloadPage nvue页面, 则这里拿不到对应的页面(也就是nvue页面不在CurrentPages页面栈中),需要使用route判断一下正确性    
      // 否则会把错误的页面赋值给self.$scope,进而引发Cannot read property '__call_hook' of undefined的错误    
      const page = getCurrentPages();    
      const route = page[page.length - 1].route;    
      if (self.$options && self.$options.route === route) {    
        self.$scope = page[page.length - 1];    
      }    
    }    
    this.self = self;    
    this._query = options;    
  }    
}

4. 在lifecycle前定义函数tryinterceptShare

const tryinterceptShare = function(ctx) {    
    if (ctx.$scope && ctx.$scope.onShareAppMessage) {    
        let oldShareAppMessage = ctx.$scope.onShareAppMessage;    
        ctx.$scope.onShareAppMessage = function(options) {    
          stat.interceptShare(false);    
          return oldShareAppMessage.call(ctx, options)    
        };    
    }    
}

5. 修改lifecycle 的 onLoad 方法

  onLoad(options) {    
    stat.load(options, this);    
    // 重写分享,获取分享上报事件    
    tryinterceptShare(this)    
    // preloadPage nvue页面,$scope可能为undefined    
    if (!this.$scope) {    
        var $scope = undefined;    
        var self = this;    
        Object.defineProperty(this, '$scope', {    
            get() {    
                return $scope    
            },    
            set(val) {    
                $scope = val;    
                setTimeout(()=>{    
                    tryinterceptShare(self)    
                }, 0)    
            }    
        })    
    }    
  }

6. try catch lifecycle 中的所有方法,防止uni-stat统计出错引发应用异常

在 lifecycle定义后,添加如下代码  

const ERROR_PREFIX = 'uni-stat@' + STAT_VERSION;    

// 把lifecycle的方法全都try catch起来,防止出什么错导致应用页面打不开    
const tryfunc = function(func, name) {    
    return function() {    
        try {    
            let args = [].slice.call(arguments);    
            func.apply(this, args)    
        } catch(e) {    
            console.error(ERROR_PREFIX + ' error in function '+ name);    
            console.error(JSON.stringify(e))    
        }    
    }    
}    
Object.keys(lifecycle).forEach(k=>{    
    lifecycle[k] = tryfunc(lifecycle[k], k)    
})  

至此改造完毕。因为我也不知道这么修改会不会在其它平台出现错误,所以大家还是在修改后调试一下再发布,不过目前我自己在安卓机子上测试没发现问题。

收起阅读 »

uni-商城开源项目,寻求志同道合开发者

开源

https://github.com/gooking/uni-app-mall

[码云镜像:] https://gitee.com/javazj/uni-app-mall

对 uni 有兴趣,热爱开源,有适当的业余时间,乐于助人的朋友,欢迎一起加入到开发团队中来~

技术栈:

  1. uni-app
  2. uview 2.0
  3. 没有其他了
继续阅读 »

https://github.com/gooking/uni-app-mall

[码云镜像:] https://gitee.com/javazj/uni-app-mall

对 uni 有兴趣,热爱开源,有适当的业余时间,乐于助人的朋友,欢迎一起加入到开发团队中来~

技术栈:

  1. uni-app
  2. uview 2.0
  3. 没有其他了
收起阅读 »

mui疑难杂症01.HBuilderX未检测到手机或模拟器

一、 问题

我使用Android 9版本的手机,开发者选项已开启USB调试,但是HBuilderX未检测到手机或模拟器。

二、 解决问题

  1. 找到HBuilderX安装目录下的D:\Archive\HBuilderX\plugins\launcher\tools\adbs

  2. 先备份该录下的除了文件夹外的其他三个文件。

  3. 然后把1.0.31目录下的文件拷贝到D:\Archive\HBuilderX\plugins\launcher\tools\adbs,直接覆盖即可。

  4. 重启HBuilderX就可以检测到手机了。

备注:adb全称Android Debug Bridge(安卓调试桥),它用于通过电脑端与模拟器或者真实设备交互。

继续阅读 »

一、 问题

我使用Android 9版本的手机,开发者选项已开启USB调试,但是HBuilderX未检测到手机或模拟器。

二、 解决问题

  1. 找到HBuilderX安装目录下的D:\Archive\HBuilderX\plugins\launcher\tools\adbs

  2. 先备份该录下的除了文件夹外的其他三个文件。

  3. 然后把1.0.31目录下的文件拷贝到D:\Archive\HBuilderX\plugins\launcher\tools\adbs,直接覆盖即可。

  4. 重启HBuilderX就可以检测到手机了。

备注:adb全称Android Debug Bridge(安卓调试桥),它用于通过电脑端与模拟器或者真实设备交互。

收起阅读 »

知识付费系统源码(开源知识付费平台系统源码下载 )

源码分享

  知识付费系统源码是几乎每一个成功企业的重要组成部分。然而,为什么许多新企业和初创企业忽视了它作为一个可选的组成部分?管理和定期维护知识库的昂贵成本是主要原因。使用复杂工具更新知识文章是企业避免建立知识库的另一个原因。然而,使用正确的KMS,您可以避免所有这些问题。并在很小的预算内建立一个易于管理的知识库。在本指南中,我们将仔细研究系统,以找到适合您平台的知识付费系统源码。
  开源知识付费系统平台源码:zs.xcxyms.top
  什么是知识付费系统源码?
  知识付费系统可以用多种方式定义。对一些人来说,这是一个教学和学习的平台。对其他许多人来说,这是一个有效解决问题的平台。
  简单地说,知识管理系统就是你用来分享知识的平台或工具。
  例如,使用知识管理系统,您可以创建文章、文档和指南。然后,您可以将它们提供给您的客户或员工,让他们找到与您的产品相关的常见问题的解决方案。
  无论是教育你的客户如何使用产品。或者教你的员工如何处理不同的情况。我们都同意,知识库在许多方面对所有类型的企业都是有益的。
  ●知识与管理的利益共享
  如果您还需要进一步的说服力,这里只是知识管理的诸多好处中的一小部分。
  ●降低客户支持成本
  有了知识库,您就可以让您的客户和员工自己找到解决方案。
  如果客户遇到问题,他们可以在知识库中搜索答案。员工可以使用知识库查找如何处理某些情况的指南,如响应客户的询问。
  不管怎样,它都有助于减少回复电子邮件和电话的时间和成本。
  ●培训员工和团队
  私人知识库通常被公司用来培训和教育他们的员工和团队。您可以为每个部门创建多个知识库。用课程、解决方案和指南将其填满。这样他们就可以自己找到常见问题的教程指南了。
  ●更快的解决问题
  知识库还通过提供按需解决方案帮助您节省时间。客户不再需要打电话等待数小时来解决问题。员工不需要经过数月的培训来回答简单的问题。都在知识库里。只需一次搜索就可以了。
  ●分享专业知识
  请教专家已成为过去。有了知识库,你就成了专家。知识库中充满了由专家编写的指南和文件。没有必要在别处寻求帮助。
  ●更好的搜索引擎排名
  公共知识库也可以作为搜索引擎优化的一个很好的来源。你可以优化知识文章和内容,在搜索引擎上为特定关键字排名更高。
  不同类型的知识管理系统
  开发知识管理系统的第一步是制定明确的目标。你的知识库的主要目的是什么?是服务客户还是管理自己的部门?
  一旦你有了明确的目标,你就可以根据不同类型的知识库找到正确的知识管理系统。
  ●客户支持系统
  这些是您为服务客户而制作的公共知识库系统。客户支持知识库包括教程和指南。尤其是对与产品或服务相关的常见问题的解答。这使得客户无需联系客户支持就可以找到解决方案。
  ●专家知识系统
  专家知识系统通常是私有的,为公司的团队或部门服务。例如,客户支持部门可以使用专家知识系统快速找到客户常见问题的答案。这类知识库包括关于特定主题的专家指南。
  ●文件管理系统
  顾名思义,文档管理系统是管理文档的知识库。它对于提供多种产品或服务的企业特别有用。文档管理系统帮助您在一个地方为所有产品创建一个大手册。
  ●数据库管理系统
  数据库管理系统用于存储和共享与产品或业务相关的不同类型的数据。无论是与客户或产品历史相关的数据,数据库管理都有助于保持一切井井有条。
  最佳知识付费系统源码和解决方案
  我们想找到最好的知识付费系统源码和解决方案,你可以用来建立不同类型的知识库。
  1、KnowAll
  KnowAll是为企业和教育构建各种知识管理系统的完整解决方案。
  使用KnowAll的主要好处是很容易开始。它基本上是WordPress的即插即用主题。一切都是为了帮助你在短时间内建立你的知识库,并定制它,使之与你的品牌保持一致。
  有了KnowAll,建立你的知识库就像开始写博客一样简单。你只需点击几下就可以创建新的文章,对它们进行排序,并按类别和子类别对它们进行组织。主题提供多种设计选项,以满足您在用户界面方面的需求。
  但这并不是全部,知识库之上的附加值是统计数据。使用KnowAll,您可以看到哪些文章是您浏览量最大的,并向您的读者征求一些反馈,这样您就可以看到哪些文章是有用的,哪些文章需要改进。
  KnowAll的最佳功能
  易于启动:安装主题,就可以发布第一篇文章了。
  易于组织:创建多个类别和子类别来组织文章并按您选择的顺序显示文章
  多个设计选项:您可以从多个设计中选择并自定义它们以匹配您的品牌。
  分析和报告:获取用户对您文章的反馈
  2、Document360
  Document360是一个自助式知识库软件,属于知识管理系统的范畴。它是一个完整的解决方案,用于收集、组织、检索和共享您可以定义的客户和内部用户的知识。
  文档的最佳功能360
  Document360屏幕截图
  多个仪表板:它支持许多项目或文档网站,因此当产品列表扩展时,您不必再去其他任何地方。
  强大的编辑:支持Markdown编辑器和WYSIWYG(你看到的就是你得到的)编辑器,以实现高效和结构化的写作。
  类似谷歌的硬盘:存储和管理文件、图像、视频,并随时将其插入知识库。
  版本控制和回滚:能够轻松地回滚到文章的前一个版本。
  第三方集成:它可以与对讲机、Freshdesk、Microsoft和Zendesk等集成。
  类别级别的安全性:Document360具有多个级别的高级安全访问,可以覆盖所有场景。您可以提供不同级别的读者访问权限。
  分析和报告:提供有关客户地理位置、性能和搜索概述的详细报告的强大见解。
  3、Zendesk
  Zendesk被认为是最好的客户支持管理软件之一。但是,它可以处理的不仅仅是支持票和实时聊天。
  Zendesk还具有一个称为Zendesk指南的平滑的知识管理系统。你可以使用它来建立私人和公共知识库,为客户和员工服务。
  Zendesk指南是Zendesk软件套件的一部分。订阅Zendesk后,您可以访问其他几个工具。这使得它成为更适合大企业和企业的选择。
  Zendesk的最佳功能
  zendesk预览
  初学者友好界面:Zendesk具有一个非常适合初学者的用户界面。它允许你管理你的知识库没有复杂的问题。
  从一个地方管理一切:Zendesk还有一个全渠道仪表盘。有了它,你可以从一个地方管理你的知识库、支持票证、聊天、电话等等。
  所见即所得编辑器:Zendesk指南包括一个What You See Is What You Get样式编辑器。您可以使用此编辑器创建丰富的内容设计。
  内置CRM:Zendesk使用Sunshine,它自己的CRM允许你为客户提供个性化的体验。
  4、HelpJuice
  HelpJuice是一款现代且易于使用的知识库软件,非常适合初创企业和小型企业。沃尔玛等许多知名大品牌都在使用这款软件,比如周一这样的初创企业。
  与大多数其他知识库软件不同,HelpJuice更易于设置和管理。它还有一个创新的编辑器,允许您创建结构良好的内容,同时保留更改历史记录。
  帮助果汁的最佳功能
  helpjuice预览
  强大的编辑:工具集编辑器支持许多有用的功能。比如协作和控制访问。
  简易设置:HelpJuice很容易设置。实际上,你可以在几分钟内建立并运行一个知识库。
  主题和自定义:您还可以使用预先制作的主题自定义知识库的设计。
  第三方集成:HelpJuice还支持Slack、Zendesk、SalesForce和其他集成。
  你应该使用哪种知识付费系统源码?
  WordPress是为中小型企业和初创企业建立知识库的理想选择。你可以用它来建立各种公共和私人知识库。它支持数千个扩展功能的插件。而且非常实惠。
  对于大型企业和公司,我们建议使用像Zendesk这样的一体式解决方案。从一个地方管理你的客户服务和知识库是值得的。尤其是当你需要监督很多部门和团队的时候。
  当然,你不应该只相信我们的话。做更多的研究和探索更多的选择,为您的企业找到正确的知识管理系统。你也可以看看我们自己的知识管理产品。 我们有一系列工具,专门用来建立各种类型的知识库。

继续阅读 »

  知识付费系统源码是几乎每一个成功企业的重要组成部分。然而,为什么许多新企业和初创企业忽视了它作为一个可选的组成部分?管理和定期维护知识库的昂贵成本是主要原因。使用复杂工具更新知识文章是企业避免建立知识库的另一个原因。然而,使用正确的KMS,您可以避免所有这些问题。并在很小的预算内建立一个易于管理的知识库。在本指南中,我们将仔细研究系统,以找到适合您平台的知识付费系统源码。
  开源知识付费系统平台源码:zs.xcxyms.top
  什么是知识付费系统源码?
  知识付费系统可以用多种方式定义。对一些人来说,这是一个教学和学习的平台。对其他许多人来说,这是一个有效解决问题的平台。
  简单地说,知识管理系统就是你用来分享知识的平台或工具。
  例如,使用知识管理系统,您可以创建文章、文档和指南。然后,您可以将它们提供给您的客户或员工,让他们找到与您的产品相关的常见问题的解决方案。
  无论是教育你的客户如何使用产品。或者教你的员工如何处理不同的情况。我们都同意,知识库在许多方面对所有类型的企业都是有益的。
  ●知识与管理的利益共享
  如果您还需要进一步的说服力,这里只是知识管理的诸多好处中的一小部分。
  ●降低客户支持成本
  有了知识库,您就可以让您的客户和员工自己找到解决方案。
  如果客户遇到问题,他们可以在知识库中搜索答案。员工可以使用知识库查找如何处理某些情况的指南,如响应客户的询问。
  不管怎样,它都有助于减少回复电子邮件和电话的时间和成本。
  ●培训员工和团队
  私人知识库通常被公司用来培训和教育他们的员工和团队。您可以为每个部门创建多个知识库。用课程、解决方案和指南将其填满。这样他们就可以自己找到常见问题的教程指南了。
  ●更快的解决问题
  知识库还通过提供按需解决方案帮助您节省时间。客户不再需要打电话等待数小时来解决问题。员工不需要经过数月的培训来回答简单的问题。都在知识库里。只需一次搜索就可以了。
  ●分享专业知识
  请教专家已成为过去。有了知识库,你就成了专家。知识库中充满了由专家编写的指南和文件。没有必要在别处寻求帮助。
  ●更好的搜索引擎排名
  公共知识库也可以作为搜索引擎优化的一个很好的来源。你可以优化知识文章和内容,在搜索引擎上为特定关键字排名更高。
  不同类型的知识管理系统
  开发知识管理系统的第一步是制定明确的目标。你的知识库的主要目的是什么?是服务客户还是管理自己的部门?
  一旦你有了明确的目标,你就可以根据不同类型的知识库找到正确的知识管理系统。
  ●客户支持系统
  这些是您为服务客户而制作的公共知识库系统。客户支持知识库包括教程和指南。尤其是对与产品或服务相关的常见问题的解答。这使得客户无需联系客户支持就可以找到解决方案。
  ●专家知识系统
  专家知识系统通常是私有的,为公司的团队或部门服务。例如,客户支持部门可以使用专家知识系统快速找到客户常见问题的答案。这类知识库包括关于特定主题的专家指南。
  ●文件管理系统
  顾名思义,文档管理系统是管理文档的知识库。它对于提供多种产品或服务的企业特别有用。文档管理系统帮助您在一个地方为所有产品创建一个大手册。
  ●数据库管理系统
  数据库管理系统用于存储和共享与产品或业务相关的不同类型的数据。无论是与客户或产品历史相关的数据,数据库管理都有助于保持一切井井有条。
  最佳知识付费系统源码和解决方案
  我们想找到最好的知识付费系统源码和解决方案,你可以用来建立不同类型的知识库。
  1、KnowAll
  KnowAll是为企业和教育构建各种知识管理系统的完整解决方案。
  使用KnowAll的主要好处是很容易开始。它基本上是WordPress的即插即用主题。一切都是为了帮助你在短时间内建立你的知识库,并定制它,使之与你的品牌保持一致。
  有了KnowAll,建立你的知识库就像开始写博客一样简单。你只需点击几下就可以创建新的文章,对它们进行排序,并按类别和子类别对它们进行组织。主题提供多种设计选项,以满足您在用户界面方面的需求。
  但这并不是全部,知识库之上的附加值是统计数据。使用KnowAll,您可以看到哪些文章是您浏览量最大的,并向您的读者征求一些反馈,这样您就可以看到哪些文章是有用的,哪些文章需要改进。
  KnowAll的最佳功能
  易于启动:安装主题,就可以发布第一篇文章了。
  易于组织:创建多个类别和子类别来组织文章并按您选择的顺序显示文章
  多个设计选项:您可以从多个设计中选择并自定义它们以匹配您的品牌。
  分析和报告:获取用户对您文章的反馈
  2、Document360
  Document360是一个自助式知识库软件,属于知识管理系统的范畴。它是一个完整的解决方案,用于收集、组织、检索和共享您可以定义的客户和内部用户的知识。
  文档的最佳功能360
  Document360屏幕截图
  多个仪表板:它支持许多项目或文档网站,因此当产品列表扩展时,您不必再去其他任何地方。
  强大的编辑:支持Markdown编辑器和WYSIWYG(你看到的就是你得到的)编辑器,以实现高效和结构化的写作。
  类似谷歌的硬盘:存储和管理文件、图像、视频,并随时将其插入知识库。
  版本控制和回滚:能够轻松地回滚到文章的前一个版本。
  第三方集成:它可以与对讲机、Freshdesk、Microsoft和Zendesk等集成。
  类别级别的安全性:Document360具有多个级别的高级安全访问,可以覆盖所有场景。您可以提供不同级别的读者访问权限。
  分析和报告:提供有关客户地理位置、性能和搜索概述的详细报告的强大见解。
  3、Zendesk
  Zendesk被认为是最好的客户支持管理软件之一。但是,它可以处理的不仅仅是支持票和实时聊天。
  Zendesk还具有一个称为Zendesk指南的平滑的知识管理系统。你可以使用它来建立私人和公共知识库,为客户和员工服务。
  Zendesk指南是Zendesk软件套件的一部分。订阅Zendesk后,您可以访问其他几个工具。这使得它成为更适合大企业和企业的选择。
  Zendesk的最佳功能
  zendesk预览
  初学者友好界面:Zendesk具有一个非常适合初学者的用户界面。它允许你管理你的知识库没有复杂的问题。
  从一个地方管理一切:Zendesk还有一个全渠道仪表盘。有了它,你可以从一个地方管理你的知识库、支持票证、聊天、电话等等。
  所见即所得编辑器:Zendesk指南包括一个What You See Is What You Get样式编辑器。您可以使用此编辑器创建丰富的内容设计。
  内置CRM:Zendesk使用Sunshine,它自己的CRM允许你为客户提供个性化的体验。
  4、HelpJuice
  HelpJuice是一款现代且易于使用的知识库软件,非常适合初创企业和小型企业。沃尔玛等许多知名大品牌都在使用这款软件,比如周一这样的初创企业。
  与大多数其他知识库软件不同,HelpJuice更易于设置和管理。它还有一个创新的编辑器,允许您创建结构良好的内容,同时保留更改历史记录。
  帮助果汁的最佳功能
  helpjuice预览
  强大的编辑:工具集编辑器支持许多有用的功能。比如协作和控制访问。
  简易设置:HelpJuice很容易设置。实际上,你可以在几分钟内建立并运行一个知识库。
  主题和自定义:您还可以使用预先制作的主题自定义知识库的设计。
  第三方集成:HelpJuice还支持Slack、Zendesk、SalesForce和其他集成。
  你应该使用哪种知识付费系统源码?
  WordPress是为中小型企业和初创企业建立知识库的理想选择。你可以用它来建立各种公共和私人知识库。它支持数千个扩展功能的插件。而且非常实惠。
  对于大型企业和公司,我们建议使用像Zendesk这样的一体式解决方案。从一个地方管理你的客户服务和知识库是值得的。尤其是当你需要监督很多部门和团队的时候。
  当然,你不应该只相信我们的话。做更多的研究和探索更多的选择,为您的企业找到正确的知识管理系统。你也可以看看我们自己的知识管理产品。 我们有一系列工具,专门用来建立各种类型的知识库。

收起阅读 »

mui初相识02:hello mui

mui hellomui

通过HBuilder X创建H5+项目

demo_01:标题栏

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport"  
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
        <title>标题栏</title>  
        <link href="css/mui.min.css" rel="stylesheet" />  
    </head>  
    <body>  

        <header class="mui-bar mui-bar-nav">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">标题</h1>  
        </header>  

        <div class="mui-content">  
            hello mui  
        </div>  

        <script src="js/mui.min.js"></script>  
        <script type="text/javascript" charset="utf-8">  
            mui.init();  
        </script>  
    </body>  
</html>

demo_02:折叠面板

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title></title>  
    <link href="css/mui.min.css" rel="stylesheet"/>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">折叠面板</h1>  
    </header>  

    <div class="mui-content">  
        <ul class="mui-table-view">  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板1</a>  
                <div class="mui-collapse-content">  
                    <p>面板1子内容</p>  
                </div>  
            </li>  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板2</a>  
                    <div class="mui-collapse-content">  
                    <p>面板2子内容</p>  
                </div>  
            </li>  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板3</a>  
                <div class="mui-collapse-content">  
                    <p>面板3子内容</p>  
                </div>  
            </li>  
        </ul>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  
    </script>  
</body>  
</html>

demo_03:操作表

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title></title>  
    <link href="css/mui.min.css" rel="stylesheet"/>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">操作表</h1>  
    </header>  

    <div class="mui-content">  
        <a id="openSheet" class="mui-btn mui-btn-primary mui-btn-block" onclick="showMenu();">打开操作表</a>  
    </div>  

    <div id="sheet" class="mui-popover mui-popover-bottom mui-popover-action ">  
        <!-- 可选择菜单 -->  
        <ul class="mui-table-view">  
          <li class="mui-table-view-cell">  
            <a href="#">菜单1</a>  
          </li>  
          <li class="mui-table-view-cell">  
            <a href="#">菜单2</a>  
          </li>  
        </ul>  
        <!-- 取消菜单 -->  
        <ul class="mui-table-view">  
          <li class="mui-table-view-cell">  
            <a href="#sheet"><b>取消</b></a>  
          </li>  
        </ul>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  

        function showMenu(){  
            mui('#sheet').popover('toggle');  
        }  
    </script>  
</body>  
</html>

以上示例很简单,目标是了解mui的标签基础,顺利完成!

继续阅读 »

通过HBuilder X创建H5+项目

demo_01:标题栏

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="utf-8">  
        <meta name="viewport"  
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
        <title>标题栏</title>  
        <link href="css/mui.min.css" rel="stylesheet" />  
    </head>  
    <body>  

        <header class="mui-bar mui-bar-nav">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">标题</h1>  
        </header>  

        <div class="mui-content">  
            hello mui  
        </div>  

        <script src="js/mui.min.js"></script>  
        <script type="text/javascript" charset="utf-8">  
            mui.init();  
        </script>  
    </body>  
</html>

demo_02:折叠面板

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title></title>  
    <link href="css/mui.min.css" rel="stylesheet"/>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">折叠面板</h1>  
    </header>  

    <div class="mui-content">  
        <ul class="mui-table-view">  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板1</a>  
                <div class="mui-collapse-content">  
                    <p>面板1子内容</p>  
                </div>  
            </li>  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板2</a>  
                    <div class="mui-collapse-content">  
                    <p>面板2子内容</p>  
                </div>  
            </li>  
            <li class="mui-table-view-cell mui-collapse">  
                <a class="mui-navigate-right" href="#">面板3</a>  
                <div class="mui-collapse-content">  
                    <p>面板3子内容</p>  
                </div>  
            </li>  
        </ul>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  
    </script>  
</body>  
</html>

demo_03:操作表

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />  
    <title></title>  
    <link href="css/mui.min.css" rel="stylesheet"/>  
</head>  
<body>  

    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">操作表</h1>  
    </header>  

    <div class="mui-content">  
        <a id="openSheet" class="mui-btn mui-btn-primary mui-btn-block" onclick="showMenu();">打开操作表</a>  
    </div>  

    <div id="sheet" class="mui-popover mui-popover-bottom mui-popover-action ">  
        <!-- 可选择菜单 -->  
        <ul class="mui-table-view">  
          <li class="mui-table-view-cell">  
            <a href="#">菜单1</a>  
          </li>  
          <li class="mui-table-view-cell">  
            <a href="#">菜单2</a>  
          </li>  
        </ul>  
        <!-- 取消菜单 -->  
        <ul class="mui-table-view">  
          <li class="mui-table-view-cell">  
            <a href="#sheet"><b>取消</b></a>  
          </li>  
        </ul>  
    </div>  

    <script src="js/mui.min.js"></script>  
    <script type="text/javascript" charset="utf-8">  
        mui.init();  

        function showMenu(){  
            mui('#sheet').popover('toggle');  
        }  
    </script>  
</body>  
</html>

以上示例很简单,目标是了解mui的标签基础,顺利完成!

收起阅读 »

如何禁用X5内核浏览器的竖直滑块?

使用X5内核浏览器时,x5内核渲染页面的滚动条,会随着页面内容的长短显示滚动滑块。关于如何禁用滚动滑块,找了一天没找到解决方法,最后在腾讯浏览服务中心的文档(https://x5.tencent.com/docs/webview.html)中查看到可以禁用滚动滑块的接口:

//竖直快速滑块,设置null可去除  
mWebview.getX5WebViewExtension().setVerticalTrackDrawable (Drawable drawable)

但是mWebview是个全限定名为 com.tencent.smtt.sdk.WebView 的 java对象,想要在js中调用java方法setVerticalTrackDrawable,有两个方案:方案一、使用Native.js;方案二、自己写个uniapp原生插件

首先尝试了方案一,但最终失败。方案一的js代码如下:

失败原因是使用x5内核时,无法使用plus api获取到webview的nativeInstanceObject对象。这个应该是一个bug。

    var pages = getCurrentPages();    
    var page = pages[pages.length - 1];    

    var wv1 = page.$getAppWebview();  

    console.log('$getAppWebview', wv1)  
    // 尝试拿到webview的原生对象  
    let mWebView = wv1.nativeInstanceObject();    

    // 当不使用x5内核时 mWebView 可以获取到值,当使用x5内核时 mWebView 总是为 null  
    console.log('mWebView', mWebView)  
    var clazz = plus.android.invoke(mWebView, 'getClass')  
    console.log('mWebView class', clazz);  
    var name = plus.android.invoke(clazz, 'getName')  
    console.log('mWebView class name', name);  
    console.log('invoke getX5WebViewExtension')  
    var extension = plus.android.invoke(mWebView, 'getX5WebViewExtension')  
    console.log('getX5WebViewExtension result:', extension)  
    if (extension != null) {  
        plus.android.invoke(mWebView, 'setVerticalTrackDrawable', null)  
        console.log('invoke setVerticalTrackDrawable success')  
    }

目前暂时有效的方法:方案二

方案二,目前仅配适了安卓。关于如何自定义原生插件,见:https://nativesupport.dcloud.net.cn/NativePlugin/course/android
自定义的原生插件,java代码如下:

package io.dcloud.uniplugin;  

import android.util.Log;  
import android.view.View;  
import android.view.ViewGroup;  

import com.alibaba.fastjson.JSONObject;  
import com.taobao.weex.WXSDKInstance;  
import com.tencent.smtt.export.external.extension.interfaces.IX5WebViewExtension;  

import java.util.HashSet;  
import java.util.LinkedList;  
import java.util.Set;  

import io.dcloud.common.DHInterface.IApp;  
import io.dcloud.common.DHInterface.IWebAppRootView;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.feature.uniapp.annotation.UniJSMethod;  
import io.dcloud.feature.uniapp.bridge.UniJSCallback;  
import io.dcloud.feature.uniapp.common.UniModule;  
import io.dcloud.feature.weex.WeexInstanceMgr;  
import io.dcloud.feature.x5.X5WebView;  

public class X5Util extends UniModule {  
    private final static String TAG = "X5Util";  

    //run ui thread  
    @UniJSMethod(uiThread = true)  
    public void disableVerticalTrack2(JSONObject options, UniJSCallback callback) {  
        JSONObject jsonObject = this.disableVerticalTrack();  
        callback.invoke(jsonObject);  
    }  

    /**  
     * {@link io.dcloud.feature.weex.extend.PlusModule#exec(String, String)}  
     * @return  
     */  
    //run JS thread  
    @UniJSMethod (uiThread = false)  
    public JSONObject disableVerticalTrack(){  
        JSONObject data = new JSONObject();  
        data.put("code", "error");  
        try {  
            WXSDKInstance instance = this.mWXSDKInstance;  
            if (instance != null && instance.isDestroy()) {  
                // ignore  
                return data;  
            } else {  
                IWebview hostWebview = WeexInstanceMgr.self().findWebview(instance);  
                IApp iApp = hostWebview.obtainApp();  
                if (iApp != null) {  
                    IWebAppRootView iWebAppRootView = iApp.obtainWebAppRootView();  
                    View view = iWebAppRootView.obtainMainView();  
                    if (view instanceof ViewGroup) {  
                        ViewGroup vg = (ViewGroup) view;  
                        Set<X5WebView> set = new HashSet<>();  
                        getChildViewGroup(vg, set);  

                        for (X5WebView iWebview : set) {  
                            disableVerticalTrackDrawable(iWebview, data);  
                        }  
                    }  
                }  
            }  
        } catch (Exception e) {  
            Log.e(TAG, "disableVerticalTrack exception: ", e);  
        }  

        return data;  
    }  

    void disableVerticalTrackDrawable(X5WebView x5, JSONObject data) {  
        IX5WebViewExtension x5WebViewExtension = x5.getX5WebViewExtension();  
        if (x5WebViewExtension != null) {  
            x5WebViewExtension.setVerticalTrackDrawable(null);  
            data.put("code", "success");  
        }  
    }  
    void getChildViewGroup(ViewGroup vg, Set<X5WebView> dist) {  
        LinkedList<ViewGroup> list = new LinkedList<>();  
        list.add(vg);  

        while (!list.isEmpty()) {  
            vg = list.removeFirst();  
            int childCount = vg.getChildCount();  
            for (int i = 0; i < childCount; i++) {  
                View childAt = vg.getChildAt(i);  
                if (childAt instanceof X5WebView) {  
                    dist.add((X5WebView)childAt);  
                } else if (childAt instanceof ViewGroup) {  
                    list.addLast((ViewGroup) childAt);  
                }  
            }  
        }  
    }  
}

在js中调用:

    // #ifdef APP-PLUS  
    //禁用滑动按钮,这个插件会遍历所有view对象,找到X5WebView,然后禁用竖直快速滑块  
    const X5Util = uni.requireNativePlugin('X5Util')  
    X5Util.disableVerticalTrack()  
    // #endif

因为没有找到如何准确地获取当前页面webview对象(也就是X5WebView对象实例), 所以想了个笨办法:遍历IWebAppRootView对象的所有子孙view然后禁用竖直滑块。存在的问题有:1. 遍历查询存在性能问题; 2. 禁用的是所有的X5WebView的竖直滑块。

如果哪位大神知道如何准确地获取X5WebView请告知一下,这样就可以解决上述两个问题了。

继续阅读 »

使用X5内核浏览器时,x5内核渲染页面的滚动条,会随着页面内容的长短显示滚动滑块。关于如何禁用滚动滑块,找了一天没找到解决方法,最后在腾讯浏览服务中心的文档(https://x5.tencent.com/docs/webview.html)中查看到可以禁用滚动滑块的接口:

//竖直快速滑块,设置null可去除  
mWebview.getX5WebViewExtension().setVerticalTrackDrawable (Drawable drawable)

但是mWebview是个全限定名为 com.tencent.smtt.sdk.WebView 的 java对象,想要在js中调用java方法setVerticalTrackDrawable,有两个方案:方案一、使用Native.js;方案二、自己写个uniapp原生插件

首先尝试了方案一,但最终失败。方案一的js代码如下:

失败原因是使用x5内核时,无法使用plus api获取到webview的nativeInstanceObject对象。这个应该是一个bug。

    var pages = getCurrentPages();    
    var page = pages[pages.length - 1];    

    var wv1 = page.$getAppWebview();  

    console.log('$getAppWebview', wv1)  
    // 尝试拿到webview的原生对象  
    let mWebView = wv1.nativeInstanceObject();    

    // 当不使用x5内核时 mWebView 可以获取到值,当使用x5内核时 mWebView 总是为 null  
    console.log('mWebView', mWebView)  
    var clazz = plus.android.invoke(mWebView, 'getClass')  
    console.log('mWebView class', clazz);  
    var name = plus.android.invoke(clazz, 'getName')  
    console.log('mWebView class name', name);  
    console.log('invoke getX5WebViewExtension')  
    var extension = plus.android.invoke(mWebView, 'getX5WebViewExtension')  
    console.log('getX5WebViewExtension result:', extension)  
    if (extension != null) {  
        plus.android.invoke(mWebView, 'setVerticalTrackDrawable', null)  
        console.log('invoke setVerticalTrackDrawable success')  
    }

目前暂时有效的方法:方案二

方案二,目前仅配适了安卓。关于如何自定义原生插件,见:https://nativesupport.dcloud.net.cn/NativePlugin/course/android
自定义的原生插件,java代码如下:

package io.dcloud.uniplugin;  

import android.util.Log;  
import android.view.View;  
import android.view.ViewGroup;  

import com.alibaba.fastjson.JSONObject;  
import com.taobao.weex.WXSDKInstance;  
import com.tencent.smtt.export.external.extension.interfaces.IX5WebViewExtension;  

import java.util.HashSet;  
import java.util.LinkedList;  
import java.util.Set;  

import io.dcloud.common.DHInterface.IApp;  
import io.dcloud.common.DHInterface.IWebAppRootView;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.feature.uniapp.annotation.UniJSMethod;  
import io.dcloud.feature.uniapp.bridge.UniJSCallback;  
import io.dcloud.feature.uniapp.common.UniModule;  
import io.dcloud.feature.weex.WeexInstanceMgr;  
import io.dcloud.feature.x5.X5WebView;  

public class X5Util extends UniModule {  
    private final static String TAG = "X5Util";  

    //run ui thread  
    @UniJSMethod(uiThread = true)  
    public void disableVerticalTrack2(JSONObject options, UniJSCallback callback) {  
        JSONObject jsonObject = this.disableVerticalTrack();  
        callback.invoke(jsonObject);  
    }  

    /**  
     * {@link io.dcloud.feature.weex.extend.PlusModule#exec(String, String)}  
     * @return  
     */  
    //run JS thread  
    @UniJSMethod (uiThread = false)  
    public JSONObject disableVerticalTrack(){  
        JSONObject data = new JSONObject();  
        data.put("code", "error");  
        try {  
            WXSDKInstance instance = this.mWXSDKInstance;  
            if (instance != null && instance.isDestroy()) {  
                // ignore  
                return data;  
            } else {  
                IWebview hostWebview = WeexInstanceMgr.self().findWebview(instance);  
                IApp iApp = hostWebview.obtainApp();  
                if (iApp != null) {  
                    IWebAppRootView iWebAppRootView = iApp.obtainWebAppRootView();  
                    View view = iWebAppRootView.obtainMainView();  
                    if (view instanceof ViewGroup) {  
                        ViewGroup vg = (ViewGroup) view;  
                        Set<X5WebView> set = new HashSet<>();  
                        getChildViewGroup(vg, set);  

                        for (X5WebView iWebview : set) {  
                            disableVerticalTrackDrawable(iWebview, data);  
                        }  
                    }  
                }  
            }  
        } catch (Exception e) {  
            Log.e(TAG, "disableVerticalTrack exception: ", e);  
        }  

        return data;  
    }  

    void disableVerticalTrackDrawable(X5WebView x5, JSONObject data) {  
        IX5WebViewExtension x5WebViewExtension = x5.getX5WebViewExtension();  
        if (x5WebViewExtension != null) {  
            x5WebViewExtension.setVerticalTrackDrawable(null);  
            data.put("code", "success");  
        }  
    }  
    void getChildViewGroup(ViewGroup vg, Set<X5WebView> dist) {  
        LinkedList<ViewGroup> list = new LinkedList<>();  
        list.add(vg);  

        while (!list.isEmpty()) {  
            vg = list.removeFirst();  
            int childCount = vg.getChildCount();  
            for (int i = 0; i < childCount; i++) {  
                View childAt = vg.getChildAt(i);  
                if (childAt instanceof X5WebView) {  
                    dist.add((X5WebView)childAt);  
                } else if (childAt instanceof ViewGroup) {  
                    list.addLast((ViewGroup) childAt);  
                }  
            }  
        }  
    }  
}

在js中调用:

    // #ifdef APP-PLUS  
    //禁用滑动按钮,这个插件会遍历所有view对象,找到X5WebView,然后禁用竖直快速滑块  
    const X5Util = uni.requireNativePlugin('X5Util')  
    X5Util.disableVerticalTrack()  
    // #endif

因为没有找到如何准确地获取当前页面webview对象(也就是X5WebView对象实例), 所以想了个笨办法:遍历IWebAppRootView对象的所有子孙view然后禁用竖直滑块。存在的问题有:1. 遍历查询存在性能问题; 2. 禁用的是所有的X5WebView的竖直滑块。

如果哪位大神知道如何准确地获取X5WebView请告知一下,这样就可以解决上述两个问题了。

收起阅读 »