HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

低价外包H5移动端页面

外包

纯切图,css3动画

纯切图,css3动画

如何在本地进行微信公众号的开发调试

在进行微信公众号开发时,以前必须要有外网域名才能收到微信服务器的推送,这给开发和调试带来很大的不便。

现在方便了,QQ浏览器提供了微信公众号调试工具,开发者可以在自己的开发机上进行开发和调试,不再需要外网IP和域名。不仅可以直观看到已接收和已发送的消息内容和事件,方便开发和问题定位,还可以重复发送已接收的微信报文,避免调试时频繁操作手机

原理

调试工具内置了一个server,监听你本地运行的程序,并为你生成一个外网的url,你只需要在公众号的开发信息中配置上这个url,公众号收到消息后,就会通过这个url转发到调试工具上,调试工具再转给你的本地运行程序。

使用方法
(1)在本机启动运行自己程序的server
(2)打开qq浏览器,在应用中心中安装“微信调试工具”,安装完成后,选择“服务器端调试”
(3)填写本地server的ip和端口号,调试工具会返回一个外部URL
(4)到公众号后台开发者配置信息中填写这个URL
(5)用微信向公众号发送消息测试

继续阅读 »

在进行微信公众号开发时,以前必须要有外网域名才能收到微信服务器的推送,这给开发和调试带来很大的不便。

现在方便了,QQ浏览器提供了微信公众号调试工具,开发者可以在自己的开发机上进行开发和调试,不再需要外网IP和域名。不仅可以直观看到已接收和已发送的消息内容和事件,方便开发和问题定位,还可以重复发送已接收的微信报文,避免调试时频繁操作手机

原理

调试工具内置了一个server,监听你本地运行的程序,并为你生成一个外网的url,你只需要在公众号的开发信息中配置上这个url,公众号收到消息后,就会通过这个url转发到调试工具上,调试工具再转给你的本地运行程序。

使用方法
(1)在本机启动运行自己程序的server
(2)打开qq浏览器,在应用中心中安装“微信调试工具”,安装完成后,选择“服务器端调试”
(3)填写本地server的ip和端口号,调试工具会返回一个外部URL
(4)到公众号后台开发者配置信息中填写这个URL
(5)用微信向公众号发送消息测试

收起阅读 »

IOS平台:如何在离线打包时配置 3D Touch图标快捷菜单

App离线打包 iOS

Apple最新发布的iPhone 6s (plus) 设备支持3D touch功能,用户可通过重按屏幕上的图标,打开设置的菜单。H5+ SDK已经支持添加应用的快捷菜单,显示效果如下:

离线工程配置的方法

打开离线打包工程的info.plist文件,在文件中添加UIApplicationShortcutItems节点,并在节点下添加item。

各字段值规范如下表
各值的关系请参考上图
节点名 节点类型 可选性 节点说明 备注
UIApplicationShortcutItems Array 必选 图标快捷菜单节点声明
UIApplicationShortcutItemType String 必选 菜单类型
UIApplicationShortcutItemTitle String 必选 菜单项的标题
UIApplicationShortcutItemSubtitle String 可选 菜单项的副标题
UIApplicationShortcutItemIconFile String 可选 指定菜单项显示安装包内自带的图标,相对安装包的根目录位置 与UIApplicationShortcutItemIconType节点同时配置时优先级高于UIApplicationShortcutItemIconType
UIApplicationShortcutItemIconType String 可选 指定菜单项显示系统自带图标 取值可参考苹果官方文档
UIApplicationShortcutItemUserInfo String 可选 用户自定义的菜单项附加信息 附带信息可在应用内通过plus.runtime.arguments获取

App中处理快捷菜单项

请参考文档iOS平台支持3D Touch快捷菜单项

继续阅读 »

Apple最新发布的iPhone 6s (plus) 设备支持3D touch功能,用户可通过重按屏幕上的图标,打开设置的菜单。H5+ SDK已经支持添加应用的快捷菜单,显示效果如下:

离线工程配置的方法

打开离线打包工程的info.plist文件,在文件中添加UIApplicationShortcutItems节点,并在节点下添加item。

各字段值规范如下表
各值的关系请参考上图
节点名 节点类型 可选性 节点说明 备注
UIApplicationShortcutItems Array 必选 图标快捷菜单节点声明
UIApplicationShortcutItemType String 必选 菜单类型
UIApplicationShortcutItemTitle String 必选 菜单项的标题
UIApplicationShortcutItemSubtitle String 可选 菜单项的副标题
UIApplicationShortcutItemIconFile String 可选 指定菜单项显示安装包内自带的图标,相对安装包的根目录位置 与UIApplicationShortcutItemIconType节点同时配置时优先级高于UIApplicationShortcutItemIconType
UIApplicationShortcutItemIconType String 可选 指定菜单项显示系统自带图标 取值可参考苹果官方文档
UIApplicationShortcutItemUserInfo String 可选 用户自定义的菜单项附加信息 附带信息可在应用内通过plus.runtime.arguments获取

App中处理快捷菜单项

请参考文档iOS平台支持3D Touch快捷菜单项

收起阅读 »

iOS平台支持3D Touch快捷菜单项

3DTouch 5+App开发 iOS

iPhone6s(plus)设备已经支持3D Touch屏幕,HBuilder已支持添加应用的快捷菜单,效果如下:

配置快捷菜单项

打开应用的manifest.json文件,切换到代码视图,在plus -> distribute -> apple 下添加shortcuts节点,并配置各菜单项,数组中每项对应一个快捷菜单项:

    "shortcuts": [  
        {  
            "type": "share",  
            "title": "分 享",  
            "subtitle": "分享到微信、微博、QQ",  
            "icontype": "UIApplicationShortcutIconTypeShare"  
        },  
        {  
            "type": "about",  
            "title": "关 于",  
            "subtitle": "www.dcloud.io",  
            "iconfile": "sa.png",  
            "userinfo": {  
                "key3":"value3"  
            }  
        }  
    ],

uni-app项目将以上数据添加到"app-plus"->"distribute"->"ios"节点下
其中各字段值规范如下:

  • type: (必选)菜单项类型,字符串类型,用于标识菜单项
  • title: (必选)菜单项上显示的标题,字符串类型
  • subtitle: (可选)菜单项上显示的子标题,字符串类型
  • icontype: (可选)菜单项上显示的图标类型,字符串类型,取值参考iOS官方文档UIApplicationShortcutIconType
  • iconfile: (可选)菜单项上显示的图标文件,字符串类型,相对5+应用根目录路径,图标要求35x35分辨率,单色,参考Apple官方PS模板图
  • userinfo: (可选)菜单项上的自定义数据,JSON格式

