HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

授权弹出误点或者测试点击拒绝授权后无法在弹出授权窗口auth deny终极解决方案

小程序 uniapp

先贴代码
视图层部分

<template>  
    <view>  
        <button @tap="startRecord">开始录音</button>  
        <button @tap="endRecord">停止录音</button>  
        <button @tap="playVoice">播放录音</button>  
        <button @tap="uploadvoice">上传文件</button>  
    </view>  
</template>

js部分

<script>  
    let recorderManager = uni.getRecorderManager();  
    let innerAudioContext = uni.createInnerAudioContext();  

    innerAudioContext.autoplay = true;  

    export default {  
        data: {  
            text: 'uni-app',  
            voicePath: ''  
        },  
        onLoad() {  

        },  
        methods: {  
            uploadvoice() {  
                var target = this  
                let haslogin = false  
                let accesstoken = ''  
                try {  
                    accesstoken = uni.getStorageSync('accesstoken');  
                    if (accesstoken) {  

                        haslogin = true  
                    } else {  
                        uni.showToast({  
                            title: '你还没有登录',  
                            icon: 'none'  
                        })  
                        return  
                    }  

                } catch (e) {  
                    // error  
                }  
                if (target.voicePath == '') {  
                    uni.showToast({  
                        title: '你还没录音',  
                        icon: 'none'  
                    })  
                    return  
                }  
                //上传音频文件  
                uni.uploadFile({  
                    url: target.$api.UploadvoiceFile, //接口地址  
                    filePath: target.voicePath, //临时音频文件  
                    name: 'appfile',  
                    formData: {  
                        'accesstoken': accesstoken,  
                        'viewfee': 5 //付费金额  
                    },  
                    success: (uploadFileRes) => {  
                        console.log(uploadFileRes.data);  
                    }  
                });  
            },  
            startRecord() {  
                //录音先判断是否有录音权限  
                uni.getSetting({  
                    success(res) {  
                        //获取设置成功  
                        console.log(res.authSetting)  
                        if (!res.authSetting['scope.record']) {  
                            //如果没开启录音麦克风权限提示打开  
                            uni.openSetting({  
                                success(res) {  
                                    //打开成功,提示获取录音权限  
                                    console.log(res.authSetting)  
                                    uni.authorize({  
                                        scope: 'scope.record',  
                                        success() {  
                                            //录音权限获取成功开始录音  
                                            console.log('开始录音2');  

                                            recorderManager.start({  
                                                format: 'mp3',  
                                                duration: 600000  
                                            });  
                                        },  
                                        complete() {  
                                            console.log('调用接口完成');  
                                        },  
                                        fail(res) {  
                                            uni.showToast({  
                                                title: '您拒绝了应用获取麦克风权限',  
                                                icon: 'none'  
                                            })  
                                            console.log(res);  
                                        }  
                                    })  
                                }  
                            });  
                        } else {  
                            recorderManager.start({  
                                format: 'mp3',  
                                duration: 600000  
                            });  
                        }  
                    }  
                })  
            },  
            endRecord() {  
                //默认官方把此onStop方法写onload里,但是如果被拒绝的状态后续录音成功也会不执行停止回调,onload只在加载状态执行一次  
                //保险起见,用户后续开启麦克风权限还能正常回调写停止方法里  
                let self = this;  
                recorderManager.onStop(function(res) {  
                    console.log('recorder stop' + JSON.stringify(res));  
                    self.voicePath = res.tempFilePath;  
                });  
                console.log('录音结束');  
                recorderManager.stop();  

            },  
            playVoice() {  
                console.log('播放录音');  

                if (this.voicePath) {  
                    innerAudioContext.src = this.voicePath;  
                    innerAudioContext.play();  
                }  
            }  
        }  
    }  
</script>

音频文件设置了mp3格式,默认10分钟
上面demo是根据官方录音功能改进的,主要是小程序端,startRecord()录音方法里写了如何解决不弹出授权问题,demo可以直接使用测试,包含上传音频文件方法

php接收音频文件
function uploavoice() {
$message = array ();
$accesstoken = $_POST ['accesstoken'];

    $user = $this->check_token ( $accesstoken );  
    if ($user) {  
        if (! empty ( $_FILES ['appfile'] )) {  
            // 获取扩展名  
            $pathinfo = pathinfo ( $_FILES ['appfile'] ['name'] );  

            $exename = strtolower ( $pathinfo ['extension'] );  
            if ($exename != 'mp3' ) {  
                $message ['code'] = 2001;  
                $message ['data'] = null;  
                $message ['message'] = "音频扩展不支持";  
                echo json_encode ( $message );  
                exit ();  
            }  
            $imageSavePath = 'data/weixinrecord/' . uniqid () . '.' . $exename;  
            if (move_uploaded_file ( $_FILES ['appfile'] ['tmp_name'], FCPATH . $imageSavePath )) {  
                $message ['code'] = 2000;  
                $message ['data'] = SITE_URL . $imageSavePath;  
                $message ['message'] = "音频上传成功";  
                echo json_encode ( $message );  
                exit ();  
            }  
        } else {  
            $message ['code'] = 2002;  
            $message ['data'] = null;  
            $message ['message'] = "请上传音频文件";  
            echo json_encode ( $message );  
            exit ();  
        }  
    } else {  
        $message ['code'] = 2088;  
        $message ['data'] = null;  
        $message ['message'] = "用户信息过期";  
        echo json_encode ( $message );  
        exit ();  
    }  
}  
继续阅读 »

先贴代码
视图层部分

<template>  
    <view>  
        <button @tap="startRecord">开始录音</button>  
        <button @tap="endRecord">停止录音</button>  
        <button @tap="playVoice">播放录音</button>  
        <button @tap="uploadvoice">上传文件</button>  
    </view>  
</template>

js部分

<script>  
    let recorderManager = uni.getRecorderManager();  
    let innerAudioContext = uni.createInnerAudioContext();  

    innerAudioContext.autoplay = true;  

    export default {  
        data: {  
            text: 'uni-app',  
            voicePath: ''  
        },  
        onLoad() {  

        },  
        methods: {  
            uploadvoice() {  
                var target = this  
                let haslogin = false  
                let accesstoken = ''  
                try {  
                    accesstoken = uni.getStorageSync('accesstoken');  
                    if (accesstoken) {  

                        haslogin = true  
                    } else {  
                        uni.showToast({  
                            title: '你还没有登录',  
                            icon: 'none'  
                        })  
                        return  
                    }  

                } catch (e) {  
                    // error  
                }  
                if (target.voicePath == '') {  
                    uni.showToast({  
                        title: '你还没录音',  
                        icon: 'none'  
                    })  
                    return  
                }  
                //上传音频文件  
                uni.uploadFile({  
                    url: target.$api.UploadvoiceFile, //接口地址  
                    filePath: target.voicePath, //临时音频文件  
                    name: 'appfile',  
                    formData: {  
                        'accesstoken': accesstoken,  
                        'viewfee': 5 //付费金额  
                    },  
                    success: (uploadFileRes) => {  
                        console.log(uploadFileRes.data);  
                    }  
                });  
            },  
            startRecord() {  
                //录音先判断是否有录音权限  
                uni.getSetting({  
                    success(res) {  
                        //获取设置成功  
                        console.log(res.authSetting)  
                        if (!res.authSetting['scope.record']) {  
                            //如果没开启录音麦克风权限提示打开  
                            uni.openSetting({  
                                success(res) {  
                                    //打开成功,提示获取录音权限  
                                    console.log(res.authSetting)  
                                    uni.authorize({  
                                        scope: 'scope.record',  
                                        success() {  
                                            //录音权限获取成功开始录音  
                                            console.log('开始录音2');  

                                            recorderManager.start({  
                                                format: 'mp3',  
                                                duration: 600000  
                                            });  
                                        },  
                                        complete() {  
                                            console.log('调用接口完成');  
                                        },  
                                        fail(res) {  
                                            uni.showToast({  
                                                title: '您拒绝了应用获取麦克风权限',  
                                                icon: 'none'  
                                            })  
                                            console.log(res);  
                                        }  
                                    })  
                                }  
                            });  
                        } else {  
                            recorderManager.start({  
                                format: 'mp3',  
                                duration: 600000  
                            });  
                        }  
                    }  
                })  
            },  
            endRecord() {  
                //默认官方把此onStop方法写onload里,但是如果被拒绝的状态后续录音成功也会不执行停止回调,onload只在加载状态执行一次  
                //保险起见,用户后续开启麦克风权限还能正常回调写停止方法里  
                let self = this;  
                recorderManager.onStop(function(res) {  
                    console.log('recorder stop' + JSON.stringify(res));  
                    self.voicePath = res.tempFilePath;  
                });  
                console.log('录音结束');  
                recorderManager.stop();  

            },  
            playVoice() {  
                console.log('播放录音');  

                if (this.voicePath) {  
                    innerAudioContext.src = this.voicePath;  
                    innerAudioContext.play();  
                }  
            }  
        }  
    }  
</script>

音频文件设置了mp3格式,默认10分钟
上面demo是根据官方录音功能改进的,主要是小程序端,startRecord()录音方法里写了如何解决不弹出授权问题,demo可以直接使用测试,包含上传音频文件方法

