GitHub帐号
个人案例
暂无发表的插件案例 国外手机号授权登录国内小程序失败?
[图片][图片]
看完记得点赞收藏好评一键三连 Q1:小程序如何开通云闪付支付功能? A1:超级管理员扫码登录商户平台「点我访问」,点击「产品中心」->「开发配置」->「支付方式配置」->「开通“云闪付付款”功能」 小提示:支付方式配置在页面最底部 Q2:开通“云闪付付款”功能后小程序需要做开发对接吗? A2:不需要,开通“云闪付付款”功能后,商户号绑定的小程序默认就支持云闪付付款了,无需做任何开发对接,原有系统无需调整。 Q3:用户使用云闪付付款,商户收款手续费是多少? A3:与商户号原费率保持一致,举例:用户在商户A小程序使用云闪付支付100元,商户A当前费率为0.6%,则应收手续费应收为1000.6%=0.6元。 Q4:用户使用云闪付付款,商户收款资金在什么时间结算到商家银行卡? A4:用户使用云闪付付款实时到微信支付商户号,根据商户号原结算周期结算。举例:用户在商户A小程序使用云闪付支付100元,商户A当前结算周期为T+1并开通自动提现功能,则在用户付款时间后一个工作日自动提现到商户银行卡。 Q5:用户使用云闪付付款的订单应如何进行查询? A5:1.在商户后台通过交易账单中的「付款银行」字段来检索对应的订单 扫码登录商户平台-交易中心-交易账单-下载交易账单,「付款银行字段」取值为:UPQUICKPASS_CREDIT和UPQUICKPASS_DEBIT的,即为用户使用云闪付付款的交易 2.微信端可以通过商家助手小程序查看,访问「微信支付商家助手小程序」,点击「收款记录」,付款人展示为“云闪付用户”的,即为用户使用云闪付进行付款的交易。[图片] Q6:小程序“云闪付付款”功能是否支持服务商模式? A5:支持 Q7:某个小程序可以单独关闭用户使用云闪付付款功能吗? A7:可以,商户可以在商户平台指定小程序关闭该功能,并查看已关闭的小程序列表。超级管理员扫码登录商户平台「点我访问」,点击「产品中心」->「开发配置」->「支付方式配置」->「新增关闭云闪付APPID」,添加成功后该小程序用户将不再支持云闪付付款 小提示:支付方式配置在页面最底部 Q8:用户在小程序使用云闪付付款是否支持云闪付优惠? A8:支持云闪付通用优惠或全场优惠 Q9:用户使用云闪付付款后支付结果通知没有「attach」字段返回是什么原因? A9:已知问题,已排期修复 Q10 :商户后台开通了“云闪付付款”功能,为什么小程序支付时没有云闪付付款功能入口呢? A:1、手机需要安装云闪付APP 2、云闪付付款选项需要调起微信支付后才会让用户选择,当小程序自身功能有多项支付选择时,需要选择「微信支付」后才可会有云闪付付款选择[图片] Q11:用户在小程序使用“云闪付”付款后,服务商还有技术服务费吗? A:满足基础技术服务费和行业政策要求的,保持不变 Q12:商户后台开通配置云闪付后,支付时没有云闪付付款选择如何排查? 1.商户在后台关闭了此功能; 2.用户手机有安装云闪付app; 3.如属于以下场景,也不会展示云闪付:境外交易、指定身份支付、未成年支付、支付中签约; 4.商户号为新开通商户或近期无交易,要有稳定的流水后才会开启入口; 5.开通了“自助清关”产品不会展示云闪付; 6.电商收付通托管模式的商户不会展示云闪付; 7.小微商户(注:商业版小微灰度内测阶段)不会展示云闪付。 如有更多疑问可以跟帖回复,也可以拨打95017进行咨询
在注册公众号时名称已经输入 但是注册好了以后名称总是就变成了 新注册的公众号 然后需要改名字 需要上传资料审核 问了其他伙伴 他们都没有这种问题 是什么原因呢?
最近微信真的越来越严格了,群折叠之后是微信朋友圈折叠,完全不给发广告的活路。 其实朋友圈折叠功能早就有,但是这次升级可厉害了:之前都是针对文字内容的折叠,这次连图片也折叠! 更绝的是,如果你短时间发布多条广告信息,还可能被折叠收录到一个合集页,进一步减少曝光,来个「叠中叠」。 [图片] ▲图源网络 什么意思?比如你辛辛苦苦排了 6 、7 条产品的九宫格+文案,发到朋友圈就等收钱了,结果一发傻眼了,全被折叠成一条「XXX等 6 条内容」的文案,连一张图都不给你曝光!是不是气死? 还能怎么办?微信爸爸说了算,咱们改还不行! 要是你感觉自己的朋友圈内容也可能被折叠,接下来的内容你真的要看看,转发给做私域的伙伴一起学,别等朋友圈真被折叠才拍大腿后悔! 什么样的朋友圈 最可能被折叠? 直接上微信团队的官方标准: 「朋友圈是用户分享和关注朋友们生活点滴的空间,一直以来批量或高频次发送营销性信息的行为,存在侵扰用户安宁、减损用户朋友圈体验的问题,(因此)被较多用户投诉。」 划重点了朋友们:批量!高频次!发送营销性信息! 用人话再解释一遍: ① 多次发布重复或相似内容的用户,内容会被折叠! 比如你手上有大量的客服微信号,复制粘贴同样的带货素材到朋友圈发布,一旦同样的内容发布超过一定次数,折叠你没商量! ② 每天频繁发布广告的用户,内容会被折叠! 比如你在微信做一次促销活动,为了宣传短时间大量发布促销相关的朋友圈内容,也等着被折叠吧,而且要告诉你:一旦被盯上,后续即使降低发广告的频次或者发送生活内容,也同样会被折叠。 [图片] ▲生活动态的朋友圈也被折叠 如何避开折叠 安全发圈? 问题出现了,怎么解决? 我熬夜刷了一礼拜朋友圈,发现不被折叠的 3 种方法: ① 企微!企微!企微! 早就劝大家做私域一定要用企微,从来没像今天这么笃定过!还在用个微的商家抓紧转! 我观察了朋友圈所有的企微号,没发现一个朋友圈被折叠的。个微卷成团,企微稳如山! 比如下图,同一时间,同样是发送广告信息,某房产个人号的内容被折叠,而用了「推荐款」、「限时限量」等明显营销词汇的企微号反而没有被折叠。 [图片] 这绝对不是个例,同样的其他企微号,有一个算一个,朋友圈都没有被折叠的情况。 [图片] 这说明什么朋友们?朋友圈折叠功能对企微开了后门啊!你做私域不用企微,就是浪费官方倾斜的资源!个微收拾收拾,赶紧把流量导到企微,越快越好! 当然已经做企微的商家也别骄傲,企微朋友圈右上角有我不感兴趣按钮,乱发朋友圈打扰用户,被用户点了直接屏蔽 7 天,比折叠还惨。 [图片] ② 营销素材,用视频号发 你要是实在用不了企微,又觉得自己折叠风险很大,还有一招,发视频号! 我观察到目前折叠的朋友圈,都是文案+图片、或者图片的形式,但是视频号就很神奇,它不折叠! [图片] 凡是微信鼓励的就是我们要做的,朋友圈就别玩九宫格了,直接做成视频号分享,短视频带货不香吗?评论区还可以带小程序的短链,曝光转化直接一步到位。 ③ 4 套防折叠必杀话术模板 如果你不想转企微,也不想做视频,OK,我还有一招必杀给你。发文案+图片的朋友圈也能防折叠。 首先复盘一下折叠的逻辑是什么:批量或高频次发送营销性信息的行为。 硬广的套路已经玩不通了,朋友圈还可以来软的,广告没人爱看还容易被折叠,但是段子人人爱看,生活分享人人爱看啊,那就这么干,分享 4 种防折叠还能带货的朋友圈话术模板: 1、 生活分享型 这类内容玩的就是种草逻辑,用得好的商家真的太多了。 比如看到蛋糕烘焙行业的一个商家,朋友圈真的太生活化了,不看昵称还以为就是在减肥、在家做蛋糕的身边朋友。 [图片] 生活分享型的文案模板:生活经历描述+产品结合+感想。 比如你是母婴食品的商家,朋友圈可以是「夏天宝宝胃口总不好,太让人头疼了。灵机一动用XX麦片给他做了凉爽的水果粥,孩子一口气吃完还要一碗,我太机智了哈哈。」 2、 场景描述 这类文案的重点是要告诉用户:用了你的产品,生活到底会有啥变化? 比如看到一个房产人的朋友圈很有意思: 通篇没有提到买房,但是营造了在这里生活的惬意场景。 [图片] 这类内容完全可以复制,比如酒类商家描述用户劳累一天后,躺在沙发上微醺的场景、或者蛋糕商家描述用户早起后给自己做一顿精致早餐的场景,通过场景化的描述,让产品融入其中种草给用户。 3、 干货内容 传递有价值的信息永远不会过时,干货内容朋友圈不用多讲了。 比如做母婴品牌的德轩母婴,创始人每天会在朋友圈发布育儿相关的干货知识,以及用户咨询的解答截图,潜移默化树立起专家的人设,让用户有问题第一时间就能想到咨询她,评论区要是再带上干货相关的小程序商品链接,完美。 [图片] 4、 互动话题 除以上的内容以外,最后一种模板就是互动讨论。 举个例子,拼图商家图益会把拼图主题和朋友圈话题巧妙的结合起来,晒美食主题的拼图朋友圈,文案配「你的火锅必点菜是什么」来引发粉丝讨论,就是不错的互动方式。 如果你是美妆商家,互动话题可以是「你包里必备的化妆品是什么?」,如果是知识付费商家,可以是「你最近一次看书是什么时候?」等等,结合图片软性植入做营销。 [图片] 朋友圈已经被折叠 那就做好这两点 如果你朋友圈已经被折叠了,那上面的方法对你用处都不大了,我只强调两点,亡羊补牢。 ① 做好一句话营销 被折叠的内容只展示前 12 个字,最后的曝光机会了,你得把握好! 一定把朋友圈的精华都集中在前面,比如「早春少女感爆款穿搭合集」、「熬夜人群 10 种护肝食品」等,把利益点集中在前面讲解,吸引用户点开看。 ② 打造朋友圈橱窗 朋友圈被折叠了,还有朋友圈主页可以做营销。 比如看过一个商家每次朋友圈只发一张图不发文案,连续发 9 次把自己的朋友圈主页排成橱窗的样式,如下图: [图片] 用户点进个人主页看朋友圈和逛商城一样,排版样式相当美观,其实也不失为一种不错的营销方式,被折叠也不怕朋友圈没用了。 最后再分享下我总结的折叠高风险关键词: 1、 和营销直接相关的词汇:秒杀、特价、红包、顾客、价格、打折、包邮、买家、优惠、团购、代购、下单等等。 2、 各行业的高频词:蛋糕、好房、女装、口红等,也要注意不能在朋友圈频繁出现。
先上代码: 普通HTML版本为:(由于使用代码选项,保存无法显示,所以只能文本显示了) <wx-open-launch-weapp id="launch-btn" username="gh_xxxxxxxx" path="pages/home/index?user=123&action=abc" <script type="text/wxtag-template"> <style>.btn { padding: 12px }</style> <button class="btn">打开小程序</button> </script> </wx-open-launch-weapp> <script> var btn = document.getElementById('launch-btn'); btn.addEventListener('launch', function (e) { console.log('success'); btn.addEventListener('error', function (e) { console.log('fail', e.detail); </script> 如果是使用框架,则为: <div class="test-position" <wx-open-launch-weapp id="launch-btn" username="gh_** *" path="pages/index/index.html?user=123&action=abc"> <template> <style>.btn { padding: 12px; height: 100px; width: 120px; }</style> <button class="btn">打开小程序-测试方法二</button> </template> </wx-open-launch-weapp> </div> 如果框架写上还不行,可以试试在文件main.js中,写上: Vue.config.ignoredElements = ['wx-open-launch-app', 'wx-open-launch-weapp']; 代码就写完了,说说需要注意的几点吧: 1、在开放标签中,<template>或者<script>里面的写样式,千万不要使用定位position,如果非要用就在最外层的div里面写,例如我这里的class=‘test-position’这里定位; 2、如果你觉得里面写样式不好写,可以在里面样式style写opacity:0;,这样的话开放标签只是用来填充,大小自己控制就行; 3、如果你跳转之后显示页面不存在,请检查下path的路径结尾是否写上了.html; 4、开发工具是无法测试的,只能使用手机测试,如果你在手机分享功能正常,说明你初始化授权没问题,记得要在初始化授权写上开放标签openTagList: ['wx-open-launch-weapp'];如果手机发现按钮不见了,初始化授权是ok的,只是标签写法出来问题;请查看其他注意点; 微信版本要求为:7.0.12及以上。 系统版本要求为:iOS 10.3及以上、Android 5.0及以上。 5、如果还不行,然后初始化授权也是成功的,请质疑一下后台初始化授权信息的jssdk中,APPID是否你想要的公众号,眼见为实; 6、如果还不行,请移步到微信官网查看是否有其他问题:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html
背景 我们发现大部分小程序都会使用 [代码]wx.getUserInfo[代码] 接口,来获取用户信息。原本设计这个接口时,我们希望开发者在真正需要用户信息的情况下才去调取这个接口,但很多开发者会直接调用这个接口,导致用户在使用小程序的时候产生困扰,归结起来有几点: 开发者在小程序首页直接调用 [代码]wx.getUserInfo[代码] 进行授权,弹框获取用户信息,会使得一部分用户点击“拒绝”按钮。 在开发者没有处理用户拒绝弹框的情况下,用户必须授权头像昵称等信息才能继续使用小程序,会导致某些用户放弃使用该小程序。 用户没有很好的方式重新授权,尽管我们增加了[代码]设置[代码]页面,可以让用户选择重新授权,但很多用户并不知道可以这么操作。 此外,我们发现开发者默认将 [代码]wx.login[代码] 和 [代码]wx.getUserInfo[代码] 绑定使用,这个是由于我们一开始的设计缺陷和实例代码导致的([代码]wx.getUserInfo[代码] 必须通过 [代码]wx.login[代码] 在后台生成 [代码]session_key[代码]后才能调用)。同时,我们收到开发者的反馈,希望用户进入小程序首页便能获取到用户的 [代码]unionId[代码],以便识别到用户是否以前关注了同主体公众号或使用过同主体的App 。 为了解决以上问题,针对获取用户信息我们更新了三个能力: 1.使用组件来获取用户信息 2.若用户满足一定条件,则可以用[代码]wx.login[代码] 获取到的[代码]code[代码]直接换到[代码]unionId[代码] 3.[代码]wx.getUserInfo[代码] 不需要依赖 [代码]wx.login[代码] 就能调用得到数据 获取用户信息组件介绍 [代码][代码]组件变化: [代码]open-type [代码]属性增加 [代码]getUserInfo[代码] :用户点击时候会触发 [代码]bindgetuserinfo[代码] 事件。 新增事件 [代码]bindgetuserinfo[代码] :当 [代码]open-type[代码]为 [代码]getUserInfo[代码] 时,用户点击会触发。可以从事件返回参数的 [代码]detail[代码] 字段中获取到和 [代码]wx.getUserInfo[代码] 返回参数相同的数据。 示例: [代码]<button open-type="getUserInfo" bindgetuserinfo="userInfoHandler"> Click me button>[代码]和 [代码]wx.getUserInfo[代码] 不同之处在于: 1.API [代码]wx.getUserInfo[代码] 只会弹一次框,用户拒绝授权之后,再次调用将不会弹框; 2.组件 [代码][代码][代码][代码]由于是用户主动触发,不受弹框次数限制,只要用户没有授权,都会再次弹框。 通过获取用户信息的组件,就可以解决用户再次授权的问题。 直接获取unionId开发者申请 [代码]userinfo[代码] 授权主要为了获取 [代码]unionid[代码],我们鼓励开发者在不骚扰用户的情况下合理获得[代码]unionid[代码],而仅在必要时才向用户弹窗申请使用昵称头像。为此,凡使用“获取用户信息组件”获取用户昵称头像的小程序,在满足以下全部条件时,将可以静默获得[代码]unionid[代码]: 1.在微信开放平台下存在同主体的App、公众号、小程序。 2.用户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权。 这样可让其他同主体的App、公众号、小程序的开发者快速获得已有用户的数据。 不依赖登录的用户信息获取某些工具类的轻量小程序不需要登录行为,但是也想获取用户信息,那么就可以在 [代码]wx.getUserInfo[代码] 的时候加一个参数[代码]withCredentials: false[代码] 直接获取到用户信息,可以少一次网络请求。 这样可以在不给用户弹窗授权的情况下直接展示用户的信息。 最佳实践 1.调用 [代码]wx.login[代码] 获取 [代码]code[代码],然后从微信后端换取到 [代码]session_key[代码],用于解密 [代码]getUserInfo[代码]返回的敏感数据。 2.使用 [代码]wx.getSetting[代码] 获取用户的授权情况 1) 如果用户已经授权,直接调用 API [代码]wx.getUserInfo[代码] 获取用户最新的信息; 2) 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。 3.获取到用户数据后可以进行展示或者发送给自己的后端。 One More Thing 除了获取用户方案介绍之外,再聊一聊很多初次接触微信小程序的开发者所不容易理解的一些概念: 1.关于OpenId和UnionId [代码]OpenId[代码] 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。 [代码]UnionId[代码] 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过[代码]UnionId[代码],实现多个小程序、公众号、甚至APP 之间的数据互通了。 同一个用户的这两个 ID 对于同一个小程序来说是永久不变的,就算用户删了小程序,下次用户进入小程序,开发者依旧可以通过后台的记录标识出来。 2.关于 getUserInfo 和 login 很多开发者会把 [代码]login[代码] 和 [代码]getUserInfo[代码] 捆绑调用当成登录使用,其实 [代码]login[代码] 已经可以完成登录,[代码]getUserInfo[代码] 只是获取额外的用户信息。 在 [代码]login[代码] 获取到 [代码]code[代码] 后,会发送到开发者后端,开发者后端通过接口去微信后端换取到 [代码]openid[代码] 和[代码]sessionKey[代码](现在会将[代码]unionid[代码] 也一并返回)后,把自定义登录态 [代码]3rd_session[代码]返回给前端,就已经完成登录行为了。而 [代码]login[代码] 行为是静默,不必授权的,用户不会察觉。 [代码]getUserInfo[代码] 只是为了提供更优质的服务而存在,比如展示头像昵称,判断性别,开发者可通过 [代码]unionId[代码] 和其他公众号上已有的用户画像结合来提供历史数据。因此开发者不必在用户刚刚进入小程序的时候就强制要求授权。 可以在官方的文档中看到 [代码]login[代码] 的最佳实践: [图片] Q & A Q1: 为什么 login 的时候不直接返回 openid,而是要用这么复杂的方式来经过后台好几层处理之后才能拿到? A: 为了防止坏人在网络链路上做手脚,所以小程序端请求开发者服务器的的请求都需要二次验证才是可信的。因为我们采取了小程序端只给 [代码]code[代码] ,由服务器端拿着 [代码]code[代码] 和 [代码]AppSecrect[代码] 去微信服务器请求的方式,才会给到开发者对应的[代码]openId[代码] 和用于加解密的[代码]session_key。[代码] Q2: 既然用户的[代码]openId[代码] 是永远不变的,那么开发者可以使用[代码]openId[代码] 作为用户的登录态么? A: 不行,这是非常危险的行为。因为 [代码]openId[代码] 是不变的,如果有坏人拿着别人的 [代码]openId[代码] 来进行请求,那么就会出现冒充的情况。所以我们建议开发者可以自己在后台生成一个拥有有效期的 [代码]第三方session[代码] 来做登录态,用户每隔一段时间都需要进行更新以保障数据的安全性。 Q3: 是不是用户每次打开小程序都需要重新[代码]login[代码]? A: 不必,可以将登录态存入[代码]storage[代码]中,用户再次登录就可以拿[代码]storage[代码] 里的登录态做正常的业务请求,只有当登录态过期了之后才需要重新[代码]login[代码] 。这样子做一则可以减少用户等待时间,二则可以减少网络带宽。 目前微信的[代码]session_key[代码] 有效期是三天,所以建议开发者设置的登录态有效期要小于这个值。
我 微信界的同传扛把子! 识别你的语音? Of course! 我就是你的知己! 翻译你的中文? No problem! 我的英语好着呢! 听懂她的英文? So easy! 你想听我就敢译! What’s more! 我还能读给你听! 如何快速实现语音转文字、文本翻译、语音合成等等能力? 不用着急,让微信同声传译插件来帮你! 今天我们的小故事想和大家分享微信同声传译插件的故事。 1 应用场景 大家好,我叫“微信同声传译”插件,是由微信智聆语音团队、微信翻译团队与公众平台联合推出的同传开放接口,可通过语音转文字、文本翻译、语音合成接口,为开发者赋能。下面介绍我的应用场景: 场景1: 英语口语、听力双管齐下,没有外教也能学好英语! 把我放在小程序里,我既可以将口语转为文字,也可以帮助用户判断发音是否标准纯正,是最佳听众和英语学习道路上的良师益友; 场景2: 出国旅游,英语蹩脚怎么办? 这时我可以应用到翻译小程序里,说中文,译英文,再也不怕出国交流难。 场景3: 看剧时想要吐槽!可是手里有零食,打字不方便怎么办? 只要在小程序里有我,用户就可以按住按钮说话,语音转文字,帮你发送弹幕! [图片] 微信同声传译插件目前开放了以下三个接口——语音识别接口、语音合成接口、文本翻译接口。 通过这个插件,插件使用者可以轻松实现语音读取识别、文本转语音和中英文文本转换,避免这类需求的重复开发工作。 [图片] 2 如何使用插件? 插件接入流程 >>> 开发者在小程序插件中搜索“同声传译”、“语音识别”可以搜索到微信同声传译,添加使用后按使用文档接入使用,同时小程序开发者还可通过 https://github.com/Tencent/Face2FaceTranslator 查看我们开源的插件使用案例。 [图片] 1 登录小程序后台管理,进入“设置”; [图片] 2 点击第三方服务; [图片] 3 点击添加插件; [图片] 4 输入“同声传译”或者“语音识别”、“翻译”等搜索关键字搜索插件并添加,同时可以点击“查看详情”,获取插件的详细介绍、接入说明和开发文档。 [图片] 3 插件简介 1 插件名称 微信同声传译 2 插件AppID wx069ba97219f66d99 3 插件使用指南 微信同声传译插件设计之初,采用和小程序官方流式录音API、网络请求API类似的javascript调用接口,小程序开发者可以快速接入使用此插件。小程序“面对面翻译”使用了这些接口,并且已经开源 [开源地址],便于开发者快速开发。 [图片] (面对面翻译小程序) 具体使用接口可以查看微信同声传译插件使用文档。
微信外部浏览器唤起微信小程序目的:通过发送短信召回流失用户。官方文档地址https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html步骤一该API我们主要用到的配置如下:jump_wxa:跳转到的目标小程序信息。该对象内包含两个字段。path:通过scheme码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带queryquery:通过scheme码进入小程序时的query。步骤二需要注意的是,query必传,然而如果我们需要跳转的页面不需要参数,也需要配置上query,默认我们填写的是from=sms, 用于统计是从浏览器渠道来的。步骤三与后端约定access_token 通过前端传递appKey进行区分,传哪个微信小程序的appKey 就生成对应小程序的access_token。步骤四支持设置scheme的过期时间,默认永久,我们的应用场景很少,暂不详说。步骤五代码里操作如下,由后端聚合参数信息。let postData = { appKey: 'QTSHE_MINI_APP', path: 'pages/partdetails/partdetails', query: 'partJobId=123456' || 'a=1' this.$axios.post('/qtsWeChat/wechat/qrCode/scheme/info', postData).then((res) => { if (res.success) { const url = res.data.openlink // 将scheme转为我们平台的短链接,否则在安卓手机上无法打开微信小程序,会默认打开浏览器搜索。 this.$axios.get(`/misc/shortLink/conversion`, {url}).then((res) => { if (res.success) { this.shortLink = res.data } else { this.$Message.error('获取失败,请稍后重试') }).catch((err) => { console.log(err) 注意点该链接只支持在外部浏览器打开,微信内部浏览器访问是无法打开的,微信内部浏览器打开需要使用微信的开放标签<wx-open-launch-weapp>,下面详说。微信内部浏览器唤起小程序官方文档地址https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html步骤一首先需要登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”,该域名我们配置的是https://m.qtshe.com, 所以导致我们调试的时候需要每次都发布到线上看效果(大坑)。步骤二在需要调用JS接口的页面引入如下JS文件:https://res.wx.qq.com/open/js/jweixin-1.6.0.js,1.6.0版本内才增加了上述标签,低于该版本的都无法显示。步骤三通过config接口注入权限验证配置并申请所需开放标签, 在wx.config里增加openTagList标签,内置两个开放标签 [代码]wx-open-launch-app[代码] 微信h5唤起本地已经安装的app,以及 [代码]wx-open-launch-weapp[代码] 微信h5唤起小程序,操作如下:window.wx.config({ debug: false, appId: window.g_info.wx_appid, timestamp: data.data.timestamp, nonceStr: data.data.nonceStr, signature: data.data.signature, jsApiList: [], openTagList: [ 'wx-open-launch-app', 'wx-open-launch-weapp' 注意点对于Vue等视图框架,为了避免template标签冲突的问题,可使用[代码]<script type="text/wxtag-template"></script>[代码]进行代替,来包裹插槽模版和样式。页面中与布局和定位相关的样式,如[代码]position: fixed; top -100;[代码]等,尽量不要写在插槽模版的节点中,请声明在标签或其父节点上;对于有CSP要求的页面,需要添加白名单[代码]frame-src https://*.qq.com webcompt:[代码],才能在页面中正常使用开放标签。Vue里操作代码如下,如果需要写样式,最好是外部包一个div进行样式编写,内部的内容宽高100%即可,调试样式可使用微信开发者工具的网页模式:<style> img { width: 100%; display: block; </style><img src=" https://qiniu-image.qtshe.com/20220317quick-apply.png" alt="" /> 示例效果[图片] 扩展// 各个端口跳转兼职详情页 jumpToPartJobDetail(partJobId) { util.isAliMiniApp().then((res) => { if (res) { // 如果是在支付宝小程序环境则打开小程序详情页 my.navigateTo({ url: `/pages/partdetails/partdetails?partJobId=${partJobId}` } else { // 如果是在客户端app环境,则打开原生岗位详情页 if (util.isAndroidApp() || util.isIosApp()) { jsBridge.evokeNormalPartJobDetailPage(partJobId) } else if (util.isMiniApp()) { // 微信小程序里则打开微信小程序岗位详情 wx.miniProgram.navigateTo({ url: `/pages/partdetails/partdetails?partJobId=${partJobId}` } else if (util.isMobile()) { // 如果是在手机自带浏览器或者除微信外的app浏览器,统一打开支付宝小程序 let url = `alipays://platformapi/startapp?appId=2018082861168647&page=pages/partdetails/partdetails&query=partJobId%3D${partJobId}` window.location.href = `https://ds.alipay.com/?scheme=${encodeURIComponent(url)}` } else { // pc端打开h5的兼职详情页 window.location.href = `/app/partdetails?partJobId=${partJobId}` 具体示例https://m.qtshe.com/test.html为了方便运营同学,做了个工具给他们使用。 [图片] 4.11号新版本后的改版方案: 首先做一个落地页:https://b.qtshe.com/1DF43E 代码如下: import toast from 'toast' export default { created() { this.init() methods: { // 获取微信scheme地址,并且主动跳转一次 init() { this.$axios.post(`https://xxx/你们的接口地址/user/device/scheme`, this.$route.query).then(res => { if (res.success) { window.location.href = res.data } else { toast(res.msg) }).catch(() => { toast('服务器错误,请稍后重试') 原理:通过地址栏配置需要跳转的参数,h5页面拿到参数后通过接口转换为weixin://xxxx 开头的scheme协议地址,并且主动location.href一次。这样能保证用户每次打开都是新的scheme地址,针对一天50w数量上限的问题,可尝试同一个用户在固定时间内相同的参数,返回相同的scheme地址。 以上限制只存在于除微信外的外部浏览器,微信容器里没有以上的限制。
现在的策略是允许公众号文章内嵌iframe,对于第三方的iframe会检查业务域名。可理解为: 1、打开公众号文章,文章内嵌iframe含有腾讯视频这种情况就是支持的。 2、打开开发者自己的业务域名,网页内嵌iframe含有腾讯视频这种情况就是不支持的。
[图片] 具体情况这样,请大佬帮忙
相信每位小程序服务商的同学,都遇过这些问题: “小程序需要紧急上线,提审后每20分钟看一次消息,却收到了驳回通知。” “提审不通过,我应该给小程序选什么类目?” “本月的提审额度用完了,想增加当月的提审额度,我还有机会吗?” “刚提审的几千个小程序出bug了,开发哥哥不在身边怎么办?” ...... 当遇到这些问题时,找内部技术同学要排期,因为不熟悉平台规则,在反复的信息查询和文字沟通中,花了大量的时间,问题也还没解决。 为了解决服务商的“痛点”,平台把我(小程序服务商助手)推出,服务商可以在小程序内查询很多实用的数据和功能:查看审核信息、加急次数、服务商的新规则新能力、查找小程序的数据和功能、咨询平台客服等等。 虽然我已经上线了半年,用过的服务商都说方便。但还是有不少服务商同学不知道我的存在,所以借着这个机会,让我来好好向大家介绍一下我的各个功能模块吧。 业务实时晴雨表,竞争风险指向标 [首页业务能力看板]使用了六项维度辅助服务商概览业务状况,每项维度都有对应的打分和评价细则,服务商可以全方位评估自己的业务现状,辅助定位发展策略。各项星级指数越高,意味着服务商对应的业务能力越好。 [图片] 精细运营资源,数据驱动增长 [数据页]分别为状态概览,调用次数,授权关系和类目占比。覆盖了服务商分析旗下小程序的最重要的维度,服务商可以通过手机,清晰的查看、分析小程序的表现,从而精细化运营,合理分配技术服务资源。 [图片] 移动管理百宝箱,合作沟通驱动轮 [我的主页]是一个助手小程序的核心管理功能集成页,功能覆盖服务商成员信息管理,小程序审核业务管理,公告通知和咨询反馈渠道,是目前服务商助手小程序使用频率最高的页面。 [图片] 【成员管理】:权限管理&成员更新 [成员管理]主要管理有权限查看和办理业务的成员名单,管理员默认为服务商open账号的管理员,通过微信号添加新的管理员,成为了开放平台账号成员后,即可进入“服务商助手”小程序,使用小程序的业务功能。 [图片] 【审核管理】:审核资源管家&加急撤回功能 [审核管理功能]可以满足服务商运营管理人员对小程序代码审核业务的核心管理需求,包括审核资源查看和管理、小程序一键加急/撤回操作。 1. 审核资源查看和管理: 包括服务商审核资源使用量/余量(本月提审、加急quota分配情况及对应剩余次数),相关审核进度/结果数据展示,审核队列明细和查询功能,历史审核合格率和驳回原因分布等。 注意:审核资源受服务商提审质量和线上表现影响,请服务商们务必做好小程序内容把控,减少提审驳回和线上违规情况。 [图片] 进入审核队列,可以浏览审核中、审核冻结、审核驳回的小程序明细。同时可以按小程序名称、appid、模板id检索服务商小程序的审核状态和结果。 [图片] 2. 加急/撤回功能 小程序一键加急后,该审核单将以最高优先级安排审核,满足服务商特殊场景需要快速被审核的需求;撤回后,可以继续修改小程序后再次提审,同时可以对队列中的某个或者多个分别或者批量进行加急/撤回审核的操作。 [图片] 更多审核专区的知识,可以从页面底部的《审核专区使用手册》去了解。 智能和人工客服系统 [客服咨询]是服务商对平台最直接最便利的沟通反馈渠道。 一般的规则类知识类问题,客服会提供丰富的运营知识库给到服务商去查阅,较为复杂的流程功能或技术类问题,则通过工作日早9点到下午6点在线的人工客服来对接,流转到内部处理。 [图片] 重要消息公告牌 [公告栏]是平台向服务商同步重要信息的首要渠道。 很多政策变更、规则更新、重大活动都将通过小程序内的红点和微信模板消息提醒告知目标服务商。服务商可以第一时间掌握最新的平台信息,不再慢人一步。 [图片] 除了以上已有的功能,平台还在下半年为广大服务商同学准备了更多方便的功能,方便服务商同学清楚自己在平台的表现和权益、便捷管理自己服务的小程序、了解最新的平台运营规则和行业知识等等,我们一起敬请期待吧。 同时,欢迎广大服务商同学前来体验「小程序服务商助手」,了解自己的业务数据和表现。如对“我”有任何问题或者服务商运营的意见,可以通过客服通道和公告专区反馈。平台将根据大家的意见建议,不断完善,让我成为大家最好用的服务商管理小助手。以后,请多多指教。
在使用小程序的时候,偶然会发生闪退。这里来讲一下闪退的问题该如何排查。 版本排查 发生闪退的时候,首先,要确认下 版本 是不是最新的。如果不是,建议更新版本再重试。旧版本的问题会在新版本进行修复哦。 微信版本: 微信官网 基础库版本:基础库更新日志小程序自查 确认版本都是最新情况下,还是有闪退的问题的话,建议先进行小程序自查~ 一般情况下,闪退是因为内存使用过多导致的,小程序侧可以通过基础库提供 wx.onMemoryWarning 接口来监听内存不足的告警,当收到告警时,通过回收一些不必要资源避免进一步加剧内存紧张。 反馈官方 如果问题还是会出现的话建议反馈给官方处理,需要附带上以下信息点协助排查(划重点:完整的提供信息才可以加速问题处理进度哦!!!) 示例: 系统及微信版本号:安卓7.0.17、IOS 7.0.17(出现问题的时候,建议两端都测试,给出有问题的case)必现 or 偶现:必现可复现场景:代码片段 或者 线上小程序复现步骤:进入首页,点击添加按钮等等,推荐录制复现的 视频(重点)进行上传。上传日志:提供微信号,复现时间点(操作步骤:手机微信那里上传下日志: 我 -> 设置 -> 帮助与反馈:右上角扳手 -> 上报日志,选择出现问题的日期,上传日志)
近期平台对第三方服务商代注册小程序的流程进行了优化,用户通过第三方服务商成功创建小程序后,将收到设置小程序登录邮箱和密码的微信消息提醒。管理员可通过点击提醒内的“前往设置邮箱密码”或直接搜索「小程序助手」小程序进行邮箱密码的设置。 温馨提示:所设置的邮箱需要是未绑定任何个人微信、公众号、小程序、微信开放平台的邮箱 详细操作流程如下: 1.打开“小程序助手”小程序,选择对应的小程序 [图片] 2.点击“登录邮箱”设置项 [图片] 3.填写邮箱以及设置小程序密码 [图片] 4.系统会发送验证邮件到邮箱 [图片][图片] 5.打开邮件中的链接,确认验证 [图片] 6.邮箱设置成功,可以在官网:mp.weixin.qq.com用所设置的邮箱和密码登录小程序后台 [图片] 如有疑惑,可微信搜索小程序服务商助手-我的-咨询反馈。
1、问题现象 [图片] 2、当前的反馈帖子 https://developers.weixin.qq.com/community/develop/doc/000eca6d5443c05f57ea3a84854400?highLine=webview%2520%2520%25E7%2599%25BE%25E5%25BA%25A6%25E5%259C%25B0%25E5%259B%25BE https://developers.weixin.qq.com/community/develop/doc/000a4c01754120c0f038259dc56800 https://developers.weixin.qq.com/community/develop/doc/000cc0bfab8258a6d3d70f4c751800 3、当前的结论 官方未回答,因为这是百度地图的功能。开发者也是迷茫为什么,该怎么办。 4、百度的开发文档和官方问答分析 http://lbsyun.baidu.com/index.php?title=wxjsapi http://lbsyun.baidu.com/index.php?title=FAQ/wxjsapi [图片] 5、结论: 小程序内web-view能否用百度地图,取决于你引用百度地图后是否有调用百度服务器。如果只是用来渲染地图类或本地交互类的是可以支持的,但是如果是打开网页直接调了百度地图的服务器,就会通不过微信服务器的校验。 如果需要再小程序内调百度地图的服务,只能用原生地图的开发模式,先注册百度地图-微信小程序类型的ak,并下载js文件后,给小程序配置服务器百度地图的request域名后,再调用。 综上所述:百度地图在小程序web-view模式,是无法调用百度地图服务。如果有此需求可以自己规避下,或者切换技术架构或实现方案。
50个。
一、小程序服务商助手监管专区上线通知平台在“小程序服务商助手”小程序新增了 【监管专区】 ,包括服务商旗下小程序违规数据与违规队列展示,支持服务商快速查询小程序违规与申诉情况。详情请参考: https://developers.weixin.qq.com/community/minihome/doc/0002026140063083364baae0250001 二、公众号、小程序回调出口IP变更通知为了提高公众号、小程序回调出口网络质量,微信callback IP地址计划灰度切换到以下腾讯云网络出口网段,详情请参考: https://developers.weixin.qq.com/community/develop/doc/0006c02b1403a0dc693b4132c51001?blockType=1 三、微信卡券将不再支持新申请开通使用“优惠券”功能因“微信卡券>优惠券”产品能力未来将统一升级为“微信支付优惠券”,12月10日0点起,“微信卡券>优惠券”功能将不再支持新商户开通,该功能后续将陆续下线。其他微信卡券功能暂无变化。详情请参考: https://developers.weixin.qq.com/community/develop/doc/000a8aeccf45284fc83bc22d251c01?blockType=1 四、服务平台开放入驻及原有服务升级通知为了更好地为用户提供服务,服务平台在近期进行了改版升级。第三方服务商可在服务平台上传对应行业的小程序开发服务,通过审核后即可进行服务展示。详情请参考: https://developers.weixin.qq.com/community/develop/doc/0004a24516cfd01ec02baefae51401 五、小程序类目资质更新[图片] 备注:绿色字体为小程序类目的更新调整,详情请参考小程序开放的服务类目 第三方快速创建的小程序可选择的类目参考第三方平台-快速创建小程序接口-类目参考表 https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/Fast_Registration_Interface_document.html
概述欢迎申请成为微信小程序开发服务商:微信小程序开发服务是微信开放平台第三方服务商的一项服务能力。 成为微信小程序开发服务商你可以创建小程序代开发服务并发布到服务平台。 商家可在服务平台小程序开发专区选购你的服务,并通过授权的方式获得你所提供的服务。 为创建SaaS小程序代开发服务你需完成以下步骤:1. 创建第三方平台 2. 登录服务平台 3. 创建小程序开发服务 内容发布并通过审核后,即可通过小程序开发服务专区向用户提供服务。 审核内容将包括:服务商在所选行业的服务能力、服务标题、服务简介、服务头图、服务效果图,服务功能和套餐的完整性,服务价格的合理性。 入驻基本标准(1)已在微信开放平台通过开发者资质认证,成为微信第三方服务商; (2)具备微信小程序开发能力和服务经验 (入驻服务所选行业下,活跃小程序案例不少于5个); (3)服务商半年内无违规记录; (4)服务商可在对应行业提供完整的小程序行业解决方案 (对应行业所提交的案例展示小程序需具备平台规定的核心场景之一)。 *详细服务上传审核标准 服务创建规则1. 单个服务对应单个行业,如服务商服务多个行业,则需分别上传多个服务; 2. 单个行业对应上传的服务内容需关联该行业解决方案,不得出现无关/其他行业的内容。 接入流程:一、创建第三方平台(如有可跳过该步骤)第三方平台是服务商创建服务并获取授权的基础体系。第三方平台的申请和上线流程参照第三方平台的申请和上线流程。 注:注册第三方流程根据开发模式选择平台型服务商或定制化服务商 平台型服务商注册说明 定制化服务商说明文档 *服务商需通过第三方平台账号与小程序建立授权关系,并满足入驻标准方可上架服务 二、登录服务平台1. 使用第三方平台管理员微信登陆服务平台(该微信号需同时为与第三方企业主体一致的小程序管理员) [图片] 2. 若无企业主页,点击“创建新的企业主页” 若有企业主页,则请查看步骤(三) [图片] 3. 若无可创建的企业主页,进入微信公众平台“创建新的企业小程序”(企业小程序创建流程),创建成功后,再回到服务平台继续创建企业主页。 [图片] 4. 创建企业主页:选择以某个公司为主体的小程序(每个主体只能创建一个企业主页) [图片] [图片] 5. 选择企业主页的类型 [图片] 6. 确认企业主页的主体 [图片] 7. 完善企业资料并提交 [图片] 三、创建小程序开发服务1. 点击右上角头像,进入“我的主页” [图片] 2. 创建服务,选择“小程序开发” [图片] 3. 填写信息并提交 [图片] 4. 提交审核 在“我的主页”点击“提交审核”,审核通过后服务将展示在服务平台 审核需时1-3个工作日,服务商可在「我的主页」服务列表状态栏查看当前审核状态 [图片] 5. 服务上架
因为有其他第三方平台提交了检测,测试资源被占用了,过段时间再尝试提交即可。
本月可提审次数 显示服务商本月剩余可提审次数和分配的总quota值,每月1号更新 本月可加急次数 显示服务商本月剩余可加急次数和分配的总加急次数,每月1号更新 以上审核资源受服务商线上表现(小程序内容安全、活跃度、平台配合度)影响,各服务商务必做好小程序商户提供内容素材的管理,减少提审驳回情况和线上违规情况 更新时间为1号早上6点,0-6点显示的是上一月quota,6点后更新为本月 审核数据 服务商可以查看近7天、本月、上月的每日审核量、合格率和期间平均合格率、驳回原因分布 平台合格率为所选择时间周期内的合格率,合格率为重要运营指标,对服务商的资源分配有较强影响,请注意提升 一期合格率存在一定的误差,仅供参考,后续会不断完善,提升这里的数据准确度 审核队列 可以对当前审核队列中的各个审核单进行管理 审核中:提审后等待审核的单,审核时效一般为7天内,表现良好的服务商有更快审核速度 审核冻结:大批量提审未通过预检,被延后审核,建议服务商基于预检不通过原因撤回整改后再次提审,审核时效>15-30个工作日,冻结不及时撤回将被平台追加处罚 审核驳回:近7天被驳回的审核单 注意,审核队列只保留最近一次审核状态的单,如近7天被驳回的单再次提审,将从审核驳回队列中消失,进入审核中队列或审核冻结队列 审核队列支持按小程序名称,小程序appid,模板id进行搜索 搜索时,将在全部审核队列中搜索。小程序名搜索的优先级高于模板id,如”幸运58”的小程序和模板id为5828的100个小程序同时存在时,搜索58将只显示“幸运58”,搜索582才会显示模板5828 appid只支持精确匹配,名称和模板id支持模糊匹配 搜索模板id时,可以按全部单和只看审核冻结单进行筛选,方便进行批量操作 目前一页可展示100个审核单,如需查看更多,请点击页面底部的“加载更多审核单”,加载第二页,以此类推 小程序加急 服务商对于审核中和审核冻结的小程序可以进行加急操作,加急后,该审核单将优先安排审核,原本被冻结的状态也会解除 加急额度属于平台稀有资源,请谨慎使用,严禁任何形式的转卖转赠,一经发现将对quota和加急次数进行一年的处罚 单个加急:可以在小程序的明细页点击加急按钮进行 多个加急:可以点击审核队列的加急按钮,选择多个小程序进行加急 不可加急的类型:需要被报备网信办等机构的审核,无法加急,提示加急失败,加急额度返还 小程序撤回 当发现bug,或者预检不通过导致大批小程序被冻结时,服务商可以操作撤回这批小程序,整改后再次提审。 对大批量问题提审单撤回有助于提升服务商合格率,降低对平台资源消耗。预检不通过的冻结单未及时撤回将会被处罚 单个撤回:可以在小程序的明细页点击撤回按钮进行 批量撤回:可以点击审核队列的撤销审核按钮,选择多个小程序进行撤回,批量操作时,可以基于模板去筛选,可以按照只看冻结去筛选 推荐操作流程:当服务商需要对某个模板id,如5234模板id中全部提审单或全部冻结审核单进行撤回时,建议点击撤销审核按钮,进入选择审核单页面,搜索模板5234,在进入的模板页,点击筛选选项(全部/只看审核冻结),然后点击全选,可以看到本次要撤回的小程序个数,最后点击确定 批量撤回在单数较多时,将生成一个后台异步任务执行,执行结束后将批量撤回结果反馈回来,大批量的撤回可能要等待较久时间 不可撤回的类型: 1,单个小程序每月撤回次数上限为10次,超过时不可撤回 2,已经被其他渠道(如API接口)撤回的单,不可重复撤回 3,其他 客服专区 本次审核专区将为部分服务商开放客服专区入口,客服专区包括机器人客服为服务商提供常用知识库,以及人工客服解决服务商具体问题 开放范围:基于服务商每月quota排名进行分配,一期开放100个,后续将不断放开,直至覆盖全部服务商 客服工作时间:工作日:9:00-12:00,14:00-18:00,特殊时期以平台公告为准 客服问题范围:小程序运营规则、服务商运营政策、提审/审核/线上处罚常见问题咨询、服务商助手体验bug问题等 使用过程中任何问题都可以通过客服入口留言,如无客服入口可以通过开放社区进行反馈,我们将以帮助服务商提高获取信息和办理业务的效率为核心目标,不断完善服务商助手小程序。 最后更新时间:2020年3月5号
第三方服务商按月按提审表现分配提审限额(quota)的机制上线以来,服务商对提审质量的重视度不断上升,大盘提审合格率也不断提升。为了保障服务商提审效率,携手与服务商共同发展,平台近期将完善quota机制,更科学合理地分配服务商提审额度。 一、提审限额分配机制优化 服务商表现排名,会综合服务商的以下表现进行评价。同时平台考察服务商表现维度更全面(11月在服务商助手小程序的“表现与权益”专区上线) 月quota分配优化前:月quota基于近3月提审单数、服务商表现排名分配月quota分配优化后:月quota基于近6月提审单数、服务商表现排名分配新服务商支持:新第三方服务商每月最少可获得50个quota。*新第三方服务商定义:自上线之日起一年内称之为新第三方服务商 安全:提审质量、线上违规情况、低风险小程序数量、高风险小程序数量等 规模:授权小程序个数、主体个数、月新增小程序个数等 活跃:高活跃小程序个数及占比、不活跃小程序个数及占比 支付:支付金额、笔数、支付活跃小程序个数及占比 体验:体验差小程序个数及占比 [图片] 二、quota自助调额范围优化: 优化前:服务商每次申请临时额度为月初分配提审额度的15%优化后:月初分配的quota作为调额范围的划分标准,具体调整范围如下图:[图片] 特别注意: 服务商在可选范围内按需求自助调额,自助调整次数以及调额范围将基于提审合格率、quota消耗率等服务商表现分配,分配规则如下: 自然年内若存在超过三个月(不含)提审合格率低于80%,将无法申请临时调额quota消耗率作为第三方服务商画像分值的一个重要参考项,请按需进行quota调额申请,若quota消耗率存在超过三个月(不含)低于60%,服务商的画像分值排序将会降低,影响后续审核的优先级以及quota等权益分配。quota消耗率指当月quota使用值/(当月quota分配值+临时申请quota总值)*100% [图片] 平台将持续为服务商提供高效快捷审核服务,若因业务发展等情况存在超额提审需求的服务商还可以通过“小程序服务商助手-我的-咨询反馈”联系人工客服进行申请更多临时额度,客服工作时间:工作日:9:00-12:00,14:00-18:00,特殊时期以平台公告为准。 临时额度人工申请周期为1-3天,请服务商提前规划好提审额度的需求。 微信开放平台运营团队 2020.11.4
Vue响应式 数据响应式系统是Vue最显著的一个特性,我们通过Vue官方文档回顾一下。 数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。 现在是时候深入一下了! 本文针对响应式系统的原理进行一个详细介绍。 响应式是什么 我们先来看一个例子 [代码]<div id="app"> <p>{{color}}</p> <button @click="changeColor">change color!</button> new Vue({ el: '#app', data() { color: 'blue' methods: { changeColor() { this.color = 'yellow'; 当我们点击按钮的时候,视图的p标签文本就会从 [代码]blue[代码]改变成[代码]yellow[代码]。 Vue要完成这次更新,其实需要做两件事情: 监听数据[代码]color[代码]的变化。 当数据[代码]color[代码]更新变化时,自动通知依赖该数据的视图。 换成专业那么一点点点的名词就是利用[代码]数据劫持/数据代理[代码]去进行[代码]依赖收集[代码]、[代码]发布订阅模式[代码]。 我们只需要记住一句话:在getter中收集依赖,在setter中触发依赖 如何追踪侦测数据的变化 首先有个问题,如何侦测一个对象的变化? 目前来说,侦测对象变化有两种方法。大家都知道的! Object.defineProperty vue 2.x就是使用[代码]Object.defineProperty[代码]来数据响应式系统的。但是用此方法来来侦测变化会有很多缺陷。例如: Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应; Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。 本文也是利用Object.defineProperty来介绍响应式系统。 Proxy vue3就是通过proxy实现响应式系统的。而且在国庆期间已经发布pre-alpha版本。 相比旧的[代码]Object.defineProperty[代码], [代码]proxy[代码]可以代理整个对象,并且提供了多个[代码]traps[代码],可以实现诸多功能。此外,Proxy支持代理数组的变化等等 当然proxy也有一个致命的缺点,就是无法通过[代码]polyfill[代码]模拟,兼容性较差。 依赖收集的重要角色 Dep Watcher [代码]Dep[代码]、[代码]Watcher[代码]是数据响应式中两个比较重要的角色。 收集依赖的地方 Dep 因为在视图模板上可能有多处地方都引用同一个数据,所以要有一个地方去存放数据的依赖,这个地方就是Dep。 Dep主要维护一个依赖的数组,当我们利用render函数生成VNode的时候,会触发数据的getter,然后则会把依赖push到Dep的依赖数组中。 依赖是Watcher! 我们可以把[代码]Watcher[代码]理解成一个中介的角色,数据发生变化时,会触发数据的setter,然后通过遍历Dep中的依赖Watcher,然后Watcher通知额外的操作,额外的操作可能是更新视图、更新computed、更新watch等等… 还是那句话:在getter中收集依赖,在setter中触发依赖。 下面我们看看代码: 代码有点长,下面会有步骤来讲解一次。 [代码]/* * 劫持数据的getter、setter function defineReactive(data, key, val) { // 1-1:把color数据变成响应式 const dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { // 3. 因为模板编译watcher访问到了color,从而触发get方法,触发了收集依赖的方法 dep.depend(); return val; set(newVal) { if (val === newVal) { return; val = newVal; // 4-1. 假设我们通过 `this.color = 'yellow';`去更改`color`的值,就会触发set方法。 dep.notify(); * dep类,收集依赖,和触发依赖 class Dep { constructor() { this.subs = []; // 收集依赖的数组 // 收集依赖 depend() { // 3-1. 通过外部的变量来添加到color的依赖中. if (window.target && !this.subs.includes(window.target)) { this.subs.push(window.target); // 通知依赖更新 notify() { // 4-2. 遍历 this.subs.forEach(watcher => { watcher.update(); * 数据与外部的中介 class Watcher { constructor(expr, cb) { this.cb = cb; this.expr = expr; // 2-1. 这里触发了get方法 this.value = this.get(); get() { // 2-2. 这里把自己(watcher)赋值给了外部其中的一个变量 window.target = this; // 2-3. data[this.expr]触发了color的get const value = data[this.expr]; window.target = undefined; return value; update() { this.value = this.get(); this.cb(this.value); 下面我们来走一次流程。 括号里面的1-1,2-2是对应代码的执行点。 1. 把数据变成响应式 利用[代码]defineReactive[代码]把color数据变成响应式(1-1),执行过这个方法后,我们调用[代码]console.log(this.color)[代码]的时候可以触发get方法。同理当我们[代码]this.color = 'yellow'[代码]。 注意:在[代码]Object.defineProperty[代码]上面初始化一个存放依赖的dep,这里其实是把dep作为数据[代码]color[代码]的一个私有变量,让get和set的方法可以访问到,也是我们经常说的闭包。 2. 编译模板创建watcher 假设我们现在编译模板遇到[代码]{{color}}[代码]。 Vue就会创建一个Watchter,伪代码如下: [代码]new Watcher('color', () => { // 当color发生变化的时候,会触发这里的方法。 这里高能!! Watcher的构造函数里面调用了[代码]get()[代码]方法(2-2),把自己(watcher)赋值给了一个外部变量。 然后再触发get方法(2-3)。 3. get中收集依赖 因为模板编译watcher访问到了color,从而触发get方法,触发了收集依赖的方法。 进入到[代码]dep.depend[代码]方法中(3-1),这里因为在Watcher中把自己存到了外部变量中,所以在dep.depend方法中可以收集到依赖。 现在,依赖就被收集了。 4. 通过setter触发依赖 假设我们通过 [代码]this.color = 'yellow';[代码]去更改[代码]color[代码]的值,就会触发set方法,执行dep.notify(4-1)。 会遍历依赖数组,从而去触发Watcher的cb方法。 cb就是上面伪代码[代码]new Watcher[代码]的那个回调函数。 只要回调函数里面运行了操作dom方法,或者触发了diff算法更新dom,都可以把视图进行更新。 响应式简易流程大概就是这样了… 侦测数据变化的类型 其实数据监听变化有两种类型,一种是“推”(push),另一种是“拉”(pull)。 React和Angular中的变化侦测都属于“拉”,就是说在数据发生变化的时候,它不知道哪个数据变了,然后会发送一个信号给框架,框架收到信号后,会进行一个暴力比对来找出那些dom节点需要重新渲染。Angular中使用的是脏检查,在React使用虚拟dom的diff。 vue的数据监听属于“推”。当数据发生变化时,就知道哪个数据发生变化了,从而去更新有此数据依赖的视图。 因此,框架知道的数据更新信息越多,也就可以进行更细粒度的更新。比如,直接通过dom api操作dom。 相对“拉”的力度是最粗的。看到这里,是不是觉得vue的更新效率最快。 我们看看下面的例子 [代码]<template> <div>{{a}}</div> <div>{{b}}</div> </template> 这里我们看出模板只有a、b两个数据依赖,也就是说我们要创建两个闭包dep去存放两个watcher依赖,我们知道闭包的缺点就是内存泄露。如果有1000个数据依赖在模板上,每个数据所绑定的依赖就越多,依赖追踪在内存上的开销就会越大。 所以,从Vue.js2.0开始,它引入了虚拟dom,一个状态所绑定的依赖不再是具体的dom节点,而是一个组件,即一个组件一个Watcher。 这样状态变化后,会通知到组件,组件内部再使用虚拟 dom进行比对。这可以大大降低依赖数量,从而降低依赖追踪所消耗的内存。但并不是引入虚拟dom后,渲染速度变快了。准确的来说,应该是80%的场景下变得更快了,而剩下的20%反而变慢了。 个人觉得,鱼和熊掌不可兼得。 “推”是牺牲内存来换更新速度。 “拉”则是牺牲更新速度来获取内存。 Vue响应式的灵魂:在getter中收集依赖,在setter中触发依赖。 我们再看看图,回顾一下整个流程。 通过[代码]defineReactive[代码],遍历data里面的属性,把数据的getter/setter劫持,用来收集和触发依赖。 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到Dep依赖中。 当数据发生变化时,会触发setter,从而Dep会向依赖(Wachter)发送通知。 Watcher收到通知后,会向外界发送通知,变化通知到外界后可能接触视图更新,也有可能触发用户的某个回调函数等等。 参考文献:《深入浅出Vue.js》
前言:看完了比赛项目,感觉像是经历了一场头脑风暴,项目的起名、涉足行业、内容、UI、架构,以及业务设计等,感到都有很多学习的地方。打算做一个学习笔记贴。 *项目有点多,我自己做了一下分类,以便查找。 记账与备忘: 《大学生记账本》 《咸鱼记账》 《KrisQin记账本》 《MY备忘》 《家庭多用记事本》 《ygjtools》 《乐考吧》 《日程管家》 《YAccount记账助手》 《随变记账》 《待办事项工具》 《小婷和小天一起记账》 《初心日历》 《寝记账》 《智慧账本》 失物招领: 《爱心收发室》 《帮寻小站》 《长大寻物》 《悦寻失物招领》 《校园寻回》 《失全拾美》 《健身助手力量日记》 《活力健身房》 《RedPoint红点》 《健身小程序简介》 音乐影视: 《云享Music》 《king电影》 《無音不泉》 《电影周周荐》 AI识别: 《鹦鹉AI端侧识别》 《ai视觉测试》 《图文识别》 《AI物以类聚》 《AI写诗》 医疗健康: 《人体生理指标》 《吃药小助》 《体重MM》 《菲特日记》 《医医查》 《全国核酸检测资质医院查询》 《糖友饮食助手》 《每日戒糖》 《自助心理成长》 《预约挂号小程序》 《蓝医先生》 社团活动: 《阮薇薇点名啦》 《山大clubs》 《素拓百分百》 《小小微距》 《娱乐投票小程序》 《活动栏》 《薇科技弹幕墙》 《头马报名》 《科联答题》 《招新Plus》 《文艺比赛小行家》 《BJUT活动助手》 《准到聚餐》 学习工具: 《错题小本本》 《为高考加分》 《分录英雄》 《口算助手》 《答案sou》 《教资易取》 《快刷题库answer question》 《拾一英语》 《魅力单词》 《魔方训练计时器》 《focusair》 《Y计算器》 《高级工匠心录》 《成长课程表》 《“倾听者”综合型语音评价系统》 《小青考证》 《IAI CDS》 教育培训: 《来这儿学》 《微学堂(在线学习平台)》 《大学生资源共享平台》 《袋鼠培培》 《宝贝积分管理》 时间管理: 《西瓜清单》 《语音倒计时器》 《西红柿时间管理》 《Do More打卡小程序》 《倒计时》 《tomato clock》 《step by step》 《BT清单》 《tusake Today》 《叮咚倒数日》 《FTodoList》 校园管理: 《班级价值分》 《校园缺勤录》 《教务小助手》 《工程课表》 《云迎新》 《CAN课程表》 《简单的课表小程序》 《运动会管理系统》 《重邮课后小程序》 《高校信息共享平台》 《校园简单易》 《中北请假助手》 《ITD智慧校园》 《We广油》 《江大电服》 校园介绍: 《阿里嘻嘻》 《民大新生助手》 《北邮宣讲通》 《志愿校园》 《浙里淘志愿》 《云校知》 校园社区: 《北院守夜人》 《校园墙》 《AIB校友会》 《校园小唤》 《xcu许院生活》 物流快递: 《高校联盟-快递代取》 《速派递》 《物流小程序》 预约与邀请: 《天翊图书馆预约》 《农大饭食》 《课室助手》 《会议邀请函》 《weSport》 《哪天约》 《易约行》 《自闭间预定》 《简约约拍》 《实验室设备预约助手》 《QSCamera》 《预约班车》 《心暖农侬》 《书香长大》 《私约团课》 情侣婚礼: 《趣婚礼》 《小酒馆》 《恋人小清单》 《云表白》 《情侣券》 《旅梦恋爱》 《快表白》 《恋爱空间》 购物商城: 《微信云商城》 《吃否CHIFOU》 《云端商城小程序》 《柠檬商城》 《武冈微商城》 《狗头的店,狗头管理》 《林林的妙妙屋》 《芳甸鲜花商城》 《优鲜配送联盟》 《汇尤e家》 《云开发带后台商城系统》 《预付费机票销售小程序》 《微购收单》 知识普及: 《科普小程序》 《百词百科》 《BOSS百科》 《球员搜搜》 《火查查》 《急速查病》 《趣答星球》 《铁路生涯》 《吃吃等你》 《男人买菜》 《诗华社》 《天天诗词》 《心跑道》 《sentry 小程序客户端》 《GitPark》 《码农SHOW营》 《OTP动态验证码》 《微源库》 《LE编程》 《一起来学计组叭》 《统一运维平台》 《见字如面》 图像处理: 《莉龙美颜工具》 《图像复原微信小程序》 《Hi头像》 《我是主角》 《修补匠》 《抽屉表情》 《人脸识别虚拟仿真实验》 《照片时光机》 语言翻译: 《CEnews》 《多源在线翻译》 《汉泰小词典》 《识译小程序》 社区周边: 《社区速修》 《虚拟社区》 《简物业》 《租户在线》 《美今管家》 《雨中送伞》 《盲小鹿》 树洞与留言: 《海豚时光瓶》 《苦海匿舟》 《深大小树洞》 《LMSH7TH》 简历与工作: 《个人简历Plus》 《快速找工作》 《猿宝典》 《云线名片》 《InterviewHub》 《企业招聘》 《JF校园云招聘平台》 《普罗名特》 《校园招聘》 资讯与娱乐: 《Killkinfe》 《开心小杜》 《旅小布短视频》 《心灵鸡汤大全》 《轨道nighty night》 《拯救不开心》 《大宗交易数据查询分析助手》 《我的旅行箱》 《云航助手》 《宝塔出行》 《PicGo图旅》 城市宣传与服务: 《数字余杭》 《哏儿通》 《联系群众客户端》 《城市预警系统》 《郑州限行查询》 《佤山行》 商业工具: 《软著助手》 《义思丽代办平台》 《契约farm》 办事工具: 《省计数字监理》 《OA外勤管家》 《报工小助手》 《make的测评程序》 《实验室管理小程序》 《星河意见箱》 《梦凡云OA》 《微助helper》 《群消息公示》 《安全帽智慧监控小程序》 地图打卡: 《高级打卡鸡》 《摄影地图游客版》 《同学在哪儿》 《每日步数打卡》 《生活智打卡》 《打卡日历》 《Mayday Online》 《嘿!我在这儿!》 《心里有树》 《地图留言》 天气与日历: 《一眼天气》 《历史日历》 《7日天气》 《历史上的今天TIH》 《实用小工具》 游戏娱乐: 《趣味游乐城》 《假如生命很短暂》 《磁力积木3D预览》 《MusicColorBlock-Detail》 《消灭癌细胞》 《红小包抽奖》 《大师请提笔》 《画画的北鼻》 《东方小游戏》 存储与分享: 《酷传CoolTran》 《次元乌托邦云网盘》 《悦分享》 《云享坊》 生活工具: 《WiFi生成码》 《古老的API小工具》 《日常工具box》 《柠檬收纳》 《买它or not》 《我车呢》 《微信小程序工具箱》 《小记易》 《电魔方智能家居》 《缸中之鱼导购系统》 《格式转换工厂》 《小神助手》 《我家的WIFI》 二手交易与租赁: 《大学校园闲置物品交易平台》 《宝宝约玩》 《精简之校园二手交易平台》 《学辰ing》 《二手市场》 《校园二手购》 《零工哥》 《瓜大e拼车》 外卖点单收银: 《来一杯a》 《美食屋》 《外卖系统》 《便利下单助手》 《云智慧收银》 《超市Boss助手(零售助手)》 《为特餐饮助手》 《Holly食刻》 《微信自助点餐小程序》 《餐饮流水记账》 《seven取餐小程序》 《校内外卖》 《秀食餐饮小程序》 《校云通》 日记博客论坛: 《博客系统》 《天天读书》 《myVlog》 《论坛小程序》 《一瞬相册》 《一只书匣》 《社交平台》 《图迹圈》 《MallBook》 《青存纪》 《酒肆 家谱》 《比斯兔u》 《校园书友》 《CC交个朋友》 《点滴互助》 《广大搜搜》 《社交点评》 《迷你论坛》 《Simple Note 短记》 垃圾分类: 《垃圾问问》 《垃圾分类小程序》 《垃圾分类赢好礼》 《萌宠创造营》 《宠幸治疗》 《宠物营地》 《流浪猫速查手册》 《lononiot》 《LoRa智能家居管理》 《温湿度实时监控及开关控制小demo的设计》 《HomeAssistant》 《流量计设备性能测试平台》 疫情防控: 《行程助手Plus》 《每天都要上报体温》 《校园疫情管理小程序》 《CUMTB疫情管控期间学生外出申请系统》 《疫简签》 《地摊生活》 《逛逛地摊》 《迷你小摊》 二、学习笔记 (一)名词解释 1,分录: 会计分录亦称“记账公式”,简称“分录”。它根据复式记账原理的要求,对每笔经济业务列出相对应的双方账户及其金额的一种记录。 2,文玩: 指的是文房四宝及其衍生出来的各种文房器玩。这些文具造型各异,雕琢精细,可用可赏,使之成为书房里、书案上陈设的工艺美术品。 3,sentry: Sentry是一个开源的实时错误追踪系统,可以帮助开发者实时监控并修复异常问题。 4,GitHub: 是一个面向开源及私有软件项目的托管平台,因为只支持Git作为唯一的版本库格式进行托管,故名GitHub。 5,软著: 全称是计算机软件著作权,是指软件的开发者或者其他权利人依据有关著作权法律的规定,对于软件作品所享有的各项专有权利。 6,打卡: 网络流行词,原指上下班时刷卡记录考勤。现衍生指到了某个地方或拥有某个事物(一般会向他人展示)。网红、圣地打卡。 7,番茄工作法: 是简单易行的时间管理方法。使用番茄工作法,选择一个待完成的任务,将番茄时间设为25分钟,专注工作,中途不允许做任何与该任务无关的事,直到番茄时钟响起,然后进行短暂休息一下(5分钟就行),然后再开始下一个番茄。每4个番茄时段多休息一会儿。 8,码农: 可以指在程序设计某个专业领域中的专业人士,或是从事软体撰写,程序开发、维护的专业人员。但一般Coder特指进行编写代码的编码员。 9,树洞: 来源于童话故事《皇帝长了驴耳朵》,意思是一个可以袒露心声的地方,是指可以将秘密告诉它而绝对不会担心会泄露出去的地方。 10,AI: 全程人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 11,OA: 办公自动化(Office Automation,简称OA)是将现代化办公和计算机技术结合起来的一种新型的办公方式。办公自动化没有统一的定义,凡是在传统的办公室中采用各种新技术、新机器、新设备从事办公业务,都属于办公自动化的领域。 12,素拓: “素质拓展训练”的简称。素质拓展起源于国外风行了几十年的户外体验式训练,通过设计独特的富有思想性、挑战性和趣味性的户外活动,培训人们积极进取的人生态度和团队合作精神,是一种现代人和现代组织全新的学习方法和训练方式。 13,磁力积木: 由若干个不同形状的积木单体组成。在各个单体的边沿嵌有磁铁或磁片,磁铁上履盖有一层搪瓷,利用磁力使各单体紧密连接在一起。 14,教资: 指教师资格证考试,是由教育部考试中心官方设定的教师资格考试。 15,Instagram: 又叫照片墙,是一款运行在移动端上的社交应用,以一种快速、美妙和有趣的方式将你随时抓拍下的图片彼此分享。 16,Redpoint: 攀岩术语,是指事前曾练习爬过该路线,以先锋攀登的方式、无坠落地完攀该路线。 17,头马: 是Toastmasters的中文简称,于1924年在美国加州成立。是一个非盈利性的、由会员自行管理的组织,目前已在全球一百多个国家成立了上万个俱乐部。 *上述名词介绍来自百度百科与知乎。 (二)出现的学校 河北科技大学(河北-石家庄) 电子科技大学(四川-成都) 重庆邮电大学(重庆) 哈尔滨理工大学(黑龙江-哈尔滨) 桂林航天工业学院理学院(广西-桂林) 河南理工大学(河南-焦作) 河北北方学院(河北-张家口) 北方民族大学(宁夏-银川) 山西大学(山西-太原) 西安交通大学(陕西-西安) 西安电子科技大学(陕西-西安) 华中科技大学(湖北-武汉) 深圳大学(广东-深圳) 浙江大学(浙江-杭州) 湖南大学(湖南-长沙) 武汉大学(湖北-武汉) 广东技术师范大学(广东-广州) 西北民族大学(甘肃-兰州) 北京邮电大学(北京) 中国民航大学(天津) 广东机电职业技术学院(广东-广州) 长江大学(湖北-荆州) 华南理工大学(广东-广州) 包头铁道职业技术学院(内蒙古-包头) 重庆工程职业技术学院(重庆) 江苏大学(江苏-镇江) 南京邮电大学(江苏-南京) 华南理工大学广州学院(广东-广州) 长安大学(陕西-西安) 泉州师范学院(福建-泉州) 桂林电子科技大学(广西-桂林) 广西医科大学(广西-南宁) 华南农业大学(广东-广州) 山西农业大学(山西-晋中、太原) 南京大学金陵学院(江苏-南京) 广东石油化工学院(广东-茂名) 兰州交通大学(甘肃-兰州) 东华理工大学(江西-南昌、抚州) 中山大学南方学院(广东-广州) 广州大学(广东-广州) 中国矿业大学(北京) 南京工业大学(江苏-南京) 中北大学(山西-太原) 华中农业大学(湖北-武汉) 东莞理工学院(广东-东莞) 广东工业大学(广东-广州) 上海电机学院(上海) 南宁职业技术学院(广西-南宁) 台州职业技术学院(浙江-台州) 福州大学(福建-福州) 厦门理工学院(福建-厦门) 美国纽约大学(美国) 英国曼彻斯特大学(英国) 昆明理工大学(云南-昆明) 天津城建大学(天津) 北京工业大学(北京) 广东建设职业技术学院(广东-广州) 湖北师范大学(湖北-黄石) 许昌学院(河南-许昌) 西北工业大学(陕西-西安) (三)个人认为的特别题材 动物保护-鹦鹉AI端侧识别 健康管理-吃药小助 学习工具-口算助手 商业工具-软著助手 图像处理-摄影地图游客版 AI换脸-我是主角 个性服务-雨中送伞 情侣生活-恋人小清单 心情宣泄-苦海匿舟 走失找回-月见 AR躲猫猫-萌宠创造 文艺共鸣-轨道nighty night 心里健康-心暖农侬 模拟红包-红小包抽奖 攀岩健身-RedPoint 职校沟通-铁路生涯 酿酒乐趣-趣酿 盲人助力-盲小鹿 历史日历-历史上的今天 消防检查-火查查 情侣福音-情侣券 家庭互助-顾家 宠物关注-流浪猫速查手册 停车助手-我车呢 诗歌创作-AI写诗 智能家居-LoRa智能家居管理 智能招领-悦寻失物招领 你画我猜-画画的北鼻 随手反馈-城市预警系统 仿真识别-人脸识别虚拟仿真实验 智慧校园-ITD智慧校园 活动协调-哪天约 家庭物联-HomeAssistant 地热监测-流量计设备性能测试平台 (四)官方公布的复赛名单 校园赛道: 职业赛道: (五)官方公布的决赛名单 校园赛道: 职业赛道: (六)最终决赛成绩 校园赛道: 职业赛道:
功能描述 快速创建小程序功能优化了小程序注册认证的流程,采用法人人脸识别方式替代小额打款等认证流程,极大的减轻了小程序主体、类目资质信息收集的人力成本。第三方只需收集法人姓名、法人微信、企业名称、企业代码信息四个信息,便可以向企业法人下发一条模板消息来采集法人人脸信息,完成全部注册、认证流程。快速创建小程序接口能帮助第三方迅速拓展线下商户,拓展商户的服务范围,占领小程序线下商业先机。 通过该接口创建小程序默认“已认证”。为降低接入小程序的成本门槛,通过该接口创建的小程序无需交300元认证费。 注:该功能只能创建线下类目小程序,创建线上类目小程序将被驳回,且影响第三方调用该接口的quota。 小程序类目参考: 使用教程注册流程第三方平台1. 第三方平台需具有以下权限集(更新权限集后,需通过审核并全网发布后才可生效) 2. 全网发布必须审核通过[图片] 3. 微擎或独立版平台配置微信开放平台信息 提交申请1. 收集法人微信、法人姓名、企业名称、信用代码四个商户信息外加第三方客服电话,方便商家与第三方联系(建议填写第三方客服电话);企业名称需与工商部门登记信息一致;法人姓名与绑定微信银行卡的姓名一致。信息收集时要确保四个信息的对应关系,否则接口无法成功调用。 2. 登录应用系统,填写对应信息(【小程序】->【快速注册】[图片] 3. 注册记录显示已经提交的注册信息,查询进度功能由于微信官方接口限制,每条记录半个小时以内只能查询一次。 法人认证 通过法人&企业主体校验,微信平台向法人微信下发模板消息。法人需在24小时内点击消息,进行身份证信息与人脸识别信息收集;[图片] 注意事项1. 注意保证主体信息与工商部门登记一致。如:广州和广州市;国家企业信用信息公示系统:http://www.gsxt.gov.cn/index.html 2. 微信号填写错误,需正确引导获取位置“微信”-“我”(不能使用手机号、QQ号) 3. 确保微信号主人和微信支付绑定银行卡的主人姓名一致。核实用户是否有改过名字,或者近期有做身份证升级(从15位身份证升级成18位身份证)。 4. 刚提交任务不会马上收到,会有几分钟或者十几分钟延迟(实际时间取决于信息收集的准确程度) 5. 若提示法人验证失败,查看服务器消息状态码(status),调整信息,重新提交任务。 登录信息完善法人信息认证通过后,用户需通过微信公众平台找回账号密码,管理小程序。 获取小程序的原始ID 获取方式:关注公众平台安全助手。点击菜单栏【绑定查询】,选择微信号绑定账号,选择小程序,可以看到新认证的小程序的原始ID。 注:这个新注册的小程序没有头像和名称,而且后方还带有已授权第三方的字样。 找回账号密码访问网址:https://mp.weixin.qq.com/,进入微信公众平台后台的登录界面,点击 找回账号密码,选择找回账号,输入获取到的原始ID。 [图片] [图片] [图片] 按照要求填写企业信息、公户信息、管理员信息以及验证邮箱,管理员会收到一条打款验证的模板消息,根据微信提供的账号和金额给微信打款 [图片] 1. 打款成功后会受到一条提示模板消息,同时,验证邮箱内会收到一条重置密码的链接。点击链接进行密码重置。成功获取小程序的账号密码,登陆后填写基本信息,而且小程序是已经认证的! 注意事项1. 提前准备一个未注册过小程序公众号的邮箱,找回密码后这个邮箱将会作为小程序的登录邮箱! 2. 没有公户的商家请根据微信提示进行验证。 3. 选择类目时必须选择线下类目
微信开放平台账号如何迁移,原来公司已注销,先想把原公司账号的应用迁移到新注册公司的账号上。麻烦问一下如何操作?
开发工具上在web-view页面内点击鼠标右键有个调试的选项 需要在真机上调试需要自行引入vconsole:https://github.com/Tencent/vConsole/blob/dev/README_CN.md
微信v1.02.1910120 [图片]
之前遇到一个需求,就是要从H5跳转到小程序里,但是微信之前一直没有提供接口做跳转,我们只能做降级方案,在要跳转小程序的地方做了一个弹窗,弹窗里面放小程序码,引导用户长按识别小程序码,然后跳转到小程序内,整个流程非常之长,转化率可想而知也是很低的。 今天刚好看到有人技术群里面问了这个问题,于是我就去看了下微信的文档,发现微信偷偷的更新的这个接口,可以让微信浏览器下的H5跳转到小程序内。 相关文档在这边: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html 用的是JS-SDK的接口,需要使用到js-sdk-1.6.0的版本才有支持,https://res.wx.qq.com/open/js/jweixin-1.6.0.js wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [], // 必填,需要使用的JS接口列表 openTagList: [] // 可选,需要使用的开放标签列表,例如['wx-open-launch-app'] 在wx.config下面多了一项openTagList,开放标签列表,目前支持配置wx-open-launch-weapp,wx-open-launch-app wx-open-launch-weapp 指H5跳转小程序 wx-open-launch-app 指H5跳转app 我们主要介绍的是wx-open-launch-weapp H5跳转小程序 先上才艺: [图片][图片][图片] html代码如下: var btn = document.getElementById('launch-btn'); btn.addEventListener('launch', function (e) { console.log('success'); btn.addEventListener('error', function (e) { console.log('fail', e.detail); username为小程序的原始id,path对应的是小程序的链接地址。之前有写过微信H5的应该会知道怎么把这段代码嵌入到之前的代码里面。 目前此功能仅开放给已认证的服务号,网页的域名要在服务号的“JS接口安全域名”下。 亲测<wx-open-launch-weapp>可以跳转到任意合法合规的小程序,是任意的小程序都能跳转!!!!这个接口真开放(不怕人干坏事?) PS: 有个坑,官方文件说的path是/a/b/c?d=1&e=2#fg,类似的这样的链接格式,但是我自己亲测如果直接使用/a/b/c?d=1&e=2#fg这样格式的链接会报页面不存在,然后我想到了小程序那边复制链接的时候会在链接后面加上.html,于是挖槽的事情发生了,把path链接格式换成/a/b/c.html?d=1&e=2#fg这样就能正常访问,不知道是微信故意这样设计的还是bug,有待考证。 然后这个接口真的可以干好多坏事,希望大家能用正确的价值观来正确使用此接口。 微信开放标签有最低的微信版本要求,以及最低的系统版本要求。 如果开发过程中出现以下情况的,要确认一下,微信版本要求为:7.0.12及以上。 系统版本要求为:iOS 10.3及以上、Android 5.0及以上。 [图片]
正常情况下手机授权点击拒绝的时候是这样的 e.detail.errMsg=getPhoneNumber : faili user deny; 但是我在huwwei-vtr-al00 这款机型上 点击拒绝的时候返回是e.detail.errMsg=getPhoneNumber : faili : user deny; 请问这个是bug还是以后的判定需要加上这个判定条件? 环境是体验版本
Kbone原理解析 “Kbone 是一个致力于微信小程序和 Web 端同构的解决方案。” Web端框架基本原理 首先我们来看下普通Web端框架,以Vue框架为例,一份Vue模板对应一个组件,在代码构建阶段编译成调用Dom接口的JS函数,执行此JS函数就会创建出组件对应的Dom树,从而渲染到浏览器页面上。 然而,小程序是双线程的,并没有Dom树的概念,逻辑层和视图层完全分离,逻辑层是一个纯粹的JSCore,开发者可以编写JS脚本,但是无法直接调用Dom/Bom的api, 没有任何浏览器相关的实现。 在小程序中,视图层和逻辑层的交互是通过数据和时间驱动的。 因此,要实现跨端同构,问题是:怎么将web端代码转为小程序代码? 业界常规做法 目前业界流行的第三方跨端框架们,常规做法都是:静态编译兼容。 原理是把代码语法分析一遍,然后将其中的模板部分翻译成对应的跨端需求的模板(微信小程序、支付宝小程序、H5、APP等)。 静态编译最大的局限性是无法保证转换的完整性,因为Vue模板和WXML模板的语法并不是直接对等的,Vue的特性设计也和小程序的设计无法划等号,这自然就导致了部分Vue特性的丢失。 比如像Vue中的v-html指令、ref获取Dom节点、过滤器等就通通用不了。 除了Vue自身的特性外,一些原本依赖Dom/Bom接口的Vue插件页无法使用,例如Vue-Router。 Kbone的做法 Kbone是通过提供 适配器 的方式来实现同构,即运行时兼容,而非静态编译。 Kbone的适配器核心包含两个部分: miniprogram-render: 仿造Dom/Bom接口,构造仿造Dom树; miniprogram-element: 监听仿造Dom树变化,渲染到页面,同时监听用户行为,触发事件。 仿造Dom树和浏览器的运行时对比: 仿造Dom树: 利用内置组件和自定义组件的自引用来进行递归,创建组件树。 如图,自定义custom-dom为递归自引用组件: 递归的终止条件是遇到特定节点、文本节点或者children空节点。 在创建出组件树后,将Dom节点和自定义组件实例进行绑定,以便后续的Dom更新和操作。 kbone这里还对节点数进行了优化: 如果一个dom节点对应一个自定义组件的话,就会创建很多自定义组件,这样会很浪费开销,这里做了子树的合并,也就是说3层才创建一个自定义组件,节省开销。 优化前:17个dom=17个自定义组件; 优化后:17个dom=4个自定义组件,蓝色那个是单节点,会合并到上面的树; dom 子树作为自定义组件渲染的层级数是可以通过配置传入,理论上层级越多,使用自定义组件数量越少,性能也就越好。 一棵很大的 Dom 树,一次性 setData 到视图层,可能会超过 setData 的大小限制(1024kB),拆分成多棵子 Dom 树,然后分批的 setData 到视图层,可以节省开销。 小程序的事件是视图层到逻辑层的通讯方式,事件绑定在组件上,当被触发时,就会执行逻辑层中对应的事件处理函数。 小程序的捕获冒泡是在视图层view端,因此逻辑层在整个捕获冒泡流程中各个节点接收到的事件不是同一个对象,小程序事件的捕获冒泡和阻止冒泡等操作必须在WXML模板中生命,无法使用接口实现。 为了能够让web端和小程序端的事件系统行为一致,kbone除了仿造了一份Dom树外,也把整个事件系统仿造了一份,即在仿造Dom树上进行捕获冒泡。 当自定义组件监听到用户的操作后,就将事件发往仿造Dom树,后续自定义组件监听到的同一个事件的冒泡就直接忽略。 当触发改节点,仿造Dom树接收到事件后,再进行捕获和冒泡,让事件在各个节点触发。 Kbone的优势 支持多个前端框架:Vue、React、Preact 等 支持更为完整的前端框架特性: Vue 中的 v-html 指令、Vue-router 插件等 提供了常用的 dom/bom 接口 可以使用小程序本身的特性: live-player 内置组件、分包功能等 提供一些 Dom 扩展接口:getComputedStyle 接口等 Kbone实践 脚手架kbone-cli 官方已经提供了kbone-cli可以用来快速开发: 用npm全局安装kbone-cli 可以根据自己的技术栈选择不同的开发模板:React/Vue/Omi/Preact 然后就可以愉快的进行开发啦~ 生成的demo项目结构如下: demo中包含了多页跳转、vue-router、vuex等的使用示例,以及mp-webpack-plugin的配置示例。 对于多页面的应用,在 Web 端可以直接通过 a 标签或者 location 对象进行跳转,但是在小程序中则行不通。同时 Web 端的页面 url 实现和小程序页面路由也是完全不一样的。 Demo示例对比 其中,有一部分两端差异的业务逻辑功能,也给出了3中不同的解决方案: 利用vue-improve-loader,在构建时对dom树节点进行删减,在需要提出的节点加上check-reduce属性 利用reduce-loader,将业务中不需要被打包的代码进行去除,使用行内loader和环境变量来判断 使用样式隐藏,即设置不需要显示的节点样式为 在实际开发中,还会碰到一些细节,例如: 多页面开发:修改webpack和mp-webpack-plugin配置 小程序内置组件: 部分用html标签代替,其他用wx-component + behavior标签 小程序自定义组件:修改mp-webpack-plugin配置,补充wxCustomComponents字段,将自定义组件放入组件根目录,使用自定义组件 自定义app.js和app.wxss:监听app的生命周期,修改webpack配置补充app.js的构建入口,修改插件配置的generate.app字段,补充app.js 扩展dom/bom对象和API:使用 window.$$extend追加方法 代码优化:用reduce-loader做体积精简,dom树精简用vue-improve-loader 区分环境实现不同功能:process.env.isMiniprogram kbone由于目前在快速发展期,更新迭代非常迅速,以下特性是对比了8月份的版本和11月份版本,可以看出已经解决了近2/3的问题。 小程序技术选型 详细了解了kbone之后,我们来分析下小程序技术框架到底应该怎么选? kbone & 小程序原生 已有web版,需要小程序版:kbone 跨平台需求(web + 小程序):kbone 对性能特别苛刻 or 追求稳定 or 要用最新功能:小程序原生 页面节点数量特别多( 1000 节点以上),且对渲染性能要求高:静态模板转义方案(第三方框架:mpvue/taro等) 第三方框架 MpVue :不推荐再用了,坑越来越多,内部也表示之后不会投入太多维护 WePY 1.7.x :不推荐再用了:1.7.x 的版本在最初的设计上的缺陷导致遗留了很多比较严重的问题 WePY 2.0:现在还是 alpha 阶段,内外部有一些小程序在跑,体验和反馈还可以。但依然 issue 比较多。害怕踩坑的也不推荐使用 Taro: 也还是有不少问题,但相对来说应该是比 mpVue 和 WePY 更稳定一点 Uni-app:mpvue的衍生版,跨端 (官方示例有6端) 支持的很好,在H5端实现了一套微信模拟器,可以尝试,是目前唯一支持app端的商用方案,有独立的编辑器HBuildX Chameleon: 统一的多态协议,渐进式跨端,提供脚手架命令工具,规划比较宏大 Omi :基于Taro完成的框架,kbone有支持omi的模板 Nanachi: 基于react的编译期间的转义框架 没有跨端需求,只需要微信小程序 ==> 小程序原生 web端转小程序 or 两端 or 想要尝鲜 ==> kbone 多端 or Vue 技术栈 ==> uni-app 多端 or React 技术栈 ==> taro 不介意学习新技术栈 ==> wepy 2.0 or chaemeleon 小程序在非常快速的更新迭代,就算是原生框架也还是有一些坑的,因此没有哪种框架是百分之百完美,需要根据业务具体需求以及自身技术栈偏好来进行选择。 文章中提及到的部分第三方框架只是参考了官方文档,没有逐个一一尝试,有问题麻烦指出,鞠躬~~
什么是kbone 微信小程序开发过程中,许多开发者会遇到 小程序 与 Web 端一起的需求,由于 小程序 与 Web 端的运行环境不同,开发者往往需要维护两套类似的代码,这对开发者来说比较耗费力气,并且会出现不同步的情况。 为了解决上述问题,微信小程序推出了同构解决方案 [代码]kbone[代码] 来解决此问题。 那么,[代码]kbone[代码] 要怎么使用呢?这里我们将通过一个 [代码]todo[代码] 的例子来跟大家讲解。 首先,我们来看下一个基本的 kbone 项目的目录结构(这里的 [代码]todo[代码] 是基于 [代码]Vue[代码] 的示例,[代码]kbone[代码] 也有 [代码]React[代码],[代码]Preact[代码],[代码]Omi[代码] 等版本,详情可移步 kbone github)。 因为 kbone 是为了解决 小程序 与 Web 端的问题,所以每个目录下的配置都会有两份(小程序 与 Web 端各一份) 不管是 小程序 端还是 Web 端,都需要入口文件。在 [代码]src/index[代码] 目录下,[代码]main.js[代码] 为 Web 端用主入口,[代码]main.mp.js[代码] 则为 小程序 端用主入口。 当然,Web 端会比 小程序 多一个入口页面,即 [代码]index.html[代码](位于根目录下)。 下面两段代码分别是 小程序端 入口与 Web 端入口的代码,可以看到 小程序端的入口代码封装在 [代码]createApp[代码] 函数里面(这里固定即可),内部会比 Web 端多一个创建 [代码]app[代码] 节点的操作,其他的基本就是一致的。 [代码]// 小程序端入口 import Vue from 'vue' import todo from './todo.vue' export default function createApp() { // 创建app节点用于绑定 const container = document.createElement('div') container.id = 'app' document.body.appendChild(container) return new Vue({ el: '#app', render: h => h(todo) [代码]// web端入口 import Vue from 'vue' import todo from './todo.vue' new Vue({ el: '#app', render: h => h(todo) todo.vue 在上面的入口图可以看到,源码目录中,除了入口文件分开之前,页面文件就是共用的了,这里直接使用 Vue 的写法即可,不用做特殊的适应。 写完代码之后,我们要怎么跑项目呢?这时,配置就派上用场啦。 Web 端配置为正常的 Vue 配置,小程序端配置与 Web 端配置的唯一不同就是需要引入 [代码]mp-webpack-plugin[代码] 插件来将 Vue 组件转化为小程序代码。 接着,我们需要构建代码,让代码可以运行到各自的运行环境中去。构建完成后,生产代码会位于 dist 目录中。 [代码]// 构建 web 端代码 // 目标代码在 dist/web npm run build // 构建小程序端代码 // 目标代码在 dist/mp npm run mp 小程序端 的构建会比 Web 端的构建多一个步骤,就是 npm 构建。 进入 [代码]dist/mp[代码] 目录,执行 [代码]npm install[代码] 安装依赖,用开发者工具将 [代码]dist/mp[代码] 目录作为小程序项目导入之后,点击工具栏下的 [代码]构建 npm[代码],即可预览效果。 最后,我们来看一下 todo 的效果。kbone 初体验,done~ todo 代码可到 kbone/demo13 自提。 如果你想了解更多 kbone 相关的使用及详情,可移步 kbone github。 如有疑问,可到 Kbone小主页 发帖沟通。
签名校验通过了,结果还是报错<xml><return_code><![CDATA[FAIL]]></return_code> <return_msg><![CDATA[签名错误]]></return_msg> </xml> 调用的是:https://api.mch.weixin.qq.com/pay/contractorder JSAPI方式,openId有传 appid:wx4ef75de5609f6986 [图片]
9月份社区为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范、然后总结下关于新规以及社区的常见问题、不定时增加社区重要公告 增加小程序违规公告:https://developers.weixin.qq.com/community/operate 增加小程序模版消息公告:https://developers.weixin.qq.com/community/develop/doc/00008a8a7d8310b6bf4975b635a401?blockType=1 增加微信外部链接内容管理规范:https://weixin.qq.com/cgi-bin/readtemplate?t=weixin_external_links_content_management_specification&from=timeline&isappinstalled=0 相关链接模版消息公告 新规范链接 类目资质 恶意对抗平台规则的违规行为公告 小程序修改名称说明 小程序账号相关问题 小程序常见违规整改处理方案 微信外部链接内容管理规范 常见问题 服务范围开放的小程序? 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 若小程序属于第一种服务范围开放的小程序,还是建议可以在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项 服务范围特定的小程序? 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序 只用于公司内部使用的小程序,应该怎么改? 公司内部小程序,只限公司内部使用。如果是公司内部人员,授权微信会自动登录,或者后台数据库存在的手机号码,利用手机验证码登录也可以,这个并没有账号密码登录模式。 你好,经核实,贵方小程序功能无法体验,建议增加一种登录方式(如:账号、密码),并提供可登录体验的测试账号信息,填写在 版本描述处提交,以便审核人员及时体验到小程序功能 经核实,贵方小程序打开即要求授权信息,且点击取消授权后仍强制授权,为企业内部工具,建议在小程序的登录页面明确介绍小程序的具体功能,并且在登录及授权界面为用户提供接受/拒绝登录的权利,由用户自主选择是否进一步授权登录。 类似本地化生活服务的小程序只开通了部分城市,审核被拒绝? 经核实,贵方小程序打开提示:该地区暂未开放,功能无法体验,建议增加手动定位,并在版本描述中写明已上架正式内容的城市,以便审核人员审核。 审核团队的测试微信或手机号并未在我们数据库存在,导致审核失败? 或者类似的问题:我们可以提供测试手机号,或者测试微信账号密码,但是这都涉及到手机验证码,请问当你们登录的时候,我如何把验证码发送给你们? 请将测试信息填写在版本描述处提交审核,审核人员不能对外联系,所以请提供一个写死的验证码,感谢您的支持和理解! 因为小程序特殊性,用户打开必须要获取用户位置。地理位置授权影响登陆规范要求? 你好,如小程序仅要求地理位置授权,暂不属于帐号登录规范要求内 关键字搜索排序与审核通过后搜索不到小程序? 系统会根据query和小程序的相关性来判断召回和排序,后续我们会优化搜索策略,感谢反馈。有异议,提供appid、搜索词、搜索入口、搜索页面截图 刚审核通过发布后搜索不到是有延迟的,大概半天左右的延迟。如果着急可以发帖,注意发帖规范、附上小程序的APPID 仅提供注册服务的小程序,审核拒绝? 你好,如果你的小程序仅提供线上注册功能,后续服务是需要以其他方式提供的话,可以在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或登录。 然后官方加了一句⬇️。说明让用户体验小程序功能是很重要的。 而如果你的小程序除了线上注册外,还同时提供其他线上服务,建议先让用户体验、知悉小程序的功能后,再要求用户进行注册、登录。 审核好几天了,官方可以加急审核吗?? 你好,暂不支持加急审核,请耐心等待审核结果。根据社区规定,提审时间为7个工作日内的催审问题暂不予反馈,故此贴隐藏 关于 企业信息或法定代表人信息不一致? 建议发帖联系客服。注意发帖格式 等15个工作日,平台是拉取的工商局的数据。 可以联系客服工作人员吗,急在线等。 联系客服工作人员的正确姿势 是想咨询什么问题呢?不方便发帖咨询的话可以私信哦(进入个人主页私信功能) 审核超过7天了还是没有消息?? 审核时间为7个工作日,和7天还是有点不一样。 审核超过7个工作日,请刷新页面注意查看站内信,点击小🔔查看最新审核消息。 为什么审核我的小程序这么久,别人可以那么快? 官方整理回答 为什么你以前可以2-3小时通过审核,是因为你在运营/性能/用户等指标都达到优秀,所以符合小程序评测——优秀的标准,因此拥有急速审核的权益。常见问题见:https://kf.qq.com/faq/190108BJnmUN190108RrEnqE.html 你在某一次提交版本之后,小程序的性能存在问题,被我们检测到,所以便失去急速审核的权益,如果你需要急速审核,请你优化好小程序的性能再重新提交,你们可以自己在开发阶段利用工具的体验评分面板先自查一次,详细见https://developers.weixin.qq.com/miniprogram/dev/devtools/audits.html 最后补充一点,如果没有达到优秀的标准,日常承诺处理的审核时间是7个工作日内,如果没有超过7个工作日,催提审是不受理的 好消息是:官方加急审核的权益已经在开发,估计不久后会上线。 同一开放平台账号,绑定的移动应用和公众号,获取的unionid为什么不一致? 只要绑在一个开发者帐号下,即使主体不一样,也允许获取到统一的unionID。绑定同一个微信开放平台帐号下,同一个用户的unionID如果不同的,原因只能是开发者搞混openid。openid要对应所属的AppID,才会相同。 举个例子: 1. 小程序AppID:wxc104eb635b8cxxxx ——帐号A, 公众号AppID:wx311a2a9a8e1dxxxx ——帐号B, 2.核实帐号A和帐号B 绑定同一个微信开放平台帐号是:xxxxxx@sina.com ,所以用一个用户的unionID相同, 3.而开发者所反馈的出现unionID不同,原因是:所提供的openid不属于帐号A,也不属于帐号B,而是属于帐号C或帐号D,而帐号C或帐号D并没有绑定在同一个微信开放平台帐号下,所以unionID不同。
拥有丰富的 Web 前端开发经验的工程师小赵今天刚刚来到新的部门,开始从事他之前没有接触过的微信小程序开发。在上手的第一天,他就向同办公室的小程序老手老李请教了自己的问题。 小赵:翻了一圈文档,小程序好像并不提供 DOM 接口?我还以为可以像之前一样用我喜欢的前端框架来做开发呢。老李,你说小程序为什么不给我们提供 DOM 接口呀。 老李:要提供 DOM 接口也没那么容易。你知道小程序的双线程模型吗?(小赵漏出了疑惑的表情)小程序是基于 Web 技术的,这你应该知道,但小程序和普通的移动端网页也不一样。你做了很多前端项目了,应该知道在浏览器里,UI 渲染和 JavaScript 逻辑都是在一个线程中执行的? 小赵:这我知道,在同一个线程中,UI 渲染和 JavaScript 逻辑交替执行,JavaScript 也可以通过 DOM 接口来对渲染进行控制。 老李:小程序使用的是一种两个线程并行执行的模式,叫做双线程模型。像我画的这样,两个线程合力完成小程序的渲染:一个线程专门负责渲染工作,我们一般称之为渲染层;而另外有一个线程执行我们的逻辑代码,我们一般叫做逻辑层。这两个线程同时运行,并通过微信客户端来交换数据。在小程序运行的时候,逻辑层执行我们编写的逻辑,将数据通过 setData 发送到渲染层;而渲染层解析我们的 WXML 和 WXSS,并结合数据渲染出页面。一方面,每个页面对应一个 WebView 渲染层,对于用户来说更加有页面的感觉,体验更好,而且也可以避免单个 WebView 的负担太重;另一方面,将小程序代码运行在独立的线程中的模式有更好的安全表现,允许有像 open-data 这样的组件可以在确保用户隐私的前提下让我们展示用户数据。 [图片] 小赵:怪不得所有和页面有关的改动都只能通过 setData 来完成。但是用两个线程来渲染我们平时用单线程来渲染的 Web 页面,会不会有些「浪费」?而且每一个页面有一个对应的渲染层,那页面变多的时候,岂不是会有很大的开销? 老李: 并不浪费,因为界面的渲染和后台的逻辑处理可以在同一时间运行了,这使得小程序整体的响应速度更快了。而在小程序的运行过程中,逻辑层需要常驻,但渲染层是可以回收的。实际上,当页面栈的层数比较高的时候,栈底页面的渲染层是会被慢慢回收的。 小赵: 原来如此。这么说的话,实际的 DOM 树是存在于渲染层的,逻辑层并不存在,所以逻辑层才没有任何的 DOM 接口,我明白了。但是……既然可以实现像 setData 这样的接口,为什么不能直接把 DOM 接口也代理到逻辑层呢?我觉得小程序可以做一个封装,让我们在逻辑层调用 DOM 接口,在渲染层调用接口后再把结果返回给我们呀。 老李:从理论上来说确实是可以的。但是线程之间的通信是需要时间的呀。将调用发送到渲染层,再将 DOM 调用结果发送回来,这中间由于线程通信发生的时间损耗可能会比这个接口本身需要的时间要多得多。如果以此为基础使用基于 DOM 接口的前端框架,大量的 DOM 调用可能会非常缓慢,让这个设计失去意义。 在实际测试中,如果每次 DOM 调用都进行一次线程通信,耗时大约是同等节点规模直接在渲染层调用的百倍以上;如果忽略通信需要的时间,一个实现良好的基于 DOM 代理的框架可以近似地看成一个动态模板的框架,而动态模板和静态模板相比要慢至少 50% 小赵:原来如此,线程通信的时间确实是我没有考虑到的问题。那现在的小程序框架中难道不存在这个问题吗? 老李: 在现在的小程序框架中,这个问题也是存在的,这也是现在的框架基于静态模板渲染的原因。静态模板可以在运行前就做好打包,直接注入到渲染层,省去线程传输的时间。在运行时,逻辑层只和渲染层进行最少的、必要的数据交换:也就是渲染用的数据,或者说 data 。另一方面,静态模板让两个线程都在启动时就拥有模板相关的所有数据,所以框架也充分利用了这一点,进行了很多优化。 小赵: 怪不得我在文档里发现很多和 setData 有关的性能提示,都提醒尽量减少设置不必要的数据,现在总算是知道为什么了。但是具体到实际开发里的时候,还是总觉得很难每次只设置需要的数据啊,像对象里或者数组里的数据怎么办呢? 老李: 如果只改变了对象里或者数组里的一部分数据,可以通过类似 array[2].message , a.b.c.d 这样的 数据路径 来进行「精准设置」。另外,现在自定义组件也支持 纯数据字段 了,只要在自定义组件的选项中设置好名为 pureDataPattern 的正则表达式, data 中匹配这个正则的字段将成为纯数据字段,例如,你可以用 /^_/ 来指定所有 开头的数据字段为纯数据字段。所有纯数据字段仅仅被记录在逻辑层的 this.data 中,而不会被发送到渲染层,也不参与任何界面渲染过程,节省了传输的时间,这样有助于提升页面更新性能。 小赵:小程序还有这样的功能,受教了。不过说来说去,我还是想在小程序里用我顺手的框架来开发,毕竟这样事半功倍嘛。我在网上搜索了一下,发现现在有很多支持用 Web 框架做小程序开发的框架,但好像都是将模板编译成 WXML,最终由小程序来做渲染,但这样的方法好像兼容性也不是很好。我在想,我们能不能在逻辑层仿造一套 DOM 接口,然后在运行时将 DOM 调用适配成小程序调用? 老李: 你的这个脑洞有一些意思。在逻辑层仿造一套 DOM 接口,直接维护一棵 DOM 树,这当然没问题。但是没有代理 DOM 接口,逻辑层的 DOM 树没法反映到渲染层,因为渲染层具体会出现什么样的组件,是运行时才能知道的,这不就没法生成静态模板了? 小赵:静态模板确实是没法生成了,但我看到小程序的框架支持自定义组件,我是不是可以做一个通用的自定义组件,让它根据传入的参数不同,变成不同的小程序内置组件。而且自定义组件还支持在自己的模板中引用自己,那么我只需要一个这个通用组件,然后从逻辑层用代码去控制当前组件应该渲染成什么内置组件,再根据它是否有子节点去递归引用自己进行渲染就可以了。你看这样可行吗? [图片] 老李: 这样的做法确实可行,而且微信官方已经按照这个思路推出小程序和 Web 端同构的解决方案 Kbone 了。Kbone 的原理就像你刚才说的那样,它提供一个 Webpack 插件,将项目编译成小程序项目;同时提供两个 npm 包,分别提供 DOM 接口模拟和你说的那个通用的自定义组件作为运行时依赖。要不你赶紧试试? 小赵:还有这么好的事,那我终于可以用我喜欢的框架开发小程序了!这么好的框架,为什么不直接内置到小程序的基础库里呀? 老李: 因为这样的功能完全可以用现在已有的基础库功能实现出来呀。Kbone 现在是 npm 包的形式,使得它的功能、问题修复可以随着自己的版本来发布,不需要依赖于基础库的更新和覆盖率,不是挺好的吗? 小赵: 好是好,但我担心的是代码包大小限制的问题。除了我们已经写好的业务逻辑之外,现在还得加上 Kbone,会不会装不下呀? 老李: 原来你是担心这个呀,放心,Kbone 现在已经可以在 扩展库 里一键搞定啦。扩展库是帮我们解决依赖的全新功能,只要在配置项中指定 Kbone 扩展库,就相当于引入了 Kbone 相关的最新版本的 npm 包,这样就不占用小程序的代码包体积了,快试试吧! 小赵:哇,那可太爽了,马上就搞起! 最后 如果你对 Kbone 感兴趣或者有相关问题需要咨询, 欢迎加入 Kbone 技术交流 QQ 群:926335938
开放平台提小程序设置类别 api /cgi-bin/wxopen/addcategory?access_token=" appid wx84b73785711a9888 返回 "message": "invaid register type hint: [ufXwka05541894]", "errcode": 41033 请问如何解决
第三方平台代小程序提交审核需要设置类目,代小程序添加类目接口在哪里。获取授权的小程序的服务类目为空,如何为授权的小程序添加类目,能否给出相应的接口文档, 该小程序不是第三方平台创建的,所以小程序基本信息设置 的接口都用不了,请问如何处理
使用的工具和库 @vue/cli v4.1.1 vue-cli-plugin-kbone 0.4.14 kbone-ui 0.5.8 关于 [代码]vue-cli 3.x[代码] 的使用这里不做介绍,请移步至Vue CLI官方文档查看 使用 [代码]vue-cli 3.x[代码] 可视化页面根据自己的需求创建一个 [代码]ts[代码] 空项目即可 因为我们是用 [代码]ts[代码] 开发微信小程序,所以需要安装微信小程序对应的 [代码]types[代码] [代码]miniprogram-api-typings[代码], 安装好后需要在 [代码]tsconfig.json[代码] 配置文件中的 [代码]types[代码] 节点添加对应的配置,最好是再加一个忽略校验 [代码]*.d.ts[代码] 的配置项,因为引入的 [代码]types[代码] 在编译时也会校验 [代码]{ "compilerOptions": { "skipLibCheck": true, "types": [ "miniprogram-api-typings" 集成kbone [代码]kbone[代码] 提供了 [代码]vue-cli 3.x[代码] 的插件,可以一键集成到 [代码]vue-cli 3.x[代码] 创建的项目中。 在刚刚创建好的空项目中选择 [代码]插件[代码] -> [代码]添加插件[代码] 搜索 [代码]vue-cli-plugin-kbone[代码] 点击安装即可 安装完成后会弹出配置页面 [代码]AppId[代码] 填写自己的小程序 [代码]AppId[代码] [代码]项目名[代码] 填写自己的项目名 [代码]Kbone 入口文件名称[代码] 填写为 [代码]main.mp.ts[代码] [代码]是否需要输出 app.js、project.config.json 等非页面相关文件[代码] 选择 [代码]不输出 project.config.json[代码] [代码]选择 app.wxss 输出配置[代码] 选择 [代码]输出默认标签样式[代码] [代码]选择是否自动构建依赖包[代码] 根据自己使用的工具选择 [代码]npm[代码] 或 [代码]yarn[代码] [代码]是否需要使用 rem[代码] 建议开启 其他的配置项默认即可,然后点击完成 插件会自动生成 [代码]kbone[代码] 的入口文件、配置文件、以及三条命令脚本 入口文件就是我们刚才输入的 [代码]main.mp.ts[代码] ,位置在 [代码]src[代码] 目录下,根据自己创建项目时所安装的库进行修改 例如我在创建时使用了 [代码]router[代码] 和 [代码]vuex[代码] 这些需要在入口文件引入的,所以也需要在 [代码]main.mp.ts[代码] 中引入 [代码]import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false // 需要将创建根组件实例的逻辑封装成方法 export default function createApp () { // 在小程序中如果要注入到 id 为 app 的 dom 节点上,需要主动创建 const container = document.createElement('div') container.id = 'app' document.body.appendChild(container) return new Vue({ router, store, render: h => h(App) }).$mount('#app') 配置文件为 [代码]miniprogram.config.js[代码] , 位置在项目根目录,其中的内容自行按需修改,对应的配置项可参考 [代码]kbone[代码] 官方文档 插件会在 [代码]package.json[代码] 中生成的三条命令脚本,分别是 [代码]build:mp[代码] 、 [代码]dev:mp[代码] 和 [代码]mp[代码] 因为我们后面需要再集成 [代码]云开发[代码] 所以命令需要做一点点修改,将打包目录修改为 [代码]dist/mp/miniprogram[代码] [代码]build:mp[代码] 改为 [代码]"build:mp": "cross-env MP_ENV=miniprogram vue-cli-service build --mode production --dest ./dist/mp/miniprogram/common" [代码]mp[代码] 改为 [代码]"mp": "cross-env MP_ENV=miniprogram vue-cli-service build --mode development --dest ./dist/mp/miniprogram/common --watch" 集成kbone-ui 万众期待的 [代码]kbone-ui[代码] 终于发布了一部分出来,官方文档 可以继续使用 [代码]vue-cli 3.x[代码] 的可视化页面安装,也可以使用命令行安装 安装完成后在 [代码]web[代码] 端的入口文件 [代码]main.ts[代码] 和 [代码]kbone[代码] 入口文件 [代码]main.mp.ts[代码] 中引入 [代码]kbobe-ui[代码] main.ts [代码]import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import KboneUI from 'kbone-ui' import 'kbone-ui/lib/weui/weui.css' Vue.use(KboneUI) Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app') main.mp.ts [代码]import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import KboneUI from 'kbone-ui' import 'kbone-ui/lib/weui/weui.css' Vue.use(KboneUI) Vue.config.productionTip = false // 需要将创建根组件实例的逻辑封装成方法 export default function createApp () { // 在小程序中如果要注入到 id 为 app 的 dom 节点上,需要主动创建 const container = document.createElement('div') container.id = 'app' document.body.appendChild(container) return new Vue({ router, store, render: h => h(App) }).$mount('#app') 集成云开发 完成上面步骤项目大致是可以跑起来了,但是 [代码]kbone[代码] 是不支持 [代码]scoped[代码] 样式的,所以在项目中用到 [代码]scoped[代码] 样式的地方需要自己再改一下,这里就不细说了 还有最重要的一点就是我们在集成 [代码]kbone[代码] 时选择的是 [代码]不输出 project.config.json[代码] 也就意味着打包的时候不会生成 [代码]project.config.json[代码] 文件,原因也是因为 [代码]云开发[代码] 中需要在 [代码]project.config.json[代码] 中配置 [代码]miniprogramRoot[代码] 和 [代码]cloudfunctionRoot[代码] ,所以我们需要手动建一个 [代码]project.config.json[代码] 文件在项目根目录,如果不会建,那就在开发者工具中新建一个 [代码]云开发[代码] 项目,从里面拷一个吧 [代码]project.config.json[代码] 文件创建完后,需要再在项目根目录创建一个 [代码]cloudfunctions[代码] 文件夹,用于存放 [代码]云开发[代码] 的代码,关于 [代码]云开发[代码] 的代码都放在这个文件夹下面就可以了,云开发文档 上面的步骤完成后,还有最后一个关键步骤,配置 [代码]webpack[代码] 在打包项目时将 [代码]云开发[代码] 相关的代码也打包进去 在项目根目录创建一个 [代码]vue.config.js[代码] 文件,添加以下代码 [代码]const path = require('path') const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { configureWebpack: config => { if (process.env.MP_ENV === 'miniprogram') { config.plugins.push(new CopyWebpackPlugin([ from: path.resolve(__dirname, './cloudfunctions'), to: path.resolve(__dirname, './dist/mp/cloudfunctions'), toType: 'dir' from: path.resolve(__dirname, './project.config.json'), to: path.resolve(__dirname, './dist/mp/project.config.json'), toType: 'file' 其配置的意思就是如果当前运行的环境变量是 [代码]miniprogram[代码] 那么就将 [代码]cloudfunctions[代码] 文件夹和 [代码]project.config.json[代码] 文件拷贝到小程序的打包目录,到这里就全部集成完了,可以开心的去码代码了 DEMO项目https://github.com/stephenml/kbone-ts-template
我们有食品经营许可证,经营项目是“预包装食品销售(含冷藏冷冻食品),散装食品销售(不含冷藏冷冻食品)”,现在要上线小吃快餐,还需要其他资质证明吗?
背景 小程序一个比较重要的能力就是获取用户信息,也就是使用 [代码]wx.getUserInfo[代码] 接口。我们发现几乎所有的小程序都会调用这个接口。虽然我们在设计文档上有提出最好的设计是在真正要用户信息的情况下才去获取用户信息,不过很多开发者并没有按照我们的期望去做,导致用户在使用的时候有很多困扰。 归结起来有几点: 开发者在首页直接调用 [代码]wx.getUserInfo[代码] 进行授权,弹框有会使得一部分用户放弃小程序的使用。 开发者没有处理用户拒绝弹框的情况,有部分小程序强制要求用户授权头像昵称等信息才能继续使用小程序。 用户没有很好的方式重新授权,虽然在前几个版本我们增加了[代码]设置[代码]页面可以让用户选择重新授权,但是操作还是不够便捷。 开发者希望进到首页就获取到用户的[代码]unionId[代码],以便和之前已经关注了公众号的用户画像关联起来。 开发者默认将 [代码]wx.login[代码] 和 [代码]wx.getUserInfo[代码] 绑定使用,这个是由于我们一开始的设计缺陷和实例代码导致: [代码]getUserInfo[代码]必须通过[代码]wx.login[代码] 在后台生成[代码]session_key[代码] 后才能调用。 为了解决以上几点,我们更新了三个能力: 使用组件来获取用户信息,用户拒绝授权后也可以重新弹窗再次授权 若用户满足一定条件(下文有详细介绍),则可以用[代码]wx.login[代码] 获取到的code直接换到[代码]unionId[代码] [代码]wx.getUserInfo[代码] 不依赖 [代码]wx.login[代码] 就能调用得到数据。 获取用户信息组件介绍[代码][代码] 组件变化: [代码]open-type[代码] 属性增加 [代码]getUserInfo[代码] :用户点击时候会触发 [代码]bindgetuserinfo[代码] 事件。 新增事件 [代码]bindgetuserinfo[代码] :当 [代码]open-type[代码] 为 [代码]getUserInfo[代码] 时,用户点击会触发。可以从事件返回参数的[代码]detail[代码]字段中获取到和[代码]wx.getUserInfo[代码] 返回参数相同的数据。 示例: [代码]<[代码][代码]button[代码] [代码]open-type[代码][代码]=[代码][代码]"getUserInfo"[代码] [代码]bindgetuserinfo[代码][代码]=[代码][代码]"userInfoHandler"[代码][代码]> Click me </[代码][代码]button[代码][代码]>[代码]和 [代码]wx.getUserInfo[代码] 不同之处在于: API [代码]wx.getUserInfo[代码] 只会弹一次框,用户拒绝授权之后,再次调用将不会弹框 组件 [代码][代码][代码][代码] 由于是用户主动触发,不受弹框次数限制,只要用户没有授权,都会再次弹框 直接获取unionId考虑很多场景下,业务方申请userinfo授权主要为了获取unionid。我们鼓励开发者在不骚扰用户的情况下合理获得unionid,而仅在必要时才向用户弹窗申请使用昵称头像。为此,凡使用“获取用户信息组件”获取用户昵称头像的小程序,在满足以下全部条件时,将可以静默获得unionid。 在微信开放平台下存在同主体的App、公众号、小程序。 用户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权。 getUserInfo 和 login很多开发者会把login和getUserInfo捆绑调用当成登录使用,其实login已经可以完成登录,可以建立账号体系了,getUserInfo只是获取额外的用户信息。 在login获取到code,然后发送到开发者后端,开发者后端再通过接口去微信后端换取到openid和sessionKey(并且现在会将unionid也一并返回)之后,然后把3rd_session返回给前端,就已经完成登录行为。而login行为是静默,不必授权的,不会对用户造成骚扰。 getUserInfo只是为了提供更优质的服务而存在,比如展示头像昵称,判断性别,通过unionId和其他公众号上已有的用户画像结合起来提供历史数据。所以不必在刚刚进入小程序的时候就强制要求授权。 推荐使用方法调用[代码]wx.login[代码] 获取[代码]code[代码],然后从微信后端换取到[代码]sessionKey[代码],用于解密[代码]getUserInfo[代码]返回的敏感数据。 使用[代码]wx.getSetting[代码] 获取用户的授权情况 如果用户已经授权,直接调用 API [代码]wx.getUserInfo[代码] 获取用户最新的信息 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。 获取到用户数据后可以进行展示或者发送给自己的后端。 文档中的quickStart已经更新 特别注意为了给用户提供更好的小程序环境,我们约定在一段时间后(具体时间会做通知),若还出现以下情况(包括但不限于),将无法通过审核 初次打开小程序就弹框授权用户信息 未处理用户拒绝授权的情况 强制要求用户授权 已经上线的小程序不会受到影响。 FAQ Q: 除了 UserInfo 呢,比如说位置信息 --- ’风の诺言 . A: 其他授权信息不像用户信息那么高频繁,也基本是在使用时候才申请授权,所以没有同 UserInfo 一起给出。我们会先看看 UserInfo 的使用情况再结合具体场景我们会给出相应的方案 Q: 后台要维护用户信息 --- Azleal 我们的小程序业务是功能都需要授权才能使用的(也就是必须拿到unionid获取用户信息) --- elemeNT 我在小程序与服务号的数据需要互通,通过unionId来确定用户的唯一性,如果在用户进入小程序后不强制他授权,单凭一个openid来存储他的用户数据,在用户下次从服务号进入时。不就会产生重复数据吗?就没做到数据互通了 --- ﺭ并向你吐了趴口水ﺭ五年. 另外看到官方提到 要强制推行,我想说我们目前所有用户是通过unionid注册的。那么这些用户就不得不使用 openid重新登录 、注册一遍。更重要的是,之前他们的相关数据都会对应不上(因为你们也不允许强制用户登录授权) --- 羊毛 现在这种方案,不能满足我们的需求,我们的小程序,必须一进入就要获取他的信息,然后加载他的数据; --- 韩文 A: 调用`wx.login`已经可以获取到用户的登录态,已经可以做用户账号的管理。 UserInfo 中带的 UnionId 是额外的信息,没有它完全可以完成登录 对于需要和开发平台绑定的业务进行数据互通的情况,一个新用户进来没有互通数据的情况下也是可以体验到所有业务,那么对于没有授权unionId的用户,可以将其当成是新用户,当真正授权unionId之后再做绑定完全是可以的 Q: 我需要确保用户的唯一性,这样就必须取unionID,否则用户删除了小程序,或者换了设备, 下次再进来这个小程序,该用什么来区分是上次来过的用户呢?? --- WEI+ A: 如果你本身没有其他公众号、App、小程序,那么也就没必要拿到unionid,因为unionid是打通你在开放平台下所有应用的标识 如果只有一个小程序,用 openid 足以, openid 是一个用户对于一个小程序的标识,永远不变 Q: wx.getUserInfo 是网络请求,如果使用了 open-type = "getUserInfo",是否每次点击都会调接口? --- SouthernBox A: 是的,open-type="getUserInfo" 的作用以及内部实现基本和 wx.getUserInfo 一样 区别是一个开发者主动(拒绝一次不再弹窗),一个是用户主动(拒绝任意次都可以重新弹窗) Q: 比如有一个创建按钮,用户点击一次授权了,我已经获取到用户信息,再次点击就没必要再调用 getUserInfo 去网络请求了。 --- SouthernBox A: 可以参考文中 quickStart 的做法,如果已经授权了,那就可以把按钮隐藏,之后的授权直接用API wx.getUserInfo 调用(因为已经授权,所以也不会弹窗),用户也不会再点了 Q: 小程序是不是必须要用微信自带的授权才可以登录 ,能否不使用授权方式登录,用自己系统的api接口数据实现?这个会不会涉及到审核不过的问题??麻烦解答一下 谢谢了。 --- WEI+ A: 自己做登录不会涉及到审核问题。 不过不建议在没有原有账号体系的情况下让用户在小程序内注册,过重的行为会损失用户。 Q: 在小程序中有一个"我的"页面,这是属于会员页,如果用户要进入这个页面就必须授权。交互方式就是在用户未授权情况下整个页面只显示一个授权获取用户信息的button 按钮,这个需要用户自己去触发,算不算强制授权? --- ﺭ并向你吐了趴口水ﺭ五年. A: 强制授权是说如果用户如果不授权基本信息,连最基础的浏览功能都不提供(当然这个也是要分具体的业务场景,不会限制得太死板) 可以有更好的交互,参考下主流App,在未登录的时候点击【我的】页面,也不会直接要求登录,而是展示了一定的页面结构,同时给一个登录按钮(例如【携程】【京东】等),之后再在这个页面做操作的话可以弹一个登录页面或按钮提示用户登录是完全可以的。 上述所说的登录只是用户感知上的登录,从业务逻辑上用户其实在 wx.login 的时候已经完成登录了。 Q: 看了很多评论,有些人还是不知道为什么官方要这样做,我作为一个商家角度来说下。 --- Mr.J 1. 比如我们要做一些户外推广的二唯码,用户只看到了你的图片宣传单,扫描二唯码一打开就提示“需要获取你的个人信息,您是否允许”,你不要当自己是开发者当自己是一个正常人,看到这个提示我相信很多人的第一反应就是拒绝。如果第一步已经把你拒之门外,谈何营销? 2. 没有小程序之前,我们在公众号有很多用户,都绑定了unionid,有小程序之后我们考虑怎么让用户接受小程序,可以静默登录我觉得非常好,从公众号过来的用户可以直接就登录了,没有任何提示,完美的对接,这是一个很好的体验。 A: 说得很好,我们的这些改造不仅是为了开发者,同时也是为了这个生态下的用户考虑。希望开发者们也能站在用户的角度去思考怎么做一个产品。 Q: 我不明白为什么login 给多个unionid 为什么不行? unionid也不能算是个人信息吧,给多个unionid可以更方便开发者,而且很多情况下就不用调用getUserInfo了 --- candyTong 我们提个建议,能否直接开放unionid呢?这样也许会有许多小程序不需要再弹窗了。既一定程度保障了用户体验,也照顾到了我们开发者的体验。 --- 羊毛 A: 如果直接开放了unionid,就会出现这种情况:当你作为一个用户进入一个小程序,这个小程序并没要求你授权就直接把你的头像昵称显示出来(它之前把unionId对应的头像昵称都存了下来),但是这个小程序主体(open平台主体和公众平台主体并不相同)相关的任何一个应用你从来没用过,你会不会觉得很奇怪并且很不舒服,觉得自己在微信内的用户信息没有丝毫的保障? Q: 那有推荐的比较好的例子么?对于必须使用用户头像、昵称这些信息的小程序而言 --- 亚里士朱德 A: 首先,没有什么逻辑是一定要使用用户的头像、昵称才能work的。对于这个case,完全可以先用默认头像、匿名昵称先做替代,用户点击默认头像后就可以弹出授权信息,非常的水到渠成。 Q: 之前看了这个帖子一直在思考,如果是一进去需要回去用户的地理位置信息显示到地图上的呢?这样算不算是一进去就弹窗授权获取用户信息? --- 吴俊绩🤔 A: 地图的情况和获取用户信息不同,我们目前还没对地图的授权请求有所调整。当前不受上述策略的影响 Q: 对于开发者而言,小程序与公众号是同级的,只是不同的入口 但是这样的设计,公众号与小程序成了主从关系咯 --- log琥珀① A: 并无什么主从关系,只是多一个渠道让开发者可以更方便的获取到已经是该主体下用户的unionId
在小程序里,识别小程序码,本来识别小程序码或者独立扫码会打开本程序的其他页面,请问在修复了吗? https://developers.weixin.qq.com/community/develop/doc/00080205234788ed7099799765bc00
小程序代码包总包上限提升至12M为了让开发者开发出功能更丰富的小程序,小程序或小游戏代码包总包上限由8M提升到12M。建议开发者优化小程序性能并将每个分包做得尽可能小,以便提升用户的打开速度,优化用户体验。 开发者可登录开发者工具,在详情-项目配置中点击刷新按钮,获取最新的配置信息。 [图片] 内置扩展库支持扩展库是将官方提供的一系列扩展能力打包而成的一些类库。为方便开发者使用,我们在最新的nightly版开发者工具支持了内置扩展库,开发者只需在app.json配置文件声明引用指定扩展库即可,无需自行引入相关 npm 包且不计入小程序代码包大小,目前支持了kbone和WeUI两种扩展库,更多详情请参考文档。 欢迎各位开发者接入,如有问题,可在本帖下方留言或在社区发帖反馈。 微信团队 2019.11.22
众所周知,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 WebView 渲染的内置组件,他们是交由原生客户端渲染的。原生组件作为 Webview 的补充,为小程序带来了更丰富的特性和更高的性能,但同时由于脱离 Webview 渲染也给开发者带来了不小的困扰。在小程序引入「同层渲染」之前,原生组件的层级总是最高,不受 [代码]z-index[代码] 属性的控制,无法与 [代码]view[代码]、[代码]image[代码] 等内置组件相互覆盖, [代码]cover-view[代码] 和 [代码]cover-image[代码] 组件的出现一定程度上缓解了覆盖的问题,同时为了让原生组件能被嵌套在 [代码]swiper[代码]、[代码]scroll-view[代码] 等容器内,小程序在过去也推出了一些临时的解决方案。但随着小程序生态的发展,开发者对原生组件的使用场景不断扩大,原生组件的这些问题也日趋显现,为了彻底解决原生组件带来的种种限制,我们对小程序原生组件进行了一次重构,引入了「同层渲染」。 相信已经有不少开发者已经在日常的小程序开发中使用了「同层渲染」的原生组件,那么究竟什么是「同层渲染」?它背后的实现原理是怎样的?它是解决原生组件限制的银弹吗?本文将会为你一一解答这些问题。 什么是「同层渲染」? 首先我们先来了解一下小程序原生组件的渲染原理。我们知道,小程序的内容大多是渲染在 WebView 上的,如果把 WebView 看成单独的一层,那么由系统自带的这些原生组件则位于另一个更高的层级。两个层级是完全独立的,因此无法简单地通过使用 [代码]z-index[代码] 控制原生组件和非原生组件之间的相对层级。正如下图所示,非原生组件位于 WebView 层,而原生组件及 [代码]cover-view[代码] 与 [代码]cover-image[代码] 则位于另一个较高的层级: 那么「同层渲染」顾名思义则是指通过一定的技术手段把原生组件直接渲染到 WebView 层级上,此时「原生组件层」已经不存在,原生组件此时已被直接挂载到 WebView 节点上。你几乎可以像使用非原生组件一样去使用「同层渲染」的原生组件,比如使用 [代码]view[代码]、[代码]image[代码] 覆盖原生组件、使用 [代码]z-index[代码] 指定原生组件的层级、把原生组件放置在 [代码]scroll-view[代码]、[代码]swiper[代码]、[代码]movable-view[代码] 等容器内,通过 [代码]WXSS[代码] 设置原生组件的样式等等。启用「同层渲染」之后的界面层级如下图所示: 「同层渲染」原理 你一定也想知道「同层渲染」背后究竟采用了什么技术。只有真正理解了「同层渲染」背后的机制,才能更高效地使用好这项能力。实际上,小程序的同层渲染在 iOS 和 Android 平台下的实现不同,因此下面分成两部分来分别介绍两个平台的实现方案。 iOS 端 小程序在 iOS 端使用 WKWebView 进行渲染的,WKWebView 在内部采用的是分层的方式进行渲染,它会将 WebKit 内核生成的 Compositing Layer(合成层)渲染成 iOS 上的一个 WKCompositingView,这是一个客户端原生的 View,不过可惜的是,内核一般会将多个 DOM 节点渲染到一个 Compositing Layer 上,因此合成层与 DOM 节点之间不存在一对一的映射关系。不过我们发现,当把一个 DOM 节点的 CSS 属性设置为 [代码]overflow: scroll[代码] (低版本需同时设置 [代码]-webkit-overflow-scrolling: touch[代码])之后,WKWebView 会为其生成一个 [代码]WKChildScrollView[代码],与 DOM 节点存在映射关系,这是一个原生的 [代码]UIScrollView[代码] 的子类,也就是说 WebView 里的滚动实际上是由真正的原生滚动组件来承载的。WKWebView 这么做是为了可以让 iOS 上的 WebView 滚动有更流畅的体验。虽说 [代码]WKChildScrollView[代码] 也是原生组件,但 WebKit 内核已经处理了它与其他 DOM 节点之间的层级关系,因此你可以直接使用 WXSS 控制层级而不必担心遮挡的问题。 小程序 iOS 端的「同层渲染」也正是基于 [代码]WKChildScrollView[代码] 实现的,原生组件在 attached 之后会直接挂载到预先创建好的 [代码]WKChildScrollView[代码] 容器下,大致的流程如下: 创建一个 DOM 节点并设置其 CSS 属性为 [代码]overflow: scroll[代码] 且 [代码]-webkit-overflow-scrolling: touch[代码]; 通知客户端查找到该 DOM 节点对应的原生 [代码]WKChildScrollView[代码] 组件; 将原生组件挂载到该 [代码]WKChildScrollView[代码] 节点上作为其子 View。 通过上述流程,小程序的原生组件就被插入到 [代码]WKChildScrollView[代码] 了,也即是在 [代码]步骤1[代码] 创建的那个 DOM 节点对应的原生 ScrollView 的子节点。此时,修改这个 DOM 节点的样式属性同样也会应用到原生组件上。因此,「同层渲染」的原生组件与普通的内置组件表现并无二致。 Android 端 小程序在 Android 端采用 chromium 作为 WebView 渲染层,与 iOS 不同的是,Android 端的 WebView 是单独进行渲染而不会在客户端生成类似 iOS 那样的 Compositing View (合成层),经渲染后的 WebView 是一个完整的视图,因此需要采用其他的方案来实现「同层渲染」。经过我们的调研发现,chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,主要用来解析和描述embed 标签。Android 端的同层渲染就是基于 [代码]embed[代码] 标签结合 chromium 内核扩展来实现的。 Android 端「同层渲染」的大致流程如下: WebView 侧创建一个 [代码]embed[代码] DOM 节点并指定组件类型; chromium 内核会创建一个 [代码]WebPlugin[代码] 实例,并生成一个 [代码]RenderLayer[代码]; Android 客户端初始化一个对应的原生组件; Android 客户端将原生组件的画面绘制到步骤2创建的 [代码]RenderLayer[代码] 所绑定的 [代码]SurfaceTexture[代码] 上; 通知 chromium 内核渲染该 [代码]RenderLayer[代码]; chromium 渲染该 [代码]embed[代码] 节点并上屏。 这样就实现了把一个原生组件渲染到 WebView 上,这个流程相当于给 WebView 添加了一个外置的插件,如果你有留意 Chrome 浏览器上的 pdf 预览,会发现实际上它也是基于 [代码]<embed />[代码] 标签实现的。 这种方式可以用于 map、video、canvas、camera 等原生组件的渲染,对于 input 和 textarea,采用的方案是直接对 chromium 的组件进行扩展,来支持一些 WebView 本身不具备的能力。 对比 iOS 端的实现,Android 端的「同层渲染」真正将原生组件视图加到了 WebView 的渲染流程中且 embed 节点是真正的 DOM 节点,理论上可以将任意 WXSS 属性作用在该节点上。Android 端相对来说是更加彻底的「同层渲染」,但相应的重构成本也会更高一些。 「同层渲染」 Tips 通过上文我们已经了解了「同层渲染」在 iOS 和 Android 端的实现原理。Android 端的「同层渲染」是基于 chromium 内核开发的扩展,可以看成是 webview 的一项能力,而 iOS 端则需要在使用过程中稍加注意。以下列出了若干注意事项,可以帮助你避免踩坑: Tips 1. 不是所有情况均会启用「同层渲染」 需要注意的是,原生组件的「同层渲染」能力可能会在特定情况下失效,一方面你需要在开发时稍加注意,另一方面同层渲染失败会触发 [代码]bindrendererror[代码] 事件,可在必要时根据该回调做好 UI 的 fallback。根据我们的统计,目前同层失败率很低,也不需要太过于担心。 对 Android 端来说,如果用户的设备没有微信自研的 [代码]chromium[代码] 内核,则会无法切换至「同层渲染」,此时会在组件初始化阶段触发 [代码]bindrendererror[代码]。而 iOS 端的情况会稍复杂一些:如果在基础库创建同层节点时,节点发生了 WXSS 变化从而引起 WebKit 内核重排,此时可能会出现同层失败的现象。解决方法:应尽量避免在原生组件上频繁修改节点的 WXSS 属性,尤其要尽量避免修改节点的 [代码]position[代码] 属性。如需对原生组件进行变换,强烈推荐使用 [代码]transform[代码] 而非修改节点的 [代码]position[代码] 属性。 Tips 2. iOS 「同层渲染」与 WebView 渲染稍有区别 上文我们已经了解了 iOS 端同层渲染的原理,实际上,WebKit 内核并不感知原生组件的存在,因此并非所有的 WXSS 属性都可以在原生组件上生效。一般来说,定位 (position / margin / padding) 、尺寸 (width / height) 、transform (scale / rotate / translate) 以及层级 (z-index) 相关的属性均可生效,在原生组件外部的属性 (如 shadow、border) 一般也会生效。但如需对组件做裁剪则可能会失败,例如:[代码]border-radius[代码] 属性应用在父节点不会产生圆角效果。 Tips 3. 「同层渲染」的事件机制 启用了「同层渲染」之后的原生组件相比于之前的区别是原生组件上的事件也会冒泡,意味着,一个原生组件或原生组件的子节点上的事件也会冒泡到其父节点上并触发父节点的事件监听,通常可以使用 [代码]catch[代码] 来阻止原生组件的事件冒泡。 Tips 4. 只有子节点才会进入全屏 有别于非同层渲染的原生组件,像 [代码]video[代码] 和 [代码]live-player[代码] 这类组件进入全屏时,只有其子节点会被显示。 阅读本文之后,相信你已经对小程序原生组件的「同层渲染」有了更深入的理解。同层渲染不仅解决了原生组件的层级问题,同时也让原生组件有了更丰富的展示和交互的能力。下表列出的原生组件都已经支持了「同层渲染」,其他组件( textarea、camera、webgl 及 input)也会在近期逐步上线。现在你就可以试试用「同层渲染」来优化你的小程序了。 支持同层渲染的原生组件
我们的需求是这样的》 客户留了一段备注信息,里面可能会有手机号(可能是多个号码),怎么才能识别出手机号,并且手机号要能高亮显示,当用户看到手机号的时候,直接点击手机号就可以拨打电话了,这样的功能App里面很好实现,但是小程序怎么实现呢。
各位开发者: 由于内部网络设备裁撤,api.weixin.qq.com(sz/hk.api.weixin.qq.com)需要更新部分接入点IP,详细如下: 深圳移动vip变更:183.232.103.145;120.198.199.239 -> 112.60.0.226;112.60.0.235 深圳电信vip变更:183.61.49.149 -> 183.3.234.152 深圳联通vip变更:58.251.61.149 -> 58.251.80.204 深圳cap vip变更:182.254.106.119 -> 121.51.166.37 香港vip变更:203.205.147.177;103.7.30.34 -> 203.205.239.94;203.205.239.82;103.7.30.16 此次变更不影响直接使用DNS解析微信域名的业务,如有业务仍在尝试旧的接入IP(通过代码写死或防火墙配置),请及时更新至新的接入IP,裁撤截止日期2019年10月17日,届时旧IP会停止服务,返回指引文档信息。 请不要通过配置hosts的方式访问api.weixin.qq.com,以免微信侧后续更新出口IP时出现接口访问失败。 特殊情况如配置防火墙等,请改造成通过接口获取api.weixin.qq.com当前所有出口IP(同一时间不会所有IP都变更,请尝试可用IP),并更新至hosts或防火墙等。 获取api.weixin.qq.com当前所有接入IP请参考官方文档。 微信团队 2019.09.24
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019年,移动用户增速放缓、下沉市场释放增长红利、巨头APP流量优势凸显……商家寻找用户和流量出现,或许就在微信、抖音、百度和支付宝的小程序之中。 8月14日,《中国移动互联网2019半年大报告》显示,微信小程序500万以上月活跃用户的数量正在快速增长,由133个增至180个,超过100 万的达到883个;同时,小程序快速向中老年用户渗透,50岁以上用户增长了22%;此外,小程序的人均使用个数和支付人数分别有156%和109%的增 长,用户使用小程序的习惯正在不断加深。抢占用户流量,小程序已成为各行业商家必争的赛道。 商家使用小程序并不一定需要复杂的运营技巧,只要做好几项基础的引流工作,就能快速吸引线上、线下用户,提升销量。 1、符合搜索习惯的名称 微信搜索和“附近的小程序”是用户获取小程序的两个重要渠道,与来自扫码、分享、有朋友信任背书不同,通过搜索展示的小程序,越是符合用户搜索习惯、直白表达出商品和服务的特点的小程序名称就能吸引用户打开。特别是在“人无我有”阶段,微信搜索导流效果显著。 2、重视小程序页面标题 在新改版的微信中,即将上线“动态”功能,直接可以展示小程序商品、服务的页面。在这种像信息流一样的展示方式下,小程序页面标题越吸引人,就越能吸引用户打开自家的小程序。 3、实体店二维码海报引流 实体店的线下客流是小程序的重要流量池,既然没有额外人力、精力,商家也需要在门店、橱窗、收银台等显著位置放上带有小程序码的易拉宝、海报,并设置新用户优惠券、首单优惠等促销方式,引导用户进入小程序体验购物,从而在非营业时间也能产生订单。 小程序下载:https://www.sucaihuo.com/source/0-0-266-0-0-0 4、公众号菜单和图文 有公众号的商家应当做三件事情:1)小程序绑定公众号;2)在自定义菜单中设置小程序;3)图文消息中插入小程序,最好是以卡片的形式,更容易吸引点击。 5、宣传单页、卡片等物料 无论是线上电商还是线下实体店商家,都应当不放过每次接触用户的机会,来推广小程序。比如在宣传单页、配送车辆、实体会员卡、商品包装等上面印上小程序码,设置扫码优惠活动,吸引用户进入小程序。 6、小程序下拉菜单 下拉菜单是快速进入微信小程序的便捷方式,也是收藏常用小程序的重要入口,但并不是每个用户都熟悉。商家需要做的就是,通过发微信红包等奖励方式,引导用户将自家小程序放进“我的小程序”,这样可以有更高的概率让用户回到小程序。 7、建用户微信群 社交营销是小程序的最大优势,特别是对高频消费的快消品商家来说,创建用户微信群,并在用户群分享小程序活动,是最有效的活动营销方式之一。在微信群中,可以定时推出秒杀引流、拼团裂变、积分兑换福利等活动,培养用户小程序下单习惯。 据悉,微信官方正在基于多维度的小程序质量评估机制,给予优质小程序更多的展现入口。对商家而言,越早投入小程序运营、提供越优质的商品和服务,就越有可能获得更多的微信流量支持。小程序已成为企业最重要的获客渠道,目前来看,没有之一。
为了便于用户便捷使用App、网站、移动端网页、小程序的服务,微信提供不同的技术方案,便于开发者在不同终端平台的服务中接入微信登录。 通过这个教程,开发者可以了解平台提供的针对各终端平台的微信登录能力,并可以根据实际使用场景合理选择接入方式。 以下为几类型微信登录的功能说明: 类型授权域/接口 用户侧使用流程 接入流程 App 接入微信SDK,并调用snsapi_userinfo (1)在App内选择使用微信登录 (2)拉起微信客户端,打开用户授权页,完成登录授权(1)注册微信开放平台(open.weixin.qq.com)帐号,并完成开发者资质认证 (2)申请【App移动应用】并审核通过后可以使用,查看开发文档网站应用 snsapi_login (1)用户使用微信“扫一扫”,在PC端扫码 (2)客户端打开授权页,完成登录授权 (1)注册微信开放平台(open.weixin.qq.com)帐号,并完成开发者资质认证 (2)申请【网站应用】并审核通过后可以使用,查看开发文档 微信客户端内H5使用公众号的登录能力: snsapi_base snsapi_userinfosnsapi_base:静默授权 snsapi_userinfo: (1)用户在H5内点击登录,唤起授权弹窗 (2)用户侧完成登录授权(1)注册微信公众号,选择“服务号”类型,并完成微信认证 (2)在公众号管理后台设置回调域名 (3)接入微信登录能力,查看开发文档 小程序wx.login wx.getUserInfowx.login:静默授权,开发者可获取openid wx.getUserInfo: (1)用户在小程序内点击组件,唤起登录窗口(2)用户侧完成登录授权 (1)注册小程序 (2)接入微信登录功能,查看开发文档,查看登录流程设计指引 开发者在不同使用场景下接入微信登录,应该注册符合要求的帐号并使用对应的登录能力。 【常见问题】 Q1: 在调用微信登录时,出现了“此帐号并没有这些scope的权限,错误码:10005”,是什么原因? A:对于场景与帐号属性、能力项不对应时(如在移动端网页中使用网站应用的AppID调用登录能力),将会出现以下的错误提示:此帐号并没有这些scope的权限,错误码:10005 [图片] Q2:我的服务同时有App、官网、公众号、小程序,那我怎么打通用户数据? A:对于多平台的服务,若开发者希望能识别用户身份,例如:希望用户在小程序内也能查看到在App内购买的商品订单,则可以通过平台提供的UnionID机制来实现用户身份识别。
第三方服务商复用公众号资质生产的小程序如何注销?这些小程序没有自己的后台,不用了又只能放在那里霸占名额~公司主体的小程序名额不够哇~
产品经理已经成为互联网行业最吃香的职业之一,不过大多数人提到这个职业最先想到的还是微信、今日头条、淘宝、支付宝等C端产品,它们有海量用户,也创造了海量的产品经理岗位。而一个不容忽视的趋势是:以腾讯为代表的诸多互联网公司正在加重To B业务,未来To B行业的产品经理将扮演更重要的角色。 ToB行业产品经理工作与To C 行业产品经理有哪些区别?要面对哪些挑战?需要怎样的职业素养?以下是有赞美业产品总监功夫关于SaaS产品的心得分享。 有赞作为一个商家服务公司,主要面向零售商家提供SaaS产品,成立7年来已积累了有赞微商城、有赞零售、有赞美业、有赞餐饮、有赞小程序等SaaS软件产品,帮商家网上开店、网上营销、管理客户、获取订单。 功夫曾在房天下(搜房网)担任产品副总监,参与房产家居的O2O业务,后在阿里巴巴负责个性化推荐业务,有近6年的产品经历,无论在To C还是To B的产品,都有着丰富的经验。这次分享中涉及到有赞产品经理的工作理念、判断逻辑以及一些很具体的产品需求场景,对SaaS行业或者To B行业的产品经理来说都是很好的参考。 以下是功夫分享内容的整理:(采访与整理 :三节课产品学院行业研究员张成翼) 一. 为什么需要SaaS? SaaS的产生,从一开始就是为了追求效率,能够让“专业人做专业事”,进而提升企业经营效率,它的产生,本质上是技术在商家经营过程应用程度不断提升的一种体现。现有的SaaS形式,是在不断演化之后产生的结果。 接下来,我们以有赞美业的SaaS为例,来看看商家经营的解决方案演化过程—— 这是比较早期的阶段,商家是通过一个厚厚的本子来管理店里面的经营情况,甚至到现在还有很多商家是用这种解决方案。 2. IT化 然后,一部分追求效率的商家开始使用单机不联网的ERP去管理店里面的经营情况,相比原来数据不易丢失,也能省不少时间。与此同时,商家初步具备了事“后”分析的能力,比如美业商家经营里有个很重要的场景——预约。 通常的场景大概是这样的:顾客打电话给前台预约某个时间做服务,前台打开系统录入后,告知技师客户姓名以及具体的服务时间和项目。店长在晚上复盘能看到今天每个时间段的客户预约量,接下来她可能会和老板商量要不要在明天采取一系列动作。 3. 互联网化 这是当前处在的阶段,美团用了7年的时间让商家开始接受了互联网的工具,知道怎么在线上找流量进而引导进店。商家开始使用微信公众号和小程序客服功能和他的顾客进行互动,从顾客的角度上来讲体验变得更好了,因为不再是冷冰冰的买卖关系。 这个阶段解决方案创造的价值是,让商家具备了事“中”反应的能力,还是以刚才的预约为例:顾客打开公众号或小程序预约时,能看到商家门店里的动态流量,当她提交信息完成预约后,前台的预约看板里会以可视化的形式展示出来,与此同时,技师也会收到实时提醒。店长随时随地打开App能知道哪个房间、哪个技师处于空闲状态,进而决定是否开启一波限时促销的活动。 4. 智能化 这是不久的将来一定会发生的,当然前提条件是有了足够多数据和信息的积累,比如说颗粒度足够丰富的“活”数据、平台间的数据可以实时共享等等。不过,到那个时候商家一定能具备事“前”分析的能力,比如,一个新顾客进店后,装在店里面的人脸识别系统会调取出这个顾客在其他店的消费记录,以及别的商家给她打上的标签,营销顾问再基于这些信息做针对性的营销。 在不同的阶段,其实提供的也是不同的产品,在最早的阶段,你只要提供一个账本就可以,而到第二个阶段,用友、金蝶等产品是主流;第三个阶段,也是当前,商家开始逐步接受使用互联网产品,这也是为什么SaaS开始在这个阶段兴起。 所以,SaaS的产生,其实是技术在商家经营过程应用程度不断提升的一种体现。而从中长期来看,SaaS针对其所服务的行业,是有可能提供领先于行业的解决方案,为整个行业赋能。 虽然我们还无法得知它未来终极的产品形态究竟如何,不过,从目前阿里、有赞等公司开始尝试的实践当中来看,基于数据智能和网络智能提供的服务将是重要的趋势,这也是曾鸣教授在《智能商业》一书中,为我们描绘的图景。 二. 对于产品经理来说,To C与To B的工作有什么区别?或者说,提出了怎样的挑战? 首先,从事SaaS的产品经理本质上仍旧是产品经理。换言之,对于大多数产品经理都适用的工作方法和评判标准,对于SaaS产品经理来说,也是适用的。 比如说,对于SaaS产品经理,或者借用现在一个时髦的概念,从事产业互联网的产品经理与消费互联网的产品经理相比,其工作基本思路与流程并无二致,都是基于场景进行设计。其核心思考的思路都是“用户,场景,需求”,大家都需要写PRD,用AXURE画原型,这一点是通用的。 但是,具体到SaaS的场景当中去,有许多SaaS产品中遇到的典型问题,在To C场景中是前所未有的,比如说: To C的产品经理,天然是用户,天然就能更好地理解用户的需求,To B的产品经理,大多数情况下不是用户,毕竟,对于大多数产品经理来说,很少会有自己开设一家企业,并经营3-5年的经历。这就意味着,To B的产品经理大多数情况下,并不真正了解用户,就更难提理解了; To C的产品,决策链通常是『点』状的,产品可能某个单点功能足够优秀,就能够获得用户,而To B的产品,决策链更多情况下是『环』状的,员工,老板之间可能各有诉求,二者的诉求有时甚至可能是冲突的,这时候,产品需要切实的解决用户问题,创造价值,用户才有可能真正的buy in; To C的产品,试错成本很低,很多时候,如果一个功能效果不好很容易就可以回滚,但是在To B的场景下,这是不允许的,一个功能一旦上线,商家在上面形成业务数据,就几乎不可能回滚了。这就意味着,SaaS产品经理在做产品设计时,前期需要扎扎实实的产品功能想清楚。 这几点差异的背后,以及很多时候我们提到的关于To B与To C的不同,究其根本,很重要的原因是在To B的场景下,产品经理们对于他们所服务的用户的相关信息输入是不够的。 对于To B,或者说SaaS来说,很多行业中通用的解决方案和思考方式,都是隐性知识,是需要你在行业中长期实践才能了解到,否则,很多只是在逻辑上或者理念上合理的解决方案,实践中就会遇到很多挑战。 也因此,对于To B产品的产品经理来说,不仅需要互联网产品的基本设计方法,还需要对其所服务行业有足够深刻的认知。他们不仅仅要关注用户体验,更多的时候,是要关注用户价值,关注在这个行业中究竟应该如何高效解决问题。 如果做不到这一点,简单的将过去在To C领域使用的方法直接套用到在To B领域,将会面临着很大的挑战。这也是刚刚转行To B的产品经理,时常会遇到的挑战。 对于有赞而言也是如此,过去在C端产品非常优秀的产品经理,在进入有赞之后,可能两三个月都没法很好的进入状态。对于他们来说,很多过去的工作方法和理念都需要抛弃,从更多的角度理解产品设计这件事,掌握更多关于SaaS产品的设计方法和理念,才能够更好的适应这个环境和过程。 也是在这个过程中,有赞内部也实践出一套方法论,帮助产品经理在面对商家,这个有些特殊的用户群体时,能够更快的进入状态,产出更高效的解决方案。 接下来,我们就借助有赞在发展过程中的一些实际案例,聊一聊有赞的产品设计方法。 三. to B产品如何进行基于业务场景的设计? 正如我们之前所说,基于场景的产品设计这一基本思路,无论是在C端还是在B端,都是适用的。 不过,有赞在实践过程中发现,在面向企业用户进行产品设计时,需要注意和回答的很多问题,是与C端不同的。 简单来看,是要依次回答这样三个问题 —— 1.业务场景究竟是什么? 如何定义业务场景?这是进行产品设计时第一个回答问题。 在定义场景时,不仅仅是简单的说一句用户在什么情况下使用这个功能,这样的描述还是太简单,对于业务场景的描述需要更有颗粒度,“我要把这个产品给你描述完,立马你就有画面感的,才叫场景“。 怎样才是有画面感的场景描述呐呢?关键在于,能够包含七个要素,分别是—— 用户、环境、动机、目标、介质,交互,动作。 只有包含这七个要素,才能够叫做有“画面感”的场景描述。你对业务场景的理解,能否到达这个颗粒度,会对你的产品设计结果带来很大差异。 在有赞内部,发生过很多次类似的事情,产品经理们因为对于业务场景理解的不同,会提出完全不同的解决方案。 比如说,现在让你给有赞设计一个店铺装修功能,你会如何做? 通用的解决方案可能是: 给商家做好基础功能,商家在后台可以自由的搭配界面,拼装颜色,调整图片大小尺寸。他能够把有赞店铺像装修淘宝店铺,京东店铺一样特别好看,这样,无论什么时候来,不同的店铺都能够有不同的主题。 这个方案确实是有效,他也确实是”店铺装修“,但它真的是最适合用户的解决方案吗? 要回答这个问题,仍旧应该是回到原点——清晰描述客户的业务场景。 这个过程,其实就是思考什么样的用户需要这个功能?什么时候需要这个功能?他想要这个功能解决什么问题,实现怎样的效果?通过怎样的方式解决这个问题?为了解决这个问题他需要使用怎样的介质?这个过程中要执行怎样的动作? 回到店铺装修这个问题上,对于有赞的商家来说,他们使用店铺装修功能的场景可能是,头部的20个品牌商家,在春节期间,为了决定突出品牌调性,获得更多的用户认可,同时为了增强跟顾客之间的这种互动,提高大家对他的好感度,所以他决定在春节期间上线一些新的活动。同时把它整个的页面设计的更符合春节的这种气息一些。于是它在后台里面选择了一套模板,并安排美工,设计一些符合这种气息的这种图片上传到后台里面,来达到这样的效果。 在这个回答当中,大致将刚刚提到的七个要素体现出来。这时,我们会发现,其实特别自由的店铺装修功能可能并不最合适,节庆期间的活动,如果店铺装修太过复杂,只会让商家感到反感。他们需要把大量的时间花在店铺装修上,针对于时间就是金钱的大促来说并不值当。而且,这样繁复的装修,在日常使用当中却又使用不到。 最终,这个功能的方案是,只设置针对不同节庆的基础模板,商家可以自己上传图片,符合节庆的氛围。但是,店铺的具体样式并不能调整。 当然,具体的功能设计,当然还有很多考量,我们这里只是简单的讨论,业务场景理解对于产品设计的价值。 2.产品价值是什么? 对产品的业务场景有了基本的理解之后,第二步可能需要理解一下,你的产品或者功能,对于用户的价值究竟是什么? 一般而言,对于To B类的产品,用户价值这件事无非两点 : 商家的经营效率是否提高? 商家的经营效果是否提高? 用户使用你的产品,工作效率是不是提高了、是不是能够赚到更多的钱,这两条是评判SaaS产品的核心标准。 如果在这两条中,一条都不占,那么,你这个需求可能就没法上了。 有赞内部之前经常有类似的需求,对于他们自己来说是有价值的,但对于商家而言,可能并不能提供价值,这时,也是考验产品经理的产品设计水平的时刻。 还是以有赞为例。 有赞运营曾向产品提出过这样一个需求,希望在商家注册或者使用付费功能时,能够填写自己的经营类目。 比如说,你是一家水果店,那么就在注册时,填写自己的类目为零售。你是一家按摩店,就填写自己的类目为服务。 之所以需要增加这一步,是为了有赞能够对于自己服务的商家有更全面的了解,之后能够提供更为优质的服务。对于商家来说,凭空在注册流程中增加这一步,短期之内他们的经营效率没有太大的帮助,而且,反而会增加操作成本。 但是,另一方面,这个需求也确实对于有赞有价值,所以也不可能直接把运营的需求拒绝掉。 这个时候,就需要产品的同学能够提出更有效的解决方案。 最后采取的方案是,让内部的数据同学,把同一商家ID下面的商品名称数据进行分析,进而得出商家的类目。 商家在使用有赞SaaS时,一定会录入商品名称,比如说水果店就会有苹果,香蕉,哈密瓜等商品名,便利店可能就会有各种饮料,零食等商品。这些商品的名称基本都是独有的。 数据同学可以在后台抽样选择20个商品,看看商品名称都落在哪个类目,进而推断商家所处的类目,这样,不仅能够得出不同商家的经营类目,而且从结果上看,反而比商家自己选择自己的经营类目更加精准一些,对于运营的同学更有价值。 而这样的案例,其实对于有赞来说时常发生。对于SaaS产品,很多功能或许是需要做的,但是,究竟是否可以做,则需要反复思考,这个功能是不是真正能够为用户提升价值,如果不能,我们倾向于不要做,这也是在SaaS产品中第二个关键的问题。 3.如何判断功能的优先级? 最后,当你开始有大量的需求之后,应该如何判断产品功能优先级? 针对这个问题的解决方案,无论是在C端还是在SaaS,判断准则都差异不大。 大致的判断标准基本都是公司战略重心、行业发展趋势、资源限制以及收入等要素来考虑。 关于如何进行宏观的考量,此处就不再过多赘述了。 四. 一个优秀SaaS产品经理的判断标准 对于产品经理而言,其实无论是在C端还是B端,能力要求都是近似的,大约都是这样一个框架—— 1. 首先是需求分析产生的能力。 我们对于业务场景的理解,对于产品价值的判断,这些前置性的思考,其实都是基于产品的理解和思考。这是一个基础能力,通过这种方法,形成产品需求; 2. 第二个就是产品设计的能力。 很多C端的产品经理,会将产品设计能力定义为UI设计能力,交互逻辑的设计能力以及一些流程或者细节的优化。坦率来说,我认为这个能力在SaaS解决方案里面是最不重要的。对于SaaS而言,最重要的是能够提供可用的解决方案,产业互联网的第一优先级就是可用。没有做到可用,但是足够好看,是无法解决本质问题的。所以,这里的产品设计能力,特指的是能够针对所服务的行业提出可行的解决方案的能力; 3. 第三个是项目跟进的能力, 就是你是否有能力主导整个项目,它能够如期按时交付,能够组织大家所有的参与者按照设定的目标推进并最终实现它,甚至是提前上线等等; 4. 最后一个就是对行业的敏感能力, 这是一个很重要的能力点,为什么说它重要?因为对于前三个能力来说,其实都有清晰的方法论,是可以训练,可以交付的,包括三节课正在开设的课程都是在解决这个问题。 但是,行业敏感度很难进行批量的训练,它有一个前提是你对这个行业要有足够的热爱,如果做不到这一点,后续都很困难。在热爱的基础上,可能需要你学习和训练一些信息获取的能力、分析调研的能力、以及全局的思考能力等等,通过对行业的掌握,最终获得这项能力。
早期实现方案 1. 方案实现 通过微信的appSecret获取小程序accessToken并缓存 微信小程序上很多操作都需要使用accessToken,比如用户授权手机号,当然也包括获取小程序码 通过微信提供的api获取到对应的小程序码,由于http接口直接返回的是图片本身,所以考虑将图片上传七牛服务器并获取图片链接,最后使用图片的链接来展示或保存小程序码 2. 方案优点 由于上传了小程序码,对于一些跳转固定页面和参数的码可以将图片链接存到数据库,以供用户下次分享使用,无需重复获取 3. 存在的问题 稳定性很差,获取小程序码的失败率比较高,甚至会出现一个时间段内完全获取不到码的情况 接口效率不好,由于每次都会存在图片上传,而且上传本身又比较耗时,导致服务器压力巨大且频繁出现慢接口,可能会影响到项目中的其他服务 改造后方案 1. 方案实现 获取小程序码后不再上传七牛,直接通过图片流的方式返回给前端 2. 方案优点 取消了图片的上传操作,接口效率大幅提升,提高了小程序码的获取成功率,也减轻了服务的压力 3. 存在的问题 依旧存在小程序码获取失败的情况 4. 问题排查 经排查日志发现是accessToken失效导致,缓存的accessToken失效时间远比微信规定的失效时间短,那究竟又是什么情况会导致accessToken失效呢?经讨论和实验发现以下三点: 我们微信的appSecret授权给第三方网站使用(比如阿拉丁),他们也有获取小程序码的服务,运营可以通过阿拉丁获取小程序码,这就会导致阿拉丁使用我们的appSecret获取accessToken,以至于我们缓存中的accessToken失效。 后端缓存中的accessToken存入和获取的逻辑存在缺陷,每当从缓存读取accessToken时,若缓存不命中,则通过微信api获取新的accessToken然后再存入缓存,这个逻辑容易导致缓存穿透,即当多个请求都没有命中缓存时,只有一个线程能通过微信api拿到新的accessToken,其他线程都拿不到。 当一个accessToken存在时间比较长时,手动调用微信api获取小程序码,会看到微信的api也会存在概率获取不到码的情况,但是一个全新生成的accessToken则不会有这种情况,至少在10分钟之内非常稳定。 最终的方案 1. 方案实现 通知运营不要再使用阿拉丁的生成小程序码的功能,若有这方面需求可以找技术帮忙获取。 缓存中的accessToken有效时间缩短至5分钟,保证每次使用的accessToken都能稳定获取小程序码。 修改accessToken的获取机制,由定时器来获取accessToken并更新缓存,定时器每4分钟执行一次,以确保每个请求都能命中缓存,若定时器出现异常,则回退之前的逻辑(请求没有命中缓存,通过微信api重新获取accessToken)。 这一个方案上线后,线上再也没有出现小程序码没有获取成功的情况,观察日志也没再出现获取失败的情况,目前已经两周保持100%成功率了。
为什么要做性能优化? 一切性能优化都是为了体验优化 1. 使用小程序时,是否会经常遇到如下问题? 打开是一直白屏 打开是loading态,转好几圈 我的页面点了怎么跳转这么慢? 我的列表怎么越滑越卡? 2. 我们优化的方向有哪些? 启动加载性能 3. 启动加载性能 1. 首次加载 你是否见过小程序首次加载时是这样的图? 这张图中的三种状态对应的都是什么呢? 小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:[代码]下载小程序代码包[代码]、[代码]加载小程序代码包[代码]、[代码]初始化小程序首页[代码]。下载到的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包。 2. 加载顺序 小程序加载的顺序是如何? 微信会在小程序启动前为小程序准备好通用的运行环境。这个运行环境包括几个供小程序使用的线程,并在其中完成小程序基础库的初始化,预先执行通用逻辑,尽可能做好小程序的启动准备。这样可以显著减少小程序的启动时间。 通过2,我们知道了,问题1中第一张图是[代码]资源准备[代码](代码包下载);第二张图是[代码]业务代码的注入以及落地页首次渲染[代码];第三张图是[代码]落地页数据请求时的loading态[代码](部分小程序存在) 3. 控制包大小 提升体验最直接的方法是控制小程序包的大小,这是最显而易见的 勾选开发者工具中“上传代码时,压缩代码”选项; 及时清理无用的代码和资源文件(包括无用的日志代码) 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限 从开发者的角度看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内。 4. 采用分包加载机制 根据业务场景,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载; 使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。 5 采用分包预加载技术 在4的基础上,当用户点击到子包的目录时,还是有一个代码包下载的过程,这会感觉到明显的卡顿,所以子包也不建议拆的太大,当然我们可以采用子包预加载技术,并不需要等到用户点击到子包页面后在下载子包,而是可以根据后期数据,做子包预加载,将用户在当先页可能点击的子包页面先加载,当用户点击后直接跳转; 这种基于配置的子包预加载技术,是可以根据用户网络类型来判断的,当用户处于网络条件好时才预加载;是灵活可控的 6. 采用独立分包技术 目前很多小程序[代码]主包+子包[代码](2M+6M)的方式,但是在做很多运营活动时,我们会发现活动(红包)是在子包里,但是运营、产品投放的落地页链接是子包链接,这是的用户在直达落地时,必须先下载主包内容(一般比较大),在下载子包内容(相对主包,较小),这使得在用户停留时间比较短的小程序场景中,用户体验不是很好,而且浪费了很大部分流量; 可以采用独立分包技术,区别于子包,和主包之间是无关的,在功能比较独立的子包里,使用户只需下载分包资源; 7. 首屏加载的优化建议 7.1 提前请求 异步请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据;当然,如果能在前置页面点击跳转时预请求当前页的核心异步请求,效果会更好; 7.2 利用缓存 利用storage API, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新,这不仅优化了性能,在无网环境下,用户也能很顺畅的使用到关键服务; 7.3 避免白屏 可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据–> 详情页),没有数据的模块可以进行骨架屏的占位,使用户不会等待的很焦虑,甚至走了; 7.4 及时反馈 及时的对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了,无响应 渲染性能优化 1. 小程序渲染原理 双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。 分析这个流程不难得知:页面初始化的时间大致由页面初始数据通信时间和初始渲染时间两部分构成。其中,数据通信的时间指数据从逻辑层开始组织数据到视图层完全接收完毕的时间,数据量小于64KB时总时长可以控制在30ms内。传输时间与数据量大体上呈现正相关关系,传输过大的数据将使这一时间显著增加。因而减少传输数据量是降低数据传输时间的有效方式。 2. 避免使用不当setData 在数据传输时,逻辑层会执行一次[代码]JSON.stringify[代码]来去除掉[代码]setData[代码]数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将[代码]setData[代码]所设置的数据字段与[代码]data[代码]合并,使开发者可以用[代码]this.data[代码]读取到变更后的数据。因此,为了提升数据更新的性能,开发者在执行[代码]setData[代码]调用时,最好遵循以下原则: 2.1 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用; 2.2 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用[代码]setData[代码]来设置这些数据; 2.3 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下 提升数据更新性能方式的代码示例 [代码]Page({ onShow: function() { // 不要频繁调用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 绝大多数时候可优化为 this.setData({ a: 1, b: 2 }) // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外 this.setData({ myData: { a: '这个字符串在WXML中用到了', b: '这个字符串未在WXML中用到,而且它很长…………………………' // 可以优化为 this.setData({ 'myData.a': '这个字符串在WXML中用到了' this._myData = { b: '这个字符串未在WXML中用到,而且它很长…………………………' 利用setData进行列表局部刷新 在一个列表中,有[代码]n[代码]条数据,采用上拉加载更多的方式,假如这个时候想对其中某一个数据进行点赞操作,还能及时看到点赞的效果 1、可以采用setData全局刷新,点赞完成之后,重新获取数据,再次进行全局重新渲染,这样做的优点是:方便,快捷!缺点是:用户体验极其不好,当用户刷量100多条数据后,重新渲染量大会出现空白期(没有渲染过来) 2、说到重点了,就是利用[代码]setData[代码]局部刷新 [代码]> a.将点赞的`id`传过去,知道点的是那一条数据, 将点赞的`id`传过去,知道点的是那一条数据 [代码]<view wx:if="{{!item.status}}" class="btn" data-id="{{index}}" bindtap="couponTap">立即领取</view> [代码]> b.重新获取数据,查找相对应id的那条数据的下标(`index`是不会改变的) > c.用setData进行局部刷新 [代码]this.setData({ list[index] = newList[index] 其实这个小操作对刚刚接触到微信小程序的人来说应该是不容易发现的,不理解setData还有这样的写法。 2.4 切勿在后台页面进行setData 在一些页面会进行一些操作,而到页面跳转后,代码逻辑还在执行,此时多个[代码]webview[代码]是共享一个js进程;后台的[代码]setData[代码]操作会抢占前台页面的渲染资源; 3. 用户事件使用不当 视图层将事件反馈给逻辑层时,同样需要一个通信过程,通信的方向是从视图层到逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。 1.去掉不必要的事件绑定(WXML中的[代码]bind[代码]和[代码]catch[代码]),从而减少通信的数据量和次数; 2.事件绑定时需要传输[代码]target[代码]和[代码]currentTarget[代码]的[代码]dataset[代码],因而不要在节点的[代码]data[代码]前缀属性中放置过大的数据。 4. 视图层渲染原理 4.1首次渲染 初始渲染发生在页面刚刚创建时。初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。 在这整个流程中,时间开销大体上与节点树中节点的总量成正比例关系。因而减少WXML中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。 简化WXML代码的例子 [代码]<view data-my-data="{{myData}}"> <!-- 这个 view 和下一行的 view 可以合并 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> <text> <!-- 这个 text 通常是没必要的 --> {{myText}} </text> </view> </view> <!-- 可以简化为 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> {{myText}} </view> 4.2 重渲染 初始渲染完毕后,视图层可以多次应用[代码]setData[代码]的数据。每次应用[代码]setData[代码]数据时,都会执行重渲染来更新界面。初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将[代码]data[代码]和[代码]setData[代码]数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将[代码]setData[代码]数据合并到[代码]data[代码]中,并用新节点树替换旧节点树,用于下一次重渲染。 在进行当前节点树与新节点树的比较时,会着重比较[代码]setData[代码]数据影响到的节点属性。因而,去掉不必要设置的数据、减少[代码]setData[代码]的数据量也有助于提升这一个步骤的性能。 5. 使用自定义组件 自定义组件的更新只在组件内部进行,不受页面其他不能分内容的影响;比如一些运营活动的定时模块可以单独抽出来,做成一个定时组件,定时组件的更新并不会影响页面上其他元素的更新;各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用。 6. 避免不当的使用onPageScroll 每一次事件监听都是一次视图到逻辑的通信过程,所以只在必要的时候监听pageSrcoll 小程序启动加载性能 控制代码包的大小 首屏体验(预请求,利用缓存,避免白屏,及时反馈 小程序渲染性能 避免不当的使用setData 合理利用事件通信 避免不当的使用onPageScroll 优化视图节点 使用自定义组件
该appid认证主体与特约商户主体不一致,怎么能一致
微信小程序的开发者们在发布小程序之前,需要给每个小程序起好一个名称,设置好logo、简介及类目信息,然后才能进行代码提审。 这些帐号的基础信息是用户对一个小程序的初步认知,平台希望这些信息能够给予用户准确预期每个小程序的功能和内容,也希望每个小程序的名称是具有独特性的,减少误导、混淆用户理解的可能性。 因此有以下几点名称规则调整及优化建议,希望开发者可以了解并遵循,后续可能会在名称审核、代码审核或已发布运营时,收到平台的审核通知或建议修改通知。 l 微信小程序的名称建议是品牌、商标或具有辨识度的短词 在微信小程序发布后,用户会通过搜索、传播等场景使用小程序的功能和服务。选择品牌、商标或具有辨识度的短词作为小程序的名称,可以帮助用户快速建立与小程序的记忆与关系。 名称为“印象笔记”会比“笔记”能更好的让用户记忆和理解,并使用“印象”品牌词进行搜索或传播。 [图片] l 不能直接使用功能性描述、广义归纳类的词汇作为名称 功能性描述的词汇虽然可传达出小程序所要提供的相关功能,但直接使用此类词汇,缺乏显著性且不具有识别、区分小程序的作用。 如名称为“牙科”的帐号,用户能直接理解小程序的内容可能与“牙科”相关,但对这个小程序帐号是没有建立认知和关系的。 [图片] l 不能同时使用地域性词汇与广义归纳类词汇进行命名 使用“地域+广义归纳类”词汇进行命名,同样存在指向性过于宽泛的问题。 如以下案例, 名称包含的地域词:“四川”、归纳类词:“系统门窗”,并不会让用户对帐号的功能产生有效的预期。以下示例是不允许的: [图片] 但在政务民生类服务场景,“广州地铁”、“广州公积金”可以被用户理解并接受属于政府或相关机构提供的服务和资讯,因此“地域+广义归纳类”词汇可被政府民生场景使用。 l 不建议使用具有营销属性的词汇进行命名 营销属性词汇如:免费、促销、清仓、打折等,鉴于活动规则普遍附有限制条件,在名称中使用,通常带有过于夸大的表达效果,并非是小程序的核心功能和用户的全部认知,存在误导的可能性。以下示例是不允许的: [图片] l 商品销售类小程序,需要包含品牌名称,不直接使用产品分类进行命名 商品销售类小程序,若仅使用产品分类进行命名,无法建立易于记忆、易于传播的品牌形象。如名称为“女鞋男鞋女包批发生产”,用户并不会因“女鞋”“男鞋”“女包”就会进行选择,反而“品牌+女装”才是用户真正需要的。以下示例是不允许的。 [图片] l 不在名称中堆砌热门搜索关键词 名称中堆砌多个关键词,过于冗长的热门搜索关键词堆砌名称无法建立品牌记忆,目的普遍是在搜索结果上获得更多的曝光。以下示例是不允许的: [图片] 结语 此次名称规则的调整和优化建议,平台希望开发者们能相应地调整自己的小程序名称。如未满足名称规范,平台会在后续的小程序名称设置、名称审核、代码审核流程进行规则提示和修改要求。并会在搜索场景优化需求基础上,对无辨识度的帐号名称进行搜索策略调整。 微信小程序完整的名称规则可具体参照《微信小程序平台运营规范》2. 基本信息规范,除以上规则与示例之外,当然还包括了不得使用违法违规的名称、不得使用侵权他人的品牌词进行命名、在命中敏感类目如医疗词时提交主体资质等规则。 平台希望小程序名称的优化调整,可以使得每个被开发者精心创造出来的小程序都能让用户在使用时、搜索时、传播时逐渐接受并建立对产品品牌的认知。
微信小程序的开发者们在发布小程序之前,需要给每个小程序起好一个名称,设置好logo、简介及类目信息,然后才能进行代码提审。 这些帐号的基础信息是用户对一个小程序的初步认知,平台希望这些信息能够给予用户准确预期每个小程序的功能和内容,也希望每个小程序的名称是具有独特性的,减少误导、混淆用户理解的可能性。 因此有以下几点名称规则调整及优化建议,希望开发者可以了解并遵循,后续可能会在名称审核、代码审核或已发布运营时,收到平台的审核通知或建议修改通知。 l 微信小程序的名称建议是品牌、商标或具有辨识度的短词 在微信小程序发布后,用户会通过搜索、传播等场景使用小程序的功能和服务。选择品牌、商标或具有辨识度的短词作为小程序的名称,可以帮助用户快速建立与小程序的记忆与关系。 名称为“印象笔记”会比“笔记”能更好的让用户记忆和理解,并使用“印象”品牌词进行搜索或传播。 [图片] l 不能直接使用功能性描述、广义归纳类的词汇作为名称 功能性描述的词汇虽然可传达出小程序所要提供的相关功能,但直接使用此类词汇,缺乏显著性且不具有识别、区分小程序的作用。 如名称为“牙科”的帐号,用户能直接理解小程序的内容可能与“牙科”相关,但对这个小程序帐号是没有建立认知和关系的。 [图片] l 不能同时使用地域性词汇与广义归纳类词汇进行命名 使用“地域+广义归纳类”词汇进行命名,同样存在指向性过于宽泛的问题。 如以下案例, 名称包含的地域词:“四川”、归纳类词:“系统门窗”,并不会让用户对帐号的功能产生有效的预期。以下示例是不允许的: [图片] 但在政务民生类服务场景,“广州地铁”、“广州公积金”可以被用户理解并接受属于政府或相关机构提供的服务和资讯,因此“地域+广义归纳类”词汇可被政府民生场景使用。 l 不建议使用具有营销属性的词汇进行命名 营销属性词汇如:免费、促销、清仓、打折等,鉴于活动规则普遍附有限制条件,在名称中使用,通常带有过于夸大的表达效果,并非是小程序的核心功能和用户的全部认知,存在误导的可能性。以下示例是不允许的: [图片] l 商品销售类小程序,需要包含品牌名称,不直接使用产品分类进行命名 商品销售类小程序,若仅使用产品分类进行命名,无法建立易于记忆、易于传播的品牌形象。如名称为“女鞋男鞋女包批发生产”,用户并不会因“女鞋”“男鞋”“女包”就会进行选择,反而“品牌+女装”才是用户真正需要的。以下示例是不允许的。 [图片] l 不在名称中堆砌热门搜索关键词 名称中堆砌多个关键词,过于冗长的热门搜索关键词堆砌名称无法建立品牌记忆,目的普遍是在搜索结果上获得更多的曝光。以下示例是不允许的: [图片] 结语 此次名称规则的调整和优化建议,平台希望开发者们能相应地调整自己的小程序名称。如未满足名称规范,平台会在后续的小程序名称设置、名称审核、代码审核流程进行规则提示和修改要求。并会在搜索场景优化需求基础上,对无辨识度的帐号名称进行搜索策略调整。 微信小程序完整的名称规则可具体参照《微信小程序平台运营规范》2. 基本信息规范,除以上规则与示例之外,当然还包括了不得使用违法违规的名称、不得使用侵权他人的品牌词进行命名、在命中敏感类目如医疗词时提交主体资质等规则。 平台希望小程序名称的优化调整,可以使得每个被开发者精心创造出来的小程序都能让用户在使用时、搜索时、传播时逐渐接受并建立对产品品牌的认知。
用vue脚手架开发微信分享功能,我在main.js封装了一个方法shareList [图片] 然后在组件的生命周期函数create()执行,这个shareList方法 [图片] 但在开发者工具中,却提示 updateAppMessageShareData is not defined [图片]
各位微信开放社区的用户: 大家下午好。 社区从上线到现在,我们看到了很多积极、热心的开发同学、运营同学在社区帮我们回答问题、分享一些经验,成为了“社区网红”,我们希望能看见更多的网红出现。因此,为了共同营造社区良好的问答、分享氛围,自2018.11.01起,我们会开始以月为时间单位,记录各位用户(个人/企业)在社区的贡献,并评选出社区每月突出贡献者。 激励措施 当选社区突出贡献者的用户,将有机会获得以下奖励: (1) 小程序极速审核通道,开发者参与开发的小程序将可享受一个月快速审核的奖励。 (2) 官方沟通群,我们希望及时听见你的反馈和建议 (3) 微信周边礼品,如 WeStore 礼品、微信公开课Pro门票 评选准则时间:每个自然月的贡献统计,下个月将清零重新计算。维度: 1.加分项,例如: 1)在社区的回答、经验分享、案例分享获得的点赞 2)高质量的回答被置顶 3)在社区内发表文章,并被评为“精选” 2.减分项,例如: 1)广告、重复问题刷屏、与社区无关帖子(如诗词等)将会被删除,会扣除相应的分数 如果帖子被删帖,该个人/企业用户将无法参与本月突出贡献者评选,请大家在社区不要提供无效的内容。 2)不被认可的回答,将会扣除相应的分数 除每月评选社区突出贡献者之外,我们将组织更多的活动,提供更多给大家展示的舞台。 我们希望看见各位个人/企业用户在社区的智慧和力量,和我们一同打造充满热情的微信开放社区! 关于社区的任何想法,欢迎大家留言给我们。 微信团队 2018.10.24
服饰类的海外主体小程序,选择商家自营售卖产品, 是否可以调用微信支付实现跨境支付? 谢谢~~ 补充下问题,从微信支付接口开发文档里面看来,境外商户的支付产品是不支持小程序的↓ 除了使用第三方支付之外,是否还有什么办法在海外主体小程序里调起微信支付呢? [图片]
为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范。本公告所称“帐号登录功能”是指开发者在小程序内提供帐号登录功能,包括但不限于进行的手机号登录,getuserinfo形式登录、邮箱登录等形式。具体规范要求如下: 1.服务范围开放的小程序 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 包括但不限于打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,都属于违反上述要求的情况; 以下反面示例,在用户打开小程序后立刻弹出授权登录页; [图片] 建议修改为如下正面示例形式:在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项。 [图片] 2.服务范围特定的小程序 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序; 下图案例为正面示例:校友管理系统,符合规范要求。 [图片] 3.仅提供注册功能小程序 对于线上仅提供注册功能,其他服务均需以其他方式提供的小程序,可在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或帐号登录。如ETC注册申请、信用卡申请; 如下反面示例,用户在进入时未获取任何信息,首页直接强制弹框要求登录注册ETC,这是不符合规范的。 [图片] 建议修改为如下正面示例所示形式:允许在首页说明注册功能后,提供登录或注册按钮供用户主动选择点击登录。 [图片] 4.提供可取消或拒绝登录选项 任何小程序调用帐号登录功能,应当为用户清晰提供可取消或拒绝的选项按钮,不得以任何方式强制用户进行帐号登录。 如下图所示反面示例,到需要登录环节直接跳转登录页面,用户只能选择点击登录或退出小程序,这不符合登录规范要求。 [图片] 建议修改为下图正面示例形式,在需帐号登录的环节,为用户主动点击登录,并提供可取消按钮,不强制登录。 [图片] 针对以上登录规范要求,平台希望开发者们能相应地调整小程序的帐号登录功能。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。
生产环境推荐使用阿里云服务器,阿里云限时优惠券领取 最近更新时间:2019-03-09 原文链接:https://nideshop.com/ 本文档为开源微信小程序商城 NideShop 项目的安装部署教程,欢迎 star NideShop 商城 api 服务端:https://github.com/tumobi/nideshop NideShop 微信小程序客户端: https://github.com/tumobi/nideshop-mini-program NideShop 后台管理客户端: https://github.com/tumobi/nideshop-admin 一、购买新浪云SAE 为什么选择SAE?免费二级域名和支持https访问,不用备案,可用做微信小程序服务器。 SAE推荐链接:http://sae.sina.com.cn/ 选择对应的部署环境 开发言语:容器虚拟机 操作系统:centos 操作系统版本:7.6.1810 单实例配置:高级 II 型 (不建议选择基础型,因为配置低可能会导致安装失败 ) 二级域名:testnideshop(不可以重复,最终域名为:testnideshop.applinzi.com) 应用名称:测试NideShop (这里可随便填写) 文中出现 testnideshop.applinzi.com 的地方,请替换为你配置的二级域名 二、使用 Web 终端登录云容器 找到新创建的容器,直接点击下图位置中的 终端 使用 [代码]Web终端[代码] 登录。如果需要打开多个 [代码]Web终端[代码] ,则多次点击 终端。 三、安装基本软件 [代码]yum update -y yum install -y epel-release yum install -y curl vim net-tools git 四、通过 nvm 安装 Node.js 安装 nvm [代码]curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash 创建文件 .bashrc 并追加内容 [代码]cat>>.bashrc<<EOF export NVM_DIR="/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 使用 .bashrc 生效 [代码]source /.bashrc 安装 Node.js [代码]nvm install v10 node -v 五、开启共享型MySQL并导入数据 下载项目 nideshop.sql 文件 访问源码地址:https://github.com/tumobi/nideshop,根据下图下载源码,解压。 开启共享型 MySQL 数据库 直接点击 服务开关 开启共享型 MySQL 导入数据库 创建导入任务,并选择源码目录下的 nideshop.sql 文件 查看导入结果 导入任务需要一定的执行时间,直到状态为 执行成功 六、部署 NideShop 小程序商城 安装 ThinkJS [代码]npm install -g think-cli thinkjs --version 下载 NideShop 的源码 [代码]mkdir /var/www cd /var/www git clone https://github.com/tumobi/nideshop [代码]cd /var/www/nideshop npm install 替换数据库配置 直接复制以下内容执行 [代码]cat>src/common/config/database.js<<EOF const mysql = require('think-model-mysql'); module.exports = { handle: mysql, database: 'app_' + process.env.APPNAME, prefix: 'nideshop_', encoding: 'utf8mb4', host: process.env.MYSQL_HOST, port: process.env.MYSQL_PORT, user: process.env.ACCESSKEY, password: process.env.SECRETKEY, dateStrings: true 修改 NideShop 运行的主机和端口 更改默认监听的主机 [代码]sed -i "N;2ahost:'0.0.0.0'," src/common/config/config.js 更改运行的端口 [代码]sed -i "N;2aport:5050," src/common/config/config.js [代码]npm run compile 通过 pm2 进行线上部署 安装 pm2 [代码]npm install -g pm2 启动 pm2 [代码]pm2 startOrReload pm2.json 浏览器访问测试 https://testnideshop.applinzi.com/ 注意:需要把 testnideshop 替换为你创建时填写的二级域名 七 修改NideShop微信小程序的配置 微信小程序商城GitHub: https://github.com/tumobi/nideshop-mini-program 打开文件config/api.js,修改ApiRootUrl为自己的域名,注意 https 和后面的 api/ 不能少 [代码]const ApiRootUrl = 'https://testnideshop.applinzi.com/api/'; 八 微信小程序端运行效果图 如使用的是阿里云服务器,请参考另一篇文章:CentOS 7.3 下部署基于 Node.js的微信小程序商城
[图片]最后一步了,打款过去[图片],说这个账户太长了,这怎么汇款给腾讯啊;25位的账户,后面10位加上去是不是就超过了最大长度啊
刚才发的截图时发生错位,现在重新发 https://developers.weixin.qq.com/s/hzVM3BmU7Paz 以后有时间会出一整套原生的框架,开源 开源 开源,希望大神指点一二
因为官方的 slider 只有横向的,所以按照官方的slider写了个纵向的,用 wxs 处理的事件(出了这么久了,却没有怎么用过。。)。代码片段如下 https://developers.weixin.qq.com/s/qYLgcDmC7Air 没什么难点,本来想着几行就写完了。。意料之外的多了些代码。。主要有几点想吐槽的 wxs 里的getState是用来存公共临时变量的。为什么不直接在 wxs里定义一个变量呢?因为如果有多个组件的话,这些变量会共享。另外还要注意下面的情况 [代码] var state = getState() state.value = 1 // 正确 state = {value: 1} // 错误 点击事件是加到线条上的,线条本身太窄了,加了padding扩大点。另外,头尾两个位置应该是用户最常用的点击,但是却不怎么好点到,甚至根本点不到,所以在两头各加了2个view来直接设置最大最小值。 想点着圆点滑动的时候,偶尔会点到滚动条上,结果造成页面滑动,所以在最外面加了 touchmove 绑定了空方法。 为什么这个组件需要有 show-value 的配置。。感觉特别鸡肋。。 早先发到群里的那一版不太好,没优化,而且value的设置忘了减去 min了。。哈哈哈。。抱歉。。
为了能让C端用户更快速直接了解小程序的功能及内容,以及基于部分行业的法律法规、平台管理的需要,小程序提供的服务需要与小程序所选的类目一致。 一、类目的选择 (1)类目根据主体的不同,开放范围分为个人、组织、境外。 (2)基于法律法规、平台管理的需要,部分类目需要提交相关的资质并经过审核后,才能申请成功。无资质要求的类目选择后即刻生效。 (3)建议开发者根据小程序自身服务内容对照类目适用范围申请相应的类目,以避免后期出现提交代码审核/小程序上架后出现类目不符等问题被驳回或按违规处理。 (4)小程序类目对应资质、部分示例模板等内容详情可参考:点击查看。 二、小程序类目配置指引 (1)初始化类目配置介绍: 登录【微信公众平台】 ->【点击立即注册】 ->【选择帐号类型:小程序进入注册详情页】 ->【填写帐号信息,点击注册】 ->【点击登录邮箱,进行账号激活】 ->【激活后进行用户信息登记】 ->【小程序后台配置】 ->【小程序信息点击填写(须填写名称、头像、介绍及类目选择)】 ->【提交】 (2)小程序类目后台配置介绍: 登录【微信公众平台】 ->【小程序信息查看详情】 ->【服务类目点击详情】 ->【选择添加服务类目并上传类目资质】 ->【提交】 三、提前准备小程序类目资质 小程序不同类目对应的资质不同,建议开发者可根据微信开放的“小程序开放的服务类目”表查看对应资质,并提前做好申请相关资质准备,且资质需在有效期内,避免提交类目审核时因资质不符或过期等情况被驳回。 四、类目审核常见问题 1.一个小程序可选 5个 服务类目,且一个月有 5次 修改类目机会; 2.为满足部分开发者业务拓展的诉求,小程序类目已达5个且满足一定条件的帐号可以在后台申请【更多类目】。 更多类目申请条件(同时满足): (1)小程序的5个类目中至少有1个资质类目。类目资质可见开放的服务类目表; (2)小程序近7天每天访问人数100人及以上; (3)小程序近3个月无违规行为; (4)本月仍有添加服务类目的次数。 申请方法:登录【微信公众平台小程序】 ->【设置】 ->【基本设置】 ->【服务类目】 ->【详情】 ->【申请更多类目】 3.类目审核时长:一般情况为1 - 7天,如出现审核量大、内容复杂等特殊情况可能延长;如有紧急申请需求,且有加急额度情况下,在选择需提交资质的类目时,可同时勾选【审核加急】按钮,详情参考:【小程序类目审核加急通道开放】 4.部分常见驳回原因: 情况1: 提交资质主体与小程序注册主体不一致 案例:小程序(主体为123)申请【医疗-私立医疗机构】类目,提交资质主体为345,即为主体不一致 建议:以有资质的主体申请小程序 或 用小程序主体申请相关资质 情况2:提交资质已过期 案例:小程序申请【医疗-私立医疗机构】类目,提交《医疗执业许可证》,许可有效期为2016.09.12,即为过期 建议:提交许可资质在有效期内且有效期大于3个月及以上 情况3:提交资质模糊 案例:小程序申请【社交-社区/论坛】类目,提交的《非经营性互联网信息服务备案核准》资质模糊,审核无法辨别 建议:请确保提交资质清晰,可供审核辨别 情况4:提交虚假资质 案例:小程序(主体为123)申请【社交-社区/论坛】类目,提交主体一致的《非经营性互联网信息服务备案核准》资质,审核人员经可信渠道核实其资质主体为456,将判断小程序提交虚假资质 建议:以有资质的主体申请小程序 或 用小程序主体申请相关资质 温馨提醒: 审核中如发现小程序提交虚假资质,平台将视违规情节采取限制、甚至停止提供相关功能服务等措施,如涉及违法行为,可能追究其民事、刑事等相关法律责任,请各位开发者勿在“危险”的边缘试探哦~
我们在搭建小程序第三方平台过程发现官方提供的有很多接口,但不是每个接口都需要开发者用代码去实现。比如代码包管理的删除接口,就可以通过登录open.weixin.qq.com里,在列表看着更加详细的代码包版本介绍通过官方提供的界面就可以删除。即如果不是特殊需要,此接口可以不做开发。 那么我们搭建一个小程序服务商第三方平台需要哪些必要的功能模块呢? 必备功能模块概况 首先用一张图总概括下搭建一个mvp小程序服务商第三方平台必备的开发功能模块 第三方平台权限集 先用一张图表明第三方平台账号在授权后可用的权限集的部分。 只有平台先申请某一个权限,经过审核且全网发布后,旗下小程序才能在授权时选择某权限。有以下等 A>=B, B>=C;也就是第三方平台即使有每个微信提供的每个权限集,通过管理员授权后也不一定有全部权权限。原因: 1 单一授权被授权给其他第三方平台账号,或管理员授权时选择不把某一个权限授权给平台。比如开发和代码管理权限集 2 旗下小程序自身主体资质性质自带,不能拥有某个api权限。案例参考3 3 最终授权成功的权限集里,每个权限下都有多个api,所以在用某个api前需要看是由有权限。比如开发和代码管理权限集下,setwebviewdomain这个api不支持个人主体资质的appid调用 4 授权成功后,用token就可以用第三方平台提供的api代调用实现业务,或代调用小程序开发文档的api(标志参数需要access_token)里的接口,来实现产品开发。 授权及回调域名验证 ####### 说明 ####### 发起域名必须要在合法登记的域名名单内,且用户授权同意后的跳转url必须要在资料登记。体现在第三方平台创建时的以下两项配置中。 ① 授权发起页域名 ② 授权事件接收URL 授权回调URL源码 [代码] public void ProcessRequest(HttpContext context) Request = context.Request; Response = context.Response; string signature = Request.QueryString["signature"]; string msg_signature = Request.QueryString["msg_signature"]; string timestamp = Request.QueryString["timestamp"]; string nonce = Request.QueryString["nonce"]; #region Response body // 微信并不会因为返回失败就再发送一次消息,相反如果不返回success, 微信会延迟推送 string ResMsg = "success"; if (Request.HttpMethod == "POST") using (Stream stream = Request.InputStream) Byte[] postBytes = new Byte[stream.Length]; stream.Read(postBytes, 0, (Int32)stream.Length); string postString = Encoding.UTF8.GetString(postBytes); if (postString.Contains("<AppId>")) XElement xdoc = XElement.Parse(postString); CurAppID = xdoc.Element("AppId").Value.Trim(); LoadAppInfo(CurAppID); string postStringXmlSrc = string.Empty; Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(CurAppToken, CurAppEncodingAESKey, CurAppID); int ret = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, postString, ref postStringXmlSrc); if (ret == 0) xdoc = XElement.Parse(postStringXmlSrc); if (xdoc != null) string InfoType = xdoc.Element("InfoType").Value.Trim(); if (InfoType == "component_verify_ticket") string componentVerifyTicket = xdoc.Element("ComponentVerifyTicket").Value.Trim(); WeixinDataHelper.UpdateComponentVerifyTicket(CurAppID, componentVerifyTicket); else if (InfoType == "unauthorized") string authorizedAppId = xdoc.Element("AuthorizerAppid").Value.Trim(); WeixinDataHelper.Unauthorized(CurAppID, authorizedAppId); // 微信平台上填写的授权URL 目前就支持这两种 InfoType else if (Request.HttpMethod == "GET") ResMsg = Request.QueryString["echostr"]; ResponseEnd(ResMsg); #endregion 各种票据有效性维护机制 1 授权后得到授权码(authorization_code ) [代码]需要用授权码去调用接口换取令牌,并保存。 2 获取令牌和刷新令牌 [代码]用授权码获得令牌authorizer_access_token和刷新令牌authorizer_refresh_token。 需要保存令牌和刷新令牌。 3 刷新令牌authorizer_access_token [代码]用刷新令牌定期去微信网关拉取令牌,维持令牌的有效性,保证后期代实现接口时令牌有效性。约1h左右的时间去刷新一次令牌。刷新令牌服务需要有重试机制,因为瞬时网络原因会返回失败,需要重试。 消息与事件处理平台 1 第三方平台component_verify_ticke更新 [代码]平台审核通过后,每隔10分钟定时推送一次component_verify_ticket,开发者需要保存在数据库。再授权场景获取预授权码时需要用到这个有效的ticket。 2 授权状态变更(成功,变更,取消) 3 代码审核通知消息 4 注意: [代码]① 这里的消息时加密的需要先解密。 ② 有开发者反馈说不知道返回信息时旗下哪个appid,这里补充下,appid是再请求头的request参数里直接返回的。 ③ 消息与通知解密部分代码 [代码] public void ProcessRequest(HttpContext context) HttpRequest Request = context.Request; HttpResponse Response = context.Response; // 所属的已授权公众号的appid string AppID = Request.QueryString["AppID"]; string reqSignature = Request.QueryString["signature"]; string reqMsgSignature = Request.QueryString["msg_signature"]; string reqTimestamp = Request.QueryString["timestamp"]; string reqNonce = Request.QueryString["nonce"]; if (string.IsNullOrEmpty(AppID) || !WeixinHelper.ValidateWeixinInterface(reqSignature, WeixinResources.ComponentAppToken, reqTimestamp, reqNonce)) ResponseEnd(Response, string.Empty,AppID); return; if (AppID == WeixinResources.AutoTestAppID) string _tmsg = new WeixinAutoTestHandler(Request, Response, WeixinResources.ComponentAppID).GetMsg(); ResponseEnd(Response, _tmsg,AppID); return; #region Response body string ResMsg = string.Empty; if (Request.HttpMethod == "POST") using (Stream stream = Request.InputStream) Byte[] postBytes = new Byte[stream.Length]; stream.Read(postBytes, 0, (Int32)stream.Length); string postString = Encoding.UTF8.GetString(postBytes); string Msg = string.Empty; // 解密 Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(WeixinResources.ComponentAppToken, WeixinResources.ComponentAppEncodingAESKey, WeixinResources.ComponentAppID); int ret = 0; ret = wxcpt.DecryptMsg(reqMsgSignature, reqTimestamp, reqNonce, postString, ref Msg); if (ret != 0) ResponseEnd(Response, string.Empty,AppID); return; // 生成响应消息 string resMsg = WeixinHelper.ReturnMessageAsThirdPlatform(AppID, Msg); // 加密消息 string EncryptMsg = string.Empty; ret = wxcpt.EncryptMsg(resMsg, reqTimestamp, reqNonce, ref EncryptMsg); if (ret != 0) EncryptMsg = string.Empty; ResMsg = EncryptMsg; else if (Request.HttpMethod == "GET") ResMsg = Request.QueryString["echostr"]; ResponseEnd(Response, ResMsg,AppID); #endregion [代码]public static string ReturnMessageAsThirdPlatform(string AppID, string requestMsg) string responseContent = string.Empty; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(requestMsg); XmlNode MsgType = xmlDoc.SelectSingleNode("/xml/MsgType"); if (MsgType != null) switch (MsgType.InnerText) case "event": responseContent = EventHandleAsThirdPlatform(AppID, xmlDoc); //事件处理 break; case "text": case "image": case "voice": case "video": case "shortvideo": case "location": responseContent = TextHandleAsThirdPlatform(AppID, xmlDoc); //消息处理 break; default: break; return responseContent; 代小程序域名设置机制 1 小程序服务器域名 为授权小程序设置服务器域名 requestdomain request合法域名 wsrequestdomain socket合法域名 uploaddomain uploadFile合法域名 downloaddomain downloadFile合法域名 2 小程序业务域名 为授权小程序提供业务域名 web-view的合法域名 第三方平台先登记后,在给旗下小程序授权 旗下小程序可以使用配置域名的子域名作为业务域名 代小程序代码包管理机制 1 提交代码包 详细关于代码包管理参考https://developers.weixin.qq.com/community/develop/article/doc/000622ad764e48a45419e25b151813 ①ext.json的配置项更改体现在代码包配置json数据里。ext_json字段对应ext.json里的字段,这里template_id关联第三方平台》小程序模板管理里的模板ID。 ② 那么要提交小程序的appid呢?还是通过ext_json字段来配置。ext_jsonjson字符串里的extAppid就是第三方平台账号旗下授权,本次要提交代码的小程序appid,通过上传代码时候的来指定extAppid就可以给一个小程序代提交代码。 2 提交审核 3 收到审核通知审核通过后提交上线 4 查询最近一次提交审核进度 5 回退到上一个版本 第三方代码模板管理 1 创建小程序账号,授权给平台账号,并绑定到第三方平台账号的开发小程序列表 2 用开发小程序appid创建项目的开发代码包模板 3 用开发小程序appid提交代码到草稿箱 4从草稿箱添加到模板库 附加总结稿
- 需求的场景描述(希望解决的问题) 一个工具类小程序,希望知道在载入的时候,用户是不是已经新增小程序到“我的小程序”中了。 - 希望提供的能力 有没有这样的接口获取这个信息?或者在用户点击“新增到我的小程序”的时候,触发事件可以捕获的? 或者通过进入来源的scene值可以判断呢? 目前只看到Android系统,如果从桌面小程序图标进入,scene值为1023,收藏到“我的小程序”后再进入,scene值和从“最近打开”进入的scene值一样是1089。1103和1104已经被废弃了。。。
微信小游戏用什么测试占用手机的cpu以及内存??求大神指导
[图片] 升级后完全不能真机调试了吗 [图片]
- 电脑端正常,手机端乱码!编码格式gbk,请求解决,如何手动转换?[图片] [图片] - 手机端,请求返回自动将gbk转成utf-8 - - http请求编码为gbk的时候就会乱码,没有自动转换,能不能让我手动转换?
口袋工具y 本项目是一个基于云开发的小程序。 本文选取项目中的一个页面 – 历史上的今天 来做一个云开发的分享,会涉及云函数和云数据库。 由于是实战项目,关于小程序的基础知识请移步官方文档,本文不再赘述。 微信搜索: [代码]口袋工具y[代码] 前期遇到的问题 数据来源:没有数据,寸步难行呀 如何解决数据来源 编写爬虫将需要的数据爬取并保存下来 找一些提供数据的平台,如阿凡达数据、聚合数据等等。 本项目选择第二种方式,并最终选择了聚合数据平台API。 新建项目,配置好名称、目录、AppID等信息,后端服务选择小程序·云开发,点击新建。 关于AppID: 请自行修改为你注册的小程序AppID。 点击新建即可完成项目初始化,得到一个云开发模板: 目录结构: [代码]+-- cloudfunctions|[指定的环境] // 存放云函数的目录 +-- miniprogram // 小程序代码编写目录 |-- README.md // 项目描述文件 |-- project.config.json // 项目配置文件 新建云开发环境 点击左上角菜单项 [代码]云开发[代码] 点击创建资源环境,环境名称及环境ID请自行设置: 点击确定即可完成创建 编写云函数 1. 新建云函数 在目录 [代码]cloudfunctions[代码] 上右键 新建云函数,填入新建云函数的名称(如todayInHistory) 回车或失去焦点即会自动创建并上传。 2. 安装依赖 云函数目前执行环境仅支持node,所以需要使用js来编写云函数的逻辑。 在控制台中进入该云函数的目录,执行 [代码]npm i -S axios 本项目使用axios来执行请求的发送,可以使用其他如request-promise等等的库来替换 3. 编写云函数 新建 [代码]config.js[代码] 文件,添加代码如下: [代码]exports.key = YOUR_JUHE_KEY // 在聚合数据平台申请的key exports.baseUrl = 'http://v.juhe.cn/todayOnhistory/queryEvent.php' 打开 ·index.js· 文件,编写代码: [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const axios = require('axios') cloud.init() const db = cloud.database() // 聚合数据 const { baseUrl, key } = require('./config') // 云函数入口函数 exports.main = async(event, context) => { const { month, } = event const resp = await axios.get(baseUrl, { params: { date: `${month}/${day}` }).then(res => { return res.data return resp.result 1. 新建页面 在开发小程序的过程中,新建一个页面是很常见的操作,有两个非常方便的方式: 在 [代码]app.json[代码] 文件中,在pages项添加我们需要的页面路径,直接保存即可。如: [代码]"pages": [ "pages/today-in-history/index" 在 [代码]pages[代码] 目录下新建目录 [代码]today-in-history[代码] ,在新建的目录上 [代码]右键[代码] -> [代码]新建page[代码] , 填入名称如[代码]index[代码] , 回车即可完成页面下四个文件的创建 2. 编写 [代码]index.wxml[代码] [代码]<view class="container"> <view class="header full-width"> <view>{{year}}年{{month}}月{{day}}日</view> </view> <view class="content full-width"> <view class="list-view"> <block wx:for="{{list}}" wx:key="index"> <view class="item-title">{{item.title}}</view> <view class="item-date">{{item.date}}</view> </block> </view> </view> </view> 3. 编写 [代码]index.js[代码] [代码]// pages/today-in-history/index.js Page({ * 页面的初始数据 data: { year: 1990, month: 1, day: 1, list: [] * 生命周期函数--监听页面加载 onLoad: function() { const now = new Date(); const year = now.getFullYear(); const month = now.getMonth() + 1; const day = now.getDate(); this.setData({ year, month, this.doGetList(); * 执行数据获取 doGetList: function() { const { month, } = this.data; wx.cloud.callFunction({ name: 'todayInHistory', data: { month, }).then(res => { let list = res.result.reverse(); this.setData({ .catch(console.error) 4. 编写 [代码]index.wxss[代码] [代码]/* pages/today-in-history/index.wxss */ .container { padding-bottom: 20rpx; background-color: #E8D3A9; .header { display: flex; justify-content: space-around; align-items: center; height: 80rpx; color: #FFF; .content { flex: 1; .list-view { height: 100%; display: flex; flex-direction: column; padding: 0 20rpx; .list-item { display: flex; flex-direction: column; border-radius: 10rpx; padding: 16rpx 0; box-sizing: border-box; margin-top: 20rpx; background-color: #fff; text-align: center; box-shadow: 1px 1px 5px 1px rgb(207, 207, 207); .item-title { font-size: 36rpx; padding: 10rpx 16rpx; color: #262626; line-height: 48rpx; 5. 效果预览 到这里我们完成了 [代码]历史上的今天[代码] 的列表页,效果如下: 添加日期选择器 1. 引入 vantweapp 项目中使用 wantweapp 的部分组件 [代码]# npm npm i vant-weapp -S --production # yarn yarn add vant-weapp --production 构建npm 点击开发者工具菜单项 [代码]工具[代码] -> [代码]构建npm[代码] 程序将自动构建已安装的依赖 2. 在app.json引入组件 [代码] "usingComponents": { "van-datetime-picker": "/miniprogram_npm/vant-weapp/datetime-picker/index", "van-popup": "/miniprogram_npm/vant-weapp/popup/index", "van-toast": "/miniprogram_npm/vant-weapp/toast/index" 3. 修改 index.wxml 添加下面的代码 [代码]<view class="full-width"> <van-popup show="{{ show }}" position="bottom"> <van-datetime-picker type="date" value="{{ currentDate }}" bind:cancel="onCancel" bind:confirm="onConfirm" </van-popup> </view> <van-toast id="van-toast" /> 4. 修改 index.js 引入 Toast [代码]import Toast from '../../miniprogram_npm/vant-weapp/toast/toast'; data 添加 属性 [代码]data: { year: 1990, month: 1, day: 1, list: [], show: false, currentDate: Date.now() 添加 监听方法 [代码]/** * 监听日期选择 onChangeDate: function() { this.setData({ show: true * 监听取消 onCancel: function() { this.setData({ show: false * 监听确定 onConfirm: function(event) { const date = new Date(event.detail); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); this.setData({ year, month, show: false this.doGetList(); 最后修改 doGetList ,添加loading [代码]/** * 执行数据获取 doGetList: function() { const { month, } = this.data; Toast.loading({ mask: true, message: '加载中...' wx.cloud.callFunction({ name: 'todayInHistory', data: { month, }).then(res => { let list = res.result.reverse(); this.setData({ Toast.clear(); .catch(console.error) 5. 效果如下 由于聚合数据平台API非会员调用次数有限(100次/天),明显是不太够用的。因此,我们可以考虑在请求到数据时,将数据存在云数据库中,其实也就实现了一个类似爬虫的功能啦。流程如下::梵 代码实现: 修改 [代码]cloudfunctions/todayInHistory/index.js[代码] [代码]// ... 省略其他无需改动的代码 exports.main = async(event, context) => { const { month, } = event const ret = await db.collection('todayInHistory').where({ date: `${month}/${day}` }).get() if (ret.data.length > 0) { return ret.data[0].result const resp = await axios.get(baseUrl, { params: { date: `${month}/${day}` }).then(res => { return res.data await db.collection('todayInHistory').add({ data: { date: `${month}/${day}`, result: resp.result return resp.result 目前只开发了两个小功能 [代码]历史上的今天[代码] 和 [代码]周公解梦[代码] ,后续会继续开发新的功能,希望可以做成一个小工具集合,这也是 [代码]口袋工具[代码] 这个名称的由来。 感谢各位读者的阅读,由于本人水平有限,文章中如有错误或不妥之处,请不吝赐教! 如果你喜欢这篇文章或是这个项目,不妨进去GitHub链接点个Star支持下 today。 https://github.com/TencentCloudBase/Good-practice-tutorial-recommended
第三方系统与公众号后台配置时重置了密钥,现第三方系统可以正常进行支付操作,但导致我们原系统出了问题,用户付款时出现:“调用支付JSAPI缺少参数:appld”字样,新的第三方系统用户支付则没有问题,请问如何解决同一个公众号后台能配置两个系统的问题呢?
H5纯签约接口调用成功后,拿到跳转签约页面URL,访问URL页面显示:"发起签约页面非法"; 签约查询失败,我用的是plan_id+contract_code模式查询,参数如下: <xml> <appid>(私密)</appid> <contract_code>55745910</contract_code> <mch_id>(私密)</mch_id> <contract_id></contract_id> <plan_id>127585</plan_id> <sign>(私密)</sign> <version>1.0</version> </xml> 查询结果如下: <xml> <return_code> <![CDATA[SUCCESS]]> </return_code> <result_code> <![CDATA[FAIL]]> </result_code> <err_code>-25</err_code> <err_code_des> <![CDATA[RESULT NULL]]> </err_code_des> </xml> RESULT NULL 开发文档显示查询为空,传入正确查询参数,我比对了下参数,都没问题,求知道的小伙伴指点下
3月份至今多达数十次的测试都失败。@官方 ,能不能帮忙看下。扫普通链接二维码打开小程序,配置失败了(提示“校验文件检查失败”),但是校验文件是已经放在相应的路径下了,并且也可以直接访问到。通过自查指引自查,都没有发现问题。 相关信息: 小程序id: wx15ccc7bfea18b99a 二维码规则: https://innertest.landow.cn:9145/Home/WxAuthEntry?appid=wxad6b3095abe190a0&hc=xiaoyi1&room= 校验文件: https://innertest.landow.cn:9145/Home/IiXiNRVSrP.txt 操作时间:2019-03-25 11:47 结果:校验文件检查失败[图片] [图片] [图片] [图片] [图片] 相关文档链接: [扫普通链接二维码打开小程序](https://developers.weixin.qq.com/miniprogram/introduction/qrcode.html#%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D) 业务域名设置--校验文件检查失败自查指引https://developers.weixin.qq.com/community/develop/doc/00084a350b426099ab46e0e1a50004
接收到的emoji 都是这种字符串 \/::D\/::D\/::D,这需要如何转化
最新:2020/06/13。修改为scroll-view与swiper联动效果,新增下拉刷新以及上拉加载效果。。具体效果查看代码片段,以下文章内容和就不改了 刚刚在社区里看到 有老哥在问如何做滚动的导航栏。这里简单给他写了个代码片段,需要的大哥拿去随便改改,先看效果图: 代码如下: [代码]<scroll-view class="scroll-wrapper" scroll-x scroll-with-animation="true" scroll-into-view="item{{currentTab < 4 ? 0 : currentTab - 3}}" > <view class="navigate-item" id="item{{index}}" wx:for="{{taskList}}" wx:key="{{index}}" data-index="{{index}}" bindtap="handleClick"> <view class="names {{currentTab === index ? 'active' : ''}}">{{item.name}}</view> <view class="currtline {{currentTab === index ? 'active' : ''}}"></view> </view> </scroll-view> [代码].scroll-wrapper { white-space: nowrap; -webkit-overflow-scrolling: touch; background: #FFF; height: 90rpx; padding: 0 32rpx; box-sizing: border-box; ::-webkit-scrollbar { width: 0; height: 0; color: transparent; .navigate-item { display: inline-block; text-align: center; height: 90rpx; line-height: 90rpx; margin: 0 16rpx; .names { font-size: 28rpx; color: #3c3c3c; .names.active { color: #00cc88; font-weight: bold; font-size: 34rpx; .currtline { margin: -8rpx auto 0 auto; width: 100rpx; height: 8rpx; border-radius: 4rpx; .currtline.active { background: #47CD88; transition: all .3s; [代码]const app = getApp() Page({ data: { currentTab: 0, taskList: [{ name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' name: '有趣好玩' onLoad() { handleClick(e) { let currentTab = e.currentTarget.dataset.index this.setData({ currentTab 最后奉上代码片段: https://developers.weixin.qq.com/s/nkyp64mN7fim
小程序开发另类小技巧 --用户授权篇 getUserInfo较为特殊,不包含在本文范围内,主要针对需要授权的功能性api,例如:wx.startRecord,wx.saveImageToPhotosAlbum, wx.getLocation 原文地址:https://www.yuque.com/jinxuanzheng/gvhmm5/arexcn 仓库地址:https://github.com/jinxuanzheng01/weapp-auth-demo 小程序内如果要调用部分接口需要用户进行授权,例如获取地理位置信息,收获地址,录音等等,但是小程序对于这些需要授权的接口并不是特别友好,最明显的有两点: 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调, 没有统一的错误信息提示,例如错误码 一般情况而言,每次授权时都应该激活弹窗进行提示,是否进行授权,例如: 而小程序内只有第一次进行授权时才会主动激活弹窗(微信提供的),其他情况下都会直接走fail回调,微信文档也在句末添加了一句请开发者兼容用户拒绝授权的场景, 这种未做兼容的情况下如果用户想要使用录音功能,第一次点击拒绝授权,那么之后无论如何也无法再次开启录音权限**,很明显不符合我们的预期。 所以我们需要一个可以进行二次授权的解决方案 常见处理方法 官方demo 下面这段代码是微信官方提供的授权代码, 可以看到也并没有兼容拒绝过授权的场景查询是否授权(即无法再次调起授权) [代码]// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope wx.getSetting({ success(res) { if (!res.authSetting['scope.record']) { wx.authorize({ scope: 'scope.record', success () { // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问 wx.startRecord() 一般处理方式 那么正常情况下我们该怎么做呢?以地理位置信息授权为例: [代码]wx.getLocation({ success(res) { console.log('success', res); fail(err) { // 检查是否是因为未授权引起的错误 wx.getSetting({ success (res) { // 当未授权时直接调用modal窗进行提示 !res.authSetting['scope.userLocation'] && wx.showModal({ content: '您暂未开启权限,是否开启', confirmColor: '#72bd4a', success: res => { // 用户确认授权后,进入设置列表 if (res.confirm) { wx.openSetting({ success(res){ // 查看设置结果 console.log(!!res.authSetting['scope.userLocation'] ? '设置成功' : '设置失败'); 上面代码,有些同学可能会对在fail回调里直接使用wx.getSetting有些疑问,这里主要是因为 微信返回的错误信息没有一个统一code errMsg又在不同平台有不同的表现 从埋点数据得出结论,调用这些api接口出错率基本集中在未授权的状态下 这里为了方便就直接调用权限检查了 ,也可以稍微封装一下,方便扩展和复用,变成: [代码] bindGetLocation(e) { let that = this; wx.getLocation({ success(res) { console.log('success', res); fail(err) { that.__authorization('scope.userLocation'); bindGetAddress(e) { let that = this; wx.chooseAddress({ success(res) { console.log('success', res); fail(err) { that.__authorization('scope.address'); __authorization(scope) { /** 为了节省行数,不细写了,可以参考上面的fail回调,大致替换了下变量res.authSetting[scope] **/ 看上去好像没有什么问题,fail里只引入了一行代码, 这里如果只针对较少页面的话我认为已经够用了,毕竟**‘如非必要,勿增实体’,但是对于小打卡这个小程序来说可能涉及到的页面,需要调用的场景偏多**,我并不希望每次都人工去调用这些方法,毕竟人总会犯错 上文已经提到了背景和常见的处理方法,那么梳理一下我们的目标,我们到底是为了解决什么问题?列了下大致为下面三点: 兼容用户拒绝授权的场景,即提供二次授权 解决多场景,多页面调用没有统一规范的问题 在底层解决,业务层不需要关心二次授权的问题 扩展wx[funcName]方法 为了节省认知成本和减少出错概率,我希望他是这个api默认携带的功能,也就是说因未授权出现错误时自动调起是否开启授权的弹窗 为了实现这个功能,我们可能需要对wx的原生api进行一层包装了(关于页面的包装可以看:如何基于微信原生构建应用级小程序底层架构) 为wx.getLocation添加自己的方法 这里需要注意的一点是直接使用常见的装饰模式是会出现报错,因为wx这个对象在设置属性时没有设置set方法,这里需要单独处理一下 [代码]// 直接装饰,会报错 Cannot set property getLocation of #<Object> which has only a getter let $getLocation = wx.getLocation; wx.getLocation = function (obj) { $getLocation(obj); // 需要做一些小处理 wx = {...wx}; // 对wx对象重新赋值 let $getLocation = wx.getLocation; wx.getLocation = function (obj) { console.log('调用了wx.getLocation'); $getLocation(obj); // 再次调用时会在控制台打印出 '调用了wx.getLocation' 字样 wx.getLocation() 劫持fail方法 第一步我们已经控制了wx.getLocation这个api,接下来就是对于fail方法的劫持,因为我们需要在fail里加入我们自己的授权逻辑 [代码]// 方法劫持 wx.getLocation = function (obj) { let originFail = obj.fail; obj.fail = async function (errMsg) { // 0 => 已授权 1 => 拒绝授权 2 => 授权成功 let authState = await authorization('scope.userLocation'); // 已授权报错说明并不是权限问题引起,所以继续抛出错误 // 拒绝授权,走已有逻辑,继续排除错误 authState !== 2 && originFail(errMsg); $getLocation(obj); // 定义检查授权方法 function authorization(scope) { return new Promise((resolve, reject) => { wx.getSetting({ success (res) { !res.authSetting[scope] ? wx.showModal({ content: '您暂未开启权限,是否开启', confirmColor: '#72bd4a', success: res => { if (res.confirm) { wx.openSetting({ success(res){ !!res.authSetting[scope] ? resolve(2) : resolve(1) }else { resolve(1); : resolve(0); // 业务代码中的调用 bindGetLocation(e) { let that = this; wx.getLocation({ type: 'wgs84', success(res) { console.log('success', res); fail(err) { console.warn('fail', err); 可以看到现在已实现的功能已经达到了我们最开始的预期,即因授权报错作为了wx.getLocation默认携带的功能,我们在业务代码里再也不需要处理任何再次授权的逻辑 也意味着wx.getLocation这个api不论在任何页面,组件,出现频次如何,**我们都不需要关心它的授权逻辑(**效果本来想贴gif图的,后面发现有图点大,具体效果去git仓库跑一下demo吧) 让我们再优化一波 上面所述大致是整个原理的一个思路,但是应用到实际项目中还需要考虑到整体的扩展性和维护成本,那么就让我们再来优化一波 代码包结构: 本质上只要在app.js这个启动文件内,引用./x-wxx/index文件对原有的wx对象进行覆盖即可 **简单的代码逻辑: ** [代码]// 大致流程: //app.js wx = require('./x-wxx/index'); // 入口处引入文件 // x-wxx/index const apiExtend = require('./lib/api-extend'); module.exports = (function (wxx) { // 对原有方法进行扩展 wxx = {...wxx}; for (let key in wxx) { !!apiExtend[key] && (()=> { // 缓存原有函数 let originFunc = wxx[key]; // 装饰扩展的函数 wxx[key] = (...args) => apiExtend[key](...args, originFunc); })(); return wxx; })(wx); // lib/api-extend const Func = require('./Func'); (function (exports) { // 需要扩展的api(类似于config) // 获取权限 exports.authorize = function (opts, done) { // 当调用为"确认授权方法时"直接执行,避免死循环 if (opts.$callee === 'isCheckAuthApiSetting') { console.log('optsopts', opts); done(opts); return; Func.isCheckAuthApiSetting(opts.scope, () => done(opts)); // 选择地址 exports.chooseAddress = function (opts, done) { Func.isCheckAuthApiSetting('scope.address', () => done(opts)); // 获取位置信息 exports.getLocation = function (opts, done) { Func.isCheckAuthApiSetting('scope.userLocation', () => done(opts)); // 保存到相册 exports.saveImageToPhotosAlbum = function (opts, done) { Func.isCheckAuthApiSetting('scope.writePhotosAlbum', () => done(opts)); // ...more })(module.exports); 更多的玩法 可以看到我们无论后续扩展任何的微信api,都只需要在lib/api-extend.js 配置即可,这里不仅仅局限于授权,也可以做一些日志,传参的调整,例如: [代码] // 读取本地缓存(同步) exports.getStorageSync = (key, done) => { let storage = null; try { storage = done(key); } catch (e) { wx.$logger.error('getStorageSync', {msg: e.type}); return storage; 这样是不是很方便呢,至于Func.isCheckAuthApiSetting这个方法具体实现,为了节省文章行数请自行去git仓库里查看吧 关于音频授权 录音授权略为特殊,以wx.getRecorderManager为例,它并不能直接调起录音授权,所以并不能直接用上述的这种方法,不过我们可以曲线救国,达到类似的效果,还记得我们对于wx.authorize的包装么,本质上我们是可以直接使用它来进行授权的,比如将它用在我们已经封装好的录音管理器的start方法进行校验 [代码]wx.authorize({ scope: 'scope.record' 实际上,为方便统一管理,Func.isCheckAuthApiSetting方法其实都是使用wx.authorize来实现授权的 [代码]exports.isCheckAuthApiSetting = async function(type, cb) { // 简单的类型校验 if(!type && typeof type !== 'string') return; // 声明 let err, result; // 获取本地配置项 [err, result] = await to(getSetting()); // 这里可以做一层缓存,检查缓存的状态,如果已授权可以不必再次走下面的流程,直接return出去即可 if (err) { return cb('fail'); // 当授权成功时,直接执行 if (result.authSetting[type]) { return cb('success'); // 调用获取权限 [err, result] = await to(authorize({scope: type, $callee: 'isCheckAuthApiSetting'})); if (!err) { return cb('success'); 关于用户授权 用户授权极为特殊,因为微信将wx.getUserInfo升级了一版,没有办法直接唤起了,详见《公告》,所以需要单独处理,关于这里会拆出单独的一篇文章来写一些有趣的玩法 最后稍微总结下,通过上述的方案,我们解决了最开始目标的同时,也为wx这个对象上的方法提供了统一的装饰接口(lib/api-extend文件),便于后续其他行为的操作比如埋点,日志,参数校验 还是那么一句话吧,小程序不管和web开发有多少不同,本质上都是在js环境上进行开发的,希望小程序的社区环境更加活跃,带来更多有趣的东西
长按删除小程序也不行,切换wifi和4g也都不行 微信版本:7.0.4 appId: wx4663ba1e3403df53 [图片]
使用scroll-view横向滚动制作的一款导航栏,只做个个简单渐变切换的效果和下划线(下划线也做了渐变觉得太难看注释了,也可以使用),欢迎各位改造,方便以后多用,也可以自己随便用用,不足也可以指出,此贴如有新想法,新改进会持续更新 原始代码链接:https://developers.weixin.qq.com/s/IffO2gmi7Y9z 增加了放大缩小透明变化:https://developers.weixin.qq.com/s/LtPfmhmD7Y9a
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 fail: err => { console.log(err) 最后绘制分享图的自定义组件就完成啦~效果图如下: tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
今天发现,在app.js 中执行 wx.getSystemInfoSync() 获取系统信息的 windowHeight是减去 tabbar 的。 在没有tabbar的 页面执行 后获取的高度是算上tabbar高度的。 这样原来想着在 globalData里放上 一次系统信息就可以调用的想法在这个 windowHeight 上是不行的。
近期有好多客户咨询我做APP,我就很恼火,你预算一点点,还想盖高楼,真是难啊!俗话说,巧妇难为无米之炊。你给那点钱,我也很难帮到客户,就重点给他推荐小程序的好处,客户最终也愿意听我的建议,低成本开发小程序,前期试错迭代,探索模式清晰后再做APP开发。APP的历史很久了,上线近两年的微信小程序功能与APP相近,而公众号同样基于微信,性能又与小程序相近,小程序和APP还有公众号到底有什么区别呢?下面我简单整理了自己总结的东西,和大神一起来讨论一下。 一、小程序和公众号的区别 在电商行业发展或者准备进入电商行业中发展的企业商家,在微信电商不断发展的现在,大多会选择进入这个市场去发展。特别是这一年多来,微信小程序的火热,让更多商家看到它的商机与市场,纷纷投入到小程序商城的开发当中。不过,即使开发完毕,还需要考虑更为重要的问题,就是如何去推广这个平台来曝光引流、转化,而保障它的发展的问题。 二、小程序和APP的区别 小程序虽然被喻为原生APP,但是开发小程序与开发APP所需要的技术不是在一个层次上的。小程序的开发,是基于微信官方提供开发指引与工具基础上去开发,相当有一个大致的框架作为基础。而APP开发,所有的框架内容都要从基础开始搭建,技术难度与开发周期都在小程序之上,这是区别之一。 区别之二,在于它们的功能实现与技术维护上。小程序虽然能实现企业商家的大部分功能需求,但是依旧有一些功能是它无法实现而APP开发可以实现的。比如一些物联网项目的需求功能,就只能在APP上才能真正实现软硬件对接。另外,在开发之后的后期维护上,APP比小程序维护所需技术更深层次,它需要针对不同类型的系统做兼容性开发、维护以及系统升级,以保障它能顺畅运行。而小程序的维护,有微信官方的支持,维护成本更低、周期更短,且流程更加简单一些。 三、如何保障小程序的发展问题? 1.线下场景投放参数码推广 小程序商城虽然作为一个线上的电商平台,但不一定它的推广就要局限于线上,可以通过小程序码、参数二维码的投放,将线下场景也用于推广引流,让线下的流量也能通过扫码而引到线上。 2.与公众号关联推广 微信小程序可以与公众号相互关联这一点,是明显让这两个平台得以互补的设定。小程序难以将客户真实留存到平台中,而公众号的粉丝体系恰好可以弥补这一功能,实现粉丝的沉淀。而开发小程序商城之后,与公众号进行关联,可通过图文、自定义菜单等,赋予小程序入口,将公号的粉丝引到小程序中,同时对商城进行推广与曝光。这种在公众号原有基础上推广小程序商城的做法,也是比较有效的。 3.利用砍价、拼团等工具去实现裂变 像拼多多这类型的商城,除了基本的商品销售之外,其实更让它名声大燥的,是它的拼团与砍价的玩法。拼团更省钱,砍价更是可以0元购物,这些对于消费者来说都是直接的利益体现,所以他们更愿意去主动转发分享这个平台,而在他们分享的过程中,商城也就得到推广。所以,微信小程序商城可以借助这种模式,用砍价、拼团等工具,去实现裂变传播。 4.利用软文做内容营销 内容营销是一种比较常见的推广方式,小程序商城可以利用这种手段,编辑软文进行营销推广并刺激转化,将主推的产品、主推的服务以软广的形式去介绍出去,在实现的曝光的同时,也用价值抓住用户眼球,刺激用户实现消费。 APP、公众号和小程序上线的时候不同,功能也有所不一样,发展的方向也不同,但不可否认的是对我们的生活有着很大的影响,为我们提供了便利。至于企业要怎么选择,要根据自身的发展需求,从基础出发,契合企业的发展需求。 个人觉得还是小程序更有前途,它是一个轻APP,基于微信小程序快速部署,也有成熟的第三方系统,可以直接拿来用,腾讯官方也会不断丰富小程序的基础功能,对我们来说是一大好处。利用第三方的成熟系统开发小程序也是不错的选择,我们自己研发的小程序商城系统现在开源出来,希望能够帮助更多开发者快速开发,而不被客户催促加班着急上火! **源码下载地址:http://github.crmeb.net/u/demo
大家好,我是小打卡的前端唐驰。刚才金轩正同学分享了基于原生小程序底层架构,在此基础上我为大家分享下如何拆分一个200+页面的小程序,主要通过以下几点来聊一聊小打卡在组件化路上的一些实践 1.背景 2.组件与方案 3.组件间通讯 4.基于组件我们做了哪些事 [图片] [图片] 1. 其实一开始小打卡是没有引入组件化的,因为微信最开始是不支持组件化的。当时js代码已经4k+行了,各种功能代码,有用的没有用的,不知道干什么的代码就躺在那里,一动不动。举个例子,一个头像点击跳转的逻辑搜索了下,遍布在各个页面。修改起来可想而知的胆战心惊。另一个原因就是当时由于业务功能直线上升,很快我们就遇到了代码包超包了。在微信还没有实现分包之前,我们就只能一个一个页面的去review剔除代码,效率极低。这也是促成我们决定寻求出路的原因之一。可是删代码删功能是不能解决问题,期间我们也考虑过h5的方式,跑了demo之后却发现h5方式的多次渲染, 与加载首页白屏,尽管有各种服务端渲染方案,但是我们一致觉得为了用户体验,放弃了。 [图片] 2. 对于小打卡来说,我们不能再任由项目裸奔了,需要一种开发方式来进行约束,主要是有几个诉求: 在之前的项目上,为了方便。功能与功能之间的耦合程度极其的高,各种为了使用方便而随意修改某一个方法。 1.降低页面上各个功能点的耦合程度 我们不希望同一个功能点同样的代码在页面肆意copy,这样带来了极高的维护成本。以至后面无法维护。并且功能的复用不希望是copy,前端与后端不同的是不仅是单单的逻辑复用、更有布局、样式等。 2.提供代码的可复用性、可维护性 对于一个程序员来说,如果你打开一个代码文件。映入眼帘的是密密麻麻的代码,行数达到好几千K行,我相信大家的第一反应是抗拒的,更别说去修改代码,天知道会改出什么问题。 3.降低单一文件的复杂度 4.如果是公共功能的化我们还希望它能够有自己的作用域,保持自己的独立性。 [图片] 3. 根据以上几点,我们用一个页面举例,如何去拆分一个页面,首先我们需要有以下几点认识: 决定一个页面如何组件化的前提是该页面的功能是否是有全局都需要的功能模块 功能模块是否需要与页面其他模块强耦合 单个功能模块逻辑是否过于复杂(占用代码空间过大)——>单纯是为了页面代码的可读性。 不是全拆成组件就是最好的,不能为了组件而组件化 [图片] 4. 说了这么多,其实我们应该首先应该了解下,组件的特性? 专一性(一个组件只干一件事情,或者某一类事情。)功能的高度内聚 比如说右侧的feed集上的头像、它是一个组件、就负责显示头像跟跳转,其他的事它都不参与 可配置(能够适应通过设置属性值的方式来输出不同的东西)输入影响部分输出 然后我们同时可以设置头像组件上的size属性来设置头像在不同页面下的大小样式 生命周期(组件可以在自身或者说所在页面的生命周期内可以做不同的事情)比如可以在组件生成的时候进行数的初始化、属性值的类型校验、组件销毁时并同时销毁定时器等其他任务 事件传递 (既然要让组件与页面保持独立性,那么组件与页面的通讯交互就得需要一个标准) 右侧的feed组件其实是一个组件集合、我们通过组合不同的组件然后就形成了feed组件。就跟搭积木一样、只需要引入组件就行了。特别方便。 [图片] 5. 说到组件,那么小程序早期的不支持自定义组件开发这就很让人头疼、同样的feed组件我们经历了几乎三个版本的大改动、从最开始的直接写在页面里,后台使用template方式、再到后来的自定义组件方式。所以我们的演进步骤就成了page->template->component, 这儿列了一个表格对比了下几种组件化方式的对比。 可以看到,include的方式其实是最鸡肋的,include的方式其实实际意义上我理解成更多的是代码的切割,并且还不能将(template、wxs)分出去、所以这种方式我们直接pass掉了, 而template的方式其实是我们曾经主力使用的方式、到现在我们也还在使用、相对于include来说,template有了独立的作用域、虽然css、跟js还是与页面共享的。但是已经可以做一些比较简单的事情了。 对于component来说,完完全全的组件,满足了组件的所有要求。 [图片] 6. 先说说template的方式吧,举个列子,这个是我们的使用template构建的头像组件。跟写页面的方式很像、同样是js、wxss、wxml组成。用名称来命名。但是由于微信当时没有很方的方式去引用这些文件,或者说没有一种方法可以打包供我们很方便的使用。但是比起之前直接copy代码的方式、这样通过引用的方式使用其实感觉已经好了很多了。 [图片] [图片] 7. 具体的使用方式我画了张图,对应组件内文件与页面文件的对应方式、这里对于js的引用其实我们是做了一些小动作, 我们在调用Page方法前做了一次page方法与组件方法的check,因为在page代码里我们不能保证所有的方法名不会跟组件内的方法名不会冲突,所以我们做了这个一个检查、 然后mix函数还做了另一个事情就是将page方法与组件方法合并。然后对于mix函数其实我们还可以做很多事情、、比如规范生命周期回调函数放在一个对象内,然后我们自己定义的方法放在另一个对象里,就跟vue一样。 But,在经历了一段template组件化的时间后,我们又觉得这个方式还是有点烂,为什么呢?在使用时仍然不能避免引入众多的文件、虽然我们对js文件做了处理,但是wxss的样式仍然会被污染、js与page仍然共享作用域。并不能成为一个真正的标准组件。好在后来,微信上了自定义组件的功能,接下来聊聊这个标准的微信自定义组件吧。 8. 微信提供了自定义组件的功能后我们也第一时间跟进了,相对于template这种方式来说,现在是真正的独立于页面存在。使用也比之前更为方便与简洁,右图是我们对component的一个项目目录划分。我们将component划分为了公共组件与页面组件、为什么会有页面组件, 1.是为了降低页面代码的复杂度 2.为了好看。 公共组件就不说了,一定是最基础、最通用的组件。 [图片] 9. 转向component方式后有一个问题逐渐便凸显出来了,由于组件的独立作用域,组件间的通讯就成了一个问题,接下来聊一聊组件的事件传递。微信最开始的时候提供了一种triggerEvent的方式,可是这样的方式似乎并不能满足我们某些场景下的需求。后来又提供了page下selectComponent方法来直接操作组件内部的属性与方法。然后还有就是基于我们自己的事件广播机制。这几种方式构成了小打卡现目前最主要的组件与page、组件与组件间的数据交互方式 [图片] 10. 先来说说triggerEvent模式,微信在自定义组件上可以自定义监听函数。我们在组件内将需要向外抛出的事件统一通过this.triggerEvent(‘invoke’,{handler:’fun’,data:{}})这个方法来执行。其中invoke对应了我们绑定在组件标签上的监听函数。而将需要外部执行的方法与数据通过数据的方式传给监听函数。而在page上面我通过统一的监听回调函数去自动执行需要执行的方法、这里的trigger与event都不要我们去手写在组件与page创建的时候底层就已经帮我们预置了,我们只需要关注业务开发就行。这是对于一部分需要page与组件交互的模式。而对于我们想直接操作组件方法而不需要反馈的模式就得使用selectComponent的模式 [图片] 11. 一个简单的列子:全局的toast组件。在需要弹出toast的时候我们想直接调用就行、不用在通过传值给组件、然后由组件来执行显示或隐藏。这类组件我们在组件目录里新增了一个lib的文件。在page里只需要引入这个lib文件然后就可以直接调用toast组件。lib主要是对this.selectCompent与执行逻辑的一个封装。 [图片] 12. 事件发布订阅模式:基于底层的eventBus。简化后我们用在了组件与组件之间的通讯上、特点是简单。 [图片] 13. 解决了组件间的通讯问题,可是对于公共组件的引用仍然让我们觉得麻烦与不畅快、所以我们构建了全局通用模版、它是干什么的呢。它提供给了一些基础的全局组件、比如自定义导航头、toast、loading等等。小打卡所有的页面都通过slot的方式插入到这个模版组件x-page下面。这样就解决了我们需要在每个页面引入公共组件的问题。另一个问题使用自定义导航栏的时定位起点会有状态栏下移动到屏幕左上方。会造成布局的错误。通过x-page可以很好解决这个问题而不用重新布局。并且通信问题也不用担心,都是由x-page组件作为中台来对内对外进行分发与执行。 [图片] [图片] [图片] 14. 通过以上小打卡的开发模式就基本形成。要做的事情还有很多,更多组件的玩儿法,对于现在或者将来我们正在做的。 是构建小打卡的组件与基础sdk的仓库。 拆分组件开发与业务开发。 通过npm包管理的方式来应对越来越多的小程序平台的开发。 或者通过形成小程序插件的方式供其他小伙伴使用。 [图片] [图片] 以上就是我今天分享的内容。谢谢。
随着各大前端框架的诞生和演变,[代码]SPA[代码]开始流行,单页面应用的优势在于可以不重新加载整个页面的情况下,通过[代码]ajax[代码]和服务器通信,实现整个[代码]Web[代码]应用拒不更新,带来了极致的用户体验。然而,对于需要[代码]SEO[代码]、追求极致的首屏性能的应用,前端渲染的[代码]SPA[代码]是糟糕的。好在[代码]Vue 2.0[代码]后是支持服务端渲染的,零零散散花费了两三周事件,通过改造现有项目,基本完成了在现有项目中实践了[代码]Vue[代码]服务端渲染。 关于Vue服务端渲染的原理、搭建,官方文档已经讲的比较详细了,因此,本文不是抄袭文档,而是文档的补充。特别是对于如何与现有项目进行很好的结合,还是需要费很大功夫的。本文主要对我所在的项目中进行[代码]Vue[代码]服务端渲染的改造过程进行阐述,加上一些个人的理解,作为分享与学习。 本文主要分以下几个方面: 什么是服务端渲染?服务端渲染的原理是什么? 如何在基于[代码]Koa[代码]的[代码]Web Server Frame[代码]上配置服务端渲染? [代码]Webpack[代码]配置 开发环境搭建 渲染中间件配置 什么是服务端渲染?服务端渲染的原理是什么? [代码]Vue.js[代码]是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出[代码]Vue[代码]组件,进行生成[代码]DOM[代码]和操作[代码]DOM[代码]。然而,也可以将同一个组件渲染为服务器端的[代码]HTML[代码]字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。 上面这段话是源自Vue服务端渲染文档的解释,用通俗的话来说,大概可以这么理解: 服务端渲染的目的是:性能优势。 在服务端生成对应的[代码]HTML[代码]字符串,客户端接收到对应的[代码]HTML[代码]字符串,能立即渲染[代码]DOM[代码],最高效的首屏耗时。此外,由于服务端直接生成了对应的[代码]HTML[代码]字符串,对[代码]SEO[代码]也非常友好; 服务端渲染的本质是:生成应用程序的“快照”。将[代码]Vue[代码]及对应库运行在服务端,此时,[代码]Web Server Frame[代码]实际上是作为代理服务器去访问接口服务器来预拉取数据,从而将拉取到的数据作为[代码]Vue[代码]组件的初始状态。 服务端渲染的原理是:虚拟[代码]DOM[代码]。在[代码]Web Server Frame[代码]作为代理服务器去访问接口服务器来预拉取数据后,这是服务端初始化组件需要用到的数据,此后,组件的[代码]beforeCreate[代码]和[代码]created[代码]生命周期会在服务端调用,初始化对应的组件后,[代码]Vue[代码]启用虚拟[代码]DOM[代码]形成初始化的[代码]HTML[代码]字符串。之后,交由客户端托管。实现前后端同构应用。 如何在基于[代码]Koa[代码]的[代码]Web Server Frame[代码]上配置服务端渲染? 需要用到[代码]Vue[代码]服务端渲染对应库[代码]vue-server-renderer[代码],通过[代码]npm[代码]安装: [代码]npm install vue vue-server-renderer --save 最简单的,首先渲染一个[代码]Vue[代码]实例: // 第 1 步:创建一个 Vue 实例 const Vue = require('vue'); const app = new Vue({ template: `<div>Hello World</div>` // 第 2 步:创建一个 renderer const renderer = require('vue-server-renderer').createRenderer(); // 第 3 步:将 Vue 实例渲染为 HTML renderer.renderToString(app, (err, html) => { if (err) { throw err; console.log(html); // => <div data-server-rendered="true">Hello World</div> 与服务器集成: module.exports = async function(ctx) { ctx.status = 200; let html = ''; try { // ... html = await renderer.renderToString(app, ctx); } catch (err) { ctx.logger('Vue SSR Render error', JSON.stringify(err)); html = await ctx.getErrorPage(err); // 渲染出错的页面 ctx.body = html; 使用页面模板: 当你在渲染[代码]Vue[代码]应用程序时,[代码]renderer[代码]只从应用程序生成[代码]HTML[代码]标记。在这个示例中,我们必须用一个额外的[代码]HTML[代码]页面包裹容器,来包裹生成的[代码]HTML[代码]标记。 为了简化这些,你可以直接在创建[代码]renderer[代码]时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中: [代码]<!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <!--vue-ssr-outlet--> </body> </html> 然后,我们可以读取和传输文件到[代码]Vue renderer[代码]中: [代码]const tpl = fs.readFileSync(path.resolve(__dirname, './index.html'), 'utf-8'); const renderer = vssr.createRenderer({ template: tpl, Webpack配置 然而在实际项目中,不止上述例子那么简单,需要考虑很多方面:路由、数据预取、组件化、全局状态等,所以服务端渲染不是只用一个简单的模板,然后加上使用[代码]vue-server-renderer[代码]完成的,如下面的示意图所示: 如示意图所示,一般的[代码]Vue[代码]服务端渲染项目,有两个项目入口文件,分别为[代码]entry-client.js[代码]和[代码]entry-server.js[代码],一个仅运行在客户端,一个仅运行在服务端,经过[代码]Webpack[代码]打包后,会生成两个[代码]Bundle[代码],服务端的[代码]Bundle[代码]会用于在服务端使用虚拟[代码]DOM[代码]生成应用程序的“快照”,客户端的[代码]Bundle[代码]会在浏览器执行。 因此,我们需要两个[代码]Webpack[代码]配置,分别命名为[代码]webpack.client.config.js[代码]和[代码]webpack.server.config.js[代码],分别用于生成客户端[代码]Bundle[代码]与服务端[代码]Bundle[代码],分别命名为[代码]vue-ssr-client-manifest.json[代码]与[代码]vue-ssr-server-bundle.json[代码],关于如何配置,[代码]Vue[代码]官方有相关示例vue-hackernews-2.0 开发环境搭建 我所在的项目使用[代码]Koa[代码]作为[代码]Web Server Frame[代码],项目使用koa-webpack进行开发环境的构建。如果是在产品环境下,会生成[代码]vue-ssr-client-manifest.json[代码]与[代码]vue-ssr-server-bundle.json[代码],包含对应的[代码]Bundle[代码],提供客户端和服务端引用,而在开发环境下,一般情况下放在内存中。使用[代码]memory-fs[代码]模块进行读取。 [代码]const fs = require('fs') const path = require( 'path' ); const webpack = require( 'webpack' ); const koaWpDevMiddleware = require( 'koa-webpack' ); const MFS = require('memory-fs'); const appSSR = require('./../../app.ssr.js'); let wpConfig; let clientConfig, serverConfig; let wpCompiler; let clientCompiler, serverCompiler; let clientManifest; let bundle; // 生成服务端bundle的webpack配置 if ((fs.existsSync(path.resolve(cwd,'webpack.server.config.js')))) { serverConfig = require(path.resolve(cwd, 'webpack.server.config.js')); serverCompiler = webpack( serverConfig ); // 生成客户端clientManifest的webpack配置 if ((fs.existsSync(path.resolve(cwd,'webpack.client.config.js')))) { clientConfig = require(path.resolve(cwd, 'webpack.client.config.js')); clientCompiler = webpack(clientConfig); if (serverCompiler && clientCompiler) { let publicPath = clientCompiler.output && clientCompiler.output.publicPath; const koaDevMiddleware = await koaWpDevMiddleware({ compiler: clientCompiler, devMiddleware: { publicPath, serverSideRender: true app.use(koaDevMiddleware); // 服务端渲染生成clientManifest app.use(async (ctx, next) => { const stats = ctx.state.webpackStats.toJson(); const assetsByChunkName = stats.assetsByChunkName; stats.errors.forEach(err => console.error(err)); stats.warnings.forEach(err => console.warn(err)); if (stats.errors.length) { console.error(stats.errors); return; // 生成的clientManifest放到appSSR模块,应用程序可以直接读取 let fileSystem = koaDevMiddleware.devMiddleware.fileSystem; clientManifest = JSON.parse(fileSystem.readFileSync(path.resolve(cwd,'./dist/vue-ssr-client-manifest.json'), 'utf-8')); appSSR.clientManifest = clientManifest; await next(); // 服务端渲染的server bundle 存储到内存里 const mfs = new MFS(); serverCompiler.outputFileSystem = mfs; serverCompiler.watch({}, (err, stats) => { if (err) { throw err; stats = stats.toJson(); if (stats.errors.length) { console.error(stats.errors); return; // 生成的bundle放到appSSR模块,应用程序可以直接读取 bundle = JSON.parse(mfs.readFileSync(path.resolve(cwd,'./dist/vue-ssr-server-bundle.json'), 'utf-8')); appSSR.bundle = bundle; 渲染中间件配置 产品环境下,打包后的客户端和服务端的[代码]Bundle[代码]会存储为[代码]vue-ssr-client-manifest.json[代码]与[代码]vue-ssr-server-bundle.json[代码],通过文件流模块[代码]fs[代码]读取即可,但在开发环境下,我创建了一个[代码]appSSR[代码]模块,在发生代码更改时,会触发[代码]Webpack[代码]热更新,[代码]appSSR[代码]对应的[代码]bundle[代码]也会更新,[代码]appSSR[代码]模块代码如下所示: [代码]let clientManifest; let bundle; const appSSR = { get bundle() { return bundle; set bundle(val) { bundle = val; get clientManifest() { return clientManifest; set clientManifest(val) { clientManifest = val; module.exports = appSSR; 通过引入[代码]appSSR[代码]模块,在开发环境下,就可以拿到[代码]clientManifest[代码]和[代码]ssrBundle[代码],项目的渲染中间件如下: [代码]const fs = require('fs'); const path = require('path'); const ejs = require('ejs'); const vue = require('vue'); const vssr = require('vue-server-renderer'); const createBundleRenderer = vssr.createBundleRenderer; const dirname = process.cwd(); const siteInfo = require('./../../core/siteinfo.js').get(); const env = siteInfo.env; let bundle; let clientManifest; if (env === 'development') { // 开发环境下,通过appSSR模块,拿到clientManifest和ssrBundle let appSSR = require('./../../core/app.ssr.js'); bundle = appSSR.bundle; clientManifest = appSSR.clientManifest; } else { bundle = JSON.parse(fs.readFileSync(path.resolve(__dirname, './dist/vue-ssr-server-bundle.json'), 'utf-8')); clientManifest = JSON.parse(fs.readFileSync(path.resolve(__dirname, './dist/vue-ssr-client-manifest.json'), 'utf-8')); module.exports = async function(ctx) { ctx.status = 200; let html; let context = await ctx.getTplContext(); ctx.logger('进入SSR,context为: ', JSON.stringify(context)); const tpl = fs.readFileSync(path.resolve(__dirname, './newTemplate.html'), 'utf-8'); const renderer = createBundleRenderer(bundle, { runInNewContext: false, template: tpl, // (可选)页面模板 clientManifest: clientManifest // (可选)客户端构建 manifest ctx.logger('createBundleRenderer renderer:', JSON.stringify(renderer)); try { html = await renderer.renderToString({ ...context, url: context.CTX.url, } catch(err) { ctx.logger('SSR renderToString 失败: ', JSON.stringify(err)); console.error(err); ctx.body = html; 如何对现有项目进行改造? 基本目录改造 使用[代码]Webpack[代码]来处理服务器和客户端的应用程序,大部分源码可以使用通用方式编写,可以使用[代码]Webpack[代码]支持的所有功能。 一个基本项目可能像是这样: [代码]src ├── components │ ├── Foo.vue │ ├── Bar.vue │ └── Baz.vue ├── frame │ ├── app.js # 通用 entry(universal entry) │ ├── entry-client.js # 仅运行于浏览器 │ ├── entry-server.js # 仅运行于服务器 │ └── index.vue # 项目入口组件 ├── pages ├── routers └── store [代码]app.js[代码]是我们应用程序的「通用[代码]entry[代码]」。在纯客户端应用程序中,我们将在此文件中创建根[代码]Vue[代码]实例,并直接挂载到[代码]DOM[代码]。但是,对于服务器端渲染([代码]SSR[代码]),责任转移到纯客户端[代码]entry[代码]文件。[代码]app.js[代码]简单地使用[代码]export[代码]导出一个[代码]createApp[代码]函数: [代码]import Router from '~ut/router'; import { sync } from 'vuex-router-sync'; const Vue = require('vue'); const { createStore } = require('./../store'); import ElementUI from 'element-ui'; import vueScroll from 'vue-scroll'; import '~mstyle/ai-scan/src/assets/less/common.less'; import Frame from './index.vue'; import breastRouter from './../routers/breast'; import lungRouter from './../routers/lung'; import List from './../page/list/index.vue'; import DicomView from '~cmpt/dicom-view'; Vue.use(vueScroll); Vue.use(ElementUI); function createVueInstance(routes, ctx) { let siteinfo; if (ctx) { siteinfo = ctx.siteinfo; } else { siteinfo = window.SiteInfo; const router = Router({ base: siteinfo.path, mode: 'history', routes: [routes], const store = createStore({ ctx }); // 把路由注入到vuex中 sync(store, router); const app = new Vue({ router, render: function(h) { return h(Frame); store, return { app, router, store }; function createVueInstanceNoRouter(component, ctx) { const componentIns = Vue.extend(component); const store = createStore({ ctx }); const app = new Vue({ store, render: h => h(component), return { app, store }; module.exports = function createApp(ctx) { // 通过typeof window是否为'undefined'判断当前是客户端还是服务端 // 针对不同页面创建不同的Vue实例 if (typeof window !== 'undefined') { // 客户端 if (/list/.test(window.location.pathname)) { return createVueInstanceNoRouter(List); } else if (/breast/.test(window.location.pathname)) { DicomView.init('image', 'imaging'); return createVueInstance(breastRouter); } else if (/lung/.test(window.location.pathname)) { DicomView.init('series', 'imaging'); return createVueInstance(lungRouter); } else if (/pacs/.test(window.location.pathname)) { DicomView.init('series', 'imaging'); return createVueInstance(pacsRouter); } else if (/esophagus/.test(window.location.pathname)) { return createVueInstance(esophagusRouter); } else { return void console.log('The path does not match any router rule'); // do nothing } else { // 服务端 if (/list/.test(ctx.path)) { return createVueInstanceNoRouter(List, ctx); } else if (/lung/.test(ctx.path)) { return createVueInstance(lungRouter, ctx); } else if (/pacs/.test(ctx.path)) { return createVueInstance(pacsRouter); } else if (/esophagus/.test(ctx.path)) { return createVueInstance(esophagusRouter); } else if (/breast/.test(ctx.path)) { return createVueInstance(breastRouter, ctx); } else { return void console.log('The path does not match any router rule'); // do nothing 注:在我所在的项目中,需要动态判断是否需要注册[代码]DicomView[代码],只有在客户端才初始化[代码]DicomView[代码],由于[代码]Node.js[代码]环境没有[代码]window[代码]对象,对于代码运行环境的判断,可以通过[代码]typeof window === 'undefined'[代码]来进行判断。 避免创建单例 如[代码]Vue SSR[代码]文档所述: 当编写纯客户端 (client-only) 代码时,我们习惯于每次在新的上下文中对代码进行取值。但是,Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。如基本示例所示,我们为每个请求创建一个新的根 Vue 实例。这与每个用户在自己的浏览器中使用新应用程序的实例类似。如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染 (cross-request state pollution)。因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例。同样的规则也适用于 router、store 和 event bus 实例。你不应该直接从模块导出并将其导入到应用程序中,而是需要在 createApp 中创建一个新的实例,并从根 Vue 实例注入。 如上代码所述,[代码]createApp[代码]方法通过返回一个返回值创建[代码]Vue[代码]实例的对象的函数调用,在函数[代码]createVueInstance[代码]中,为每一个请求创建了[代码]Vue[代码],[代码]Vue Router[代码],[代码]Vuex[代码]实例。并暴露给[代码]entry-client[代码]和[代码]entry-server[代码]模块。 在客户端[代码]entry-client.js[代码]只需创建应用程序,并且将其挂载到[代码]DOM[代码]中: [代码]import { createApp } from './app'; // 客户端特定引导逻辑…… const { app } = createApp(); // 这里假定 App.vue 模板中根元素具有 `id="app"` app.$mount('#app'); 服务端[代码]entry-server.js[代码]使用[代码]default export[代码] 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配和数据预取逻辑: [代码]import { createApp } from './app'; export default context => { const { app } = createApp(); return app; 在服务端用[代码]vue-router[代码]分割代码 与[代码]Vue[代码]实例一样,也需要创建单例的[代码]vueRouter[代码]对象。对于每个请求,都需要创建一个新的[代码]vueRouter[代码]实例: [代码]function createVueInstance(routes, ctx) { let siteinfo; if (ctx) { siteinfo = ctx.siteinfo; } else { siteinfo = window.SiteInfo; const router = Router({ base: siteinfo.path, mode: 'history', routes: [routes], const store = createStore({ ctx }); // 把路由注入到vuex中 sync(store, router); const app = new Vue({ router, render: function(h) { return h(Frame); store, return { app, router, store }; 同时,需要在[代码]entry-server.js[代码]中实现服务器端路由逻辑,使用[代码]router.getMatchedComponents[代码]方法获取到当前路由匹配的组件,如果当前路由没有匹配到相应的组件,则[代码]reject[代码]到[代码]404[代码]页面,否则[代码]resolve[代码]整个[代码]app[代码],用于[代码]Vue[代码]渲染虚拟[代码]DOM[代码],并使用对应模板生成对应的[代码]HTML[代码]字符串。 [代码]const createApp = require('./app'); module.exports = context => { return new Promise((resolve, reject) => { // ... // 设置服务器端 router 的位置 router.push(context.url); // 等到 router 将可能的异步组件和钩子函数解析完 router.onReady(() => { const matchedComponents = router.getMatchedComponents(); // 匹配不到的路由,执行 reject 函数,并返回 404 if (!matchedComponents.length) { return reject('匹配不到的路由,执行 reject 函数,并返回 404'); // Promise 应该 resolve 应用程序实例,以便它可以渲染 resolve(app); }, reject); 在服务端预拉取数据 在[代码]Vue[代码]服务端渲染,本质上是在渲染我们应用程序的"快照",所以如果应用程序依赖于一些异步数据,那么在开始渲染过程之前,需要先预取和解析好这些数据。服务端[代码]Web Server Frame[代码]作为代理服务器,在服务端对接口服务发起请求,并将数据拼装到全局[代码]Vuex[代码]状态中。 另一个需要关注的问题是在客户端,在挂载到客户端应用程序之前,需要获取到与服务器端应用程序完全相同的数据 - 否则,客户端应用程序会因为使用与服务器端应用程序不同的状态,然后导致混合失败。 目前较好的解决方案是,给路由匹配的一级子组件一个[代码]asyncData[代码],在[代码]asyncData[代码]方法中,[代码]dispatch[代码]对应的[代码]action[代码]。[代码]asyncData[代码]是我们约定的函数名,表示渲染组件需要预先执行它获取初始数据,它返回一个[代码]Promise[代码],以便我们在后端渲染的时候可以知道什么时候该操作完成。注意,由于此函数会在组件实例化之前调用,所以它无法访问[代码]this[代码]。需要将[代码]store[代码]和路由信息作为参数传递进去: 举个例子: [代码]<!-- Lung.vue --> <template> <div></div> </template> <script> export default { // ... async asyncData({ store, route }) { return Promise.all([ store.dispatch('getUserInfo'), store.dispatch('lung/getSeriesByStudyId', { studyId: route.params.id, aiEngine: route.params.aiEngine, }, { root:true }), store.dispatch('lung/getDicomViewConfig', { root:true }), store.dispatch('lung/getDialogWindow', { root:true }), // ... </script> 在[代码]entry-server.js[代码]中,我们可以通过路由获得与[代码]router.getMatchedComponents()[代码]相匹配的组件,如果组件暴露出[代码]asyncData[代码],我们就调用这个方法。然后我们需要将解析完成的状态,附加到渲染上下文中。 [代码]const createApp = require('./app'); module.exports = context => { return new Promise((resolve, reject) => { const { app, router, store } = createApp(context.CTX); // 针对没有Vue router 的Vue实例,在项目中为列表页,直接resolve app if (!router) { resolve(app); // 设置服务器端 router 的位置 router.push(context.CTX.url.replace('/imaging', '')); // 等到 router 将可能的异步组件和钩子函数解析完 router.onReady(() => { const matchedComponents = router.getMatchedComponents(); // 匹配不到的路由,执行 reject 函数,并返回 404 if (!matchedComponents.length) { return reject('匹配不到的路由,执行 reject 函数,并返回 404'); Promise.all(matchedComponents.map(Component => { if (Component.asyncData) { return Component.asyncData({ store, route: router.currentRoute, })).then(() => { // 在所有预取钩子(preFetch hook) resolve 后, // 我们的 store 现在已经填充入渲染应用程序所需的状态。 // 当我们将状态附加到上下文,并且 `template` 选项用于 renderer 时, // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。 context.state = store.state; resolve(app); }).catch(reject); }, reject); 客户端托管全局状态 当服务端使用模板进行渲染时,[代码]context.state[代码]将作为[代码]window.__INITIAL_STATE__[代码]状态,自动嵌入到最终的[代码]HTML[代码] 中。而在客户端,在挂载到应用程序之前,[代码]store[代码]就应该获取到状态,最终我们的[代码]entry-client.js[代码]被改造为如下所示: [代码]import createApp from './app'; const { app, router, store } = createApp(); // 客户端把初始化的store替换为window.__INITIAL_STATE__ if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__); if (router) { router.onReady(() => { app.$mount('#app') } else { app.$mount('#app'); 常见问题的解决方案 至此,基本的代码改造也已经完成了,下面说的是一些常见问题的解决方案: 在服务端没有[代码]window[代码]、[代码]location[代码]对象: 对于旧项目迁移到[代码]SSR[代码]肯定会经历的问题,一般为在项目入口处或是[代码]created[代码]、[代码]beforeCreate[代码]生命周期使用了[代码]DOM[代码]操作,或是获取了[代码]location[代码]对象,通用的解决方案一般为判断执行环境,通过[代码]typeof window[代码]是否为[代码]'undefined'[代码],如果遇到必须使用[代码]location[代码]对象的地方用于获取[代码]url[代码]中的相关参数,在[代码]ctx[代码]对象中也可以找到对应参数。 [代码]vue-router[代码]报错[代码]Uncaught TypeError: _Vue.extend is not _Vue function[代码],没有找到[代码]_Vue[代码]实例的问题: 通过查看[代码]Vue-router[代码]源码发现没有手动调用[代码]Vue.use(Vue-Router);[代码]。没有调用[代码]Vue.use(Vue-Router);[代码]在浏览器端没有出现问题,但在服务端就会出现问题。对应的[代码]Vue-router[代码]源码所示: [代码]VueRouter.prototype.init = function init (app /* Vue component instance */) { var this$1 = this; process.env.NODE_ENV !== 'production' && assert( install.installed, "not installed. Make sure to call `Vue.use(VueRouter)` " + "before creating root instance." // ... 服务端无法获取[代码]hash[代码]路由的参数 由于[代码]hash[代码]路由的参数,会导致[代码]vue-router[代码]不起效果,对于使用了[代码]vue-router[代码]的前后端同构应用,必须换为[代码]history[代码]路由。 接口处获取不到[代码]cookie[代码]的问题: 由于客户端每次请求都会对应地把[代码]cookie[代码]带给接口侧,而服务端[代码]Web Server Frame[代码]作为代理服务器,并不会每次维持[代码]cookie[代码],所以需要我们手动把 [代码]cookie[代码]透传给接口侧,常用的解决方案是,将[代码]ctx[代码]挂载到全局状态中,当发起异步请求时,手动带上[代码]cookie[代码],如下代码所示: [代码]// createStore.js // 在创建全局状态的函数`createStore`时,将`ctx`挂载到全局状态 export function createStore({ ctx }) { return new Vuex.Store({ state: { ...state, getters, actions, mutations, modules: { // ... plugins: debug ? [createLogger()] : [], 当发起异步请求时,手动带上[代码]cookie[代码],项目中使用的是[代码]Axios[代码]: [代码]// actions.js // ... const actions = { async getUserInfo({ commit, state }) { let requestParams = { params: { random: tool.createRandomString(8, true), headers: { 'X-Requested-With': 'XMLHttpRequest', // 手动带上cookie if (state.ctx.request.headers.cookie) { requestParams.headers.Cookie = state.ctx.request.headers.cookie; // ... let userInfoRes = await Axios.get(`${requestUrlOrigin}${url.GET_USERINFO}`, requestParams); commit(globalTypes.SET_USERINFO, { userInfo: userInfoRes.data, // ... 接口请求时报[代码]connect ECONNREFUSED 127.0.0.1:80[代码]的问题 原因是改造之前,使用客户端渲染时,使用了[代码]devServer.proxy[代码]代理配置来解决跨域问题,而服务端作为代理服务器对接口发起异步请求时,不会读取对应的[代码]webpack[代码]配置,对于服务端而言会对应请求当前域下的对应[代码]path[代码]下的接口。 解决方案为去除[代码]webpack[代码]的[代码]devServer.proxy[代码]配置,对于接口请求带上对应的[代码]origin[代码]即可: [代码]let requestUrlOrigin; if (state.ctx.URL.hostname === 'localhost' || state.ctx.URL.hostname === '127.0.0.1') { // 本地开发,转到localhost:9000 requestUrlOrigin = constParams.CONST_LOCAL_WEBSERVER_ORIGIN; } else { // 测试环境、正式环境、盒子 requestUrlOrigin = state.ctx.URL.origin; let userInfoRes = await Axios.get(`${requestUrlOrigin}${url.GET_USERINFO}`, requestParams); 对于[代码]vue-router[代码]配置项有[代码]base[代码]参数时,初始化时匹配不到对应路由的问题 在官方示例中的[代码]entry-server.js[代码]: [代码]// entry-server.js import { createApp } from './app'; export default context => { // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise, // 以便服务器能够等待所有的内容在渲染前, // 就已经准备就绪。 return new Promise((resolve, reject) => { const { app, router } = createApp(); // 设置服务器端 router 的位置 router.push(context.url); // ... 原因是设置服务器端[代码]router[代码]的位置时,[代码]context.url[代码]为访问页面的[代码]url[代码],并带上了[代码]base[代码],在[代码]router.push[代码]时应该去除[代码]base[代码],如下所示: [代码]router.push(context.url.replace('/base', '')); 本文为笔者通过对现有项目进行改造,给现有项目加上[代码]Vue[代码]服务端渲染的实践过程的总结。 首先阐述了什么是[代码]Vue[代码]服务端渲染,其目的、本质及原理,通过在服务端使用[代码]Vue[代码]的虚拟[代码]DOM[代码],形成初始化的[代码]HTML[代码]字符串,即应用程序的“快照”。带来极大的性能优势,包括[代码]SEO[代码]优势和首屏渲染的极速体验。之后阐述了[代码]Vue[代码]服务端渲染的基本用法,即两个入口、两个[代码]webpack[代码]配置,分别作用于客户端和服务端,分别生成[代码]vue-ssr-client-manifest.json[代码]与[代码]vue-ssr-server-bundle.json[代码]作为打包结果。最后通过对现有项目的改造过程,包括对路由进行改造、数据预获取和状态初始化,并解释了在[代码]Vue[代码]服务端渲染项目改造过程中的常见问题,帮助我们进行现有项目往[代码]Vue[代码]服务端渲染的迁移。
最近前端届多端框架频出,相信很多有代码多端运行需求的开发者都会产生一些疑惑:这些框架都有什么优缺点?到底应该用哪个? 作为 Taro 开发团队一员,笔者想在本文尽量站在一个客观公正的角度去评价各个框架的选型和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光去看待,权当抛砖引玉。 那么,当我们在讨论多端框架时,我们在谈论什么: 笔者以为,现在流行的多端框架可以大致分为三类: 1. 全包型 这类框架最大的特点就是从底层的渲染引擎、布局引擎,到中层的 DSL,再到上层的框架全部由自己开发,代表框架是 Qt 和 Flutter。这类框架优点非常明显:性能(的上限)高;各平台渲染结果一致。缺点也非常明显:需要完全重新学习 DSL(QML/Dart),以及难以适配中国特色的端:小程序。 这类框架是最原始也是最纯正的的多端开发框架,由于底层到上层每个环节都掌握在自己手里,也能最大可能地去保证开发和跨端体验一致。但它们的框架研发成本巨大,渲染引擎、布局引擎、DSL、上层框架每个部分都需要大量人力开发维护。 2. Web 技术型 这类框架把 Web 技术(JavaScript,CSS)带到移动开发中,自研布局引擎处理 CSS,使用 JavaScript 写业务逻辑,使用流行的前端框架作为 DSL,各端分别使用各自的原生组件渲染。代表框架是 React Native 和 Weex,这样做的优点有: 复用前端生态 易于学习上手,不管前端后端移动端,多多少少都会一点 JS、CSS 交互复杂时难以写出高性能的代码,这类框架的设计就必然导致 [代码]JS[代码] 和 [代码]Native[代码] 之间需要通信,类似于手势操作这样频繁地触发通信就很可能使得 UI 无法在 16ms 内及时绘制。React Native 有一些声明式的组件可以避免这个问题,但声明式的写法很难满足复杂交互的需求。 由于没有渲染引擎,使用各端的原生组件渲染,相同代码渲染的一致性没有第一种高。 3. JavaScript 编译型 这类框架就是我们这篇文章的主角们:[代码]Taro[代码]、[代码]WePY[代码] 、[代码]uni-app[代码] 、 [代码]mpvue[代码] 、 [代码]chameleon[代码],它们的原理也都大同小异:先以 JavaScript 作为基础选定一个 DSL 框架,以这个 DSL 框架为标准在各端分别编译为不同的代码,各端分别有一个运行时框架或兼容组件库保证代码正确运行。 这类框架最大优点和创造的最大原因就是小程序,因为第一第二种框架其实除了可以跨系统平台之外,也都能编译运行在浏览器中。(Qt 有 Qt for WebAssembly, Flutter 有 Hummingbird,React Native 有 [代码]react-native-web[代码], Weex 原生支持) 另外一个优点是在移动端一般会编译到 React Native/Weex,所以它们也都拥有 Web 技术型框架的优点。这看起来很美好,但实际上 React Native/Weex 的缺点编译型框架也无法避免。除此之外,编译型框架的抽象也不是免费的:当 bug 出现时,问题的根源可能出在运行时、编译时、组件库以及三者依赖的库等等各个方面。在 Taro 开源的过程中,我们就遇到过 Babel 的 bug,React Native 的 bug,JavaScript 引擎的 bug,当然也少不了 Taro 本身的 bug。相信其它原理相同的框架也无法避免这一问题。 但这并不意味着这类为了小程序而设计的多端框架就都不堪大用。首先现在各巨头超级 App 的小程序百花齐放,框架会为了抹平小程序做了许多工作,这些工作在大部分情况下是不需要开发者关心的。其次是许多业务类型并不需要复杂的逻辑和交互,没那么容易触发到框架底层依赖的 bug。 那么当你的业务适合选择编译型框架时,在笔者看来首先要考虑的就是选择 DSL 的起点。因为有多端需求业务通常都希望能快速开发,一个能够快速适应团队开发节奏的 DSL 就至关重要。不管是 React 还是 Vue(或者类 Vue)都有它们的优缺点,大家可以根据团队技术栈和偏好自行选择。 如果不管什么 DSL 都能接受,那就可以进入下一个环节: 以下内容均以各框架现在(2019 年 3 月 11日)已发布稳定版为标准进行讨论。 就开发工具而言 [代码]uni-app[代码] 应该是一骑绝尘,它的文档内容最为翔实丰富,还自带了 IDE 图形化开发工具,鼠标点点点就能编译测试发布。 其它的框架都是使用 CLI 命令行工具,但值得注意的是 [代码]chameleon[代码] 有独立的语法检查工具,[代码]Taro[代码] 则单独写了 ESLint 规则和规则集。 在语法支持方面,[代码]mpvue[代码]、[代码]uni-app[代码]、[代码]Taro[代码] 、[代码]WePY[代码] 均支持 TypeScript,四者也都能通过 [代码]typing[代码] 实现编辑器自动补全。除了 API 补全之外,得益于 TypeScript 对于 JSX 的良好支持,Taro 也能对组件进行自动补全。 CSS 方面,所有框架均支持 [代码]SASS[代码]、[代码]LESS[代码]、[代码]Stylus[代码],Taro 则多一个 [代码]CSS Modules[代码] 的支持。 所以这一轮比拼的结果应该是: [代码]uni-app[代码] > [代码]Taro[代码] > [代码]chameleon[代码] > [代码]WePY[代码]、[代码]mpvue[代码] 多端支持度 只从支持端的数量来看,[代码]Taro[代码] 和 [代码]uni-app[代码] 以六端略微领先(移动端、H5、微信小程序、百度小程序、支付宝小程序、头条小程序),[代码]chameleon[代码] 少了头条小程序紧随其后。 但值得一提的是 [代码]chameleon[代码] 有一套自研多态协议,编写多端代码的体验会好许多,可以说是一个能戳到多端开发痛点的功能。[代码]uni-app[代码] 则有一套独立的条件编译语法,这套语法能同时作用于 [代码]js[代码]、样式和模板文件。[代码]Taro[代码] 可以在业务逻辑中根据环境变量使用条件编译,也可以直接使用条件编译文件(类似 React Native 的方式)。 在移动端方面,[代码]uni-app[代码] 基于 [代码]weex[代码] 定制了一套 [代码]nvue[代码] 方案 弥补 [代码]weex[代码] API 的不足;[代码]Taro[代码] 则是暂时基于 [代码]expo[代码] 达到同样的效果;[代码]chameleon[代码] 在移动端则有一套 SDK 配合多端协议与原生语言通信。 H5 方面,[代码]chameleon[代码] 同样是由多态协议实现支持,[代码]uni-app[代码] 和 [代码]Taro[代码] 则是都在 H5 实现了一套兼容的组件库和 API。 [代码]mpvue[代码] 和 [代码]WePY[代码] 都提供了转换各端小程序的功能,但都没有 h5 和移动端的支持。 所以最后一轮对比的结果是: [代码]chameleon[代码] > [代码]Taro[代码]、[代码]uni-app[代码] > [代码]mpvue[代码]、[代码]WePY[代码] 组件库/工具库/demo 作为开源时间最长的框架,[代码]WePY[代码] 不管从 Demo,组件库数量 ,工具库来看都占有一定优势。 [代码]uni-app[代码] 则有自己的插件市场和 UI 库,如果算上收费的框架和插件比起 [代码]WePy[代码] 也是完全不遑多让的。 [代码]Taro[代码] 也有官方维护的跨端 UI 库 [代码]taro-ui[代码] ,另外在状态管理工具上也有非常丰富的选择(Redux、MobX、dva),但 demo 的数量不如前两个。但 [代码]Taro[代码] 有一个转换微信小程序代码为 Taro 代码的工具,可以弥补这一问题。 而 [代码]mpvue[代码] 没有官方维护的 UI 库,[代码]chameleon[代码] 第三方的 demo 和工具库也还基本没有。 所以这轮的排序是: [代码]WePY[代码] > [代码]uni-app[代码] 、[代码]taro[代码] > [代码]mpvue[代码] > [代码]chameleon[代码] 接入成本有两个方面: 第一是框架接入原有微信小程序生态。由于目前微信小程序已呈一家独大之势,开源的组件和库(例如 [代码]wxparse[代码]、[代码]echart[代码]、[代码]zan-ui[代码] 等)多是基于原生微信小程序框架语法写成的。目前看来 [代码]uni-app[代码] 、[代码]Taro[代码]、[代码]mpvue[代码] 均有文档或 demo 在框架中直接使用原生小程序组件/库,[代码]WePY[代码] 由于运行机制的问题,很多情况需要小改一下目标库的源码,[代码]chameleon[代码] 则是提供了一个按步骤大改目标库源码的迁移方式。 第二是原有微信小程序项目部分接入框架重构。在这个方面 Taro 在京东购物小程序上进行了大胆的实践,具体可以查看文章《Taro 在京东购物小程序上的实践》。其它框架则没有提到相关内容。 而对于两种接入方式 Taro 都提供了 [代码]taro convert[代码] 功能,既可以将原有微信小程序项目转换为 Taro 多端代码,也可以将微信小程序生态的组件转换为 Taro 组件。 所以这轮的排序是: [代码]Taro[代码] > [代码]mpvue[代码] 、 [代码]uni-app[代码] > [代码]WePY[代码] > [代码]chameleon[代码] 从 GitHub 的 star 来看,[代码]mpvue[代码] 、[代码]Taro[代码]、[代码]WePY[代码] 的差距非常小。从 NPM 和 CNPM 的 CLI 工具下载量来看,是 Taro(3k/week)> mpvue (2k/w) > WePY (1k/w)。但发布时间也刚好反过来。笔者估计三家的流行程度和案例都差不太多。 [代码]uni-app[代码] 则号称有上万案例,但不像其它框架一样有一些大厂应用案例。另外从开发者的数量来看也是 [代码]uni-app[代码] 领先,它拥有 20+ 个 QQ 交流群(最大人数 2000)。 所以从流行程度来看应该是: [代码]uni-app[代码] > [代码]Taro[代码]、[代码]WePY[代码]、[代码]mpvue[代码] > [代码]chameleon[代码] 一个开源作品能走多远是由框架维护团队和第三方开发者共同决定的。虽然开源建设不能具体地量化,但依然是衡量一个框架/库生命力的非常重要的标准。 从第三方贡献者数量来看,[代码]Taro[代码] 在这一方面领先,并且 [代码]Taro[代码] 的一些核心包/功能(MobX、CSS Modules、alias)也是由第三方开发者贡献的。除此之外,腾讯开源的 [代码]omi[代码] 框架小程序部分也是基于 Taro 完成的。 [代码]WePY[代码] 在腾讯开源计划的加持下在这一方面也有不错的表现;[代码]mpvue[代码] 由于停滞开发了很久就比较落后了;可能是产品策略的原因,[代码]uni-app[代码] 在开源建设上并不热心,甚至有些部分代码都没有开源;[代码]chameleon[代码] 刚刚开源不久,但它的代码和测试用例都非常规范,以后或许会有不错的表现。 那么这一轮的对比结果是: [代码]Taro[代码] > [代码]WePY[代码] > [代码]mpvue[代码] > [代码]chameleon[代码] > [代码]uni-app[代码] 最后补一个总的生态对比图表: 从各框架已经公布的规划来看: [代码]WePY[代码] 已经发布了 [代码]v2.0.alpha[代码] 版本,虽然没有公开的文档可以查阅到 [代码]2.0[代码] 版本有什么新功能/特性,但据其作者介绍,[代码]WePY 2.0[代码] 会放大招,是一个「对得起开发者」的版本。笔者也非常期待 2.0 正式发布后 [代码]WePY[代码] 的表现。 [代码]mpvue[代码] 已经发布了 [代码]2.0[代码] 的版本,主要是更新了其它端小程序的支持。但从代码提交, issue 的回复/解决率来看,[代码]mpvue[代码] 要想在未来有作为首先要打消社区对于 [代码]mpvue[代码] 不管不顾不更新的质疑。 [代码]uni-app[代码] 已经在生态上建设得很好了,应该会在此基础之上继续稳步发展。如果 [代码]uni-app[代码] 能加强开源开放,再加强与大厂的合作,相信未来还能更上一层楼。 [代码]chameleon[代码] 的规划比较宏大,虽然是最后发的框架,但已经在规划或正在实现的功能有: 快应用和端拓展协议 通用组件库和垂直类组件库 面向研发的图形化开发工具 面向非研发的图形化页面搭建工具 如果 [代码]chameleon[代码] 把这些功能都做出来的话,再继续完善生态,争取更多第三方开发者,那么在未来 [代码]chameleon[代码] 将大有可为。 [代码]Taro[代码] 的未来也一样值得憧憬。Taro 即将要发布的 [代码]1.3[代码] 版本就会支持以下功能: 快应用支持 Taro Doctor,自动化检查项目配置和代码合法性 更多的 JSX 语法支持,1.3 之后限制生产力的语法只有 [代码]只能用 map 创造循环组件[代码] 一条 H5 打包体积大幅精简 同时 [代码]Taro[代码] 也正在对移动端进行大规模重构;开发图形化开发工具;开发组件/物料平台以及图形化页面搭建工具。 那说了那么多,到底用哪个呢? 如果不介意尝鲜和学习 DSL 的话,完全可以尝试 [代码]WePY[代码] 2.0 和 [代码]chameleon[代码] ,一个是酝酿了很久的 2.0 全新升级,一个有专门针对多端开发的多态协议。 [代码]uni-app[代码] 和 [代码]Taro[代码] 相比起来就更像是「水桶型」框架,从工具、UI 库,开发体验、多端支持等各方面来看都没有明显的短板。而 [代码]mpvue[代码] 由于开发一度停滞,现在看来各个方面都不如在小程序端基于它的 [代码]uni-app[代码] 。 当然,Talk is cheap。如果对这个话题有更多兴趣的同学可以去 GitHub 另行研究,有空看代码,没空看提交: chameleon: https://github.com/didi/chameleon mpvue: https://github.com/Meituan-Dianping/mpvue Taro: https://github.com/NervJS/taro uni-app: https://github.com/dcloudio/uni-app WePY: https://github.com/Tencent/wepy (按字母顺序排序)
1. 介绍docker是什么Docker使用go基于linux lxc(linux containers)技术实现的开源容器,诞生于2013年年初,最开始叫dotcloud公司,13年年底改名为docker inc。 2017年下载次数达到了百亿次,估值达13亿美元,通过对应用封装(Packaging)、分发(Distribution)、部署(Deployment)、运行(Runtime)全生命周期管理,达到“一次封装,到处运行” [图片] 为何使用docker?Docker直译码头工人,将各种大小和形状的物品装进船里。这对从事软件行业的人来说,听起来很熟悉,花了大量时间和精力把一个应用放在另一个应用里 [图片] docker出现之前,对不同环境的安装、配置、维护工作量很多,如部署,配置文件,crontab,依赖等等。 使用docker,无需关心环境,只需要一些配置就能构建镜像,而部署则用一条run命令 [图片] 虚拟机 vs 容器 虚拟机需要有额外的虚拟机管理应用和虚拟机操作系统层,操作系统层不仅占用空间而且运行速度也相对慢 docker容器是在本机操作系统层面上实现虚拟化,因此很轻量,速度接近原生系统速度 [图片] 虚拟机启动速度是分钟级别,性能较弱、内存和硬盘占用大,一个物理机最多跑几十个虚拟机,但它的隔离性比较好。 docker启停都是秒级实现,内存和硬盘占用非常小,单机支持上千个容器,在ibm服务器上可运行上万个容器 容器跟虚机相比,有着巨大的优势 [图片] docker优点 只关心应用:以往我们需要关心操作系统、软件、项目,有了docker我们可以只关心应用而不是操作系统,docker发展迅速,基于docker的paas平台也层出不穷,使得我们能更方便的使用docker 快速交付:docker可在秒级提供沙箱环境,开发,测试,运维使用完全相同的环境来部署代码 微服务:docker有助于将一个复杂系统分解,让用户用更离散的方式思考服务 离线开发:将服务编排在笔记本中移动办公,使用docker可在本机秒级别启动一个本地开发环境 降低调试成本:在测试和上线时产生无效的类、有问题的依赖、缺少的配置等问题,docker可让一个问题调试和环境重现变得更简单 CD:docker让持续交付实现变得更容易,特别是对于蓝绿部署就更简单。 第一版上线时,需要上第二版新功能,两个版本功能会有冲突,这时用docker实现蓝绿部署就非常方便了 如:可以部署两个版本同时在线,新版本测试没问题了把老版本流量切到新版本就可以了 迁移:可以很快的迁移到其他云或服务器 与传统虚拟机方式相比,容器化方式在很多场景下都是存在极为明显的优势。无论是开发、测试、运维都应该尽快掌握docker,尽早享受其带来的巨大便利 [图片] 容器化方式在很多场景下都有极大的优势。无论是开发、测试、运维都应该尽快掌握docker,尽早享受其带来的巨大便利 [图片] 概念再来了解docker非常关键的概念,这样才能理解docker容器整个生命周期 [图片] 概念—镜像 镜像(类)=文件系统+数据,我常常用开发语言中的类比作镜像,对象比作容器 镜像由多个层加上一些docker元数据组成,容器运行着由镜像定义的系统 [图片] 概念—容器容器(对象)=镜像运行实例 容器是镜像的运行实例,可以使用同一个镜像运行多个实例。如图所示,一个ubuntu docker镜像产生了三个ubuntu容器,docker利用容器运行和隔离应用 [图片] 从读写角度来说,镜像是只读的,容器是在镜像上添加了一层可读写的文件系统 [图片] [图片] 概念—层层=文件变更集合 像传统虚机应用,每个应用都需要拷贝一份文件副本,运行成百上千上磁盘空间会迅速耗光,而docker采用写时复制来减少磁盘空间,当一个运行中的容器要写入一个文件时,它会把该文件复制到新区域来记录这次的修改,在执行docker提交时将这次修改记录下并产生一个新的层。docker分层解决大规模使用容器时碰到的磁盘和效率问题 [图片] 概念—仓库docker借鉴了大量git优秀的经验。docker仓库分公有库和私有库,最大的公开仓库是docker hub,国内也有很多仓库源 [图片] 2. 创建第一个docker应用通过创建一个docker应用来看看docker是怎么方便使用的 创建docker镜像方式 创建docker有四种方式 [图片] 但最常用的docker命令+手工提交和Dockerfile的方式 [图片] 对于我们来说Dockerfile是最常用也是最有用的 “dockerfile” [图片] 那创建一个docker应用只需要三步:编写dockerfile、构建镜像、运行容器 编写dockerfile那我们就开始用dockerfile来创建一个应用 Dockerfile是包含一系列命令的文本文件,这个文件包含6条命令 1、FROM是使用php官方镜像,左边是镜像名字,右边是标签名字,标签名字不写默认是latest 2、声明维护人员 3、RUN运行一条linux命令,我们把php代码重定向到/tmp/index.php 4、EXPOSE声明要开放的端口 5、WORKDIR启动容器后默认目录 6、CMD容器启动后,默认执行的命令,相当于应用的入口,用php自带的webserver监听8000 [图片] 构建镜像使用docker build命令生成镜像,—tag指定镜像的名字,左边是名字,右边是标签,最后有个.表示在当前目录查找Dockerfile 可以看到,每个命令都会有个输入输出,输入是命令,输出是给到层的id,所以,基本上每个命令都会产生一个层 最后提示镜像构建成功,并打上镜像标签 [图片] 运行容器第三,使用docker run命令运行镜像,-p将容器的8000端口映射到本机8000端口,—name给容器起个名字 用curl对本机8000端口请求,服务器返回当前时间,说明我们构建的容器运行成功了 [图片] 请求本地8000端口,服务器返回当前时间 [图片] dockerfile常用命令其实Dockerfile常用命令就5个:from、add、run、workdir、cmd 创建docker应用步骤编写dockerfile 构建镜像 运行容器 使用docker应用步骤拉取镜像 运行容器 dockerfile最佳实践精简镜像用途 尽量让每个镜像的用途单一 选择合适基础镜像 选择以alpine、busybox等基础的镜像 busybox:号称操作系统里的瑞士军刀,只有……这么大,但却有一百多常用命令 如果你的目标是小而精,busybox是首选,因为它已经精简到没有bash,使用的是ash,一个兼容posix的shell [图片] Alpine:你的目标是小但是又有一些工具的话,可以选择alpine,它是一个面向安全的轻量linux发行版,它关注安全、性能和资源效能,比busybox功能更完善,还提供apk查询和安装软件包,大小只有2-3兆 [图片] 很多官方的镜像都有alpine的镜像,像刚刚使用的php镜像 [图片] 提供维护者信息 正确使用版本 使用明确的版本号,而非依赖于默认的latest,避免环境不一致导致的问题 [图片] 删除临时文件 如安装软件后的安装包,如上图2、3步骤 提高生成速度 如内容不变的指令尽量放在前面,这样可以复用 减少镜像层数 多条命令写在一起,使生成的镜像层数少,如上图2、3步骤 恰当使用multi-stage 保证最终生成镜像最小化 3. 常用命令search想使用一个镜像,用这个命令就可以了,默认按评分排序 official如果是ok表示是官方镜像 Auto标示它是否用dickerfile进行自动化镜像构建 [图片] pull一旦确定一个镜像,通过对其名称执行docker pull来下载 标签默认是latest,严格来讲,镜像的仓库名还应该添加仓库地址的,默认是registry.hub.docker.com Docker images命令查找下载的镜像 [图片] run使用docker run运行一个容器,it表示用交互式方式运行,最后表示要执行的命令 [图片] 其实更常用的方式是以后台方式来执行,这时用d参数在后台运行 运行后用exec命令进去到容器 [图片] tagDocker tag给镜像一个新tag名字 Docker images查看centos镜像,把centos:latest打上centos:yeedomliu,这时再看会有3个centos,latest和yeedomliu的镜像id是相同的 把centos:yeedomliu删除,再查看latest还会存在,最后用rmi命令删除latest就会真正把latest镜像删除掉 如果相同镜像存在多个标签,只有最后一次的rmi命令会真正删除镜像 [图片] psPs可以查看运行中的容器 [图片] rmi删除一个镜像,同一个镜像id的不同标签的镜像,使用rmi删除最后一个镜像才会真正删除这个镜像 [图片] rm删除docker容器,如果运行中的容器需要加-f [图片] diff容器启动后文件变化情况 [图片] logs查看容器运行后的日志 [图片] cp我们想从容器里面拷贝文件到宿主机,或相反的过程就可以用到cp命令 [图片] container prune随着使用docker时间越长,停止状态下的容器会越来越多,这些都会占据磁盘空间 [图片] image prune未被打标签的镜像可以用image prune命令清理 [图片] system prune/df如果你觉得刚刚两条命令执行起来麻烦,可以用docker system prune一条命令搞定 另外用system df查看docker磁盘空间 [图片] 4. 实战了解了docker基础知识后,可进入相对实战的环节 本地开发 常见问题 架构 优化 本地开发 我们的项目使用了很多服务,如redis/mysql/mongodb等等,如果一个个运行起来,还加上配置,容易出手,也比较麻烦 kitematic:与使用命令行管理本地容器相比,你更想使用图形工具对容器管理,官方推出的容器管理工具,通过它可以查找镜像、创建容器、配置、启停容器等管理 [图片] [图片] 这是配置容器端口和宿主机端口,目录,网络等映射界面 [图片] docker-composecompose定位是“定义和运行多个docker容器的应用”,前身fig,目前仍然兼容fig格式的模板文件。 一条命令可以把一个复杂的应用启动起来 日常工作中,经常碰到多个容器相互完成某项任务 [图片] docker-compose示例1 默认模板文件名叫docker-compose.yml,结构很简单,每个顶级元素为服务名称,次级信息为配置信息 这里使用了redis/mongodb/mysql/nginx镜像,分别给它们映射了本地目录、端口、密码等信息,nginx镜像需要使用redis/mysql等服务,用links命令连接进来 [图片] docker-compose示例2 如果在本地开发,每个项目都可以像之前说的那样配置,这里提供了另外一种做法 我把公共的资源在一开始就启动,每个项目里只启动nginx镜像并关联其它的服务即可 公共服务compose [图片] 项目compose [图片] 常见问题 主进程:docker启动第一个进程称主进程,就是id为1的进程,这个进程退出就意味着容器退出,所以想要使docker作为服务使用,这个进程是不能退出的 expose命令是声明暴露的端口,运行时用-P才会生效。一般ports命令是做真正的端口映射,比较常用 架构 安装了docker的主机,一般在一个私有网络上 1、调用docker客户端可以从守护进程获取信息或发送指令 2、docker守护进程使用http协议接收来自docker客户端的请求 3、私有docker注册中心存储docker镜像 4、docker hub是由docker公司运营的最大的公共注册中心 互联网上也存在其他公共的注册中心 调用 Docker客户端可以从守护进程获取信息或给它发送指令。守护进程是一个服务器,它使用 HTTP协议接收来自客户端的请求并返回响应。相应地,它会向其他服务发起请求来发送和接收镜像,使用的同样是 HTTP协议。该服务器将接收来自命令行客户端或被授权连接的任何人的请求。守护进程还负责在幕后处理用户的镜像和容器,而客户端充当的是用户与 REST风格 API之间的媒介。 理解这张图的关键在于,当用户在自己的机器上运行 Docker时,与其进行交互的可能是自己机器上的另一个进程,或者甚至是运行在内部网络或互联网上的服务。 [图片] 优化 使用小镜像:一般来说,使用小的镜像都相对比较优秀,如官方的镜像基本上都有基于alpine的镜像 事后清理:删除镜像里软件包或一些临时文件,减小镜像大小 命令写一行:多个命令尽量写在一起有助于减少层数,也会减少镜像的大小 脚本安装:使用脚本进行初始化时,可以有效减少dockerfile的命令,同时带来另外的问题,可读性不好并且构建镜像时缓存不了 扁平化镜像:构建镜像过程中,可能会涉及到一些敏感信息,或者用了上面的办法镜像依然很大,可以试试这个办法 docker export 容器名或容器id | docker import - 镜像标签 multi-stage:从docker 17.05版本开始,docker支持multi-stage(多阶段构建),特别适合编译型语言,如我在一个镜像下编译,在另外一个很小的系统运行,如下图,go项目在golang环境下编译,在alpine环境下运行 [图片]
说到程序开发,bug总是如影随形,开发过程中50%的时间在debug,30%是在修之前发布了的bug,毫不奇怪。 如何把bug见到最少,甚至是0 bug呢?看似遥不可及,但实际是可以追求的,方法就是完整科学的测试,事实上,测试和使用是证明代码没bug的唯一方式。 测试又分为白盒测试跟黑盒测试,一般来说,产品上线前经过测试同学测试做的功能测试都是黑盒测试,毕竟测试同学不可能了解所有的代码,而能做白盒测试的,都是最了解代码的人,也就是写代码的程序员。而只有最了解代码的人也才能写出最完善的测试用例。而单元测试就是白盒测试里面最重要的方法之一。 很多程序员,特别是前端程序员是没有写单元测试的习惯。诚然,写单元测试其实是比较费劲的事情,特别在前端领域,涉及大量ui及交互操作,写单元测试尤为困难。但是近年来js模块化越演越烈,模块化的同时也使得写单元测试变得更容易了。 本文就致力于探讨在当前的开发环境下做单元测试的一些实践方法。 先介绍下被测试的对象 —— 一个js工具库,这个公共库有几个比较重要的标签:ES6,移动端,浏览器端。 从最简单的讲起吧,js的测试框架其实不算少,比较有名的有mocha,Jasmine,Jest等,基本用法都比较简单明了,看看官方文档就大概能写出来一些测试用例了。下面我主要使用比较强大mocha来作为主要的测试框架。 下面是我项目中的一份test.js: [代码]import * as lib from './index'; import chai from 'chai'; let expect = chai.expect; describe('testcase',function(){ it('single one',function(){ let a = 'aer'; expect(a).to.be.a('string'); it('test /common/getUrlParam',function(){ let ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com#a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','https://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('b','http://vip.qq.com?a=1&b=2'); expect(ret).to.equal('2'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1#a=2'); expect(ret).to.equal('2'); 这份test.js包含两个测试用例,第一用例是用于测试的,单纯试用下测试框架跟断言库的功能,第二个用例是对库文件中一个模块的一个方法的测试用例,估计前端大佬们看看方法名大概都能看懂是什么东西,我就不多说了。写好test.js后就跑了试试看,运行mocha,然后马上就报错了: [代码]JUSTYNCHEN-MC0:gxh-lib-es6 justynchen$ ./node_modules/.bin/mocha /path/to/your/project/somelib/test.js:1 (function (exports, require, module, __filename, __dirname) { import * as lib from './index'; SyntaxError: Unexpected token * at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module._compile (internal/modules/cjs/loader.js:656:28) 我当前的node版本是v10.13.0(对,近日node的LTS版本已经升级到10.x了,没升的同学赶紧玩玩吧),按理说是默认支持import的。仔细一看报错,原来mocha不是直接执行test.js,而是把test.js的内容放到了一个沙箱里面执行的。那就有点蛋疼了,就算node新版本已经支持了也没法直接使用。第一想法就是先把代码编译了,然后再做测试,可是测试的代码都是编译后的代码,就算测试出什么问题,还要经过sourcemap才能找到源码中出错的位置,想想都蛋疼。 官方当然是不会这么蠢的,稍微找了下官方的方案,不难找到对es6的支持。babel提供了一个register,给到不同的应用去做转换,mocha同样也可以使用这个register先转换然后再跑。命令就变成了这个: [代码]mocha --require babel-core/register 同时,package.json里面的选项也要加上babel的选项: [代码]"babel": { "presets": [ "stage-3", "latest" 当然,相关的包(babel,babel-core)也要同时装上,大家都懂的后面我就不提了,缺啥装啥就对了,后面提到的工具如没特别说明都是指npm包。 到这里相关的资料还比较好找,接下来就是干货了。 browser 运行上面改造过的mocha命令,是不是就ok了呢?当然没那么顺利,这里遇到了第二个坑: [代码]./node_modules/.bin/mocha --require babel-core/register /path/to/your/project/somelib/common/cache.js:15 var storage = window[storageType]; ReferenceError: window is not defined at initStorage (/Users/justynchen/....../cache.js:10:16) at Object.<anonymous> (/Users/justynchen/....../cache.js:41:16) at Module._compile (internal/modules/cjs/loader.js:688:30) 前面也说了这份库是给移动端浏览器用的,其中就免不了使用一些浏览器的API,这些API在node里面都是不存在的。解决方案有两个: 直接不测试使用了浏览器API的代码,使用前先做检测并return掉。 找一个模拟浏览器的环境,让浏览器的API也能正常执行。 方案一是我们不愿意看到的,特别是一份浏览器用的库,不测试浏览器相关的特性那跟咸鱼有什么区别【手动狗头】。那就按方案二的思路想走吧,想到node模拟浏览器的环境,脑中浮现的第一个名词估计大部分人跟我都一样 —— electron。作为业界最著名的“没有界面的浏览器”,用在这里再合适不过了。但是该怎么用呢,稍作搜索,果然已经有前人做了相应的工作,有一个electron-mocha的工具刚好就是把这两个东西合了起来。 然后命令就变成了这样: [代码]electron-mocha --renderer --require babel-core/register 然后终于得到了我们想要的结果: [代码]JUSTYNCHEN-MC0:somelib justynchen$ ./node_modules/.bin/electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam 2 passing (37ms) 完美~(请自动脑补金星脸) 可是,就这么完了是否有点意犹未尽? 是的,就是缺了点什么东西,说好的0 bug呢,写了测试用例就能保证0 bug了么?肯定不是的,如果有的地方就是有bug,只是用例没写好,并没有覆盖到有问题的地方怎么办?只有写“全”了的测试用例才能保证0 bug。如何确保用例写全了呢?请往下看。 代码测试覆盖率 这里引入一个概念,叫代码测试覆盖率,大概意思就是说,你的测试用例到底覆盖了多少的代码。理想情况下,肯定只有100%覆盖所有代码的用例,才能说自己经过测试的代码是0 bug的,当然现实中100%总是很难的,一般覆盖到90%以上已经是比较理想的情况了。 JS也有统计代码测试覆盖率的库 —— Istanbul。这库名也很有意思,库名直译是伊斯坦布尔,没错,就是那个正常中国人可能名字都没听说过的中东城市。这个地方有个特产是毯子,然后这个库的作者就想,覆盖就是毯子该做的事情嘛,脑洞一开就把库名起作Istanbul了。 继续“稍微读下文档”,哦,原来这个库有一个命令行工具,nyc,装上然后放到执行命令的前面就能做覆盖率统计了。然后就有了下面的命令 [代码]nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register 其中reporter是定制化报告的内容,默认是text,lcov就是生成一个网页版的覆盖率报告。 然而,跑完之后是酱婶儿的 [代码]JUSTYNCHEN-MC0:somelib justynchen$ ./node_modules/.bin/nyc ./node_modules/.bin/electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam ✓ aidMaker test 3 passing (18ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 0 | 0 | 0 | 0 | | ----------|----------|----------|----------|----------|-------------------| 苍天大地啊,为啥啥都没有。。。。 这里就又开始苦逼的查资料环节,不得不吐槽一下,这个资料是真不好查,中文资料没有就算了,反正早习惯了,文档翻遍了还是没有。。。那就过分了。然后查了下别的资料,基本都是mocha跟Istanbul一起用的,也没有electron-mocha相关的。 最后还是找到了github的issue里面,果然有人是跟我有类似问题,找了几个提了没啥回音的,终于找到一个maintainer的回复。里面指向了一个插件 —— babel-plugin-istanbul。 皇天不负有心人,得益于之前已经引入了babel,这里只要加上这个插件就ok了,照例先装包,package.json里面的babel配置加上这个参数 [代码]"babel": { "presets": [ "stage-3", "latest" "env": { "test": { "plugins": [ "istanbul" ] 然后按照插件的README改一下命令,最终得到了这个 [代码]cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register 然后一跑。。。发现,x你x的。。。跟前面的输出一毛一样,啥的没有!!! 冷静一下,再回头看看maintainer回复的原话 Basically, yes. Note that if you’re using babel already you can now just use the excellent istanbul-plugin to instrument your code. If you do this, you really only need to write out the __coverage__ object after the tests have run. (I write them out for both renderer and main thread tests, then combine them using nyc report from the command line). 里面提到了一个奇怪的参数__coverage__,又查一波资料,在这个项目的某些commit comment里面看到这个__coverage__的蛛丝马迹,这东西好像是一个全局参数,那这个参数有啥用咧?管他有用没用,打出来看看再说,然后就在test.js里面吧这个参数打了下,发现,卧槽,还真有,而且里面不就是覆盖率的数据么???? 嗯,有数据。。。怎么生成报告呢?作者写的语焉不详。。。啥叫write out这个参数然后配合nyc report命令就能用了,write到啥地方啊,咋配合啊!!! 这个时候就想到了Istanbul的一些特性,其实它是会在测试后生成一个.nyc_output的文件夹的,打开一看,里面不就是一些json么!那是不是直接write进去就好了呢?文件该叫啥名字咧,原来的文件都是hash命名的,这hash哪来的呀。不管了写了再说,然后得到如下test.js [代码]import * as lib from './index'; import chai from 'chai'; import fs from 'fs'; let expect = chai.expect; describe('testcase',function(){ it('single one',function(){ let a = 'aer'; expect(a).to.be.a('string'); it('test /common/getUrlParam',function(){ let ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com#a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','https://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('b','http://vip.qq.com?a=1&b=2'); expect(ret).to.equal('2'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1#a=2'); expect(ret).to.equal('2'); after(function() { fs.writeFileSync('./.nyc_output/coverage.json',JSON.stringify(__coverage__)); 终于生效了 [代码]JUSTYNCHEN-MC0:somelib justynchen$ tnpm run test > @tencent/somelib@1.0.1 test /path/to/your/project/..... > cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam 3 passing (26ms) ----------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------------------|----------|----------|----------|----------|-------------------| All files | 5.96 | 3.3 | 3.6 | 6.02 | | gxh-lib-es6 | 0 | 0 | 0 | 0 | | index.js | 0 | 0 | 0 | 0 | | gxh-lib-es6/business | 16.89 | 8.84 | 3.45 | 16.89 | | aid-maker.js | 80 | 66.67 | 100 | 80 | 325,340,341,344 | cgi-handler.js | 0 | 0 | 0 | 0 |... 57,159,160,161 | index.js | 0 | 0 | 0 | 0 | | pay.js | 0 | 0 | 0 | 0 |... 86,88,89,91,97 | 至此,终于可以说出那句 整体架构图 后记:整个单元测试的技术其实都没什么困难的,基本上都有库可以用,主要把时间都花在了查询资料上面。写本文的时候好像是遇到问题马上就找到了解决方案,其实真实情况是,几乎遇到每个坑都会试了至少一两个走不通的方案,最后才找到正确的方案的,所以对于不经常关注社区的人来说,单靠文档是很难解决所有的问题的。单从前端领域来看,前端的技术日新月异,再完善的文档都很快会跟不上发展的步伐,还是要靠多关注社区的动向,甚至多参与社区的讨论和建设才不至于在需要用某些技术的时候无从下手。
自定义组件 代码的复用 在起初小程序只支持 Page 的时候,就会有这样蛋疼的问题:多个页面有相同的组件,每个页面都要复制粘贴一遍,每次改动都要全局搜索一遍,还说不准哪里改漏了就出翔了。 组件化设计 在前端项目中,组件化是很常见的方式,某块通用能力的抽象和设计,是一个必备的技能。组件的管理、数据的管理、应用状态的管理,这些在我们设计的过程中都是需要去思考的。当然你也可以说我就堆代码就好了,不过一个真正的码农是不允许自己这么随便的! 所以,组件化是现代前端必须掌握的生存技能! 自定义组件的实现 一切都从 Virtual DOM 说起 前面《解剖小程序的 setData》有讲过,基于小程序的双线程设计,视图层(Webview 线程)和逻辑层(JS 线程)之间通信(表现为 setData),是基于虚拟 DOM 来实现数据通信和模版更新的。 自定义组件一样的双线程,所以一样滴基于 Virtual DOM 来实现通信。那在这里,Virtual DOM 的一些基本知识(包括生成 VD 对象、Diff 更新等),就不过多介绍啦~ Shadow DOM 模型 基于 Virtual DOM,我们知道在这样的设计里,需要一个框架来支撑维护整个页面的节点树相关信息,包括节点的属性、事件绑定等。在小程序里,Exparser 承担了这个角色。 前面《关于小程序的基础库》也讲过,Exparser 的主要特点包括: 基于 Shadow DOM 模型 可在纯 JS 环境中运行 Shadow DOM 是什么呢,它就是我们在写代码时候写的自定义组件、内置组件、原生组件等。Shadow DOM 为 Web 组件中的 DOM 和 CSS 提供了封装。Shadow DOM 使得这些东西与主文档的 DOM 保持分离。 简而言之,Shadow DOM 是一个 HTML 的新规范,其允许开发者封装 HTML 组件(类似 vue 组件,将 html,css,js 独立部分提取)。 例如我们定义了一个自定义组件叫[代码]<my-component>[代码],你在开发者工具可以见到: [代码]#shadow-root[代码]称为影子根,DOM 子树的根节点,和文档的主要 DOM 树分开渲染。可以看到它在[代码]<my-component>[代码]里面,换句话说,[代码]#shadow-root[代码]寄生在[代码]<my-component>[代码]上。[代码]#shadow-root[代码]可以嵌套,形成节点树,即称为影子树(Shadow Tree)。 Shadow Tree 拼接 既然组件是基于 Shadow DOM,那组件的嵌套关系,其实也就是 Shadow DOM 的嵌套,也可称为 Shadow Tree 的拼接。 Shadow Tree 拼接是怎么做的呢?一切又得从模版引擎讲起。 我们知道,Virtual DOM 机制会将节点解析成一个对象,那这个对象要怎么生成真正的 DOM 节点呢?数据变更又是怎么更新到界面的呢?这大概就是模版引擎做的事情了。 《前端模板引擎》里有详细描述模版引擎的机制,通常来说主要有这些: DOM 节点的创建和管理:[代码]appendChild[代码]/[代码]insertBefore[代码]/[代码]removeChild[代码]/[代码]replaceChild[代码]等 DOM 节点的关系(嵌套的处理):[代码]parentNode[代码]/[代码]childNodes[代码] 通常创建后的 DOM 节点会保存一个映射,在更新的时候取到映射,然后进行处理(通常包括替换节点、改变内容[代码]innerHTML[代码]、移动删除新增节点、修改节点属性[代码]setAttribute[代码]) 在上面的图我们也可以看到,在 Shadow Tree 拼接的过程中,有些节点并不会最终生成 DOM 节点,例如[代码]<slot>[代码]这种。 但是,常用的前端模版引擎,能直接用在小程序里吗? 双线程的难题 自定义组件渲染流程 双线程的设计,给小程序带来了很多便利,安全性管控力都拥有了,当然什么鬼东西都可以比作一把双刃剑,双线程也不例外。 我们知道,小程序分为 Webview 和 JS 双线程,逻辑层里是没法拿到真正的 DOM 节点,也没法随便动态变更页面的。那在这种情况下,我们要怎么去使用映射来更新模版呢(因为我们压根拿不到 Webview 节点的映射)? 所以在双线程下,其实两个线程都需要保存一份节点信息。这份节点信息怎么来的呢?其实就是我们需要在创建组件的时候,通过事件通知的方式,分别在逻辑层和视图层创建一份节点信息。 同时,视图层里的组件是有层级关系的,但是 JS 里没有怎么办?为了维护好父子嵌套等节点关系,所以我们在 逻辑层也需要维护一棵 Shadow Tree。 那么我们自定义组件的渲染流程大概是: 组件创建。 逻辑层:先是 wxml + js 生成一个 JS 对象(因为需要访问组件实例 this 呀),然后是 JS 其中节点部分生成 Virtual DOM,拼接 Shadow Tree 什么的,最后通过底层通信通知到 视图层 视图层:拿到节点信息,然后吭哧吭哧开始创建 Shadow DOM,拼接 Shadow Tree 什么的,最后生成真实 DOM,并保留下映射关系 组件更新。 这时候我们知道,不管是逻辑层,还是视图层,都维护了一份 Shadow Tree,要怎么保证他们之间保持一致呢? 让 JS 和 Webview 的组件保持一致 为了让两边的 Shadow Tree 保持一致,可以使用同步队列来传递信息。(这样就不会漏掉啦) 同步队列可以,每次变动我们就往队列里塞东西就好了。不过这样还会有个问题,我们也知道 setData 其实在实际项目里是使用比较频繁的,要是像 Component 的 observer 里做了 setData 这类型的操作,那不是每次变动会导致一大堆的 setDate?这样通信效率会很低吧? 所以,其实可以把一次操作里的所有 setData 都整到一次通信里,通过排序保证好顺序就好啦。 Page 和 Component Component 是 Page 的超集 事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含[代码]usingComponents[代码]定义段。 来自官方文档-Component 所以,基于 Component 是 Page 的超集,那么其实组件的渲染流程、方式,其实跟页面没多大区别,应该可以一个方式去理解就差不多啦。 既然页面就是组件,那其实页面的渲染流程跟组件的渲染流程基本保持一致。 视图层渲染,可以参考7.4 视图层渲染说明。 其实很多新框架新工具出来的时候,经常会让人眼前一亮,觉得哇好厉害,哇好高大上。 但其实更多时候,我们需要挖掘新事物的核心,其实大多数都是在原有的事物上增加了个新视角,从不一样的视角看,看到的就不一样了呢。作为一名码农,我们要看到不变的共性,变化的趋势。
开发者工具 Nightly Build 版本 近期包含了如下云开发能力的更新: 云调用 是云开发提供的基于云函数使用小程序开放接口的能力,目前覆盖服务端调用的场景,后续将会陆续开放开放数据调用、消息推送、支付等其他多种使用场景。 云调用需要在云函数中通过 wx-server-sdk 使用。在云函数中调用服务端接口不再需要换取 access_token,只要是在从小程序端触发的云函数中发起的云调用都经过微信自动鉴权,可直接在 SDK 中发起调用。目前支持云调用的服务端开放接口列表可在 服务端接口列表页 查看。 想快速体验云调用和查看代码示例,可下载最新 Nightly Build 版的开发者工具,新建云开发快速启动模板项目,在其中新增了云调用的部分: 云函数本地调试 IDE 新增了云函数本地调试功能,方便开发者在本地进行云函数调试,提高开发效率。云函数开发可以不再需要频繁上传测试,只需用 IDE 在本地调试完成再上传到现网验证。开发者可在云函数根目录右键选择本地调试来开启本地调试界面。 详细内容可参考文档。 全新的云开发控制台 云开发控制台经过全新设计和改版,优化交互和视觉体验,功能分类更加清晰、各项功能更加易用。 可以在开发者工具 Nightly Build 版本 下载并体验上述功能。如果有遇到任何使用问题可以在本帖提出。
什么是[代码]Service Worker[代码] [代码]Service Worker[代码]本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步[代码]API[代码]。 [代码]Service Worker[代码]的本质是一个[代码]Web Worker[代码],它独立于[代码]JavaScript[代码]主线程,因此它不能直接访问[代码]DOM[代码],也不能直接访问[代码]window[代码]对象,但是,[代码]Service Worker[代码]可以访问[代码]navigator[代码]对象,也可以通过消息传递的方式(postMessage)与[代码]JavaScript[代码]主线程进行通信。 [代码]Service Worker[代码]是一个网络代理,它可以控制[代码]Web[代码]页面的所有网络请求。 [代码]Service Worker[代码]具有自身的生命周期,使用好[代码]Service Worker[代码]的关键是灵活控制其生命周期。 [代码]Service Worker[代码]的作用 用于浏览器缓存 实现离线[代码]Web APP[代码] [代码]Service Worker[代码]兼容性 [代码]Service Worker[代码]是现代浏览器的一个高级特性,它依赖于[代码]fetch API[代码]、[代码]Cache Storage[代码]、[代码]Promise[代码]等,其中,[代码]Cache[代码]提供了[代码]Request / Response[代码]对象对的存储机制,[代码]Cache Storage[代码]存储多个[代码]Cache[代码]。 在了解[代码]Service Worker[代码]的原理之前,先来看一段[代码]Service Worker[代码]的示例: [代码]self.importScripts('./serviceworker-cache-polyfill.js'); var urlsToCache = [ '/index.js', '/style.css', '/favicon.ico', var CACHE_NAME = 'counterxing'; self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; return fetch(event.request); self.addEventListener('activate', function(event) { var cacheWhitelist = ['counterxing']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); 下面开始逐段逐段地分析,揭开[代码]Service Worker[代码]的神秘面纱: [代码]polyfill[代码] 首先看第一行:[代码]self.importScripts('./serviceworker-cache-polyfill.js');[代码],这里引入了Cache API的一个polyfill,这个[代码]polyfill[代码]支持使得在较低版本的浏览器下也可以使用[代码]Cache Storage API[代码]。想要实现[代码]Service Worker[代码]的功能,一般都需要搭配[代码]Cache API[代码]代理网络请求到缓存中。 在[代码]Service Worker[代码]线程中,使用[代码]importScripts[代码]引入[代码]polyfill[代码]脚本,目的是对低版本浏览器的兼容。 [代码]Cache Resources List[代码] And [代码]Cache Name[代码] 之后,使用一个[代码]urlsToCache[代码]列表来声明需要缓存的静态资源,再使用一个变量[代码]CACHE_NAME[代码]来确定当前缓存的[代码]Cache Storage Name[代码],这里可以理解成[代码]Cache Storage[代码]是一个[代码]DB[代码],而[代码]CACHE_NAME[代码]则是[代码]DB[代码]名: [代码]var urlsToCache = [ '/index.js', '/style.css', '/favicon.ico', var CACHE_NAME = 'counterxing'; [代码]Lifecycle[代码] [代码]Service Worker[代码]独立于浏览器[代码]JavaScript[代码]主线程,有它自己独立的生命周期。 如果需要在网站上安装[代码]Service Worker[代码],则需要在[代码]JavaScript[代码]主线程中使用以下代码引入[代码]Service Worker[代码]。 [代码]if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { console.log('成功安装', registration.scope); }).catch(function(err) { console.log(err); 此处,一定要注意[代码]sw.js[代码]文件的路径,在我的示例中,处于当前域根目录下,这意味着,[代码]Service Worker[代码]和网站是同源的,可以为当前网站的所有请求做代理,如果[代码]Service Worker[代码]被注册到[代码]/imaging/sw.js[代码]下,那只能代理[代码]/imaging[代码]下的网络请求。 可以使用[代码]Chrome[代码]控制台,查看当前页面的[代码]Service Worker[代码]情况: 安装完成后,[代码]Service Worker[代码]会经历以下生命周期: 下载([代码]download[代码]) 安装([代码]install[代码]) 激活([代码]activate[代码]) 用户首次访问[代码]Service Worker[代码]控制的网站或页面时,[代码]Service Worker[代码]会立刻被下载。之后至少每[代码]24[代码]小时它会被下载一次。它可能被更频繁地下载,不过每[代码]24[代码]小时一定会被下载一次,以避免不良脚本长时间生效。 在下载完成后,开始安装[代码]Service Worker[代码],在安装阶段,通常需要缓存一些我们预先声明的静态资源,在我们的示例中,通过[代码]urlsToCache[代码]预先声明。 在安装完成后,会开始进行激活,浏览器会尝试下载[代码]Service Worker[代码]脚本文件,下载成功后,会与前一次已缓存的[代码]Service Worker[代码]脚本文件做对比,如果与前一次的[代码]Service Worker[代码]脚本文件不同,证明[代码]Service Worker[代码]已经更新,会触发[代码]activate[代码]事件。完成激活。 如图所示,为[代码]Service Worker[代码]大致的生命周期: [代码]install[代码] 在安装完成后,尝试缓存一些静态资源: [代码]self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); 首先,[代码]self.skipWaiting()[代码]执行,告知浏览器直接跳过等待阶段,淘汰过期的[代码]sw.js[代码]的[代码]Service Worker[代码]脚本,直接开始尝试激活新的[代码]Service Worker[代码]。 然后使用[代码]caches.open[代码]打开一个[代码]Cache[代码],打开后,通过[代码]cache.addAll[代码]尝试缓存我们预先声明的静态文件。 监听[代码]fetch[代码],代理网络请求 页面的所有网络请求,都会通过[代码]Service Worker[代码]的[代码]fetch[代码]事件触发,[代码]Service Worker[代码]通过[代码]caches.match[代码]尝试从[代码]Cache[代码]中查找缓存,缓存如果命中,则直接返回缓存中的[代码]response[代码],否则,创建一个真实的网络请求。 [代码]self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; return fetch(event.request); 如果我们需要在请求过程中,再向[代码]Cache Storage[代码]中添加新的缓存,可以通过[代码]cache.put[代码]方法添加,看以下例子: [代码]self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // 缓存命中 if (response) { return response; // 注意,这里必须使用clone方法克隆这个请求 // 原因是response是一个Stream,为了让浏览器跟缓存都使用这个response // 必须克隆这个response,一份到浏览器,一份到缓存中缓存。 // 只能被消费一次,想要再次消费,必须clone一次 var fetchRequest = event.request.clone(); return fetch(fetchRequest).then( function(response) { // 必须是有效请求,必须是同源响应,第三方的请求,因为不可控,最好不要缓存 if (!response || response.status !== 200 || response.type !== 'basic') { return response; // 消费过一次,又需要再克隆一次 var responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); return response; 在项目中,一定要注意控制缓存,接口请求一般是不推荐缓存的。所以在我自己的项目中,并没有在这里做动态的缓存方案。 [代码]activate[代码] [代码]Service Worker[代码]总有需要更新的一天,随着版本迭代,某一天,我们需要把新版本的功能发布上线,此时需要淘汰掉旧的缓存,旧的[代码]Service Worker[代码]和[代码]Cache Storage[代码]如何淘汰呢? [代码]self.addEventListener('activate', function(event) { var cacheWhitelist = ['counterxing']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); 首先有一个白名单,白名单中的[代码]Cache[代码]是不被淘汰的。 之后通过[代码]caches.keys()[代码]拿到所有的[代码]Cache Storage[代码],把不在白名单中的[代码]Cache[代码]淘汰。 淘汰使用[代码]caches.delete()[代码]方法。它接收[代码]cacheName[代码]作为参数,删除该[代码]cacheName[代码]所有缓存。 sw-precache-webpack-plugin sw-precache-webpack-plugin是一个[代码]webpack plugin[代码],可以通过配置的方式在[代码]webpack[代码]打包时生成我们想要的[代码]sw.js[代码]的[代码]Service Worker[代码]脚本。 一个最简单的配置如下: [代码]var path = require('path'); var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const PUBLIC_PATH = 'https://www.my-project-name.com/'; // webpack needs the trailing slash for output.publicPath module.exports = { entry: { main: path.resolve(__dirname, 'src/index'), output: { path: path.resolve(__dirname, 'src/bundles/'), filename: '[name]-[hash].js', publicPath: PUBLIC_PATH, plugins: [ new SWPrecacheWebpackPlugin( cacheId: 'my-project-name', dontCacheBustUrlsMatching: /\.\w{8}\./, filename: 'service-worker.js', minify: true, navigateFallback: PUBLIC_PATH + 'index.html', staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], 在执行[代码]webpack[代码]打包后,会生成一个名为[代码]service-worker.js[代码]文件,用于缓存[代码]webpack[代码]打包后的静态文件。 一个最简单的示例。 [代码]Service Worker Cache[代码] VS [代码]Http Cache[代码] 对比起[代码]Http Header[代码]缓存,[代码]Service Worker[代码]配合[代码]Cache Storage[代码]也有自己的优势: 缓存与更新并存:每次更新版本,借助[代码]Service Worker[代码]可以立马使用缓存返回,但与此同时可以发起请求,校验是否有新版本更新。 无侵入式:[代码]hash[代码]值实在是太难看了。 不易被冲掉:[代码]Http[代码]缓存容易被冲掉,也容易过期,而[代码]Cache Storage[代码]则不容易被冲掉。也没有过期时间的说法。 离线:借助[代码]Service Worker[代码]可以实现离线访问应用。 但是缺点是,由于[代码]Service Worker[代码]依赖于[代码]fetch API[代码]、依赖于[代码]Promise[代码]、[代码]Cache Storage[代码]等,兼容性不太好。 本文只是简单总结了[代码]Service Worker[代码]的基本使用和使用[代码]Service Worker[代码]做客户端缓存的简单方式,然而,[代码]Service Worker[代码]的作用远不止于此,例如:借助[代码]Service Worker[代码]做离线应用、用于做网络应用的推送(可参考push-notifications)等。 甚至可以借助[代码]Service Worker[代码],对接口进行缓存,在我所在的项目中,其实并不会做的这么复杂。不过做接口缓存的好处是支持离线访问,对离线状态下也能正常访问我们的[代码]Web[代码]应用。 [代码]Cache Storage[代码]和[代码]Service Worker[代码]总是分不开的。[代码]Service Worker[代码]的最佳用法其实就是配合[代码]Cache Storage[代码]做离线缓存。借助于[代码]Service Worker[代码],可以轻松实现对网络请求的控制,对于不同的网络请求,采取不同的策略。例如对于[代码]Cache[代码]的策略,其实也是存在多种情况。例如可以优先使用网络请求,在网络请求失败时再使用缓存、亦可以同时使用缓存和网络请求,一方面检查请求,一方面有检查缓存,然后看两个谁快,就用谁。 优化方向:目前我所负责的DICOM项目,虽然还没有用上[代码]Service Worker[代码],但前面经过不断地优化迭代,通过从增加http层的缓存、无损压缩图像的替换、有损压缩图像的渐进加载、更换DICOM解压缩策略、使用indexed DB缓存CT图像、首屏可见速度已经从20多秒降低到5秒左右,内存占用从700M以上降低到250M左右。后期还会一直深挖这一块。主要方向之一就是service worker的替换,全站缓存静态资源。此外,高优先级的则是DICOM无损图像解压算法的最优选择与优化、cornerstone的jpg图像展示。 项目优化还在继续,力求极致性能和用户体验~
接着上篇文章《小程序架构设计(一)》 前边我们说到采用Web+离线包的方式可以解决很多问题,但是遗留了一个安全问题有待解决。 经过了一番讨论,我们决定把开发者的JS逻辑代码放到单独的线程去运行,因为不在Webview线程里,所以这个环境没有Webview任何接口,自然的开发者就没法直接操作Dom,也就没法动态去更改界面,“管控”的问题得以解决。 还存在一个问题:开发者没法操作Dom,如果用户交互需要界面变化的话,开发者就没办法动态变化界面了。所以我们要找到一个办法:不直接操作Dom也能做到界面更新。 其实Facebook早有方案解决这个问题,就是上篇文章提到的React。React引入了Virtual Dom的概念(后文简称VD),业务侧只需要改变数据即可引起界面变化,相关原理后边再写篇文章来分享。 至此小程序双线程的模型就定下来了:渲染层(Webview)+逻辑层(JSCore) 其中渲染层用了Webview进行渲染,开发者的JS逻辑运行在一个独立的JSCore线程。 渲染层提供了带有数据绑定语法的WXML,逻辑层提供了setData等等API,开发者需要进行界面变化时,只需要通过setData把变化的数据传进去,小程序框架就会进行Dom Diff等流程最后把正确的结果更新在Dom树上。 可以看到在开发者的逻辑下层,还需要有一层小程序框架的支持(数据通信、API、VD算法等等),我们把它称为基础库。 我们在两个线程各自注入了一份基础库,渲染层的基础库含有VD的处理以及底层组件系统的机制,对上层提供一些内置组件,例如video、image等等。逻辑层的基础库主要会提供给上层一些API,例如大家经常用到的wx.login、wx.getSystemInfo等等。 解决了渲染问题,我们还要看一下用户在和界面交互时的问题。 用户在屏幕点击某个按钮,开发者的逻辑层要处理一些事情,然后再通过setData引起界面变化,整个过程需要四次通信。对于一些强交互(例如拖动视频进度条)的场景,这样的处理流程会导致用户的操作很卡。 对于这种强交互的场景,我们引入了原生组件,这样用户和原生组件的交互可以节省两次通信。 正如上图所示,原生组件和Webview不是在同一层级进行渲染,原生组件其实是叠在Webview之上,想必大家都遇到过这个问题,video、input、map等等原生组件总是盖在其他组件之上,这就是这个设计带来的问题。 我们也很重视这个问题,经过了一段时间的努力,我们攻克了这个难题,把原生组件渲染到Webview里,从而实现同层渲染。目前video组件已经完成同层渲染的全量发布,详细可以看我们之前的公告:同层渲染公测。 为了让开发者可以更好的开发小程序,我们在后来还引入了自定义组件和插件的概念,我们后续会有相关的文章再介绍这两块的设计,希望大家关注我们社区的文章板块。 以上就是小程序架构设计的历史。
一、写作背景 接触小程序一年多,真实体验就是小程序开发门槛相对而言确实比较低。不过小程序的开发方式,一直是开发者吐槽的,如习惯了 Vue,React 开发的开发者经常会吐槽小程序一个 Page 必须由多个文件组成,组件化支持不完善或者说不能非常愉快的开发组件。在以前小项目中没太大感觉,从加入有赞,参与有赞微商城小程序的开发,是真切的体会到对于大型小程序项目开发的复杂性。 有赞从微信小程序内测就开始开发小程序,在不支持自定义组件的时代,只能通过 import 的形式拆分模块或实现组件。在业务复杂的页面,可能会 import 非常多的模块,而相应的 wxss 也需要 import 样式,除了操作繁琐,有时候也难免遗漏。 作为开发者,我们当然希望可以让工作更简单,更愉快,也希望改善我们的开发方式。所以希望能够更了解微信小程序框架,减少不必要的试错,于是有了一次对小程序框架的 debug 之旅。(基础库 1.9.93) 通过三周空余时间的 debug,也算对小程序框架有了一些浅显的认识,达到了最初的目的;对小程序启动,实例,运行等有了真切的体会。这篇文章记录了小程序框架的基本代码结构,启动流程,以及程序实例化过程。 本文的目的是希望把我看到的分享给对小程序感兴趣或者正在开发小程序的读者,主要解答“框架对传入的对象等到底做了什么”。 二、从启动流程一窥小程序框架细节 在开发者工具中使用 help() 方法,可以查看一些指令和方法。使用其中的 openVendor 方法可以打开微信开发者工具在小程序框架所在目录。其中以包括以基础库命名的目录和其他帮助文件,如其中有两个工具 wcc,wcsc。wcc 可把 wxml 转换为对应的 JS 函数 —— $gwx(path, global),wcsc 可将 wxss 转换为 css。而基础库目录包括 WAService.js 和 WAWebview.js 文件。小程序框架在开发者工具中以 WAService.js 命名(WAWebview.js 不知其作用,听说在真机环境使用该文件)。 在开发中工具命令行使用 document.head 可以查看到小程序的启动流程大致如下: 以小节的方式分别介绍这些流程,小程序是如何处理的(小节编号与图中编号相同)。 1、初始化全局变量 下图是小程序启动是初始化的一些全局的变量: 那些使用“__”开头,未在文档中提及可使用变量是不建议使用的,wxAppCode 在开发者工具中分为两类值,json 类型和 wxml 类型。以 .json 结尾的,其 key 值为开发者代码中对应的 json 文件的内容,.wxml 结尾的,其 key 值为通过调用 $gwx(’./pages/example/index.wxml’) 将得到一个可执行函数,通过调用这个函数可得到一个标识节点关系的 JSON 树。 2、加载框架(WAService.js) 使用工具对 WAService.js 进行格式化后进行 debug。可以发现小程序框架大致由: WeixinJSBridge、 NativeBuffer、 wxConsole、 WeixinWorker、 JavaScript兼容(这部分为猜测)、 Reporter、 wx、 exparser、 virtualDOM、 appServiceEngine 几部分组成。 其中除了 wx 和 WeixinJSBridge 这两个基础 API 集合, exparser, virtualDOM, appServiceEngine 这三部分作为框架的核心, appServiceEngine 提供了框架最基本的接口如 App,Page,Component; exparser 提供了框架底层的能力,如实例化组件,数据变化监听,view 层与逻辑层的交互等;而 virtualDOM 则起着链接 appServiceEngine 和 exparser 的作用,如对开发者传入 Page 方法的对象进行格式化再传入 exparser 的对应方法处理。 框架对外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。 3、业务代码的加载 在小程序中,开发者的 JavaScript 代码会被打包为 [代码]define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) { 'use strict'; // your code 这里的 define 是在框架中定义的方法,在框架中提供了两个方法:require 和 define 用来定义和使用业务代码。其方式有些像 AMD 规范接口,通过 define 定义一个模块,使用 require 来应用一个模块。但是也有很大区别,首先 define 限制了模块可使用的其他模块,如 window,document;其次 require 在使用模块时只会传入 require 和 module,也就是说参数中的其他模块在定义的模块中都是 undefined,这也是不能在开发者工具中获取一些浏览器环境对象的原因。 在小程序中,JavaScript 代码的加载方式和在浏览器中也有些不同,其加载顺序是首先加载项目中其他 js 文件(非注册程序和注册页面的 js 文件),其次是注册程序的 app.js,然后是自定义组件 js 文件,最后才是注册页面的 js 代码。而且小程序对于在 app.js 以及注册页面的 js 代码都会加载完成后立即使用 require 方法执行模块中的程序。其他的代码则需要在程序中使用 require 方法才会被执行。 下面详细介绍了 app.js,自定义组件,页面 js 代码的处理流程。 4、加载 app.js 与注册程序 在 app.js 加载完成后,小程序会使用 require(‘app.js’) 注册程序,即对 App 方法进行调用,App 方法是对 appServiceEngine.App 方法的引用。 下图是框架对于 App 方法调用时的处理流程: App 方法根据传入的对象实例化一个 app 实例,其生命周期函数 onLaunch 和 onShow 因为使用不同的方式获取 options的参数。在有些需要根据场景值来实现需求的,或许使用 onShow 中的场景值更合适。 在实际开发过程中发现,在微信顶部唤起小程序和在小程序列表唤起的 options 也是不一样的。在该案例中通过点击分享的小程序进入后,关闭小程序,再通过不同方式进入小程序,通过顶部唤起的还是 options 的 path 属性还是分享出来的 path,但是通过列表中打开直接回到了首页,这里 App 中的 onShow 就会获取到不同的 options。 5、加载自定义组件代码以及注册自定义组件 自定义组件在 app.js 之后被加载,小程序会在这个过程中加载完所有的自定义组件(分包中自定义组件没有有测试过),并且是加载完成后自动注册,只有注册完成后才会加载下一个自定义组件的代码。 下图是框架对于 Component 方法处理流程: 图中介绍了框架如何对传入 Component 方法的对象的处理,其后面还有很多深入的对于组件实例化的步骤没有在图中表示出来,具体可以在文章最后的附件中查看。 自定义组件在小程序中越来越完善,其拥有的能力也比 Page 更强大,而后面会提到在使用自定义组件的 Page 中,Page 实例也会使用和自定义组件一样的实例化方式,也就是说,他拥有和自定义组件一样的能力。 6、加载页面代码和注册页面 加载页面代码的处理流程和加载自定义组件一样,都是加载完成后先注册页面,然后才会加载下一个页面。 下图是注册一个页面时框架对于 Page 方法的处理流程: Page 方法会根据是否使用自定义组件做不同的处理。使用自定义组件的 page 对象会被处理为和自定义组件的结构,并在页面实例化时使用不同的处理流程进行实例化。当然对于开发而言没任何不同。 从图中可以发现 Page 传入的(生命周期)代码并不会在这里被执行,可以通过下面小节了解 Page 实例化的详细过程。 7、等待页面 Ready 和 Page 实例化 还记得上面介绍的启动流程中最后一步等待页面 Ready?严格来讲是等待浏览器 Ready,小程序虽然有部分原生的组件,不过本质上还是一个 web 程序。 在小程序中切换页面或打开页面时会触发 onAppRoute 事件,小程序框架通过 wx.onAppRoute 注册页面切换的处理程序,在所有程序就绪后,以 entryPagePath 作为入口使用 appLaunch 的方式进入页面。 下图是处理导航的程序流程: 从图中可以看出页面的实例化是在进入页面时进行,下图是具体的实例化过程: 下图是最终可得到 Page 实例: 可以发现其中多了 onRouteEnd API,实际该接口不会被调用。其中以 component 标记的表示只有在使用了自定义组件时才会有的方法和属性。在前面第 5 小节提到了对于使用自定义组件的页面会按照自定义组件方式解析,这些属性和方法与自定义组件表现一致。 8、关于 setData 小程序框架是一个以数据驱动的框架,当然不能少了对他如何实现数据绑定的探索,下图是 Page 实例的 setData 执行流程: 其中 component:setData 表示使用自定义组件的 Page 实例的 setData 方法。 三、写在最后 这是一次不完全的小程序框架探索,是在微信开发工具中 debug 的结果。虽然对于实际开发没有什么太大的帮助,但是对框架如何对开发的 js 代码进行处理有了一个很明确的认识,在使用一些 js 特性时可以有明确的感知。如果你还疑惑“小程序框架对传入的对象等到底做了什么”那一定是我表达能力太差,说声对不起。 通过这一次 debug ,也给我引入了新的问题,还希望能够有更多的讨论: · 自定义组件太多启动时会耗时处理自定义组件 · 文件太多会耗时读文件 · 合理的设计分包很重要 当然最后对于框架中已有的能力,还是非常希望微信可以开放更多稳定的接口,并在文档中告知开发者,让开发变得简单一些。
大家好,上次给大家分享了swiper仿tab的小技巧: https://developers.weixin.qq.com/community/develop/article/doc/000040a5dc4518005d2842fdf51c13 [代码]今天给大家分享两个有用的函数,《函数防抖和函数节流》 函数防抖和函数节流是都优化高频率执行js代码的一种手段,因为是js实现的,所以在小程序里也是适用的。 首先先来理解一下两者的概念和区别: [代码] 函数防抖(debounce)是指事件在一定时间内事件只执行一次,如果在这段时间又触发了事件,则重新开始计时,打个很简单的比喻,比如在打王者荣耀时,一定要连续干掉五个人才能触发hetai kill '五连绝世'效果,如果中途被打断就得重新开始连续干五个人了。 函数节流(throttle)是指限制某段时间内事件只能执行一次,比如说我要求自己一天只能打一局王者荣耀。 这里也有个可视化工具可以让大家看一下三者的区别,分别是正常情况下,用了函数防抖和函数节流的情况下:http://demo.nimius.net/debounce_throttle/ 搜索框搜索联想。只需用户最后一次输入完,再发送请求 手机号、邮箱验证输入检测 窗口resize。只需窗口调整完成后,计算窗口大小。防止重复渲染 高频点击提交,表单重复提交 滚动加载,加载更多或滚到底部监听 搜索联想功能 [代码] 函数防抖 [代码]const _.debounce = (func, wait) => { let timer; return () => { clearTimeout(timer); timer = setTimeout(func, wait); [代码] 函数节流 [代码]const throttle = (func, wait) => { let last = 0; return () => { const current_time = +new Date(); if (current_time - last > wait) { func.apply(this, arguments); last = +new Date(); [代码] 上面两个方法都是比较常见的,算是简化版的函数 lodash中的 Debounce 、Throttle [代码] lodash中已经帮我们封装好了这两个函数了,我们可以把它引入到小程序项目了,不用全部引入,只需要引入debounce.js和throttle.js就行了,链接:https://github.com/lodash/lodash 使用方法可以看这个代码片段,具体的用法可以看上面github的文档,有很详细的介绍:https://developers.weixin.qq.com/s/vjutZpmL7A51[代码]
大家好,上次给大家分享了swiper多图片的解决方案:https://developers.weixin.qq.com/community/develop/article/doc/0008aa4fdbce08405c288f37951813 今天再给大家分享一个关于swiper的小技巧,利用swiper仿tab切换。 相信大家在app或浏览器上阅读新闻时,比如今日头条,会有这样一个场景,左右滑动的时候可以切换不同栏目,体验非常好,但是小程序好像没有提供相关组件,如果想实现这种效果该怎么做呢今天就给大家介绍一下在小程序里是怎么实现的。 首先先看下效果 实现原理很简单,利用小程序swiper再配合scroll-view就能实现,不过这里面有几点需要注意一下: 1.scroll-view一定要给一个高度,不然会有问题; 2.切换的时候只显示当前的swiper-item里的内容,其它swiper-item里的内容可以先隐藏掉,这是因为如果你的swiper-item里的图片太多的话可能会造成页面回收,因为新闻列表大多是图文列表,而tab经常是不止两个的,可能是7、8个或更多,如果每个tab都显示的话到时上拉加载页面会非常庞大,所以这里我建议不用显示的内容先隐藏,记住是swiper-item里的内容不是swiper-item,到时切换回来时再重新渲染,如果你要保存滚动的位置还要做其它的一些处理,这里就不仔细讲解了; 3.这里适用的是整个页面都是tab切换的,如果只是在页面的某处实现tab切换,还要考虑高度的问题,加载数据的时候根据数据个数长度来计算高度,每次加载数据都要计算高度,切换到不同的tab也是,这部分比较麻烦,因为要计算,不过并不难,只要 计算正确的话是没有问题的; 大概就是这样,基本实现思路,大家可以根据这个思路去拓展,在上面加上自己的功能,over! 代码片段:https://developers.weixin.qq.com/s/89OO1smX736d
相信各位在开发的时候应该有遇到这样一个场景,比如商品的图片浏览,有时图片的浏览会很大,多的时候达几百张或上千张,这样就需要swiper里需要很多swiper-item,如此一来渲染的时候就会很消耗性能,渲染时会有一大段的空白时间,有时还会造成卡顿,体验非常差,下面给大家介绍一下我的解决方案。 首先是wxml结构: 主要是利用current属性,swiper里面只放3个swiper-item,要显示的图片放在第二,第一和第三放的是加载的动画背景,步骤如下: 将请求到的数据存入一个数组picListAll内,这里不需要setData,只需要在data外面定义一个变量就行了,以减少渲染性能; 把要显示的图片路径赋值给picUrl; 切换的时候根据bindchange获取current属性,当current改变时判断当前图片在picListAll的index,根据index拿到图片再赋值给picUrl; 主要实现步骤就是以上3 步,比较简单,要注意的是当切换到第一张和最后一张的时候要判断一下,把loding动画去掉,请求的时候还可以传入index参数以显示不同的图片,方便从前一页点击图片进入到此页面时能定位到该图片,例子里我是自己mock数据的,只是为了展示,如果你有服务器的话可以弄几百张看看效果,对比直接渲染和用以上方式渲染的差异。当然,这只是我的解决方案,如果各位有更好的方案欢迎一起讨论,一起进步。 完整代码:https://github.com/HaveYuan/swiper
我们知道小程序的wx.request网络接口只支持HTTPS协议(文档-小程序网络说明),为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。 Alice与Bob的通信 我们以Alice与Bob一次通信来贯穿全文,一开始他们都是用明文的形式在网络传输通信内容。 嗅探以及篡改 如果在他们的通信链路出现了一个Hacker,由于通信内容都是明文可见,所以Hacker可以嗅探看到这些内容,也可以篡改这些内容。 公众号的文章之前就遇到很多被挟持篡改了内容,插入广告。 既然明文有问题,那就需要对明文进行加密处理,让中间人看不懂内容,于是乎要对原来的内容变成一段看不懂的内容,称为加密,反之则是解密。而本质其实就是一种数学运算的逆运算,类似加法减法,例如发送方可以将 abcd…xyz 每个字母+1映射成 bcd…yza,使得原文的字母变成看不懂的序列,而接收方只需要将每个字母-1就可以恢复成原来的序列,当然这种做法规律太容易被破解了,后边会有个案例示意图。 如果对2个二进制数A和B进行异或运算得到结果C, 那C和B再异或一次就会回到A,所以异或也可以作为加密解密的运算。 把操作数A作为明文,操作数B作为密钥,结果C作为密文。可以看到加密解密运用同一个密钥B,把这种加解密都用同一个密钥的方式叫做对称加密。 可以看到简单的异或加密/解密操作,需要密钥跟明文位数相同。为了克服这个缺点,需要改进一下,把明文进行分组,每组长度跟密钥一致,分别做异或操作就可以得到密文分片,再合并到一起就得到密文了。 但是这种简单分组的模式也是很容易发现规律,可以从下图看到,中间采用对原图进行DES的ECB模式加密(就是上边提到简单分组的模式) 很明显,原图一些特征在加密后还是暴露无遗,因此需要再改进一把。一般的思路就是将上次分组运算的结果/中间结果参与到下次分组的运算中去,使得更随机混乱,更难破解。以下图片来自维基百科: 经过改良后,Alice与Bob如果能提前拿到一个对称加密的密钥,他们就可以通过加密明文来保证他们说话内容不会被Hacker看到了。 非对称加密 刚刚还引发另一个问题,这个对称加密用到的密钥怎么互相告知呢?如果在传输真正的数据之前,先把密钥传过去,那Hacker还是能嗅探到,那之后就了无秘密了。于是乎出现另外一种手段: 这就是非对称加密,任何人都可以通过拿到Bob公开的公钥对内容进行加密,然后只有Bob自己私有的钥匙才能解密还原出原来内容。 RSA就是这样一个算法,具体数学证明利用了大质数乘法难以分解、费马小定理等数学理论支撑它难以破解。相对于前边的对称加密来说,其需要做乘法模除等操作,性能效率比对称加密差很多。 由于非对称加密的性能低,因此我们用它来先协商对称加密的密钥即可,后续真正通信的内容还是用对称加密的手段,提高整体的性能。 上边虽然解决了密钥配送的问题,但是中间人还是可以欺骗双方,只要在Alice像Bob要公钥的时候,Hacker把自己公钥给了Alice,而Alice是不知道这个事情的,以为一直都是Bob跟她在通信。 要怎么证明现在传过来的公钥就是Bob给的呢?在危险的网络环境下,还是没有解决这个问题。 一般我们现实生活是怎么证明Bob就是Bob呢?一般都是政府给我们每个人发一个身份证(假设身份证没法伪造),我只要看到Bob身份证,就证明Bob就是Bob。 网络也可以这么做,如果有个大家都信任的组织CA给每个人出证明,那Alice只要拿到这个证明,检查一下是不是CA制作的Bob证书就可以证明Bob是Bob。所以这个证书里边需要有两个重要的东西:Bob的公钥+CA做的数字签名。 前边说到用公钥进行加密,只有拥有私钥的人才能解密。数字证书有点反过来:用私钥进行加密,用公钥进行解密。CA用自己的私钥对Bob的信息(包含Bob公钥)进行加密,由于Alice无条件信任CA,所以已经提前知道CA的公钥,当她收到Bob证书的时候,只要用CA的公钥对Bob证书内容进行解密,发现能否成功解开(还需要校验完整性),此时说明Bob就是Bob,那之后用证书里边的Bob公钥来走之前的流程,就解决了中间人欺骗这个问题了。 这种方式也是一种防抵赖的方式,让对方把消息做一个数字签名,只要我收到消息,用对方的公钥成功解开校验这个签名,说明这个消息必然是对方发给我的,对方不可以抵赖这个行为,因为只有他才拥有做数字签名的私钥。 CA其实是有多级关系,顶层有个根CA,只要他信任B,B信任C,C信任D,那我们基本就可以认为D是可信的。 上边基本上已经解决了保密性和认证,还有一个完整性没有保障。虽然Hacker还是看不懂内容,但是Hacker可以随便篡改通信内容的几个bit位,此时Bob解密看到的可能是很乱的内容,但是他也不知道这个究竟是Alice真实发的内容,还是被别人偷偷改了的内容。 单向Hash函数可以把输入变成一个定长的输出串,其特点就是无法从这个输出还原回输入内容,并且不同的输入几乎不可能产生相同的输出,即便你要特意去找也非常难找到这样的输入(抗碰撞性),因此Alice只要将明文内容做一个Hash运算得到一个Hash值,并一起加密传递过去给Bob。Hacker即便篡改了内容,Bob解密之后发现拿到的内容以及对应计算出来的Hash值与传递过来的不一致,说明这个包的完整性被破坏了。 一次安全可靠的通信 总结一下,安全可靠的保障: 对称加密以及非对称加密来解决:保密性 数字签名:认证、不可抵赖 单向Hash算法:完整性 来一张完整的图:
2018年12月,腾讯相册累计用户量突破[代码]1亿[代码],月活1200万,阿拉丁指数排行 [代码]Top 30[代码],已经成为小程序生态的重量级玩家。 三个多月来,腾讯相册围绕【在微信分享相册照片】这一核心场景,快速优化和新增一系列社交化功能,配合适当的运营,实现累计用户量突破[代码]1亿[代码],大大超过预期。 (腾讯相册用户量破亿) 可是,谁曾想到,这样一个亿级体量的小程序,竟然是一个开发做出来的?他又是有哪般“绝技”,可以一个人撑起一个用户过亿的小程序? 后台人力紧缺,怎么办? 当我第一次见到腾讯相册小程序的开发David(化名)时,他显得忧心忡忡。 “年底的目标是要过千万的用户,但现在只有几位前端和后台开发。不仅如此,我们的后台开发还不是百分百能够投入到这个项目,大部分时间要抽身支援其它项目,人力非常紧缺。此外,原有后台系统有不少历史包袱,在原有架构上做新的社交化功能开发是不现实的。怎么办? “要不试试’小程序·云开发’吧,只需要前端就可以把小程序搞起,正好解决我们缺后台的难题。” 于是,David作为腾讯相册前端开发团队的骨干,担当起用小程序·云开发实现腾讯相册小程序社交化功能的重任。 “第一次接触到’小程序·云开发‘时,觉得这个东西(小程序·云开发)理念挺新颖的———小程序无服务开发模式。在一般的小程序开发中,有三大功能小程序开无法绕开后台的帮助,它门分别是数据读取、文件管理以及敏感逻辑的处理(如权限)。因此,传统的开发模式,在小程序端都必须发送请求到后台进行鉴权,并且处理相关的文件或者数据。即使使用 Node 来搭建后端服务,也需要耗费不少的搭基础架构、后期运维的工作量。” “而小程序·云开发则释放了小程序开发者的手脚,赋予了开发者安全、稳定读取数据、上传文件和控制权限的能力,其它的负载、容灾、监控等,我们小程序开发者只需要关注业务逻辑,专注写好业务逻辑即可,其他的事情完全可以不用操心了!本来我还一筹莫展,了解完’小程序·云开发‘的产品原理以后,我瞬间心里有谱了。” 二维码扫不出来了 道路总是不平坦的 ,在腾讯相册小程序通往用户破亿的道路上,困难重重。 由于腾讯相册的二维码需要带上的信息量过大,因此它的二维码显得密密麻麻。这种密集的二维码在某些Android机型下,容易出现无法识别小程序的问题。 这严重制约了腾讯相册小程序分享获客的能力。 (需要存储name, ownerid, page等大量信息) 这个事情的解决并不难,只需后台开发把数据先存储到数据库中,然后把数据id放到分享链接上,这样,链接便可以转化成32个字符的短链接,让二维码看起来没有那么密集了。 但由于后台人力不足,于是前端开发David利用小程序· 云开发的数据库存储能力,通过调用db.collection(‘qr’).add接口,快速实现数据在数据库中的存储。 (云开发数据库,格式类似MongoDB) (云开发数据库索引,可加快数据读取) 此外,腾讯相册还借住小程序·云开发的云函数能力,生成辨识度更高的小程序码(小程序码文档),用以在朋友圈里传播分享。 (生成小程序码的云函数逻辑) (优化后的分享图片和小程序码) 2天上线评论点赞功能 (评论与点赞功能) 腾讯相册在微信端的核心应用场景是“在微信做分享相册照片”,为了增强腾讯相册用户在微信里的互动,提升用户粘性和留存,腾讯相册决定新增评论与点赞功能,并且把聊天评论就直接在微信聊天窗口里面实现。 在这里,腾讯相册的David面临了两个选择,一是按原开发模式(前台开发-后台开发-前后台联调)做这个功能,面临的问题便是开发周期长、缺后台、迭代速度慢;另一个就是借助云开发的能力,亲自上阵。 为了加快产品迭代速度,David决定采取云开发的开发方式。评论、点赞通过云开发的数据库插入和查询接口,如db.collection(‘comment’).add,很快就实现了。 但遇到棘手的问题是,对于一些敏感的操作比如删除和编辑评论、点赞这些敏感操作,还需要到用户的鉴权操作,而这些鉴权信息,都在原有的后台。此时,云函数的路由功能便发挥出作用了。 (评论点赞逻辑) 用户进行评论点赞的时候,会在小程序端发起请求调用云函数并带上 [代码]openid[代码],云函数用 [代码]openid[代码] 查询原有的后台服务看看该用户是否有权限进行操作,如果用户具有权限,则把评论和点赞的数据都写入云开发的数据库中。 就这样,借住小程序·云开发的能力,腾讯相册仅用2天时间,完成了在传统开发模式下需要1周多工作量的开发工作。 原有开发模式 云开发全栈开发 什么是小程序·云开发? 小程序·云开发是基于腾讯云研发的全新 云开发 Tencent Cloud Base(简称 TCB) 服务,腾讯云与微信力推的这套云开发服务的诞生,恰逢其时地帮助腾讯相册走出开发效率的瓶颈。 (基于腾讯云的云开发) 小程序云目前提供三大基础能力支持: 云函数:充当了后台的角色,开发者可以在上面用 Node (后续还会支持 PHP, Python 等)写后台逻辑,跟微信私有协议天然鉴权,可以云函数里直接获取 [代码]appid[代码], [代码]openid[代码], [代码]unionid[代码] 等重要鉴权信息,大大简化了小程序后台的开发工作量。 数据库:一个既可在小程序前端操作,也能在云函数中读写的文档型数据库,提供控制台可视化管理 文件存储:在小程序前端和云函数里都可以直接上传/下载云端文件,提供控制台可视化管理 如果你是全新开发的小程序,架构非常轻量简单,如下图。 如果你是已有的小程序,部份需要跟原有后台交互的功能,完全可把云函数作为路由,节省获取openid 等用户信息的逻辑,如下图: 小程序·云开发解决方案 小程序·云开发文档
- 需求的场景描述(希望解决的问题) getWXACodeUnlimit接口从微信后台获取到了小程序码的二进制流。 如何用 获得的小程序码二进制流 生成 可见的小程序图片? 试过file.writeFile() 生成的文件,不能用图片编辑器查看 - 希望提供的能力 ++++++++++++++++++++++++++++++ 分割线 +++++++++++++++++++++++++++++++ 最后还是没有解决,我是用了第三方的后台,直接生成的
各位开发者: 大家好。 近期我们上线了微信开发者·代码管理 的功能,这是为开发者提供的一项基于 Git 的代码管理服务。方便微信开发者进行代码推送、拉取、版本管理和多人协作,并且可以简单的将仓库设置为私有或者公开。 系统会为每个小程序自动创建一个专属项目组,用户无需单独开通。后续登录过微信开发者·代码管理的小程序开发者会自动成为该项目组成员。 微信开发者·代码管理的登录用户必须同时满足以下两个条件: 1.该用户必须为微信开发者,即微信小程序、订阅号以及服务号的开发者; 2.该用户必须为实名认证的用户。 微信开发者·代码管理的登录方式包括: 1.通过微信开发者工具版本管理中的“代码管理”或工具栏中的“微信开发者·代码管理”快速进入。 2.通过浏览器微信扫码登录:https://git.weixin.qq.com 。 具有登录权限的用户,可在小程序专属项目组中创建远程仓库或创建其他非小程序专属项目组。 在微信开发者工具版本管理中添加远程仓库时,系统会自动拉取该小程序专属项目组下已创建远程仓库列表。用户可点击选择相应仓库,系统会自动填充该仓库的 URL 和名称,方便用户添加。 [图片] 详细内容可参考 文档。 微信团队 2019.01.28
事情是这样的。 我司一小破程序,打开时类似这样,显示一个logo,一个标题 [图片] 经过一个2秒的动画效果,logo和标题就移动到上面部分了,同时渐显出来一个loading组件,这些都是使用小程序的Animation API实现的。 [图片] [图片] 现在需求来了。 我们想在首屏渲染后。在图标往上移的动画执行周期中,将背景色缓慢从蓝色变为白色。 (别问为什么要变背景色,我们准备待会加完班拿上弹弓组团去打设计师家玻璃了) [图片] 有朋友会说了,这不是很简单嘛,弄个定时器去替换class不就行了? 我只想说,no no no。朋友,我们搬砖就要有搬砖的样子嘛。 什么时间搬,搬多少,什么时间停,都要严谨嘛。 天真的我,想当然的就拍着胸脯向BOSS表示小意思啦。 [图片] naive的我心里想着 肯定会有动画执行开始和结束一个callback接口的嘛 然鹅,、翻遍了小程序文档里关于动画的各个段落之后才发现 [图片] 神马?? 我不信!一定是我的眼刚刚瞎了,我要再看一遍。 [图片] [图片] [图片] [图片] [图片] PS 看,多么言简意赅的文档! 在看多了外面那些"妖艳贱货"的文档后,如此小清新的文档,还真让我这老司机虎躯一震。 // TODO 我当即在心里暗暗发誓,我一定要强烈建议我司将此文档规范引进并在我司大范围实践,太他【文明用语】高效了。 END PS 在我不懈的努力下 在某毒找到了一篇关于动画重置的实例 [图片] [图片] 哦也,三七三十一,一定是我聋了才没看见这么大个接口 同事心里还在做自我批判,怎么能轻易的就甩锅给腾讯爸爸。 祭出我的Ctrl+F大法 [图片] 果然。还是我太天真。竟然没有搜到 0/0? 在经过了一番苦苦的某毒搜索之后,猛然意识到,或许是我姿势不对? [图片] 谢天谢地,博客园诚不我欺。确实有这个东东。 我默默的打开了唯一的一条搜索结果学习了起来。你猜怎么着? [图片] 我发现了腾讯爸爸藏起来的彩蛋。 哇,没想到小程序团队这么调皮。 在动画相关的所有文档里,竟然半个字都没提有这几个事件。保密工作做的很到位。表扬。5星好评。 [图片] 根据文档,照猫画虎。 [图片] [图片] 控制台没有任何反应 [图片] 一定是我姿势不对,我换换姿势。 [图片] [图片] 一顿操作猛如虎,然鹅发现并没有什么卵用。 [图片] [图片] [图片] 我盯着这条说明,默默的给自己点上了一根烟后陷入了痛苦的沉思。 期间我尝试了各种姿势,都没有找到关于WXSS animation到底是个什么鬼。 我只知道有Animation这个动画API。或许他俩是一个东西? 但是为什么Animation里没有关于它的只言片语? [图片] 既然Animation里没有写,肯定是另外一套体系吧? 灵光一闪, oh no,别又是腾讯爸爸调皮了把文档藏起来了吧。 [图片] [图片] 经过地毯式的搜索及换遍了各种姿势想要跟我的小程序互动一把后。 [图片] [图片] [图片] 我选择死亡。 [图片] [图片] 我想起那天夕阳下调的微信小程序,那是我逝去的青春。。。 IDE: v1.02.1901230 Library: 2.4.2
开发者在开发小程序的时候可能会碰到一些这样的问题: 问题1 开发者工具上看效果没问题,但是在真机上测试不行? 问题2 有用户遇到小程序功能无法使用的问题,但无法快速定位解决? 今天我们的小故事与大家分享一些真机定位的技巧,可以解决上面两个问题。 1 vConsole开发利器和远程调试功能 针对问题1,我们提供了 vConsole 开发利器和远程调试功能,可以协助开发者在定位真机上的问题。 vConsole 的有四个Tab面板,可以先看下 Log 面板,看是否有异常信息,异常类型 thirdScriptError 是框架捕捉到的开发者的代码执行的异常,可以优先处理异常信息看是否可以解决问题。Log 面板可以看到异常出现的文件和行数。 [图片] 除了异常日志,开发者还可以通过 console.log 接口在一些关键执行路径上打日志来定位问题,这些日志会呈现在 Log 面板上。 vConsole 默认是不开启的,可以通过下面2个方法来开启: 1 开发版和体验版可以点击小程序页面右上角的...按钮打开的菜单项“打开调试”来开启 vConsole。 2 正式版没有“打开调试”的菜单项,可以先通过开发版和体验版来开启 vConsole,然后再打开正式版。或者可以预埋一个隐藏操作,比如连续点击某个 Button 多次,然后调用 API 接口 wx.setEnableDebug 来打开。 vConsole 虽然强大,但在手机上查看大量的日志信息不方便,此外,vConsole 没有断点调试、无法修改样式,定位复杂问题需要花费比较多的时间。 小程序的业务逻辑运行在 AppService 层,页面渲染在 WebView 运行,并通过微信客户端通信,因此,我们想到了可以让 AppService 运行在开发者工具,页面渲染还是在手机 WebView,两者通过网络来通信,这样借助开发者工具的调试能力,就可以实现远程调试功能。 远程调试窗口通过手机客户端扫描开发者工具上生成的二维码来打开,无需像普通手机 H5 页面调试一样,需要在手机端进行一些设置。 [图片] 打开的远程调试界面和开发者工具的模拟器的调试界面很像,需要注意的是,要在 Console 里对小程序进行调试,需要将调试的上下文切换到 VM Context 1 。 [图片] 更多的远程调试的使用方法请参考使用文档。 2 意见反馈能力 对于问题2,小程序的使用反馈来自用户投诉,这种情况用户无法联系到开发者。我们遇见过有小程序功能出现问题,用户无法使用,但投诉无门的情况,而这些问题,开发者也没有途径去收集以及处理,这就导致了小程序服务质量下降,用户流失。 为此,我们开发了“意见反馈”功能,当出现问题时,开发者可以引导用户使用“意见反馈”进行反馈,并上传日志来辅助开发者定位问题。操作过程如下: 引导用户进入小程序帐号详情页面,具体可以在小程序界面点击右上角...按钮,选择关于菜单。接着在帐号详情页面点击右上角...按钮,选择意见反馈菜单进入页面。页面可以上传图片和日志,建议用户上传异常情况的截图,以及勾选允许开发者使用小程序日志选项上传日志,反馈信息越详细,越有助于定位问题。 [图片] [图片] 如果觉得上面的操作步骤太麻烦,开发者可以通过在页面 WXML 添加下面的按钮,用户点击按钮可以直接打开“意见反馈”页面。 [图片] 开发者需要定时处理用户的反馈,这样才能保证小程序的质量。开发者可以登录小程序管理后台,进入左侧菜单客服反馈,就可以看到用户的反馈内容以及下载日志来辅助定位问题。 [图片] 为了保证日志信息足够详细,开发者需要用下面的接口在代码的关键执行路径上写日志。 [图片] wx.getLogManager 接口的更详细使用请参考文档。 希望通过这些小技巧,可以帮助大家顺畅地开发小程序。
各位开发者:大家下午好。在一些小程序/小游戏的业务逻辑中,有时需要依赖用户所在的地理位置来提供服务,当前开发者可以通过调用 调用 wx.getLocation / wx.authorize 等接口获取用户的地理位置信息或授权。 根据 iOS 系统对用户隐私保护的要求,同时我们也为了让用户可以更好的判断是否要将地理位置信息提供给开发者,故调整为当小程序/小游戏获取用户地理位置信息时,开发者需要填写获取用户地理位置的用途说明。填写的说明将在地理位置授权弹窗中展示,如下图所示: [图片] 具体开发方法如下: 在 app.json 里面增加 permission 属性配置(小游戏需在game.json中配置): [代码][代码]"permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" } } [代码][代码]详见 小程序开发文档/小游戏开发文档 可在开发者工具(1.02.1812260及以上版本)中进行调试。 2019年1月14日起新提交发布的版本将会受到此调整的影响。需要各位开发者注意,2019年1月14日起新提交发布的版本若未填写地理位置用途说明,则将无法正常调用地理位置相关接口,请及时填写地理位置用途说明。该调整策略在微信客户端 7.0.0 版本生效。另外,考虑到兼容性等问题,在微信客户端 7.0.0 版本以下的环境中不受此策略影响。 微信团队 2018.12.26
1. 插件开发中,使用自定义组件需用到相对路径 "usingComponents":{ "alert":"../../components/alert/alert" } 注:选择自定义组件需要加上in(this) wx.createSelectorQuery().in(this).select('.xxxxx') 2. 获取小程序码相关 通过接口生成的小程序码page必须是已经发布的 生成小程序码后可在客户端开发工具中,通过二维码编译进行测试scene参数 [图片] 3. swiper组件current不重置问题(bug) 如:通过arr=[1,2]遍历swiper-item组件,当swiper滑动到current=1时,setData({arr:[2]});此时swiper会出现空白。 原因:current未重置为0,需自己去设置current。 https://developers.weixin.qq.com/community/develop/doc/00066c8beacfa05f24d7d144056800 4. 模板消息相关 时效性:1次支付可下发3条,1次提交表单可下发1条。(7天内有效) 对应性:发送消息的对象openId和formId是匹配的。 获取方式:发起支付或表单提交 5. 分包加载大小限制问题:使用分包加载时,如果在分包中使用插件,插件大小只会算在分包大小2MB与整包8MB内,不算入主包2MB。(之前算在了主包内,目前已修复)。 6. 获取unionId(包括openId)流程(前提:小程序 或 其主体公众号 与 微信开发平台账号关联) 开放平台关联同主体的公众号且 用户已经关注公众号: wx.login()=> 获取到code,后端通过appid+appserect+code,拿到openId+ session_key+unionId 开放平台关联小程序: 用户授权后通过wx. getUserInfo(需要授权)获取iv、encryptedData,然后解密(需要用到上面的 session_key),appid+ session_key + encryptedData + iv解密 得到unionId、openId及用户信息 [图片] 注:如果再次获取code会导致之前的session_key过期 7. h5与小程序跳转问题 公众号=>小程序:公众号自定义菜单可配置跳转到小程序 小程序=>h5:webview(需配置业务域名、webview不支持个人账号) 注:目前不支持h5与小程序的直接跳转 8. canvas原生组件覆盖自定义弹层的解决方案 用css样式控制器显示或隐藏,如hidden 纯显示性的canvas可以生成图片之后展示 9. 自定义弹层背景滚动问题 方法一:打开的函数中,如果自定义弹框当前显示,则isScroll设为true,否则设为false <scroll-viewclass="scanInvoice_content" height="100%"scroll-y="{{isScroll}}"> //设置Page的overflow-y属性值为hidden </scroll-view> 方法二:事件捕获,顶层加上catchtouchmove 10. 小程序图片分享截取变形或显示不全:保持分享的图片是5:4。 11. 授权问题 wx.authorize可以对除scope.userinfo之外的权限进行授权,scope.userinfo需要用<button open-type="getUserInfo"/>组件进行授权。 统一小程序下的用户拒绝授权之后会直接进入失败回调,这种情况可使用wx.openSetting引导用户授权。(用户手动删除小程序才会重新提示授权) 12. 数据绑定是双括号内只能是data里面的变量或者wxs里声明的函数。 13.不要用wx.request去访问微信接口,官方限制且不能把[代码]api.weixin.qq.com[代码]配置为服务器域名 基础库版本:v2.4.0 欢迎更新指正
这款颜值检测小程序使用了腾讯开放人脸识别API,本项目适合刚入门的同学练手,熟悉整个框架,整体实现如下: 后台定义接口调用函数,返回前端渲染需要的数据格式,以及构造汉化字典。 通过小程序提供的云函数编写后台逻辑,调用接口放在云函数处理即可。 前端开发根据自己的审美或者请人设计,想自己弄得可以参考material design的规范,或者去GitHub使用现成的UI组件。建议自己动手写写前端,个人经验,写过后会对数据结构设计,前后端分离有个很直观的印象,关键是能提高个人审美(手动围笑) 这里还需要的是对数据的持久化处理,可以选用云开发的数据库,也可以区别的平台,建议是找接口封装简单易用的。 最后是交互设计,这个要尽量简单明了,比如就提供一个上传图片按钮,不能占地方。比如对于图片的处理,用卡片类展示,最好使用瀑布流可以展示完全,用其他的对称的方案,后台需要做很多图像裁剪,影响速度和观感。比如历史记录入口,不推荐用tabbar的导航方式,减少了可视面积,建议是浮层提供交互,虽然路径会深一层,但是图片展示效果会好很多 下面上图片,给想上手的同学提供一点思路和灵感,扫码体验下,需要源码参照的同学,请在小程序内扫码联系作者。 [图片] [图片] [图片] [图片] [图片]
业务逻辑:用webview来展示H5,在H5页面里面添加手指滑动事件的判断,touchstart时记录手指按下的位置 starY = e.touches[0].pageY,touchend时记录手指离开时的位置endY = e.changedTouches[0].pageY,当endY - starY > 0时,表示手指向下滑动,反之向上滑动,=0时未滑动。 场景与表现: 1,开发工具,H5页面不超过屏幕高度,按照业务逻辑正常执行,手机上也一样 2,开发工具,H5页面高度超出屏幕高度,按照业务逻辑正常执行,但是在手机上,手指滑动频率比较正常时没问题,当手指滑动频率比较快的时候,用上面业务逻辑得到的结果就会出现异常,比如:明明是向下滑动,判断出的结果却是向上 希望官方大大帮我看下,为什么会出现这种情况
window.onload = function(){ let isApp = window.__wxjs_environment === 'miniprogram'; console.log(isApp) } h5页面嵌套在小程序里面,ios true 安卓 false
各位开发者: 下午好。 从微信6.7.2客户端版本开始,以下功能的逻辑将进行调整,请开发者注意适配: 1.为便于用户方便使用小程序,web-view 组件在小程序全屏模式下,将统一显示导航栏 自该版本起,[代码]navigationStyle:custom[代码] 对 [代码]<web-view>[代码] 组件无效。 开发者在全屏模式下,无需再自行绘制 web-view 导航栏。 可详见文档: https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html 2.背景音频管理器 [代码]backgroundAudioManager[代码]在退出小程序后不再默认继续播放 近期发现部分小程序退出后仍在播放无意义的音频,引起用户疑惑。 因此自该版本起,若需要在小程序切后台后继续播放音频,需要在 app.json 中配置 [代码]requiredBackgroundModes[代码] 属性。 开发版和体验版上可以直接生效,正式版还需通过审核。 详见文档: https://developers.weixin.qq.com/miniprogram/dev/framework/config.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE 请大家及时调整。 微信团队 2018.08.23
前段时间和大家一起分享了一篇关于学习方法内容《大牛与搬运工的差距——学习方法的力量》。我们将学习过程分成八步,并借鉴了敏捷开发的迭代思想,以达到自我迭代学习的效果。行胜于言,理论结合实践才是王道,所以本文我将基于前面的学习方法,分享我是如何学习微信小程序的。 关于小程序小程序是继订阅号、服务号、企业号后,16年腾讯新推出的又一种开发能力。小程序无需下载安装,可以在微信内便捷地获取和传播,可谓是一款“即扫即用,用完即扔”的便捷应用,同时具有近似APP般的出色使用体验。 坦白讲,此刻和大家分享小程序学习,已经有点后知后觉的意味了。小程序刚出来时,和很多人一样,我其实也是持有怀疑、观望的态度,即便到目前为止,我接触的小程序恐怕就是上次玩的那个“跳一跳”了。 最近也是因为想在我们的一款建站系统的开源项目中关联小程序,才决定学习了解下小程序,接下来我们就马上开启小程序的学习之旅。 1.确立目标我学习小程序的目标很明确,总的来说就两个: 1.学习小程序相关技术,能独立开发制作一款小程序。 2.将小程序与我们的开源项目(蝉知建站系统)关联打通。 2.拆分任务我们通过逆向分解,将每个目标拆分成具体的可执行任务。这个过程其实就是将目标细分整理成具体的小目标或问题,然后将其关联到一个个可执行的任务,目的是完成目标或解决问题。 完成我的两个目标,需要具体学习哪些知识,完成哪些事项呢?第一个目标需要学习小程序的代码框架结构、运行机制、组件用法、小程序后台操作以及小程序开发工具的使用等。第二个目标则需要学习小程序的API、插件、相关接口调用,以及蝉知建站系统这边的微信模块代码。 梳理之后,就变成了一个个需要完成的具体任务。这里我借助待办任务管理工具,将需要做的任务都罗列出来,方便后面执行跟踪。 [图片] 3.搜集知识资源学习过程中必然少不了各种学习资料,在搜集知识资源时主要侧重三方面:书籍、官方文档和网络技术文章。 我首先找的就是微信的官方文档——《小程序开发文档》,内容涵盖了快速入门的简易教程、组件、框架、API、开发者工具等内容,既权威又全面。其次书籍我选的也是由腾讯官方微信架构师熊普江编写的《小程序,巧应用:微信小程序开发实战 第2版》。学习过程中以这两份材料为主要资源,除此之外,还在CSDN、掘金、博客园等IT技术门户里查阅一些小程序相关的“干货”。 对于知识资源的搜集,还是要提醒大家注意一点,切勿做“收藏控”。很多朋友对于书籍、各种教程、网络文章等资源,购买了下载了收藏了,然后,就没有然后了,最终真的沦落为“收藏品”。而我们真正要做的是要把知识“收藏”并沉淀到我们脑子里。 [图片] 4.分配自身资源其实就是时间管理。我们时间、精力等各方面资源有限,所有的知识不可能平均分配有限资源。对于我们搜集的知识资源,要分清主次,然后使用不同的时间管理策略进行学习。 前面梳理了一些需要执行的学习任务,并整理了大量的学习资源,这一步要做的就是将这些任务、资源结合自身的实际情况,合理规划分配时间精力。每个人的情况不同,所以这一步因人而异,这里我还是借助待办工具,做一个具体可执行性的学习计划。 [图片] 5.勤学学习效率与效果取决于执行力。之前文章介绍过学习过程中的一些方法论,这里我简要地介绍一下学习小程序时的大体过程。 a.快速泛读 对于小程序的基本的代码框架结构、运行机制、后台管理操作以及开发工具的使用这些内容,都是快速的过了一遍。熟悉核心概念与基本的操作流程,具体的细节问题在后面的使用操作时在深入研究。 b.深入解读 根据小程序的MVVM开发模式,深入学习其视图层与逻辑层相关知识,包括常用的函数、事件、组件、API等的使用。并结合开发工具实践操作,通过做个demo案例来进一步了解这些知识点的运用。 c.问题攻读 对于学习过程中遇到的问题,先查阅资料或谷歌来解决。比如小程序里的WXML、WXSS与HTML、CSS还是有些区别的,包括JS的使用也有别于PC端的DOM操作机制。如果有难以解决的问题,可以先记下,或许后面会柳暗花明或找他人帮助。 这一步学习的正确姿势是在实践操作中发现问题,然后带着问题找答案。 6.建立知识体系不要以为这一步可有可无,建立知识体系有助于帮我们理清各个概念和知识点之间的关联,逐渐形成系统的知识体系。基于前面的大量泛读和深入解读,我们可以逐步构建出一个知识体系。这里我借助xmind脑图工具,整理了一下小程序的知识体系。 [图片] 7.好问基于前面问题攻读后还有未解决的问题,就要求教他人获取帮助了。提问也是一门艺术,这仍是一个学习交流的过程,而不是让别人去解决原本你该解决的问题。 向别人提问求助时,要讲问题的来龙去脉讲清楚,如怎么引起的,自己尝试做了哪些操作等等。这些年在众多技术交流社群和平台,见过很多奇葩的提问方式,这里列几个,与大家共勉。 a.直抛结果型 直接甩出一张截图或错误提醒,并附上一句:怎么解决?怎么破?跪求科普…… b.不懂就问型 不动脑,不动手,碰到问题就问。这种选手即便帮他解决了问题,他也学不到东西。 c.懒人型 与前者类似,但更滑稽可笑,看到这种问题我个人向来直接无视。比如“这个问题是这样操作么?”,“我这样整对不对?”,“是点这里么?”…… 8.分享复述 其实即便不提“费曼技巧”,我们很多人在学习时都会用到分享复述这个方法。无论是演讲、录视频、还是像我此刻这样写文章,其实都是在向他人阐述自己对知识的理解和观点,然后大家会给自己一些反馈,比如你的错误、问题,接着自己再回归到知识里完善不缺“漏洞”,如此迭代学习。 小结我将自己学习小程序的过程分为八步: 1.确立目标:没有目标的学习是不会有结果和效果的。 2.拆分任务:将目标分解成具体可执行的学习任务。 3.搜集知识资源:查阅官方文档、购买书籍、搜集网络干货文章。 4.分配自身资源:合理分配时间精力,制定学习计划。 5.勤学:强大的执行力是学习的根本保障。 6.建立知识体系:将学习的知识在脑海里建立系统模型。 7.好问:避免孤立学习,但好问也要会问。 8.分享复述:检验学习成果,提高学习效果的最好方法。 作者:GavinHsueh,专注项目管理、办公自动化、网站建设等web应用开发领域。
- 当前 Bug 的表现(可附上截图) [图片] 我是在api文档看到应该有这个功能的,这还是api上面的demo改了下,不知道为什么不行,是不是我写错了,还是我犯了啥低级错误?? - 预期表现 - 复现路径 - 提供一个最简复现 Demo [图片]
- 当前 Bug 的表现(可附上截图) 在页面中setData操作较频繁,但也不是特别频繁,看点击速度,最多也就是10秒进行setData20次,然后在iPhoneX真机中就直接闪退,把微信也给强退了,微信还让提交错误日志,闪退了几次之后还给警告了。安卓测试过的机型都没有出现闪退的现象,iPhone6S也没问题,iPhone7也出问题了。 - 预期表现 - 复现路径 - 提供一个最简复现 Demo
- 当前 Bug 的表现(可附上截图) image标签中的网络图片在服务器端更换后,客户端图片并未更新,无论删除小程序还是清理微信缓存都不行。在开发者工具中是可以被更新的。基于http缓存策略,通过设置cache-control:no-store响应头强制关闭缓存也无果 - 预期表现 在服务器关闭缓存策略的情况下,客户端能够实现服务端更改图片后客户端响应更新后的图片 - 复现路径 任意一个服务端图片地址,图片更换后必现
小程序上我使用image标签 路径是一张网络图片 我把服务器上的网络图片替换之后(名称不变) 小程序显示的还是原来的图片。。。 怎么回事???
要求:小程序里面个人动态有更新的情况下,手机会响铃提醒用户去打开小程序,能打电话最好 我该怎么办???
亟待解决!!!
appid: wxd25bb1943daf726f token: access_token=13_KMCAfVoLQuGLVC4pr5sAE_spy7xLnDMBYYeKKOkhK7fPD-v7dAc_uKC3HtG4mjEbt9P_5tPsbxmkbV1hGP7B7_9y0XuFjrbevr8xd--Gf12GjYIXP7rXhn1YGPUGIFcAHANPM 发送的post请求方式: [图片] 返回值: [图片]
1.语音语音合成:接口:https://reptile.akeyn.com/voice/text2audio?content=合成的文字 例子:https://reptile.akeyn.com/voice/text2audio?content=我们在广州天河 语音识别:接口:https://reptile.akeyn.com/voice/recognition?url=mp3地址 例子:https://reptile.akeyn.com/voice/recognition?url=http://img.bazhuay.com/152896248842554.mp3 2.文字识别识别银行卡:接口:https://reptile.akeyn.com/ocr/bankcard?url=银行卡图片路径 例子:https://reptile.akeyn.com/ocr/bankcard?url=https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=888049859,2568911766&fm=27&gp=0.jpg 识别身份证:接口:https://reptile.akeyn.com/ocr/idcard?url=身份证图片路径 例子:https://reptile.akeyn.com/ocr/idcard?url=https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3264366825,616996887&fm=27&gp=0.jpg 识别营业执照证:接口:https://reptile.akeyn.com/ocr/idcard?url=营业执照图片路径 例子:https://reptile.akeyn.com/ocr/business?url=https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528787767575&di=2092bea8f13a7d36980bcfea48672a77&imgtype=0&src=http://d.hiphotos.baidu.com/zhidao/pic/item/3b292df5e0fe9925cfa37f6b37a85edf8db17159.jpg 识别带文字的图片:接口:https://reptile.akeyn.com/ocr/general?url=有文字的图片路径 例子:https://reptile.akeyn.com/ocr/general?url=https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528795796906&di=c969a302739ae5fa138619fbd1589428&imgtype=0&src=http://g.hiphotos.baidu.com/zhidao/pic/item/6159252dd42a283402bc167753b5c9ea15cebfb7.jpg 3.图片识别识别动物:接口:https://reptile.akeyn.com/image/animal?url=动物图片路径 例子:https://reptile.akeyn.com/image/animal?url=https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=741061832,2708652394&fm=27&gp=0.jpg 识别植物:接口:https://reptile.akeyn.com/image/plant?url=植物图片路径 例子:https://reptile.akeyn.com/image/plant?url=https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3121747171,2429610755&fm=27&gp=0.jpg 识别菜肴:接口:https://reptile.akeyn.com/image/dish?url=菜肴图片路径 例子:https://reptile.akeyn.com/image/dish?url=https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=d21700a6ecfe9925cb0c6e560c9339e2/4d086e061d950a7bea3656a706d162d9f2d3c956.jpg 识别车型:接口:https://reptile.akeyn.com/image/car?url=汽车图片路径 例子:https://reptile.akeyn.com/image/car?url=https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=808288941,469095564&fm=27&gp=0.jpg 识别品牌:接口:https://reptile.akeyn.com/image/logo?url=logo图片路径 例子:https://reptile.akeyn.com/image/logo?url=https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4067228399,201083271&fm=27&gp=0.jpg
当小程序正式上线后,打开生产版也出现了vConsole按钮,多半是因为曾经打开过体验版,并且在体验版上打开了调试模式。这样打开生产版的时候,也允许调试窗口。解决方法就是,再次进入体验版,点击右上角三个点,关闭调试模式。这样生产上也就关闭了。 不知道是小程序故意的还是bug,可能是为了方便生产调试?但是我的体验版也可以做成和生产环境一模一样的啊。所以。。。。