App中处理快捷菜单项

判断应用是否通过快捷菜单项启动
plus.runtime.launcher的值为"shortcut"表示应用从快捷菜单项启动,示例如下:

if(plus.runtime.launcher=='shortcut'){  
     // ...  
}

判断快捷菜单项启动的参数
plus.runtime.arguments中保存所有快捷菜单项配置的值(JSON格式字符串),示例如下:

var cmd = JSON.parse(plus.runtime.arguments);  
console.log("Shortcut-plus.runtime.arguments: "+plus.runtime.arguments)  
var type=cmd&&cmd.type;  
switch(type){  
    case 'share':  
        // 用户点击了‘share'菜单项  
    break;  
    case 'about':  
        // 用户点击了’about'菜单项  
    break;  
    default:  
    break;  
}

plus.runtime.arguments的JSON格式字符串示例如下:
“{"type":"about","title":"关 于","subtitle": "www.dcloud.io","userinfo":{"key3":"value3"}}”
其中包括以下键值:

  • type: 菜单项类型,字符串类型
  • title: 菜单项上显示的标题,字符串类型
  • subtitle: 菜单项上显示的子标题,字符串类型
  • userinfo: 菜单项上的自定义数据

注:真机运行不生效,需提交App云端打包后才生效

实际用法参考HelloH5应用的“js/shortcut.js”
iOS平台5+SDK原生环境配置方法

uni-app网友经验分享:https://ask.dcloud.net.cn/article/36103

继续阅读 »

iPhone6s(plus)设备已经支持3D Touch屏幕,HBuilder已支持添加应用的快捷菜单,效果如下:

配置快捷菜单项

打开应用的manifest.json文件,切换到代码视图,在plus -> distribute -> apple 下添加shortcuts节点,并配置各菜单项,数组中每项对应一个快捷菜单项:

    "shortcuts": [  
        {  
            "type": "share",  
            "title": "分 享",  
            "subtitle": "分享到微信、微博、QQ",  
            "icontype": "UIApplicationShortcutIconTypeShare"  
        },  
        {  
            "type": "about",  
            "title": "关 于",  
            "subtitle": "www.dcloud.io",  
            "iconfile": "sa.png",  
            "userinfo": {  
                "key3":"value3"  
            }  
        }  
    ],

uni-app项目将以上数据添加到"app-plus"->"distribute"->"ios"节点下
其中各字段值规范如下:

  • type: (必选)菜单项类型,字符串类型,用于标识菜单项
  • title: (必选)菜单项上显示的标题,字符串类型
  • subtitle: (可选)菜单项上显示的子标题,字符串类型
  • icontype: (可选)菜单项上显示的图标类型,字符串类型,取值参考iOS官方文档UIApplicationShortcutIconType
  • iconfile: (可选)菜单项上显示的图标文件,字符串类型,相对5+应用根目录路径,图标要求35x35分辨率,单色,参考Apple官方PS模板图
  • userinfo: (可选)菜单项上的自定义数据,JSON格式

App中处理快捷菜单项

判断应用是否通过快捷菜单项启动
plus.runtime.launcher的值为"shortcut"表示应用从快捷菜单项启动,示例如下:

if(plus.runtime.launcher=='shortcut'){  
     // ...  
}

判断快捷菜单项启动的参数
plus.runtime.arguments中保存所有快捷菜单项配置的值(JSON格式字符串),示例如下:

var cmd = JSON.parse(plus.runtime.arguments);  
console.log("Shortcut-plus.runtime.arguments: "+plus.runtime.arguments)  
var type=cmd&&cmd.type;  
switch(type){  
    case 'share':  
        // 用户点击了‘share'菜单项  
    break;  
    case 'about':  
        // 用户点击了’about'菜单项  
    break;  
    default:  
    break;  
}

plus.runtime.arguments的JSON格式字符串示例如下:
“{"type":"about","title":"关 于","subtitle": "www.dcloud.io","userinfo":{"key3":"value3"}}”
其中包括以下键值:

  • type: 菜单项类型,字符串类型
  • title: 菜单项上显示的标题,字符串类型
  • subtitle: 菜单项上显示的子标题,字符串类型
  • userinfo: 菜单项上的自定义数据

注:真机运行不生效,需提交App云端打包后才生效

实际用法参考HelloH5应用的“js/shortcut.js”
iOS平台5+SDK原生环境配置方法

uni-app网友经验分享:https://ask.dcloud.net.cn/article/36103

收起阅读 »

如何动态兼容沉浸式状态栏模式

UserAgent navigator 沉浸式状态栏 5+App开发

HBuilder6.6.1版本已经完全支持沉浸式状态栏,可以下载最新版本HelloH5应用体验。
各平台配置参考:

  1. Android平台设置沉浸式状态栏显示效果
  2. iOS平台设置沉浸式状态栏显示效果

由于各系统版本的限制,沉浸式状态栏对系统有要求(Android4.4及以上、iOS7.0及以上),如果要兼容各系统版本,需要动态判断当前环境是否支持沉浸式状态栏以及系统状态栏的高度:

使用5+API

  • 判断当前环境是否支持沉浸式状态栏
    plus.navigator.isImmersedStatusbar()
    如果当前支持沉浸式状态栏则返回true,否则返回false。
  • 获取当前系统状态栏高度
    plus.navigator.getStatusbarHeight()
    获取系统状态栏高度,Number类型。
    其单位是逻辑像素值,即css中可直接使用的像素值,可能存在小数点。

实际用法参考HelloH5应用的“plus/doc.html”:

    // 创建加载内容窗口  
    var topoffset='45px';  
    if(plus.navigator.isImmersedStatusbar()){// 兼容immersed状态栏模式  
        // 获取状态栏高度并根据业务需求处理,这里重新计算了子窗口的偏移位置  
        topoffset=(Math.round(plus.navigator.getStatusbarHeight())+45)+'px';  
    }  
    // 使用偏移位置创建子窗口  
    wc=plus.webview.create(null,'doccontent',{top:topoffset,bottom:'0px',bounce:'vertical',bounceBackground:'#FFFFFF'});

