- 微信小程序审核相关贴指引
给提问的开发者的建议: 1、审核申诉问题,建议优先走腾讯客服(工单)流程。(路径:kf.qq.com) 2、提问之前先查询文档、通过社区右上角搜索已经存在的问题。 3、写一个简明扼要的标题,并在正文描述清楚你的问题。 4、对于提供信息过少的问题,会直接关闭,请提供完整信息以后重新打开问题。 一、各位开发者,当审核未通过,请按如下模版反馈问题至工单或者社区发帖 1、小程序注册主体类型(企业、个人还是第三方): 2、小程序账号(APPID、原始ID或者邮箱,三者之一即可): 3、问题描述(具体问题介绍): 4、问题截图(客户端问题界面截图): 二、代码审核时间 登录微信公众平台小程序,进入开发管理,开发版本中展示已上传的代码,管理员可提交审核或是删除代码,代码审核7个工作日完成。 注:常规审核时间为2-3个工作日完成,如无特殊情况且未超过7个工作日发帖将不予反馈,请耐心等待审核结果。 三、小程序未通过会涉及哪些类型? 主要涉及有小程序服务类目、基础信息、上线后功能使用、内容、可用性和完整性。 注意事项服务类目小程序发布的内容与小程序申请的服务类目要保持一致。 小程序发布的内容涉及特殊行业时,未选择相应的类目。特殊行业参考:特殊行业所需资质材料 小程序内容小程序内容不得发布平台支持的服务类目以外的内容:如游戏、虚拟支付等; 不得发布非法博彩,违反相关法律法规的内容。基础信息logo不得侵犯其他品牌权利; 详细小程序简介避免政治敏感、色情、敏感词语的出现; 不得对知名品牌名称添加特殊符号,恶意引导用户。小程序上线后功能使用小程序所实际提供的服务和内容,必须是正式的,不能以Demo形式提交; 不得以使用其他应用程序为条件使用小程序。可用性和完整性ios和安卓系统环境下,小程序首页都无法加载或者一直处于加载状态中。
2017-06-15 - 【转】微信小程序服务器配置-suse版
本文主要内容: 1、配置浏览器认可的HTTPS; 2、配置wss协议的websocket。 一、配置Apache 支持HTTPS 1、安装openssl 1.1、去官网下载http://www.openssl.org/source/,然后安装,具体命令在此不给出 1.2、安装完成后,设置path路径,查看是否安装成功: vi /etc/profile 在文件末尾加入 export PATH="$PATH:/usr/local/ssl/bin/" 这句 source /etc/profile openssl version 1.3、生成服务器私钥和对应的csr文件 openssl req -newkey rsa:2048 -keyout yourname.key -out yourname.csr 1.4、生成服务器证书 openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 365 2、Apache启用ssl 2.1、在httpd.conf中找到下面两行去掉前面的注释 # LoadModule ssl_modulemodules/mod_ssl.so Include conf/extra/httpd-ssl.conf 2.2、配置httpd-ssl.conf SSLCertificateFile "/opt/app/apache/conf/server.crt" SSLCertificateKeyFile "/opt/app/a pache/conf/server.key" 2.3、重启Apache service httpd restart 不过这种方式的证书浏览器一般是不信任的,会出现类似12306那样的警告,解决办法请看第三节。 二、配置php版 ssl websocket服务器 1、安装libopenssl-devel 下载网址:http://www.convirture.com/repos/deps/SLES/11.x/x86_64/ rpm -ivh libopenssl-devel.xxx.rpm 2、安装swoole,并启用ssl 去官网下载最新的安装包: 解压然后进入解压目录:tar -zxvf swoole.xx.tar.gz phpize 启用openssl: ./configure --enable-openssl make && make install 3、启用swoole扩展 在php.ini加入: extension=swoole.so 4、重启Apache 用微信小程序开发工具连接wss即可 [图片] 三、高级--安装浏览器认可的证书 这个方法可以省去购买证书的钱 进入网页:https://www.startssl.com/ 注册一个账号,然后验证域名 [图片] [图片] [图片] 然后点击发送验证码,输入验证码即可,然后生成crt文件 [图片] 第一栏输入要生成的证书的子域名,一行一个 第二栏输入要服务器生成的csr文件,必须微2048位,然后提交即可生成crt文件 下载证书: [图片] 上传证书到服务器,然后配置httpd-ssl.conf如下图所示 [图片] 重启Apache即可看到证书被浏览器信任。 [图片] 参考网址:https://www.freehao123.com/startssl-ssl/ 四、扩展--利用Nginx进行ssl代理转发 如果觉得Apache配置openssl麻烦,也可以通过Nginx进行代理转发,不过需要Nginx启用ssl和安装openssl。 先从官网下载最新源码,zlib, zlib-devel并安装 ./configure --prefix=/opt/app/nginx --with-http_ssl_module –with-http_stub_status_module --with-openssl=openssl源码路径 make && make install 配置文件如下: server { listen 6443 ; server_name api.wecareinfo.com; ssl_certificate /opt/app/nginx/conf/server.crt; ssl_certificate_key /opt/app/nginx/conf/server.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { #root html; #index index.html index.htm; proxy_pass http://api.wecareinfo.com; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } 参考网站:http://codego.net/450201/ 五、注意事项 1、如果编译swoole的时候提示GCC版本过低,可以编辑 vi /opt/tools/web/swoole-src-1.8.13-stable/swoole_config.h,将以下文件语句屏蔽即可 [图片] 2、编译的时候如果提示找不到openssl,请安装libopenssl-devel开发包,安装完后还是提示没有openssl,请先安装完Nginx后再编译安装。 3、生成证书的时候,秘钥的位数和申请的时候一定要一致,不然网站会访问不了。 4、每次启动ssl需要输入密码,不需要密码的解决办法 openssl rsa -in yourname.key -outyourname.key.unsecure 然后修改http-ssl.conf配置文件:SSLCertificateKeyFile "/opt/app/a pache/conf/yourname.key.unsecure" 5、查看端口占用:netstat -anp | grep 9502 6、微信小程序开发工具连不上websocket的时候,请换个端口,原因未知。 原作者:huazai123(简书作者) 出自:http://www.jianshu.com/p/9b3ba3192c87#
2016-12-13 - 【转】Java后端实现websocket与微信小程序端连接简单例子
直接附代码 前端代码: <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML> <html> <head> <base href="<%=basePath%>"> <title>My WebSocket</title> </head> <body> Welcome<br/> <input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <div id="message"> </div> </body> <script type="text/javascript"> var websocket = null; //判断当前浏览器是否支持WebSocket if('WebSocket' in window){ websocket = new WebSocket("ws://localhost:8080/myWebSocket/websocket"); } else{ alert('Not support websocket') } //连接发生错误的回调方法 websocket.onerror = function(){ setMessageInnerHTML("error"); }; //连接成功建立的回调方法 websocket.onopen = function(event){ setMessageInnerHTML("open"); } //接收到消息的回调方法 websocket.onmessage = function(event){ setMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function(){ setMessageInnerHTML("close"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function(){ websocket.close(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML){ document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //关闭连接 function closeWebSocket(){ websocket.close(); } //发送消息 function send(){ var message = document.getElementById('text').value; websocket.send(message); } </script> </html> 后端代码: import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; //该注解用来指定一个URI,客户端可以通过这个URI来连接到WebSocket。类似Servlet的注解mapping。无需在web.xml中配置。 @ServerEndpoint("/websocket") public class MyWebSocket { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(Session session){ this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message); //群发消息 for(MyWebSocket item: webSocketSet){ try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); continue; } } } /** * 发生错误时调用 * @param session * @param error */ @OnError public void onError(Session session, Throwable error){ System.out.println("发生错误"); error.printStackTrace(); } /** * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 * @param message * @throws IOException */ public void sendMessage(String message) throws IOException{ this.session.getBasicRemote().sendText(message); //this.session.getAsyncRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { MyWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { MyWebSocket.onlineCount--; } } 以上是网上的前端及后端的代码(原文地址:http://www.cnblogs.com/xdp-gacl/p/5193279.html?utm_source=tuicool&utm_medium=referral),jdk版本要求是在jdk1.7.0以上,tomcat版本也需要在tomcat7.0版本以上。另外有个需要注意的点就是websocket的api包要用tomcat自带的(websocket-api.jar),一开始本人采用的是非tomcat自带的javax.websocket-api-1.0.jar包,在配置了正确的环境之后总是连接不上,会报错:Error during WebSocket handshake: Unexpected response code: 404。 正确的效果图如下: 进入页面http://localhost:8080/myWebSocket/index.jsp [图片] 输入内容,点击send [图片] 微信小程序端发起请求: 请求代码: connectWebSocket:function(){ console.log("创建webSocket连接"); wx.connectSocket({ url:'ws://localhost:8080/myWebSocket/websocket', // url:'http://eservicesit.prlife.com.cn:7001', data:{ x: '', y: '' }, header:{ 'content-type': 'application/json' }, method:"GET", success: function(res){ console.log("创建连接成功"); //do something }, fail:function(res){ console.log("创建连接失败,原因因::"+res.errMsg); }, complete:function(){ console.log("创建连接complete"); } }) }控制台输出: [图片] 原作者: 郑春宏 来自: http://blog.csdn.net/v824394795/article/details/53507645
2016-12-08 - 【转】微信小程序:tabbar未显示,页面之间传参,picker组件取值
作者:太多的无奈啊 微信小程序发布到现在有段时间了,公司内部正在做一个小程序的项目,做的过程中踩到了不少问题,这里说一下遇到的问题和解决方案, 希望能帮到正在做的人,也希望又正好的解决方案的人可以给我提供意见和建议,谢谢! 1.小程序底部导航条设置未显示问题[图片] 这两个地址逻辑上是一个页面,如果你设置的不是一个页面,就会导致底部导航条不显示。 2.如何更改小程序别的页面的顶部标题小程序每个页面都有一个顶部标题,这个标题是在app.json中的navigationBarTitleText,进行设置的, 如果别的页面不设置就默认全显示app.json中的标题,如果需要每个页面有自己的标题, 就需要在当前页面的文件夹下建一个xxxx.json的配置文件,navigationBarTitleText:“xxxx”,就可以进行设置了。 3.如何在页面之间传参数譬如从列表页跳转到详情页,这样需要传相应内容的id到详情页 [图片] 可以再绑定点击事件的地方 加上data-XXXX 里面是内容的id 这样通过bintap的事件就可以拿到相应的参数[图片] 在通过下面的方法就可以拿到id 在进行跳转就行了,id在详情页会带到options中console.log(options), 就可以看到从列表页带过来的id了。 4.picker组件取值问题小程序没有select option 的下拉菜单(至少本萌新不太会做。。。),通过picker实现的选择,在选择相应的值进行查询的时候遇到了一个问题,picker的change事件拿到的是当前值在数组中的位置,想要往后台传值进行查询的话一般会传id,这里我第一个方案是把id放在picker的data-id={{id}}上,这样确实取到id了,但是取到的却总是上一次选择的id,然后就换了一个方案,通过this.data.数组名,可以获取到当前page下data里面的某个数组 在把获取到的值传到数组名里面,就可以拿到相应的值了[图片] 代码如下,这个项目里有三个筛选,我都是这么进行操作的,感觉吧。。。并不是很好,希望有大神看到可以给出更好的方法。 5.浮动问题写页面的时候总是会用到浮动,就会出现相应的浮动问题,其实这个问题不大,在群里看到有人问过,就说一嘴,大神可忽略。。。html里面是通过添加clear中的样式是clear:both;然后把这层div放到浮动div底下 就会取除下面的dom的浮动问题,小程序中是一样的,添加<viewclass="clear"> 就可以了。</view 6.公共部分的代码(譬如在拼链接时的path)听同事说小程序最大上传只能有1M(但是我没找到哪里有说。。),不过为了少写点代码,可以把一些东西提成公共的部分,譬如path [图片] 这里就把path的app.js全局变量设置好了,然后在需要的页面里通过 var app = getApp(); var path = app.globalData.path;就可以拿到在app.js里的全局变量了,剩下的就是拼字符串了,同理与样式表,公共的样式最好提成一个文件夹,通过@import 的方式引入,js同理,这样代码也会好维护很多,操作起来也方便很多。(然而我的项目在尝试的时候并没有注意这个问题,每个页面就是每个页面的,现在在忙于提出公共部分,唉说多了都是泪)。 7.navigaterTo和navigaterBacknavigaterTo可以再url后面还拼参数,这样可以把需要的参数带到别的页面,navigaterBack是无法往回带参数的然后有的时候需要去别的页面插值的时候需要往上一级页面传参, 这里我是用的setStorage的方式写入缓存中,这样在上一级页面的onShow中可以再进行塞值处理 [图片][图片] 这样就可以往上一级页面传值并显示了,我这里的是表单组件,input中虚显示查询页面返回的信息,没想到别的处理方案,这样进行处理的。 如果有好的方案敬请告知,谢谢! 8.获取openId和session_key通过调用login的方法,可以返回一组数据 通过data.code可以拿到api中需要的js_code另外两个一个是appid 另外一个是秘钥 这个在小程序里面可以再设置中找到,具体参数可以参考api文档,里面提供了接口地址。 9.真机调试真机调试的时候首先需要把接口的http改成https,需要配置自己的域名,貌似一个月只有三次,需谨慎。。。。android上调试没有问题,但是ios上会出现ssl错误, [图片] 这个需要在服务器那边进行配置,具体咋配置的我就不知道了,只是知道会有这么一个问题。 @天下雪的回复:ssl方面的错误,可以参考这个链接http://www.wxapp-union.com/forum.php?mod=viewthread&tid=648,一般都是证书有问题; @钟良: wx.redirectTo和wx.navigateTo 以及系统自带的tab:重定向和跳转都能实现到某个界面,a、不过如果从tab的一个界面跳到另一个tab的界面,就要用重定向,否则tab高亮位置会出错;b、通过tab切换时,开发工具会打开新栈,这样老的界面就无法关掉了,超出8个时,跳转和重定向都不能用了,真机上无该问题。 我遇到的问题目前就这么多了,其实现在想想有些也不算问题,多看文档就行了,希望看官大人们多提意见,又不对的地方指出告诉我,我会虚心接受的。谢谢! 原作者: 太多的无奈啊 来自: http://blog.csdn.net/u010783527
2016-12-06 - 【转】dongtao :微信小程序里碰到的坑和小知识
已解决 在app.wxss里设置了图片路径,在IDE里正常无误,但是在手机上是没有显示的, 解决办法:(这段话位置放那么偏~ ) [图片] 问题描述 [图片] 代码截图 app.wxss.png [图片] 模拟器里的效果.png [图片] 手机里的效果.png 未解决 用小程序自带的底部导航组件的话, 没法实现跟微信原生底部小红点或者消息提醒的功能 已解决 picker使用时候 picker组件里必须要有内容,放一个值为空的变量并没有作用 [图片] [代码]这里我如果只保留{{age}}的话, 这个组件是无法触发的. 组件里面必须至少要有1个字符(哪怕是1),这个组件才可以被触发,但是空格是不行 放一个宽高为100%,display:block的view,如果view里没内容,view是不会显示的, 必须要在view里放东西,哪怕一个数字也行. 然后这里只能写成了这种(原本需求是这个日期选择器默认状态是空,)[代码]已解决 没仔细考证,网上说图是没法批量上传的,所以这里只能用单个上传,成功后递归调用来解决批量上传. 以下代码有一个错误,第十会说到文件上传的另一个问题 [图片] 已解决 结论: 手机预览的时候会进行域名合法校验的, 模拟器里的关闭域名校验只是对模拟器有效 问题描述: 模拟器里开启"开发工具不开启域名校验"之后在模拟器里是可以用http的,但是当在手机上预览的时候http并不能用, 模拟器开的这个 只负责在模拟器里. 如果不开启的话, 并且在APP配置信息里,没有设置合法域名的话, 在模拟器里是可以使用图片上传之类的接口,但是在手机上并不可以 [图片] Paste_Image.png 已解决 在index点击跳转到login时候,标记三的部分没有写page()进行实例化,导致2 的页面加载时候没加载相应的js , 会报以下错 [图片] [图片] 已解决 textArea 没有bindinput事件 [代码]之前描述: 有个业务场景是检测多行输入框,如果有值,按钮立马变成可点击. 然而textarea是没有input事件的, 最后只能用blur进行失去焦点的时候监听 解决办法: blur的在电脑上的体验如描述所说,可是真正在手机上运行的时候, 体验和需求中的一致...[代码]已解决 更新: 以下在模拟器里是可以的但是在手机上是无效的! 原因参考第一条 app.wxss里的图片路径要按照组件文件夹的图片路径来写 [代码]//用最上面的1来距离这个样式最开始在一个pages文件夹里,所以图片路径是 ../../imgs/XXX, 按理说如果把这个样式提取到外面的app.wxss里的话 图片路径应该改为./imgs/XXX.. 然而改成这样之后在模拟器都无法生效.[代码]未解决 输入法掩盖textarea, 这个bug有一半的概率会出现 [图片] 已解决: wx.uploadFile 中formData参数问题: 这个接口的formData 微信在处理的时候 已经用formData进行了包装. 所以这里是没有必要自己用formData来传递参数的(比如注释掉的代码) 扯淡的是, 传递了form格式的参数之后 在浏览器和安卓下, 接口是可以正常使用的, 并不会报什么错 但是! 在ios里 会直接报错, 不是运行异常,是直接红色的报错.!!!!! [图片] 原作者: dongtao 来自: http://www.jianshu.com/p/55621c85c5dd
2016-12-02 - 【转】微信小程序 textarea 简易解决方案
微信小程序中textarea没有bindchange事件,所以无法在输入时给变量赋值。 虽然可以使用bindblur事件,但是绑定bindblur事件,如果再点击按钮,则先执行完按钮事件后,再去执行bindblur事件,所以在js文件取不到输入值, 解决方法:结合from表单,textarea文本框输入后,再去点击提交按钮,这时会先执行textarea事件(获取文本框输入内容),再去执行数据提交,这样问题就解决了 wxml文件代码: << span="">form bindsubmit="evaSubmit"> << span="">textarea name="evaContent" maxlength="500" value="{{evaContent}}" class="weui-textarea" placeholder="填写内容(12-500字)"bindblur="charChange" /> << span="">button formType="submit" disabled="{{subdisabled}}" class="weui-btn mini-btn" type="primary" size="mini">提交button> form>js文件代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152[代码]var[代码] [代码]app = getApp();[代码][代码]Page({[代码][代码] [代码][代码]data:{[代码][代码] [代码][代码]evaContent : [代码][代码]''[代码][代码] [代码][代码]},[代码][代码] [代码][代码]onLoad:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]},[代码][代码] [代码][代码]onReady:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 页面渲染完成[代码][代码] [代码][代码]},[代码][代码] [代码][代码]onShow:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 页面显示[代码][代码] [代码][代码]},[代码][代码] [代码][代码]onHide:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 页面隐藏[代码][代码] [代码][代码]},[代码][代码] [代码][代码]onUnload:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]// 页面关闭[代码][代码] [代码][代码]},[代码][代码] [代码][代码]//事件[代码][代码] [代码][代码]textBlur: [代码][代码]function[代码][代码](e){[代码][代码] [代码][代码]if[代码][代码](e.detail&&e.detail.value.length>0){[代码][代码] [代码][代码]if[代码][代码](e.detail.value.length<12||e.detail.value.length>500){[代码][代码] [代码][代码]//app.func.showToast('内容为12-500个字符','loading',1200);[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]evaContent : e.detail.value[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]evaContent : [代码][代码]''[代码][代码] [代码][代码]});[代码][代码] [代码][代码]evaData.evaContent = [代码][代码]''[代码][代码];[代码][代码] [代码][代码]app.func.showToast([代码][代码]'请输入投诉内容'[代码][代码],[代码][代码]'loading'[代码][代码],1200);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]},[代码][代码] [代码][代码]//提交事件[代码][代码] [代码][代码]evaSubmit:[代码][代码]function[代码][代码](eee){ [代码][代码] [代码][代码]var[代码] [代码]that = [代码][代码]this[代码][代码];[代码][代码] [代码][代码]//提交(自定义的get方法)[代码][代码] [代码][代码]app.func.req([代码][代码]'http://localhost:1111/ffeva/complaint?content='[代码][代码]'+this.data.evaContent),get,function(res){[代码][代码] [代码][代码]console.log(res);[代码][代码] [代码][代码]if(res.result==='[代码][代码]1[代码][代码]'){[代码][代码] [代码][代码]//跳转到首页[代码][代码] [代码][代码]app.func.showToast('[代码][代码]提交成功[代码][代码]','[代码][代码]loading[代码][代码]',1200);[代码][代码] [代码][代码]}else{[代码][代码] [代码][代码]app.func.showToast('[代码][代码]提交失败[代码][代码]','[代码][代码]loading',1200);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码]})[代码] 缺点: 这样操作后,功能就有缺陷。例如,无法即时获取用户文本框输入字符个数,如果有更好的解决方法,希望能学习一下! 作者:天下雪 出自:http://www.wxapp-union.com/portal.php?mod=view&aid=581
2016-12-02 - 【转】微信小程序填坑-Android真机环境下的bluebird.js
今天,有朋友反映说,我的微信小程序的例子在andriod真机环境下运行出错,研究调试了半天,发现原来是使用的bluebird.js(Promise实现库)导致的。 由于小程序框架最近的更新中移除了原生Promise的支持,喜欢使用Promise特性的开发者都会去引入其他的第三方Promise库来替代,bluebird.js是一个性能不错,特性又丰富的Promise实现库,所以大家都爱用。不过,由于bluebird.js中有些代码还是用到了document对象上的方法,在iOS的真机JavascriptCore环境中,貌似这些代码并不会被触及到,而在Android真机环境中,就会被执行到这块代码,导致了错误的发生。 怎么办呢?找找其他能用的库吧,Promise实现库还算比较多的!NPM上搜搜就有。其中es6-promise是另一个被开发者下载使用比较多的库。通过npm命令我们可以很方便的将它下载下来: [代码]npm install es6-promise[代码]然后在下载下来的文件夹下,把dist目录中的es6-promise.js或es6-promise.min.js复制到你的微信小程序项目中,替换原来的bluebird.js就行啦,标准的Promise API一个都不会少。 而且es6-promise的代码文件体积比bluebird小个3,4倍,也算一个好处吧(只能这么自我安慰了...哈哈) 希望这篇文章能解决同样遇到这个问题的朋友。 原作者:一斤代码(简书作者) 原文链接:http://www.jianshu.com/p/d29746bf29e3
2016-11-29 - 【转】微信小程序中实现瀑布流布局和无限加载
瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。 在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形式。在微信小程序中,我们也可以做出这样的效果,不过由于小程序框架的一些特性,在实现思路上还是有一些差别的。 今天我们就来看一下如何在小程序中去实现这种瀑布流布局: [图片] 小程序瀑布流布局 我们要实现的是一个固定2列的布局,然后将图片数据动态加载进这两列中(而加载进来的图片,会根据图片实际的尺寸,来决定到底是放在左列还是右列中)。 [代码]/* 单个图片容器的样式 */.img_item { width: 48%; margin: 1%; display: inline-block; vertical-align: top; }[代码]我们知道,在HTML中,我们要动态加载图片的话,通常会使用new Image()创建一个图片对象,然后通过它来动态加载一个url指向的图片,并获取图片的实际尺寸等信息。而在小程序框架中,并没有提供相应的JS对象来处理图片加载。其实我们可以借助wxml中的[图片]组件来完成这样的功能,虽然有点绕,但还是能满足我们的功能要求的。 [代码]<view style="display:none"> <image wx:for="{{images}}" wx:key="id" id="{{item.id}}" src="{{item.pic}}" bindload="onImageLoad">image>view>[代码]我们可以在Page中通过数据绑定,来传递要加载的图片信息到wxml中,让[图片]组件去加载图片资源,然后当图片加载完成的时候,通过bindload指定的事件处理函数来做进一步处理。 我们来看一下Page文件中定义的onImageLoad函数。在其中,我们可以从传入的事件对象e上,获取到[图片]组件的丰富信息,包括通过它加载进来的图片的实际大小。然后我们将图片按照页面上实际需要显示的尺寸,计算出同比例缩放后的尺寸。接着,我们可以根据左右两列目前累积的内容高度,来决定把当前加载进来的图片放到哪一边。 [代码]let col1H = 0;let col2H = 0; Page({ data: { scrollH: 0, imgWidth: 0, loadingCount: 0, images: [], col1: [], col2: [] }, onLoad: function () { wx.getSystemInfo({ success: (res) => { let ww = res.windowWidth; let wh = res.windowHeight; let imgWidth = ww * 0.48; let scrollH = wh; this.setData({ scrollH: scrollH, imgWidth: imgWidth }); //加载首组图片 this.loadImages(); } }) }, onImageLoad: function (e) { let imageId = e.currentTarget.id; let oImgW = e.detail.width; //图片原始宽度 let oImgH = e.detail.height; //图片原始高度 let imgWidth = this.data.imgWidth; //图片设置的宽度 let scale = imgWidth / oImgW; //比例计算 let imgHeight = oImgH * scale; //自适应高度 let images = this.data.images; let imageObj = null; for (let i = 0; i < images.length; i++) { let img = images[i]; if (img.id === imageId) { imageObj = img; break; } } imageObj.height = imgHeight; let loadingCount = this.data.loadingCount - 1; let col1 = this.data.col1; let col2 = this.data.col2; //判断当前图片添加到左列还是右列 if (col1H <= col2H) { col1H += imgHeight; col1.push(imageObj); } else { col2H += imgHeight; col2.push(imageObj); } let data = { loadingCount: loadingCount, col1: col1, col2: col2 }; //当前这组图片已加载完毕,则清空图片临时加载区域的内容 if (!loadingCount) { data.images = []; } this.setData(data); }, loadImages: function () { let images = [ { pic: "../../images/1.png", height: 0 }, { pic: "../../images/2.png", height: 0 }, { pic: "../../images/3.png", height: 0 }, { pic: "../../images/4.png", height: 0 }, { pic: "../../images/5.png", height: 0 }, { pic: "../../images/6.png", height: 0 }, { pic: "../../images/7.png", height: 0 }, { pic: "../../images/8.png", height: 0 }, { pic: "../../images/9.png", height: 0 }, { pic: "../../images/10.png", height: 0 }, { pic: "../../images/11.png", height: 0 }, { pic: "../../images/12.png", height: 0 }, { pic: "../../images/13.png", height: 0 }, { pic: "../../images/14.png", height: 0 } ]; let baseId = "img-" + (+new Date()); for (let i = 0; i < images.length; i++) { images[i].id = baseId + "-" + i; } this.setData({ loadingCount: images.length, images: images }); } })[代码]这里是显示在两列图片的wxml代码,我们可以看到在scroll-view>组件上,我们通过使用bindscrolltolower设置了事件监听函数,当滚动到底部的时候,会触发loadImages去再加载下一组的图片数据,这样就形成了无限的加载:/scroll-view> [代码]<scroll-view scroll-y="true" style="height:{{scrollH}}px" bindscrolltolower="loadImages"> <view style="width:100%"> <view class="img_item"> <view wx:for="{{col1}}" wx:key="id"> <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px">image> view> view> <view class="img_item"> <view wx:for="{{col2}}" wx:key="id"> <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px">image> view> view> view>scroll-view>[代码]好了,挺简单的一个例子,如果你有更好的方法,不吝分享一下哦。 完整代码可以在我的Github下载:https://github.com/zarknight/wx-falls-layout 原作者:一斤代码(简书作者) 原文链接:http://www.jianshu.com/p/260f2623562d
2016-11-29 - 【转】微信小程序入门教程--列表渲染多层嵌套循环及wx:key的使用
前言入门教程之列表渲染多层嵌套循环,目前官方的文档里,主要是一维数组列表渲染的案例,还是比较简单单一,给刚入门的童鞋还是无从入手的感觉。 [代码]<view wx:for="{{items}}"> {{index}}: {{item.message}}</view>[代码]还有一个九九乘法表把数据直接写到wxml里的,并不是动态二维数组的列表渲染。 [代码]<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i"> <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j"> <view wx:if="{{i <= j}}"> {{i}} * {{j}} = {{i * j}} </view> </view></view>[代码]那么今天,我们主要来讲讲动态多维数组和对象混合的列表渲染。 [图片] '' 讲解何为多维数组和对象混合,给个很简单的例子 [代码] twoList:[{ id:1, name:'应季鲜果', count:1, twodata:[{ 'id':11, 'name':'鸡脆骨' },{ 'id':12, 'name':'鸡爪' }] },{ id:2, name:'精致糕点', count:6, twodata:[{ 'id':13, 'name':'羔羊排骨一条' },{ 'id':14, 'name':'微辣' }] }][代码]上述例子就是一个数组,这都是我们日常开发过程中,经常会碰到的JSON格式, 该数组的元素是有对象,对象又分为属性,属于数组对象混合,可能对于刚接触小程序的童鞋,碰到这种数组对象混合的就会发难了。 一层循环[代码] oneList:[{ id:1, name:'应季鲜果', count:1 },{ id:2, name:'精致糕点', count:6 },{ id:3, name:'全球美食烘培原料', count:12 },{ id:4, name:'无辣不欢生猛海鲜', count:5 }][代码]以上数组对象混合JSON,是测试只有一层循环的,我们看看在[代码]wxml[代码]里怎么循环,我们先看一下要循环渲染到页面上的效果图。 [图片] '' [代码]<view wx:for="{{oneList}}" wx:key="id"> {{index+1}}、{{item.name}}</view>[代码]我们可以看到,这里直接用两个花括号来给[代码]view[代码] 循环列表,注意强调一下,请记得一下要用 两个花括号数据起来,如果不包起来,[代码]view[代码]也会循环出来,但并不是自己要循环的数据,而且是给了一个假象给你以为是有循环了,这里开发工具有点坑人的感觉,这个还需要多细心点,这里记住一点,只要是有数据的,就需要花括号。 另外默认数组的当前项的下标变量名默认为index,数组当前项的变量名默认为item,同时我这里也顺道演示了如何使用数组变量名和下标。 二层循环[图片] '' JSON代码 [代码] twoList:[{ id:1, name:'应季鲜果', count:1, twodata:[{ 'id':11, 'name':'鸡脆骨' },{ 'id':12, 'name':'鸡爪' }] },{ id:2, name:'精致糕点', count:6, twodata:[{ 'id':13, 'name':'羔羊排骨一条' },{ 'id':14, 'name':'微辣' }] },{ id:3, name:'全球美食烘培原料', count:12, twodata:[{ 'id':15, 'name':'秋刀鱼' },{ 'id':16, 'name':'锡箔纸金针菇' }] }][代码]wxml代码 [代码] <view class="pad10" wx:for="{{twoList}}" wx:key="id"> <view> {{index+1}}、{{item.name}} </view> <view wx:for="{{item.twodata}}" wx:for-item="twodata" wx:key="id"> ----{{twodata.name}}---{{item.name}} </view> </view>[代码]以上截图和代码是二层嵌套内容。 我们在wxml代码里,很明显的看到有两个[代码]wx:for[代码]的控制属性,在二层循环的JSON代码里,我们看每个单数组里还有一级数据[代码]twodata[代码],这里是需要再循环渲染到页面上的,在第一层数据里,直接再循环[代码]item.twodata[代码]即可,请记得一定要带上花括号。 在第二层的循环里,建议把当前项的变量名改为其他,即在wxml代码里看到的[代码]wx:for-item="twodata"[代码],因为默认的当前项的变量名为[代码]item[代码],如果不改换其他的话,你是拿不到第一层循环的数据的,因为被第二层的变量名覆盖了。 所以我们在wxml代码里,在第二层循环时,可以看到还可以循环第一层的值,即[代码]----{{twodata.name}}---{{item.name}}[代码]。 三层以上的多层循环三层以上的多层的数组循环,在原理上同二层循环是一样的,能理解了二层数组循环,对于三层以及三层以上都能得心应用的。 需要注意的地方,那就是老生常谈的问题了,数据需要用花括号括起来,从第二层起,把默认的当前项的变量名改为其他,例如[代码]wx:for-item="twodata"[代码],还有细心再细心。 wx:key唯一标识符为什么会有[代码]wx:key[代码]的出现呢,官方给的解释是,如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 [代码]input[代码] 中的输入内容,[代码]switch[代码] 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符。 当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。 在开发过程中,[代码]wx:key[代码]的作用对于项目作用是非常大的,如果从文字上无法理解的童鞋,可以到github clone demo到微信开发工具里,亲自体验下。 [图片] '' 我们看到这个GIF动画图,这里有一个[代码]switch[代码]的开启状态,[代码]switch[代码]的状态是在标题为[代码]羔羊排骨一条[代码]的,在对这个数组增加数据时,这个[代码]switch[代码]的状态并不跟随着[代码]羔羊排骨一条[代码],并不保持自己的状态。 那我们再看另一个例子,使用了wx:key唯一标识符。 [图片] '' 这个GIF动画图,也是点击开启了[代码]switch[代码]的状态,唯一有不同的地方,就是在新增数据时,是保持着自己的状态的。 相信通过这两个小例子,对wx:key唯一标识符应该也有所了解啦,想要提升技术,就要多折腾,自己在小程序里,写个[代码]wx:for[代码] 和 [代码]wx:key[代码] 体会下。 还有一个需要注意的地方,我们先看看以下代码 [代码]<view class="pad10" wx:for="{{twoList}}" wx:key="id"> </view>[代码][代码]wx:key="id"[代码],我们看到[代码]wx:key[代码]里的值并不需要花括号的,是的,这里是比较特别的地方,不需要花括号,同时也不需要参数名,需要是虽然数据里的一个字段名。 原作者:蓝狐锅锅(简书作者) 来自:http://www.jianshu.com/p/87cdf985b2b9
2016-11-28 - 【转】如何在微信小程序里面实现跨页面通信?
我们在处理业务需求的时候,常常会遇到一些情况,在二级或者三级页面进行某些操作或者变更后,需要将结果通知到上级页面去。比如: 选择了某些配置项,点击保存后,外部页面能够立即变更 在上传头像页面,上传完毕后,外部页面的头像能够立即显示为新头像。 所以,这个时候就涉及到如何在页面之间通信的问题了。 跨页面通信进一步说其实就是一个程序内部的事件通知机制问题,在其他平台或者OS上都一些相应的实现,比如: iOS SDK自带的 NotificationCenter Android 平台著名的第三方库 EventBus 目前微信小程序官方SDK还没有提供 Event API 来帮助开发者实现页面间通信,所以我们今天来看看,自己如何实现这样一个简单的小工具。 说到这里就不得不说“云梦”的微信小程序版本了,在小程序开始公测后,我们也在第一时间将“云梦”的基本功能移植到了小程序平台上。 整个过程相当顺利,除了小程序的IDE还不是太稳定外,基本上没啥大问题。 开发过程和React-Native基本相似,大概一天时间就搞定了。 Quick And Dirty我们知道,在小程序里面一个页面的变化,是通过调用 setData 函数来实现的。所以想做到在二级页面里让一级页面产生变化,最 Quick And Dirty 的做法就是把一级页面的 this 传入到二级页面去,这样我们在二级页面调用 page1.setData(…) 就可以立即引发外部的变化。 但是这并不是一个好的方案,不仅产生了页面的耦合,而且也并不能处理复杂的数据逻辑,因为二级页面不并清楚也不应该关心一级页面想怎么处理当前数据。所以二级页面只应该把变更后的数据通知给一级页面即可,至于一级页面是想刷新界面,还是想本地存储或者发起网络通信,别人都不需知晓了。 简单的Callback如果只是想把数据通知给外部页面,那应该怎么做呢? 我们来看看第二个方案,如果想产生一个通知,这里就需要用到 callback 机制了。 即关心数据变化的页面,注册一个 callback 函数到一个公共的地方;而数据变更者在变更数据后,将新的数据放入同一个公共的地方;在放入数据时,同时调用这个 callback 函数,让 callback 函数实现者接收到这个变化。 哪这个公共的地方在哪里呢? 第一反应就是 app.js 里面,因为小程序提供了一个 API 叫做 getApp(),让 page 初始化时,可以通过以下代码: var app = getApp()来获取 app 实例,从而实现全局的数据共享,并且微信也很贴心的在 Demo 代码里面留了一个 globalData 字段,以暗示开发者这里是可以用来存储全局数据的。 App({ ... globalData:{ userInfo:null } ...})基于 app.js 方案的伪代码如下: //app.jsApp({ addListener: function(callback) { this.callback = callback; }, setChangedData: function(data) { this.data = data; if(this.callback != null) { this.callback(data); } } })然后我们在一级页面的 onLoad中 调用 addListener: //page1.jsvar app = getApp()Page({ onLoad: function () { app.addListener(function(changedData) { that.setData({ data: changedData }); }); } })在二级页面数据变更的地方调用: //page2.jsvar app = getApp()Page({ onBtnPress: function() { app.setChangedData('page2-data'); } })一个基本合格的方案以上就是跨页面通信的最基本原理,不过这也是一个很 dirty 的方案,因为上面的代码只能支持一种 Event 的通知,而且也不能针对这个 Event 添加多个监听者(比如有多个页面需要同时知道某数据变更)。 让我们来看看一个基本合格的 Event 管理器应该具备怎样的能力? 支持多种 Event 的通知 支持对某一 Event 可以添加多个监听者 支持对某一 Event 可以移除某一监听者 将 Event 的存储和管理放在一个单独模块中,可以被所有文件全局引用 根据以上的描述,我们来设计一个新的 Event 模块,对应上面的能力,它应该具有如下三个函数: on 函数,用来向管理器中添加一个 Event 的 Callback,且每一个 Event 必须有全局唯一的 EventName,函数内部通过一个数组来保存同一 Event 的多个 Callback remove 函数,用来向管理器移除一个 Event 的 Callback emit 函数,用来触发一个 Event 我们在小程序的 utils 目录中,新建一个 event.js 文件,来作为一个独立的模块,伪代码如下: //event.jsvar events = {};function on(name, callback) { var callbacks = events[name]; addToCallbacks(callbacks, callback); }function remove(name, callback) { var callbacks = events[name]; removeFromCallbacks(callbacks, callback); }function emit(name, data) { var callbacks = events[name]; emitToEveryCallback(callbacks, data); }exports.on = on;exports.remove = remove;exports.emit = emit;我们来看看在一二级页面应该如何来使用这个 Event 模块 在二级页面中触发事件: //page2.jsvar event = require('../../utils/event.js');Page({ onBtnPress: function() { event.emit('DataChanged', 'page2-data'); } });在一级页面的 onLoad 中监听事件,onUnload 中取消监听: //page1.jsvar event = require('../../utils/event.js');Page({ onLoad: function() { var that = this; event.on('DataChanged', function(changedData) { that.setData({ data: changedData }); }); }, onUnload: function() { event.remove('DataChanged', ...); } });咦,似乎哪里不对? remove 需要接受两个参数,第一个是 EventName,第二个是 Callback,但是我们的 Callback 以匿名函数的方式写在了 event.on(...) 的调用语句里面 好吧,那我们不得不修改一下语句的调用方式: //page1.jsvar event = require('../../utils/event.js');Page({ onDataChanged: function(changedData) { this.setData({ data: changedData }) }, onLoad: function() { event.on('DataChanged', this.onDataChanged); }, onUnload: function() { event.remove('DataChanged', this.onDataChanged); } });这样就 OK 了么?NO NO NO NO 熟悉 Javascript this 这个大坑的朋友们一定会知道,在 onDataChanged 这个函数中调用的 this 并不是我们 Page 中的那个 this,所以根本不可能调用到 this.setData(....),于是我们用 bind 大法稍微调整一下: onLoad: function() { event.on('DataChanged', this.onDataChanged.bind(this)); }onUnload: function() { event.remove('DataChanged', this.onDataChanged.bind(this)); }现在OK了么?NO NO NO NO!如果大伙敲代码试试,就会发现依然还是不行! 因为 this.onDataChanged.bind(this)会产生一个新的匿名函数,即 bind的 返回值是一个函数,那么在 onLoad 和 onUnload 里面,各自调用了 bind 大法,从而产生了各自的匿名函数,也就是说 event.remove(...) 塞进去的那个函数,并不是 event.on(...) 塞进去的那个函数,这样就造成了 remove 时无法正确匹配。removeFromCallbacks 的伪代码大致如下: function removeFromCallbacks(callbacks, callback) { var newCallbacks = []; for(var item in callbacks) { if(item != callback) { newCallbacks.push(item); } } return newCallbacks; }所以我们会发现 remove 传入的 callback 永远无法在 callbacks 数组中被匹配到,从而也就无法正确移除了。 最终的代码实现当 EventName + Callback 无法唯一决定需要移除的监听者时,那么自然想到的就是再增加一个 key 值,我们可以用Page自身的某个特性来做 key,比如 page name ,新的 remove 原型如下: function remove(eventName, pageName, callback);pageName 是一个字符串,如果开发者不能做到全局内 page name 唯一的话(比如开发者一不小心写错了),那就可能会出现后来监听者冲掉前面监听者的情况,从而造成无法收到通知的 bug。 所以这里看起来还是用 page 的 this 做 key 比较靠谱,修改后的函数原型如下: function on(name, self, callback); function remove(name, self, callback);让我们来看看内部具体怎么实现。以下是一个完整的 on 函数实现: function on(name, self, callback) { var tuple = [self, callback]; var callbacks = events[name]; if (Array.isArray(callbacks)) { callbacks.push(tuple); } else { events[name] = [tuple]; } }第二行我们将 self (即 page 的 this)和 callback 合并成一个 tuple 第三行从 events 容器中,取出该 EventName 下的监听者数组 callbacks 如果该数组存在,则将 tuple 加入数组;如果不存在,则新建一个数组。 remove的完整实现: function remove(name, self) { var callbacks = events[name]; if (Array.isArray(callbacks)) { events[name] = callbacks.filter((tuple) => { return tuple[0] != self; }); } }第二行从 events 容器中,取出该 EventName 下的监听者数组 callbacks 如果 callbacks 不存在,则直接返回 如果存在,则调用 callbacks.filter(fn) 方法 filter 方法的含义是通过 fn 来决定是否过滤掉 callbacks 中的每一个项。fn 返回 true 则保留,fn 返回 false 则过滤掉。所以我们调用 callbacks.filter(fn) 后,callbacks 中的每一个 tuple 都会被依次判定。 fn的定义为: (tuple) => { return tuple[0] != self; }tuple 中的第一个元素 self 和 remove 传入的 self 相比较,如果不相等则返回 true 被保留,如果相等则返回 false 被过滤掉。 callbacks.filter(fn) 会返回一个新的数组,然后重新写入 events[name],最终达到移除callbacks中某一项的逻辑。 最后再来看看emit的实现: function emit(name, data) { var callbacks = events[name]; if (Array.isArray(callbacks)) { callbacks.map((tuple) => { var self = tuple[0]; var callback = tuple[1]; callback.call(self, data); }); } }第二行从 events 容器中,取出该 EventName 下的监听者数组 callbacks 如果 callbacks 不存在,则直接返回 如果存在,则调用 callbacks.map(fn) 方法 和 filter 的用法类似,map 函数的作用相当于 for 循环,依次取出 callbacks 中的每一个项,然后对其执行 fn(tuple),从其名字就可以看出 map 就是映射变换的意思,将 item 变换为另外一种东西,这个映射关系就是fn。 fn 的定义为: (tuple) => { var self = tuple[0]; var callback = tuple[1]; callback.call(self, data); }对传入的 tuple,分别取出 self 和 callback,然后调用 Javascript 的 call大法: fn.call(this, args)从而最终实现调用到监听者的目的。 讲到这里就基本上差不多了,因为 Event 模块持有了 Page 的 this,所以一定要在 Page 的 Unload 函数中调用 event.remove(…),不然会造成内存泄露。 源代码event.js 的完整源代码和Demo请见 https://github.com/danneyyang/weapp-event 原作者: danneyyang
2016-11-25