php接收音频文件
function uploavoice() {
$message = array ();
$accesstoken = $_POST ['accesstoken'];

    $user = $this->check_token ( $accesstoken );  
    if ($user) {  
        if (! empty ( $_FILES ['appfile'] )) {  
            // 获取扩展名  
            $pathinfo = pathinfo ( $_FILES ['appfile'] ['name'] );  

            $exename = strtolower ( $pathinfo ['extension'] );  
            if ($exename != 'mp3' ) {  
                $message ['code'] = 2001;  
                $message ['data'] = null;  
                $message ['message'] = "音频扩展不支持";  
                echo json_encode ( $message );  
                exit ();  
            }  
            $imageSavePath = 'data/weixinrecord/' . uniqid () . '.' . $exename;  
            if (move_uploaded_file ( $_FILES ['appfile'] ['tmp_name'], FCPATH . $imageSavePath )) {  
                $message ['code'] = 2000;  
                $message ['data'] = SITE_URL . $imageSavePath;  
                $message ['message'] = "音频上传成功";  
                echo json_encode ( $message );  
                exit ();  
            }  
        } else {  
            $message ['code'] = 2002;  
            $message ['data'] = null;  
            $message ['message'] = "请上传音频文件";  
            echo json_encode ( $message );  
            exit ();  
        }  
    } else {  
        $message ['code'] = 2088;  
        $message ['data'] = null;  
        $message ['message'] = "用户信息过期";  
        echo json_encode ( $message );  
        exit ();  
    }  
}  
收起阅读 »

无条件支持Dcloud,希望Dcloud越走越远

uniapp DCloud

在客户端产品开发没选型之前,北京一个客户建议用apicloud开发,我当时建议是uni-app,也算是眼缘吧,然后试着把老的vue项目重构成uni-app,并编译了百度小程序,头条小程序,微信小程序,体验流畅不卡,唯独app端还没做适配,估计改起来也不是很大工作量,uni-app开发文档写的还可以的,国产应该支持,本人就是做国产开源社区的,Dcloud是我唯一见过能快速编译到多平台且兼容性不错的开发工具,确实提升了开发速度。
有个小建议:
插件市场里的有些作者有点不负责,导致项目会开发可能会返工,他们有的开发完成没标注是否适合app端或者兼容到哪个平台小程序或者哪些机型有问题。
希望增加一个选择项,把已知问题让作者列出来,或者在插件入口给此插件单独开通一个话题讨论,这样利于知识沉淀,后来者也可以少踩坑,直接可以找到现成问题并解决。

继续阅读 »

在客户端产品开发没选型之前,北京一个客户建议用apicloud开发,我当时建议是uni-app,也算是眼缘吧,然后试着把老的vue项目重构成uni-app,并编译了百度小程序,头条小程序,微信小程序,体验流畅不卡,唯独app端还没做适配,估计改起来也不是很大工作量,uni-app开发文档写的还可以的,国产应该支持,本人就是做国产开源社区的,Dcloud是我唯一见过能快速编译到多平台且兼容性不错的开发工具,确实提升了开发速度。
有个小建议:
插件市场里的有些作者有点不负责,导致项目会开发可能会返工,他们有的开发完成没标注是否适合app端或者兼容到哪个平台小程序或者哪些机型有问题。
希望增加一个选择项,把已知问题让作者列出来,或者在插件入口给此插件单独开通一个话题讨论,这样利于知识沉淀,后来者也可以少踩坑,直接可以找到现成问题并解决。

收起阅读 »

hbuilderX编辑器貌似有点low

HBuilder

HBuilderX编辑器下载后,点击运行,出现卡死的界面。

我的是win10 64位机器。且发现官方给的windows下载版本是32位的。。

官方是不是有点太草率了。连一个编辑器安装都出现各种奇葩问题。。。

HBuilderX编辑器下载后,点击运行,出现卡死的界面。

我的是win10 64位机器。且发现官方给的windows下载版本是32位的。。

官方是不是有点太草率了。连一个编辑器安装都出现各种奇葩问题。。。

uni-app使用总结

uniapp

本文主要uni-app开发项目遇到的一些问题的总结,关于uni-app具体的介绍和使用请查看uni-app官网
笔者编译使用的HBuilderX版本为2.4.2.20191115。

样式

1.1 无法设置背景图片

直接使用图片,并且图片大于40kb,官网有说明,主要是针对web之外的平台存在限制。

1.2 根据官方文档设置窗口背景颜色无效(在pages.json设置窗口默认backgroundColor或者单独设置窗口style中的backgroundColor都没有效果)

在页面中设置

page {  
    background-color: #ccc;  
}

1.3 设置页面navigationBarShadow导航栏阴影无效