通过userAgent判断
5+API需要在plusready事件后才能调用,通常此事件在DOM加载渲染后才会触发,无法再渲染前根据不同的状态来设置css。
为了解决此问题,在支持5+API运行环境的userAgent中特定字段Html5Plus/1.0后添加Immersed标识,如下:
"Html5Plus/1.0 (Immersed/30)"
其中Immersed/后的30表示状态栏的高度,单位为逻辑像素值。

可以使用正则表达式进行获取:

var immersed = 0;  
var ms=(/Html5Plus\/.+\s\(.*(Immersed\/(\d+\.?\d*).*)\)/gi).exec(navigator.userAgent);  
if(ms&&ms.length>=3){ // 当前环境为沉浸式状态栏模式  
    immersed=parseFloat(ms[2]);// 获取状态栏的高度  
}

immersed值如果大于0则表示当前环境支持沉浸式状态栏。
获取状态栏高度后,可以使用js动态修改DOM元素的css属性来设置样式,如设置界面头区域的顶部内边距为状态栏的高度(避免系统状态栏与界面头重叠),示例如下:

var t=document.getElementById('header');  
t&&t.style.paddingTop=immersed+'px';

具体项目中可根据界面设计,灵活使用immersed值来动态适配各种效果。
完整用法可参考HelloH5应用中的“js/immersed.js”

继续阅读 »

HBuilder6.6.1版本已经完全支持沉浸式状态栏,可以下载最新版本HelloH5应用体验。
各平台配置参考:

  1. Android平台设置沉浸式状态栏显示效果
  2. iOS平台设置沉浸式状态栏显示效果

由于各系统版本的限制,沉浸式状态栏对系统有要求(Android4.4及以上、iOS7.0及以上),如果要兼容各系统版本,需要动态判断当前环境是否支持沉浸式状态栏以及系统状态栏的高度:

使用5+API

  • 判断当前环境是否支持沉浸式状态栏
    plus.navigator.isImmersedStatusbar()
    如果当前支持沉浸式状态栏则返回true,否则返回false。
  • 获取当前系统状态栏高度
    plus.navigator.getStatusbarHeight()
    获取系统状态栏高度,Number类型。
    其单位是逻辑像素值,即css中可直接使用的像素值,可能存在小数点。

实际用法参考HelloH5应用的“plus/doc.html”:

    // 创建加载内容窗口  
    var topoffset='45px';  
    if(plus.navigator.isImmersedStatusbar()){// 兼容immersed状态栏模式  
        // 获取状态栏高度并根据业务需求处理,这里重新计算了子窗口的偏移位置  
        topoffset=(Math.round(plus.navigator.getStatusbarHeight())+45)+'px';  
    }  
    // 使用偏移位置创建子窗口  
    wc=plus.webview.create(null,'doccontent',{top:topoffset,bottom:'0px',bounce:'vertical',bounceBackground:'#FFFFFF'});

通过userAgent判断
5+API需要在plusready事件后才能调用,通常此事件在DOM加载渲染后才会触发,无法再渲染前根据不同的状态来设置css。
为了解决此问题,在支持5+API运行环境的userAgent中特定字段Html5Plus/1.0后添加Immersed标识,如下:
"Html5Plus/1.0 (Immersed/30)"
其中Immersed/后的30表示状态栏的高度,单位为逻辑像素值。

可以使用正则表达式进行获取:

var immersed = 0;  
var ms=(/Html5Plus\/.+\s\(.*(Immersed\/(\d+\.?\d*).*)\)/gi).exec(navigator.userAgent);  
if(ms&&ms.length>=3){ // 当前环境为沉浸式状态栏模式  
    immersed=parseFloat(ms[2]);// 获取状态栏的高度  
}

immersed值如果大于0则表示当前环境支持沉浸式状态栏。
获取状态栏高度后,可以使用js动态修改DOM元素的css属性来设置样式,如设置界面头区域的顶部内边距为状态栏的高度(避免系统状态栏与界面头重叠),示例如下:

var t=document.getElementById('header');  
t&&t.style.paddingTop=immersed+'px';

具体项目中可根据界面设计,灵活使用immersed值来动态适配各种效果。
完整用法可参考HelloH5应用中的“js/immersed.js”

收起阅读 »

Android平台设置沉浸式状态栏显示效果

沉浸式状态栏 5+App开发 Android navigator

系列文章导航:
状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别
Android平台设置沉浸式状态栏显示效果
iOS平台设置沉浸式状态栏显示效果

正文:

应用可视区域到系统状态栏下透明显示效果,如下图所示:

此模式下应用占用全屏区域,而系统状态栏会拦截用户操作事件,此时需要预留出系统状态栏高度。
获取系统状态栏高度及沉浸式状态判断参考:如何动态判断沉浸式状态栏模式

HBuilder创建的应用默认不使用沉浸式状态栏样式,需要进行如下配置开启:
打开应用的manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true。

"plus": {  
    "statusbar": {  
        "immersed": true  
    }  
}

如下图所示:

保存后提交App云端打包

注意:

  1. 真机运行不生效,需提交App云端打包后才生效;
  2. 此功能仅在Android4.4及以上系统有效。

状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别
Android平台5+SDK原生环境配置方法
iOS平台设置沉浸式状态栏显示效果

继续阅读 »

系列文章导航:
状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别
Android平台设置沉浸式状态栏显示效果
iOS平台设置沉浸式状态栏显示效果

正文:

应用可视区域到系统状态栏下透明显示效果,如下图所示:

此模式下应用占用全屏区域,而系统状态栏会拦截用户操作事件,此时需要预留出系统状态栏高度。
获取系统状态栏高度及沉浸式状态判断参考:如何动态判断沉浸式状态栏模式

HBuilder创建的应用默认不使用沉浸式状态栏样式,需要进行如下配置开启:
打开应用的manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true。

"plus": {  
    "statusbar": {  
        "immersed": true  
    }  
}

如下图所示:

保存后提交App云端打包

注意:

  1. 真机运行不生效,需提交App云端打包后才生效;
  2. 此功能仅在Android4.4及以上系统有效。

状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别
Android平台5+SDK原生环境配置方法
iOS平台设置沉浸式状态栏显示效果

收起阅读 »

