微信开放平台申请接口应用签名的获取方式
在微信开放平台申请相关应用接口的时候,要求一个应用签名的东西!
还有在一些平台申请相关服务也需要,比如在个推在推送配置!
下面介绍获取方法
1、首先下载安装安卓应用签名获取的APK
https://share.weiyun.com/55MsELw
2、找到需要获取的那个APP进去查看复制(需要获取应用签名的APP要先安装到手机)
像微信那边的就是需要证书指纹(MD5)这个值,复制填写即可!
在微信开放平台申请相关应用接口的时候,要求一个应用签名的东西!
还有在一些平台申请相关服务也需要,比如在个推在推送配置!
下面介绍获取方法
1、首先下载安装安卓应用签名获取的APK
https://share.weiyun.com/55MsELw
2、找到需要获取的那个APP进去查看复制(需要获取应用签名的APP要先安装到手机)
像微信那边的就是需要证书指纹(MD5)这个值,复制填写即可!
收起阅读 »
HBuilderX eslint一键修复功能使用说明(适用于HBuilderX 2.6.8之前的版本)
eslint-js插件下载地址
eslint-vue插件下载地址
特别说明
本文所述功能,仅对2.2.0和2.6.8之间的版本生效,2.6.9+版本请参考:https://ask.dcloud.net.cn/article/37070
1. eslint一键修复功能说明
- eslint一键修复功能,仅支持
cli
项目,不支持普通web项目 cli
项目,需要安装eslint库,并配置eslint规则- HBuilderX需要安装eslint插件。进入菜单【工具】【插件安装】,安装
eslint-js
、eslint-vue
两个插件 - 若满足上述条件,当编写完代码,保存时,若代码中存在错误,可使用
eslint一键修复
功能。 如下图:
2. eslint一键修复功能说明
一键修复功能,跟项目下配置的eslint规则
有关。
本文不再罗列eslint如何配置规则, 请自行搜索。
特别说明: eslint一键修复功能,并不能修复所有的语法错误。比如定义了某个变量,但未使用,eslint校验保存,一键修复功能并不能修复此类错误。
3. 保存文件时,自动修复语法错误
- 点击菜单【帮助】【插件配置】,找到
eslint-js
、eslint-vue
插件 - 点击插件下的
package.json
文件 - 如下图,修改两处(注意id),然后
重启HBuilderX
(注意:必须重启HBuilderX)
eslint-js插件下载地址
eslint-vue插件下载地址
特别说明
本文所述功能,仅对2.2.0和2.6.8之间的版本生效,2.6.9+版本请参考:https://ask.dcloud.net.cn/article/37070
1. eslint一键修复功能说明
- eslint一键修复功能,仅支持
cli
项目,不支持普通web项目 cli
项目,需要安装eslint库,并配置eslint规则- HBuilderX需要安装eslint插件。进入菜单【工具】【插件安装】,安装
eslint-js
、eslint-vue
两个插件 - 若满足上述条件,当编写完代码,保存时,若代码中存在错误,可使用
eslint一键修复
功能。 如下图:
2. eslint一键修复功能说明
一键修复功能,跟项目下配置的eslint规则
有关。
本文不再罗列eslint如何配置规则, 请自行搜索。
特别说明: eslint一键修复功能,并不能修复所有的语法错误。比如定义了某个变量,但未使用,eslint校验保存,一键修复功能并不能修复此类错误。
3. 保存文件时,自动修复语法错误
- 点击菜单【帮助】【插件配置】,找到
eslint-js
、eslint-vue
插件 - 点击插件下的
package.json
文件 - 如下图,修改两处(注意id),然后
重启HBuilderX
(注意:必须重启HBuilderX)
Uparse修复版的说明
举例有这样一段代码
<div>
<span>1243 5346</span>
</div>
uparse文件执行顺序简单说明
parse.vue插件入口文件 → 调用lib/html2json.js → 返回nodes对象 (这里得到一个html节点解析后的json对象)
↓↑
调用wxDiscode(例:把&nbsp;转成字符),htmlparser
parse.vue把nodes输入到wxParseTemplate0.vue
首先会处理最外层的 div,就变成了
<view>
nodes
<view/>
nodes = '<span>1243 5346</span>'
再把新的nodes再次输入到wxParseTemplate0.vue(小程序是wxParseTemplate1.vue)
这样就得到一个递归调用的模板,直到输出所有节点
wxParseTemplate里面遇到table,img,video,audio,标签时会分别调用自定义组件(更多的可以直接看源码)
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
import wxParseTable from './wxParseTable';
下面说说优化了哪些地方.优化的同时也存在些问题.希望大家提出好的解决方案.
0.去掉了标签在递归过程中多出来的标签,解决导致内联元素没有内联的问题
1.table标签很简单目前是用的rich-text
https://uniapp.dcloud.io/component/rich-text
没有找到更好的处理方法,问题是,标签内部的非文字节点,,可能会有问题
2.wxDiscode相对于原版做了一些处理
// 例:
str = str.replace(/ /g, " ");
改为
str = str.replace(/ | | /g, "<span class='spaceshow'> </span>");
3.parse.vue文件的getWidth方法原版获取的是屏幕的宽度,实际上应该获取组件的宽度,该方法获取的宽度主要是用于计算image的图片宽度.(组件可能有边距,边距不应该用于计算图片的宽度).
有兴趣的可以去看看image的处理,,目前是有些问题的,,因为官方的图片插件必须要设置一个宽高覆盖掉默认的宽高,做百分比计算感觉比较复杂.目前我也没什么好的处理方式.
另外还要吐槽一下uni.createSelectorQuery(),之前在处理时,各个小程序的兼容情况好像并不是很好,所以用的条件编译.有报错可以自行处理一下
4.parse.vue文件的setHtml方法
this.nodes = results.nodes;
// 改为
this.nodes = [];
results.nodes.forEach((item) => {
setTimeout(() => {
this.nodes.push(item)
}, 0);
})
这段代码可以异步的加载根节点,,
像这样:
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
但是如果用div标签包裹起来就不是异步加载了,
像这样
<div>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
</div>
目前还没想好如何异步加载所有节点,,有想到如何处理的可以留言或私信我谢谢.之前有尝试,但是性能反降.遂放弃
实际上是分段渲染的结果,让人感觉好像渲染速度加快,,实际上并没快多少
5.parse.vue文件的 navigate方法
原方法是只能获取href的内容,,有人提出需求需要获取onclick的内容,所以增加了attr,
6.对br标签的处理{wxDiscode应该处理"<",">"字符,避免转码后输出br标签,br再转换行的问题,暂时还没改}
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'">
<!-- #endif -->
7.wxParse.css文件我就不说了但是需要在APP.vue文件中引入,
目前就想到这么多了.
举例有这样一段代码
<div>
<span>1243 5346</span>
</div>
uparse文件执行顺序简单说明
parse.vue插件入口文件 → 调用lib/html2json.js → 返回nodes对象 (这里得到一个html节点解析后的json对象)
↓↑
调用wxDiscode(例:把&nbsp;转成字符),htmlparser
parse.vue把nodes输入到wxParseTemplate0.vue
首先会处理最外层的 div,就变成了
<view>
nodes
<view/>
nodes = '<span>1243 5346</span>'
再把新的nodes再次输入到wxParseTemplate0.vue(小程序是wxParseTemplate1.vue)
这样就得到一个递归调用的模板,直到输出所有节点
wxParseTemplate里面遇到table,img,video,audio,标签时会分别调用自定义组件(更多的可以直接看源码)
import wxParseImg from './wxParseImg';
import wxParseVideo from './wxParseVideo';
import wxParseAudio from './wxParseAudio';
import wxParseTable from './wxParseTable';
下面说说优化了哪些地方.优化的同时也存在些问题.希望大家提出好的解决方案.
0.去掉了标签在递归过程中多出来的标签,解决导致内联元素没有内联的问题
1.table标签很简单目前是用的rich-text
https://uniapp.dcloud.io/component/rich-text
没有找到更好的处理方法,问题是,标签内部的非文字节点,,可能会有问题
2.wxDiscode相对于原版做了一些处理
// 例:
str = str.replace(/ /g, " ");
改为
str = str.replace(/ | | /g, "<span class='spaceshow'> </span>");
3.parse.vue文件的getWidth方法原版获取的是屏幕的宽度,实际上应该获取组件的宽度,该方法获取的宽度主要是用于计算image的图片宽度.(组件可能有边距,边距不应该用于计算图片的宽度).
有兴趣的可以去看看image的处理,,目前是有些问题的,,因为官方的图片插件必须要设置一个宽高覆盖掉默认的宽高,做百分比计算感觉比较复杂.目前我也没什么好的处理方式.
另外还要吐槽一下uni.createSelectorQuery(),之前在处理时,各个小程序的兼容情况好像并不是很好,所以用的条件编译.有报错可以自行处理一下
4.parse.vue文件的setHtml方法
this.nodes = results.nodes;
// 改为
this.nodes = [];
results.nodes.forEach((item) => {
setTimeout(() => {
this.nodes.push(item)
}, 0);
})
这段代码可以异步的加载根节点,,
像这样:
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
但是如果用div标签包裹起来就不是异步加载了,
像这样
<div>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
<p>这里是<span>段落</span></p>
</div>
目前还没想好如何异步加载所有节点,,有想到如何处理的可以留言或私信我谢谢.之前有尝试,但是性能反降.遂放弃
实际上是分段渲染的结果,让人感觉好像渲染速度加快,,实际上并没快多少
5.parse.vue文件的 navigate方法
原方法是只能获取href的内容,,有人提出需求需要获取onclick的内容,所以增加了attr,
6.对br标签的处理{wxDiscode应该处理"<",">"字符,避免转码后输出br标签,br再转换行的问题,暂时还没改}
<!--br类型-->
<!-- #ifndef H5 -->
<text v-else-if="node.tag == 'br'">\n</text>
<!-- #endif -->
<!-- #ifdef H5 -->
<br v-else-if="node.tag == 'br'">
<!-- #endif -->
7.wxParse.css文件我就不说了但是需要在APP.vue文件中引入,
目前就想到这么多了.
收起阅读 »【申精】 uni-simple-router H5强化路由篇
uni-simple-router@1.3.5 H5路由强化篇
<br/>
很高兴能为开源项目做一些贡献,uni-simple-router@1.3.5已经在群内测了。今天啥口水话都不说,直接上代码。
<br/>
一直以来uni-app在路由上做的都是比较简洁了,特别是在h5端对url的美观、拦截、动态路由等都是比较欠缺的。而今天你看到了这个文章 说明有望了? 哈哈!! 截止1.2.5以前的版本只是在做一些基本的功能,没有对端进行强化。1.3.5这个版本是对H5的强化
<br/>
更新内容
太多了现在就说了,以后文档上见 现在把以前的文档进行重新整理。换正式点。
<br/>
强化内容如下
1.能完全使用vue-router进行开发
2.动态导入组件作为页面
3.完全拦截uni-app自带的所有跳转
4.自定义路由表。通配、404、子路由。嵌套路由 废弃pages.json中的配置
(#31)
5.提供3中开发模式:1.读取pages.json中的配置开发,2.不使用pages.json中的配置进行开发,开发者直接用component动态导入组件。3.完全使用vue-router开发
6.自定义加载样式,可追加、可替换、可隐藏
(#24)
7.全权使用vue-router配置,必须使用vue-router开发才行
8.传递参数方式新增为两种模式,开发者可自行切换
(#25,#28)
9.动态路由 addRoutes TODO
<br/>
先睹为快
使用了自定义路径的url,依然使用pages.json的配置
uni-app原始配置
404捕捉页面,依然使用pages.json中的配置
自定义加载样式
完全使用vue-router开发
<br/>
结束语
下一篇 为uni-simple-router之H5拦截uni-app自带tabbar篇
uni-simple-router@1.3.5 H5路由强化篇
<br/>
很高兴能为开源项目做一些贡献,uni-simple-router@1.3.5已经在群内测了。今天啥口水话都不说,直接上代码。
<br/>
一直以来uni-app在路由上做的都是比较简洁了,特别是在h5端对url的美观、拦截、动态路由等都是比较欠缺的。而今天你看到了这个文章 说明有望了? 哈哈!! 截止1.2.5以前的版本只是在做一些基本的功能,没有对端进行强化。1.3.5这个版本是对H5的强化
<br/>
更新内容
太多了现在就说了,以后文档上见 现在把以前的文档进行重新整理。换正式点。
<br/>
强化内容如下
1.能完全使用vue-router进行开发
2.动态导入组件作为页面
3.完全拦截uni-app自带的所有跳转
4.自定义路由表。通配、404、子路由。嵌套路由 废弃pages.json中的配置
(#31)
5.提供3中开发模式:1.读取pages.json中的配置开发,2.不使用pages.json中的配置进行开发,开发者直接用component动态导入组件。3.完全使用vue-router开发
6.自定义加载样式,可追加、可替换、可隐藏
(#24)
7.全权使用vue-router配置,必须使用vue-router开发才行
8.传递参数方式新增为两种模式,开发者可自行切换
(#25,#28)
9.动态路由 addRoutes TODO
<br/>
先睹为快
使用了自定义路径的url,依然使用pages.json的配置
uni-app原始配置
404捕捉页面,依然使用pages.json中的配置
自定义加载样式
完全使用vue-router开发
<br/>
结束语
下一篇 为uni-simple-router之H5拦截uni-app自带tabbar篇
收起阅读 »HBuilderX 格式化操作、及格式化插件配置说明
本帖文档已集成到: hx产品文档
1. HBuilderX的格式化插件说明
插件名称 | 对应插件配置中的名称 | 是否内置 | 可格式化的文件 | 插件市场 |
---|---|---|---|---|
js-beautify | format | 内置插件 | vue、html、js、css、json | |
prettier | format-prettier | 非内置,需要下载 | less、sass、vue、stylus(vue内嵌)、ts、yaml | 下载地址 |
stylus-supremacy | format-stylus-supremacy | 非内置,需要下载 | 格式化单独stylus文件 | 下载地址 |
特别说明:
- 当同时存在
js-beautify
和format-prettier
插件是,格式化vue
文件,调用的是format-prettier
插件 stylus-supremacy
只支持格式化独立的stylus文件,如需格式化vue文件内的stylus代码,需要同时安装prettier插件- 本地插件目录:HBuilderX所有的插件,都存放于本地
plugins
目录下
2. 格式化插件配置
点击菜单【工具】【设置 -> 插件配置】,选择相应插件, 点击配置文件
进行配置。
在格式化插件配置文件
内,每项配置均有说明(或阅读插件中README.MD
文件); 无说明文档的请参考插件官网配置说明。
插件配置中格式化插件 | 配置文件 | 插件官网 |
---|---|---|
format | format/jsbeautifyrc.js | 官网 |
format-prettier | formator-prettier/prettier.config.js | 官网 |
format-stylus-supremacy | formator-stylus-supremacy/supremacy.config.js | 官网 |
3. 格式化快捷键
格式化菜单:【右键菜单】-->【重排代码格式】
格式化快捷键, win:ctrl + K
; mac: command + K
自定义格式化快捷键: 点击菜单【工具】【自定义快捷键】,在【用户设置】中,拷贝如下代码, key
为您要定义的快捷键
{"key":"ctrl+k","command":"editor.action.format"}
4. 格式化风格
格式化时, 使用的缩进方式,是读取的菜单【工具 --> 设置】中的配置
特别说明:
editorconfig
配置会覆盖HBuilderX编辑器配置;当项目下存在.editorconfig
文件时,格式化时,读取的是此配置文件。
.editorconfig使用说明文档
5. 保存文件时,自动格式化
部分小伙伴反馈, 如何实现保存文件时,自动格式化? 答: 不支持。
- 普通web项目
不支持
保存文件的同时,自动格式化。 - vue-cli项目,可通过配置
eslint
,通过eslint自动校验修复
的功能,来实现相同的效果。
例如:保存时,去除分号等。
6. estlint自动修复、实时校验
本帖文档已集成到: hx产品文档
1. HBuilderX的格式化插件说明
插件名称 | 对应插件配置中的名称 | 是否内置 | 可格式化的文件 | 插件市场 |
---|---|---|---|---|
js-beautify | format | 内置插件 | vue、html、js、css、json | |
prettier | format-prettier | 非内置,需要下载 | less、sass、vue、stylus(vue内嵌)、ts、yaml | 下载地址 |
stylus-supremacy | format-stylus-supremacy | 非内置,需要下载 | 格式化单独stylus文件 | 下载地址 |
特别说明:
- 当同时存在
js-beautify
和format-prettier
插件是,格式化vue
文件,调用的是format-prettier
插件 stylus-supremacy
只支持格式化独立的stylus文件,如需格式化vue文件内的stylus代码,需要同时安装prettier插件- 本地插件目录:HBuilderX所有的插件,都存放于本地
plugins
目录下
2. 格式化插件配置
点击菜单【工具】【设置 -> 插件配置】,选择相应插件, 点击配置文件
进行配置。
在格式化插件配置文件
内,每项配置均有说明(或阅读插件中README.MD
文件); 无说明文档的请参考插件官网配置说明。
插件配置中格式化插件 | 配置文件 | 插件官网 |
---|---|---|
format | format/jsbeautifyrc.js | 官网 |
format-prettier | formator-prettier/prettier.config.js | 官网 |
format-stylus-supremacy | formator-stylus-supremacy/supremacy.config.js | 官网 |
3. 格式化快捷键
格式化菜单:【右键菜单】-->【重排代码格式】
格式化快捷键, win:ctrl + K
; mac: command + K
自定义格式化快捷键: 点击菜单【工具】【自定义快捷键】,在【用户设置】中,拷贝如下代码, key
为您要定义的快捷键
{"key":"ctrl+k","command":"editor.action.format"}
4. 格式化风格
格式化时, 使用的缩进方式,是读取的菜单【工具 --> 设置】中的配置
特别说明:
editorconfig
配置会覆盖HBuilderX编辑器配置;当项目下存在.editorconfig
文件时,格式化时,读取的是此配置文件。
.editorconfig使用说明文档
5. 保存文件时,自动格式化
部分小伙伴反馈, 如何实现保存文件时,自动格式化? 答: 不支持。
- 普通web项目
不支持
保存文件的同时,自动格式化。 - vue-cli项目,可通过配置
eslint
,通过eslint自动校验修复
的功能,来实现相同的效果。
例如:保存时,去除分号等。
6. estlint自动修复、实时校验
eslint-js自动修复功能
eslint-vue自动修复功能
uniapp开发支付宝小程序中碰到的事项
1.支付宝小程序的本地存储我使用的是 该模式
const storageSet = (key, value) => { // 设置本地存储 set
uni.setStorage({
key,
value
});
}
const storageGet = (key) => { // 获取本地存储 get
uni.getStorage({
key: 'storage_key',
success: function (res) {
return res.data
}
});
}
const storageRemove = (key) => { // 获取本地存储 get
uni.removeStorage({
key
});
}
2.如果涉及到了扫普通二维码进入小程序,如“http://****.com?pid=1&cid=2&did=4”
无法在page页面的onLoad(options)中拿到指定的参数,
只能在app.vue中onLaunch(options)中拿到数据
并且这里是拿到 完整的 普通二维码地址 “http://****.com?pid=1&cid=2&did=4”,并不是对象形式,所以这里需要转为对象
我这边是采用的vuex中存起来,在page页面自行调用
顺便提供转码方法
export function getUrlAli(url){
let scan_url = url; // 解码
scan_url = scan_url.substring(scan_url.indexOf('?')+1);
let obj ={},arr = scan_url.split('&');// 存参数
if(url.includes('token')) { // 判断是否是支付宝小程序开发,体验版
arr.pop()
}
arr.map((item,index)=>{
let subArr = arr[index].split('=');
let key = subArr[0];
let value = subArr[1];
obj[key] = value;
})
return obj
}
【以上都是个人的做法,如果有更好的做法,欢迎一起交流!】
1.支付宝小程序的本地存储我使用的是 该模式
const storageSet = (key, value) => { // 设置本地存储 set
uni.setStorage({
key,
value
});
}
const storageGet = (key) => { // 获取本地存储 get
uni.getStorage({
key: 'storage_key',
success: function (res) {
return res.data
}
});
}
const storageRemove = (key) => { // 获取本地存储 get
uni.removeStorage({
key
});
}
2.如果涉及到了扫普通二维码进入小程序,如“http://****.com?pid=1&cid=2&did=4”
无法在page页面的onLoad(options)中拿到指定的参数,
只能在app.vue中onLaunch(options)中拿到数据
并且这里是拿到 完整的 普通二维码地址 “http://****.com?pid=1&cid=2&did=4”,并不是对象形式,所以这里需要转为对象
我这边是采用的vuex中存起来,在page页面自行调用
顺便提供转码方法
export function getUrlAli(url){
let scan_url = url; // 解码
scan_url = scan_url.substring(scan_url.indexOf('?')+1);
let obj ={},arr = scan_url.split('&');// 存参数
if(url.includes('token')) { // 判断是否是支付宝小程序开发,体验版
arr.pop()
}
arr.map((item,index)=>{
let subArr = arr[index].split('=');
let key = subArr[0];
let value = subArr[1];
obj[key] = value;
})
return obj
}
【以上都是个人的做法,如果有更好的做法,欢迎一起交流!】
2.3.7 你这个bug终于修复了
2.3.7.20191024
- 修复 html 连续编写有默认值的属性时(例如 autocomplete accesskey等), 覆盖位置不对的Bug
2.3.7.20191024
- 修复 html 连续编写有默认值的属性时(例如 autocomplete accesskey等), 覆盖位置不对的Bug
iOS平台HBuilder基座证书过期无法真机运行的说明(return code=-402620395)
原因
HBuilder/HBuilderX真机运行时需在iPhone/iPad手机上安装HBuilder运行基座App,此App非苹果企业证书(iEP)签名发布,通过数据线安装到iOS设备。
因为苹果政策要求,这样的App证书有效期只有1年,过期后将无法安装,需要重新打包基座
故障现象
在HBuilder/HBuilderX上真机运行证书过期的基座,控制台会提示以下错误:
正在建立手机连接...
正在安装手机端HBuilder调试基座...
安装失败 return code=-402620395,请手动安装..\HBuilder\plugins\com.pandora.tools.android_1.0.0.201811231756\base\iPhone_base.ipa到手机上(可使用iTools安装),并重新运行真机调试。
注:这个提示语不太精准,未来HBuilderX会提供更准确的提示。
影响范围
老HBuilder早已停止更新(1年前已经公告过),请开发者及时升级最新版HBuilderX。
HBuilderX在2.3.3.20190923及以下版本也已过期。
但这个只影响HBuilder/HBuilderX的默认运行基座,用户使用自己证书制作的自定义运行基座不受影响,自定义基座的证书过期时间是开发者自己管理的。
已经安装过的运行基座仍然可以使用。
解决方案
- 升级到HBuilderX 2.3.4及以上版本,目前最新版是HBuilder 2.3.7
- 在老版HBuilder或HBuilderX里自行制作自定义基座。运行菜单里有自定义基座的制作选项和教程。
升级注意
老版HBuilder/HBuilderX升级新版,有一些常见的升级注意事项,比如新版默认的webview从UIWebview调整为WKWebview,比如微信SDK升级引发要求通用链接。
请开发者仔细阅读升级公告指南:https://ask.dcloud.net.cn/article/36260
使用自定义调试基座出现此问题
通常出现此问题是提交云端打包自定义基座时使用了提交appstore的证书及profile文件,这种包只能用于提交appstore,不能作为自定义基座。
应该使用开发(Development)证书及profile文件打包生成自定义基座
苹果官方提供的profile类型,分别适用的场景:
Development
- iOS App Development
开发者调试使用,在特定设备上测试使用。可用于制作自定义基座。
Distribution
- Ad Hoc
发布测试时使用,在有限的设备上可安装使用。可以用于制作自定义基座,必须在指定的设备上真机运行 - App Store
发布到AppStore使用,只能用于提交AppStore。不能用于制作自定义基座。 - In House
仅iEP账号可创建,可用于企业内部发行应用使用。可以用于制作自定义基座。
原因
HBuilder/HBuilderX真机运行时需在iPhone/iPad手机上安装HBuilder运行基座App,此App非苹果企业证书(iEP)签名发布,通过数据线安装到iOS设备。
因为苹果政策要求,这样的App证书有效期只有1年,过期后将无法安装,需要重新打包基座
故障现象
在HBuilder/HBuilderX上真机运行证书过期的基座,控制台会提示以下错误:
正在建立手机连接...
正在安装手机端HBuilder调试基座...
安装失败 return code=-402620395,请手动安装..\HBuilder\plugins\com.pandora.tools.android_1.0.0.201811231756\base\iPhone_base.ipa到手机上(可使用iTools安装),并重新运行真机调试。
注:这个提示语不太精准,未来HBuilderX会提供更准确的提示。
影响范围
老HBuilder早已停止更新(1年前已经公告过),请开发者及时升级最新版HBuilderX。
HBuilderX在2.3.3.20190923及以下版本也已过期。
但这个只影响HBuilder/HBuilderX的默认运行基座,用户使用自己证书制作的自定义运行基座不受影响,自定义基座的证书过期时间是开发者自己管理的。
已经安装过的运行基座仍然可以使用。
解决方案
- 升级到HBuilderX 2.3.4及以上版本,目前最新版是HBuilder 2.3.7
- 在老版HBuilder或HBuilderX里自行制作自定义基座。运行菜单里有自定义基座的制作选项和教程。
升级注意
老版HBuilder/HBuilderX升级新版,有一些常见的升级注意事项,比如新版默认的webview从UIWebview调整为WKWebview,比如微信SDK升级引发要求通用链接。
请开发者仔细阅读升级公告指南:https://ask.dcloud.net.cn/article/36260
使用自定义调试基座出现此问题
通常出现此问题是提交云端打包自定义基座时使用了提交appstore的证书及profile文件,这种包只能用于提交appstore,不能作为自定义基座。
应该使用开发(Development)证书及profile文件打包生成自定义基座
苹果官方提供的profile类型,分别适用的场景:
Development
- iOS App Development
开发者调试使用,在特定设备上测试使用。可用于制作自定义基座。
Distribution
- Ad Hoc
发布测试时使用,在有限的设备上可安装使用。可以用于制作自定义基座,必须在指定的设备上真机运行 - App Store
发布到AppStore使用,只能用于提交AppStore。不能用于制作自定义基座。 - In House
仅iEP账号可创建,可用于企业内部发行应用使用。可以用于制作自定义基座。
Android平台云端打包 - 公共测试证书
由于公共测试证书的描述信息都是测试数据,并且任何人都可以使用,也可以下载此证书,最近发现有开发者使用此证书发布了一些涉嫌欺诈的APP,被某些安全检测平台将此证书列入黑名单,因此可能会将使用了公共测试证书的应用误报为病毒。HBuilderX3.1.10+版本更新了公共测试证书,避免在部分手机安装测试时提示应用存在风险的问题,已经使用测试证书的应用需尽快更新使用自有证书。另外后续HBuilderX版本将不再支持使用公共测试证书,新增自动生成证书功能来替代公共测试证书。
!!!注意!!!
公共测试证书仅适合应用开发期间体验测试使用
公共测试证书中的描述信息都是测试数据,任何人都能下载使用,存在安全隐患,在部分安全检测平台可能会误报病毒。不要使用公共测试证书正式发布应用!!!
测试证书更新引出的问题
HBuilderX3.1.10版本更新了测试证书,解决使用功能测试证书在部分机器上安装测试时提示风险的问题
如果之前发布的应用已经使用了测试证书,更新HBuilderX3.1.10后提交云端打包将会使用新的公共测试证书,可能会出现以下情况:
- 无法覆盖安装,这是因为公共测试证书更新了
- 在部分手机安装时可能被系统弹出提示有风险,这是因为系统记录了之前使用的签名证书,重新打包后使用了新证书,因此被系统怀疑为钓鱼App
- 三方SDK相关功能(地图、UniPush、一键登录)无法正常使用,这是因为签名证书改变,需要到对应后台更新配置签名证书信息
解决方案
- 更新证书,注意不要继续使用测试证书发布应用,应该更新使用自有证书,参考:生成Android签名证书。如果已经上架到应用市场,需要先下架再重新提交。
- 继续使用原来测试证书,请在此文章的“HBuilderX3.1.10之前版本公共测试证书”下载,作为自己生成的证书提交云端打包`
关于Android证书的用途
证书是一个开发者的身份标志,对Android系统而言。使用同一个证书签发的App,是属于同一个开发者的App,并确保此证书不被泄露(务必管理好自己的证书)。
举个极端的例子,如果你的应用证书泄露,那么别人可以用这个证书签名一个仿冒App,假如包名和你的包名也一样,就可以覆盖安装安卓手机上你之前的包。
当前仅依赖证书校验是不完善的,所以主流的Android应用市场,通过实名认证开发者信息和著作权,强化了App的唯一性。也就是从主流应用市场点更新,不会发生冒充事件。但是通过其他方式安装apk,仍然会发生冒充的可能性。
如果签名不同,即使包名相同,也无法覆盖安装。此时安卓手机会在安装时报错,需要先卸载老的版本,才能安装新版。
云端打包使用公共测试证书
提交云端打包时在“App云端打包界面”选择“使用公共测试证书”:
HBuilderX3.1.10+版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
应用签名: 06838cc840093b9d4689fc419ba1a3f3
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: android
Creation date: 2021-4-12
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Issuer: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Serial number: 363bc393
Valid from: Mon Apr 12 16:27:53 CST 2021 until: Wed Mar 19 16:27:53 CST 2121
Certificate fingerprints:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
公共测试证书下载
本地离线打包如果需要使用公共测试证书,可从这里下载。
证书密码:123456
证书别名:android
HBuilderX3.1.10之前版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
应用签名: f9f6c81fdbab50147d6f2c4fcee60aa5
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: test
Creation date: 2019-10-28
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Issuer: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Serial number: 7dd12840
Valid from: Fri Jul 26 20:52:56 CST 2019 until: Sun Jul 02 20:52:56 CST 2119
Certificate fingerprints:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
公共测试证书下载
本地离线打包如果需要使用公共测试证书,可从这里下载。
证书密码:123456
证书别名:test
再次强调:为了确保应用的安全性,正式发布应用不要使用公共测试证书
由于公共测试证书的描述信息都是测试数据,并且任何人都可以使用,也可以下载此证书,最近发现有开发者使用此证书发布了一些涉嫌欺诈的APP,被某些安全检测平台将此证书列入黑名单,因此可能会将使用了公共测试证书的应用误报为病毒。HBuilderX3.1.10+版本更新了公共测试证书,避免在部分手机安装测试时提示应用存在风险的问题,已经使用测试证书的应用需尽快更新使用自有证书。另外后续HBuilderX版本将不再支持使用公共测试证书,新增自动生成证书功能来替代公共测试证书。
!!!注意!!!
公共测试证书仅适合应用开发期间体验测试使用
公共测试证书中的描述信息都是测试数据,任何人都能下载使用,存在安全隐患,在部分安全检测平台可能会误报病毒。不要使用公共测试证书正式发布应用!!!
测试证书更新引出的问题
HBuilderX3.1.10版本更新了测试证书,解决使用功能测试证书在部分机器上安装测试时提示风险的问题
如果之前发布的应用已经使用了测试证书,更新HBuilderX3.1.10后提交云端打包将会使用新的公共测试证书,可能会出现以下情况:
- 无法覆盖安装,这是因为公共测试证书更新了
- 在部分手机安装时可能被系统弹出提示有风险,这是因为系统记录了之前使用的签名证书,重新打包后使用了新证书,因此被系统怀疑为钓鱼App
- 三方SDK相关功能(地图、UniPush、一键登录)无法正常使用,这是因为签名证书改变,需要到对应后台更新配置签名证书信息
解决方案
- 更新证书,注意不要继续使用测试证书发布应用,应该更新使用自有证书,参考:生成Android签名证书。如果已经上架到应用市场,需要先下架再重新提交。
- 继续使用原来测试证书,请在此文章的“HBuilderX3.1.10之前版本公共测试证书”下载,作为自己生成的证书提交云端打包`
关于Android证书的用途
证书是一个开发者的身份标志,对Android系统而言。使用同一个证书签发的App,是属于同一个开发者的App,并确保此证书不被泄露(务必管理好自己的证书)。
举个极端的例子,如果你的应用证书泄露,那么别人可以用这个证书签名一个仿冒App,假如包名和你的包名也一样,就可以覆盖安装安卓手机上你之前的包。
当前仅依赖证书校验是不完善的,所以主流的Android应用市场,通过实名认证开发者信息和著作权,强化了App的唯一性。也就是从主流应用市场点更新,不会发生冒充事件。但是通过其他方式安装apk,仍然会发生冒充的可能性。
如果签名不同,即使包名相同,也无法覆盖安装。此时安卓手机会在安装时报错,需要先卸载老的版本,才能安装新版。
云端打包使用公共测试证书
提交云端打包时在“App云端打包界面”选择“使用公共测试证书”:
HBuilderX3.1.10+版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
应用签名: 06838cc840093b9d4689fc419ba1a3f3
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: android
Creation date: 2021-4-12
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Issuer: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Serial number: 363bc393
Valid from: Mon Apr 12 16:27:53 CST 2021 until: Wed Mar 19 16:27:53 CST 2121
Certificate fingerprints:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
公共测试证书下载
本地离线打包如果需要使用公共测试证书,可从这里下载。
证书密码:123456
证书别名:android
HBuilderX3.1.10之前版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
应用签名: f9f6c81fdbab50147d6f2c4fcee60aa5
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: test
Creation date: 2019-10-28
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Issuer: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Serial number: 7dd12840
Valid from: Fri Jul 26 20:52:56 CST 2019 until: Sun Jul 02 20:52:56 CST 2119
Certificate fingerprints:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
公共测试证书下载
本地离线打包如果需要使用公共测试证书,可从这里下载。
证书密码:123456
证书别名:test
再次强调:为了确保应用的安全性,正式发布应用不要使用公共测试证书
收起阅读 »uniapp H5图片上传压缩自动旋转
//H5压终审图片上传
const upload= async (opt) => {
let maxWidth = 500; //压缩图片最大宽度
opt = opt || {};
opt.url = opt.url || '';
opt.success = opt.success || function(){ };
console.log(opt.url)
let Orientation = 1;
//获取图片META信息
await getImageTag(opt.url, 'Orientation', function(e) {
if(e != undefined) Orientation = e;
})
var img = null;
var canvas = null;
await comprossImage(opt.url, maxWidth, function(e) {
img = e.img;
canvas = e.canvas;
})
console.log(Orientation)
let baseStr = '';
//如果方向角不为1,都需要进行旋转
switch(Orientation){
case 6://需要顺时针(向右)90度旋转
console.log('(向右)90度旋转');
baseStr = rotateImg(img,'right',canvas);
break;
case 8://需要逆时针(向左)90度旋转
console.log('向左)90度旋转');
baseStr = rotateImg(img,'left',canvas);
break;
case 3://需要180度旋转 转两次
console.log('需要180度旋转');
baseStr = rotateImg(img,'right',canvas, 2);
break;
default:
baseStr = rotateImg(img,'',canvas);
break;
}
opt.success(baseStr); //公共方法的回调函数
}
const comprossImage = async (imgSrc, maxWidth, func) => {
if(!imgSrc) return 0;
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: imgSrc,
success(res) {
let img = new Image();
img.src = res.path;
console.log(img)
let canvas = document.createElement('canvas');
let obj = new Object();
obj.img = img;
obj.canvas = canvas;
resolve(func(obj));
}
});
})
}
/**
* @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
* @param {Object} file 上传的图片文件
* @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
* @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
*/
const getImageTag = (file, tag, suc) => {
if (!file) return 0;
return new Promise((resolve, reject) => {
/* eslint-disable func-names */
// 箭头函数会修改this,所以这里不能用箭头函数
let imgObj = new Image()
imgObj.src = file
console.log(imgObj)
uni.getImageInfo({
src: file,
success(res) {
Exif.getData(imgObj, function () {
Exif.getAllTags(this);
let or = Exif.getTag(this,'Orientation');//这个Orientation 就是我们判断需不需要旋转的值了,有1、3、6、8
resolve(suc(or))
});
}
})
});
};
//网上提供的旋转function
const rotateImg = (img, direction, canvas, times = 1) => {
console.log('开始旋转')
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
if (img == null)return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
let maxWidth = 500;
let canvasWidth = width; //图片原始长宽
let canvasHeight = height;
let base = canvasWidth/canvasHeight;
console.log(maxWidth);
if(canvasWidth > maxWidth){
canvasWidth = maxWidth;
canvasHeight = Math.floor(canvasWidth/base);
}
width = canvasWidth;
height = canvasHeight;
var step = 0;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step += times;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else if(direction == 'left'){
step -= times;
step < min_step && (step = max_step);
} else { //不旋转
step = 0;
}
//旋转角度以弧度值为参数
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
console.log(degree)
console.log(step)
switch (step) {
case 1:
console.log('右旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
//console.log('旋转 180度')
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
console.log('左旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
default: //不旋转
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
break;
}
let baseStr = canvas.toDataURL("image/jpeg", 1);
return baseStr;
}
//H5压终审图片上传
const upload= async (opt) => {
let maxWidth = 500; //压缩图片最大宽度
opt = opt || {};
opt.url = opt.url || '';
opt.success = opt.success || function(){ };
console.log(opt.url)
let Orientation = 1;
//获取图片META信息
await getImageTag(opt.url, 'Orientation', function(e) {
if(e != undefined) Orientation = e;
})
var img = null;
var canvas = null;
await comprossImage(opt.url, maxWidth, function(e) {
img = e.img;
canvas = e.canvas;
})
console.log(Orientation)
let baseStr = '';
//如果方向角不为1,都需要进行旋转
switch(Orientation){
case 6://需要顺时针(向右)90度旋转
console.log('(向右)90度旋转');
baseStr = rotateImg(img,'right',canvas);
break;
case 8://需要逆时针(向左)90度旋转
console.log('向左)90度旋转');
baseStr = rotateImg(img,'left',canvas);
break;
case 3://需要180度旋转 转两次
console.log('需要180度旋转');
baseStr = rotateImg(img,'right',canvas, 2);
break;
default:
baseStr = rotateImg(img,'',canvas);
break;
}
opt.success(baseStr); //公共方法的回调函数
}
const comprossImage = async (imgSrc, maxWidth, func) => {
if(!imgSrc) return 0;
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: imgSrc,
success(res) {
let img = new Image();
img.src = res.path;
console.log(img)
let canvas = document.createElement('canvas');
let obj = new Object();
obj.img = img;
obj.canvas = canvas;
resolve(func(obj));
}
});
})
}
/**
* @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
* @param {Object} file 上传的图片文件
* @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
* @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
*/
const getImageTag = (file, tag, suc) => {
if (!file) return 0;
return new Promise((resolve, reject) => {
/* eslint-disable func-names */
// 箭头函数会修改this,所以这里不能用箭头函数
let imgObj = new Image()
imgObj.src = file
console.log(imgObj)
uni.getImageInfo({
src: file,
success(res) {
Exif.getData(imgObj, function () {
Exif.getAllTags(this);
let or = Exif.getTag(this,'Orientation');//这个Orientation 就是我们判断需不需要旋转的值了,有1、3、6、8
resolve(suc(or))
});
}
})
});
};
//网上提供的旋转function
const rotateImg = (img, direction, canvas, times = 1) => {
console.log('开始旋转')
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
if (img == null)return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
let maxWidth = 500;
let canvasWidth = width; //图片原始长宽
let canvasHeight = height;
let base = canvasWidth/canvasHeight;
console.log(maxWidth);
if(canvasWidth > maxWidth){
canvasWidth = maxWidth;
canvasHeight = Math.floor(canvasWidth/base);
}
width = canvasWidth;
height = canvasHeight;
var step = 0;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step += times;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else if(direction == 'left'){
step -= times;
step < min_step && (step = max_step);
} else { //不旋转
step = 0;
}
//旋转角度以弧度值为参数
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
console.log(degree)
console.log(step)
switch (step) {
case 1:
console.log('右旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
//console.log('旋转 180度')
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
console.log('左旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
default: //不旋转
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
break;
}
let baseStr = canvas.toDataURL("image/jpeg", 1);
return baseStr;
}
收起阅读 »