关于导航栏的一些问题只能自己编写自定义组件或者nvue组件来解决,具体可以阅读[官方文档](uni-app官网

1.4 使用flex布局,如果嵌套了scroll-view,flex布局会失效

<view class="content">  
        <view class="fixed-item"></view>  
        <view class="flex-item">  
            <scroll-view></scroll-view>  
        </view>  
 </view>
.content {  
    display: flex;  
    flex-direction: column;  
    align-items: center;  
    justify-content: center;  
    position: absolute;  
    top: 0;  
    bottom: 0;  
    width: 100%;  
}  

.fixed-item {  
    width: 100%;  
    height: 100px;  
    background-color: #007AFF;  
}  
.flex-item {  
    flex: 1;     
}

1.5 原生端给组件设置margin无效,margin不会出现合并的情况(使用flex时)

原因未知

vue语法

2.1 在原生端App.vue没有vue的生命周期,但是有页面的生命周期,但是web端两者都存在

原因未知

2.2 vuex在原生端是不支持使用命名空间的,但是在web端是支持的

原因未知

2.3 $attrs无效

2.4 不支持vue-router

使用uni.navigateTo和uni.switchTab代替router.push方法实现页面跳转,uni.swtichTab在跳转tab页面时使用,但是不能再tab页面使用uni.redirectTo,不然跳转的目标页面也会出现底部tab栏

2.5 app端不支持v-slot传值

原因未知

2.6 v-for方法遍历数字时,web端从1开始,但是原生端从0开始

原因未知

2.7 原生端ref无法获取uni原生组件,web端可行

原因未知

2.8 onShow第一次触发时,$refs的内容为空

在this.$nextTick(() => {})中使用$refs

组件与接口

3.1 组件不可控,无法通过event修改组件的显示值(input、switch组件)

Web端可以通过$refs设置(switch通过$refs获取然后设置switchChecked的值),但原生端无法通过$refs获取组件无法使用这个方法实现

3.2 uni-request无法设置cookie

将cookie数据放在header字段中

3.3 监听subNVue的显示和隐藏

SubNVue无法监听显示和隐藏,显示可以在调用show时在回调函数中触发。可以通过下列方式监听:

const qrcode = uni.getSubNVueById(‘qr_code’)   
qrcode.addEventListener(‘hide’, () => {   
    console.log(‘hideQrCode’)   
})   
qrcode.addEventListener('show', () => {   
    console.log(‘showQrCode’)   
}) 

3.4 disableScroll: true无法禁止页面整体滚动

设置

“App-plus”: {  
    “bounce”: “none”  
}

3.5 使用subNVues,点击遮罩将无法关闭popup

style样式中的background不能设置为除transparent的其他值

3.6 CanvasContext.draw在App中无法执行回调函数

在vue页面中可以使用,在nvue中无法执行回调函数

3.7 修改导航栏

var wv = this.$mp.page.$getAppWebview();  
        wv.setStyle({    
            titleNView: {    
                “buttons”: [  
                {  
                  “fontSrc”: “/static/uni.ttf”,  
                  “fontSize”: “14”,  
                  “color”: “#FFFFFF”,  
                  “text”: `\ue333 ${address.city}`,  
                  "background": "rgba(0,0,0,0)”,  
                  "float": “left”,  
                  “width”: “96px”  
                }  
              ]  
            }    
        });  

具体可以参考[官网](uni-app在App端动态修改原生导航栏 - DCloud问答

其他

  1. 在原生端不同平台也会存在差异。
  2. 针对很多比较奇怪的问题可以选择重新编译。
继续阅读 »

本文主要uni-app开发项目遇到的一些问题的总结,关于uni-app具体的介绍和使用请查看uni-app官网
笔者编译使用的HBuilderX版本为2.4.2.20191115。

样式

1.1 无法设置背景图片

直接使用图片,并且图片大于40kb,官网有说明,主要是针对web之外的平台存在限制。

1.2 根据官方文档设置窗口背景颜色无效(在pages.json设置窗口默认backgroundColor或者单独设置窗口style中的backgroundColor都没有效果)

在页面中设置

page {  
    background-color: #ccc;  
}

1.3 设置页面navigationBarShadow导航栏阴影无效

关于导航栏的一些问题只能自己编写自定义组件或者nvue组件来解决,具体可以阅读[官方文档](uni-app官网

1.4 使用flex布局,如果嵌套了scroll-view,flex布局会失效

<view class="content">  
        <view class="fixed-item"></view>  
        <view class="flex-item">  
            <scroll-view></scroll-view>  
        </view>  
 </view>
.content {  
    display: flex;  
    flex-direction: column;  
    align-items: center;  
    justify-content: center;  
    position: absolute;  
    top: 0;  
    bottom: 0;  
    width: 100%;  
}  

.fixed-item {  
    width: 100%;  
    height: 100px;  
    background-color: #007AFF;  
}  
.flex-item {  
    flex: 1;     
}

1.5 原生端给组件设置margin无效,margin不会出现合并的情况(使用flex时)

原因未知

vue语法

2.1 在原生端App.vue没有vue的生命周期,但是有页面的生命周期,但是web端两者都存在

原因未知

2.2 vuex在原生端是不支持使用命名空间的,但是在web端是支持的

原因未知

2.3 $attrs无效

2.4 不支持vue-router

使用uni.navigateTo和uni.switchTab代替router.push方法实现页面跳转,uni.swtichTab在跳转tab页面时使用,但是不能再tab页面使用uni.redirectTo,不然跳转的目标页面也会出现底部tab栏

2.5 app端不支持v-slot传值

原因未知

2.6 v-for方法遍历数字时,web端从1开始,但是原生端从0开始

原因未知

2.7 原生端ref无法获取uni原生组件,web端可行

原因未知

2.8 onShow第一次触发时,$refs的内容为空

在this.$nextTick(() => {})中使用$refs

组件与接口

3.1 组件不可控,无法通过event修改组件的显示值(input、switch组件)

Web端可以通过$refs设置(switch通过$refs获取然后设置switchChecked的值),但原生端无法通过$refs获取组件无法使用这个方法实现

3.2 uni-request无法设置cookie

将cookie数据放在header字段中

3.3 监听subNVue的显示和隐藏

SubNVue无法监听显示和隐藏,显示可以在调用show时在回调函数中触发。可以通过下列方式监听:

const qrcode = uni.getSubNVueById(‘qr_code’)   
qrcode.addEventListener(‘hide’, () => {   
    console.log(‘hideQrCode’)   
})   
qrcode.addEventListener('show', () => {   
    console.log(‘showQrCode’)   
}) 

3.4 disableScroll: true无法禁止页面整体滚动

设置

“App-plus”: {  
    “bounce”: “none”  
}

3.5 使用subNVues,点击遮罩将无法关闭popup

style样式中的background不能设置为除transparent的其他值

3.6 CanvasContext.draw在App中无法执行回调函数

在vue页面中可以使用,在nvue中无法执行回调函数

3.7 修改导航栏

var wv = this.$mp.page.$getAppWebview();  
        wv.setStyle({    
            titleNView: {    
                “buttons”: [  
                {  
                  “fontSrc”: “/static/uni.ttf”,  
                  “fontSize”: “14”,  
                  “color”: “#FFFFFF”,  
                  “text”: `\ue333 ${address.city}`,  
                  "background": "rgba(0,0,0,0)”,  
                  "float": “left”,  
                  “width”: “96px”  
                }  
              ]  
            }    
        });  

具体可以参考[官网](uni-app在App端动态修改原生导航栏 - DCloud问答

其他

  1. 在原生端不同平台也会存在差异。
  2. 针对很多比较奇怪的问题可以选择重新编译。
收起阅读 »

uni-app商业级应用实战!

uniapp uni_app

需要资料加企鹅:3340563142

需要资料加企鹅:3340563142

加载字体文件过大的问题,不是icon,而是fontFamily

App iconfont

目前在写app时遇到ttf文件加载太大了,放本地打包比app还大(差不多了),放服务器加载太慢,于是乎想要看看能不能把这个ttf字体包搞小一点。
首先是加载字体文件的loadFontFace,插件市场也有很好的字体引入插件。但是各种字体文件的通病就是ttf字体包很小的不支持汉字,支持全汉字等的文件比较大(动不动就支持拉丁,日,韩,朝等各种文字,还有各种生僻字,总共5-6万字的字体库)。
后来有人说,这些字体在html中可以这样加载,但是这样加载是不是相当于从字体包中挑出相应的文字重新生成一个文件,这样不能满足我的需求,因为我的文字有新闻类的文章,总不能每次都请求一个字体文件啊(或许这样也可以)。
然后就是类似于阿里icon的在线字体生成,我觉得是不是这样的也许能满足我的需求,直接把汉字常用字生成对应的字体文件,但是发现文字数量有限制,并不现实(某些文本确定的地方这样生成的文件更合适)。
最后,找到了这个将原有字体文件多余文字抽离,精简字体包的方法,精简ttf的方法,按照这个教程,我实现了3500常用字,7600常用字和8000常用字的字体包的生成。
为什么要发这个帖子,效果太显著了,有下面几点,1、我使用的思源宋体,原otf大概23M,精简版ttf只有3M左右(7600字),加载速度大大提升;2、最后的精简字体方法中有坑:首先,FontCreator工具收费,仅有win版,FontSubsetGUI工具仅支持ttf,对于otf支持度不够等;3、最后就是FontSubsetGUI的下载源收费,而且需要.net环境的依赖,各种字体文件大家随便去各种字体网站找一找都有下载的。
##############
刚又发现了,Nvue中引入的字体没有生效,weex引入,loadFontface引入都不行,css引入还是不行oo原来路径的问题啊。比如我的代码‘/static/font/siyuanSimSun.ttf’这个路径,还有就是引入的fontfamily不要用‘string’,倒是和Vue页面内的相反。
var domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "siyuanSimSun",
'src': "url('/static/font/siyuanSimSun.ttf')"
});
##############
刚测试苹果机,注意一下ttf的路径,原安卓的路径(/static/font/siyuanSimSun.ttf)比较随意,倒是iOS相对路径(./static/font/siyuanSimSun.ttf)一定要写准确,也可以转本地绝对路径'src': 'url("'+"file:/" + plus.io.convertLocalFileSystemURL("_www/static/font/siyuanSimSun.ttf")+'")'。

##############
又发现一个问题,之前的环境都是V3引擎,现在我发现关闭V3后Nvue里的字体引入方式失效了,我会重新找方案。

##############
最近把字体的引入搞差不多了,有两个点,V3没有问题,尽量不要在app里引Vue页面的字体,分别引入。Nvue比较好一点,但是不论Nue还是Vue的input,textarea,在安卓端都没办法把内容字体使用指定字体。

##############
本身随笔性质的,也没描述太清楚,既然官方大佬来了,我要把这个字体精简工具放出来,里边有好几个,具体是什么我也不懂,反正按上边的教程,很傻瓜的。

##############
突然发现忘记了一点,我在app.vue里放了一个全局字体
.siyuanSimSun{
font-family: siyuanSimSun; //这里不要带引号
}

继续阅读 »

目前在写app时遇到ttf文件加载太大了,放本地打包比app还大(差不多了),放服务器加载太慢,于是乎想要看看能不能把这个ttf字体包搞小一点。
首先是加载字体文件的loadFontFace,插件市场也有很好的字体引入插件。但是各种字体文件的通病就是ttf字体包很小的不支持汉字,支持全汉字等的文件比较大(动不动就支持拉丁,日,韩,朝等各种文字,还有各种生僻字,总共5-6万字的字体库)。
后来有人说,这些字体在html中可以这样加载,但是这样加载是不是相当于从字体包中挑出相应的文字重新生成一个文件,这样不能满足我的需求,因为我的文字有新闻类的文章,总不能每次都请求一个字体文件啊(或许这样也可以)。
然后就是类似于阿里icon的在线字体生成,我觉得是不是这样的也许能满足我的需求,直接把汉字常用字生成对应的字体文件,但是发现文字数量有限制,并不现实(某些文本确定的地方这样生成的文件更合适)。
最后,找到了这个将原有字体文件多余文字抽离,精简字体包的方法,精简ttf的方法,按照这个教程,我实现了3500常用字,7600常用字和8000常用字的字体包的生成。
为什么要发这个帖子,效果太显著了,有下面几点,1、我使用的思源宋体,原otf大概23M,精简版ttf只有3M左右(7600字),加载速度大大提升;2、最后的精简字体方法中有坑:首先,FontCreator工具收费,仅有win版,FontSubsetGUI工具仅支持ttf,对于otf支持度不够等;3、最后就是FontSubsetGUI的下载源收费,而且需要.net环境的依赖,各种字体文件大家随便去各种字体网站找一找都有下载的。
##############
刚又发现了,Nvue中引入的字体没有生效,weex引入,loadFontface引入都不行,css引入还是不行oo原来路径的问题啊。比如我的代码‘/static/font/siyuanSimSun.ttf’这个路径,还有就是引入的fontfamily不要用‘string’,倒是和Vue页面内的相反。
var domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "siyuanSimSun",
'src': "url('/static/font/siyuanSimSun.ttf')"
});
##############
刚测试苹果机,注意一下ttf的路径,原安卓的路径(/static/font/siyuanSimSun.ttf)比较随意,倒是iOS相对路径(./static/font/siyuanSimSun.ttf)一定要写准确,也可以转本地绝对路径'src': 'url("'+"file:/" + plus.io.convertLocalFileSystemURL("_www/static/font/siyuanSimSun.ttf")+'")'。

##############
又发现一个问题,之前的环境都是V3引擎,现在我发现关闭V3后Nvue里的字体引入方式失效了,我会重新找方案。

##############
最近把字体的引入搞差不多了,有两个点,V3没有问题,尽量不要在app里引Vue页面的字体,分别引入。Nvue比较好一点,但是不论Nue还是Vue的input,textarea,在安卓端都没办法把内容字体使用指定字体。