怎样选择微信外包机构

外包

在互联网的浪潮下,不少传统公司都纷纷开通了自己的微信公众平台,因此微信公众平台的二次开发也如火如荼,那么请务必记住以下这几条,以后有做微信外包的机构找你时,就拿这几条去观察,虽然无法保证你会赚钱,但至少能保证你不会被坑蒙拐骗了还帮流氓数钱。
首先查看他们做过哪些案例,这是最容易想到的,但要从几个步骤入手。
1、案例究竟是不是他们做的,这个可以要求把牛逼吹上天的微信外包机构给个甲方的联系方式,要座机哦,去调查,问问,那个外包机构是不是他们的年度服务商,所讲的案例的数字是不是真的,例如一周涨了50万微信粉丝,微信上一个月流水过百万,是不是这个外包机构干出来的。要是说对方负责人休假电话不方便给啊,对方联系人已经离职啊啥的,这些借口别听,可以送客了。
顺道给大家普及个常识,大品牌公司在选择年度服务商的时候会通过比稿的方式来选择外包机构,比稿通过的,成为年度服务商,比稿不通过的,会拿到比稿费,他们比稿里所陈述的方案会被选上的外包机构拿来用。
因此,不少外包服务机构会到处吹嘘某公司是他们服务的,并在网站上挂着一大堆牛逼品牌的LOGO,其中90%以上都只是跟他们的工作人员见过一面,或者是外包产业链里最低端的分包商而已,例如很多没见过世面的公司,经常会因此而激动地向别人吹嘘那谁谁谁是他们服务过的客户,事实上嘛,前面告诉过你啦。
2、他们所说的案例做得怎样,既然是案例,肯定有微信公众号,上去看看他们公众号的内容做得如何,微博上给微信拉粉的素材创作水平,天天都有促销信息、配图难看、行文如嚼蜡、样式死板、最近还顶风作案诱导用户把内容分享到朋友圈的,你想被封号就找他们吧。
3、看名声和网络形象
诚信的重要性就不用多说了,用人德为先,寻找靠谱的微信外包机构同样如此。除了向同行打听这家公司的专业度和诚信度,或者到百度上搜索这家公司的名字,看看这个公司是不是在自己的百度百科词条下面紧跟着就是一堆离职员工的讨薪控诉,或者一些企划经理的帖子骂他们狗屎傻逼饭桶。
最主要的,是要上新浪微博和腾讯微博去看这家公司的官方微博做得怎样,是不是三十多万粉丝,但平均每条微博的内容转发数为0,或者转发者都是自己公司的员工,在下面留言是不是及时有回复。一个真正重视口碑和名声的微信外包机构会很珍惜自己的羽毛,重视自己在社会化媒体上的用户关系经营的。那些页面大气,内容精致原创,和用户互动热烈,观点对口,这个代运营公司值得谈一谈。  
4、观察团队素养
微信外包机构的最大资产和财富就是人才,有实力的外包机构聘请的人员素质都不会差,对于外包团队成员的观察是必不可少的一环。看团队成员是否具备一定的职业成熟度。至少谈吐要过得去吧,形象可以不要求帅漂亮,但至少要顺眼,着装得体,上得了台面。跟他们闲聊时可以再问问他们微博和微信的差别,看他们能掰出多少不同来,说得系统全面的,可以合作。

继续阅读 »

在互联网的浪潮下,不少传统公司都纷纷开通了自己的微信公众平台,因此微信公众平台的二次开发也如火如荼,那么请务必记住以下这几条,以后有做微信外包的机构找你时,就拿这几条去观察,虽然无法保证你会赚钱,但至少能保证你不会被坑蒙拐骗了还帮流氓数钱。
首先查看他们做过哪些案例,这是最容易想到的,但要从几个步骤入手。
1、案例究竟是不是他们做的,这个可以要求把牛逼吹上天的微信外包机构给个甲方的联系方式,要座机哦,去调查,问问,那个外包机构是不是他们的年度服务商,所讲的案例的数字是不是真的,例如一周涨了50万微信粉丝,微信上一个月流水过百万,是不是这个外包机构干出来的。要是说对方负责人休假电话不方便给啊,对方联系人已经离职啊啥的,这些借口别听,可以送客了。
顺道给大家普及个常识,大品牌公司在选择年度服务商的时候会通过比稿的方式来选择外包机构,比稿通过的,成为年度服务商,比稿不通过的,会拿到比稿费,他们比稿里所陈述的方案会被选上的外包机构拿来用。
因此,不少外包服务机构会到处吹嘘某公司是他们服务的,并在网站上挂着一大堆牛逼品牌的LOGO,其中90%以上都只是跟他们的工作人员见过一面,或者是外包产业链里最低端的分包商而已,例如很多没见过世面的公司,经常会因此而激动地向别人吹嘘那谁谁谁是他们服务过的客户,事实上嘛,前面告诉过你啦。
2、他们所说的案例做得怎样,既然是案例,肯定有微信公众号,上去看看他们公众号的内容做得如何,微博上给微信拉粉的素材创作水平,天天都有促销信息、配图难看、行文如嚼蜡、样式死板、最近还顶风作案诱导用户把内容分享到朋友圈的,你想被封号就找他们吧。
3、看名声和网络形象
诚信的重要性就不用多说了,用人德为先,寻找靠谱的微信外包机构同样如此。除了向同行打听这家公司的专业度和诚信度,或者到百度上搜索这家公司的名字,看看这个公司是不是在自己的百度百科词条下面紧跟着就是一堆离职员工的讨薪控诉,或者一些企划经理的帖子骂他们狗屎傻逼饭桶。
最主要的,是要上新浪微博和腾讯微博去看这家公司的官方微博做得怎样,是不是三十多万粉丝,但平均每条微博的内容转发数为0,或者转发者都是自己公司的员工,在下面留言是不是及时有回复。一个真正重视口碑和名声的微信外包机构会很珍惜自己的羽毛,重视自己在社会化媒体上的用户关系经营的。那些页面大气,内容精致原创,和用户互动热烈,观点对口,这个代运营公司值得谈一谈。  
4、观察团队素养
微信外包机构的最大资产和财富就是人才,有实力的外包机构聘请的人员素质都不会差,对于外包团队成员的观察是必不可少的一环。看团队成员是否具备一定的职业成熟度。至少谈吐要过得去吧,形象可以不要求帅漂亮,但至少要顺眼,着装得体,上得了台面。跟他们闲聊时可以再问问他们微博和微信的差别,看他们能掰出多少不同来,说得系统全面的,可以合作。

