- 部分Android7.0用户 websocket 线上版本无法连接问题
部分Android7.0用户 websocket 线上版本无法连接问题,仅真机连接测试时可用,请问官方这个问题什么时候能够解决?
2019-01-21 - 微信Android7.0.4版本,小程序建立websocket连接一直超时
微信Android7.0.4版本,小程序建立websocket连接一直超时,微信Android7.0.3版本可以建立连接,iOS各版本均可建立连接。 如下图微信Android7.0.4建立websocket连接超时,报connect response time out [图片] 如下图微信Android7.0.3可建立websocket连接 [图片]
2019-05-30 - 微信支付实名信息小程序授权接口能力【监管原因,暂停开放】
备注说明:接口因为监管原因,2019年11月10日起停止开放。 一、能力背景 互联网+国家战略发布以来,各行业紧锣密鼓推进改革。其中,众多政府民生服务,例如社保查询、公积金查询提取、医疗机构挂号、公用事业缴费等均需要用户提供实名信息。此外,手机卡实名办理、火车客运实名购票、酒店实名入住等行业实名制都在推行。 基于此,为了让用户有更好的体验,让各行业的互联网+服务更顺畅,基于微信支付实名用户基础,提供微信支付实名支付账户信息授权接口。即经过用户的授权,小程序的服务提供者可以获得用户在微信支付认证的姓名以及身份证信息。 二、接入必读 名称: getRealnameAuthInfo 功能: 经过用户授权,可获得用户在微信支付认证的姓名以及身份证信息(非身份证的其他证件信息暂不提供授权) 验证方式: 因为需要用户主动触发才能发起获取实名信息接口,所以该功能不由 API 来调用,需用 <button> 组件的点击来触发。且需要用户输入微信支付密码验证后,方可算授权。 兼容的微信版本: iOS6.5.22及Android6.5.22 及以上版本 调用必备条件: (1)小程序appid获得内测邀请后提交资质经平台审核通过; (2)小程序开通了微信支付账号; (3)向微信支付工作人员申请证书 开放范围: 现为内测邀请阶段,白名单开通。务必需按下述内容,及第三节指引,申请开通权限后再按照接口文档开发,否则无效。 开放说明: 针对小程序的业务方,小程序的主体以及类目,需要在限定的类目范围内。开展的业务也需要是国家相关法规、政策规定的需要“实名办理”的相关业务。 现阶段微信支付实名信息授权接口能力开通的主体类目限定包含: 政务:政府机构或事业单位 医疗:公立医疗机构 教育:公立教育机构 轨道交通:铁路官方,地铁官方 三、接口申请 满足第二节中描述的开放类目的小程序,可申请微信支付实名授权接口。请按照如下描述,进行接口申请。 必须完成以下两步的描述才算申请成功: 第一步. 邮件申请,开通小程序后台接口能力入口: 申请邮件请发送至腾讯工作邮箱wx_city@tencent.com。腾讯工作人员会邮件回复资料是否准确无误,并说明是否已开通小程序后台入口。 可通过通过以下路径查看是否已开通入口:通过mp.weixin.qq.com登录小程序,在设置->接口设置中,查看是否有“实名授权”的能力卡片。 邮件申请入口的资料内容如下: 微信支付实名信息授权接口内测申请表 [图片] 第二步. 线上入口申请权限: 接收到已开通线上入口的邮件回复后。可登录小程序后台,(登录mp.weixin.qq.com的小程序账号,在设置-接口能力中)完成接口权限的申请。 包括:使用类目的配置、填写相关的申请信息等。然后等待线上审核通过。 完成以上两步工作,且线上审核通过后,即可按照后续的接口文档进行开发调用。 备注:此接口一直处于内测邀请阶段。但由于近期申请流程和方式,需要根据合规监管及用户敏感隐私信息加强审核的要求,进行修改调整。因此申请流程修改调整期间,此接口暂不接受新的小程序申请;已经在使用此接口的小程序,暂不受影响。(建议正在使用的小程序,限于自身业务使用,并加强用户隐私信息保护); 正在申请中的小程序,近期请留意wx_city@tencent.com官方邮件的回复,以进一步增加说明材料。 四、接口文档 4.1使用方法及参数 使用方法为: 需要将 <button> 组件 open-type 的值设置为 getRealnameAuthInfo,当用户点击并同意之后,可以通过 bindgetRealnameAuthInfo事件回调获取到微信服务器返回的auth_token,再用auth_token调用API来获取用户加密过后的实名信息 示例: <button open-type=“getRealnameAuthInfo” bindgetRealnameAuthInfo=“authinfo” category-id="{{[99, 904]}}">实名授权</button> 调用参数为: [图片] 错误码说明: errCode:40003 errMsg:category id not exist 错误说明:添加的类目ID有误(需检查类目ID是否准确。仅需使用一级和二级类目ID即可) 4.2获取小程序类目 本节主要描述如何获取授权小程序账号的可选类目。 (1)请求方式: get(请使用https协议) https://api.weixin.qq.com/wxa/get_category?access_token=TOKEN (2)参数说明 access_token (3)返回说明(正常时返回的json示例): { “errcode”:0, “errmsg”: “ok”, “category_list” : [ { “first_class”:“工具”, “second_class”:“备忘录”, “first_id”:1, “second_id”:2, } { “first_class”:“教育”, “second_class”:“学历教育”, “third_class”:“高等” “first_id”:3, “second_id”:4, “third_id”:5, } ] } (4)返回参数说明: 参数 说明 category_list 可填选的类目列表 first_class 一级类目名称 second_class 二级类目名称 third_class 三级类目名称 first_id 一级类目的ID编号 second_id 二级类目的ID编号 third_id 三级类目的ID编号 (5)错误码说明: 返回码 说明 -1 系统繁忙 4.3获取实名信息 说明 根据小程序返回的auth_token获取用户加密过后的实名消息 使用方法 通过https POST请求,数据为json格式 请求url https://api.weixin.qq.com/cgi-bin/wxopen/getrealnameinfo?access_token={access_token} access_token说明 详见公众号开发文档, api使用的appid必须和小程序的appid保持一致 请求参数 [图片] 返回参数 [图片] 示例: #!/bin/bash TOKEN=‘xxxxxxxxxxxx’ URL=‘https://api.weixin.qq.com/cgi-bin/wxopen/getrealnameinfo’ JSON=’{ “auth_token”: “xxx”, “mch_id”: “xxx”, “cert_serialno”: “xxx”, “timestamp”: 1234444, “sign”: “xxx” }’ curl “${URL}?access_token=${TOKEN}” -d ${JSON} 返回码 返回码说明: [图片] 4.4数据加密文档说明 说明 由于实名信息属于敏感数据,不能以明文数据传输,所以开发者需要用私钥对请求进行签名(sha256后base64编码). 微信支付会对用户的姓名和身份证信息用开发者的公钥加密 ,开发者可以使用私钥解密出明文. 加密的padding算法为RSA_PKCS1_PADDING 商户号、证书序列号和私钥文件的获取详见第五部分微信支付证书指引 签名原串 cert_serialno={cert_serialno}×tamp={timestamp} 签名示例 #!/bin/bash cert_serialno='1234567890’ timestamp=[代码]date +%s[代码] private_key_file=“1900006511_rsa_private_key.pem” ori_content="cert_serialno=${cert_serialno}×tamp=${timestamp}" echo $ori_content sign=[代码]echo -n $ori_content | openssl dgst -sha256 -binary -sign $private_key_file | base64 -w 0[代码] echo “sign: $sign” 解密示例 #!/bin/sh encryted_real_name="BtqSM3KOyt+mDhJhyLCS9vsEoo3gTBupZHwS3i8daCyrUGxlEv+k7cE6U+9eiTo2DPNMouZnPSqv5vRERvwvm//JwkKdrV/xvSB4Ak7mJB+/t4Y4lV6gfeyggzN4xtdWoJfkgm0wa4V7oZGrpnexdwYuwyJYTMoz+87qJRwUfWAgF7U7trJ+b5DvCk9Y6KwT0N4j6PtDAk23k0zg06rTANzU3Mq1IWF7LVBcvSvR9nkNAPzcv06LQ70kxqQqVj5z+H+ERuILwBjuIQozCh6pO37Q3slz8UNnl7r48vw7uZe6be1fSDyf0hYE43n2DMpljnATQOMeJxp7nBrsvwDdPQ==" private_key_file=“1900006511_rsa_private_key.pem” echo -n $encryted_real_name | base64 -d | openssl rsautl -decrypt -ssl -inkey $private_key_file | iconv -f gbk -t utf-8 4.5微信支付商户申请指引 敏感数据需要使用权威CA颁发的API证书来加密。 如果已经获取到了权威CA颁发的API证书,可直接使用。 未获取到的话, 可按下面的方法操作: 证书申请或升级: 登录商户平台申请或者升级到权威CA颁发的证书。 申请指引 升级指引 查看证书序列号: 登录微信支付商户平台:pay.weixin.qq.com,进入【账户中心】->【账户设置】->【API安全】,点击“查看证书”文字按钮。 [图片] 点击查看证书,即可看到证书序列号。 [图片] 五、案例展示 案例:粤省事小程序,实名信息登录。 粤省事小程序是广东省政务一站式服务小程序,为了给用户便捷的体验,使用了微信支付实名授权功能。一方面校验使用者的身份,一方面便捷的获取用户信息,以便为用户提供个性化的政务服务。 具体实现的效果截图如下: [图片]
2022-11-22 - 「笔记」订阅消息体验踩坑
前言 10月12日夜晚社区发了公告小程序模板消息能力调整通知,正式发布了 一次性订阅消息 这一能力,所以第一时间进行了体验。 本文主要是补充一下官方未提供的使用方法,和使用中与模板消息用法的不同。 文档地址 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html 使用方法 [代码]wx.requestSubscribeMessage({ tmplIds: ["模板id1","模板id2"], success: function (res) { //成功 }, fail(err) { //失败 console.error(err); } }) [代码] 第一个坑 如果不勾选红色方框内的内容,用户每次触发订阅消息功能都会弹出授权窗口,如果用户勾选了则不会出现弹窗。 [图片] 第二个坑 目前开发者工具(v1.02.191012)不支持调试,只能通过真机调试。 [图片] 第三个坑 微信不会为开发者保存订阅次数,需要自己在后台记录用户触发的次数。 超过次数调用接口下发订阅消息会返回失败。 [图片] 第四个坑 发送模板格式和原来的模板消息格式不一致,特别是data内的内容,订阅消息的字段key是和数据类型有关,value的参数需要严格按照设置的类型提交,具体使用参考后台的模板详情。 模板消息的格式: [代码]"data": { "keyword1": { "value": "内容", "color": "#000" }, "keyword2": { "value": "内容", "color": "#000" } } [代码] 订阅消息的格式: [代码]"data": { "thing1": { "value": "内容" }, "number2": { "value": 20 } [代码] 第五个坑 订阅消息申请模板的时候,需要选择所属类目,而且只能是自己小程序相关类目,模板消息是不需要选择对应类目的。 如果删除小程序类目,则会把订阅消息模板一起删除,需谨慎操作。 [图片] 第六个坑 长期订阅消息只针对特定行业开放,所以普通开发者并无法使用。 结束 暂时就先总结这些,有其它坑再补充。
2019-10-13 - 一键完成小程序国际化
随着小程序使用的人数增长,小程序管理后台陆续收到非中文母语的用户要求支持英文的请求。小程序一开始是直接在程序里使用中文字符串的方式,要做国际化只能把这些硬编码中文的地方全部替换为i18n调用。写了个脚本跑了下,发现小程序里涉及到需要转换硬编码的地方有2000+处。。。手动修改不太科学,于是写了个名为mina-i18n的工具,用于小程序的国际化转换,有兴趣的同学可以用自己的小程序项目实验一下: npm install -g mina-i18n mina-i18n /path/to/origin/mina/project /path/to/i18n/mina/project工具主要包含对JS和WXML两种文件的处理,JS使用babel处理,WXML使用htmlparser库处理,json和css由于没法使用函数调用,这个需要用户自行处理了。 JS处理: JS文件的处理思路:找到中文字符串,将其改为以中文字符串为参数的i18n函数调用。我们的目标是一键转换后可以直接运行,而替换的行为发生在任何一个文件里,所以我们需要一个全局可访问的函数。在小程序里,有两个可以全局访问的变量,一个是getApp(),一个是wx,为了代码的简洁性,我们决定在wx上挂载一个L函数作为全局i18n函数。即: "中文" 转换为 wx._t("中文")直接使用正则表达式匹配是无法做到的符合语法的替换,这里使用babel完成保留程序上下文的替换,替换逻辑写在babel 的插件里的 StringLiteral 的回调里(所有JS程序里解析到的字符串都会进入这个回调),代码如下: const visitor = { StringLiteral(path) { const parentPath = path.parent // 每个中文字符串只处理一次,识别到已处理过就退出,不然会死循环 if (t.isCallExpression(parentPath)) { const callee = parentPath.callee if (t.isMemberExpression(callee)) { const { object, property } = callee if ( t.isIdentifier(object) && object.name === MINA_I18N_JS_FUNCTION_CALLEE && property.name === MINA_I18N_FUNCTION_NAME ) { return } } } const reg = /\s*[\u4E00-\u9FA5]+\s*/g const stringValue = path.node.value if (reg.test(stringValue)) { path.replaceWith(t.CallExpression( t.MemberExpression( t.identifier(MINA_I18N_JS_FUNCTION_CALLEE), t.identifier(MINA_I18N_FUNCTION_NAME) ), [t.stringLiteral(stringValue)] )) } } } WXML处理: 对WXML的处理思路与JS类似,就是找出WXML文件里的中文文本,替换为以文本为参数的i18n函数调用。在小程序里使用函数调用需要用到 WXS语言 ,这个语言无法使用小程序API,只有极少数的类库,反正你当成是个纯运算逻辑的WXML语法助手就行。因为WXS语言没法从外部获取数据,或者WXML里的i18n函数调用只能是类似 i18nFunc("中文","en") 这种形式,就是说具体要转为哪种语言,必须明确地告知WXS的函数。一般用户的语言都是从系统信息中获取,或者用户手动选择后存在服务端,这个语言的变量信息我们在JS里可以获取,而且这个变量每个页面都会用到,而WXML里是没法访问到getApp()和wx这些全局变量的,我们只能在每个页面的Page函数的data变量里加上这个语言变量Lang,才能在每个WXML里使用类似 i18nFunc("中文", Lang) 的形式来做文本替换。处理代码如下: const visitor = { ObjectProperty(path, state) { if (path.node.key.name === 'data') { const parentPath = path.parentPath || {} const parentParentPath = parentPath.parentPath || {} const node = parentParentPath.node if ( t.isObjectExpression(parentPath) && t.isCallExpression(parentParentPath) && node && node.callee && t.isIdentifier(node.callee) && node.callee.name === 'Page' ) { // 变量插入只处理一次,识别到已处理过就退出,不然会死循环 if ( t.isCallExpression(path.node.value) && t.isObjectExpression(path.node.value.arguments[0]) && path.node.value.arguments[0].properties[0].key.name === MINA_I18N_LANG_NAME ) { return } path.replaceWith( t.objectProperty( path.node.key, t.CallExpression( t.MemberExpression( t.identifier('Object'), t.identifier('assign') ), [ t.objectExpression([ t.objectProperty( t.identifier(MINA_I18N_LANG_NAME), t.CallExpression( t.MemberExpression( t.identifier(MINA_I18N_JS_FUNCTION_CALLEE), t.identifier(MINA_I18N_GETLANG_FUNCTION_NAME) ), [] ) ) ]), path.node.value ] ) ) ) } } } } 有了可在WXML里运行的 i18n函数,接下来就是对WXML源文件里的中文文本进行替换。WXML的转换处理要比JS复杂一些,处理JS的时候babel帮我们做好了语法树的解析、遍历和代码生成过程,我们只要实现语法树访问的回调就行,WXML就没有这么好用的工具了,所以需要自己做多点工作。WXML的解析用了 htmlparser2 ,这个库会返回给你一棵DOM树,按着根节点就能遍历整棵树。不过在实际解析生成WXML的时候,发现这个库有个地方没法满足,就是它解析出来的节点属性无法区分开是为空还是 boolean attributes ,也就是说<view hidden></view>和<view hidden=""></view>解析出来的结果都是一样的{hidden: ""},这其实有很大的歧义,因为在小程序里<view hidden></view>会表示这个view的hidden=true,而<view hidden=""></view>这会被解释为hidden=false,因此按照这个解析结果生成的话,就发现原来小程序里应该隐藏的view都被显示了。。。搜了下其他有其他人给作者提过这个问题的 issue ,我看作者的意思大概是这个库不是用来处理序列化或者生成代码这类事的,在github搜了下好像这个库又确实是JS里HTML解析库里排名第一的,于是就自己fork出来 一份,加了个选项来做boolean attributes 的区分。 在做WXML处理的时候,我犯了个错误,就是把处理WXML跟处理HTML这两件事等同起来了,可能之前做过一些HTML相关的处理工作,所以写着写着就很顺地按着原来用正则表达式处理的方式去做了,结果发现在处理节点属性为{{xxx}}的动态属性时总是有edge case处理不到。中间隔了两天去做其他需求后再回来一想,这{{xxx}}里的xxx就是JS代码啊,直接用babel不就完事了吗?困在原来处理HTML的思路里调试了大半天浪费时间。正确的处理WXML的方式就是用htmlparser解析静态文本,解析到的{{xxx}}语法交给babel处理,最后再重新合成WXML代码。这里面还有属性的单双引号处理、uincode与汉字的一些处理等细节,具体就不展开了,代码里都有: function buildWXML(root) { if (Array.isArray(root)) { let xmlString = '' root.forEach(node => { xmlString += buildWXML(node) }) return xmlString } else if (root.type === 'text') { const text = root.data return processMinaTemplateText(text) } else if (root.type === 'tag') { const tagName = root.name.replace('wx-', '') let tagString = `<${tagName}` const attr = root.attribs || {} Object.keys(attr).forEach(key => { if (attr[key] === null) { tagString += ` ${key}` } else { const attrValue = processMinaTemplateText(attr[key], { isAttrValue: true }) tagString += ` ${key}="${attrValue}"` } }) const children = root.children || [] if (isSelfCloseTag(tagName) && children.length === 0) { tagString += '/>' } else { tagString += '>' children.forEach(node => { tagString += buildWXML(node) }) tagString += `</${tagName}>` } return tagString } else if (root.type === 'comment') { return `<!--${root.data}-->` } else { return '' } } function processMinaTemplateText(text, options = {}) { const textArray = text.split(/({{[^}]*}})/g) let returnText = '' textArray.forEach(item => { if (item.startsWith('{{') && item.endsWith('}}')) { let expression = item.substring(2, item.length - 2) returnText += '{{' + processMinaTemplateExpression(expression) + '}}' } else { returnText += processPlainText(item) } }) return returnText } function processMinaTemplateExpression(code) { const visitor = { StringLiteral(path) { const parentPath = path.parent if (t.isCallExpression(parentPath)) { const callee = parentPath.callee if (t.isIdentifier(callee) && callee.name === MINA_I18N_FUNCTION_NAME) { return } } const reg = /\s*[\u4E00-\u9FA5]+\s*/g const stringValue = path.node.value if (reg.test(stringValue)) { path.replaceWith(t.CallExpression( t.identifier(MINA_I18N_FUNCTION_NAME), [t.stringLiteral(stringValue), t.identifier(MINA_I18N_LANG_NAME)] )) createI18NData(stringValue) } } } try { const result = babel.transform(code, { plugins: [ { visitor } ], generatorOpts: { quotes: 'single', compact: false } }) const i18nScriptContent = transformText(toSingleQuotes(result.code)) return i18nScriptContent.replace(/[\r\n]+$/g, '').replace(/^;/g, '').replace(/;$/g, '') } catch (e) { console.log(`parser expression : ${code}, error: ${JSON.stringify(e)}`) return code } } 翻译:一开始做这个工具的目的,就是要做到一键转换后就可以在微信开发者工具里跑起来。经过上面两步的处理,我们已经把项目里出现过的所有中文字符串都提取并替换,接下来我们就要实现 i18n 翻译函数。 最简单的是简体和繁体的转换,因为已经有 opencc 这个优秀的开源库可以处理 。 const OpenCC = require('opencc') const opencc = new OpenCC() const hant_text = opencc.convertSync(text) 中英文的翻译没法离线,只能找线上服务。在找了google,有道,金山,必应,百度等翻译服务后,发现微软的必应是最方便的,不需要申请token或者去对抗防爬虫,翻译效果也不错,非常适合用在一个可以无条件使用的工具里,接口调用非常简洁: request.post({ url: 'https://cn.bing.com/ttranslatev3', form: { fromLang: 'zh-Hans', to: 'en', text: text }, json: true, timeout: 2000 }).then(res => { return res[0].translations[0].text }) 绝大多数中文项目的i18n做到繁体+英文就够了,外贸项目等特殊情况的话,改一下调用bing API的语言参数就行,bing支持多种语言翻译。 至此,工具也算基本完成了,使用了自己小程序和github上找的几个开源小程序项目做测试,基本都是一键转换后就能直接在开发者工具上跑的,真机预览也没出问题。有兴趣的同学可以在自己项目的小程序上跑跑看。我看了下日常使用的各个小程序,可以说几乎100%都是没有提供切换语言选项的,万一以后有需求的话,希望这个工具可以方便到大家。 对源码有兴趣的同学可以到 github 看一下,如果转换的小程序跑起来有错误的话也欢迎提issue,我会尽快解决的。
2019-08-13 - 用小程序·云开发打造功能全面的博客小程序丨实战
用小程序·云开发将博客小程序常用功能“一网打尽” 本文介绍mini博客小程序的详情页的功能按钮如何实现,具体包括评论、点赞、收藏和海报功能,这里记录下整个实现过程和实际编码中的一些坑。 评论、点赞、收藏功能 实现思路 实现文章的一些操作功能,最主要的还是评论,这是作者和读者之间沟通的桥梁,评论功能的衍生无非是细化作者和读者之间的互动,或者增加文章的传播,所以在动手开发时需要思考下你期望实现哪些功能,并对应功能进行细化。 我一般的经验是,先在脑子里过一遍需要的功能和大致流程,然后在笔记稍微画下「最最基础的原型,相当于产品的角色」。 然后就开始直接开始搭建页面和简单的交互「使用假数据,优先完成页面」,在构造页面的时候其实也能够补充最初想法上一些流程上的缺陷,这样在设计后端和数据库结构的时候可以补上,整体下来也基本比较完善了。 回头看我的小程序的需求,首先肯定是操作,在文章底部需要有个操作栏,用于发送点评和其他一些操作,在参考了一些同类型的小程序之后,逐步实现自己的一套风格,样式截图如下: [图片] 在有了功能之后,点评的数据需要有地方展示「通常是文章底部」,然后就有了文章底部的评论列表,样式如下: [图片] 既然有[代码]点赞[代码]和[代码]收藏[代码]的功能按钮,是否用户需要看下我点赞和收藏的文章列表呢,所以在「我的」中就有相应的列表,样式如下: [图片] 到这里,最最基础的功能基本差不多,接下来就要看后端是否能支持这些页面了「主要就是数据的保存和展示了」 对于评论来说,肯定需要一个集合用于保存用户的评论,而对于用户的喜欢和收藏也需要一个集合来进行保存。 所以根据页面我们就可以设计[代码]mini_comments[代码]和[代码]mini_posts_related[代码]两个集合。前者用于保存评论数据,后者用户保存用户操作与文章之间的关联。 剩下的工作就是变现了,无非就是页面交互和数据的增删改查了。 细节点解析 关于评论数量 目前在文章的集合中有个[代码]totalComments[代码]这个属性,当这篇文章每新增一个评论时,需要加1。 最初在写这个的时候,每次都是先查再更新,两段式,原代码如下: [代码] let count=post.totalComments+1; let result =await db.collection('mini_posts').doc(event.commentContent.postId).update({ data: { totalComments: count } }); [代码] 后来看文档发现,可以使用[代码]db.command.inc[代码]这个指令,无需再查一遍,直接可对原字段加1,还能保证原子性。代码如下: [代码] const _ = db.command let result = db.collection('mini_posts').doc(event.commentContent.postId).update({ data: { totalComments: _.inc(1) } }); [代码] 关于新增子评论 需要实现在某个评论下进行回复。 在交互上,点击评论者的昵称或头像时,触发相应的点击事件,在事件中去记录相应的评论ID及必要数据,同时去设置焦点到评论框内: [代码] /** * 点击评论内容回复 */ focusComment: function (e) { let that = this; let name = e.currentTarget.dataset.name; let commentId = e.currentTarget.dataset.id; let openId = e.currentTarget.dataset.openid; that.setData({ commentId: commentId, placeholder: "回复" + name + ":", focus: true, toName: name, toOpenId: openId }); }, [代码] 利用云开发新增子评论时可以使用[代码]db.command.push[代码]来进行操作「更新指令,对一个值为数组的字段,往数组尾部添加一个或多个值」,往子评论集合中新增: [代码] /** * 新增子评论 * @param {} event */ async function addPostChildComment(event) { let task = db.collection('mini_posts').doc(event.postId).update({ data: { totalComments: _.inc(1) } }); await db.collection('mini_comments').doc(event.id).update({ data: { childComment: _.push(event.comments) } }) await task; } [代码] 关于判断是否已收藏 在文章第一次加载时,我们需要判断下该用户是否有对该文章有相关操作,如果有相应的收藏和点赞操作,在初始化时需要更新相应的功能图标,核心代码如下: [代码] /** * 获取收藏和喜欢的状态 */ getPostRelated: async function (blogId) { let where = { postId: blogId, openId: app.globalData.openid } let postRelated = await api.getPostRelated(where, 1); let that = this; for (var item of postRelated.data) { if (config.postRelatedType.COLLECTION === item.type) { that.setData({ collection: { status: true, text: "已收藏", icon: "favorfill" } }) continue; } if (config.postRelatedType.ZAN === item.type) { that.setData({ zan: { status: true, text: "已赞", icon: "appreciatefill" } }) continue; } } }, [代码] 至于其他一些交互细节和代码细节,可以自行阅读源码去体会,如果有任何疑问或者有更好的实现方式,也可以与我沟通。 海报功能 交代些背景 其实在最早之前的小程序中已经实现了一次,具体可以参考利用云开发优化博客小程序(三)——生成海报功能,主要还是使用原生的[代码]cavans[代码]进行组装,原本想代码copy过来改改就行了,但总觉得原来的代码写的不是特别好。 于是想看看是否有现成的轮子可以利用,果然发现了[代码]wxa-plugin-canvas[代码]这款组件,通过非常简单的配置就可以生成精美的海报。 小程序使用npm 在总结生成海报功能之前还是有必要记录下小程序npm的使用,避免一些不必要的坑。 考虑到小程序本身的大小限制,使用npm的方式是最佳的。 原因是根据官方文档介绍,小程序 npm 包里只有构建文件生成目录会被算入小程序包的占用空间,上传小程序代码时也只会上传该目录的代码。这样大大减少了上传的代码体积。 下面简单介绍下小程序端如何使用npm的「其实根据官方文档按照步骤就可以了」。 以我目前小程序的路径为例,在[代码]/miniprogram[代码]新增文件夹[代码]node_modules[代码],在命令行指向到[代码]/miniprogram[代码]目录下: [图片] 通过命令进行安装: [代码] npm install wxa-plugin-canvas --production [代码] 安装成功后,即可在小程序开发工具中进行构建,构建前需要勾选[代码]使用 npm 模块[代码] [图片] 然后点击开发者工具中的菜单栏:工具 --> 构建 npm即可: [图片] 构建完成后会生成miniprogram_npm目录,到这里,项目端基本就调通了。 [图片] wxa-plugin-canvas 在构建完之后,就可以正常使用wxa-plugin-canvas这个自定义组件,使用方式还是比较简单的。 首先在你需要的页面引入该组件: [代码] { "usingComponents": {"poster": "wxa-plugin-canvas/poster"} } [代码] 然后就可以在[代码]wsml[代码]中使用了: [代码] <poster id="poster" hide-loading="{{false}}" preload="{{false}}" config="{{posterConfig}}" bind:success="onPosterSuccess" bind:fail="onPosterFail"></poster> [代码] 由于我们在生成海报前,需要异步获取一些用于海报的数据,所以我们采用异步生成的海报方式。 需要引入该组件的[代码]poster/poster.js[代码]文件,然后在代码中调用即可: [代码] import Poster from '../../utils/poster'; Page({ /** * 异步生成海报 */ onCreatePoster() { // setData配置数据 this.setData({ posterConfig: {...} }, () => { Poster.create(); }); } }) [代码] 核心代码解析 海报需要的数据 先来看看分享海报的整体结构: [图片] 首先需要确认海报的构成需要哪些数据,在调用组件前先获取好相应的数据。 在我设计的海报中主要包含三块内容,用户的信息(头像和昵称),文章信息(首图,标题,简介)和最重要的文章的小程序码。 用户信息和文章信息其实比较简单,在小程序的详情页两者数据都有,但这里有两个问题点需要注意下。 第一个是域名问题,在画布中使用到的图片都需要配置域名,头像的域名和公众号文章首图的域名 [代码] https://mmbiz.qpic.cn https://wx.qlogo.cn [代码] [图片] 第二个是公众号首图的问题,公众号素材列表返回的图片url其实是[代码]http[代码]的,但小程序规定绑定的域名必须是[代码]https[代码]的,当时比较无奈,后来尝试改用https访问首图的url也可以,不幸中的万幸,所以在使用首图地址时进行替换下: [代码] imageUrl = imageUrl.replace('http://', 'https://') [代码] 最后就是文章的小程序码了,需要利用小程序的[代码]getUnlimited[代码]的api,具体可以参考官方文档,目前已经提供了云调用的方式「无需获取access_token」,调用起来还是比较简单的。 原本打算在文章同步的时候「adminService」直接生成对应文章的小程序码,代码写完后本地调试可以,但上传至云端后测试发现一直报错,逛了轮胎才知道原来不支持,同时触发器也不支持云调用,所以这个计划泡汤了,我在代码中打了TODO。 [图片] 既然这样,那就在生成海报的时候进行生成,同时生成后直接上传至云存储,将对应的FileID保存至文章集合中,这样只用生成一次就可以一直使用了,具体代码如下: [代码] /** * 新增文章二维码 * @param {} event */ async function addPostQrCode(event) { let scene = 'timestamp=' + event.timestamp; let result = await cloud.openapi.wxacode.getUnlimited({ scene: scene, page: 'pages/detail/detail' }) if (result.errCode === 0) { let upload = await cloud.uploadFile({ cloudPath: event.postId + '.png', fileContent: result.buffer, }) await db.collection("mini_posts").doc(event.postId).update({ data: { qrCode: upload.fileID } }); let fileList = [upload.fileID] let resultUrl = await cloud.getTempFileURL({ fileList, }) return resultUrl.fileList } return [] } [代码] 但这里有个尴尬的地方是,生成小程序码的api中的[代码]scene[代码]参数最大长度是32,而文章id的长度已经是32了,无法根据文章id进行拼接跳转页面的路径了,所以这里暂时用了[代码]mini_posts[代码]集合中timestamp字段「理论上也是唯一的」。 所以在详情页中也需要兼容timestamp这个字段。 海报图片展示 海报图片展示就比较简单了,使用个弹窗,将生成好的海报图片进行展示即可: [代码] /** * 生成海报成功-回调 * @param {} e */ onPosterSuccess(e) { const { detail } = e; this.setData({ posterImageUrl: detail, isShowPosterModal: true }) console.info(detail) }, [代码] 保存海报图片 保存图片使用wx.saveImageToPhotosAlbum调用用户相册,这里主要需要兼容用户拒绝相册授权的一些列操作,具体代码如下: [代码] /** * 保存海报图片 */ savePosterImage: function () { let that = this wx.saveImageToPhotosAlbum({ filePath: that.data.posterImageUrl, success(result) { console.log(result) wx.showModal({ title: '提示', content: '二维码海报已存入手机相册,赶快分享到朋友圈吧', showCancel: false, success: function (res) { that.setData({ isShowPosterModal: false, isShow: false }) } }) }, fail: function (err) { console.log(err); if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") { console.log("再次发起授权"); wx.showModal({ title: '用户未授权', content: '如需保存海报图片到相册,需获取授权.是否在授权管理中选中“保存到相册”?', showCancel: true, success: function (res) { if (res.confirm) { console.log('用户点击确定') wx.openSetting({ success: function success(res) { console.log('打开设置', res.authSetting); wx.openSetting({ success(settingdata) { console.log(settingdata) if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('获取保存到相册权限成功'); } else { console.log('获取保存到相册权限失败'); } } }) } }); } } }) } } }); }, [代码] 体验总结 有好的开源组件可以充分利用,避免重复造轮子,有机会也可以学习下别人的实现方式。 多看看文档,其实小程序的文档真的挺详细的。 这里主要想分享实现一个功能实现的过程,有想法的时候如何一步步去成功实现。 小程序本身不难,相应的文档也很详细,但是组装的过程和逻辑的实现需要自身去思考和体会。多看看文档,其实小程序的文档真的挺详细的。 如果你的想法和流程都非常清晰,但还是没办法实现你的预期功能,那我建议你先放放,先把[代码]html[代码],[代码]css[代码],[代码]javascript[代码]熟悉下,再看几遍小程序的文档,也许你当时面临的问题就不再是问题了。 源码链接 https://github.com/TencentCloudBase/Good-practice-tutorial-recommended 如果你有关于使用云开发CloudBase相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们哦~比心! [图片]
2019-08-26 - 微信开发者工具 1.02.1905081 更新说明
下载地址https://developers.weixin.qq.com/miniprogram/dev/devtools/rc.html 增强编译新版工具增加了[代码]增强编译[代码]的选项来增强[代码]ES6转ES5[代码]的能力,启用后会使用新的编译逻辑以及提供额外的选项供开发者使用。 启用[代码]增强编译[代码]后的编译能力的对比: 特性原有逻辑增强编译Babel版本babel6babel7Presetses2015、stage0env {chrome:53, ios:8}Helpers单文件内联跨文件共享Async/Await不支持支持严格模式开关不支持支持忽略文件目录不支持支持代码压缩uglify-jsterserBabel插件-一系列proposal[代码]*[代码]polyfill大部分es6新增三个polyfill[代码]*[代码]使用[代码]preset-env[代码],支持最新的[代码]ECMAScript[代码]语法 共享helpers函数,默认放在项目[代码]@babel/runtime[代码]目录,可通过[代码]项目配置文件[代码]配置 支持async/await语法,按需注入[代码]regeneratorRuntime[代码],目录位置与helpers函数一致 文件首行是[代码]// use strict disable;[代码]时,即可禁用文件严格模式 可通过[代码]项目配置文件[代码]指定任意文件、目录不经过编译(如:miniprogram_npm) 原有逻辑是支持[代码]stage0[代码]语法的,为了向前兼容,引入了一系列proposal插件 关于polyfill,基础库中已经引入了大量的[代码]es6[代码]相关的polyfill 可参考文档,增强编译下,新增:Array.prototype.includes[代码](es7)[代码]、Object.entries[代码](es8)[代码]、Object.values[代码](es8)[代码] 开启增强编译项目详情页中,勾选[代码]增强编译[代码]选项 [图片] 打开后,即可在项目中使用最新的js语法 [图片] 工具运行时,会按需注入一些辅助函数和regeneratorRuntime, 注入的目录路径可通过配置修改,上传代码时(包括预览时)这些文件会作为代码包的一部分 [图片] 其他选项其他可选项详情请参考项目配置文档 任务通知中心任务完成后的消息通知会自动进入通知中心,通知中心入口在右下角的任务状态栏 [图片] 任务状态栏也进行了优化,以图标形式显示正在进行中的任务、失败任务数、及通知中心入口。 [图片] 控制台命令 cleanAppCache在新版中,我们升级了 nw 内核,如果回退到旧版本,会在启动旧版本时出现提示 nw 版本不匹配的弹框,如需回退且避免这种情况,可在回退前在小程序调试器输入命令 cleanAppCache,然后再安装旧版本。 上传时版本号推荐上传的对话框现在会显示版本号推荐 [图片] 控制台显示当前页面是否被索引[图片] 代码保护现在默认打开开启代码保护之后可以增加编译后的代码包的破解难度,在详情页中可以设置是否开启。 设置中增加通用设置[图片] Network 面板显示图片渲染层的图片请求现在也会展示在 Network 面板。 [图片] Nightly 快速更新机制Nightly 版本是包含我们最新能力和 bugfix 的版本。现在 Nightly 版本支持快速更新和快速回退。安装了 Nightly 版本开发者工具后,如果再进行了升级,此时可以在菜单的检查更新下会有快速回退的选项,可以快速回退到上一个版本。
2019-05-08 - Web直播,你需要先知道这些
转自IMWeb社区,原文链接 Web直播,你需要先知道这些 直播知识小科普 一个典型的直播流程:录制->编码->网络传输(推流->服务器处理->CDN分发)->解码->播放 IPB:一种常用的视频压缩方案,用I帧表示关键帧,B帧表示前向差别帧,P帧表示双向差别帧 GOP (Group of Pictures):GOP 越长(I帧之间的间隔越大),B 帧所占比例越高,编码的率失真性能越高。虽然B帧压缩率高,但解码时CPU压力会更大。 音视频直播质量好坏的主要指标:内容延时、卡顿(流畅度)、首帧时长 音视频直播需要克服的主要问题:网络环境、多人连麦、主辅路、浏览器兼容性、CDN支持等 MSE(Media Source Extensions):W3C 标准API,解决 HTML5 的流问题(HTML5 原生仅支持播放 mp4/webm 非流格式,不支持 FLV),允许JavaScript动态构建 [代码]<video>[代码] 和 [代码]<audio>[代码] 的媒体流。可以用MediaSource.isTypeSupported() 判断是否支持某种MINE类型。在ios Safari中不支持。 [图片] 文件格式/封装格式/容器格式:一种承载视频的格式,比如flv、avi、mpg、vob、mov、mp4等。而视频是用什么方式进行编解码的,则与Codec相关。举个栗子,MP4格式根据编解码的不同,又分为nMP4、fMP4。nMP4是由嵌套的Boxes 组成,fMP4格式则是由一系列的片段组成,因此只有后者不需要加载整个文件进行播放。 Codec:多媒体数字信号编码解码器,能够对音视频进行压缩(CO)与解压缩( DEC ) 。CODEC技术能有效减少数字存储占用的空间,在计算机系统中,使用硬件完成CODEC可以节省CPU的资源,提高系统的运行效率。 常用视频编码:MPEG、H264、RealVideo、WMV、QuickTime。。。 常用音频编码:PCM、WAV、OGG、APE、AAC、MP3、Vorbis、Opus。。。 现有方案比较 RTMP协议 基于TCP adobe垄断,国内支持度高 浏览器端依赖Flash进行播放 2~5秒的延迟 RTP协议 Real-time Transport Protocol,IETF于1996提出的一个标准 基于UDP 实时性强 用于视频监控、视频会议、IP电话 CDN厂商、浏览器不支持 HLS 协议 Http Live Streaming,苹果提出的基于HTTP的流媒体传输协议 HTML5直接支持(video),适合APP直播,PC断只有Safari、Edge支持 必须是H264+AAC编码 因为传输的是切割后的音视频片段,导致内容延时较大 [图片] flv.js Bilibli开源,解析flv数据,通过MSE封装成fMP4喂给video标签 编码为H264+AAC 使用HTTP的流式IO(fetch或stream)或WebSocket协议流式的传输媒体内容 2~5秒的延迟,首帧比RTMP更快 WebRTC协议 [图片] 1、Google力推,已成为W3C标准 2、现代浏览器支持趋势,X5也支持(微信、QQ) [图片] 3、基于UDP,低延迟,弱网抗性强,比flv.js更有优势 方案 CPU占用 帧率 码率 延时 首帧 flv.js 0.4 30 700kbit/s 1.5s 2s WebRTC 1.9 30 700kbit/s 0.7s 1.5s 4、支持Web上行能力 5、编码为H264+OPUS 6、提供NAT穿透技术(ICE) **实际情况下,当用户数量很大时,对推流设备的性能要求很高,复杂的权限管理也难以实现,采用P2P的架构基本不可行。对于个别用户提供上行流、海量用户只进行拉流的场景,腾讯课堂实现了一种P2S的解决方案。**进一步学习可阅读jaychen的系列文章《WebRTC直播技术》。 [图片] 小程序+直播 技术方案 基于RTMP,官方说底层使用HTTP/2的一种内部传输机制,但又说是基于UDP的,这就搞不懂了。。。 live-pusher 和 live-player 没有限制第三方云服务 可直接使用腾讯云视频直播能力,只需配置好推流url、播放url即可 推流url: [图片] 播放url: [图片] 下面是我根据官网教程搭建的一个音视频小程序,搭建过程简单,同一个局域网下直播体验也很流畅(读者也可直接搜索腾讯视频云小程序进行体验): [图片] 前端核心代码还是相当简洁的: live-pusher组件:设置好url推流地址(仅支持 flv, rtmp 格式)等参数即可,使用bindstatechange获取播放状态变化 [图片] live-player组件:设置后src音视频地址(仅支持 flv, rtmp 格式)等参数即可,使用bindstatechange获取播放状态变化 [图片] 能否和WebRTC同时使用? 对于腾讯课堂的应用场景,老师上课推流采用的是RTMP协议,考虑到WebRTC目前只能用于PC端拉流,那么在移动端能否让用户可以直接通过小程序来观看直播课呢?我觉得在技术层面可行的,接入小程序直播对于扩大平台影响力、社交圈分享、提高收费转化都会产生很大的帮助。难点在于复杂的权限控制、多路音视频流、多人连麦等问题,比如权限控制只能单独放到房间控制逻辑中完成,而音视频流本身缺乏这种校验;主辅路的切换还需要添加单独的信令控制,同时在小程序中加入相应的判断逻辑。 补充:最近看到已经有小程序的webrtc方案了,基于live-player、live-pusher组件,加入腾讯云强大的音视频后台服务,官方提供了一套封装度更高的自定义组件方案 —— <webrtc-room> ,甚至可以和Chrome打通。详情可以参考WebRTC 互通、webrtc-room [图片] 参考文章 HTTP 协议入门 使用flv.js做直播 面向未来的直播技术-WebRTC【视频、PPT】 小程序音视频能力技术负责人解读“小程序直播” 小程序开发简易教程 小程序音视频解读
2019-03-26 - 你不知道的Virtual DOM(二):Virtual DOM 的更新
一、前言 目前最流行的两大前端框架,React 和 Vue,都不约而同的借助 Virtual DOM 技术提高页面的渲染效率。那么,什么是 Virtual DOM ?它是通过什么方式去提升页面渲染效率的呢?本系列文章会详细讲解 Virtual DOM 的创建过程,并实现一个简单的 Diff 算法来更新页面。本文的内容脱离于任何的前端框架,只讲最纯粹的 Virtual DOM 。敲单词太累了,下文 Virtual DOM 一律用 VD 表示。 这是 VD 系列文章的第二篇,以下是本系列其它文章的传送门: 你不知道的Virtual DOM(一):Virtual Dom介绍 本文将会实现一个简单的 VD Diff 算法,计算出差异并反映到真实的 DOM 上去。 二、思路 使用 VD 的框架,一般的设计思路都是页面等于页面状态的映射,即UI = render(state)。当需要更新页面的时候,无需关心 DOM 具体的变换方式,只需要改变state即可,剩下的事情(render)将由框架代劳。我们考虑最简单的情况,当 state 发生变化时,我们重新生成整个 VD ,触发比较的操作。上述过程分为以下四步: - state 变化,生成新的 VD - 比较 VD 与之前 VD 的异同 - 生成差异对象(patch) - 遍历差异对象并更新 DOM 差异对象的数据结构是下面这个样子,与每一个 VDOM 元素一一对应: [代码]{ type, vdom, props: [{ type, key, value }] children } [代码] 最外层的 type 对应的是 DOM 元素的变化类型,有 4 种:新建、删除、替换和更新。props 变化的 type 只有2种:更新和删除。枚举值如下: [代码]const nodePatchTypes = { CREATE: 'create node', REMOVE: 'remove node', REPLACE: 'replace node', UPDATE: 'update node' } const propPatchTypes = { REMOVE: 'remove prop', UPDATE: 'update prop' } [代码] 三、代码实现 我们做一个定时器,500 毫秒运行一次,每次对 state 加 1。页面的li元素的数量随着 state 而变。 [代码]let state = { num: 5 }; let timer; let preVDom; function render(element) { // 初始化的 VD const vdom = view(); preVDom = vdom; const dom = createElement(vdom); element.appendChild(dom); timer = setInterval(() => { state.num += 1; tick(element); }, 500); } function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); } function view() { return ( <div> Hello World <ul> { // 生成元素为0到n-1的数组 [...Array(state.num).keys()] .map( i => ( <li id={i} class={`li-${i}`}> 第{i * state.num} </li> )) } </ul> </div> ); } [代码] 接下来,通过对比 2 个 VD,生成差异对象。 [代码]function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); // 生成差异对象 const patchObj = diff(preVDom, newVDom); } function diff(oldVDom, newVDom) { // 新建 node if (oldVDom == undefined) { return { type: nodePatchTypes.CREATE, vdom: newVDom } } // 删除 node if (newVDom == undefined) { return { type: nodePatchTypes.REMOVE } } // 替换 node if ( typeof oldVDom !== typeof newVDom || ((typeof oldVDom === 'string' || typeof oldVDom === 'number') && oldVDom !== newVDom) || oldVDom.tag !== newVDom.tag ) { return { type: nodePatchTypes.REPLACE, vdom: newVDom } } // 更新 node if (oldVDom.tag) { // 比较 props 的变化 const propsDiff = diffProps(oldVDom, newVDom); // 比较 children 的变化 const childrenDiff = diffChildren(oldVDom, newVDom); // 如果 props 或者 children 有变化,才需要更新 if (propsDiff.length > 0 || childrenDiff.some( patchObj => (patchObj !== undefined) )) { return { type: nodePatchTypes.UPDATE, props: propsDiff, children: childrenDiff } } } } // 比较 props 的变化 function diffProps(oldVDom, newVDom) { const patches = []; const allProps = {...oldVDom.props, ...newVDom.props}; // 获取新旧所有属性名后,再逐一判断新旧属性值 Object.keys(allProps).forEach((key) => { const oldValue = oldVDom.props[key]; const newValue = newVDom.props[key]; // 删除属性 if (newValue == undefined) { patches.push({ type: propPatchTypes.REMOVE, key }); } // 更新属性 else if (oldValue == undefined || oldValue !== newValue) { patches.push({ type: propPatchTypes.UPDATE, key, value: newValue }); } } ) return patches; } // 比较 children 的变化 function diffChildren(oldVDom, newVDom) { const patches = []; // 获取子元素最大长度 const childLength = Math.max(oldVDom.children.length, newVDom.children.length); // 遍历并diff子元素 for (let i = 0; i < childLength; i++) { patches.push(diff(oldVDom.children[i], newVDom.children[i])); } return patches; } [代码] 计算得出的差异对象是这个样子的: [代码]{ type: "update node", props: [], children: [ null, { type: "update node", props: [], children: [ null, { type: "update node", props: [], children: [ null, { type: "replace node", vdom: 6 } ] } ] }, { type: "create node", vdom: { tag: "li", props: { id: 5, class: "li-5" }, children: ["第", 30] } } ] } [代码] 下一步就是遍历差异对象并更新 DOM 了: [代码]function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); // 生成差异对象 const patchObj = diff(preVDom, newVDom); preVDom = newVDom; // 给 DOM 打个补丁 patch(element, patchObj); } // 给 DOM 打个补丁 function patch(parent, patchObj, index=0) { if (!patchObj) { return; } // 新建元素 if (patchObj.type === nodePatchTypes.CREATE) { return parent.appendChild(createElement(patchObj.vdom)); } const element = parent.childNodes[index]; // 删除元素 if (patchObj.type === nodePatchTypes.REMOVE) { return parent.removeChild(element); } // 替换元素 if (patchObj.type === nodePatchTypes.REPLACE) { return parent.replaceChild(createElement(patchObj.vdom), element); } // 更新元素 if (patchObj.type === nodePatchTypes.UPDATE) { const {props, children} = patchObj; // 更新属性 patchProps(element, props); // 更新子元素 children.forEach( (patchObj, i) => { // 更新子元素时,需要将子元素的序号传入 patch(element, patchObj, i) }); } } // 更新属性 function patchProps(element, props) { if (!props) { return; } props.forEach( patchObj => { // 删除属性 if (patchObj.type === propPatchTypes.REMOVE) { element.removeAttribute(patchObj.key); } // 更新或新建属性 else if (patchObj.type === propPatchTypes.UPDATE) { element.setAttribute(patchObj.key, patchObj.value); } }) } [代码] 到此为止,整个更新的流程就执行完了。可以看到页面跟我们预期的一样,每 500 毫秒刷新一次,构造渲染树和绘制页面花的时间也非常少。 [图片] 作为对比,如果我们在生成新的 VD 后,不经过比较,而是直接重新渲染整个 DOM 的时候,会怎样呢?我们修改一下代码: [代码]function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); newDom = createElement(newVDom); element.replaceChild(newDom, dom); dom = newDom; /* // 生成差异对象 const patchObj = diff(preVDom, newVDom); preVDom = newVDom; // 给 DOM 打个补丁 patch(element, patchObj); */ } [代码] 效果如下: [图片] 可以看到,构造渲染树(Rendering)和绘制页面(Painting)的时间要多一些。但另一方面花在 JS 计算(Scripting)的时间要少一些,因为不需要比较节点的变化。如果算总时间的话,重新渲染整个 DOM 花费的时间反而更少,这是为什么呢? 其实原因很简单,因为我们的 DOM 树太简单了!节点很少,使用到的 css 也很少,所以构造渲染树和绘制页面就花不了多少时间。VD 真正的效果还是要在真实的项目中才体现得出来。 四、总结 本文详细介绍如何实现一个简单的 VD Diff 算法,再根据计算出的差异去更新真实的 DOM 。然后对性能做了一个简单的分析,得出使用 VD 在减少渲染时间的同时增加了 JS 计算时间的结论。基于当前这个版本的代码还能做怎样的优化呢,请期待下一篇的内容:你不知道的Virtual DOM(三):Virtual DOM 更新优化
2019-03-05 - 医院室内位置需求轻松Get 道易寻位置服务插件助力小程序开发
全球医疗行业室内外位置服务领导者道一循公司行业内率先上线“医院LBS位置服务”微信小程序插件,为广大医疗行业小程序开发者提供快速、便捷的LBS室内位置能力输出,可广泛应用于各种医疗信息化场景当中。 [图片] “医院LBS位置服务”插件提供医院位置信息接口,将医院院内各类科室、设施的位置数字化后提供给小程序使用。通过此插件,小程序开发者即可调用道一循覆盖医院的室内位置信息。 目前,已有近30家全国百强三甲医院上线道一循院内位置服务系统。 “医院LBS位置服务”插件为各种医疗行业 小程序应用提供位置信息输出,支持各类基于位置的应用场景。 例如在“浙江大学医学院附属儿童医院小程序”中,实现了“在线签到”功能。 STEP 1 使用微信小程序挂号成功后进入功能页面 [图片] [图片] STEP 2 小程序通过“医院LBS位置服务”插件识别用户不在就诊科室附近,不能完成报到 [图片] STEP 3 使用院内导航引导至就诊科室 [图片] [图片] STEP 4 小程序通过“医院LBS位置服务”插件识别用户已到科室附近,可以进行报到 [图片] [图片] STEP 5 自动进入排队系统进行排队 [图片] [图片] --长按识别,进入“道易寻”小程序 --
2018-09-21 - 每周社区 | 上周社区问题反馈以及功能优化更新
各位微信开发者: 大家下午好。从本周开始,我们每周收到的 问题反馈、处理进度、社区功能更新 将以公告形式同步给大家,希望和大家一同打造小程序的生态。 一、上周问题反馈和处理进度(06.25-06.29) (一)修复中的问题: 关于微信小程序设置过多监听事件后报错 查看反馈 @font-face引入新字体,仅在IOS下且为外部字体时生效。查看反馈 查看反馈 cover-view 出现 background 无法覆盖 padding 区域 查看详情 iOS下,切换input组件,键盘会重新推出且位置计算有误 查看详情 意见反馈日志读写太频繁 查看详情 安卓全屏模式下,web-view没有占满屏幕,底部有部分留白 查看详情 工具中showToast icon='none'时无法触摸穿透 查看详情 iOS页面中多个video标签,其中一个全屏退出后video展示异常 查看详情 安卓光标移动后无法移回最后 查看详情 chooseLocation 工具上偶现只返回了经纬度 查看详情 开发工具将窗体独立之后,页面刷新/切换后不渲染 查看详情 wx.pageScrollTo导致fixed元素闪动 查看详情 小程序插件页面无法配置导航栏样式 查看详情 工具上分包预下载接口分包大小计算有误 查看详情 setBackgroundColor报无权限 接口上线后未放开权限位 查看详情 工具全局查找的查找范围不包含wxs文件 查看详情 iOS下 textarea的padding无法置0 查看详情 (二)修复完成,待客户端版本上线 安卓textarea adjust-position 设置失效 查看详情 statusBarHeight安卓返回0 查看详情 安卓text长按复制失效 查看详情 (三)已修复的问题 touch事件手机上timestamp位置不一致 查看详情 (四)需求反馈 webview 业务域名 优化 查看详情 支持自定义下拉刷新圆点的位置 查看详情 开发工具素材管理支持svg 查看详情 待讨论评估支持需求: 提供微信版本和公共库版本的对应关系数据,其他类似反馈 查看详情 uploadFile支持多文件上传 查看详情 (二)近期社区能力更新 我们在社区收到了大家希望增加”关注问题“、”收藏“帖子和通知消息回答定位的需求,近期已新增优化如下能力: 1. 增加“关注问题”的能力,关注有消息变动,会收到消息通知 2. 所有帖子新增“收藏”功能 3. 在个人主页上有所体现关注和收藏功能 4. 优化消息通知回答定位 [图片] [图片] 三、《诚邀开发者共同营造社区》的反馈 1.关于社区的官方回答 我们每天都有安排相关的同学值班回复处理大家的问题。 但是我们希望通过这些改造,促进开发者们之间的交流, 问题和回答在大家思维碰撞中产生火花。而bug和需求类,由我们来处理。 [图片] 2.关于通知中心的反馈 已优化上线该问题,可以具体定位到该问题的回答、该帖子的评论。 [图片] 3.关于已知问题 关于已知问题,大家可以关注社区“已知问题”模块,看我们的已知问题和相关修复。 [图片] 4.关于社区的评论bug 大家反馈符号超过了编辑框,帮助我们发现了一个bug,我们已经修复了这个问题。谢谢大家的反馈。 [图片] 5.关于交流群 我们希望所有的交流可以在社区完成,方便更多的开发者看见这类问题。 [图片] 感谢大家的反馈,也希望大家和我们一起共同营造社区的良好氛围。 微信团队 2018.07.03
2018-07-03