##############
本身随笔性质的,也没描述太清楚,既然官方大佬来了,我要把这个字体精简工具放出来,里边有好几个,具体是什么我也不懂,反正按上边的教程,很傻瓜的。

##############
突然发现忘记了一点,我在app.vue里放了一个全局字体
.siyuanSimSun{
font-family: siyuanSimSun; //这里不要带引号
}

收起阅读 »

地点搜索功能分享,用来弥补uniapp 使用uni.chooseLocation无法选择其他城市地点的不足

自己在使用uni.chooseLocation时发现只能搜索到当前城市的地点,无法搜索到其他城市的地点,这样导致实现地点搜索时限制很多,因此分享给出自己结合HTML5+实现的地点选择。

<template>  
    <view class="full-wrap">  
        <view id="map">  
        </view>  
        <view class="now-pos" @tap="searchPos(posCity)">  
            定位城市:{{posCity}}  
        </view>  
        <view class="choose-city flex-box">  
            <view class="city-item flex-box">  
                <text class="city flex-box" @tap="chooseCity">{{nowCity}}</text>  
                <image src="../../static/down.png" class="down-ico"></image>  
            </view>  
            <view class="search-box">  
                <input placeholder="搜索地点" type="text" name="input" @input="searchPos(nowCity)" style="width: 100% ;" v-model="searchText"></input>  
            </view>  
        </view>  
        <view class="search-result-wrap">  
            <view class="cu-item padding" :key="index" v-for="(item,index) in cityList" @click="chooseHandle(item)">  
                <view class="search-result-content">  
                    <text class="list-city-name">{{item.name}}</text>  
                    <text class="list-city-address text-gray">{{item.city+item.address}}</text>  
                </view>  
            </view>  
        </view>  
        <!-- 城市选择 该插件从插件市场下载 -->  
        <mpvue-picker :themeColor="themeColor" ref="mpvuePicker" :mode="mode" :deepLength="deepLength" :pickerValueDefault="pickerValueDefault"  
         @onConfirm="onConfirm" @="" :pickerValueArray="pickerValueArray"></mpvue-picker>  
    </view>  
</template>  

<script>  
    import cityData from '../../commonjs/city.data.js'  
    import mpvuePicker from '../../components/mpvue-picker/mpvuePicker.vue'  
    export default {  
        components: {  
            mpvuePicker  
        },  
        data() {  
            return {  
                lat: " 39.915",  
                lng: '116.404',  
                posCity: "",  
                nowCity: '',  
                searchText: "",  
                cityList: [],  
                pickerValueDefault: [0, 0],  
                themeColor: '#007AFF',  
                mode: 'multiLinkageSelector',  
                deepLength: 2,  
                pickerValueArray: cityData  
            }  
        },  
        mounted() {  
            // 默认以当前位置为中心  
            uni.getLocation({  
                geocode: 'true',  
                success: res => {  
                    this.posCity = res.address.city;  
                    this.nowCity = res.address.city;  
                    this.lat = res.latitude;  
                    this.lng = res.longitude;  
                    this.searchPos(res.address.city);  
                }  
            });  
        },  
        methods: {  
            searchPos(cityName) {  
                let map = this.$refs.map;  
                let searchObj = new plus.maps.Search(map);  
                searchObj.onPoiSearchComplete = (state, result)=> {  
                    if (state == 0) {  
                        if (result.currentNumber <= 0) {  
                            uni.showToast({  
                                title: '没有检索到结果',  
                                icon: 'none'  
                            });  
                        }  
                        this.cityList = [];  
                        for (let i = 0; i < result.currentNumber; i++) {  
                            let pos = result.getPosition(i);  
                            this.cityList.push(pos);  
                        }  
                    } else {  
                        uni.showToast({  
                            title: '检索失败',  
                            icon: 'none'  
                        });  
                    }  
                }  
                let pt = new plus.maps.Point(this.lng, this.lat);  
                // 默认搜索火车站  
                let nowPosCIty = this.searchText ? this.searchText : '火车站';  
                searchObj.poiSearchInCity(cityName, nowPosCIty);  
            },  
            chooseCity() {  
                this.$refs.mpvuePicker.show();  
            },  
            onCancel(e) {  
                console.log(e);  
            },  
            onConfirm(e) {  
                let pickerText = e.label.split('-');  
                this.searchPos(pickerText[1])  
            },  
            chooseHandle(e) {  
                console.log('选择地点的地点信息:');  
                console.log(e);  
            }  
        }  
    }  
</script>  

<style scoped>  
    #map1 {  
        height: 0;  
    }  
    .flex-box {  
        display: flex;  
    }  
.now-pos {  
    font-size: 30upx;  
    padding: 25upx;  
}  
    .choose-city {  
        background-color: #FFFFFF;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .city-item {  
        width: 160upx;  
        color: grey;  
        padding: 20upx 15upx;  
        margin-left: 15upx;  
        border-right: 0.5px solid #ddd;  
        border-radius: 5upx;  
    }  

    .city {  
        font-size: 30upx;  
        margin-top: 8upx;  
    }  
.down-ico {  
    width: 30upx;  
    height: 30upx;  
    position: relative;  
    top:16upx;  
}  
    .search-result-wrap {  
        padding: 10upx;  
    }  

    .search-result-content {  
        display: flex;  
        flex-direction: column;  
        padding: 10upx;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .search-box {  
        font-size: 30upx;  
        width: 550upx;  
        padding: 20upx 15upx;  
        background-color: #FFFFFF;  
    }  

    .list-city-name {  
        font-size: 32upx;  
        margin-bottom: 0;  
        padding: 0;  
    }  

    .list-city-address {  
        font-size: 24upx;  
        color: gray;  
        margin-top: 0;  
    }  
</style>  

其中使用了城市选择插件,结合自己的需要自行选择,我直接在插件市场找了一个使用了,如有侵权,请联系本人QQ:1414901782;
演示视频:见附件
项目地址:uniapp选择地点

继续阅读 »

自己在使用uni.chooseLocation时发现只能搜索到当前城市的地点,无法搜索到其他城市的地点,这样导致实现地点搜索时限制很多,因此分享给出自己结合HTML5+实现的地点选择。

<template>  
    <view class="full-wrap">  
        <view id="map">  
        </view>  
        <view class="now-pos" @tap="searchPos(posCity)">  
            定位城市:{{posCity}}  
        </view>  
        <view class="choose-city flex-box">  
            <view class="city-item flex-box">  
                <text class="city flex-box" @tap="chooseCity">{{nowCity}}</text>  
                <image src="../../static/down.png" class="down-ico"></image>  
            </view>  
            <view class="search-box">  
                <input placeholder="搜索地点" type="text" name="input" @input="searchPos(nowCity)" style="width: 100% ;" v-model="searchText"></input>  
            </view>  
        </view>  
        <view class="search-result-wrap">  
            <view class="cu-item padding" :key="index" v-for="(item,index) in cityList" @click="chooseHandle(item)">  
                <view class="search-result-content">  
                    <text class="list-city-name">{{item.name}}</text>  
                    <text class="list-city-address text-gray">{{item.city+item.address}}</text>  
                </view>  
            </view>  
        </view>  
        <!-- 城市选择 该插件从插件市场下载 -->  
        <mpvue-picker :themeColor="themeColor" ref="mpvuePicker" :mode="mode" :deepLength="deepLength" :pickerValueDefault="pickerValueDefault"  
         @onConfirm="onConfirm" @="" :pickerValueArray="pickerValueArray"></mpvue-picker>  
    </view>  
</template>  

<script>  
    import cityData from '../../commonjs/city.data.js'  
    import mpvuePicker from '../../components/mpvue-picker/mpvuePicker.vue'  
    export default {  
        components: {  
            mpvuePicker  
        },  
        data() {  
            return {  
                lat: " 39.915",  
                lng: '116.404',  
                posCity: "",  
                nowCity: '',  
                searchText: "",  
                cityList: [],  
                pickerValueDefault: [0, 0],  
                themeColor: '#007AFF',  
                mode: 'multiLinkageSelector',  
                deepLength: 2,  
                pickerValueArray: cityData  
            }  
        },  
        mounted() {  
            // 默认以当前位置为中心  
            uni.getLocation({  
                geocode: 'true',  
                success: res => {  
                    this.posCity = res.address.city;  
                    this.nowCity = res.address.city;  
                    this.lat = res.latitude;  
                    this.lng = res.longitude;  
                    this.searchPos(res.address.city);  
                }  
            });  
        },  
        methods: {  
            searchPos(cityName) {  
                let map = this.$refs.map;  
                let searchObj = new plus.maps.Search(map);  
                searchObj.onPoiSearchComplete = (state, result)=> {  
                    if (state == 0) {  
                        if (result.currentNumber <= 0) {  
                            uni.showToast({  
                                title: '没有检索到结果',  
                                icon: 'none'  
                            });  
                        }  
                        this.cityList = [];  
                        for (let i = 0; i < result.currentNumber; i++) {  
                            let pos = result.getPosition(i);  
                            this.cityList.push(pos);  
                        }  
                    } else {  
                        uni.showToast({  
                            title: '检索失败',  
                            icon: 'none'  
                        });  
                    }  
                }  
                let pt = new plus.maps.Point(this.lng, this.lat);  
                // 默认搜索火车站  
                let nowPosCIty = this.searchText ? this.searchText : '火车站';  
                searchObj.poiSearchInCity(cityName, nowPosCIty);  
            },  
            chooseCity() {  
                this.$refs.mpvuePicker.show();  
            },  
            onCancel(e) {  
                console.log(e);  
            },  
            onConfirm(e) {  
                let pickerText = e.label.split('-');  
                this.searchPos(pickerText[1])  
            },  
            chooseHandle(e) {  
                console.log('选择地点的地点信息:');  
                console.log(e);  
            }  
        }  
    }  
</script>  

<style scoped>  
    #map1 {  
        height: 0;  
    }  
    .flex-box {  
        display: flex;  
    }  