收起阅读 »

多页签使用webview下拉刷新的实现思路

下拉刷新

原先使用demo中" 选项卡切换+下拉刷新"(pullrefresh_with_tab.html) 发现卡顿非常严重,在数据量多或者多滑动几次后出现卡顿情况。

最近有个新思路,使用demo中“默认实现”来实现 选项卡切换的功能,缺点是不能实现选项卡的滑动切换,但是卡顿现象消失。

大体思路:父页面放置选项卡,子页面 设置多个mui-scroll ,通过选项卡的tap事件,来控制 子页面的mui-scroll的显示和隐藏。

继续阅读 »

原先使用demo中" 选项卡切换+下拉刷新"(pullrefresh_with_tab.html) 发现卡顿非常严重,在数据量多或者多滑动几次后出现卡顿情况。

最近有个新思路,使用demo中“默认实现”来实现 选项卡切换的功能,缺点是不能实现选项卡的滑动切换,但是卡顿现象消失。

大体思路:父页面放置选项卡,子页面 设置多个mui-scroll ,通过选项卡的tap事件,来控制 子页面的mui-scroll的显示和隐藏。

收起阅读 »

论软件外包中后期维护的重要性

软件外包过程中,发包方应该在外包协议中要求承包方在系统计划的生命周期中始终提供支持和维护,而不是仅仅负责最初的开发项目。

这就要求在软件外包的时候,不仅要考虑眼前的使用,还要考虑到软件的长期使用,所以外包协议中一定要明确软件的维护期,要求承包方在包括维护期内始终提供支持。

另外,即使需要为承包方提供足够的经济激励,也要让承包方留住最初的开发人员,并让这些人维护、补充你的应用程序。

同时在软件外包协议中还应明确,承包方应确保即使进入维护期,也会有开发人员来做维护。我们应该与最初开发者保持联系,以确保应用程序能够尽快得到修补和维护。因为如果让新手来维护,他们通常速度很慢,而且常犯错误。

软件外包的承包方虽然允许有新人加入维护团队,但承包方必须确保新手是逐渐加入的,这样他们才能得到足够的督导和锻炼。而且对于维护团队,也应该给他们足够的经济激励,使他们有动力快速完成维护任务。

如果承包方对于承担长期的软件维护有困难的话,可以用一种比较安全的方法来替代,让本公司的员工在项目中扮演学徒的角色,熟悉和并了解软件架构。随着时间推移,随着内部员工逐渐学习提高,就可以考虑将软件维护的责任从软件外包团队手中转交给公司内部的人员。

继续阅读 »

软件外包过程中,发包方应该在外包协议中要求承包方在系统计划的生命周期中始终提供支持和维护,而不是仅仅负责最初的开发项目。

这就要求在软件外包的时候,不仅要考虑眼前的使用,还要考虑到软件的长期使用,所以外包协议中一定要明确软件的维护期,要求承包方在包括维护期内始终提供支持。

另外,即使需要为承包方提供足够的经济激励,也要让承包方留住最初的开发人员,并让这些人维护、补充你的应用程序。

同时在软件外包协议中还应明确,承包方应确保即使进入维护期,也会有开发人员来做维护。我们应该与最初开发者保持联系,以确保应用程序能够尽快得到修补和维护。因为如果让新手来维护,他们通常速度很慢,而且常犯错误。

软件外包的承包方虽然允许有新人加入维护团队,但承包方必须确保新手是逐渐加入的,这样他们才能得到足够的督导和锻炼。而且对于维护团队,也应该给他们足够的经济激励,使他们有动力快速完成维护任务。

如果承包方对于承担长期的软件维护有困难的话,可以用一种比较安全的方法来替代,让本公司的员工在项目中扮演学徒的角色,熟悉和并了解软件架构。随着时间推移,随着内部员工逐渐学习提高,就可以考虑将软件维护的责任从软件外包团队手中转交给公司内部的人员。

收起阅读 »

MUI 项目模板之 “酒店预订”

酒店预订 Gulp MVVM 项目模板 技术分享 开源

模板简介

使用 MUI 的开发者越来越多,基于 MUI 的开发的 App 也越来越多,一些开发者也提到了如何使用 MV* 模式进行编码相关的问题。

与此同时,mui team 也希望进一步方便开发者,为大家提供更多类型的 APP 模板,所以,我们在 GitHub 上放出了一个 “酒店预定” 的 APP 模板。

开源地址: https://github.com/dcloudio/casecode/tree/master/hotel
image

mui 作为一个 UI 框架,并未对 “开发模式、构建方式、模块化方案” 等做出任何限制,当然,每一个开发者或 team 都可以采用熟悉且合适项目的 “库、框架、工具” 去开展开发工作。

在这个 App 模板中:

  1. 使用 Gulp 作为构建工具
  2. 使用 AMD 模块组织代码
  3. 使用 MVVM 进行双向绑定

在这里,不会对以上提到的 “知识点或工具” 进行深入讲解,不懂的同学可以问下 “google”。

使用 AMD 组织代码

因为基于 5+ 的多 WebView 模式,这个项目模板并不是单页的,所以针对每一个页面都有且只有一个入口模块,而对于 “mui 相关文件以及 mui 的扩展组件”,还是用传统的 script 标签方式引入页面中。
示例如下:

<script src="../mui/js/mui.min.js"></script>  
<script src="../mui/js/mui.picker.min.js"></script>  
<script src="../libs/ems.js" data-main='./index.js'></script>

注意:上边代码中 “data-original” 为真实的 “src”,因为 ask 会对 src 做了防 xss 处理。

可以看到前两个 “script” 分别引用了 “mui” 和 “mui.picker”,而最后一个是通过模块加载器进行加载的,指定了入口模块 “./index.js”

模块代码:

define(function(require, exports, module) {  

    //设置状态栏颜色(for ios)  
    mui.init({  
        "statusBarBackground": "#099FDE"  
    });  

    mui.plusReady(function() {  
        plus.navigator.setStatusBarStyle("UIStatusBarStyleBlackOpaque");  
    });  

    var utils = require('../libs/utils.js');  
    var pageHepler = require('../common/page-helper');  
    var hotelPriceFilter = require('../controls/hotel-price-filter/main');  

    var self = exports;  

    self.dayNum = 1;  
    self.beginDate = utils.formatDate(new Date(), 'yyyy-MM-dd');  

    ...  

    ...  

    //设置价格过滤条件  
    self.setPriceFilter = function() {  
        hotelPriceFilter.show(function(rs) {  
            self.priceRange = rs.priceRange;  
            self.star = rs.star;  
        });  
    };  

    //初始化页面辅助模块  
    pageHepler.init({  
        handler: self,  
        mvvm: true  
    });  

    ...  
        ...  

});

如上述代码,在每个页面的 "入口模块" (一文件一模块、一模块一文件),在模块中通过 require 去引用需要的公共模块,模块有可能是一个 "实用函数模块、完整的获立组件,或者其他形式"。

基于 VUE 的双向绑定 (MVVM)

目前流行的 mv* 框架有很多,其中 mvvm 框架也有不少,在这个 “酒店 app 模板” 中,我们期望利用 mvvm 的 “双向绑定” 简化我们的数据绑定以及 dom 操作,经过比较,我们选用了比较轻量的 vue.js

在此 app 模板中,将 vue 封在了 /common/page-helper.js 中,主要代码如下:

...  
...  
//mvvm 绑定  
if (options.mvvm) {  
    if (options.mvvm === true) {  
        options.mvvm = {};  
    }  
    options.mvvm.el = options.mvvm.el || options.holder;  
    options.mvvm.data = options.mvvm.data || options.handler;  
    options.mvvm.methods = options.mvvm.methods || options.handler;  
    options.mvvm.computed = options.mvvm.computed || options.handler.computed;  
    exports.vue = new Vue(options.mvvm);  
}  
...  
...

完整的代码见 GitHub 开源 Repository。

使用 Gulp 构建

在这个模板中,并没有进行文件合并,而只进行了代码 "混淆和压缩",构建脚本如下:

var gulp = require("gulp");  
var pkg = require("./package.json");  
var uglify = require("gulp-uglify");  
var minifycss = require('gulp-minify-css');  
var del = require('del');  
var concat = require('gulp-concat');  
var rename = require('gulp-rename');  
var header = require('gulp-header');  

var banner = ['/**',  
    ' * <%= name %> - <%= description %>',  
    ' * @version v<%= version %>',  
    ' * @link <%= homepage %>',  
    ' * @author <%= author %>',  
    ' * @license <%= license %>',  
    ' */',  
    ''  
].join('\r\n');  

gulp.task('clear', function(cb) {  
    del(['build'], cb);  
});  

gulp.task('build', ["clear"], function() {  
    //css  
    gulp.src(["./src/**/*.css"])  
        .pipe(minifycss())  
        .pipe(header(banner, pkg))  
        .pipe(gulp.dest("./build/"));  
    //js  
    gulp.src(["./src/**/*.js"])  
        .pipe(uglify({  
            mangle: {  
                except: ['require']  
            }  
        }))  
        .pipe(header(banner, pkg))  
        .pipe(gulp.dest("./build/"));  
    //html  
    gulp.src(["./src/**/*.html"])  
        .pipe(gulp.dest("./build/"));  
    //ttf  
    gulp.src(["./src/**/*.ttf"])  
        .pipe(gulp.dest("./build/"));  
        ...  
        ...   
    //ico  
    gulp.src(["./src/**/*.ico"])  
        .pipe(gulp.dest("./build/"));  
    //png  
    gulp.src(["./src/**/*.png"])  
        .pipe(gulp.dest("./build/"));  
    //json  
    gulp.src(["./src/**/*.json"])  
        .pipe(gulp.dest("./build/"));  
});  

gulp.task('default', ["clear", "build"]);

执行构建脚本前,需要分别 “全局和本地” 都安装 Gulp ,同时需要安装相关 Gulp 插件

全局安装 Gulp

[sudo] npm install gulp -g

本地安装 Gulp 以及构建依赖的 Gulp 插件

npm install

从前边的构建脚中可以看到共有三个 “task”,分别为:

  1. clear : 用于清理已生成的文件
  2. build : 生成新的 release 版本
  3. default : 会依次执行 clear 、build

执行 Gulp 任务

gulp [clear|build|default]

-- EOF --

继续阅读 »

模板简介

使用 MUI 的开发者越来越多,基于 MUI 的开发的 App 也越来越多,一些开发者也提到了如何使用 MV* 模式进行编码相关的问题。

与此同时,mui team 也希望进一步方便开发者,为大家提供更多类型的 APP 模板,所以,我们在 GitHub 上放出了一个 “酒店预定” 的 APP 模板。

开源地址: https://github.com/dcloudio/casecode/tree/master/hotel
image

mui 作为一个 UI 框架,并未对 “开发模式、构建方式、模块化方案” 等做出任何限制,当然,每一个开发者或 team 都可以采用熟悉且合适项目的 “库、框架、工具” 去开展开发工作。

在这个 App 模板中:

  1. 使用 Gulp 作为构建工具
  2. 使用 AMD 模块组织代码
  3. 使用 MVVM 进行双向绑定

在这里,不会对以上提到的 “知识点或工具” 进行深入讲解,不懂的同学可以问下 “google”。

使用 AMD 组织代码

因为基于 5+ 的多 WebView 模式,这个项目模板并不是单页的,所以针对每一个页面都有且只有一个入口模块,而对于 “mui 相关文件以及 mui 的扩展组件”,还是用传统的 script 标签方式引入页面中。
示例如下:

<script src="../mui/js/mui.min.js"></script>  
<script src="../mui/js/mui.picker.min.js"></script>  
<script src="../libs/ems.js" data-main='./index.js'></script>

注意:上边代码中 “data-original” 为真实的 “src”,因为 ask 会对 src 做了防 xss 处理。

可以看到前两个 “script” 分别引用了 “mui” 和 “mui.picker”,而最后一个是通过模块加载器进行加载的,指定了入口模块 “./index.js”

模块代码:

define(function(require, exports, module) {  

    //设置状态栏颜色(for ios)  
    mui.init({  
        "statusBarBackground": "#099FDE"  
    });  

    mui.plusReady(function() {  
        plus.navigator.setStatusBarStyle("UIStatusBarStyleBlackOpaque");  
    });  

    var utils = require('../libs/utils.js');  
    var pageHepler = require('../common/page-helper');  
    var hotelPriceFilter = require('../controls/hotel-price-filter/main');  

    var self = exports;  

    self.dayNum = 1;  
    self.beginDate = utils.formatDate(new Date(), 'yyyy-MM-dd');  

    ...  

    ...  

    //设置价格过滤条件  
    self.setPriceFilter = function() {  
        hotelPriceFilter.show(function(rs) {  
            self.priceRange = rs.priceRange;  
            self.star = rs.star;  
        });  
    };  

    //初始化页面辅助模块  
    pageHepler.init({  
        handler: self,  
        mvvm: true  
    });  

    ...  
        ...  

});

如上述代码,在每个页面的 "入口模块" (一文件一模块、一模块一文件),在模块中通过 require 去引用需要的公共模块,模块有可能是一个 "实用函数模块、完整的获立组件,或者其他形式"。

基于 VUE 的双向绑定 (MVVM)

目前流行的 mv* 框架有很多,其中 mvvm 框架也有不少,在这个 “酒店 app 模板” 中,我们期望利用 mvvm 的 “双向绑定” 简化我们的数据绑定以及 dom 操作,经过比较,我们选用了比较轻量的 vue.js

在此 app 模板中,将 vue 封在了 /common/page-helper.js 中,主要代码如下:

...  
...  
//mvvm 绑定  
if (options.mvvm) {  
    if (options.mvvm === true) {  
        options.mvvm = {};  
    }  
    options.mvvm.el = options.mvvm.el || options.holder;  
    options.mvvm.data = options.mvvm.data || options.handler;  
    options.mvvm.methods = options.mvvm.methods || options.handler;  
    options.mvvm.computed = options.mvvm.computed || options.handler.computed;  
    exports.vue = new Vue(options.mvvm);  
}  
...  
...

完整的代码见 GitHub 开源 Repository。

使用 Gulp 构建

在这个模板中,并没有进行文件合并,而只进行了代码 "混淆和压缩",构建脚本如下:

var gulp = require("gulp");  
var pkg = require("./package.json");  
var uglify = require("gulp-uglify");  
var minifycss = require('gulp-minify-css');  
var del = require('del');  
var concat = require('gulp-concat');  
var rename = require('gulp-rename');  
var header = require('gulp-header');  

var banner = ['/**',  
    ' * <%= name %> - <%= description %>',  
    ' * @version v<%= version %>',  
    ' * @link <%= homepage %>',  
    ' * @author <%= author %>',  
    ' * @license <%= license %>',  
    ' */',  
    ''  
].join('\r\n');  

gulp.task('clear', function(cb) {  
    del(['build'], cb);  
});  

gulp.task('build', ["clear"], function() {  
    //css  
    gulp.src(["./src/**/*.css"])  
        .pipe(minifycss())  
        .pipe(header(banner, pkg))  
        .pipe(gulp.dest("./build/"));  
    //js  
    gulp.src(["./src/**/*.js"])  
        .pipe(uglify({  
            mangle: {  
                except: ['require']  
            }  
        }))  
        .pipe(header(banner, pkg))  
        .pipe(gulp.dest("./build/"));  
    //html  
    gulp.src(["./src/**/*.html"])  
        .pipe(gulp.dest("./build/"));  
    //ttf  
    gulp.src(["./src/**/*.ttf"])  
        .pipe(gulp.dest("./build/"));  
        ...  
        ...   
    //ico  
    gulp.src(["./src/**/*.ico"])  
        .pipe(gulp.dest("./build/"));  
    //png  
    gulp.src(["./src/**/*.png"])  
        .pipe(gulp.dest("./build/"));  
    //json  
    gulp.src(["./src/**/*.json"])  
        .pipe(gulp.dest("./build/"));  
});  

gulp.task('default', ["clear", "build"]);

执行构建脚本前,需要分别 “全局和本地” 都安装 Gulp ,同时需要安装相关 Gulp 插件

全局安装 Gulp

[sudo] npm install gulp -g

本地安装 Gulp 以及构建依赖的 Gulp 插件

npm install

从前边的构建脚中可以看到共有三个 “task”,分别为:

  1. clear : 用于清理已生成的文件
  2. build : 生成新的 release 版本
  3. default : 会依次执行 clear 、build

执行 Gulp 任务

gulp [clear|build|default]

-- EOF --

收起阅读 »

js实现对图片的二进制流md5计算

js实现对图片的二进制流md5计算,没找到好的办法,所以自己写了一个,分享出来大家参考。

//计算图片md5  
        function img_MD5(img_path,callback) {  
            plus.io.resolveLocalFileSystemURL(img_path, function(entry) {  
                var fileReader = new plus.io.FileReader();  
                fileReader.readAsDataURL(entry);  
                fileReader.onloadend = function(evt) {  
                    var format="image/jpeg";  
                    //抽取DataURL中的数据部分,从Base64格式转换为二进制格式  
                    var bin = atob(evt.target.result.split(',')[1]);  
                    //创建空的Uint8Array  
                    var buffer = new Uint8Array(bin.length);  
                    //将图像数据逐字节放入Uint8Array中  
                    for (var i = 0; i < bin.length; i++) {  
                        buffer[i] = bin.charCodeAt(i);  
                    };  
                    //利用Uint8Array创建Blob对象  
                    blob = new Blob([buffer.buffer], {type : format});  
                    var fileReader1 = new FileReader();  
                    fileReader1.readAsBinaryString(blob);  
                    fileReader1.onload = function(evt) {  
                        if (evt.target.readyState == FileReader.DONE) {  
                            var imgblob = evt.target.result;  
                            var sparkMD5 = new SparkMD5();  
                            sparkMD5.appendBinary(imgblob);  
                            var MD5 = sparkMD5.end();  
                            console.log("MD5:" + MD5);  
callback(MD5)  
                        }  
                    };  
                }  
            }, function(e) {  
                console.log("Resolve file URL failed: " + e.message);  
            });  
        }  
//使用方法  
var url=document.getElementById("ID").src;  
img_MD5(url,function (md5){  
                    console.log(md5)  
                })

对二进制流进行md5加密需要用spark-md5.js