.now-pos {  
    font-size: 30upx;  
    padding: 25upx;  
}  
    .choose-city {  
        background-color: #FFFFFF;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .city-item {  
        width: 160upx;  
        color: grey;  
        padding: 20upx 15upx;  
        margin-left: 15upx;  
        border-right: 0.5px solid #ddd;  
        border-radius: 5upx;  
    }  

    .city {  
        font-size: 30upx;  
        margin-top: 8upx;  
    }  
.down-ico {  
    width: 30upx;  
    height: 30upx;  
    position: relative;  
    top:16upx;  
}  
    .search-result-wrap {  
        padding: 10upx;  
    }  

    .search-result-content {  
        display: flex;  
        flex-direction: column;  
        padding: 10upx;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .search-box {  
        font-size: 30upx;  
        width: 550upx;  
        padding: 20upx 15upx;  
        background-color: #FFFFFF;  
    }  

    .list-city-name {  
        font-size: 32upx;  
        margin-bottom: 0;  
        padding: 0;  
    }  

    .list-city-address {  
        font-size: 24upx;  
        color: gray;  
        margin-top: 0;  
    }  
</style>  

其中使用了城市选择插件,结合自己的需要自行选择,我直接在插件市场找了一个使用了,如有侵权,请联系本人QQ:1414901782;
演示视频:见附件
项目地址:uniapp选择地点

收起阅读 »

在uni-app中使用pdf.js预览 pdf文件

分享 uniapp pdf

主要根据 掘金上的这篇博文 跨平台(uni-app)文件在线预览解决方案 设置,但是遇到了几个坑,和大家分享下:

  • 出现跨域问题
    可以将 view.js 的
       if (origin !== viewerOrigin && protocol !== 'blob:') {  
        throw new Error('file origin does not match viewer\'s');  
      }

注释掉

  • 无法读取文件流
    uniapp一读取文件流就闪退。可以下载后读取。但是下载后按照原方法,会被读取view.js下的路径,需要用H5+的api转换成绝对路径
        onLoad() {  
            uni.downloadFile({  
                url: 'http://192.168.18.28:8280/webupload/service/sys/file/upload/download?appKey=accessKey&prodID=prodID&fileID=3494049602848768',  
                success: (result) => {  
                    var tempFilePath = result.tempFilePath  
                    let fileUrl = plus.io.convertLocalFileSystemURL(tempFilePath)  
                    this.allUrl = this.viewerUrl + '?file=' + fileUrl  
                }  
            })  
        }
继续阅读 »

主要根据 掘金上的这篇博文 跨平台(uni-app)文件在线预览解决方案 设置,但是遇到了几个坑,和大家分享下:

  • 出现跨域问题
    可以将 view.js 的
       if (origin !== viewerOrigin && protocol !== 'blob:') {  
        throw new Error('file origin does not match viewer\'s');  
      }

注释掉

  • 无法读取文件流
    uniapp一读取文件流就闪退。可以下载后读取。但是下载后按照原方法,会被读取view.js下的路径,需要用H5+的api转换成绝对路径
        onLoad() {  
            uni.downloadFile({  
                url: 'http://192.168.18.28:8280/webupload/service/sys/file/upload/download?appKey=accessKey&prodID=prodID&fileID=3494049602848768',  
                success: (result) => {  
                    var tempFilePath = result.tempFilePath  
                    let fileUrl = plus.io.convertLocalFileSystemURL(tempFilePath)  
                    this.allUrl = this.viewerUrl + '?file=' + fileUrl  
                }  
            })  
        }
收起阅读 »

APICloud终于承认侵权并向DCloud道歉了(附最新产品对比)

APICloud

​在北京市高级人民法院的法令要求下,APICloud终于承认了侵犯DCloud著作权,并在官网和公众号向DCloud道歉。

2015年,DCloud起诉APICloud侵权后,APICloud不但否认侵权,还污蔑DCloud在造谣,在开发者和媒体处混淆视听。如今,这份迟来了4年的道歉函,终于让真相大白!

但令人遗憾的是,与海底捞等诚信企业的真挚道歉相比,APICloud的道歉行为缺少诚意。

首先从表达形式上,这篇道歉函堪称反SEO经典教材。

  1. 网页以图片方式显示标题,防止爬虫抓取
  2. 链接:https://www.apicloud.com/static_pages/qz.html,不能直接访问,必须从APICloud官网首页点那个图片来跳转
  3. 禁止搜索引擎爬虫访问(robots:noindex,nofollow)
  4. 道歉函页面没有title
  5. 道歉函源码没有一句正文,所有内容使用js编写

网友纷纷表示学习了,这反SEO做的太用心了。
有这份心思,花在正道上,岂不是更好?

道歉形式权当调侃玩笑了。但DCloud不能接收的,是道歉内容本身,完全看不出APICloud真正反思了自己的错误,看不出它的道歉诚意。
下为APICloud道歉函正文:

注:apicloud官网的致歉函已经下线,可在微信公众号搜索“apicloud”,查询2019年12月3日的历史文章,了解致歉函内容。

DCloud已通过法律途径和微信公众号明确提出了我们对致歉内容的意见,但APICloud仍拒不更改致歉函。

在此,DCloud再次公开提出我们的3点道歉要求:

  • 要求1

APICloud持续多个版本破解DCloud产品、抄袭源码、盗用DLL文件和图片。在APICloud的致歉函中,不能以“开发人员的不慎”,轻描淡写的敷衍过去了。
破解反编译产品是费时费力的事情,我们很难相信普通开发人员,在没有领导的安排下,会长时间去做这么冒险的事情。管理层应该反思自己,而不是甩锅给不知名的开发人员。

  • 要求2

在DCloud起诉APICloud后,APICloud反而污蔑DCloud在造谣,数次中伤DCloud声誉、讽刺DCloud管理层。APICloud需收回自己的错误言论,并为此向DCloud道歉。

  • 要求3

APICloud误导、欺瞒开发者,使开发者做出错误判断,APICloud不仅应该向DCloud道歉,也应该向被误导和欺瞒的开发者道歉。

如果APICloud真正认识到自己的错误,请认真反思,回应DCloud的3点要求,重新给出一份有诚意的致歉函。

后记

在DCloud发出本文的公众号后,不少开发者在后台留言:

  • 有人依然为侵权行为做掩护,并嘲笑使用正版软件的人虚伪。
  • 有不太了解DCloud产品,希望详细了解产品区别。

前者我们懒得理他,并且不欢迎他进入DCloud社区。
后者的问题,我们在此整理一下最新版比较

  1. 产品覆盖
    DCloud有HBuilder和uni-app两个产品,HBuilder是前端开发工具,有400多万开发者,月活百万;uni-app是多端应用开发框架,有手机端月活高达8.4亿。
    可以详细了解DCloud的案例,很多知名开发商:https://uniapp.dcloud.io/case
    使用uni-app,可以同时开发跨平台App、H5网页,以及微信/阿里/百度/头条/QQ等小程序。

    • 技术人员掌握uni-app,可以完成老板交代的任何项目
    • 开发商使用uni-app,可以大幅提升开发效率,降低开发成本,获得更多用户
      而apicloud只能开发App,无法覆盖其他平台。
  2. 发展趋势
    从百度指数可以看出,apicloud这些年基本在原地踏步

    iOS13发布,UIWebview被列为私有API,苹果强推Apple登录。DCloud第一时间提供了wkwebview白屏恢复等各种方案,以及Apple登录。
    Android10发布,无法获取IMEI等设备信息。DCloud及时提供了OAID等业内新标准。
    而APICloud落后几个月才参考DCloud方案更新版本,维护力度很弱。

  3. 收费
    APICloud有大量收费点,详见:https://www.apicloud.com/vipservice/price
    一个应用装机量超5万收费、打渠道包收费、打包总次数收费、创建应用多了收费、一个应用多人协作收费...到处是收费点。基本策略就是初期免费引用户进来,应用做大就会开始收费。
    而DCloud全免费,不管你的应用做到多大,哪怕用户过亿,DCloud的产品也仍然是免费的。并且以后也永远不会做这种养韭菜、割韭菜的事情,永不变更目前的免费策略。
    有人说DCloud云打包也收费,并非如此。只是经受过恶意流量攻击,DCloud的云打包会控制每日的不合理打包次数,正常使用肯定不会收费。而且DCloud支持完全免费的离线打包,更自由。
    除了上述收费点,APICloud把大量重要功能从基础产品中剥离,放到插件市场销售。
    比如原生视频、直播,比如跨多家手机厂商的统一推送,DCloud均包含在基础产品中免费提供,而APICloud要单独购买插件。
    开发一个应用,使用APICloud要比DCloud付出更多成本。

  4. App端的性能体验
    uni-app支持nvue,使用纯原生渲染,性能体验、内存占用优于竞品更多。
    即便是使用uni-app的webview渲染,由于它是一个小程序引擎,体验更加优秀,不管是新页面加载速度,还是Android手机输入框弹出的感受,都和原生一样顺滑。
    uni-app自带一个独立的js引擎,运行更快,可以直接运行所有es6语法,而不需要转换适配低版本WebView。
    包括软键盘弹出等很多细节体验,apicloud都做不细做不好。

  5. 生态
    DCloud的周边插件和培训生态丰富。
    DCloud的插件市场虽然推出较晚,但目前已有一千多款插件。详见:https://ext.dcloud.net.cn/
    DCloud基础产品的功能较多,支持Native.js调用各种手机端API,支持丰富的小程序SDK直接在App端使用,又有各种丰富的原生插件,形成了非常完善的插件生态。
    uni-app还支持小程序生态的组件、sdk在app里直接使用,通过renderjs支持web组件生态库在app端的使用,可以说汇聚了最多的生态力度,可以找到各种优秀轮子。
    uni-app插件市场优秀的原生插件作者,月收入可过万。
    插件市场还有大量行业应用模板,如电商、电子书、漫画、音乐等整套免费模板,可拿来就用。
    开发者在DCloud的生态中寻找轮子,不说应有尽有,但也更加丰富,而且更加便宜、ui定制更灵活。
    vue的一个重要创新,就是组件化。uni-app将其发扬光大,还新增了easycom技术,市场中各种组件可以极快的完成应用的搭建。
    而uni-app的培训生态也非常完善,腾讯课堂官方为uni-app录制视频教程,腾讯课堂、网易云课堂、哔哩哔哩中有各种收费或免费的视频教程。

  6. 社区
    DCloud的问答社区,月活百万,每日几百篇帖子。QQ/微信群,累计70多个,其中uni-app有几十个2千人大群,每日几十万聊天记录。
    在csdn、掘金等社区也有大量开发者的经验分享。
    在更活跃、更开放的社区里,开发者可以快速进步。
    DCloud官方也投入大量精力,听取社区反馈意见,每月积极更新产品,持续帮助开发者更加成功。

uni-app当然也还不完美,官方也在持续升级。但整体而言,比某些竞品要更优秀、更良心。
DCloud无意垄断市场,欢迎竞争。开发者可以在DCloud、react native、flutter等各种优秀的跨平台开发工具中选择,但不要被某些并无诚意的产品蒙蔽。

继续阅读 »

​在北京市高级人民法院的法令要求下,APICloud终于承认了侵犯DCloud著作权,并在官网和公众号向DCloud道歉。

2015年,DCloud起诉APICloud侵权后,APICloud不但否认侵权,还污蔑DCloud在造谣,在开发者和媒体处混淆视听。如今,这份迟来了4年的道歉函,终于让真相大白!

但令人遗憾的是,与海底捞等诚信企业的真挚道歉相比,APICloud的道歉行为缺少诚意。

首先从表达形式上,这篇道歉函堪称反SEO经典教材。

  1. 网页以图片方式显示标题,防止爬虫抓取
  2. 链接:https://www.apicloud.com/static_pages/qz.html,不能直接访问,必须从APICloud官网首页点那个图片来跳转
  3. 禁止搜索引擎爬虫访问(robots:noindex,nofollow)
  4. 道歉函页面没有title
  5. 道歉函源码没有一句正文,所有内容使用js编写

网友纷纷表示学习了,这反SEO做的太用心了。
有这份心思,花在正道上,岂不是更好?

道歉形式权当调侃玩笑了。但DCloud不能接收的,是道歉内容本身,完全看不出APICloud真正反思了自己的错误,看不出它的道歉诚意。
下为APICloud道歉函正文:

注:apicloud官网的致歉函已经下线,可在微信公众号搜索“apicloud”,查询2019年12月3日的历史文章,了解致歉函内容。

DCloud已通过法律途径和微信公众号明确提出了我们对致歉内容的意见,但APICloud仍拒不更改致歉函。

在此,DCloud再次公开提出我们的3点道歉要求:

  • 要求1

APICloud持续多个版本破解DCloud产品、抄袭源码、盗用DLL文件和图片。在APICloud的致歉函中,不能以“开发人员的不慎”,轻描淡写的敷衍过去了。
破解反编译产品是费时费力的事情,我们很难相信普通开发人员,在没有领导的安排下,会长时间去做这么冒险的事情。管理层应该反思自己,而不是甩锅给不知名的开发人员。

  • 要求2

在DCloud起诉APICloud后,APICloud反而污蔑DCloud在造谣,数次中伤DCloud声誉、讽刺DCloud管理层。APICloud需收回自己的错误言论,并为此向DCloud道歉。

  • 要求3

APICloud误导、欺瞒开发者,使开发者做出错误判断,APICloud不仅应该向DCloud道歉,也应该向被误导和欺瞒的开发者道歉。

如果APICloud真正认识到自己的错误,请认真反思,回应DCloud的3点要求,重新给出一份有诚意的致歉函。

后记

在DCloud发出本文的公众号后,不少开发者在后台留言:

  • 有人依然为侵权行为做掩护,并嘲笑使用正版软件的人虚伪。
  • 有不太了解DCloud产品,希望详细了解产品区别。

前者我们懒得理他,并且不欢迎他进入DCloud社区。
后者的问题,我们在此整理一下最新版比较

  1. 产品覆盖
    DCloud有HBuilder和uni-app两个产品,HBuilder是前端开发工具,有400多万开发者,月活百万;uni-app是多端应用开发框架,有手机端月活高达8.4亿。
    可以详细了解DCloud的案例,很多知名开发商:https://uniapp.dcloud.io/case
    使用uni-app,可以同时开发跨平台App、H5网页,以及微信/阿里/百度/头条/QQ等小程序。

    • 技术人员掌握uni-app,可以完成老板交代的任何项目
    • 开发商使用uni-app,可以大幅提升开发效率,降低开发成本,获得更多用户
      而apicloud只能开发App,无法覆盖其他平台。
  2. 发展趋势
    从百度指数可以看出,apicloud这些年基本在原地踏步

    iOS13发布,UIWebview被列为私有API,苹果强推Apple登录。DCloud第一时间提供了wkwebview白屏恢复等各种方案,以及Apple登录。
    Android10发布,无法获取IMEI等设备信息。DCloud及时提供了OAID等业内新标准。
    而APICloud落后几个月才参考DCloud方案更新版本,维护力度很弱。

  3. 收费
    APICloud有大量收费点,详见:https://www.apicloud.com/vipservice/price
    一个应用装机量超5万收费、打渠道包收费、打包总次数收费、创建应用多了收费、一个应用多人协作收费...到处是收费点。基本策略就是初期免费引用户进来,应用做大就会开始收费。
    而DCloud全免费,不管你的应用做到多大,哪怕用户过亿,DCloud的产品也仍然是免费的。并且以后也永远不会做这种养韭菜、割韭菜的事情,永不变更目前的免费策略。
    有人说DCloud云打包也收费,并非如此。只是经受过恶意流量攻击,DCloud的云打包会控制每日的不合理打包次数,正常使用肯定不会收费。而且DCloud支持完全免费的离线打包,更自由。
    除了上述收费点,APICloud把大量重要功能从基础产品中剥离,放到插件市场销售。
    比如原生视频、直播,比如跨多家手机厂商的统一推送,DCloud均包含在基础产品中免费提供,而APICloud要单独购买插件。
    开发一个应用,使用APICloud要比DCloud付出更多成本。

  4. App端的性能体验
    uni-app支持nvue,使用纯原生渲染,性能体验、内存占用优于竞品更多。
    即便是使用uni-app的webview渲染,由于它是一个小程序引擎,体验更加优秀,不管是新页面加载速度,还是Android手机输入框弹出的感受,都和原生一样顺滑。
    uni-app自带一个独立的js引擎,运行更快,可以直接运行所有es6语法,而不需要转换适配低版本WebView。
    包括软键盘弹出等很多细节体验,apicloud都做不细做不好。

  5. 生态
    DCloud的周边插件和培训生态丰富。
    DCloud的插件市场虽然推出较晚,但目前已有一千多款插件。详见:https://ext.dcloud.net.cn/
    DCloud基础产品的功能较多,支持Native.js调用各种手机端API,支持丰富的小程序SDK直接在App端使用,又有各种丰富的原生插件,形成了非常完善的插件生态。
    uni-app还支持小程序生态的组件、sdk在app里直接使用,通过renderjs支持web组件生态库在app端的使用,可以说汇聚了最多的生态力度,可以找到各种优秀轮子。
    uni-app插件市场优秀的原生插件作者,月收入可过万。
    插件市场还有大量行业应用模板,如电商、电子书、漫画、音乐等整套免费模板,可拿来就用。
    开发者在DCloud的生态中寻找轮子,不说应有尽有,但也更加丰富,而且更加便宜、ui定制更灵活。
    vue的一个重要创新,就是组件化。uni-app将其发扬光大,还新增了easycom技术,市场中各种组件可以极快的完成应用的搭建。
    而uni-app的培训生态也非常完善,腾讯课堂官方为uni-app录制视频教程,腾讯课堂、网易云课堂、哔哩哔哩中有各种收费或免费的视频教程。

  6. 社区
    DCloud的问答社区,月活百万,每日几百篇帖子。QQ/微信群,累计70多个,其中uni-app有几十个2千人大群,每日几十万聊天记录。
    在csdn、掘金等社区也有大量开发者的经验分享。
    在更活跃、更开放的社区里,开发者可以快速进步。
    DCloud官方也投入大量精力,听取社区反馈意见,每月积极更新产品,持续帮助开发者更加成功。

uni-app当然也还不完美,官方也在持续升级。但整体而言,比某些竞品要更优秀、更良心。
DCloud无意垄断市场,欢迎竞争。开发者可以在DCloud、react native、flutter等各种优秀的跨平台开发工具中选择,但不要被某些并无诚意的产品蒙蔽。

收起阅读 »

uniapp做一个前端,大概30个页面,最好是个人开发者。


可以加微信详谈,样式只是大概,样式全部使用colorui拼接,不求优化适配全部设备。只求速度上线开发,能做的我们细谈。


可以加微信详谈,样式只是大概,样式全部使用colorui拼接,不求优化适配全部设备。只求速度上线开发,能做的我们细谈。

个推 实现个推绑定、解绑别名。。。

uni-app极光推送:https://blog.csdn.net/dashenid/article/details/103437459

由于html5+没有提供绑定、解绑别名的方法,

用native.js去实现,点用原生的个推方法

igexinTool.js

function igexinTool() {  
    var isAndorid, PushManager, context, Instance, GeTuiSdk;  

    if(plus.os.name == 'Android') {  
        isAndorid = true;  
    } else {  
        isAndorid = false;  
    }  

    if(isAndorid) {  
        PushManager = plus.android.importClass("com.igexin.sdk.PushManager");  
        context = plus.android.runtimeMainActivity().getContext();  
        Instance = PushManager.getInstance();  
    } else {  
        GeTuiSdk = plus.ios.importClass("GeTuiSdk");  
    }  

    this.bindAlias = function(alias) {  
        if(isAndorid) {  
            Instance.bindAlias(context, alias);  
        } else {  
            GeTuiSdk.bindAliasandSequenceNum(alias, alias);  
        }  
    }  

    this.unbindAlias = function(alias) {  
        if(isAndorid) {  
            Instance.unBindAlias(context, alias, true);  
        } else {  
            GeTuiSdk.unbindAliasandSequenceNumandIsSelf(alias, alias, true);  
        }  
    }  

    this.getVersion = function() {  
        if(isAndorid) {  
            return Instance.getVersion(context);  
        } else {  
            return GeTuiSdk.version;  
        }  
    }  

    //开启推送  
    this.turnOnPush = function() {  
        if(isAndorid) {  
            Instance.turnOnPush(context);  
        } else {  
            GeTuiSdk.setPushModeForOff(false);  
        }  
    }  

    //关闭推送  
    this.turnOffPush = function() {  
        if(isAndorid) {  
            Instance.turnOffPush(context);  
        } else {  
            GeTuiSdk.setPushModeForOff(true);  
        }  
    }  

}

使用:
var tool = new igexinTool();
tool.bindAlias("ykj");

继续阅读 »

uni-app极光推送:https://blog.csdn.net/dashenid/article/details/103437459

由于html5+没有提供绑定、解绑别名的方法,

用native.js去实现,点用原生的个推方法

igexinTool.js

function igexinTool() {  
    var isAndorid, PushManager, context, Instance, GeTuiSdk;  

    if(plus.os.name == 'Android') {  
        isAndorid = true;  
    } else {  
        isAndorid = false;  
    }  

    if(isAndorid) {  
        PushManager = plus.android.importClass("com.igexin.sdk.PushManager");  
        context = plus.android.runtimeMainActivity().getContext();  
        Instance = PushManager.getInstance();  
    } else {  
        GeTuiSdk = plus.ios.importClass("GeTuiSdk");  
    }  

    this.bindAlias = function(alias) {  
        if(isAndorid) {  
            Instance.bindAlias(context, alias);  
        } else {  
            GeTuiSdk.bindAliasandSequenceNum(alias, alias);  
        }  
    }  

    this.unbindAlias = function(alias) {  
        if(isAndorid) {  
            Instance.unBindAlias(context, alias, true);  
        } else {  
            GeTuiSdk.unbindAliasandSequenceNumandIsSelf(alias, alias, true);  
        }  
    }  

    this.getVersion = function() {  
        if(isAndorid) {  
            return Instance.getVersion(context);  
        } else {  
            return GeTuiSdk.version;  
        }  
    }  

    //开启推送  
    this.turnOnPush = function() {  
        if(isAndorid) {  
            Instance.turnOnPush(context);  
        } else {  
            GeTuiSdk.setPushModeForOff(false);  
        }  
    }  

    //关闭推送  
    this.turnOffPush = function() {  
        if(isAndorid) {  
            Instance.turnOffPush(context);  
        } else {  
            GeTuiSdk.setPushModeForOff(true);  
        }  
    }  

}

使用:
var tool = new igexinTool();
tool.bindAlias("ykj");

收起阅读 »

uni-app 使用axios 分享 ,解决: adapter is not a function

uni-app 使用axios 真机会提示: adapter is not a function 。我也不懂,我也不会,就菜鸟一个。

然后跟了下问题所在,好像调用不到xhr.js ,
翻到这里 var adapter = config.adapter || defaults.adapter; 报错。

参考:
https://juejin.im/post/5dbd8c64e51d4529fc3facd4
https://blog.csdn.net/u013704958/article/details/90713386 我借鉴5楼提供的方法,用uni.request 来调用
https://ext.dcloud.net.cn/plugin?id=930

添加了一段代码,重新引用
//真机获取
axios.defaults.adapter = function (config) {....}

下面axios封装来自大佬的 HzyAdmin 项目
https://gitee.com/hzy6/HzyAdminSpa

import axios from 'axios';  
import qs from 'qs';  
import tools from './tools';  

let loading;  
let isloading = true;  
//http request 拦截器  
axios.interceptors.request.use(config => {  
        if (isloading) {  
            uni.showLoading();  
            loading=true;  
        }  
        var cookie = uni.getStorageSync('Authorization');  
        config.headers['x-requested-width'] = 'XMLHttpRequest';  
        config.headers['Authorization'] = cookie;  
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';  

        if (!config.data) return config;  

        if (config.data.isUpload)  
            config.headers['Content-Type'] = 'multipart/form-data';  
        else  
            config.data = qs.stringify(config.data); //如果是非上传类型 则 将数据重新组装  

        return config;  
    },  
    error => {  
        console.log(error);  
        return Promise.reject(error);  
    });  

//http response 拦截器  
axios.interceptors.response.use(response => {  
        if (loading) {  
            uni.hideLoading();  
            loading=false;  
        }  
        var data = response.data;  

        if (data.hasOwnProperty('status')) {  
            // public enum EMessageBoxStatus  
            // {  
            //     接口授权码无效 = -3,  
            //     服务端异常 = -2,  
            //     自定义 = -1,  
            //     失败 = 0,  
            //     成功 = 1,  
            // }  

            if (data.status == -3) { //接口授权码无效global.$router.push("/Login")  
                tools.alert(data.msg + '请重新登录!', () => uni.navigateTo({url:'../pages/Login'}));  
                return;  
            }  
            if (data.status == -2) { //服务端异常  
                tools.alert(data.msg);  
                return;  
            }  
            if (data.status == 0) { //失败  
                tools.msg(data.msg, '错误');  
                return;  
            }  
        }  

        return response;  
    },  
    error => {  
        console.log(error);  
        uni.hideLoading();  
        if (error.response.status === 401) {  
            if (loading) {  
                loading=false;  
            }  
            global.tools.notice("无权访问!", "错误");  
            return uni.navigateTo({url:'../pages/Login'}) //global.$router.push('/Login');  
        } else {  
            return Promise.reject(error)  
        }  
    });  

//真机获取  
axios.defaults.adapter = function (config) {  
    return new Promise((resolve, reject) => {  
        console.log(config)  
        var settle = require('axios/lib/core/settle');  
        var buildURL = require('axios/lib/helpers/buildURL');  
        uni.request({  
            method: config.method.toUpperCase(),  
            url: buildURL(config.url, config.params, config.paramsSerializer),  
            header: config.headers,  
            data: config.data,  
            dataType: config.dataType,  
            responseType: config.responseType,  
            sslVerify: config.sslVerify,  
            complete:function complete(response){  
                response = {  
                  data: response.data,  
                  status: response.statusCode,  
                  errMsg: response.errMsg,  
                  header: response.header,  
                  config: config  
                };  

            settle(resolve, reject, response);  
            }  
        })  
    })  
}  

/**  
 * 封装get方法  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param headers 头部信息  
 * @returns {Promise}  
 */  
export function get(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    url =`${url}?${qs.stringify(data)}`;  
    return new Promise((resolve, reject) => {  
        axios.get(url, config)  
            .then(response => {  
                resolve(response);  
            })  
            .catch(err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装post请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function post(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.post(url, data, config)  
            .then(response => {  
                if (response != undefined) {  
                    resolve(response);  
                }  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装 post 请求 用于上传文件   
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function upload(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    if (!data) data = {};  
    data.isUpload = true;  
    return new Promise((resolve, reject) => {  
        axios.post(url, data, config)  
            .then(response => {  
                if (response != undefined) {  
                    resolve(response);  
                }  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装 get请求 用于下载文件  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @returns {Promise}  
 */  
export function download(url, data = {}, loading = true) {  
    this.get(url, data, loading, {  
        // responseType: 'stream',  
        responseType: 'blob',  
        // responseType: 'arraybuffer',  
    }).then(res => {  
        var data = res.data;  
        var headers = res.headers;  
        //"attachment; filename=6a9c13bc-e214-44e4-8456-dbca9fcd2367.xls;filename*=UTF-8''6a9c13bc-e214-44e4-8456-dbca9fcd2367.xls"  
        var contentDisposition = headers['content-disposition'];  
        var contentType = headers['content-type'];  
        var attachmentInfoArrary = contentDisposition.split(';');  
        var fileName = '';  
        if (attachmentInfoArrary.length > 1) {  
            fileName = attachmentInfoArrary[1].split('=')[1];  
        }  
        var blob = new Blob([data], { type: contentType });  

        if (window.navigator && window.navigator.msSaveOrOpenBlob) { // IE  
            window.navigator.msSaveOrOpenBlob(blob, fileName);  
        } else {  
            let url = (window.URL || window.webkitURL).createObjectURL(blob);  
            // window.open(url, "_blank"); //下载  
            // window.URL.revokeObjectURL(url) // 只要映射存在,Blob就不能进行垃圾回收,因此一旦不再需要引用,就必须小心撤销URL,释放掉blob对象。  

            let a = document.createElement('a');  
            a.style.display = 'none';  
            a.href = url;  
            a.setAttribute('download', fileName);  
            document.body.appendChild(a);  
            a.click()  
            document.body.removeChild(a); // 下载完成移除元素  
            // window.location.href = url  
            window.URL.revokeObjectURL(url); // 只要映射存在,Blob就不能进行垃圾回收,因此一旦不再需要引用,就必须小心撤销URL,释放掉blob对象。  

        }  
    });  
}  

/**  
 * 封装patch请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function patch(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.patch(url, data, config)  
            .then(response => {  
                resolve(response);  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装put请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function put(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.put(url, data, config)  
            .then(response => {  
                resolve(response);  
            }, err => {  
                reject(err)  
            })  
    })  
}
继续阅读 »

uni-app 使用axios 真机会提示: adapter is not a function 。我也不懂,我也不会,就菜鸟一个。

然后跟了下问题所在,好像调用不到xhr.js ,
翻到这里 var adapter = config.adapter || defaults.adapter; 报错。

参考:
https://juejin.im/post/5dbd8c64e51d4529fc3facd4
https://blog.csdn.net/u013704958/article/details/90713386 我借鉴5楼提供的方法,用uni.request 来调用
https://ext.dcloud.net.cn/plugin?id=930

添加了一段代码,重新引用
//真机获取
axios.defaults.adapter = function (config) {....}

下面axios封装来自大佬的 HzyAdmin 项目
https://gitee.com/hzy6/HzyAdminSpa

import axios from 'axios';  
import qs from 'qs';  
import tools from './tools';  

let loading;  
let isloading = true;  
//http request 拦截器  
axios.interceptors.request.use(config => {  
        if (isloading) {  
            uni.showLoading();  
            loading=true;  
        }  
        var cookie = uni.getStorageSync('Authorization');  
        config.headers['x-requested-width'] = 'XMLHttpRequest';  
        config.headers['Authorization'] = cookie;  
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';  

        if (!config.data) return config;  

        if (config.data.isUpload)  
            config.headers['Content-Type'] = 'multipart/form-data';  
        else  
            config.data = qs.stringify(config.data); //如果是非上传类型 则 将数据重新组装  

        return config;  
    },  
    error => {  
        console.log(error);  
        return Promise.reject(error);  
    });  

//http response 拦截器  
axios.interceptors.response.use(response => {  
        if (loading) {  
            uni.hideLoading();  
            loading=false;  
        }  
        var data = response.data;  

        if (data.hasOwnProperty('status')) {  
            // public enum EMessageBoxStatus  
            // {  
            //     接口授权码无效 = -3,  
            //     服务端异常 = -2,  
            //     自定义 = -1,  
            //     失败 = 0,  
            //     成功 = 1,  
            // }  

            if (data.status == -3) { //接口授权码无效global.$router.push("/Login")  
                tools.alert(data.msg + '请重新登录!', () => uni.navigateTo({url:'../pages/Login'}));  
                return;  
            }  
            if (data.status == -2) { //服务端异常  
                tools.alert(data.msg);  
                return;  
            }  
            if (data.status == 0) { //失败  
                tools.msg(data.msg, '错误');  
                return;  
            }  
        }  

        return response;  
    },  
    error => {  
        console.log(error);  
        uni.hideLoading();  
        if (error.response.status === 401) {  
            if (loading) {  
                loading=false;  
            }  
            global.tools.notice("无权访问!", "错误");  
            return uni.navigateTo({url:'../pages/Login'}) //global.$router.push('/Login');  
        } else {  
            return Promise.reject(error)  
        }  
    });  

//真机获取  
axios.defaults.adapter = function (config) {  
    return new Promise((resolve, reject) => {  
        console.log(config)  
        var settle = require('axios/lib/core/settle');  
        var buildURL = require('axios/lib/helpers/buildURL');  
        uni.request({  
            method: config.method.toUpperCase(),  
            url: buildURL(config.url, config.params, config.paramsSerializer),  
            header: config.headers,  
            data: config.data,  
            dataType: config.dataType,  
            responseType: config.responseType,  
            sslVerify: config.sslVerify,  
            complete:function complete(response){  
                response = {  
                  data: response.data,  
                  status: response.statusCode,  
                  errMsg: response.errMsg,  
                  header: response.header,  
                  config: config  
                };  

            settle(resolve, reject, response);  
            }  
        })  
    })  
}  

/**  
 * 封装get方法  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param headers 头部信息  
 * @returns {Promise}  
 */  
export function get(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    url =`${url}?${qs.stringify(data)}`;  
    return new Promise((resolve, reject) => {  
        axios.get(url, config)  
            .then(response => {  
                resolve(response);  
            })  
            .catch(err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装post请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function post(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.post(url, data, config)  
            .then(response => {  
                if (response != undefined) {  
                    resolve(response);  
                }  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装 post 请求 用于上传文件   
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function upload(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    if (!data) data = {};  
    data.isUpload = true;  
    return new Promise((resolve, reject) => {  
        axios.post(url, data, config)  
            .then(response => {  
                if (response != undefined) {  
                    resolve(response);  
                }  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装 get请求 用于下载文件  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @returns {Promise}  
 */  
export function download(url, data = {}, loading = true) {  
    this.get(url, data, loading, {  
        // responseType: 'stream',  
        responseType: 'blob',  
        // responseType: 'arraybuffer',  
    }).then(res => {  
        var data = res.data;  
        var headers = res.headers;  
        //"attachment; filename=6a9c13bc-e214-44e4-8456-dbca9fcd2367.xls;filename*=UTF-8''6a9c13bc-e214-44e4-8456-dbca9fcd2367.xls"  
        var contentDisposition = headers['content-disposition'];  
        var contentType = headers['content-type'];  
        var attachmentInfoArrary = contentDisposition.split(';');  
        var fileName = '';  
        if (attachmentInfoArrary.length > 1) {  
            fileName = attachmentInfoArrary[1].split('=')[1];  
        }  
        var blob = new Blob([data], { type: contentType });  

        if (window.navigator && window.navigator.msSaveOrOpenBlob) { // IE  
            window.navigator.msSaveOrOpenBlob(blob, fileName);  
        } else {  
            let url = (window.URL || window.webkitURL).createObjectURL(blob);  
            // window.open(url, "_blank"); //下载  
            // window.URL.revokeObjectURL(url) // 只要映射存在,Blob就不能进行垃圾回收,因此一旦不再需要引用,就必须小心撤销URL,释放掉blob对象。  

            let a = document.createElement('a');  
            a.style.display = 'none';  
            a.href = url;  
            a.setAttribute('download', fileName);  
            document.body.appendChild(a);  
            a.click()  
            document.body.removeChild(a); // 下载完成移除元素  
            // window.location.href = url  
            window.URL.revokeObjectURL(url); // 只要映射存在,Blob就不能进行垃圾回收,因此一旦不再需要引用,就必须小心撤销URL,释放掉blob对象。  

        }  
    });  
}  

/**  
 * 封装patch请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function patch(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.patch(url, data, config)  
            .then(response => {  
                resolve(response);  
            }, err => {  
                reject(err)  
            })  
    })  
}  

/**  
 * 封装put请求  
 * @param url  
 * @param data  
 * @param loading 是否有加载效果  
 * @param config config信息  
 * @returns {Promise}  
 */  
export function put(url, data = {}, loading = true, config = {}) {  
    isloading = loading;  
    return new Promise((resolve, reject) => {  
        axios.put(url, data, config)  
            .then(response => {  
                resolve(response);  
            }, err => {  
                reject(err)  
            })  
    })  
}
收起阅读 »