最新发现本地图片nativeObj 里的bitmap.toBase64Data();拿到Base64是经过压缩的,拿到的是jepg;所以要用本地io把图片Base64读出来。

继续阅读 »

js实现对图片的二进制流md5计算,没找到好的办法,所以自己写了一个,分享出来大家参考。

//计算图片md5  
        function img_MD5(img_path,callback) {  
            plus.io.resolveLocalFileSystemURL(img_path, function(entry) {  
                var fileReader = new plus.io.FileReader();  
                fileReader.readAsDataURL(entry);  
                fileReader.onloadend = function(evt) {  
                    var format="image/jpeg";  
                    //抽取DataURL中的数据部分,从Base64格式转换为二进制格式  
                    var bin = atob(evt.target.result.split(',')[1]);  
                    //创建空的Uint8Array  
                    var buffer = new Uint8Array(bin.length);  
                    //将图像数据逐字节放入Uint8Array中  
                    for (var i = 0; i < bin.length; i++) {  
                        buffer[i] = bin.charCodeAt(i);  
                    };  
                    //利用Uint8Array创建Blob对象  
                    blob = new Blob([buffer.buffer], {type : format});  
                    var fileReader1 = new FileReader();  
                    fileReader1.readAsBinaryString(blob);  
                    fileReader1.onload = function(evt) {  
                        if (evt.target.readyState == FileReader.DONE) {  
                            var imgblob = evt.target.result;  
                            var sparkMD5 = new SparkMD5();  
                            sparkMD5.appendBinary(imgblob);  
                            var MD5 = sparkMD5.end();  
                            console.log("MD5:" + MD5);  
callback(MD5)  
                        }  
                    };  
                }  
            }, function(e) {  
                console.log("Resolve file URL failed: " + e.message);  
            });  
        }  
//使用方法  
var url=document.getElementById("ID").src;  
img_MD5(url,function (md5){  
                    console.log(md5)  
                })

对二进制流进行md5加密需要用spark-md5.js

最新发现本地图片nativeObj 里的bitmap.toBase64Data();拿到Base64是经过压缩的,拿到的是jepg;所以要用本地io把图片Base64读出来。

收起阅读 »

关于微信支付返回-1错误以及无法打开微信支付界面的问题

支付 微信 微信支付 Payment

翻了下论坛里面,到处都是问-1错误的,这里我把我自己踩过的坑分享一下,希望对大家开发微信支付有些帮助。
我已经测试过的原因如下:
1,微信支付只支持打包之后安装到手机上进行测试,不支持真机调试,所以骚年们,打包之后再试吧
2,请在打包测试之前,一定要确认和官方的回调字符串是不是一模一样,参数键值不能多也不能少,为了确保成功还是相信官方不会忽悠你的!
这段代码是dcloud官方微信支付返回的字符串,大家可以把自己的字符串拿出来比对一下,确认一致,那就打包测试吧

{"retcode":0,  
"retmsg":"ok",  
"appid":"wx041********d61297",  
"noncestr":"29dfdc1**d9c7ef283cad8ecb1448c8",  
"package":"Sign=WXPay",  
"partnerid":"123*****01",  
"prepayid":"52010***0151019c3820473613388ca",  
"timestamp":1445239300,  
"sign":"16a5efa393b50*****4d6d76e1b2ff703d27"}

3,如果以上两点都已经确认没问题了但是还是无法吊起支付,那么就去微信开放平台https://open.weixin.qq.com/确认下你的应用签名和包名与你在平台上配置的签名和应用包名是不是相同吧,如何获取应用签名这个论坛里面有教材,实在不知道的去腾讯应用宝市场发布下应用,那里也有教程如何获取app的签名,微信开放平台签名修改之后需要等一段时间才会生效,所有如果确认是签名或者包名问题,下班之前再改吧,不然一天白瞎了
特别说一下,这里的签名是长度为32位的MD5字符串
4,ios和安卓在获取支付通道的时候有个现象,他们在支付通道数组对象里面的先后顺序不一样,如果你们在测试微信支付的时候报什么620XX错误,那就检查下支付通道对象获取的对不对
5,补充上面第三点,大家的证书在发布之前一定要换成自己的私有证书,用dcloud的公有虽然也没什么问题,但是一旦dcloud切换证书,之前发布的应用都会失效的,特别麻烦,这个只是个建议

继续阅读 »

翻了下论坛里面,到处都是问-1错误的,这里我把我自己踩过的坑分享一下,希望对大家开发微信支付有些帮助。
我已经测试过的原因如下:
1,微信支付只支持打包之后安装到手机上进行测试,不支持真机调试,所以骚年们,打包之后再试吧
2,请在打包测试之前,一定要确认和官方的回调字符串是不是一模一样,参数键值不能多也不能少,为了确保成功还是相信官方不会忽悠你的!
这段代码是dcloud官方微信支付返回的字符串,大家可以把自己的字符串拿出来比对一下,确认一致,那就打包测试吧

{"retcode":0,  
"retmsg":"ok",  
"appid":"wx041********d61297",  
"noncestr":"29dfdc1**d9c7ef283cad8ecb1448c8",  
"package":"Sign=WXPay",  
"partnerid":"123*****01",  
"prepayid":"52010***0151019c3820473613388ca",  
"timestamp":1445239300,  
"sign":"16a5efa393b50*****4d6d76e1b2ff703d27"}

3,如果以上两点都已经确认没问题了但是还是无法吊起支付,那么就去微信开放平台https://open.weixin.qq.com/确认下你的应用签名和包名与你在平台上配置的签名和应用包名是不是相同吧,如何获取应用签名这个论坛里面有教材,实在不知道的去腾讯应用宝市场发布下应用,那里也有教程如何获取app的签名,微信开放平台签名修改之后需要等一段时间才会生效,所有如果确认是签名或者包名问题,下班之前再改吧,不然一天白瞎了
特别说一下,这里的签名是长度为32位的MD5字符串
4,ios和安卓在获取支付通道的时候有个现象,他们在支付通道数组对象里面的先后顺序不一样,如果你们在测试微信支付的时候报什么620XX错误,那就检查下支付通道对象获取的对不对
5,补充上面第三点,大家的证书在发布之前一定要换成自己的私有证书,用dcloud的公有虽然也没什么问题,但是一旦dcloud切换证书,之前发布的应用都会失效的,特别麻烦,这个只是个建议

收起阅读 »