- 新canvas接口生成图片的bug,wx.canvasToTempFilePath生成图片异常?
用小程序新canvas接口创建canvas后,调用wx.canvasToTempFilePath生成图片异常。(在开发者工具里正常,安卓和ios都异常) 代码片段连接:https://developers.weixin.qq.com/s/BwrGaem67Ec6 Ios生成的图片不全,wx.canvasToTempFilePath中的width和height两个参数没起作用 安卓报错: [图片] 源码: wx.canvasToTempFilePath({ canvas:canvas对象 x: 0, y: 0, width: 300, height: 300, destWidth: 600, destHeight: 600, quality: 1, fileType: 'png', success: function (res) { });
2019-10-21 - compressVideo 压缩m4v视频 报错 video duration error,?
compressVideo 压缩m4v视频 报错 video duration error,请问是不支持m4v格式吗 还是时长有限制 基础库 2.14.1 工具版本也是新版[图片] 选择视频 使用的是 chooseMedia 文档未发现相关格式限制 [图片] [图片]
2020-12-28 - 新canvas接口 进行图片压缩处理,多次就会出现图片变形
安卓上目前没有问题,但是iphoneX上反复出现该bug,第一次选择图片是正常压缩,第二次开始就变形了 [图片] 代码片段: https://developers.weixin.qq.com/s/RuWzeWms7lgs
2020-04-29 - 我们做一个家政类小程序,对于工人身份我们需要人脸核身,能申请吗?
一个家政后台管理类小程序,不对外开放,仅用于管理层进行后台管理,需要对员工身份进行核实。 1.公司主体有《民办学校办学许可证》及营业执照,培训项目为家政服务员,孕婴员,养老护理员相关,能用这个证明申请开通人脸核身吗? 2.另一个问题,如果我们只要求上传员工身份证照片,不要求手持身份证照片,能过审吗?
2021-01-18 - 腾讯自选股开户流程分析
腾讯自选股开户流程分析 ~ 这几天一直在调研小程序开户方面的信息,说到开户,离不开 腾讯自选股,作为腾讯旗下唯一的证券类产品,其用户堪比头部券商,自选股app也是我经常使用的, 那么自选股在小程序端开户又如何呢 众所周知,受限于监管限制,小程序端开户目前还是一个未知事物,头部券商也鲜有涉及这块,那么,自选股提供的开户引导方案是监管层面合规,也是小程序运营合规层面值得借鉴和学习的唯一方案 1 我今天大概体验了下, 首先进入自选股小程序后,来到「我的-交易 」通过客服引导用户来到其公众号,在公众号内引导完成开户,目前具体支持二家券商 1、招商证券 2、华林证券 1 [图片] [图片] 1 [图片] [图片] 1 [图片] 1 [图片] 1 [图片] [图片] 1 1
2021-01-18 - 小程序端上传用户正面头像照片,不能通过审核吗?
小程序端上传用户正面头像照片,不能通过审核吗? 是这样的,有个业务,需要采集用户的正面照片,用户可以从本地上传,也可以通过拍照的形式,请问这样收集用户的头像,不合规吗? [图片]
2021-01-20 - 金融证券持牌机构需要在小程序端实现开户业务,上传人脸头像审核被拒了?
是这样的,我们是金融证券行业,现在尝试做小程序端开户的业务,开户过程中需要用户上传身份证正反面,以及人脸头像,但是目前了解下来,小程序不能上传人脸正面照片,如果上传人脸正面头像,会审核被拒。 请问这种情况该如何处理 我仅仅指的是在小程序端完成人脸正面头像上传,而非小程序进行人脸识别,这是完全不同的二件事
2021-01-18 - 红包封面的N种玩儿法
首先,需要有足够多的红包封面样式,越多越好,而且还得好看。 红包封面申请地址:https://cover.weixin.qq.com,支持个人(认证视频号)和企业认证公众号申请。 需要提供资质:作品源文件+作品著作权,缺一不可。可同时提交三个封面,简单的封面3个工作日审核,复杂的封面30天审核。审核通过后可无限购买,1元一个。 做自媒体朋友,千万不要错过这波流量,赶紧推广,活动可持续到2月底。这波营销可以让你的公众号从0-100万粉丝,一个月完成。可以让你的小程序流量主在一个月之内从日收益1元到日收益万元。 下面说下玩儿法,玩儿法很多,选择合适自己的,一定要看到最后,要不然你的封面怎么没的都不清楚。 红包封面公众号玩儿法: 1、关注赠送红包封面,对接系统以openid发放,按取关率50%来说,就是5块钱一个粉丝,成本较高,不推荐。 2、完成公众号任务送红包封面,比如集字、集卡活动,自己可设置概率。因集卡活动周期比较长,所以取关率较低,大约在20%左右,粉丝成本也不是很高。实验数据:7天新增3300粉丝(数据比较低,因为我只有一个红包封面,而且特别low),支出205个封面,34元现金,粉丝单价7分钱一个。(红包封面以openid发放) [图片] [图片] 3、公众号其他任务,比如邀请关注等等,成本相对都比较低。(红包封面以openid发放) 红包封面小程序玩儿法: 1、签到赠送,比如签到7天+邀请5个好友,赠送一个封面,签到以激励方式进行,配合页面视频、插屏广告等。 我做的活动是7天内签到3天+邀请5个好友,赠送一个封面。7天总收益是3W+,支出数据是500个封面左右,我的签到没设置订阅消息,大部分都是用户自己忘记,或者邀请好友数量不足。(因为做了openid 判断,必须邀请新用户助力) 2、抽奖赠送,比如满X人抽奖,随机中奖者可获得一个红包封面,可以设置激励抽奖,配合视频、插屏广告。 我做的活动是满20人抽奖,随机中奖,邀请好友赠送抽奖次数(非强制)。7天总收益是5W+,支出是300个封面左右。后来添加粉丝好友做了私域流量群,抽奖次数50+不中奖的赠送一个封面,邀请好友超过50+的赠送一个红包封面,这个具体看自己操作。收益绝对高 3、其他类型小程序玩儿法,比如闯关赠送、答题赠送等等,根据自己的系统去做方案,千万不要忘记裂变,这个活动主要靠裂变。 红包封面后期裂变: 当用户使用你的封面发红包时,所有人都可以看到你的封面故事,从而点击进你的公众号、小程序,是给你二次裂变用户,也是一个可观的数据。 [图片] 红包封面注意事项: 不要投机取巧,让用户购买你的会员、商品等赠送红包封面。一旦被微信查到,直接封掉。未经腾讯允许,你也不得因微信红包封面而以任何形式、向最终用户或其他任何主体收取任何费用。也就是说,只要和钱挂钩,那么你的封面就会被封。(能以openid 发放尽量以openid 发) [图片] [图片] 最后祝大家粉丝多多,流量主收益多多,牛年发发发!!!
2021-01-13 - 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能
背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android、IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更丰富的title效果 左上角的事件无法监听、定制 路由导航单一,只能够返回上一页,深层级页面的返回不够友好 探索 小程序自定义导航栏已开放许久>>了解一下,相信不少小伙伴已使用过这个功能,同时不少小伙伴也会发现一些坑: 机型多如牛毛:自定义导航栏高度在不同机型始终无法达到视觉上的统一 调皮的胶囊按钮:导航栏元素(文字,图标等)怎么也对不齐那该死的胶囊按钮 各种尺寸的全面屏,奇怪的刘海屏,简直要抓狂 一探究竟 为了搞明白原理,我先去翻了官方文档,>>飞机,点过去是不是很惊喜,很意外,通篇大文尽然只有最下方的一张图片与这个问题有关,并且啥也看不清,汗汗汗… 我特意找了一张图片来 [图片] 分析上图,我得到如下信息: Android跟iOS有差异,表现在顶部到胶囊按钮之间的距离差了6pt 胶囊按钮高度为32pt, iOS和Android一致 动手分析 我们写一个状态栏,通过wx.getSystemInfoSync().statusBarHeight设置高度 Android: [图片] iOS:[图片] 可以看出,iOS胶囊按钮与状态栏之间距离为:4px, Android为8px,是不是所有手机都是这种情况呢? 答案是:苹果手机确实都是4px,安卓大部分都是7和8 也会有其他的情况(可以自己打印getSystemInfo验证)如何快速便捷算出这个高度,请接着往下看 如何计算 导航栏分为状态栏和标题栏,只要能算出每台手机的导航栏高度问题就迎刃而解 导航栏高度 = 胶囊按钮高度 + 状态栏到胶囊按钮间距 * 2 + 状态栏高度 注:由于胶囊按钮是原生组件,为表现一致,其单位在各种手机中都为px,所以我们自定义导航栏的单位都必需是px(切记不能用rpx),才能完美适配。 解决问题 现在我们明白了原理,可以利用胶囊按钮的位置信息和statusBarHeight高度动态计算导航栏的高度,贴一个实现此功能最重要的方法 [代码]let systemInfo = wx.getSystemInfoSync(); let rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null; //胶囊按钮位置信息 wx.getMenuButtonBoundingClientRect(); let navBarHeight = (function() { //导航栏高度 let gap = rect.top - systemInfo.statusBarHeight; //动态计算每台手机状态栏到胶囊按钮间距 return 2 * gap + rect.height; })(); [代码] gap信息就是不同的手机其状态栏到胶囊按钮间距,具体更多代码实现和使用demo请移步下方代码仓库,代码中还会有输入框文字跳动解决办法,安卓手机输入框文字飞出解决办法,左侧按钮边框太粗解决办法等等 胶囊信息报错和获取不到 问题就在于 getMenuButtonBoundingClientRect 这个方法,在某些机子和环境下会报错或者获取不到,对于此种情况完美可以模拟一个胶囊位置出来 [代码]try { rect = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null; if (rect === null) { throw 'getMenuButtonBoundingClientRect error'; } //取值为0的情况 if (!rect.width) { throw 'getMenuButtonBoundingClientRect error'; } } catch (error) { let gap = ''; //胶囊按钮上下间距 使导航内容居中 let width = 96; //胶囊的宽度,android大部分96,ios为88 if (systemInfo.platform === 'android') { gap = 8; width = 96; } else if (systemInfo.platform === 'devtools') { if (ios) { gap = 5.5; //开发工具中ios手机 } else { gap = 7.5; //开发工具中android和其他手机 } } else { gap = 4; width = 88; } if (!systemInfo.statusBarHeight) { //开启wifi的情况下修复statusBarHeight值获取不到 systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20; } rect = { //获取不到胶囊信息就自定义重置一个 bottom: systemInfo.statusBarHeight + gap + 32, height: 32, left: systemInfo.windowWidth - width - 10, right: systemInfo.windowWidth - 10, top: systemInfo.statusBarHeight + gap, width: width }; console.log('error', error); console.log('rect', rect); } [代码] 以上代码主要是借鉴了拼多多的默认值写法,android 机子中 gap 值大部分为 8,ios 都为 4,开发工具中 ios 为 5.5,android 为 7.5,这样处理之后自己模拟一个胶囊按钮的位置,这样在获取不到胶囊信息的情况下,可保证绝大多数机子完美显示导航头 吐槽 这么重要的问题,官方尽然没有提供解决方案…竟然提供了一张看不清的图片??? 网上有很多ios设置44,android设置48,还有根据不同的手机型号设置不同高度,通过长时间的开发和尝试,本人发现以上方案并不完美,并且bug很多 代码库 Taro组件gitHub地址详细用法请参考README 原生组件npm构建版本gitHub地址详细用法请参考README 原生组件简易版gitHub地址详细用法请参考README 由于本人精力有限,目前只计划发布维护好这2种组件,其他组件请自行修改代码,有问题请联系 备注 上方2种组件在最下方30多款手机测试情况表现良好 iPhone手机打电话和开热点导致导航栏样式错乱,问题已经解决啦,请去demo里测试,这里特别感谢moments网友提出的问题 本文章并无任何商业性质,如有侵权请联系本人修改或删除 文章少量部分内容是本人查询搜集而来 如有问题可以下方留言讨论,微信zhijunxh 比较 斗鱼: [图片] 虎牙: [图片] 微博: [图片] 酷狗: [图片] 知乎: [图片] [图片] 知乎是这里边做的最好的,但是我个人认为有几个可以优化的小问题 打电话或者开启热点导致样式错落,这也是大部门小程序的问题 导航栏下边距太小,看起来不舒服 搜索框距离2侧按钮组距离不对等 自定义返回和home按钮中的竖线颜色重了,并且感觉太粗 如果您看到了此篇文章,请赶快修改自己的代码,并运用在实践中吧 扫码体验我的小程序: [图片] 创作不易,如果对你有帮助,请移步Taro组件gitHub原生组件gitHub给个星星 star✨✨ 谢谢 测试信息 手机型号 胶囊位置信息 statusBarHeight 测试情况 iPhoneX 80 32 281 369 48 88 44 通过 iPhone8 plus 56 32 320 408 24 88 20 通过 iphone7 56 32 281 368 24 87 20 通过 iPhone6 plus 56 32 320 408 24 88 20 通过 iPhone6 56 32 281 368 24 87 20 通过 HUAWEI SLA-AL00 64 32 254 350 32 96 24 通过 HUAWEI VTR-AL00 64 32 254 350 32 96 24 通过 HUAWEI EVA-AL00 64 32 254 350 32 96 24 通过 HUAWEI EML-AL00 68 32 254 350 36 96 29 通过 HUAWEI VOG-AL00 65 32 254 350 33 96 25 通过 HUAWEI ATU-TL10 64 32 254 350 32 96 24 通过 HUAWEI SMARTISAN OS105 64 32 326 422 32 96 24 通过 XIAOMI MI6 59 28 265 352 31 87 23 通过 XIAOMI MI4LTE 60 32 254 350 28 96 20 通过 XIAOMI MIX3 74 32 287 383 42 96 35 通过 REDMI NOTE3 64 32 254 350 32 96 24 通过 REDMI NOTE4 64 32 254 350 32 96 24 通过 REDMI NOTE3 55 28 255 351 27 96 20 通过 REDMI 5plus 67 32 287 383 35 96 28 通过 MEIZU M571C 65 32 254 350 33 96 25 通过 MEIZU M6 NOTE 62 32 254 350 30 96 22 通过 MEIZU MX4 PRO 62 32 278 374 30 96 22 通过 OPPO A33 65 32 254 350 33 96 26 通过 OPPO R11 58 32 254 350 26 96 18 通过 VIVO Y55 64 32 254 350 32 96 24 通过 HONOR BLN-AL20 64 32 254 350 32 96 24 通过 HONOR NEM-AL10 59 28 265 352 31 87 24 通过 HONOR BND-AL10 64 32 254 350 32 96 24 通过 HONOR duk-al20 64 32 254 350 32 96 24 通过 SAMSUNG SM-G9550 64 32 305 401 32 96 24 通过 360 1801-A01 64 32 254 350 32 96 24 通过
2019-11-17 - 云调用实现内容安全【文本、图片】
应用场景: 解决小程序输入内容违规,导致小程序被封风险,或者微信官方检查到小程序未使用安全审核机制,则警告要求使用,否则封禁搜索功能。 核心代码: 云函数端: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { try { let result = ''; if(event.content){ result = await cloud.openapi.security.msgSecCheck({ content: event.content }); }else if(event.base64){ result = await cloud.openapi.security.imgSecCheck({ media: { contentType: 'image/jpeg', value: Buffer.from(event.base64, 'base64') } }) } return { result } } catch (error) { return { error } } } 小程序端: //文本安全检测 wx.cloud.callFunction({ name: "secCheck", data: { content: "花里胡哨", } }).then((res) => { console.log('msgSecCheck =', res) }) //图片安全检测 wx.chooseImage({ count: "1", complete: (res) => { wx.getFileSystemManager().readFile({ filePath: res.tempFilePaths[0], encoding: "base64", success: (res) => { wx.cloud.callFunction({ name: "secCheck", data: { base64: res.data, } }).then((res) => { console.log('imgSecCheck =', res) }) } }); }, }) 说明提示: 由于代码片段不支持云开发,故无法放代码片段,使用过程中有什么问题,欢迎讨论。
2020-05-07 - 做好内容安全检测,和风险说「再见」!(上)
前言 内容安全检测,是每一个小程序主都面临的“头疼”问题,轻则短暂性不可访问,重则永久封号,甚至关小黑屋。本文将为您详细说明,如何在小程序中对一段文本进行合法内容检测,以判断是否含有违法违规内容。 本文重点为你讲述: 内容安全检测常见应用场景及解决办法 学会使用小程序·云开发的云函数+结合request-promise第三方库实现内容请求校验 掌握如何在小程序端请求云函数(有别于传统的wx.request的方式(类似AJax)) 在云开发的云函数端,利用第三方https请求库(request,request-promise),获取Access_token,以及向微信官方提供的内容检测接口发请求进行校验 云函数端与小程序端错误码的处理 01.背景 无论是小程序还是自行开发的一些类似社交,带有用户自行产生内容的软件应用,例如:即时通讯,社群,论坛,音视频直播等,对于接入内容安全的检测是非常有必要的。 对于小程序而言,这一点在审核上是非常严格的,净化言行,做一个知法守法的人很重要… [图片] 接入内容安全检测,规避输入一些违法违规低俗等内容,避免辛辛苦苦开发出来的应用。 被恶意上传反动言论或上传一些违规内容(文字/图片/视频等),导致小程序或应用被下架,或遭永久禁封,或个人及公司被公安机关打电话,约喝茶等,这样的话,就得不偿失了的。 02.应用场景 检测小程序用户个人文字资料是否违规 针对特点词汇(如过于商业以及营销之类的词)可以进行过滤或禁止输入 在内容发布之前自动检测用户发表的信息(包括评论、留言等)是否违规 03.解决办法 围绕如何处理内容安全检测问题,一般有3种方法: 方案1**:引入第三方接口对内容进行校验(例如:百度AI内容审核平台,网易云盾等)** 方案2: 公司后台小伙伴自行开发文本,图片,音视频等内容审核接口 方案3: 小程序服务端提供的API进行校验 每一种方法各有优劣势,具体如下图。 解决方案 优势 劣势 1 引入第三方接口对内容进行校验 前端同学只需按照官方提供的第三方接口文档,进行校验即可,无需后台介入,功能强大,覆盖范围广 接口调用的频次有限制,收费 2 公司后台小伙伴自行开发文本,图片,音视频等内容审核接口 后台小伙伴自己造轮子,根据自己的业务需求以及用户属性,自定义内容审核机制 开发周期长,成本大,难以覆盖全面 3 调用小程序服务端提供的内容安全API进行校验 简单,高效 想不出来,因为相比前两种方案,对于不依赖后端接口的开发者来说,简直是雪中送炭 在微信小程序生态下,官方提供了2种路径帮助用户解决内容检测问题,即 使用服务器开发模式,通过HTTPS调用 使用小程序·云开发,通过云函数或云调用来实现。 服务器开发模式,相信大家都相对比较熟悉,在此就不再赘述。接下来为大家重点介绍,如何通过小程序·云开发的云函数实现内容安全检测。 04.通过云开发的云函数+request-promise第三方库实现内容请求校验 Step 1: 在小程序端先布局:完成静态页面。(pages文件夹下的文件都是属于小程序前端代码,每个文件夹目录代表的就是一个模块,一个页面) 小程序前端wxml代码示例 [代码]<view class="container"> <textarea class="content" placeholder="写点文字..." bindinput="onInput" auto-focus bindfocus="onFocus" bindblur="onBlur"> </textarea> </view> <view class="footer"> <button class="send-btn" size="default" bind:tap="send">发布</button> </view> [代码] 小程序前端wxss代码示例 [代码]/* pages/msgSecCheck/msgSecCheck.wxss */ .container { padding: 20rpx; } .content { width: 100%; height: 360rpx; box-sizing: border-box; font-size: 32rpx; border: 1px solid #ccc; } .footer { width: 100%; height: 80rpx; line-height: 80rpx; position: fixed; bottom: 0; box-sizing: border-box; background: #34bfa3; } .send-btn { width: 100% !important; color: #fff; font-size: 32rpx; } button { width: 100%; background: #34bfa3; border-radius: 0rpx; } button::after { border-radius: 0rpx !important; } [代码] 经过wxml与wxss的编写后,UI最终长成这样 [图片] Step 2: 完成小程序端业务逻辑的处理 小程序端逻辑JS代码示例 [代码]// pages/msgSecCheck/msgSecCheck.js Page({ /** * 页面的初始数据 */ data: { textareaVal: '' // 页面中需要显示的数据,初始化定义在data下面 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, // 监听表单时,数据有变化时 onInput(event) { let textVal = event.detail.value; this.setData({ textareaVal: textVal }) }, // 聚焦焦点时 onFocus() { console.log('聚焦焦点时'); }, // 失去焦点时 onBlur(event) { console.log("失去焦点时"); // 前端可进行手动的弱校验,也可以在失去焦点时发送请求进行文本的校验,但是每次失去焦点就请求一次,这样是消耗云资源的,在发布时候与失去焦点做校验两者都可以 }, // 发布 send() { console.log("触发发布按钮") wx.cloud.callFunction({ // 请求msgSecCheck1云函数 name: 'msgSecCheck1', data: { content: this.data.textareaVal // 需要向云函数msgSecCheck1传入的值 } }).then(res => { // 成功时的响应返回结果 console.log(res); }).catch(err => { // 失败时,返回的结果 console.error(err); }) } }) [代码] **Step 3 :服务端逻辑处理。**在小程序云函数端创建云函数msgSecCheck1,这个名字你可以自定义,与小程序前端请求的名字保持一致就可以了。 [图片] 选中云函数,右键并打开命令行终端安装request,request-promise,因为request-promise依赖于request,两个都要安装,最后一键上传部署就可以了 [代码]npm install request npm install request-promise [代码] 如果遇到在小程序端请求云函数时,遇到类似下面的错误,找不到什么xxx模块之类的 先看错误码,然后在官方文档中找到该错误码代表的含义 [图片] 一看错误,没有找到模块,在云函数的目录下的package.json中查看是否有安装错误中提示的包的,要是没有的话,就安装一下就可以了的,同时记得每次更改后都要上传部署一下,也可以选择云函数中文件的增量上传 接下来是将是本文的重点内容! **Step 4 :通过云函数+**request-promise实现内容安全检测 对于小程序开发,其实与web端开发也是类似,给元素绑定事件,然后获取元素,只是小程序端没有DOM,BOM的那一套东西,它是数据驱动视图的,吸收了Angular,Vue,React的各个框架的优点,形成了自己的一套规范。 如果有这方面开发经验的小伙伴来说,平缓过度到小程序开发当中来,你会发现总会有惊人的相似,用的语言都是JavaScript,但是与web开发还是多少有很多差异的,这里就不拓展了。 废话不多说,直接上代码 : 小程序前端逻辑代码: [代码]// 点击发送按钮,对输入的文本内容进行校验 send() { wx.cloud.callFunction({ name: 'msgSecCheck1', // 云函数的名称 data: { // 需要向云函数传递过去的数据 content: this.data.textareaVal // 具体要检测的内容 } }).then(res => { // 成功时,做什么事情 console.log(res); // 检测到文本成功时,做一些业务 }).catch(err => { // 失败时,做什么事情 // 失败时,也就是违规做一些用户提示,或者禁止下一步操作等之类的业务逻辑操作 console.error(err); }) } [代码] 上面的代码还可以在优化一下,就是将请求云函数的代码封装成一个函数。 如下所示,不封装也是没事的,只是我习惯性封装一下,如果其他地方也用到该云函数,那么直接调用,避免写重复的代码。 下面是将请求云函数的部分核心代码: [代码]// 发布 send() { // 请求msgSecCheck1云函数,对文本内容进行校验 this._requestCloudMsgCheck(); }, _requestCloudMsgCheck() { let textareaVal = this.data.textareaVal; wx.cloud.callFunction({ name: 'msgSecCheck1', data: { content: textareaVal // 这里可以使用官方文档测试用例,特3456书yuuo莞6543李zxcz蒜7782法fgnv级 } }).then(res => { console.log(res); // 检测到文本成功时,做一些业务 }).catch(err => { // 失败时,也就是违规做一些用户提示,或者禁止下一步操作等之类的业务逻辑操作 console.error(err); }) } [代码] 至于是在失去焦点事件时发送请求还是在点击发送按钮时发送请求,两种方式都可以。 您也可以自定义文本校验,而我个人觉得在小程序端,失去焦点时,可以自定义做一些常规敏感词的弱校验,而在点击发送按钮时,做强校验 。 如果是放在失去焦点时就立马请求,这样请求次数会增多,而放在点击发送按钮时进行校验,一定程度上可以减少小程序端频繁请求。 接下来就是处理云函数端,使用request-promise请求请求微信内容安全接口的示例代码。 [代码]/* * Description: 利用第三方库request-promise请求微信内容安全接口 * * 相关文档链接: * 微信文本内容安全接口文档https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.msgSecCheck.html * access_token获取调用凭证文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html * * request-promise使用文档: https://github.com/request/request-promise * */ const APPID = "wx21baa58c6180c2eb"; // 注意是你自己小程序的appid const APPSECRET = ""; // 你自己小程序的appsecret // 安全校验接口 const msgCheckURL = `https://api.weixin.qq.com/wxa/msg_sec_check?access_token=`; // 向下面的这个地止发送请求,携带appid和appsecret参数,获取token认证 const tokenURL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}` // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 引入request-promise const rp = require('request-promise'); // 云函数入口函数 exports.main = async(event, context) => { try { let tokenResponse = await rp(tokenURL); // 获取token值,因为返回的结果是字符串,需要用JSON.parse转化为json对象 let getAccessToken = JSON.parse(tokenResponse).access_token; // 请求微信内容安全接口,post请求,返回最终的校验结果 let checkResponse = await rp({ method: 'POST', url: `${msgCheckURL}${getAccessToken}`, body: { content: event.content // 这里的event.content是小程序端传过来的值,content是要向内容接口校验的内容 }, json: true }) return checkResponse; } catch (err) { console.error(err); } } [代码] 当你在小程序端输入文本,发送请求时,查看控制台下的结果时,功能是没有问题的。 [代码]特3456书yuuo莞6543李zxcz蒜7782法fgnv级 完2347全dfji试3726测asad感3847知qwez到 [代码] 您可以根据官方文档中提供的测试用例,进行测试,看具体的返回结果的。 [图片] [图片] (控制台错误码) [图片] (合规内容) 云函数请求成功,看看错误信息的反馈,对于熟悉该错误码的人清楚该文本违规了,但是反馈不是很明显,即使当下自己很清楚,然而,在过几个月在回来看代码,你或许都不知道是啥意思。 Step 5 :错误码的正确处理方式 [图片] 对于处理错误码,返回具体的合适信息,对于调试代码,排查问题,也是非常重要 。 这些错误码具体的含义,在官方文档里都有对应的解释,不用去记,去查文档就行。 在面试中,有很多面试官喜欢问http相关状态码的问题,状态码有很多,也真的记不住,但是常见的错误http状态码还是要知道的,我觉得,具体知道怎么处理,怎么查文档就可以了。 真正考验背后目的是,对于根据后端返回的状态码,判断接口哪里出了问题,定位是前端问题还是后端问题,这是一个非常常见的问题。 如果你说你不知道,没有处理过,对于候选人,那肯定是没有信服力的,无论是成功状态还是失败状态,都是应该有对应的用户提示。 05.完整文本安全校验示例代码 [代码]/* * * 相关文档链接: * 微信文本内容安全接口文档https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.msgSecCheck.html * access_token获取调用凭证文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html * * request-promise使用文档: https://github.com/request/request-promise * */ const APPID = "wx21baa58c6180c2eb"; const APPSECRET = ""; const msgCheckURL = `https://api.weixin.qq.com/wxa/msg_sec_check?access_token=`; // 向下面的这个地止发送请求,携带appid和appsecret参数,获取token认证 const tokenURL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}` // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 引入request-promise const rp = require('request-promise'); // 云函数入口函数 exports.main = async(event, context) => { try { let tokenResponse = await rp(tokenURL); // 获取token值,因为返回的结果是字符串,需要用JSON.parse转化为json对象 let getAccessToken = JSON.parse(tokenResponse).access_token; // 请求微信内容安全接口,post请求,返回最终的校验结果 let checkResponse = await rp({ method: 'POST', url: `${msgCheckURL}${getAccessToken}`, body: { content: event.content // 这里的event.content是小程序端传过来的值,content是要向内容接口校验的内容 }, json: true }) // 有必要根据错误码,确定内容是否违规 if (checkResponse.errcode == 87014) { return { code: 500, msg: "内容含有违法违规内容", data: checkResponse } } else { return { code: 200, msg: "内容OK", data: checkResponse } } } catch (err) { if (err.errcode == 87014) { return { code: 500, msg: '内容含有违法违规内容', data: err } } else { return { code: 502, msg: '调用msgCheckURL接口异常', data: err } } } } [代码] 在云函数端,经过添加错误码的判断之后,在来看看小程序端发送的请求,返回的结果。 [图片] (这与没有添加错误码判断,是不一样的,有具体的错误信息内容) 至此,我们在小程序端可以根据这个返回的错误码或成功码,进行一些业务逻辑处理的,比如给一些用户提示,在数据插入数据库之前就做一些判断操作,只有内容合规时,才插入数据库,进入下一步的业务逻辑处理。 [代码]_requestCloudMsgCheck() { let textareaVal = this.data.textareaVal; wx.cloud.callFunction({ name: 'msgSecCheck1', data: { content: textareaVal } }).then(res => { console.log(res); const errcode = res.result.data.errcode; // 检测到文本错误时,做一些业务 if (87014 === errcode) { wx.showToast({ // 当内容违规时,做一些用户提示 title: '您输入的文本内容含有敏感内容,请重新输入', }) }else { // 成功时做其他业务操作 } }).catch(err => { // 失败时,也就是违规做一些用户提示,或者禁止下一步操作等之类的业务逻辑操作 console.error(err); }) } [代码] [图片](当输入的内容有违规时,给一些用户提示或者阻止下一步操作等的) 注意在云函数(后)端处理错误码与小程序端都是要进行处理的,两者不要混淆了的,小程序端最终的一些业务逻辑判断,是根据后端接口返回的状态,最终决定要做什么操作的。 至此,通过request-promise库就完成了文本内容校验的问题。 这个request,request-promise库非常实用,功能也非常强大,类似这种库,常见什么got,axios等之类的,都是支持promise风格的 处理方式大同小异,大家可以去npm或github上阅读相关使用文档的。 06.结语 在小程序中有多种解决方案,推荐使用小程序端请求云开发云函数的方式,无论是不使用云函数方式,自己有后端服务,获取access_token都应该是从后端返回给前端的。 而小程序的秘钥 AppSecret是不应该放在小程序端的,那样不安全的,无论是服务器开发模式还是小程序·云开发模式,都绕不过后台请求微信提供的内容安全接口,然后在返回给小程序端 。 其实在小程序·云开发中,还提供了一种更简便的方法,那就是云调用,它是小程序·云开发提供的在云函数中调用微信开放接口的能力,只需简单的进行配置一下就可以了。 限于篇幅所致,放在下一节介绍。 reference:方案1参考链接: 微信内容安全: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.msgSecCheck.html 云调用 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html#%E4%BA%91%E5%87%BD%E6%95%B0 百度文本审核 https://ai.baidu.com/tech/textcensoring 网易云盾 https://dun.163.com/product/text-detection
2020-09-14 - 另外一种生成海报的方式
前段时间,IOS 7.0.20 进入小程序canvas.createImage()方法报错,为了解决生成海报的问题,使用wx.canvasToTempFilePath()方法把当前画布指定区域的内容导出生成指定大小的图片。直接上代码片段:https://developers.weixin.qq.com/s/vDORkdmS7Jno
2021-01-09 - 微信 schema 跳转之非官方文档
微信“应该”是最近开放了 schema 跳转小程序 的能力,大大方便了短信、邮件、外部网页等唤起微信小程序。 schema 链接格式大体是这样:[代码]weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e[代码],但是这段文本在安卓端无法识别。小规模测试结果如下: [图片] 后来想到用一个正常能够识别的网页地址,内容是重定向到指定的 schema 链接。这就是擅长的领域了,query 参数上带上 schema 链接,location.href 一下不就行了。这里就不 show 代码了,能看到文章的你一定行。 但是,发现在部分安卓手机下(如小米)还是没反应,原来简单的 schema 跳转水这么深的,于是百度谷歌了一下,找到了下面两份关键材料: H5唤起APP进行分享的尝试 AlanZhang001/H5CallUpNative: H5端唤醒移动客户端程序 看源码也不多,总结下来,因不同系统和浏览器对 schema 规范的理解不同,还有一些商业因素,不同环境下面需要用不同的方式进行跳转,甚至有的环境你根本就跳不了。 时间紧,任务重。简单处理吧,不同方式都来一遍,谁好使就用谁。所以简单总结了下,能用的几种方式: location 跳转 a 链接跳转 iframe 跳转 以上三种方式,逐一试用,最后实在不行就不行吧,简单处理,看有没有大神补充的。 相关代码如下: location [代码]location.href = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; [代码] a 链接跳转 [代码]var aLink = document.createElement("a"); aLink.className = 'call_up_a_link'; aLink.href = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; aLink.style.cssText = "display:none;width:0px;height:0px;"; document.body.appendChild(aLink); aLink.click(); [代码] iframe [代码]var iframe = document.createElement('iframe'); iframe.className = 'call_up_iframe'; iframe.src = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; iframe.style.cssText = "display:none;width:0px;height:0px;"; document.body.appendChild(iframe); [代码] 以上代码均可从参考资料中找到出处,感谢 是直接一进来就执行,还是事件触发,都可以。或者是一开始进来就执行,失败了显示几个可选跳转按钮让用户手动触发跳转。 但是关键问题还有一个,如何判断是可以成功唤起了呢?上述 github 代码里提到了一个根据页面 hidden 状态,但不够精准,如果用户没有选择跳转到微信呢?这是另一个需要深究的问题。 出于时间考虑,先以业务交付优先,如果有朋友知道的也可以一起讨论下。 另行文时间短,以技术交流为主,若有瑕疵,欢迎指出。 附上 vue 版本源码:微信 schema 跳转 参考链接: 微信官方文档:urlscheme.generate H5唤起APP进行分享的尝试 AlanZhang001/H5CallUpNative: H5端唤醒移动客户端程序 安卓端,微信schema无法跳转微信小程序?
2021-01-04 - canvas绘制图片卡顿严重?
需求:绘制四张图片,可以对其中任意一张图片拖动。 实现:使用canvas2d中的drawImage函数绘图,使用bindtachmove监听手指移动变换对应坐标,然后render,render函数中遍历四张图片的信息,分别绘制。 问题:拖动图片时,严重卡顿,即使将render函数放在bindtachend中只调用一次,也能感觉到有一点卡。 思考:起初以为是图片onload时耗费了大量时间,后来提前把图片预先onload并存储下来,但没有作用,还是卡顿严重。以及要考虑图片的绘制顺序问题使用了promise,以为是这里导致的,就把promise取消了,也还是卡顿严重。 请问问题是出在哪里了?或者说要如何做才不会卡顿?(困扰几天了,非常感谢解答!)
2021-01-01 - 永远对微信小程序保持尊重——小程序心得体会和开发经验
开篇 我第一次接触小程序时,还清楚的记得是在2017年的春节期间。当我升级最新版的微信后,开始摸索着新的功能变化。在发现页有一个叫小程序的入口,点进去有一列的“应用”。当我打开一个叫“亲戚关系计算器”的小程序后,简单的使用,然后退出,再去寻找其他的小程序,猫眼电影,自选股,滴滴出行…… 当我尝试着去探索更多小程序的过程中,我突然发现,微信正在发展为一个超级的应用流量入口。而微信中的小程序也可以轻松的坐拥10亿的可用用户人群。 那时候过年,我做的唯一一件事情就是细细的读了小程序的开发文档,心想他可以具有多大的活力与动力,能够引发多大的改变。而我能不能适应他,去随着他的发展,带动自己的腾飞。 很遗憾,当时的小程序并不对个人开发者开放,没有办法注册一个小程序,只能在开发者工具上写一些小的应用,去熟悉小程序的开发。 3月27日,小程序重要更新,其中之一就是支持个人开发者注册小程序。那时候的我,在学校上大一,用一个运营公众号的微信号注册了第一个小程序,并做了一些实验性的开发,并上线。 从这个过程中,我开始细细的体会小程序的优势以及不足,小程序适用于那些领域,小程序适合怎么推广。小程序适合那些行业领域的应用。现在看来,其实从小程序提供的能力,就可以依稀端详出小程序所致力的场景与应用领域。 2018年1月,在我历经半年多的思考和衡量下,做出第一款真正属于自己的小程序——GS比赛计分。去尝试探索线上小程序和线下场景的交融,在这过程中有顾虑,有大胆创新,但都为我更深层次理解小程序有很大的帮助。 目前,自己已经做过10余款小程序,除了GS比赛计分开发时间很长,其他的小程序都是一个月左右的时间完工。比如2019年2月的高校课程助手;2019年3月的数据查询助手;2019年4月的数据汇总助手。 我接下来会从小程序的需求分析与应用设计,小程序的开发,小程序的运营3篇来讲述我对于微信小程序的独自见解与经验。希望能为更多的学生开发者有所启发和借鉴。 需求分析与应用设计 要清楚的认识到小程序对自己需求的最大赋能,需要从最初去理解小程序的定位,微信团队对于小程序的定义是这样的: 微信小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。 通过对小程序提供能力的分析,不难看出。小程序相对于APP来说,在降低开发门槛的同时能够满足最普遍的应用需求,适合生活服务类线下商铺以及非刚需低频应用领域。使得微信通过小程序作为生态的建立者和维护者,赋能商家和应用者。以一种生态触角的状态来迅速的捕捉最大化生态红利。 当清楚的理解小程序的定位后,那么就需要合理的筛选需求并进行应用的功能设计。我将会使用【GS比赛计分】作为例子进行分析说明。 1.用实质的问题引出明确需求,并确定解决问题的功能边界 首先思考的是,自己需要做的功能都有哪些,使用人群是谁?使用场景是什么?需要数据的实时性吗? 一开始设计【GS比赛计分】的时候,目的是为了解决现在的中小型比赛使用人工计分的失效性差,出错率高,人工和时间成本昂贵的问题。程序的目的是将比赛计分的相关人员用互联网工具联系起来,以提升计分效率。 [图片] 当明晰了产品目标后,开始考虑使用人群。一般来看,大多数的比赛计分都是由两种角色构成的,一个是评委(分数确定者),另一个是工作人员(分数汇总者)。 传统的计分过程是通过现场工作人员将评委的分数以纸质的形式送达计分人员。在这一过程中就有大量的成本浪费了(一个是人力成本,另一个是时间成本)。另外,计分人员以Excel或计算器和笔记的形式进行比分汇总,在这一过程中又存在大量的成本浪费(人力成本和时间成本甚至还有错误风险)。 [图片] 所以横观所有的主观评分的线下比赛,无一例外都存在比赛结束后有长时间的节目或视频热场环节,其实这都是在为人工计分的缓慢争取时间。 所以,【GS比赛计分】的使用人群和使用场景就确定下来了,与传统人工计分的过程类似,只不过需要互联网赋能,解决时间和人力成本。需要数据的实时交互。 【GS比赛计分】作为解决现象问题的工具,首先需要做的就是不要违背现象中事件的发生次序。所以功能完全参照比赛计分过程来设计。具体功能如下,提交成绩,撤回成绩,弃权处理,实时的选手分数,实时的选手名次,清楚评委数据,解绑评委,重启比赛,结束比赛,比赛内置会话。 2.对程序平台进行横向比对,明确小程序的优势和劣势 当程序的功能确定好后,不要着急着手界面设计,功能模块的组合,交互设计这些过程。当你在进行这些过程之前,需要认真的去考虑你的应用是不是适合在小程序上开发。 小程序的火爆,注定有许多人盲目的进入这一领域,但不是所有的应用都适合小程序的推广模式,也不是所有应用都能吃得起小程序的运行效率的。所以在确定开发小程序之前,正确的认识的合适与不合适,避免让自己的成本白白的付出。 如何确定适不适合用小程序来做呢?我们可以让小程序作为一个互联网平台,把应用带入到互联网的每一个平台中去,进行横向的对比,去找出各自的优点与不足,当做出客观的判断后,应用在小程序上相比于其他平台有没有优势就很明显了。 还是以【GS比赛计分】举例。我所横向对比平台包括PC、原生APP、Web平台、轻应用、小程序。首先从满足功能来说,比赛计分需要很稳定的实时性,所以我将Web平台排除(因为复杂的比赛环境,不同的设备浏览器难免会有问题)。 然后将剩下的平台做分析。PC平台使用可能性太小(原因:比赛现场成本太高昂,租借笔记本可能都不是现实的);原生APP不适合(原因:评委评分前需要下载APP,比赛结束后没什么用了,需要把这个APP卸载。而且IOS和安卓两大平台增加了研发成本);轻应用有阻碍(原因:百度轻应用由于装机量小,覆盖人群不多。支付宝小程序没有社交关系链,无法有效的推送给需要的人。产业联盟轻应用,苹果用户怎么考虑?) [图片] 经过一系列的对比,很明显。对于【GS比赛计分】来讲,微信小程序是最好的应用平台。同时微信小程序仍然可以和不同的平台进行联系,所以可扩展性,功能延展性都是最佳的。 3.应用设计要满足即用即走的理念 当确定小程序是最理想的应用平台时,我们需要对小程序进行便利化的设计。这就需要抛弃一部分原生应用开发或Web开发的一些设计理念。追求“极致、简约”的风格设计。 在【GS比赛计分】的详细设计时,我考虑到,以个人账户的形式去下发比赛的流程是行不通的,既然服务于比赛,那么就以比赛为最基本的账户组成单元,明确一个比赛ID。而同一个比赛的不同角色,以不同的IK进行区别,而角色的设置包含在比赛设计中,由比赛创建者在创建比赛时自行创建。 那么小程序的使用场景就出来了。以比赛现场的告知或者微信聊天的分享,告知比赛参与角色其ID和IK,就可以让角色快速的进入绑定角色并使用程序。免去注册的一系列烦恼。 同时在微信用户面前,这个账户体系是平权的,任何用户在得知ID和IK的情况下都可以进入(账户绑定情况下不可以进入),一定程度上免除密码和忘记密码,注销账户这一系列的麻烦。 当完成比赛后,程序已经完成了自己所有的任务时,使用者可以直接退出程序,不需要注销比赛等操作。真正做到即走。 在使用过程中,要清楚的考虑用户的使用过程,从而做一些保险机制。微信小程序运行在微信上,微信是社交工具,就免不了用户会退出小程序甚至微信去做一些其他的事情。所以【GS比赛计分】在设计时要保证用户回来要用到自己想要的,在程序设计中有中间状态界面能够保证用户可以迅速进入使用状态。 4.对于很大的系统,要把最适合小程序部分拿出来,而不是全部 现在的【GS比赛计分】其实是一个大的生态系统,结合有线下的网络接口,展示接口,线上小程序,web平台。每一个部分都承载着自己独特的应用价值。 比如Web平台就承载比赛管理的任务,创建比赛,上传比赛文件,选手图片,设置选手(名称、介绍、手机号、图片、出场顺序),设置评分项(名称、权值、预置分数),设置评委(名称、权值,IK)。从实际的分析来看,比赛管理最适合在PC端进行,不管是文件还是图片,公认都是PC上传比较容易。 在最初设计的时候,我错误的把系统分成了多个小程序进行系统搭建,在实际使用过程中造成了重大的缺陷和用户流失。最大的表现是,我开发了【GS比赛创建】小程序,作为比赛的入口程序。从而造成比赛数量增速缓慢,大的使用场景无法突破,老用户意见上升等一系列问题。不得不注销了【GS比赛创建】小程序,并进行很大的架构调整。【GS比赛计分】暂停使用,造成大量的用户流失。 [图片] 所以,当设计多场景,前后联动性很强的应用时,需要将功能进行使用划分,每一个划分需要找最适合的平台。最适合小程序的部分,就做好与其他平台的无缝结合。 5.小程序的应用场景和机会 目前来看,小程序的应用场景主要包括支付场景,比如扫码支付、快消餐饮、移动购物、交通出行等等,工具类小程序能更碎片化、垂直化地满足细分的应用场景需求。 根据微信的最近更新变化来看,公众号和小程序的协同作用将越发明显,公众号的作用也将进一步放大,因此未来发展的机会可能在这几方面。 内容营销,小程序能通过更完善更精准的服务进而提高用户黏性。具体来看包括各大知识付费的小程序以及在教育风口上的小程序。当然,这些小程序也可同时开发APP(按微信对小程序的开发步骤来看)从而实现用户沉淀。 具有支付场景的各类商家。包括传统的已有一定客户群的商家提供更方便的服务或者实现线上线下联动以及新零售。 工具类小程序。包括共享经济领域,这类小程序即用即走,轻量化,便捷化。 天生具有社交属性的小程序。比如抽奖、互赠礼品、拼团减价、社交性的小游戏以及帮拿快递等。 小程序的开发经验 1. 微信小程序开发文档是最好的学习文档 很多同学喜欢看视频教程,或者买一本小程序开发书来学习。觉得这么学会加深理解更加容易上手,而官方文档干巴巴不好学。现在的微信小程序能力更新速度很快,当一个教程或书出来的时候,其实已经过时了。建议同学去微信公开课中学习微信小程序的入门教程,开发入门后,根据自己的开发需要,自行阅读官方文档来学习。 [图片] 2. 必须了解小程序的运行原理 微信小程序是运行在微信中的,所以运行速度并不能和原生媲美。但是在开发小程序的过程中,可以用良好的编程思路来追求程序的高效运行水平。但前提是,你需要对小程序的运行环境有所理解,需要知道在开发环境和真实环境(IOS和Android)下的运行差别。大部分的开发坑都是因为不同运行环境造成的。 官方的声明:微信小程序运行在三端:iOS、Android 和 用于调试的开发者工具;在 iOS 上,小程序的 javascript代码是运行在JavaScriptCore中;在Android上,小程序的javascript代码是通过 X5 内核来解析;在开发工具上,小程序的javascript代码是运行在nwjs(chrome内核)中。 微信小程序的运行环境类似 ReactNative ,而不是纯 Html5。两者最大的不同在于,ReactNative 的界面是由原生控件渲染出来的,而 Html5 的界面是由浏览器内核渲染出来的。两者在性能上有较大的差异,从而表现出来微信小程序要比h5页面好很多。 3.安利小程序云开发 云开发为开发者提供完整的云端支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。目前提供三大基础能力支持: 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写自身业务逻辑代码 数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库 存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理 云开发使得小程序开发门槛进一步降低,甚至偏前端的开发人员也可以独立开发小程序了。另外,云开发中云函数支持去请求第三方服务接口。所以云开发最大程度上提供了小程序所需基本API的构建,另外可以通过第三方服务请求能力扩展API能力。 [图片] 而且云开发的数据库和存储完全可以与云函数构建一个小型的后端平台。并发能力中等,适合大部分的小程序业务API的构建。 4. 养成良好的小程序代码习惯 微信小程序的代码文件分为4种,WXML、WXSS、JS、JSON。 JSON文件是小程序的配置文件,APP.JSON是小程序全局配置,另外每个页面也会有配置文件。建议页面中的JSON文件只填写页面需要配置的(比如页面标题),而不变化的不要写到里面(比如标题风格,背景颜色等等)。 JS主要是javascript语法,建议以模块的形式对通用的方法进行封装,尽可能的利用好代码,不要有大量重复的方法语句。如果多个页面需要,可以单独建立js文件,在需要的页面进行引入。 WXSS是样式文件,遵从css的编写规则,尽可能的少些样式表,多多利用已经写过的样式,如果多个页面需要,可以单独建立wxss或者写到app.wxss中 WXML是页面结构文件。如果多个页面中有相同的结构,可以单独封装为模块,而模块中的逻辑方面也尽可能遵从代码最大效益化。 总之,开发代码简洁,会使得编译后的小程序包很小,使加载速度更加快速。 5. 小程序填坑总结: 首先,微信开发者社区是好的小程序开发交流平台,从中可以获得大多数问题的解决方法: https://developers.weixin.qq.com/community/develop/question 另外,许多人常用搜索引擎来直接搜索问题的解决方法,一般大部分的错误或者问题都会在开发文档上写的很清楚,只不过很少有人去注意到,推荐几个小程序填坑的集合文章: https://blog.csdn.net/weixin_42448956/article/details/82414225 https://www.cnblogs.com/shaoting/p/6051261.html https://www.imooc.com/article/36148 https://www.cnblogs.com/wangking/p/6946438.html https://www.jianshu.com/p/4362e52f5c49 小程序的运营 小程序运营属于软件产品运营的一种类别。从产品生命期来看,小程序运营分别为研发期、种子期、成长期、成熟期、衰退期。另外由于小程序在运行模式和定位的不同,表现在推广形式上与平常的软件产品有很大的差别。接下来,我会用【GS比赛计分】【数据查询助手】小程序为例子佐证分享小程序的运营经验。 1. 小程序研发期,搞清楚产品的定位以及目标用户 当你不是运营小程序的产品策划者,你需要首先要搞清楚产品的定位以及目标用户。(这也是多数互联网公司将产品策划和运营作为一个岗位的原因)。在整个产品的研发期,需要跟进产品的每一个细微功能点,要明晰产品的使用用户;还要时刻去观察产品的领域有没有竞品的出现,用户习惯有什么改变;要时刻去衡量产品的竞争优势,为之后的发布运营做好准备。 【数据查询助手】是提供自定义信息查询服务的小程序。任何微信用户均可以上传自定义的数据(报表,成绩单等任何表格数据)建立查询。 [图片] 当在产品研发的一个月,我不断的探索小程序领域,APP领域,Web领域有没有相同的功能产品或服务。甚至将问卷系统(腾讯问卷、问卷星、问卷网)作为潜在的竞争对手。 另外我还制作了web版和原生APP的Demo去体验他们与小程序体验的不同。去确定微信小程序是很好的适应平台。从而为之后的运营做足了准备,提升了自己的信心。 2. 小程序种子期,要充分利用体验版过程 小程序体验版相当于其他应用平台的内测版。体验版可以更高层次的模仿真实的用户环境。在这一阶段更容易发现用户间连接要求高的应用缺陷。同时可以在安全的范围内去聆听用户的真是使用反馈。 【GS比赛计分】开展了长达1个月的体验版。邀请了20个核心用户去体验。由于应用需要与服务端建立实时连接。用户不同的设备,不同网络环境对程序的稳定性做出了很大的考验。在这一阶段修复了不少场景不同导致的错误或者效率低下问题。 另外一开始推出的小程序界面设计只遵从了功能设计,没有很好的考虑真实的使用场景。所以在这一阶段,我最大程度上听取体验用户的建议,对整个界面进行改版,使交互更加的亲近用户。 3. 小程序成长期,明晰获取用户的手段和推广手段,最大化的成长: 当小程序功能稳定后,到了成长阶段,用户使用是最核心的任务。获取用户的时候,必须先让对方了解自己的产品,建立认知,将产品介绍给用户,让用户进入小程序之后,想方设法让产品与用户产生交互,让用户不断体验产品,让活动始终覆盖用户,让用户对产品认可,要完成产品与用户的关系构建。 从现在许多小程序的运营手段来看,基本上都围绕着社交裂变和线下推广的方式来提升小程序的获客表现。而小程序由于较低的开发成本,较快的更新速度,以及较低的试错成本,使得多数很强势的小程序都采用功能矩阵发展模式,快速实现功能及迭代。 小程序获取用户的手段主要由下: 朋友圈分享(包括图片二维码、广告直接进入); 聊天好友推荐转发; 线下二维码(包含商家推广、广告推广); 微信搜索(一般由其他社交平台或者用户需求引起); 线上识别二维码(线上推广,文章推广,或其他社交平台的推广); 其他小程序的跳转(互相推荐) 公众号跳转(公众号运营推广) APP跳转(一般是APP延展的简洁功能推广,或者轻量级触达用户形式的推广) 小程序发掘社交推广的手段主要由下: 社交立减金,实现社交裂变; 社交比拼玩法,引导社交裂变; 互动加入分享按钮,提醒用户转发; 设计同伴环境,鼓励社群传播; 设计任务玩法,领取奖励; 设计福利,鼓励好友助力; 聚焦核心功能,促进口碑传播; 【数据查询助手】从产品定位上就自带社交裂变的元素,当查询创建者创建了一个查询后,他可以根据要分享的人群情况选择多种分享方法。如果是企业微信或者微信工作群,那么可以直接分享小程序到聊天窗口。其他的用户可以直接进入小程序进行查询。如果是线下的查询(推广会,现场发布等),可以通过小程序二维码的方式进入查询界面查询。对于其他社交平台来讲,可以用二维码来做分享,如果是常用用户(添加到我饿小程序或者桌面作为常用工具的用户),可以直接通过复制文字(含查询码)然后进入应用的方式快速开始查询。 同时每一个查询者,都可能是潜在的查询创建者和程序推广者。所以要在这一阶段不断的优化体验流程,尽可能做更多事情覆盖多场景的查询(比如微信搜一搜直接搜索查询码,直接查询),去吸引用户,留住用户。 由于小程序用完即走的理念,导致许多工具类小程序(不含深度融合线下和社交的)用户的留存普遍较低。既然工具类就是服务用户,那么就把小程序慢慢的做成一种用户习惯,从习惯的养成变为行业应用的转化。从转化中寻找切入点,进而挖掘可以创造价值的功能产品。 所以,做小程序不要过分贪图大规模的使用率,大批量的用户。他本身是一种服务理念的触达和养成,你需要在用户心里养成使用习惯,而不要上来糖衣炮弹把用户打蒙,甚至反感。这样小程序的生态就被搞乱了。 4. 小程序成熟期,稳定期最重要的就是小版本的迭代更新: 刚才讲到,小程序有较低的开发成本,较快的更新速度,以及较低的试错成本。所以在小程序成熟期需要根据用户数据不断的去更正调整功能,去保持较高的运营分数。在产品功能中,适时的进行用户付费转化。 付费转化一方面可以拉开用户层次,对小程序的用户是一种活水作用,提升用户的使用粘性,容易过滤最核心的用户,提供更加好的产品服务。如果在成长期很好的进行用户习惯的养成,这个过程会更加自然。反之,应用将会更快的进入衰退期。 付费转化的方式主要由几类: 电商类:主要靠活动、优惠刺激(现实抢购、秒杀、预约、限时满减、显示商品库存和抢购人数); 游戏类:游戏道具(向朋友求助、每日签到、社区活动等方式免费获得,但数量有限,且都是一些级别较低的道具)高价值道具付费、皮肤售卖、游戏币购买; 内容类:付费文章、阅读币购买、付费课程; 5.小程序衰退期:适时舍弃,进行新产品开始推出 小程序开发周期短,很多时候应用分析不够透彻,更多的是一些商业或推广试错。导致许多小程序还没有进入成长期就进入晚期了。这一类小程序直接舍弃就好,不需要什么转化了,这也不算小程序的衰退期(没有盛何来衰)。 对于经历过成熟期的产品主要有几个原因导致进入衰退期: 市场中有新的创新型产品,导致冲击衰退。 自身功能设计有缺陷,导致用户流失。 对用户了解不透彻,付费转化失败,用户迁移到同类产品。 产品质量下降,不能适应用户行为的变化。 由于运营组织的原因,产品运营出现危机。 在这一阶段,运营已经无能为力。那么就进行产品的复盘,积极的去投入到新产品或者新领域的探究上面去。小程序的开发成本较低,所以可以有很多机会去重新塑造一个好的产品。 6.总结,谈谈自己的想法: 现在多种多样的互联网产品不断产生,产品竞争异常激烈。互联网产品的运营手段也是推陈出新,花样繁多。在这里我不给大家分享一些运营花样,因为每个小程序都是独特的,应该有自己独特的运营手段,具体是什么,希望产品者和运营者本身用热爱小程序的心去发现和实践。 永远保持对产品的尊重,对用户的尊敬是每个产品人最重要的事情。如果对应用足够热爱,你会厌恶他被污染,他被别人嫌弃;你会尽自己的可能让他变得更加本真,你会合理的去运营突破,去帮助产品走向更高的位置。 如果你没有爱你的产品,再出色的产品也只是昙花一现而已,并不能给你带来任何长远的意义。 这是最好的小程序运营指南:https://developers.weixin.qq.com/miniprogram/product。 [图片]
2019-06-23 - 清楚自己的价值——如何判断应用是否适合微信小程序?
每个场景都适合微信小程序吗? 小程序更偏向于场景服务: 我们都知道微信公众号、服务号等公号更多偏向于传媒、内容的输出;而小程序更偏向于基于特定场景(商业、办公等)的服务。两者之间一个是内容的产出带动用户情感,一个是面向用户的服务带动用户的多方面体验。 面对支付宝小程序聚焦在商业和生活服务(租借和支付场景),百度轻应用基于搜索功能、地图来做相关布局的情况下,微信小程序则是扎根于社交、拼团、游戏等领域; 无论是聚焦哪个领域,都离不开小程序本身偏向于场景的服务。小程序是铺设另一个移动互联网基础设施的承载体。随着各个小程序的日活益日增高,小程序已经慢慢融入大众的生活。 在拼多多、美团、京东等旗下APP中,小程序的释放也逐渐使用户养成使用的习惯:乘车码小程序、扫码购小程序、停车小程序等。用户在线下场景可通过各种小程序来进行办事。 小程序将来的用户数将逐渐递增,无论是对小程序的开发者还是小程序的使用者而言,小程序都将不可替代的应用在各种生活场景。 不是每个场景都适合小程序: 在这2年多的时间里,我帮助过至少50多个团队完成小程序的设计规划。有些团队想做原生APP,但项目却很适合用小程序来做,所以被我推荐做成微信小程序,现在有不错的运营成绩。 有些团队特地找我去设计规划小程序,但是项目本身并不适合小程序的产品模型,所以被我劝退,或者简化了一大半的功能做先导。 并不是每个项目场景服务都适合小程序来做的,因为有的做不到,及时做到了用户也不会来,因为小程序的此种场景目前还没有渗透到用户的使用习惯中去。 在不断的实践中,我总结出一套模型,专门用于判断目标场景是不是适合小程序: 如何判断目标场景适不适合微信小程序? 引入 我们在移动互联网时代的时候,有四维产品定位方法曾被应用于产品的自身价值定位和之后的产品演进当中。不过目前的互联网发展态势更多注重功能的叠加和增量划分,这种产品定位方法并没有多少人使用了。但就我实际应用而言,这套方法可以用于原生APP和微信小程序之间转移的界定标杆。 四维产品定位 所谓四维,就是有4个划分标准,分别是量级、功能、频次、需求,我们将这四个标准随意两者相交,就可以清晰的给自身产品进行定义。 量级、功能划分: 我们将量级和功能两个标准做成坐标图,就可以得出四个象限:分别是重量级功能复杂、重量级功能单一、轻量级功能复杂、轻量级功能单一。 所谓重量级,就是在APP运行时需要的底层服务多,比如拍照、录音、定位、控制蓝牙WIFI、NFC、读取通讯录、写入短信、拨打电话等等。调用的服务越多,说明APP的量级越重。 [图片] 但量级大小与功能复杂度没有直接的关系,比如上图中翻译类(谷歌地图、有道词典),他们所提供的功能就是翻译,但是他们却需要录音、拍照等功能,因为他们有拍照翻译,录音翻译的功能,所以属于重量级功能单一的划分。 重量级功能复杂的产品数不胜数,我这只列举装机量比较高的几类,有像微信、qq等通信平台工具,有支付宝这类的支付安全…… 这些工具调用的服务很多,提供的功能也是非常丰富,大部分安卓安装包大小都在50M往上。 值得一提的是,百度云盘属于重型工具,里面除了网盘基本功能之外,还加入了一些工具类小程序作为功能扩展,所以也属于重量级功能复杂的一类。 [图片] 而轻量级功能复杂就有些争议了,许多人会问购物(淘宝、京东)为什么算是轻量级的。在这里需要科普一下,大部分购物类APP都属于混合式开发,展示给用户的大部分是H5界面。大家可以断掉自己的手机网络,打开淘宝或者京东,点击首页的任何一个类目,就可以发现他们的真实面目了。 同时还有许多重量级应用的lite版本,在砍掉一些功能简化设计后,属于轻量级应用的范畴。 最后功能单一轻量级应用就很多了,日常的便签,计算器都属于这一类。 频次、需求划分: 让我们把上个坐标图中的应用打散,按照新的衡量标准重新定义这些应用,我们可以得到下面的这个坐标图。 [图片] 频次、需求这些属于夹杂个人情感的,不可统一界定的,所以只能从侧面去印证。同时这两个维度也可以作为后续运营的目标恒定。在这里我尽可能的取折中的分类,当然对于每个用户这都不完全正确,毕竟用户群体不同。 刚需如何界定呢? 就是用户在有限的资源下保留这款应用的可能性,可能性越高越属于刚需,可能性越低越属于非刚需。 比如部分用户使用的手机是低配版本,或者因为工作学习的需要使用16G存储的手机,所装的软件也只有10几个。通常来讲,微信、QQ、支付宝是必须存在的,也就是说对于大部分用户来说,通信和支付的刚需最大。 但像淘宝、京东虽然在目前网购时代很流行的阶段,但对于低配版本的手机来讲由于安装所占空间太大,迫不得已只能选择不安装这类软件。所以看大数据,淘宝在低配手机的装机量是很小比例的。有些应用为了满足这类用户的刚需心里,会选择出lite版本。当然,目前小程序逐渐将lite代替了,我们稍后再讨论这个问题。 [图片] 而频次就比较好理解了,就是用户每天打开多少次,或者多少天打开一次。同样,由于用户的不同,应用间的频次就不同;比如说地图类,有些旅行爱好者几乎是每天必须打开,当然还有出差上班路痴一族。但对于出行频次不高的人群(如学生),地图的频次就很低了,有可能是几天一次。 所以,按照各自应用的运维数据,或者去访谈用户的使用心理。应用拥有者会清楚的给自己的产品找到属于自己的位置。 四维汇合: 我们单独讨论了可界定的两种标准和不可界定的两种标准,分别对产品进行定位之后,我们就可以得到一个关键词组。比如: 微信: 重量级、功能复杂、高频次、刚需 淘宝: 轻量级、功能复杂、高频次、刚需 百度网盘: 重量级、功能复杂、低频次、刚需 工具: 轻量级、功能单一、低频次、刚需 生活服务类: 重量级、功能单一、低频次、非刚需 每个应用都会有自己的专属关键词组,并且随着版本的不断升级,其关键词也会不断发生变化。总之,一个好的应用最重要发展目标是刚需,如何让自己的产品被更多用户使用,并且让用户没有办法抛弃;并且在量级和功能上的性价比与同类竞争产品相比更高。如果做成这两点,那当之无愧就是好的应用产品。 四维产品定位在小程序上的应用: 当我们对自己的项目产品进行划分界定的时候,就可以清楚的体现出产品定位和价值目标。那么自己的应用如何判断适不适合小程序呢?我们来做以下几个梳理: 小程序偏向于构建轻量级的应用 首先,小程序偏向于构建轻量级的应用,如果强行将重量级的应用赋予小程序,就会出现功能的不稳定。比如,非常依赖蓝牙或者GPS定位的应用,就有体验的不好处。例如肯德基的小程序,我周围小伙伴经历,定位多半是无法使用的,也没有办法选择店面,导致用户体验非常不好。 小程序偏向于构建功能单一的应用 在这里,我不是说功能必须是一个,而是说应用的页面关系不要太复杂。不要有太多webview的东西在里面,这样体验感觉不是很舒服的。例如京东小程序,基本上包含了APP的基础功能,使用体验稍微逊色一些。这也就是我为什么要说京东、淘宝是轻量级应用的原因。但是购物小程序一般调优都特别的好,所以对于频次不高,只是偶尔买买或者推荐购买的用户来讲,小程序这么做是非常值得推荐的。 非刚需的应用很适合在小程序上做 我们之前界定刚需和非刚需,是以用户在有限的资源下保留这款应用的可能性划分的。对于非刚需的应用,原生APP的开发成本会显得太高,而且没有多大的装机量。由于小程序持续火热,许多用户会直接在搜索页中搜索相关应用来替代原生APP,所以,非刚需的应用适合放到小程序上做。 低频的应用根据自身的定位来确定 由于产品属于低频的领域,所以应该根据自身的情况,分解功能或者简化产品之后再放到小程序上,一方面刺激产品的发展,另外可以当作触角,能够更清楚的了解用户的行为心理和习惯,以便更好的决定产品的未来变迁。 例如,百度网盘小程序就简化了应用APP上的功能,并主打推出好友共享文件这一功能,用来刺激自身产品的使用。 合并分析,自己明晰 当按照四维的单项分析完之后,发现自己的产品有多个重叠点,比如功能复杂的轻量级应用,一个标准适合,一个标准不适合。那么就要考虑坎功能或者坎重量了,但这种坎必须要保存产品原有的灵魂前提下进行。我看过不少产品,小程序版砍的面目全非,根本和主应用打不成任何的关系,这种就是没有意义的了。 一般来讲,可以按照标准适合程度分别来建议: 4个标准适合的应用(查询、工具),非常适合小程序。 3个标准适合的应用(生活便利),注意适应不适合的标准。 2个标准适合的应用(百度网盘),定向取舍功能缩减,可以当作引流和刺激使用的触角。 1个标准适合的应用(地图),适合大型互联网企业尝试,做成功就是行业标准。 都不适合的应用(微信、支付宝),还是做小程序吧(滑稽) 而对于功能特别复杂的,重型的大型应用来说,可以适当的将其拆分多个小程序,但是前提是要简化功能的基础上进行。目前有许多线下商铺的小程序分的有些过分,你每点一个模块,就会跳到另一个小程序,用户就会产生跳转恐惧心理,并不敢继续尝试功能的使用了。 总结 我上篇文章中提到过: 小程序相对于APP来说,在降低开发门槛的同时能够满足最普遍的应用需求,适合生活服务类线下商铺以及非刚需低频应用领域。使得微信通过小程序作为生态的建立者和维护者,赋能商家和应用者。以一种生态触角的状态来迅速的捕捉最大化生态红利。 小程序正属于上升期,优质的小程序越来越多,可替代的APP也越来越多。所以很多应用都十分着急的上线了自己的小程序,以期依靠流量来获得更好的产品发展,使自己不会被小程序洪流所冲散。 顺风行船每个人都想,但清楚的认识自身是很重要的,如何顺风,如何行船需要好好考虑。在这两年中,亲眼目睹许多小程序的衰退,最初的To B介绍类的小程序说没就没了。 我在不断用自己的所见所学和实践来总结经验,尤其是总结产品方面的经验,好给志同道合的伙伴们以思考。希望未来有越来越多优质的小程序出来,也希望微信可以保持初心和本色,更好的发展生态。有许多人为此不断努力着,希望努力终有收获!!! 文章全部原创,思考来自于学习和交流,其中有不正确之处请各位大佬指正,谢谢您能看到最后,我们一起努力
2022-09-26 - 小程序中Lottie动画库展示在iOS 微信7.0.18版本上展示还正常,升级7.0.20后白屏咋?
[图片]报这种东西,,,
2020-12-31 - 新拟态Neumorphism样式的实现
流行是一种轮回,今年新拟态的风格在设计圈非常火,虽然这个设计风格存在一些问题,但个人也是蛮喜欢的,于是就把这个应用到自己的小程序里了。 一、新拟态定义和特点 [图片] 通过观察,我们发现新拟态有如下特点: 只有一个光源,左上角亮色投影,右下角深色投影常用与按钮组件和卡片上与背景对比度较弱分为两种状态,凹下去和凸出来二、代码实现 .neumorphism{ box-shadow: -7px -7px 20px 0px #fff9, -4px -4px 5px 0px #fff9, 7px 7px 20px 0px #0002, 4px 4px 5px 0px #0001; } .neumorphismin,.neumorphism:active{ box-shadow: 0px 0px 0px 0px #fff9, 0px 0px 0px 0px #fff9, 0px 0px 0px 0px #0001, 0px 0px 0px 0px #0001, inset -7px -7px 20px 0px #fff9, inset -4px -4px 5px 0px #fff9, inset 7px 7px 20px 0px #0003, inset 4px 4px 5px 0px #0001; } 在使用的过程中,只需要在原来的按钮加上class即可,点击态自动加上凹下去效果。 凸起:class ="neumorphism" 凹下:class ="neumorphismin" 实现前后对比图: [图片]
2020-05-13 - 小程序开发中的一些实践和踩坑
在公司小程序也开发了一段时间了,中间遇到过很多问题,特此记录几个比较典型的问题和解决方案。 一、textarea 的高层级问题 此问题提供源码 demo,可导入微信开发者工具查看。 症状(表现) textarea 是小程序的原生组件,它的一个表现就是优先级很高,这导致了一些困扰,比如我们有一个表单页面,最下面就是一个 textarea 和一个保存按钮,这会导致 textarea 的文字会浮现在按钮上。如下图: [图片] 它最大的问题时会导致保存按钮可能点击无效或者会弹出键盘,并且开发者工具模拟器和真机表现不一样,这真是个坑! 诊断(实验) 模拟器中,针对 [代码]position:fixed[代码] 定位的按钮,我们加一个 [代码]z-index:10[代码] 即可, [代码]z-index[代码] 等于多少合适不清楚,试了等于 1 是不行的,10 就可以,其余的值没试过。 [代码].submit-cls { position: fixed; left: 30px; right: 30px; bottom: 300px; text-align: center; background-color: green; color: #fff; z-index: 10; } [代码] 模拟器中的表现: [图片] 然儿,真机上(Android)依然无效!如下图: [图片] 于是我想到了 cover-view 标签,cover-view 是微信提供的一个原生组件,它是覆盖在原生组件之上的文本视图,可覆盖的原生组件包括 map、video、canvas、camera、live-player、live-pusher 之上,只支持嵌套 cover-view、cover-image,可在 cover-view 中使用 button。 用 cover-view 标签包裹 button 如何呢?郁闷的事情发生了,真机上按钮不见了!。。。这方法貌似不行。。 [代码]<cover-view> <button class="submit-cls" id="button" bindtap="onClick"> Button z-index: 10 </button> </cover-view> [代码] 那我直接用 cover-view 标签作为按钮呢? [代码]<cover-view class="cover-view-clas" id="cover-view" bindtap="onClick" > cover-view z-index: 10 </cover-view> [代码] [代码].cover-view-clas { position: fixed; height: 40px; line-height: 40px; left: 30px; right: 30px; bottom: 250px; text-align: center; background-color: orangered; color: #fff; } [代码] 结果在模拟器里不行 [图片] 但是真机上表现很好。于是我也加了一个 [代码]z-index: 10[代码] ,这样模拟器和真机表现就一致。 药方(总结) 综上所述,要解决这个问题似乎只有一个办法,那就是用 [代码]cover-view[代码] + [代码]z-index:10[代码] ,然儿这样会导致一个的副作用,没法使用微信的开放能力比如 [代码]open-type[代码]。 二、setData 优化 我们知道,与传统的浏览器 Web 页面最大区别在于,小程序的是基于 双线程 模型的,在这种架构中,小程序的渲染层使用 WebView 作为渲染载体,而逻辑层则由独立的 JsCore 线程运行 JS 脚本,双方并不具备数据直接共享的通道,因此渲染层和逻辑层的通信要由 Native 的 JSBrigde 做中转。 每当小程序视图数据需要更新时,逻辑层会调用小程序宿主环境提供的 [代码]setData[代码] 方法将数据从逻辑层传递到视图层,经过一系列渲染步骤之后完成 UI 视图更新。然而当 [代码]setData[代码] 传递大量的新数据、频繁的执行 [代码]setData[代码] 操作、过多的页面节点数时会影响渲染性能。 区分数据类别 意思是, setData 只用来通知页面更新,只有需要通知页面更新的时候,页面引用了某个 data 字段时才使用,其它字段数据我们有时候可能只是为了让 js 方便使用。比如如下数据 [代码]data: { form: { name: 'xxxx', ... ... }, index: 0 } [代码] 假如 页面上根本没用到 index 来展示,只是我们的逻辑变量,那么我们在赋值的时候就直接 [代码]this.data.index = xxx[代码] 即可,不要用 setData 去赋值了。 合理利用局部更新 setData 是支持使用 数据路径 的方式对对象的局部字段进行更新,我们可能会遇到这样的场景: list 列表是从后台获取的数据,并展示在页面上,当 list 列表的第一项数据的 src 字段需要更新时,一般情况下我们会从后台获取新的 list 列表,执行 setData 更新整个 list 列表。 [代码]// 后台获取列表数据 const list = requestSync(); // 更新整个列表 this.setData({ list }); [代码] 实际上,只有个别字段需要更新时,我们可以这么写来避免整个 list 列表更新: [代码]// 后台获取列表数据 const list = requestSync(); // 局部更新列表 this.setData({ "list[0].src": list[0].src, }); [代码] 善用自定义组件 小程序自定义组件的实现是由小程序官方设计的 Exparser 框架所支持,框架实现的自定义组件的组件模型与 Web Components 标准的 Shadow DOM 相似: [图片] 在页面引用自定义组件后,当初始化页面时,Exparser 会在创建页面实例的同时,也会根据自定义组件的注册信息进行组件实例化,然后根据组件自带的 data 数据和组件 WXML,构造出独立的 Shadow Tree ,并追加到页面 Composed Tree 。创建出来的 Shadow Tree 拥有着自己独立的逻辑空间、数据、样式环境及 setData 调用,这是组件化带来的好处。 [图片] 基于自定义组件的 Shadow DOM 模型设计,我们可以将页面中一些需要高频执行 setData 更新的功能模块(如倒计时、进度条等)封装成自定义组件嵌入到页面中。当这些自定义组件视图需要更新时,执行的是组件自己的 setData ,新旧节点树的对比计算和渲染树的更新都只限于组件内有限的节点数量,有效降低渲染时间开销。 三、大表单交互的一点实践经验 在项目中,有一个预约模块,字段忒多,保险业务嘛,需要用户填写各种数据的,为了用户体验拆成了多个步骤,如图 [图片] 一开始,业务上要求切换 tab 的时候数据要缓存,跟 Vue 的 keep-alive 一样,但是小程序没有这样的机制,所以利用小程序的 [代码]hidden[代码] 属性,也就是 Vue 中的 [代码]v-show[代码],组件始终会被渲染,只是简单的控制显示与隐藏。关于 wx:if 和 hidden。 这样的导致页面节点太多,在低性能手机上会出现卡死的现象,直接无法渲染或者渲染太慢。 后来改为 [代码]wx:if[代码] 来切换 [代码]<view wx:if="{{current === 0}}">......</view> <view wx:if="{{current === 1}}">......</view> <view wx:if="{{current === 2}}">......</view> ... ... [代码] 这样以来一次渲染节点太多的问题解决了,但是怎么实现 tab 切换的时候输入的内容杯缓存呢?其实我们的笨办法就是切换的时候把前一个表单内容保存到 localStorage 或 gloabData 中,切换回去的时候再取出来填充,这中间会有一个明显的渲染过程,肉眼可见,没办法,目前只能牺牲一点点体验了。 对于这种大型表单,数据处理和逻辑交互的时候要非常注意,很容易出现性能问题。 这次就说这么多吧,文章如有什么错误,或有什么想法,请留言,不吝赐教! 全文完。
2020-12-23 - iPhone 安全区适配,小黑条适配标准解决方案
老规矩,先上代码 https://developers.weixin.qq.com/s/WcultVmb7Hmy 需求场景 自从有了iPhone底部小黑条之后,页面底部就有各种适配 小黑条还不止iPhone X这一种机型,后续新出的还要不要改代码? 可以通过js来匹配机型,可以通过媒体查询来匹配机型,新的机型如何适配? 解决办法 解决标准 希望尽可能少的改动,不需要js里面设置,然后模板里面再判断,然后再加个对应的样式啥的,太麻烦 希望尽可能通用,不能加了iPhoneX,又加iPhone XI,又加iPhone XII,没完没了 希望可以适配未来的类似的机型 解决思路 找到这个小黑条的尺寸获取方式, 在iPhone上可以通过如下方式获取(兼容写法)[代码] height:50px; height: calc(50px + constant(safe-area-inset-bottom)); /* ios < 11.2*/ height: calc(50px + env(safe-area-inset-bottom)); /* ios >= 11.2*/ [代码] 在我们页面样式对应的地方加上这个尺寸,需要就加上,不需要就不加 加的方式可以根据情况来选择 直接加在页面底部,padding 如果是吸底元素,要加元素的padding,通过加height高的方式也行 如果元素浮空,需要加margin的方式来设置,或者加底边距bottom的方式 当然内部加margin也行,需要注意背景颜色是否一致 有些地方需要全量的,有些地方可能需要高度折半,请自行选择 如果碰到一些特殊的情况,上面方式处理不了的,比如听说某些安卓机(我目前还没遇到),还可以回归原始的处理办法 媒体查询 配合JS通过getSystemInfo直接处理 [代码]screenHeight - safeArea.bottom[代码] 即是底部小黑条影响的范围,也就是上面方法求得的值 小程序中的解决办法 直接使用上面的兼容写法的适配方案即可 当前(20201128)最新的微信开发者工具开发版本上可以直接验证了,h5部分目前还不支持,开发哥哥么已经在搞了 H5中的解决办法 在html模板里面加如下meta标签,如果存在就合并一下, [代码] <meta name="viewport" content="viewport-fit=cover"> [代码] 然后用上面的兼容写法处理 手边没有设备怎么办? 模拟器 XCode(XCode 14) New =>iOS=>App类型的Application 接下来 Interface 选StoryBoard, Language选Swift 创建工程完成 把[代码]ViewController.swift[代码]代码改成如下:[代码]import UIKit import WebKit class ViewController: UIViewController { var webView: WKWebView! override func loadView() { webView = WKWebView() view = webView } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. let url = URL(string: "http://127.0.0.1:8080/iphone-style-adapt.html")! webView.load(URLRequest(url: url)) webView.allowsBackForwardNavigationGestures = true } } [代码] 在[代码]Info.plist[代码]中加入如下内容并合并<dict>标签 ,也就是把中间两行加入到dict标签中,(通过文本编辑器打开,或者XCode中以源码方式打开都行) [代码] <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> [代码]
2021-01-26 - 推荐一个标准websocket接口的wrapper
自己写的一个小程序socket api包装到websocket的wrapper,欢迎大家一起维护,一起使用 有时候我们开发了一个websocket应用,想把它移植到微信小程序上,或是想使用依赖于websocket的库,然而小程序API提供的socket接口不是标准的websocket接口,无法在上述场景内直接应用。这个库以小程序socket API为基础实现了websocket的接口。 https://github.com/jhd124/wxmapp-web-socket https://www.npmjs.com/package/wxmapp-web-socket
2020-07-01 - 共享图书小程序3.0 全新UI 免费下载
[图片][图片][图片][图片][图片][图片][图片] 图书共享小程序-建始图书共享书 20201220新增功能: 1.电子书下载(年度豆瓣榜、亚马逊销售榜) 2.纸质书借阅(适合公司、学校、地区共享图书) 3.连载书阅读 4.媒体号推荐(收集一些达人视频号教学) 5.评星/留言 6.跟随系统调整风格 7.书籍多级分类筛选 8.书籍列表与瀑布流显示 9.分享到朋友圈/好友/群/生成海报 10.点赞/收藏 11.借阅帮助/借阅福利 12.语录增加酷炫音效播放(仿豆瓣FM特效) 13.搜索增加记录(仿QQ侧滑特效)
2020-12-27 - 云开发能否处理秒杀或者电商中超卖的问题?
- 需求的场景描述(希望解决的问题) 比如在秒杀的场景,怎么处理超卖的问题? 是否能给某次读写加锁,或者其他解决方案? - 希望提供的能力 云函数读写加锁或者其他能够保证读写过程中没有其他写入操作。
2019-01-28 - 网赚问题咨询
请问《滴滴出行》微信小程序这种活动是否属于网赚行为? [图片][图片]
2020-12-22 - 简单实现签到日历效果
wxml: <view class="box"> <view class="section"> <picker mode="date" value="{{date}}" fields="month" start="2010-01-01" end="{{cy+'-'+cm}}" bindchange="bindDateChange"> <view class="picker">{{cur_year || "--"}} 年 {{cur_month || "--"}} 月</view> </picker> </view> <view> <!-- 显示星期 --> <view class="week color9b"> <view wx:for="{{weeks_ch}}" wx:key="unique">{{item}}</view> </view> <view class='days'> <!-- 行 --> <view class="rows" wx:for="{{days.length/7}}" wx:for-index="i" wx:key="unique"> <!-- 列 --> <view class="columns" wx:for="{{7}}" wx:for-index="k" wx:key="unique"> <!-- 每个月份的空的单元格 --> <view class='cell' wx:if="{{days[7*i+k].date == null}}"> <text decode="{{true}}"> </text> </view> <!-- 每个月份的有数字的单元格 --> <view class='cell' wx:else> <!-- 当前日期已签到 --> <view wx:if="{{days[7*i+k].isSign == true}}" class='qianbg'> <text class="colorff">{{days[7*i+k].date}}</text> <text class="sourse">+{{days[7*i+k].Score}}</text> </view> <!-- 当前日期未签到 --> <view wx:else> <text>{{days[7*i+k].date}}</text> </view> </view> </view> </view> </view> </view> </view> 简单提下思路,首先默认确定当前年月,cy cm, 初始化:获取days遍历日历的格子,通过获取当前月第一天是星期几来判断前面有几个空格,放入days,再当月天数放入days,然后进行渲染,再通接口去拿签到信息,签到成功的突出显示。这里签到初始化时我默认给了标识isSign,将已签到列表和当前年月日进行比较,符合条件则更新签到状态。切换选择日期,这里我用的是选择器,当然可以写成点击左侧按钮上一月,右侧按钮下一月那种,重新选择日期后,initdata(e) 传入年月,就是当前选择年月的数据。将星期日作为第一日的我也备注上去了。样式根据自己的喜好改就行了,最后看看我写的两个项目效果: [图片][图片] 写了个demo:https://developers.weixin.qq.com/s/SSlwjGmb7Wm9
08-14 - 小程序使用webview嵌套H5中使用iframe真机无法跳转 wx.miniProgram.getEnv等失效问题分享
小程序使用webview嵌套H5真机无法跳转 wx.miniProgram.getEnv、wx.miniProgram.navigateTo等失效无响应问题分享 原因分析:由于页面使用了iframe导致在子页面中真机无法准确获取页面运行环境(开发者工具中无影响); 解决方案:在调用方法前加一个 parent. 在父页面中调用方法即可正常运行; 过程:尝试过切换jweixin代码版本但是问题依旧; 分享:遇到同类问题用这个方法解决了的小伙伴欢迎评论区留言点赞👍! <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script> <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> 核心代码: var ua = navigator.userAgent.toLowerCase(); if(ua.match(/MicroMessenger/i)=="micromessenger") { //由于页面是iframe 所以在调用wx.miniProgram.getEnv的时候直接调用会没反应,需要加一个parent父组件调用即可成功调起来。(开发者工具可以成功跳转但是真机没办法跳转1.3.2-1.6.0的js都试过问题依旧) parent.wx.miniProgram.getEnv((res)=>{ if (res.miniprogram) { // alert("在微信内,在小程序内") // console.log('在小程序内') // alert("在小程序内") parent.wx.miniProgram.navigateTo({ url: "/pages/minipay/minipay?outTradeNo="+outTradeNo+"&type=miniapp&code=" , //小程序的支付地址,queryParam是需要传递的商品id等数据 fail: function(err){ alert("跳转小程序失败!") }, }); return false; } else{ alert("在微信内,但是不在小程序内") console.log('在微信内,但是不在小程序内')//嵌入公众号 return false; } }) } else{ console.log('在微信外') if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) { console.log("移动") // 支付在同一页面打开 // return; } else { console.log("PC") // 支付在同一页面打开 // return; } return false; }
2020-12-21 - 在线答题小程序技术方案征集?
寻求一个解决方案: 目前答题活动有10 0000 人参与,满分100分,每个微信用户只参加一次答题,也就是目前集合有10 0000条记录,那么当一个用户进来的时候怎么获取他的排名? 排名规则:分数优先,分数相同的,按答题用时从短到长升序排列。 数据库选择:目前使用云开发,答题记录放在集合historys里面,也就是historys集合目前躺着10 0000条答题记录数据。 ~~~ 已知 1)当前用户_openid 2)10000 条答题记录 historys 求解 该用户的排名? ~~~ 只说下思路即可,不需要具体实现代码? 方案一经录用,打赏100, 方案可不唯一,也就是说最终方案可不止一个,视参与情况,最多会打赏最优的五个方案,如果有效方案多于10个,最少打赏二个方案,最终会在该贴公布具体打赏情况。 具体方案可评论区留言,同一技术方案,以提出时间最早有效。 具体数据结构如下所示 [图片] ~~~~ 该方案征集有效期为3天,截止时间为本周日晚上9点,即12月20号晚上9点 ~~ 由于社区私信不能发收款码图片,请评论第一,第二两位热心用户,在下面文章评论区留下收款码截图,或者直接微信联系我,我好安排后续的转账工作,非常感谢各位的参与 学习贯彻十九届五中全会知识竞赛答题活动小程序? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/00068ecced0278a1dd6b58f2051413 相关赏金已打赏两位同学 [图片] [图片] 再次感谢
2020-12-22 - 目前有哪些小程序案例,提供了合规的引导用户下载app的功能?
目前有哪些小程序案例,提供了引导用户下载app的功能? 当然不能诱导下载app,要在合规的前提下 想总结下,这些案例,有没有推荐我调研下的,小女子非常感谢
2020-12-17 - 优雅解决:关于app.js的onLaunch 与 页面的onLoad 的异步问题
// 常见的场景:打开小程序时要先获取用户数据,再调其他接口 // 步骤: // 1、获取openid // 2、根据openid获取用户数据 // 3、获取到用户数据后 再 调取其他接口 啥也别说,直接看代码吧: 实际开发会把很多步骤合并,我这展示就每一步详细说明 [图片] [图片] [图片] [图片] -------分割线--------------------- 以上为app.js页面------------ 页面index.js(打开小程序页面栈的第一个页面) [图片] 总结:原理就是跨页面调用而已。该方法也可以使用在扫码进入的场景。只需在目标页面加上接收数据的函数init即可。
2020-12-16 - 记一次字体渐变导致setData之后,视图没有更新的坑
今天测试小哥急忙忙的跑过来,问我接口返回正确,为什么视图没有更新?我信誓旦旦的说道:我写的代码怎么可能有问题!因为setData视图没有理由不更新呀?后来当我自己复现之后彻底傻眼了???what???的确setData了,模拟器和ios是没有问题的,为什么在安卓机视图没有更新??后面经过各种console,才发现setData之后数据的确更新了。后来经过种种排查,某一瞬间我把class名称去掉了?what?竟然可以了?好在class里只有一段改变文字渐变的代码?冷静下来想了想 有可能是微信渲染层报错了,并没有把报错信息抛出来! wxml <view bindtap='changeData'>点击更新数据</view> <text>{{value}}</text> css text{ font-size: 30px; background: linear-gradient(to right, red, blue); -webkit-background-clip: text; color: transparent; } js Page({ data:{ value:'' }, changeData(){ this.setData({ value:Math.floor(Math.random()*(1000 - 1) + 1); }) } })
2020-12-15 - 个人主体答题小程序,可以有语音讲解功能吗?
个人主体答题小程序,可以有语音讲解功能吗? ~~ 这个主要是面向初中生的刷题小程序,所以最首要的三个功能是试卷可以直接打印PDF版本、有习题讲解语音界面、还有错题本按月生成,并可以直接打印PDF版本。
2020-12-15 - 抽奖小程序抽奖算法的实现,附赠算法库
简易思路如下: 1、获取参与抽奖的人员列表,组合为数组 2、使用数组乱序算法,将抽奖列表大乱 3、根据一定规则取出打乱后的数组中的抽奖人员,这些就是中奖人员了 乱序算法思路具体如下,使用Fisher–Yates 洗牌算法: Fisher–Yates 算法由Fisher和Yates这两个人的发明的,一开始只是用来人工混排(真实荷官,现场发牌……)一组数字序列,原始算法的步骤非常容易理解。 写下从 1 到 N 的数字(一副扑克)取一个从 1 到剩下的数字(包括这个数字)的随机数 k——(从中间抽一沓,)从低位开始,得到第 k 个数字(这个数字还没有被取出),把它写在独立的一个列表的最前面一位重复第 2 步,直到所有的数字都被取出第 3 步写出的这个序列,现在就是原始数字的随机排列说通俗点就是: 一副扑克从中间随便取一沓(取的越少,越容易实现乱序,算法中只取一个)把取出的牌放在旁边,堆成一堆重复2、3步骤,直到手里没牌第三步开始另外堆起来的牌,就足够乱了算法库地址如下,需要自取: https://www.npmjs.com/package/lodash
2020-12-13 - 云开发,数据库如何保存number类型的数据?js已确认数据类型为int
[图片] 云开发,数据库如何保存number类型的数据?js已确认数据类型为int
2018-11-14 - 激励视频广告onClose多次回调问题解决办法
多次播放激励视频广告要先卸载之前的监听事件,否则回造成多次回调,广告offClose的Demo太简单,开发中浪费了时间,发出来让大家少走弯路 [代码] let videoAd = wx.createRewardedVideoAd({ adUnitId: "你的广告id", }); try{ if(videoAd.closeHandler){ videoAd.offClose(videoAd.closeHandler); console.log("---videoAd.offClose 卸载成功---"); } } catch (e) { console.log("---videoAd.offClose 卸载失败---"); console.error(e); } videoAd.closeHandler = function (res) { // 用户点击了【关闭广告】按钮 if (res && res.isEnded || res === undefined) { // 正常播放结束,可以下发奖励 console.log("播放完毕"); } else { //提前关闭小程序 } }; videoAd.onClose(videoAd.closeHandler); [代码]
2020-12-02 - 小程序代码审核加入同意授权与敏感信息收集提示,仍然审核不通过,为什么?
1、小程序在注册时,涉及到的用户敏感信息收集已明确告知用户该数据的用途,且经过同意和授权操作,为啥还是审核不通过? 开发者:娄振鹏 小程序"易考勤",提审时间2020-09-30 13:57:09 [图片][图片][图片][图片]
2020-10-09 - banner效果美化
先上图 [图片] wxss: .bannerSwiper { height:300rpx; width: 100vw; position: absolute; left: 0rpx; top: 10rpx; } .imageBanner { width: 100%; height: 100%; border-radius: 20rpx; position: relative; padding: 0; line-height: 100%; background: transparent; text-align: left; } .imageBanner_small { transform: scale(0.9); transition: 0.2s, ease-in 0s; border-radius: 10rpx; width: 100%; height: 100%; position: absolute; bottom: 0; z-index: 100; padding: 0; line-height: 100%; background: transparent; text-align: left; opacity: 0.5; } wxml: <view class="container"> <swiper class='bannerSwiper' previous-margin="80rpx" next-margin='80rpx' indicator-dots="true" indicator-color='#999' indicator-active-color='#fff' indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}" bindchange='onChange' circular='true'> <block wx:for="{{banner}}"> <swiper-item> <image class="{{index==selindex?'imageBanner':'imageBanner_small'}}" style="background:{{item}}"></image> </swiper-item> </block> </swiper> </view> github地址:https://github.com/jieqwer/-Banner
2020-11-27 - 小程序web-view跳转应用宝app下载页面ios无法点击跳转,安卓可以?
- 当前 Bug 的表现(可附上截图) web-view跳转应用宝app下载页面ios无法点击跳转,安卓可以 - 预期表现 web-view跳转应用宝app下载页面下载app - 复现路径 - 提供一个最简复现 Demo
2018-05-10 - 小程序自定义组件知多少
自定义组件 why 代码的复用 在起初小程序只支持 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 视图层渲染说明。 结束语 其实很多新框架新工具出来的时候,经常会让人眼前一亮,觉得哇好厉害,哇好高大上。 但其实更多时候,我们需要挖掘新事物的核心,其实大多数都是在原有的事物上增加了个新视角,从不一样的视角看,看到的就不一样了呢。作为一名码农,我们要看到不变的共性,变化的趋势。
2019-04-01 - 关于小程序的基础库
小程序基础库的组成 基础库成分 关于基础库的成分,不得不提到我们之前说过的小程序渲染机制,参考 React 的 Virtual DOM。 [图片] 基础库除了处理 VD 的渲染问题,它还包括内置组件和逻辑层API,总的来说负责处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑。 小程序的基础库是 JavaScript 编写的,它可以被注入到渲染层和逻辑层运行。在渲染层可以用各类组件组建界面的元素,在逻辑层可以用各类 API 来处理各种逻辑。 同时,小程序的一些补充能力:自定义组件和插件,也有相应的基础代码,当然也需要添加到基础库里。 所以我们可以看到,小程序的基础库主要包括: 提供 VD 渲染机制相关基础代码。(Exparser 框架) 提供封装后的内置组件。 提供逻辑层的 API。 提供其他补充能力(自定义组件和插件等)的基础代码。 [图片] Exparser 框架 Exparser 是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。 小程序内的所有组件,包括内置组件和自定义组件,都由 Exparser 组织管理。 Exparser 会维护整个页面的节点树相关信息,包括节点的属性、事件绑定等,相当于一个简化版的 Shadow DOM 实现。Exparser 的主要特点包括以下几点: 基于 Shadow DOM 模型:模型上与 WebComponents 的 ShadowDOM 高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他 API 以支持小程序组件编程。 可在纯 JS 环境中运行:这意味着逻辑层也具有一定的组件树组织能力。 高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。 基于这个框架,内置了一套组件,以涵盖小程序的基础功能,便于开发者快速搭建出任何界面。同时也提供了自定义组件的能力,开发者可以自行扩展更多的组件,以实现代码复用。 内置组件 小程序基于 Exparser 框架,内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件。 内置组件在小程序框架里的定义是:在小程序架构里无法实现或者实现不好某类功能,使用组件内置到小程序框架里。 常见包括: 开放类组件:如 open-data 组件提供展示群名称、用户信息等微信体系下的隐私信息,有 button 组件里 open-type 属性所提供分享、跳转 App 等敏感操作的能力 视图容器类组件:如 movable-view 这种因双线程模型导致手势识别不好实现的组件(在双线程模型中,触摸事件从渲染层发出,派发到逻辑层,这中间是有一定的延时而导致视图跟随手指运动这类交互变得有些卡顿) API 宿主环境提供了丰富的API,可以很方便调起微信提供的能力。 小程序提供的 API 按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口。 需要注意 API 调用大多都是异步的。 自定义组件 自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。 在使用自定义组件的小程序页面中,Exparser 将接管所有的自定义组件注册与实例化。以 Component 为例: 在小程序启动时,构造器会将开发者设置的 properties、data、methods 等定义段,写入 Exparser 的组件注册表中。 这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。 Page 构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。 在初始化页面时,Exparser 会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。 插件 插件是对一组 js 接口、自定义组件或页面的封装,用于嵌入到小程序中使用。 插件不能独立运行,必须嵌入在其他小程序中才能被用户使用;而第三方小程序在使用插件时,也无法看到插件的代码。因此,插件适合用来封装自己的功能或服务,提供给第三方小程序进行展示和使用。 插件开发者可以像开发小程序一样编写一个插件并上传代码,在插件发布之后,其他小程序方可调用。小程序平台会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。 小程序基础库机制 基础库的载入 在开发网页时,经常会引用很多开源的 JS 库,在使用到这些库所提供的 API 前,我们需要先在业务代码前边引入这些库。 同样道理,我们需要在启动 APP 之前载入基础库,接着再载入业务代码。由于小程序的渲染层和逻辑层是两个线程管理,而我们 一般说起基础库,也通常包括 WebView 基础库(渲染层),和 AppService 基础库(逻辑层)。 显然,所有小程序在微信客户端打开的时候,都需要注入相同的基础库。所以,小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。 将基础库内置在微信客户端,有两个好处: 降低业务小程序的代码包大小。 可以单独修复基础库中的Bug,无需修改到业务小程序的代码包。 小程序的启动 在小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页。 这里就包括了逻辑层和渲染层分别的初始化以及公共库的注入。 [图片] 在小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。 [图片] 基础库的更新 小程序的很多能力需要微信客户端来支撑,例如蓝牙、直播能力、微信运动等,可以说,小程序基础库的迭代离不开微信客户端的发布。 为了避免新版本的基础库给线上小程序带来未知的影响,微信客户端都是携带上一个稳定版的基础库发布的。等到微信客户端正式发布后,小程序会开始灰度推送新版本的基础库到微信客户端里,在这个过程需要仔细监控各类异常现象以及开发者和用户的反馈,一般灰度时长为12小时,灰度结束后,用户设备上就会有新版本的基础库。如果存在重大Bug,那此次推送会被回退。 参考 《小程序开发指南——小程序基础库的更新迭代》 《小程序开发指南——6.2 组件系统》 结束语 本节大致结合了小程序的启动来讲了下小程序的基础库。其实很多都能在小程序开发指南里找到,只是文字非常详(fan)细(duo),看一遍未必能记住,但是第二遍又找不到了。 哈哈哈吐槽下小程序的文档,很详细就是有点容易找不到。
2019-02-27 - 情侣券 v2.0 使用的 4 款开源组件
前言 之前写了《情侣券 v2.0 版本迭代思考》的产品思考接下来,我来分享下里面用的开源组件。 案例 1. 裁剪组件 使用场景 首页自定义图片 [图片] 自定义卡券背景 [图片] 自定义分享图 [图片] 使用流程 先在触发页面调用选择图片API [代码]wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success: function (res) { let tempPath = res.tempFilePaths[0]; wx.navigateTo({ url: '/pages/cropper/cropper?action=home&src=' + tempPath, }) } }) [代码] 选择完成之后把图片地址传递到截图页面 [代码]onLoad: function (options) { this.cropper = this.selectComponent("#image-cropper"); this.cropper.pushImg(options.src) } [代码] 截图后把截图地址回传到触发页面进行上传替换。 [代码]submit() { this.cropper.getImg((obj) => { app.globalData.imgSrc = obj.url; console.log('obj.url', obj.url) wx.navigateBack({ delta: -1 }) }); } [代码] 组件地址 https://github.com/wx-plugin/image-cropper 2. 日历组件 使用场景 我们的回忆模块 [图片] 使用时候直接把数据处理成组件需要的格式就好了。 风格支持 [图片] 组件地址 https://github.com/treadpit/wx_calendar 3. 海报组件 使用场景 记录完成之后的分享海报功能。 [图片] 海报json [代码]this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: '背景图地址', views: [ { type: 'image', url: 卡券背景图地址, css: { top: '393rpx', left: '40rpx', width: '670rpx', height: '240rpx', borderRadius: '16rpx' }, }, { type: 'text', text: 卡券标题, css: { top: '440rpx', left: '100rpx', align: 'left', fontSize: '40rpx', color: 标题文字颜色 } }, { type: 'text', text: 卡券介绍, css: { top: '520rpx', left: '100rpx', align: 'left', fontSize: '26rpx', color: 介绍文字颜色 } }, { type: 'image', url: 头像A地址, css: { top: '75rpx', left: '190rpx', width: '120rpx', height: '120rpx', borderWidth: '2rpx', borderColor: '#FAC0BE', borderRadius: '60rpx' } }, { type: 'image', url: '爱心图片地址', css: { top: '102rpx', left: '322rpx', width: '108rpx', height: '65rpx', } }, { type: 'image', url: 头像B地址, css: { top: '75rpx', left: '441rpx', width: '120rpx', height: '120rpx', borderWidth: '2rpx', borderColor: '#FAC0BE', borderRadius: '60rpx' } }, { type: 'text', text: 用户A昵称 +'与'+ 用户B昵称 +'的美好回忆', css: { top: '253rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '36rpx', color: '#2A2A2A' } }, { type: 'text', text: 用户A昵称 +'使用了'+ 用户B昵称 +'制作的'+卡券标题+',并留下记录。', css: { top: '317rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '26rpx', color: '#F58C98' } }, { type: 'image', url: 小程序码地址, css: { top: '674rpx', left: '283rpx', width: '184rpx', height: '184rpx' } }, { type: 'text', text: '长按识别小程序码 围观TA的甜蜜时刻', css: { top: '882rpx', left: '375rpx', width: '216rpx', align: 'center', fontSize: '24rpx', color: '#2A2A2A' } } ] } }) }, [代码] 详细介绍 《生成海报很复杂?有它轻松搞定!》 4. 导航组件 使用场景 每一个页面的导航。 详细介绍 《自定义导航栏开源库》 总结 好的轮子能够帮助我们剩下不少的时间。 后续我会分享更多实用组件的案例给到大家。
2020-11-06 - 【问题排查】小程序闪退
在使用小程序的时候,偶然会发生闪退。这里来讲一下闪退的问题该如何排查。 版本排查 发生闪退的时候,首先,要确认下 版本 是不是最新的。如果不是,建议更新版本再重试。旧版本的问题会在新版本进行修复哦。 微信版本: 微信官网 基础库版本:基础库更新日志小程序自查 确认版本都是最新情况下,还是有闪退的问题的话,建议先进行小程序自查~ 一般情况下,闪退是因为内存使用过多导致的,小程序侧可以通过基础库提供 wx.onMemoryWarning 接口来监听内存不足的告警,当收到告警时,通过回收一些不必要资源避免进一步加剧内存紧张。 反馈官方 如果问题还是会出现的话建议反馈给官方处理,需要附带上以下信息点协助排查(划重点:完整的提供信息才可以加速问题处理进度哦!!!) 示例: 系统及微信版本号:安卓7.0.17、IOS 7.0.17(出现问题的时候,建议两端都测试,给出有问题的case)必现 or 偶现:必现可复现场景:代码片段 或者 线上小程序复现步骤:进入首页,点击添加按钮等等,推荐录制复现的 视频(重点)进行上传。上传日志:提供微信号,复现时间点(操作步骤:手机微信那里上传下日志: 我 -> 设置 -> 帮助与反馈:右上角扳手 -> 上报日志,选择出现问题的日期,上传日志)
2020-11-03 - 使用微信的扫码功能实现网站和小程序端用户账号统一
前言 上一篇文章(使用小程序内的扫码功能实现网站和小程序端用户账号统一)发表后,社区大佬杨泉和拾忆分别给出了改进建议,总结两人的建议,就有了用户体验更好的方法,直接使用微信扫码来实现网站和小程序端用户账号统一。 先说一下前提,这次的方法都要用到扫普通链接二维码打开小程序来实现功能。在小程序后台完成相关配置后,直接用微信扫码就会打开小程序指定页面个并将网址直接通过参数[代码]q[代码]带给页面。 小程序扫码登录网站 在小程序后台配置中,将二维码规则设置为附带登录码的网址(比如:[代码]http://www.abc.com/?loginCode=[代码]),小程序功能页面建议设置为需要登录才能访问的页面,小程序中这个页面直接访问时需要判断是否登录,未登录状态要触发登录功能,登录后原样返回。 网页端业务逻辑不变,要登录时先生成一个唯一的登录码,比如:[代码]1234[代码],然后放在小程序后台配置的二维码规则网址中,比如:[代码]http://www.abc.com/?loginCode=1234[代码],然后把这个网址生成二维码展示。 用户使用微信扫码后会自动打开小程序对应页面,并将二维码对应网址通过参数[代码]q[代码]带给页面,在onLoad事件中提取[代码]q[代码]参数并提取出其中的登录码,提交到后端接口,后端接口查询该登录码绑定的用户返回后在小程序端完成登录。网页端在展示二维码后,开启一个轮询,定时访问后端接口查询该登录码的登录状态,在该登录码和小程序已登录用户绑定后完成网站上的用户登录。 [图片] 小程序扫码登录小程序 小程序后台配置和上一个情况一致,网页端流程略有不同。网页上用户登录后展示带登录码的网址对应的二维码,同时将当前登录用户和登录码绑定。微信扫码打开小程序后提取出登录码,提交到后端接口,后端接口查询该登录码绑定的用户返回后在小程序端完成登录。还可以配合wx.login绑定当前用户的openid,实现自动登录。 网页端展示二维码后使用轮询查看登录码的登录状态,到期或者已登录后在网页端销毁二维码。小程序端也需要对当前是否已登录做一个判断并做好切换用户的相关功能。 [图片] 总结 总结下来,这个方法就是以登录码为媒介,通过在某一端将登录码和登录用户绑定后,在另一端实现同一用户的登录,实际使用中还需要考虑登录码的有效期等。 以上方法流程不知道会有什么风险,欢迎大家的讨论。
2020-11-01 - 微信小程序内嵌webview相关知识点整理
前言 随着微信小程序的广泛应用,越来越多的商家选择将营销阵营选择迁移到了小程序中,但受其小程序体积限制的影响,不能够完全满足商户的要求,应运而生的web-view组件很好的解决的这一问题。一方面内嵌web-view能够直接运行在小程序中,大大减少了商户的开发成本,另一方面能够实现小程序与h5的跳转,有良好的扩展性,方便商户多端间引流。 一、web-view内嵌h5与传统小程序对比 通过查找相关资料发现,内嵌web-view和微信小程序在离线能力、页面切换体验、二次渲染,操作响应和开发灵活性上有如下对比,在不同场景下可能性能上有些许出入,仅供参考。 [图片] 表格来源:https://segmentfault.com/a/1190000017752299 从上表中可以对比出 H5 相较于小程序的优缺点,开发者可以根据实际需要选择。此外,使用web-view还有以下优点: (1)己方账号(第三方)与小程序openId/UnionId的关联绑定,实现免登陆 比如你是某门户网站,需要识别自己小程序上的用户与网站用户的关系,可以通过三种方法绑定关系,公众号,小程序原生,小程序web-view内嵌跳转三种方法。 (2)内嵌H5的富文本,减少重复开发 比如你是门户网站,社区,以往有大量的新闻和帖子,里面带了各种css样式的富文本,音视频自定义的点击事件等,小程序的原生组件可能无法兼容,这时候直接内嵌这些H5新闻,将会大大降低开发成本。 (3)热更新,减少发布审核 内嵌H5可以减少频繁提交小程序审核,商户只需要重新发布h5的版本,就可以更新内嵌web-view的内容。 <script type="text/javascript" src='//res.wx.qq.com/open/js/jweixin-1.4.0.js'></script 3.4 web-view和小程序间通信 官方给出了两种通信方法。 1、postMessage 通信 在 H5 中需要先用 wx.miniProgram.postMessage 接口,把需要分享的信息,推送给小程序。 在用户点击了小程序后退、组件销毁、分享这些特殊事件之后,小程序页面通过 bindmessage 绑定的函数读取 post 信息。 2、设置 web-view 组件的 URL 通信 H5 跳转小程序: // h5中跳转小程序 navigateToWeixin() { wx.miniProgram.navigateTo({url: '/pages/shop/index'}); } // 小程序跳转h5--第一步 <view> <web-view src="{{url}}" ></web-view> </view> // 小程序跳转h5--第二步:在小程序的 js 文件中设置通过 URL 以问号传参的方式传入参数到 H5中 if(!option.page){ this.setData({ url: `${this.data.url}?${test}` }); } else { this.setData({ url: `${this.data.url}${option.page}?${test}` }); } 四、内嵌web-view调试解决方案 首先通过微信开发者工具打开,这里url本地调试可以跟局域网链接,开发者工具可以实时显示,如果跟的是线上链接,可能只能通过预览扫码。 [图片] 右键点击页面,左上角会出现调试,可以在右图的调试器中对h5进行调试。 [图片] 五、踩过的小坑 (1)H5唤醒一些小程序API有一定的延时,0.3~1秒; (2)请调用小程序专用的JSSDK,公众号中的JSSDK不通用; (3)web-view一定是撑满全屏的,自定义顶部菜单,悬浮的都没用; (4)web-view后边跟的h5链接,必须要url转义,否则会和小程序自身的符号定义冲突,可能识别不到?后的参数; (5)H5控制小程序的跳转路径必须是“/”开头,如 “/pages/xxx/xxx”,且路径必须在app.json里有,地址错误的话,有时不报错。 (6)postMessage的json必须是data开始,不然接收不到数据。 (7)bindmessage相关: 小程序后退:使用接口名 wx.miniProgram.navigateTo,wx.miniProgram.navigateBack,wx.miniProgram.switchTab 等切换小程序页面/场景的API时候都会触发。分享:这个就是当你点分享小程序的时候,会接受到H5之前发送的postMessage。组件销毁,web-view组件销毁,类似 wx.miniProgram.redirectTo 都会触发。(8)内嵌h5跳转小程序是有严重缓存的,比如:h5页面有计时器,跳转小程序后计时器依然会计时,可以通过在url后面加时间戳的方式解决。 还有小程序已经关闭,H5自带的背景音乐仍然在手机后台播放,可以通过浏览器标签页被隐藏或显示的时候会触发visibilitychange(页面可见性状态)事件解决。 六、总结 看到这里,相信大家对于内嵌web-view已经有了大致的了解,我们主要对内嵌web-view与传统小程序进行对比,分析了内嵌web-view的优缺点,其次对如何配置内嵌web-view进行了阐述,然后对实战中内嵌web-view如何调试进行了示例,最后对实战中可能踩坑和注意的小点进行了友情提示。所以如果本篇文章有帮助到你的话,小编会非常欣慰,欢迎点赞和转发呦~
2020-06-16 - webview跳转小程序的另一种实现
起因 因为公司业务原因,小程序嵌套了大量的h5页面供; 但涉及到支付类的操作必须在小程序原生页面完成; 这就牵扯了webview跳转原生小程序问题; 我们在线上经常遇到用户投诉;在webview页面点去支付没有反应; 代码逻辑上,这个按钮点击应该跳转原生页面才对的; 我们也在相关页面添加了日志,显示已经触发jssdk成功跳转回掉 但是并没有跳转成功 就在前几天我们又接到用户关于跳转失败投诉; 这种收到反馈,搜集微信日志,联系官方的操作过于被动; 每次都要被公司质量管控部门吐槽,有苦难言; 社区也有不少开发者反馈相关问题;但是偶现bug,官方也不易排查 所以除了等待官方解决我们就没有别的办法了么? 突破 就在我辗转反侧,彻夜难眠的时候,官方文档的一句话吸引了我 [图片] 每次网页加载都能触发bindload事件获取到url 那么我们能不能指定一个url,获取url上的指定的参数,利用小程序原生能力进行跳转呢 实现 wxml 页面添加bindload事件监听 [代码]<web-view src="{{url}}" bindload="load"></web-view> [代码] js 监听url中的变化,检测到指定值执行跳转逻辑 [代码]load: function (e) { // 获取url const src = e.detail.src; const query = src.split('?')[1] || ''; // 检测url参数中是否有指定的参数 const isJump = query.indexOf('word=jump'); // 检测到指定值执行跳转逻辑 if(isJump >= 0){ wx.navigateTo({ url: '/index/index' }) } } [代码] demo 这里写了一个简单的demo https://developers.weixin.qq.com/s/HCPpEzmU7DkV 嵌入了触屏的baidu 当监听到搜索关键词为jump时,会执行原生的跳转 [图片] 兼容性的问题 因为公司小程序基础库是2.9.2,大于等于这个版本的可靠性是经过我们线上验证的 低于2.9.2的基础库版本有待考证,可能需要使用者自测 这里给大家提供一种思路,可以结合场景使用 安卓微信8.0.1,出现webview bindload没有执行的情况,这个api在新版本微信上可能有兼容性问题(20210322)
2021-08-13 - 微信小程序页面停留时间统计
近来在研究微信小程用户是否在使用小程序或者查看用户在小程序停留的时间,无意中在git上找到了相关的解决问题方法,希望正在开发这个功能的的你,能帮助你解决! [图片]但是好像有 收到一个需求,要统计一个用户在我们小程序的每个页面的停留时间。 看了下现成的API,除了这个好像也没有别的可以用:https://mp.weixin.qq.com/debug/wxadoc/dev/api/analysis-visit.html#访问趋势, 这个里面貌似有页面停留时间的数据, 参数说明ref_date时间,如:"20170306-20170312"session_cnt打开次数(自然周内汇总)visit_pv访问次数(自然周内汇总)visit_uv访问人数(自然周内去重)visit_uv_new新用户数(自然周内去重)stay_time_uv人均停留时长 (浮点型,单位:秒)stay_time_session次均停留时长 (浮点型,单位:秒)visit_depth平均访问深度 (浮点型) 但是好像有查询时间限制,只能查询一天的数据。毕竟小程序数据很大,估计也是怕数据量太大查询慢吧。 算了,自己写一个吧, 初步想法,在页面的[代码]onShow[代码]事件里面,打一个开始的时间戳,然后在[代码]onHide[代码]里面再弄一个时间戳,两个一减,然后把得出来的数据,一提交,齐活。 BUT~,尼玛,[代码]onShow[代码]和[代码]onHide[代码]不仅在页面切换的时候会触发,小程序切换到后台和回到前台,也会触发,这就有干扰了。 但是在[代码]app.js[代码]里面的[代码]onShow[代码]和[代码]onHide[代码]事件只在小程序前后台切换的时候才会触发,不会在页面切换的时候触发,利用这点,把前后台切换排除掉,只在页面切换的时候,上报页面停留时间就好了 在[代码]app.js[代码]里面,初始化以下三个状态, globalData: { firstIn:1, onShow: 0, onHide: 0 } [代码]onShow[代码]和[代码]onHide[代码]的值默认为[代码]0[代码],当小程序进入后台或者返回前台的时候,给这两个值变为[代码]1[代码],用来告诉页面,刚才的切换是前后台切换,不是页面切换,不用上报页面停留时间。代码如下: 依旧是在[代码]app.js[代码]里面 onShow(){ if(this.globalData.firstIn){ this.globalData.firstIn = 0; } else{ this.globalData.onShow = 1; } }, onHide(){ this.globalData.onHide = 1; } 里面的[代码]firstIn[代码]表示是不是第一次进入小程,因为第一次进入的时候也会触发[代码]onShow[代码](相当于从后台切换到前台了),要把这个也排除在外。默认是第一次进入,进入之后就把这个值置为[代码]0[代码] OK,[代码]app.js[代码]准备好了,然后看下具体页面的, 在页面里面,先声明两个变量,一个[代码]startTime[代码],一个[代码]endTime[代码]分别来存储用户进入页面的时间和离开的时间 var startTime, endTime, app = getApp(); Page({ onShow(){ setTimeout(function () { if (app.globalData.onShow) { app.globalData.onShow = 0; console.log("demo前后台切换之切到前台") } else { console.log("demo页面被切换显示") startTime = +new Date(); } }, 100) }, onHide(){ setTimeout(function () { if (app.globalData.onHide) { app.globalData.onHide = 0; console.log("还在当前页面活动") } else { endTime = +new Date(); console.log("demo页面停留时间:" + (endTime - startTime)) var stayTime = endTime - startTime; //这里获取到页面停留时间stayTime,然后了可以上报了 } }, 100) } }) 有几个页面要统计的,就把这几个页面都加一下。 嫌麻烦的话,可以修改一下[代码]Page[代码]方法,默认自带[代码]onShow[代码]和[代码]onHide[代码],然后如果外面有传入的话,可以合并。页面在使用的时候,直接用这个心的[代码]Page[代码],就不用每个页面都[代码]onHide[代码]、[代码]onShow[代码]了,这里就不上具体的代码了。 关于[代码]setTimeout[代码]的说明: 页面的[代码]onShow[代码]和[代码]onHide[代码]会在[代码]app.js[代码]的[代码]onShow[代码]和[代码]onHide[代码]之前执行,加个延迟,放到后面执行,这样每次都可以先检测是页面切换还是前后台切换,然后再去做对应的逻辑,不然就反了。 参考地址:https://github.com/ireeoome/reeoome/issues/3 作者:Ams
2020-12-01 - 关于小程序接入给赞赞赏功能(给赞小程序已于2020年5月5号暂停小程序服务)
给赞小程序已于2020年5月5号暂停小程序服务,本文章仅起纪念意义 - 关于小程序接入给赞赞赏功能 背景:由于个人小程序没有支付功能,无法让用户直接通过调用支付接口直接打赏,喵咪咪科技专门开发了一款打赏小程序:‘给赞’:它可以每个用户创建一个独立的打赏账户,还可以编辑各种打赏样式风格,废话不多说上图; 1. 觉得有用可以先打赏个啦!没事就算1分钱也是支持!(长按识别) [图片] 2. 首先微信搜索‘给赞’小程序完成授权注册(也可直接扫上面我的赞赏码直接进入后点击左下角:我也要收赞赏 即可),然后再关注‘给赞’公众号:回复‘路径’二字如图,系统会自动返回一个小程序带参跳转地址,将这个地址放在跳转路径里面即可; [图片] [图片] 3.前端页面代码如下图: app_id : wx18a2ac992306a5a4 路径:path=“pages/apps/largess/detail?id=cTq9h%2BdgXUU%3D” 你到时候自己填入自己的就行了,我比较懒就不打码了别填错了; [图片] 4.运行示意图(闪图有些粗糙): [图片] 5.体验程序: [代码]a.此生余时(生命话题)(长按识别) [代码] [图片] [代码] b.心疑(恋爱话题)(长按识别) [代码] [图片] 6. 到此收工,确实觉得有用可以打赏个啦!没事就算1分钱也是支持!(长按识别) [图片]
2020-08-16 - 微信标榜的支持原创,反倒是在纵容侵权和抄袭
[图片] 我们的小程序“你要去吃啥”被“今天吃啥呀”小程序在功能上和设计上抄袭,严重侵权。我们也积极的通过官方投诉渠道进行了投诉,但是结果“投诉审核不通过”。 [图片] 那么微信标榜的支持原创?现在这样是不是在纵容侵权和抄袭?
2018-09-14 - 小程序调用wx.chooseImage程序奔溃的问题
就在昨天把老板提的需求写好之后,上传审核提交发布。然后就出现了问题。在用户需要上传图片的时候,只要选择拍照程序就会重新启动。我当场就蒙逼了。小程序的bug还真是数不胜数啊。 下面贴上部分代码: wx.chooseImage({ count, sizeType: ['original', 'compressed'], sourceType: ['camera', 'album'] 调试的时候,同事的手机使用了之后直接重新启动,或者卡住不动弹。然而另外两位同事却没啥问题,于是在两台小米8都不行,一台Mix2和Mi10都没问题的情况下,安卓开发组的同事下了判断,有可能是内存不足。调用相机是很占系统内存的,导致了小程序奔溃。 在我反复的看文档都找不到解决问题的办法时,我在论坛上看看有啥解决问题的方法。却发现18年就有帖子说出现这种情况了。但是官方没给出没有解决问题的方法。于是我灰头土脸的回到百度了起来。却没有发现一个有用的。都是教导怎么使用wx.chooseImage的demo。 看着代码我突然发现sourceType同时调用两种类型album+camera会占用较大内存,那么单一调用是不是会好一点。虽然这不是解决问题的方法。但是应该可以让更多的手机兼容一下小程序。 于是我就写了个弹窗 [图片] const sourceType = Number(type) === 1 ? ['camera'] : ['album'] wx.chooseImage({ count, sizeType: ['original', 'compressed'], sourceType, // camera album success (res) { // tempFilePath可以作为img标签的src属性显示图片 const tempFilePaths = res.tempFilePaths 不得不说这效果还是可以的。至少我的小米8兼容了此项做法,皆大欢喜。老板现在要是问起来,接下来的锅让腾讯背上就好。(~ ̄▽ ̄)~
2020-10-15 - 离职了,要如何才能解绑公众号管理员?
之前在一家公司上班,注册了微信公众号(包含一个订阅号和一个服务号)主体名称:“吉美智康”;后来离职了,公众号一直没有接触绑定,现在想注册公众号也注册不了;给腾讯官方打5次电话,一直没人接听;腾讯客服用机器人回复,始终解决不了问题;是不是公司做大了,一个普通的用户的问题对腾讯来说已经不再重视了?
2020-10-14 - 请教关于小程序全屏背景图的尺寸问题?
请教关于小程序全屏背景图的尺寸问题? [图片] 大家都知道不同手机有不同的尺寸,那么在小程序全屏背景图设计时,大家一般是如何处理这个问题的? 或者大家有什么经验? https://developers.weixin.qq.com/miniprogram/dev/component/image.html 比如下面小程序的启动页背景图,现在设计师问我,启动页背景图片尺寸是多少 [图片] 参考帖子 如何图片要填充全屏的话,背景图片的尺寸应该设置为多少?? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/000684e6a3c4e803c7e94448a56000 getSystemInfo里面三个高度问题请教?? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/0000ec1a4dcbf863abfab2d1f51800 .page-lead z-index 100 height 100% position absolute left 0 right 0 top 0 bottom 0 background #fff url('../../../../images/bg-lead.jpg') center bottom background-size 100% auto z-index 100 所以图片比例为750x1624,保证在iPhone X系列上没问题
2020-10-07 - 内容违规你倒告诉我哪违规了?
[图片]就一个内容违规无法查看,我学校活动信息哪里违规你倒是指出来呀,一堆条文我知道哪个字涉及敏感字了?
2020-09-30 - 业务域名只校验一次是否就可以把校验文件删了,删掉后会不会再次触发校验
业务域名的校验通过后是否可以删除? 删除后在什么情况下会触发再次校验?
2019-07-17 - #小程序云开发挑战赛#-心灵鸡汤大全-小斌
应用场景 在这个压力巨大的社会,每个人都有自己的负面情绪、迷茫以及不知所措,因此,拯救心灵的 心灵鸡汤小程序 诞生啦~ 功能 点赞、收藏、评论、搜索、通告、生成海报、后台管理、定时爬取数据 效果截图 首页(tab 、搜索、分页) [图片] 详情页(收藏、点赞、评论、生成海报、复制文案、保存图片) [图片] 海报 [图片] 个人中心 [图片] 后台管理 [图片] 可用订阅消息数量 [图片] 公告管理 [图片] 评论管理 [图片] 收藏点赞 [图片] 关于 [图片] 代码链接https://gitee.com/wuxuebin/chicken-soup 扫码体验[图片] 视频地址 https://www.iqiyi.com/v_247r8nfgogo.html 团队简介 个人开发
2020-09-22 - #小程序云开发挑战赛#-流浪猫速查手册-猫
项目背景 今年5月中下旬,作者在网上发现了一款非常有爱的小程序,叫做《燕园猫速查手册》,因为作者本身也参与流浪猫的救助工作,流浪猫的管理主要是靠记忆,非常不方便管理,于是用业余时间参考《燕园猫速查手册》开发了《流浪猫速查手册》小程序,主要针对的是社会上的流浪猫信息的管理。后续也会开放 [代码]领养[代码] 等模块,致力于倡导大家关爱流浪猫,领养代替购买。 6月1日上线了 [代码]1.0.0[代码] 版本,小程序中只有查看功能,配套也开发了一个 [代码]PC[代码] 管理端,上线后发现志愿者和救助站等人员不习惯使用 [代码]PC[代码] 端操作,也发现了很多细节问题,如流浪猫的隐私保护等。 8月中旬本作者和《燕园猫速查手册》的作者 circle 在微信中聊了聊,重新梳理的整个小程序的功能模块,也趁此机会报名参加了 [代码]云开发挑战赛[代码],重构了 [代码]1.0.0[代码] 版本的代码,并迭代了 [代码]2.0.0[代码] 版本。 应用场景 为了方便管理流浪猫的信息,把流浪猫的信息分享给喜爱猫咪的小伙伴,流浪猫信息的上报等。 目标用户 所有喜欢猫的用户 功能模块 [图片] 实现思路 本项目前端采用 uni-app 框架开发,后端完全采用 微信小程序云开发 实现 业务流程图 [图片] 功能演示 腾讯视频:https://v.qq.com/x/page/w3153x67dxh.html 效果截图 [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] 代码链接 GitHub: https://github.com/zhiiee/cats 开源许可 《流浪猫速查手册》的源代码基于 [代码]GPL-3.0[代码] 协议全网开源,可用于商业用途,如果您使用了《流浪猫速查手册》的源代码,那么您的项目必须遵守 [代码]GPL-3.0[代码] 协议进行全网开源,点击 LICENSE 查看许可协议。 体验二维码 线上版本 最新版本还在审核中 可以扫描下面的体验版本申请访问 [图片] 体验版本 同步最新的开发进度(生产环境) [图片] 团队/作者简介 Stephen: 一个热爱宠物,兼职创业,想获得一次天使轮投资,在宠物行业闯出一片天的程序员。
2020-09-20 - 个人项目学习笔记
前言:看完了比赛项目,感觉像是经历了一场头脑风暴,项目的起名、涉足行业、内容、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 地热监测-流量计设备性能测试平台 (四)官方公布的复赛名单 校园赛道: [图片] 职业赛道: [图片] (五)官方公布的决赛名单 校园赛道: [图片] 职业赛道: [图片] (六)最终决赛成绩 校园赛道: [图片] 职业赛道: [图片]
2020-11-14 - 小程序周刊#1 | 2019-08-05
你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。 新闻 行业相关的新闻、趣事、看法 小程序赋能第三方服务商,提供加急审核:微信团队针对第三方服务商提供了单独的预检加速机制。为优质小程序服务商赋能,帮助服务商更好的进行小程序的审核。 小程序发布用户账户登陆规范调整和优化建议:微信团队针对小程序中出现的账户登录功能给出了官方的指导意见,帮助开发者开发小程序时,合理利用用户登陆功能,优化用户体验。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。 小程序新功能 小程序发布的新功能 新能力解读:小程序后台持续定位能力 @bestony:这篇文章介绍了 7 月 30 日发布的小程序后台持续定位能力的使用说明和介绍。 新能力解读:小程序页面间通信介绍 @bestony:这篇文章介绍了 7 月 2 日发布的小程序页面间通讯接口的使用说明和介绍。 新手推荐 适合新手的文章、教程 小程序云开发·学习路线 V1.0.0 @bestony: 这篇文章介绍了一些适合新手学习小程序云开发的视频课程。 文章 写的不错的技术博客,包含但不局限于小程序、设计、产品等 😺小程序性能优化的几点实践技巧 @bestony: 这篇文章分享了一下常见的几个小程序的优化点,包括了 存在 setData 中的数据过大 存在短时间内发起太多图片请求 存在图片太大而显示区域过小 列表渲染的问题 对于绝大多数开发者来说,仔细审阅你的代码,大多会遇到这个问题。所以你可以好好审查一下自己的代码,解决这几个问题,就可以提升小程序的速度。 😺小程序数据通信方法大全 @bestony: 这篇文章主要介绍的是小程序中的几种数据通信的模式,包括了页面与组件、组件与组件、页面与页面等。 在读这篇文章的时候需要注意的是,小程序现已经支持了页面之间之间传递数据。 😺微信小程序Video组件实践总结 - 掘金 @bestony: 这篇文章分享了如何基于 Cover-View 及 Video 组件进行页面布局、Video 组件的实现诸如”视频 WiFi 下自动播放“、”视频静音播放“、”视频列表“、”视频不在可视范围,停止播放“等功能的实现。 在我们使用 Video 组件制作小程序时,这些功能都是我们会用到的,你可以看一看这篇文章,或者收藏文章,后续需要的时候直接参考代码。 工具 开发过程中常用的工具,及一些新工具的介绍 🧐Remax.js:使用 React.js 开发小程序 @bestony:Remax.js 是一个新的小程序开发框架,支持使用 React 的 原生语法如 React Hooks 来编写小程序。 该组件库支持微信小程序、支付宝小程序等平台、集成了Umi 、React Developer Tools等工具。如果你是一个 React 开发者,又对小程序的编写有兴趣,那么这个仓库值得你去看。 👍 westore: 微信小程序的状态管理解决方案 @bestony: westore 是腾讯官方提供的状态管理解决方案,借助 westore ,你可以在原生的小程序代码中实现多页之间数据的同步。 对于简单的应用来说,你可以使用页面间跳转、App 实例的 globalData 数据来做页面间的同步,不过对于复杂的应用,显然这样是不够的,因此,你可以考虑使用 wstore 来完成更加复杂的状态管理。 蚂蚁搬家工具:一键实现微信小程序项目到支付宝小程序的迁徙,不再为重复开发而烦恼。 @bestony: 除了微信小程序,目前做的比较好的是支付宝的小程序,如果你对于开发支付宝小程序没有兴趣,同时你的应用对于性能、样式等的要求不那么高,你可以考虑使用这个工具来完成将微信小程序转换为支付宝小程序。 微信小程序开发助手 for VSCode @bestony: VSCode 是我自己用的比较多的编辑器,但是在开发小程序时,除了官方的 IDE 以外,其他的编辑器很少有对小程序支持的比较好的,这个工具,可以帮助你更好的使用 VSCode 开发小程序。这样你可以使用 VSCode 来写代码,然后使用小程序官方的 IDE 来完成预览。 hanzi-writer-miniprogram: Wechat Miniprogram plugin for Hanzi Writer (微信小程序组件) @bestony: 这是一个用来演示汉字笔顺的小程序组件,使用它,你可以在你自己的小程序中加入汉字笔顺的功能,对于一些教育型的小程序来说,这样的组件可以大大的节省时间,提升工作效率。 miniprogram-network @bestony: 这是一个小程序网络库,相比于小程序原生的网络请求,提供了完整代码自动完成 和 类型检查,支持Promise、自动重试、缓存、取消、自定义超时、全局拦截、和事件监听等…… 代码 库,代码段,开源小程序 WeChat-MiniProgram-WebAR:A WeChat MiniProgram Web AR with JavaScript Computer Vision Libraries. @bestony: 在 7 月 5 号,小程序官方发布 Web AR 的小程序 API,这个项目使用了其 API,完成了在小程序端的颜色识别、人脸识别等功能。 关注我们 我们开通了公众号,每期发布时公众号(程序百晓生)会推送消息,欢迎关注。 [图片] 同时也支持了 RSS 订阅:https://www.chengxuzhoukan.com/atom.xml 。 你可以在电脑访问我们的官网,查看最新的周刊:https://www.chengxuzhoukan.com 说明 🚧 表示需翻墙,🌟 表示编辑推荐 预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)
2019-08-05 - 小程序外嵌h5,可否做邀请返现机制?
现在遇到一个需求,在小鹅通里面嵌入一个上课报名页面,然后要加多一个邀请好友买课,就给邀请人返现金红包,不过这个上课报名的页面本来就是外部h5写进去的,不归属于小程序的主账号,如果要做的话,还需要做钱包-->绑卡-->实名制,不知道这种操作是否合规?
2020-09-15 - 为什么社交红包用了几天微信支付就被封了呢?
如题,请问下是怎么回事,麻烦官方人员回答一下呢。社交红包小程序依然在线的,支付更换了两三次都接连被封,是怎么回事呢?按理说社交红包本身没有违规,就不存在支付被封的情况吧。
2020-09-10 - 滑块拖动排序 【恋爱小清单开发总结】
分享一下滑块拖动排序的案例,其实主要的关键点在于判断当前长按的坐标点跟滑块中心的位置关系,这次直接分享代码片段了。有兴趣的有小伙伴可以打开瞧一瞧。 https://developers.weixin.qq.com/s/qKcgIVmL7Qj7 也可以打开恋爱小清单小程序 -> 我-> 纪念日 页面效果图: [图片]
2021-09-09 - 小程序性能优化
小程序性能优化[图片]启动加载优化在小程序启动时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。 初始化小程序环境是微信环境做的工作,我们只需要控制代码包大小,和通过一些相关的缓存策略控制,和资源控制,逻辑控制,分包加载控制来进行启动加载优化。 勾选开发者工具中, 上传时压缩代码(若采用wepy高级版本,自带压缩,请按官网文档采取点击)精简代码,去掉不必要的WXML结构和未使用的WXSS定义。减少在代码包中直接嵌入的资源文件。(比如全国地区库,微信有自带的,在没必要的时候,勿自用自己的库)及时清理无用的资源(js文件、图片、demo页面等)压缩图片,使用适当的图片格式,减少本地图片数量等如果小程序比较复杂,优化后的代码总量可能仍然比较大,此时可以采用分包加载的方式进行优化,分包加载初始化时只加载首评相关、高频访问的资源,其他的按需加载。提前做异步请求,页面最好在onLoad时异步请求数据,不要在onReady时请求启用缓存数据策略,请求时先展示缓存内容,让页面尽快展示,请求到最新数据之后再刷新避免白屏,使用骨架屏等数据通信优化为了提升数据更新的性能,开发者在执行setData调用时,最好遵循以下原则: 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用;数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据;与界面渲染无关的数据最好不要设置在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中用到,而且它很长…………………………' } } }) 事件通信优化视图层会接受用户事件,如点击事件、触摸事件等。当一个用户事件被触发且有相关的事件监听器需要被触发时,视图层会将信息反馈给逻辑层。这个反馈是异步的,会产生延迟,降低延迟的方法有两个: 去掉不必要的事件绑定(WXML中的bind和catch),从而减少通信的数据量和次数;事件绑定时需要传输target和currentTarget的dataset,因而不要在节点的data前缀属性中放置过大的数据。渲染优化页面方法onPageScroll使用, 每次页面滚动都会触发,避免在里面写过于复杂的逻辑 ,特别是一些执行重渲染页面的逻辑在进行视图重渲染的时候,会进行当前节点树与新节点树的比较,去掉不必要设置的数据、减少setData的数据量也有助于提升这一个步骤的性能。
2020-08-20 - 从小程序内进入客服消息,点击聊天记录里的卡片消息,页面无法正常跳转
1、从小程序内进入客服消息,点击聊天记录里的卡片消息,页面无法正常跳转。 2、从微信首页的“小程序客服消息”进入,点击聊天记录里的卡片消息,却能正常跳转。 bug录屏如下:http://m.v.qq.com/play/play.html?vid=i09583qxghq&url_from=share&second_share=0&share_from=copy
2020-04-29 - 使用分页和空白view支撑解决Dom limit exceeded 问题
列表页数据量过大页面渲染节点过多会触发Dom limit exceeded . 解决方案如下。 欢迎讨论交流 [代码]// index.js[代码] [代码]Page({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]currentPage: 0,[代码][代码] [代码][代码]pageFrame:[][代码][代码] [代码][代码]},[代码][代码] [代码][代码]pageSize: 10,[代码][代码] [代码][代码]currentPage:0,[代码][代码] [代码][代码]onLoad: [代码][代码]function[代码] [代码]() {[代码] [代码] //mock 一些数据[代码] [代码] [代码][代码]var[代码] [代码]list = [];[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i = 0; i< 10000 ; i++) {[代码][代码] [代码][代码]list.push({[代码][代码] [代码][代码]name: [代码][代码]'ssss----'[代码] [代码]+ i,[代码][代码] [代码][代码]desc: [代码][代码]'aaaaa----'[代码] [代码]+ i,[代码][代码] [代码][代码]content: [代码][代码]'adasdadasdadadasdadadadadasdadad ----'[代码] [代码]+ i[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码] [代码] //数据分页[代码] [代码] [代码][代码]this[代码][代码].pagedList = [代码][代码]this[代码][代码].pageList(list);[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]listLength: list.length,[代码][代码] [代码][代码]list: [[代码][代码]this[代码][代码].pagedList[0]][代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码] [代码] //滚动监听[代码] [代码] [代码][代码]onListScroll(e) {[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].inPageUpdate) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]{ scrollTop } = e.detail;[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].currentPage > 0) {[代码][代码] [代码][代码]var[代码] [代码]pageFrame = [代码][代码]this[代码][代码].data.pageFrame[[代码][代码]this[代码][代码].currentPage - 1];[代码][代码] [代码][代码]var[代码] [代码]screenHeight = wx.getSystemInfoSync().screenHeight;[代码][代码] [代码][代码]if[代码] [代码]((scrollTop + screenHeight) - (pageFrame.lastBottom + pageFrame.height) < -200) {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]this[代码][代码].currentPage -= 1;[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]currentPage: [代码][代码]this[代码][代码].currentPage,[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]currentPageFrame = [代码][代码]this[代码][代码].data.pageFrame[[代码][代码]this[代码][代码].currentPage];[代码][代码] [代码][代码]if[代码] [代码](currentPageFrame) {[代码][代码] [代码][代码]if[代码] [代码](scrollTop - (currentPageFrame.lastBottom + currentPageFrame.height) > 200) {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]this[代码][代码].currentPage += 1;[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]currentPage: [代码][代码]this[代码][代码].currentPage,[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]},[代码] [代码] //计算分页高度[代码] [代码] [代码][代码]reachPageBottom() {[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].inPageUpdate) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].currentPage < [代码][代码]this[代码][代码].pagedList.length - 1) {[代码][代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]currentPage = [代码][代码]this[代码][代码].currentPage;[代码][代码] [代码][代码]wx.createSelectorQuery().select([代码][代码]'#listpage-'[代码] [代码]+ [代码][代码]this[代码][代码].currentPage).boundingClientRect([代码][代码]function[代码] [代码](rect) {[代码][代码] [代码][代码]if[代码] [代码](currentPage > 0) {[代码][代码] [代码][代码]rect.lastBottom = self.data.pageFrame[currentPage - 1].height + self.data.pageFrame[currentPage - 1].lastBottom[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]rect.lastBottom = 0;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]self.setData({[代码][代码] [代码][代码][`pageFrame[${currentPage}]`]: rect[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}).exec();[代码] [代码] [代码][代码]this[代码][代码].currentPage = [代码][代码]this[代码][代码].currentPage + 1;[代码][代码] [代码][代码]var[代码] [代码]nextPage = [代码][代码]this[代码][代码].pagedList[[代码][代码]this[代码][代码].currentPage];[代码][代码] [代码][代码]var[代码] [代码]key = `list[${[代码][代码]this[代码][代码].currentPage}]`[代码][代码] [代码][代码]var[代码] [代码]data = {};[代码][代码] [代码][代码]data[key] = nextPage;[代码][代码] [代码][代码]data.currentPage = [代码][代码]this[代码][代码].currentPage;[代码][代码] [代码][代码]console.log(data);[代码][代码] [代码][代码]this[代码][代码].setData(data, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]});[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]pageEnd: [代码][代码]true[代码][代码],[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码][代码] [代码][代码]listItemTap(e) {[代码] [代码] //响应点击事件[代码] [代码] [代码] [代码] [代码][代码]console.log(e.currentTarget.dataset);[代码][代码] [代码][代码]},[代码] [代码] //分页函数[代码] [代码] [代码][代码]pageList(list) {[代码][代码] [代码][代码]var[代码] [代码]splitArray = (arr, len) => {[代码][代码] [代码][代码]var[代码] [代码]a_len = arr.length;[代码][代码] [代码][代码]var[代码] [代码]result = [];[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i = 0; i < a_len; i += len) {[代码][代码] [代码][代码]result.push(arr.slice(i, i + len));[代码][代码] [代码][代码]}[代码][代码] [代码][代码]return[代码] [代码]result;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]pagedList = splitArray(list, [代码][代码]this[代码][代码].pageSize);[代码][代码] [代码][代码]return[代码] [代码]pagedList;[代码][代码] [代码][代码]}[代码] [代码]})[代码] [代码]/*index.wxss*/[代码][代码]page {[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码]}[代码] [代码].list {[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码]}[代码] [代码].listitem {[代码][代码] [代码][代码]line-height[代码][代码]: [代码][代码]50[代码][代码]rpx;[代码][代码] [代码][代码]padding[代码][代码]: [代码][代码]20[代码][代码]rpx;[代码][代码] [代码][代码]font-size[代码][代码]: [代码][代码]32[代码][代码]rpx;[代码][代码] [代码][代码]color[代码][代码]: [代码][代码]#ff0000[代码][代码];[代码][代码] [代码][代码]border-bottom[代码][代码]: [代码][代码]1[代码][代码]rpx [代码][代码]solid[代码] [代码]#eeeeee[代码][代码];[代码][代码]}[代码] [代码].list-end{[代码][代码] [代码][代码]text-align[代码][代码]: [代码][代码]center[代码][代码];[代码][代码] [代码][代码]font-size[代码][代码]: [代码][代码]18[代码][代码]rpx;[代码][代码] [代码][代码]color[代码][代码]: [代码][代码]#b2b2b2[代码][代码];[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]15[代码][代码]rpx [代码][代码]0[代码] [代码]7.5[代码][代码]rpx;[代码][代码] [代码][代码]display[代码][代码]: flex;[代码][代码] [代码][代码]flex-[代码][代码]direction[代码][代码]: row;[代码][代码] [代码][代码]justify-[代码][代码]content[代码][代码]: [代码][代码]center[代码][代码];[代码][代码] [代码][代码]align-items: [代码][代码]center[代码][代码];[代码][代码]}[代码] [代码].list-end .line {[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]20%[代码][代码];[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]1px[代码][代码];[代码][代码] [代码][代码]background-color[代码][代码]: [代码][代码]#dddddd[代码][代码];[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]0[代码] [代码]30[代码][代码]rpx;[代码][代码]}[代码] [代码]<!-- index.wxml -->[代码][代码] [代码][代码]<[代码][代码]scroll-view[代码] [代码]class[代码][代码]=[代码][代码]"list"[代码] [代码]style[代码][代码]=[代码][代码]''[代码] [代码]scroll-y[代码][代码]=[代码][代码]"true"[代码] [代码]bindscrolltolower[代码][代码]=[代码][代码]"reachPageBottom"[代码] [代码]bindscrolltoupper[代码][代码]=[代码][代码]"reachPageTop"[代码] [代码]bindscroll[代码][代码]=[代码][代码]"onListScroll"[代码] [代码]enable-back-to-top[代码][代码]=[代码][代码]"true"[代码] [代码]upper-threshold[代码][代码]=[代码][代码]'100'[代码] [代码]lower-threshold[代码][代码]=[代码][代码]"100"[代码] [代码]scroll-top[代码][代码]=[代码][代码]"{{scrollTo}}"[代码][代码]>[代码] [代码] <!-- 循环分页 -->[代码] [代码] [代码][代码]<[代码][代码]view[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{list}}"[代码] [代码]wx:for-item[代码][代码]=[代码][代码]"subList"[代码] [代码]wx:for-index[代码][代码]=[代码][代码]"page"[代码] [代码]id[代码][代码]=[代码][代码]"listpage-{{page}}"[代码] [代码]wx:key[代码][代码]=[代码][代码]"listpage"[代码] [代码]>[代码] [代码] [代码] [代码] <!-- 判断页面状态 -->[代码] [代码] [代码][代码]<[代码][代码]block[代码] [代码]wx:if="{{page - currentPage >= -1 && page - currentPage <= 1}}" >[代码] [代码] <!-- item渲染 -->[代码] [代码] [代码][代码]<[代码][代码]template[代码] [代码]is[代码][代码]=[代码][代码]"listitem"[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{subList}}"[代码] [代码]data[代码][代码]=[代码][代码]"{{item:item, page,index}}"[代码] [代码]/>[代码][代码] [代码][代码]</[代码][代码]block[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]wx:else [代码][代码]style[代码][代码]=[代码][代码]'height:{{pageFrame[page].height}}px;'[代码] [代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"list-end"[代码] [代码]wx:if="{{listLength > 0}}">[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'line'[代码][代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码] [代码] [代码][代码]<[代码][代码]text[代码] [代码]wx:if[代码][代码]=[代码][代码]"{{pageEnd}}"[代码][代码]>以上共{{listLength || 0}}个数据</[代码][代码]text[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]button[代码] [代码]wx:else [代码][代码]loading[代码][代码]=[代码][代码]"true"[代码][代码] [代码][代码]disabled[代码][代码]=[代码][代码]"true"[代码] [代码]bindtap[代码][代码]=[代码][代码]"empty"[代码] [代码]class[代码][代码]=[代码][代码]"button-noborder"[代码] [代码]style[代码][代码]=[代码][代码]'font-size: 26rpx; background-color:transparent'[代码] [代码]>正在加载更多...</[代码][代码]button[代码][代码]> [代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'line'[代码][代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]scroll-view[代码][代码]>[代码] [代码] [代码][代码]<[代码][代码]template[代码] [代码]name[代码][代码]=[代码][代码]"listitem"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'listitem'[代码] [代码]bindtap[代码][代码]=[代码][代码]'listItemTap'[代码] [代码]data-item[代码][代码]=[代码][代码]'{{item}}'[代码] [代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.name}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.desc}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.content}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<!-- 循环100个节点 -->[代码][代码] [代码][代码]<[代码][代码]text[代码][代码]>{{page}}--</[代码][代码]text[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]text[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{100}}"[代码] [代码]wx:for-index[代码][代码]=[代码][代码]'i'[代码] [代码]>{{index}}</[代码][代码]text[代码][代码]>[代码] [代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]template[代码][代码]>[代码] demo见代码片段 [代码]https:[代码][代码]//developers.weixin.qq.com/s/ItRHXsmg7L5r[代码]使用小程序开发工具导入片段可直接运行
2019-01-16 - 一招解决如何在小程序里,通过useExtendedLib方式引入weui
1.在app.json里新增 “useExtendedLib”: { “weui”: true } 2.在使用的页面json文件应用组件,比如在index.json里 "usingComponents": { "mp-navigation-bar": "weui-miniprogram/navigation-bar/navigation-bar", "mp-icon": "weui-miniprogram/icon/icon" } 3.可以参考此代码片段 https://developers.weixin.qq.com/s/mjAXwumk7gjD
2020-08-10 - 小程序云开发中上传多图时,使用时间戳为文件命名时会有重复命名,怎么解决这个问题?
doUpload(filePath) { const that = this; var timestamp = (new Date()).valueOf(); const cloudPath = timestamp + '.png'; wx.cloud.uploadFile({ cloudPath, filePath }).then(res => { console.log('[上传文件] 成功:', res) const { params } = that.data; const { imgUrl } = params; imgUrl.push(res.fileID); params['imgUrl'] = imgUrl; that.setData({ imgUrl, }); }).catch(error => { console.error('[上传文件] 失败:', error); wx.showToast({ icon: 'none', title: '上传失败', duration: 1000 }) }) }, chooseImage: function () { const that = this; // 选择图片 wx.chooseImage({ count: 5, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: function (res) { const filePath = res.tempFilePaths; //将选择的图片上传 filePath.forEach((path, _index) => { that.doUpload(path); }); const { tempFilePaths } = that.data; that.setData({ tempFilePaths: tempFilePaths.concat(filePath) }, () => { console.log(that.data.tempFilePaths) }) }, fail: e => { console.error(e) } }) },
2020-07-29 - 像美团这样的分享是怎样实现的呢?
[图片] 点击立即邀请,就能直接调起分享的聊天列表了
2020-07-28 - 实现小程序分享朋友圈功能
一大早在各科技公众号有看到小程序支持直接分享到朋友圈,这么好的功能,忍不住要实践下。 根据官方文档走起,文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html 然后根据官方文档的意思,目前之支持Android版本,为什么IOS版本总是慢一些呢,(╯▔皿▔)╯ 接下来先上代码(需要注意的是得是最新版的预发布版本才行,下载链接:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) [图片] [图片] 在需要分享的页面对应的js文件里面写入"onShareTimeline"即可,需要注意的是需要先编写"onShareAppMessage"方法。 [图片] 点击右上角三个点,出来的效果如下图所示 [图片][图片] 再次点击右下角得"查看小程序分享页"就可以看到类似官方文档上的效果,具体如下图所示: [图片][图片] 在Android手机端下载最新版本的小程序示例,也未发现对应的分享朋友圈功能,静候微信团队的更新
2020-07-08 - button share如何防止多次点击,安卓手机会多次调用转发功能
安卓多次弹出分享页面
2018-10-15 - 利用css圆锥渐变实现环形进度条
[图片] 锥形渐变的正式语法如下: conic-gradient( [ from <angle> ]? [ at <position> ]?, <angular-color-stop-list> ) 可以看出锥形渐变由3部分组成: 起始角度 中心位置 角渐变断点 其中起始角度和中心位置都是可以省略的 这里只用了角渐变断点 background-image: conic-gradient(green 80%,#fff 80% 100%); 主要实现就两步 1.利用conic-gradient画一个圆 [图片] 2.利用任意元素做个内圆遮挡 [图片] 最外层的背景色原来是白色的,这里为了便于识别改成了灰色,现在将灰色的背景还原为白色 [图片] 环形进度条完成,就这么简单 通过动态的设置green的值就可以改变进度条的值 补充:还可以实现渐变进度条效果 [图片] 代码片段: 利用css圆锥渐变实现环形进度条
2020-06-22 - 如何在小程序中快速实现环形进度条
在小程序开发过程中经常涉及到一些图表类需求,其中环形进度条比较属于比较常见的需求 [图片] [中间的文字部分需要自己实现,因为每个项目不同,本工具只实现进度条] 上图中,一方面我们我们需要实现动态计算弧度的进度条,还需要在进度条上加上渐变效果,如果每次都需要自己手写,那需要很多重复劳动,所以决定为为小程序生态圈贡献一份小小的力量,下面来介绍一下整个工具的实现思路,喜欢的给个star咯 https://github.com/lucaszhu2zgf/mp-progress 环形进度条由灰色底圈+渐变不确定圆弧+双色纽扣组成,首先先把页面结构写好: .canvas{ position: absolute; top: 0; left: 0; width: 400rpx; height: 400rpx; } 因为进度条需要盖在文字上面,所以采用了绝对定位。接下来先把灰色底圈给画上: const context = wx.createContext(); // 打底灰色曲线 context.beginPath(); context.arc(this.convert_length(200), this.convert_length(200), r, 0, 2*Math.PI); context.setLineWidth(12); context.setStrokeStyle('#f0f0f0'); context.stroke(); wx.drawCanvas({ canvasId: 'progress', actions: context.getActions() }); 效果如下: [图片] 接下来就要画绿色的进度条,渐变暂时先不考虑 // 圆弧角度 const deg = ((remain/total).toFixed(2))*2*Math.PI; // 画渐变曲线 context.beginPath(); // 由于外层大小是400,所以圆弧圆心坐标是200,200 context.arc(this.convert_length(200), this.convert_length(200), r, 0, deg); context.setLineWidth(12); context.setStrokeStyle('#56B37F'); context.stroke(); // 辅助函数,用于转换小程序中的rpx convert_length(length) { return Math.round(wx.getSystemInfoSync().windowWidth * length / 750); } [图片] 似乎完成了一大部分,先自测看看不是满圆的情况是啥样子,比如现在剩余车位是120个 [图片] 因为圆弧函数arc默认的起点在3点钟方向,而设计想要的圆弧的起点从12点钟方向开始,现在这样是没法达到预期效果。是不是可以使用css让canvas自己旋转-90deg就好了呢?于是我在上面的canvas样式中新增以下规则: .canvas{ transform: rotate(-90deg); } 但是在真机上并不起作用,于是我把新增的样式放到包裹canvas的外层元素上,发现外层元素已经旋转,可是圆弧还是从3点钟方向开始的,唯一能解释这个现象的是官方说:小程序中的canvas使用的是原生组件,所以这样设置css并不能达到我们想要的效果 [图片] 所以必须要在canvas画图的时候把坐标原点移动到弧形圆心,并且在画布内旋转-90deg [图片] // 更换原点 context.translate(this.convert_length(200), this.convert_length(200)); // arc原点默认为3点钟方向,需要调整到12点 context.rotate(-90 * Math.PI / 180); // 需要注意的是,原点变换之后圆弧arc原点也变成了0,0 真机预览效果达成预期 [图片] 接下来添加环形渐变效果,但是canvas原本提供的渐变类型只有两种: 1、LinearGradient线性渐变 [图片] 2、CircularGradient圆形渐变 [图片] 两种渐变中离设计效果最近的是线性渐变,至于为什么能够形成似乎是随圆形弧度增加而颜色变深的效果也只是控制坐标开始和结束的坐标位置罢了 const grd = context.createLinearGradient(0, 0, 100, 90); grd.addColorStop(0, '#56B37F'); grd.addColorStop(1, '#c0e674'); // 画渐变曲线 context.beginPath(); context.arc(0, 0, r, 0, deg); context.setLineWidth(12); context.setStrokeStyle(grd); context.stroke(); 来看一下真机预览效果: [图片] 非常棒,最后就剩下跟随进度条的纽扣效果了 [图片] 根据三角函数,已知三角形夹角根据公式radian = 2*Math.PI/360*deg,再利用cos和sin函数可以x、y,从而计算出纽扣在各部分半圆的坐标 const mathDeg = ((remain/total).toFixed(2))*360; // 计算弧度 let radian = ''; // 圆圈半径 const r = +this.convert_length(170); // 三角函数cos=y/r,sin=x/r,分别得到小点的x、y坐标 let x = 0; let y = 0; if (mathDeg <= 90) { // 求弧度 radian = 2*Math.PI/360*mathDeg; x = Math.round(Math.cos(radian)*r); y = Math.round(Math.sin(radian)*r); } else if (mathDeg > 90 && mathDeg <= 180) { // 求弧度 radian = 2*Math.PI/360*(180 - mathDeg); x = -Math.round(Math.cos(radian)*r); y = Math.round(Math.sin(radian)*r); } else if (mathDeg > 180 && mathDeg <= 270) { // 求弧度 radian = 2*Math.PI/360*(mathDeg - 180); x = -Math.round(Math.cos(radian)*r); y = -Math.round(Math.sin(radian)*r); } else{ // 求弧度 radian = 2*Math.PI/360*(360 - mathDeg); x = Math.round(Math.cos(radian)*r); y = -Math.round(Math.sin(radian)*r); } [图片] 有了纽扣的圆形坐标,最后一步就是按照设计绘制样式了 // 画纽扣 context.beginPath(); context.arc(x, y, this.convert_length(24), 0, 2 * Math.PI); context.setFillStyle('#ffffff'); context.setShadow(0, 0, this.convert_length(10), 'rgba(86,179,127,0.5)'); context.fill(); // 画绿点 context.beginPath(); context.arc(x, y, this.convert_length(12), 0, 2 * Math.PI); context.setFillStyle('#56B37F'); context.fill(); 来看一下最终效果 [图片] 最后我重新review了整个代码逻辑,并且已经将代码开源到https://github.com/lucaszhu2zgf/mp-progress,欢迎大家使用
2020-05-27 - 【求解答?】如何比较两个数组,得出相同的元素和不同的元素?
我之前测试的,不管用 let list1 = ['a', 'b','c' ]; let list2 = ['a', 'b']; let same = []; let tet =[] for (let i = 0; i < list1.length; i++) { for (let p = 0; p < list1.length; p++) { if (list1[i]!=list2[p]) { same.push(list1[i]) }else{ tet.push(list1[i]) } } 请教一下,如何运算得出相同元素为a,b,不同的元素c 想获得比较数组比较好用的方法
2020-02-17 - 什么小程序需要教育相关类目?
什么内容或服务的小程序需要补充教育相关类目? [图片] 1、培训机构:1-职业培训 2-课外辅导,小程序涉及以学历教育或成人教育为目的、有线下教学场景的教育培训机构,提供职业培训、课外辅导、语言培训等教育培训业务,需补充教育-培训机构 所需资质:需提供区、县级教育部门颁发的《民办学校办学许可证》及营业执照(或事业单位法人证书、民办非企业单位登记证书)(18年新法规要求,仅针对新增帐号) 案例:下图小程序涉及提供在线教育培训服务,需补充教育-培训机构。 [图片] 2、教育信息服务:小程序内提供教育政策、考试通知等教育信息服务,需补充教育-教育信息服务。 案例:小程序涉及提供高考成绩查询服务,需补充教育-教育信息服务。 [图片] 3、学历教育(学校):适用于小程序提供初等、中等和高等学历教育等服务,需补充教育-学历教育(学校)类目。 所需资质(2选1): 1)公立学校:由教育行政部门出具的审批设立证明或《事业单位法人证书》 2)私立学校:《民办学校办学许可证》 案例:如下图小程序涉及提供专升本学历教育服务,需补充教育-学历教育(学校类目。 [图片] 4、驾校培训:适用于小程序涉及提供驾校在线付费培训服务,需补充教育-驾校培训类目。 所需资质:《机动车驾驶员培训备案》 案例:如下图小程序涉及提供付费学车培训套餐服务,需补充教育-驾校培训类目。 [图片] 5、驾校平台:适用于小程序涉及为驾校主体提供入驻渠道,提供多家驾校在线培训等服务,需补充教育-驾校平台类目。 所需资质(同时提供): 1、两家或以上的驾校培训公司的合作协议 2、驾校培训机构的《机动车驾驶员培训备案》 3、《非经营性互联网信息服务备案核准》 4、《小程序开发者承诺函》(注:《小程序开发者承诺函》在申请类目详情页处含示例模板) 6、教育平台:适用于小程序涉及为学历教育、成人教育的培训机构提供入驻渠道、提供多家教育培训等服务,需补充教育-教育平台类目。 所需资质(二选一): 1、《非经营性互联网信息服务备案核准》、两家或以上的培训机构合作协议、及培训机构的《民办学校办学许可证》 2、《小程序开发者承诺函》、及其他平台良好经营情况的证明材料(如app store 评分超过50万人及app内提供该类目的服务内容截图) 7、素质教育:适用于小程序涉及艺术培训、语言培训等为目的、有线下教学场景的培训机构,及为艺术、语言培训机构提供入驻渠道等服务 注:若提供多家艺术教育培训等服务,需补充“教育-素质教育”类目。 8、婴幼儿教育:适用于小程序0~6岁年龄阶段的婴幼儿教育服务,需补充教育-婴幼儿教育类目。 案例:小程序涉及提供婴幼儿教育服务,需补充教育-婴幼儿教育类目。 [图片] 9、教育-在线教育:小程序涉及提供教育答题类服务,需补充教育-在线教育类目。 案例:下图小程序涉及提供教育类答题服务,需补充教育-在线教育类目。 [图片] 10、教育装备:小程序提供教育教学活动所需的教具、学具、器材、设施、场所及其配置服务,需补充教育-教育装备类目 案例:下图小程序涉及提供科学实验、电子演示器教育设备配置服务,需补充教育-教育装备类目 [图片] 11、出国留学:小程序内涉及提供境外教育服务,需补充教育-出国留学类目。 案例:如下图小程序涉及提供国外留学签证服务,需补充教育-出国留学类目。 [图片] 12、特殊人群教育:小程序内提供特殊人群方面相关的教育服务,需补充教育-特殊人群教育类目。 案例:如图小程序涉及提供自闭症儿童教育服务,需补充教育-特殊人群教育类目。 [图片] 13、在线视频课程:小程序内提供教育行业提供,网课、在线培训、讲座等教育类直播,需补充教育-在线视频课程类目。 所需资质(5选1): 1、《事业单位法人证书》(适用公立学校) 2、区、县级教育部门颁发的《民办学校办学许可证》(适用培训机构) 3、《信息网络传播视听节目许可证》 4、全国校外线上培训管理服务平台备案 5、教育部门的批准文件 案例:如下图小程序涉及提供小学生作文思维提升在线视频课程培训服务,需补充教育-在线视频课程类目。 [图片] 14、素质教育:小程序内提供以艺术培训、语言培训等为目的、有线下教学场景的培训机构,及为艺术、语言培训机构提供入驻渠道等服务,需补充教育素质教育类目。 案例:如下图小程序涉及提供线下门店的拉丁舞蹈培训服务,需补充教育素质教育类目。 [图片] 15、教育平台:小程序内涉及为学历教育、成人教育的培训机构提供入驻渠道、提供多家教育培训等服务,需补充教育-教育平台服务类目。 所需资质(需同时提供): 1)《非经营性互联网信息服务备案核准》 2)两家或以上的培训机构合作协议,及培训机构的《民办学校办学许可证》 案例:如下小程序涉及为职业资格、技能培训、电脑课程等培训机构提供入住渠道,需补充教育-教育平台服务类目。 [图片]
2021-12-03 - 关于 swiper 中 数据过多时 滑动卡顿掉帧的解决办法
[图片] 做小程序的时候 使用到了 swiper, 大概15个 <swiper-item> 每个<swiper-item>中是一个展示列表大概100条数据. 发现滑动的时候非常的卡顿掉帧, 苹果手机稍微好点但是也掉帧. 我的解决方式是: 定义2个数组 第一个数组存放所有数据 allList 第二个数组 存放 15个空数组 用于 <swiper-item>循环 list: [[],[],[],[]...] 然后获取当前页 下一页: 当前页+1, 上一页:当前页-1 如果当前页的索引为 0 (说明是第一页) 则上一页为最后一页 = list.length -1 如果当前页的索引为 list.length-1 (说明是最后一页) 则下一页为第一页 = 0 这样判断, 就可以拿到了 上一页 当前页 下一页的索引 然后每次切换页面 动态改变list { list [上一页索引] = allList [上一页索引] list [当前页索引] = allList [当前页索引] list [下一页索引] = allList [下一页索引] 可选: 可以选择清空之前的数据 例如 清空上上页的数据 这样就可以一直维持 只有3个数组中有数据 swiper就不会卡顿了, 也可以保留达到缓存的效果 } 这样就每次渲染其实只有3个数组中有数据 别的 <swiper-item>为空 就会流畅很多 如果表达不清晰 见谅
2020-06-15 - 小程序-业务域名-没有入口配置了?
上周还能配置业务域名。 这周打开。没有业务域名配置的入口了。请问是什么情况、。?、 [图片] 下面这是上周配置的结果 [图片]
2020-07-06 - 小程序将小程序码与图片结合生成海报分享朋友圈
样例参考(瑞幸咖啡小程序) [图片][图片][图片] 需求分析 服务器端会返回不确认的图片资源到前端 前端将返回的每张图片都要贴上小程序码 将贴上小程序码的图片使用 swiper 组件轮播 用户点击保存时,将图片保存至相册。 至于点击保存如何保存至相册(wx.saveImageToPhotosAlbuml 了解一下,注意一下授权问题即可) 碰到的问题 canvas为原生组件, 而原生组件的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。 [代码]采用方法: 通过定位,将其移除到不在可视范围,如iphone6 .canvas { position: relative; left: -375px; } [代码] 绘制多张图片时, 一个canvas 标签只能对应画一张图片 (目前我测试的是这样,如有其它方法,欢迎评论交流) [代码]采用方法: 1. html端循环要生成的图片张数,对应循环出多个 canvas 组件,分别设置不同的 canvasId 区分 2. 封装一个绘制海报的函数,返回一个Promise对象,用于后面绘制完所有的图片,统一赋值渲染,避免多次触发数据更新 3. js端 循环执行一次 绘制海豹函数,存于一个数组列表 4. 使用 Promise.all 函数,统一绘制完毕将生产图片路径赋值 html: <canvas v-for="u in m.urlList" :key="u" :canvas-id="'poster'+index" class="canvas" style="width:100%; height:100%;"> </canvas> <swiper :indicator-dots="true" :circular="true" :autoplay="true" indicator-color="rgba(255,255,255,.2)" indicator-active-color="#fff" class="swiper"> <swiper-item v-for="f in m.filePaths" :key="index"> <image :src="f"> </swiper-item> </swiper> js: // 数据 (mpvue 开发) function data(){ return { m : { urlLIst: [ // 图片资源 '/static/poster0.jpg', '/static/poster1.jpg' ], filePaths: [], // 生成图片(贴上小程序码) } } } try { let res = wx.getSystemInfoSync(); // 同步获取系统信息 let w = res.windowWidth; // 手机可用区域宽度 let h = res.windowHeight; // 手机可用区域高度 let codeUrl = '/static/code.jpg'; // 小程序码 let drawList = []; // 用于保存绘制海报图Promise对象 m.urlList.forEach( (u, i)=> { // 传入canvas组件ID,图片路径(测试使用的是本地路径) drawList.push(drawPoster('poster'+i, u, codeUrl)); }) // 统一更新数据 Promise.all(drawList).then((valuse)=>{ m.filePaths = valuse; }); // 封装绘制图片函数 function drawPoster(canvasId, bgUrl, codeUrl){ return new Promise( (resolve, reject) => { // 创建画布实例 let ctx = wx.createCanvasContext(canvasId); // 绘制背景图: 图片路径,x坐标,y坐标,宽,高 ctx.drawImage(bgUrl, 0, 0, w, h); // 绘制小程序码 ctx.drawImage(codeUrl, w-120, h-120, 100, 100); // 绘制 ctx.draw(false, ()=>{ // 该通过函数将canvas绘制导出为图片 wx.canvasToTempFilePath({ x: 0, y: 0, width: w, height: h, canvasId: canvasId, success(res){ resolve(res.tempFilePath); } }); }); } }catch(e){ // 自己封装了一成 wx.$toast(e); } [代码] 最终demo效果图 [图片][图片] 在社区中暂未看到多张海报实现的方案,如果有更好的实现方案,欢迎交流
2019-07-30 - wx.requestPayment( ) success回调函数,需要点完成执行?
wx.requestPayment( ) success回调函数,需要点完成执行,但是再success里我需要跳转页面,跳转页面然后我设置了定时器,时长200, 但是老是点完后,先显示之前的结算按钮界面,再跳转, 这样用户如果又点击了一次,又调起了一次微信支付
2020-05-08 - 不同手机对于字符串转换成日期处理不一样
pc 及小米手机[图片] 苹果7p和魅族Pro5[图片]
2018-09-03 - 在 app.js 里面引入 js 文件,这个问题无法获取到 globalData 数据?
[图片] [图片] [图片]
2020-04-16 - “小程序直播”接入指引
各位微信开发者: “小程序直播” 功能正在公测中,具体接入指引请参考《小程序直播产品介绍及操作指引》。 一、功能简介 小程序直播是微信官方在2020年2月公测推出的产品能力,帮助商家在自有小程序中实现直播互动与商品销售的闭环。 [图片] 二、商家准入要求 满足以下条件,即可开通小程序直播: ①属于小程序直播开放类目,具体见《微信小程序直播功能准入要求》 ②主体下小程序近半年没有严重违规; ③小程序近90天内,有过支付行为; 三、具体产品功能及操作指引 具体接入指引请参考《小程序直播产品介绍及操作指引》,以下为商家操作步骤: 1. 开通权限 1) 登录“小程序后台”(mp.weixin.qq.com),在左侧导航栏找到“小程序—功能—直播”,点击开通。 小程序直播需要基于小程序,如若开发者还未创建小程序,可按照《小程序接入指南》流程指引创建小程序并完成开发。 2) 符合上述开放范围的即可开通。 2.功能开发 小程序直播需要实现【直播组件】与【后台配置】两个部分,其中组件部分需要在小程序中进行配置开发。 具体开发文档,请参考《小程序直播开发文档》。 2.直播间配置 开发完成后,商家可通过小程序后台设置直播计划、开通、设置抽奖等操作。具体操作指引,请参考《小程序直播产品介绍及操作指引》。
2020-08-26 - 开源小程序推荐系列一,垃圾分类小程序
开源小程序推荐系列一,垃圾分类小程序 https://developers.weixin.qq.com/community/develop/article/doc/0000ea84ad881084852aee39e56013 具体代码请移步 https://github.com/qi19901212/Garbage 垃圾分类小程序 小程序使用到的云开发内容不了解小程序云开发请访问文档云函数,云数据库: 数据库:存储四种垃圾分类的相关垃圾数据, 创建表commit,sort,product。把sort.json 和product.csv 导入云数据库即可云函数:获取百度识别库的accessToken 百度AI识别库地址 QQ AI地址 需要修改为自己的key小程序key 在文件project.config.json->appid 记住创建小程序的时候选择云开发百度key 主要做拍照识别的cloudfunctions->baiduAccessToken->index->apiKey和secretKey 此处替换为:API Key 和 Secret Key智能询问采用ai.qq 的智能闲聊接口 key在pages->android->qa->app_id和appKey 云开发管理系统此管理系统用来管理这个小程序的数据,这样子我们彻底不需要服务器了。 GarbageAdmin 方便管理小程序云的数据 常见错误没有自己部署云函数数据库没有给与相应的权限,最好给最大权限需要的key 配置错误。没有创建数据库名称 最常见错误(遇到的人最多) 3 4 未完待续
2020-06-09 - 使用云函数切记不要再export 之外定义全局变量.
如题,云函数生产环境的运作方式为多例模式, 微信提供有线程池机制,你的云函数在一定时间不调用的话会被销毁.。下次调用的时候会重新创建functio. 记录下踩坑过程, 做了一个小程序,准备用云函数填充些mock数据,本地运行正常,然后提交上传后,一直卡在初始化,走不出去。 感觉不太对,做个测试,发现他内存中的数据不是销毁,而是每次实例化新的云函数以及旧实例复用. 贴下测试代码, ``` const cloud = require("wx-server-sdk"); const moment = require("moment"); cloud.init({ env: `could-run-time-b32305`, traceUser: true }); let n = 0; exports.main = async () => { n++; return { n }; }; 可怜, 今天一天白写了.. 2020年6月9日 07:12:55 补充下 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/mechanism.html
2020-06-09 - 小程序云开发,适合百万量级的数据吗?
之前用微信小程序,写了一个社区团购的系统.目前每天几百订单,上千条数据的写入. 现在有两个问题,第一是,云开发,无法对接web端,如果打印一些东西的话,存在不便利. 第二个,就是数据量越来越大,怎么办?
2020-05-31 - 小程序快速接入 ECharts 图表插件功能
[图片] ECharts 图表组件插件可以让小程序轻松使用 ECharts 图表功能,不用担心接入成本以及占用较大体积的问题,以及较为复杂的接入使用问题。详细接入文档:https://mp.weixin.qq.com/wxopen/plugindevdoc?appid=wx1db9e5ab1149ea03 接入步骤1、插件申请首先要在小程序管理后台的“设置-第三方服务-插件管理”中添加插件。开发者可登录小程序管理后台,通过 [代码]wx1db9e5ab1149ea03[代码] 查找插件并添加。通过申请后,方可在小程序中使用相应的插件 2、引入插件配置在 app.json 中插件如下代码,注意 AppId 为 [代码]wx1db9e5ab1149ea03[代码] { "plugins": { "echarts": { "version": "1.0.2", "provider": "wx1db9e5ab1149ea03" } } } 3、使用方式在使用的页面中的 json 配置文件中,插件如下代码: { "usingComponents": { "chart": "plugin://echarts/chart" } } 在 wxml 中需要展示图表的位置,插入如下代码: <chart chart-class="chart" option="{{ option }}" bindinstance="onInstance" /> 其中 chart-class 为样式类,option 为 ECharts 中的 option 配置对象,bindinstance 会回调 ECharts 实例对象,如果需要操作 ECharts 实例对象可以实现此方法 4、效果展示[图片] 注意事项如果需要兼容低于 2.6.1 的小程序,在更新 option 的时候需要手动 setOption,具体代码如下: // 支持 observers 的版本可以直接修改 option if (echarts.isSupportObservers()) { this.setData({ option, }); } else if (this.instance) { // 在不支持 observers 的小程序版本中需要手动通过 ECharts 实例更新 option 配置 this.instance.setOption(option, true); } 如果你的小程序最低支持版本大于 2.6.1,或者你不会动态改变 option 配置,那么你不需要关系这段代码,也不需要处理 bindinstance 回调。 关于使用中遇到任何问题可以添加微信反馈,添加微信时可以备注 ECharts。 [图片]
2020-06-16 - 小程序页面通信、数据刷新、事件总线 、event bus 终极解决方案之 iny-bus
#### 背景介绍 在各种小程序中,我们经常会遇到 这种情况 有一个 列表,点击列表中的一项进入详情,详情有个按钮,删除了这一项,这个时候当用户返回到列表页时, 发现列表中的这一项依然存在,这种情况,就是一个 `bug`,也就是数据不同步问题,这个时候测试小姐姐 肯定会找你,让你解决,这个时候,你也许会很快速的解决,但过一会儿,测试小姐姐又来找你说,我打开了 四五个页面更改了用户状态,但我一层一层返回到首页,发现有好几个页面数据没有刷新,也是一个 bug, 这个时候你就犯愁了,怎么解决,常规方法有下面几种 #### 解决方法 1. 将所有请求放到 生命周期 `onShow` 中,只要我们页面重新显示,就会重新请求,数据也会刷新 2. 通过用 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据 3. 通过设置一个全局变量,例如 App.globalData.xxx,通过改变这个变量的值,然后在对应 onShow 中检查,如果值已改变,刷新数据 4. 在打开详情页时,使用 redirectTo 而不是 navigateTo,这样在打开新的页面时,会销毁当前页面, 返回时就不会回到这个里面,自然也不会有数据不同步问题 #### 存在的问题 1. 假如我们将 所有 请求放到 onShow 生命周期中,自然能解决所有数据刷新问题,但是 onShow 这个生命周期,有两个问题 第一个问题,它其实是在 onLoad 后面执行的,也就是说,假如请求耗时相同,从它发起请求到页面渲染, 会比 onLoad 慢 第二个问题,那就是页面隐藏、调用微信分享、锁频等等都会触发执行,请求放置于 `onShow` 中就会造成 大量不需要的请求,造成服务器压力,多余的资源浪费、也会造成用户体验不好的问题 2. 通过 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据,这也 不失为一个办法,但是就如微信官方文档所说 > 不要尝试修改页面栈,会导致路由以及页面状态错误。 > 不要在 App.onLaunch 的时候调用 `getCurrentPages()`,此时 page 还没有生成。 同时、当需要通信的页面有两个、三个、多个呢,这里去使用 `getCurrentPages` 就会比较困难、繁琐 3. 通过设置全局变量的方法,当需要使用的地方比较少时,可以接受,当使用的地方多的时候,维护起来 就会很困难,代码过于臃肿,也会有很多问题 4. 使用 redirectTo 而不是 navigateTo,从用来体验来说,很糟糕,并且只存在一个页面,对于 tab 页面,它也无能为力,不推荐使用 #### 最佳实践 在 Vue 中, 可以通过 new Vue() 来实现一个 event bus作为事件总线,来达到事件通知的功能,在各大 框架中,也有自身的事件机制实现,那么我们完全可以通过同样的方法,实现一个事件中心,来管理我们的事件, 同时,解决我们的问题。iny-bus 就是这样一个及其轻量的事件库,使用 typescript 编写,100% 测试覆 盖率,能运行 js 的环境,就能使用 传送门 [源码](https://github.com/landluck/iny-bus) [NPM](https://www.npmjs.com/package/iny-bus) [文档](https://landluck.github.io/iny-bus/docs/) #### 简单使用 iny-bus 使用及其简单,在需要的页面 onLoad 中添加事件监听, 在需要触发事件的地方派发事件,使监 听该事件的每个页面执行处理函数,达到通信和刷新数据的目的,在小程序中的使用可以参考以下代码 [代码] // 小程序[代码] [代码] import bus from [代码][代码]'iny-bus'[代码] [代码] // 添加事件监听[代码] [代码] // 在 onLoad 中注册, 避免在 onShow 中使用[代码] [代码] onLoad () {[代码] [代码] this[代码][代码].eventId = bus.on([代码][代码]'事件名'[代码][代码], (a, b, c, d) => {[代码] [代码] // 支持多参数[代码] [代码] console.log(a, b, c, d)[代码] [代码] this[代码][代码].setData({ a [代码]}) [代码] // 调用页面请求函数,刷新数据[代码] [代码] this[代码][代码].refreshPageData()[代码] [代码] })[代码] [代码] // 添加只需要执行一次的 事件监听[代码] [代码] this[代码][代码].eventIdOnce = bus.once([代码][代码]'事件名'[代码][代码], () => {[代码] [代码] // do some thing[代码] [代码] })[代码] [代码] }[代码] [代码] // 移除事件监听,该函数有两个参数,第二个事件id不传,会移除整个事件监听,传入ID,会移除该[代码] [代码] // 页面的事件监听,避免多余资源浪费, 在添加事件监[代码][代码]/// 听后,页面卸载(onUnload)时建议移除[代码] [代码] onUnload () {[代码] [代码] bus.remove([代码][代码]'事件名'[代码][代码], [代码][代码]this[代码][代码].eventId)[代码] [代码] }[代码] [代码] // 派发事件,触发事件监听处更新视图[代码] [代码] // 支持多参传递[代码] [代码] onClick () {[代码] [代码] bus.emit([代码][代码]'事件名'[代码][代码], a, b, c)[代码] [代码] }[代码] 更详细的使用和例子可以参考 [Github iny-bus 小程序代码](https://github.com/landluck/iny-bus/tree/master/examples) #### iny-bus 具体实现 * 基本打包工具,这里使用非常优秀的开源库 [typescript-library-starter](https://github.com/alexjoverm/typescript-library-starter),具体细节不展开 * 测试工具 使用 facebook 的 [jest](https://github.com/facebook/jest) * build ci 使用 [travis-ci](https://www.travis-ci.org/) * 测试覆盖率上传使用 [codecov](https://codecov.io/) * 具体的其他细节大家可以看源码中的 [package.json](https://github.com/landluck/iny-bus/blob/master/package.json),这里就一一展开讲了 iny-bus 的核心代码,其实就这么多,总的来说,非常少,但是能解决我们在小程序中遇到的大量 通信 和 数据刷新问题,是采用 各大平台小程序 原生开发时,页面通信的不二之选,同时,100% 的测试覆盖率,确保了 iny-bus 在使用中的稳定性和安全性,当然,每个库都是从简单走向复杂,功能慢慢完善,如果 大家在使用或者源码中发现了bug或者可以优化的点,欢迎大家提 pr 或者直接联系我 最后,如果 iny-bus 给你提供了帮助或者让你有任何收获,请给 作者 点个赞,感谢大家 [点赞](https://github.com/landluck/iny-bus)
2019-08-04 - 云数据库 _id好长
_id:"1584021120434_0.983830209324497_33605008-1584021172520_1_24654"(string)为啥要这么长的id啊小程序码scene参数限制好烦啊~
2020-03-12 - 微信小程序CI流程搭建教学(1) -- 实现云构建
demo仓库: https://github.com/jinxuanzheng01/blog-xcx-ci-demo, 推荐对照阅读 CI流程搭建教学三部曲: 实现云构建如何接入CI(jenkins版)搭建一个版本发布管理后台 + 企业微信机器人群通知背景自微信小程序诞生以来,上传体验版/生成开发版这个事就离不开微信开发者工具,最开始是依赖于人手动去点ide上的上传按钮,再然后就是,微信开放出了命令行调用接口但始终不能解决一个根本问题就是不能实现服务器发布(开发者工具没有linux版本,云服务器买windows的当我没说...),发布流程依赖于本地环境,这里会有三个问题: 本地环境发布是存在风险的,一旦出现问题很难在第一时间找到替代环境整套流程做在本地,整个项目会变得很重(额外的包 + 发布逻辑),包括一些权限,ip白名单的处理也很麻烦没办法平滑的接入CI流程,多个小程序项目发布很头疼,需要不停切换仓库这个也是我们在之前的解决方案中遇到的问题 契机我一般会保持一周过一遍文档的习惯(量子阅读,手动滑稽~~),发现文档里出了一个叫做CI的东西,文档地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html [图片] [图片] 里面有这样一句话,开发者可不打开小程序开发者工具,独立使用miniprogram-ci进行小程序代码的上传、预览等操作,看描述终于是可以脱离开发者工具了,既然之前最大的阻碍"开发者工具没有linux版本"消失了,那么是不是就意味着可以在服务器上进行构建(云构建)呢? Let's Go目录结构因为要使用到npm包,所以为了避免在项目中安装不会被使用到的minprogram-ci这个包,所以需要在原先的目录结构上做下调整,主要是在项目外包了一层,这样除了可以安装minprogram-ci外也可以安装一些其他在build过程中用到的一些东西 [图片] CI配置增加ci.js文件,这个相当于是我们的入口代码,引用node包minprogram-ci,并进行appid,项目路径,私钥的配置,官方文档里写的很清楚,我这里快速过 [图片] 这里说下privateKeyPath,这个东西需要在微信公众平台后台去拿,下面为官方描述👇 [图片] 我这里为了方便直接把这个秘钥放在项目里了,不过我这里开了ip白名单,倒也不担心安全问题,当前项目目录: [图片] 基础功能实现miniprogram-ci基本是将开发者工具提供的命令行工具复刻了一套版本,这里也不一一赘述了,主要是在写下上传代码的功能 // new ci实例 const project = new ci.Project({ appid: projectConfig.appid, type: 'miniProgram', projectPath: projectConfig.miniprogramRoot, privateKeyPath: './ci-private.key', ignores: ['node_modules/**/*'], }); /** 上传 */ async function upload({version = '0.0.0', desc ='test'}) { await ci.upload({ project, version, desc, setting: { es7: true, minify: true, autoPrefixWXSS: true }, onProgressUpdate: console.log, }) } upload({version: '1.2.3', desc: '2222'}) 调用ci提供的upload方法,将之前生成的project实例丢到对应属性里,version和desc分别对应 [图片] setting的话主要是一些编译项,看自己的需求 [图片] onProgressUpdate主要是用来用来看上传状态的,(个人觉得没什么用,有错误这个包也会自己抛出来) [图片] 执行上传这样一个上传功能数了下大概23行代码,简单快捷,基本上只是配置一些选项就👌,那么我们尝试跑一下这个脚本行不行得通~~ 执行`node ci.js` [图片] 上来抛出一个ip错误。。 不要慌,登录微信公众后台-开发-开发设置,配置下白名单 [图片] 再执行一次,可以看到显示upload成功 [图片] 回到微信公众后台,多了一个ci机器人1的提交,成功~~~(这里吐槽下这个开发者名字有点丑,而且不能改,只能改序号,如ci机器人2。。) [图片] 用交互式命令提升幸福感inquirer 的文档: https://github.com/SBoudrias/Inquirer.js如果不打算接入jenkins这种CI平台的话,可以考虑通过[代码]inquirer[代码] 这个包,解决手写版本号带来的忘,写错的尴尬 先安装一下 npm install inquirer 写一段简单的命令 function inquirerResult() { return inquirer.prompt([ // 设置版本号 { type: 'input', name: 'version', message: `设置上传的版本号:`, }, // 设置上传描述 { type: 'input', name: 'versionDesc', message: `写一个简单的介绍来描述这个版本的改动过:`, }, ]); } async function init() { let result = await inquirerResult(); console.log(result); // 输出 } init(); 重新执行, 得到了一个对象,key名是我们设置的name,value是我们输入的值 [图片] (截屏好像有点问题,放一段我们公司之前用的gif,大概效果是这样) [图片] 但是这里发现一个问题,交互命令有了,但是版本号和文字还是手输的,并没有解决问题啊,这里其实可以设置默认值和选项,最好不要让我去想 既然需要有默认选项,相当于我们要存储每一次生成的版本号,其实最简单的方法就是新建一个json文件,把版本号和版本描述补进来,每次执行上传操作的时候去读取该json的信息 [图片] /** 入口函数 */ async function init() { // 版本信息 let versionData = await inquirerResult(versionConfig); // 版本发布 await upload(); // 重写版本文件 fs.writeFileSync('./version.config.json', JSON.stringify(versionData), err => { if(err) { console.log('自动写入app.json文件失败,请手动填写,并检查错误'); } }); } 具体的版本号自增逻辑demo仓库里有,这里就不粘了,大概的效果: [图片] 是不是感觉好多了 ~~ 云服务器运行回归到目标,我们本身目标是为了解决小程序无法在服务器上构建的问题,既然我们把demo写完,那到底能不能运行就该出来溜溜了~~ 链接我们的服务器,clone一下仓库: https://github.com/jinxuanzheng01/blog-xcx-ci-demo.git, 记得运行前修改下ci-private.key里面的内容,换成自己的密匙,不然会报ip错误 运行以下命令: git clone https://github.com/jinxuanzheng01/blog-xcx-ci-demo.git cd blog-xcx-ci-demo && npm i node start.js 执行上传: [图片] 查看微信公众后台: [图片] 看上去已经上传成功 ~~~ 待续 到这里为止已经实现在云服务器上进行构建 + 上传,但是也仅仅只是换了个环境,怎么接入CI流程才是真正能够提升生产力的东西 预告下文主题是: 小程序云构建如何接入jenkins实现持续集成 相关阅读《小打卡小程序自动化构建及发布的工程化实践》 《从0到1开发一个小程序cli脚手架(二) --版本发布/管理篇》
2020-05-28 - 微信小程序下载Excel报表
需求: 微信小程序内,要增加,导出Excel报表的功能。 服务器生成excel报表,在微信小程序内,下载下来。并能够分享给 自己的微信好友~ 在微信小程序内,这个功能可以实现吗? 现在我的解决办法是:点击下载按钮,复制下载的链接,引导用户去,手机浏览器下载,(得到了文件,通过浏览器分享给微信好友) 各位大佬,有没有好的想法,或者建议。 我也用了 API 通过 wx.downloadFile() 下载 wx.saveFile() 保存文件 wx.getSavedFileList() 获取到缓存的文件列表 这也没法做,文件 的分享,求指点
2019-04-12 - WeUI官方组件库:助力小程序高效设计与开发
提起 WeUI,相信大家都不陌生,WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。 不过,对于 WeUI 样式库,开发者就有疑问了。 [图片] [图片] 我们来看看 WeUI 组件库到底有什么可用的 UI 组件呢?WeUI 样式库有的各个元素,WeUI 组件库是基于 WeUI 样式库做了组件化处理,开发者可以便捷的使用,无需考虑组件层面的逻辑问题。 [图片] 有了心动的组件之后,大家肯定想知道 WeUI 组件库是怎么使用的。 [图片] 要使用 WeUI,首先要把 WeUI 引入我们的小程序项目,引入 WeUI 的方式有以下两种,使用其中一种即可~ 方法一:通过 useExtendedLib 扩展库 的方式引入,这种方式引入的组件将不会计入代码包大小。(推荐👍) 方法二:可以通过 npm方式下载构建,npm 包名为 weui-miniprogram。 与方法一不同,npm 引入的方式需要多操作一步,在 app.wxss 中引用 weui.wxss。 // app.wxss @import '/miniprogram_npm/weui-miniprogram/weui-wxss/dist/style/weui.wxss'; [图片] 引入之后,我们就要开始来使用了,WeUI 组件库是基于小程序自定义组件构建的,所以使用是以自定义组件的形式来使用。 下面通过几个例子来感受下 WeUI 组件库的使用。 由于是自定义组件的形式,所以使用组件都需要在页面配置中引入,像这样: // page.json { "usingComponents": { "mp-half-screen-dialog": "weui-miniprogram/half-screen-dialog/half-screen-dialog", "mp-searchbar": "weui-miniprogram/searchbar/searchbar" } } 引入组件之后,就可以直接在 wxml 中使用了,当然,为了让开发者接入更加简便,我们也加入了做了一些常见的实用性功能。 半屏弹窗 小程序提供了 wx.showModal、wx.showToast 供开发者进行页面交互,在开发过程中,可能需要自定义按钮相关的内容,所以 WeUI 提供了半屏弹窗让开发者可以有更多的自定义空间。 我们来看下代码,使用很简单,直接使用 mp-half-screen-dialog,配置相关属性即可。 // page.wxml <mp-half-screen-dialog bindbuttontap="buttontap" show="{{show}}" mask-closable="{{false}}" title="测试标题B" sub-title="测试标题B的副标题" desc="辅助描述内容,可根据实际需要安排" tips="辅助提示内容,可根据实际需要安排" buttons="{{buttons}}"> </mp-half-screen-dialog> // page.js data配置 buttons: [ { type: 'default', className: '', text: '辅助操作', value: 0 }, { type: 'primary', className: '', text: '主操作', value: 1 } ] 来看下半屏弹窗的效果~ u1s1,这交互体验真的爱了😍 [图片] Form 表单校验 Form 表单这里,除了基础的的功能之外,WeUI 组件库还提供了表单校验的能力,通过 rules 规则的配置(支持长度、手机号码、电子邮件、url 链接地址等),轻松解决表单校验问题。 [图片] 左滑删除 相比 Web 端,手机端的操作按钮更多的是通过⬅️左滑等来实现,考虑到左滑删除的普遍性,WeUI 组件库也是支持的。 在 mp-slideview 组件中设置 buttons属性即可。 [图片] 搜索组件 同样是基本功能的搜索,WeUI 组件库也封装了搜索组件,开发者只需配置搜索结果即可拥有搜索功能~ [图片] 除了这些组件之外,WeUI 组件库还提供了很多实用的组件,包括基础的 icon、loading,表单的 uploader、cell 等等。 [图片] 伴随客户端、小程序对 DarkMode 的支持,WeUI 组件库也同步适配 DarkMode 的模式,让 WeUI 组件库的使用同学可以快速适配 DarkMode。 在根结点(或组件的外层结点)增加属性 data-weui-theme="dark" ,即可把 WeUI 组件切换到 DarkMode 的表现,如: <view data-weui-theme="dark"> ... </view> [图片] 最后,如果想体验 WeUI 组件库的效果,欢迎点击下方小程序示例体验👏及接入使用,使用过程中如有建议或者疑问,欢迎到微信开放社区与我们交流。 [图片]
2020-05-21 - 一眼告诉你什么是订阅消息了,看完就懂订阅消息。
消息通知有两种: 一、A的动作后,发消息给A自己,这种容易解决,不多说明; 二、A动作后,发消息给B(比如管理员、店家、楼主),如何保证B收到消息?这种是本方案要解决的问题。 一张图片一眼告诉你什么是订阅消息,产品经理的设计UI居然让人一眼就知道订阅消息是什么玩意。 [图片] 用户 B (管理员、商家、组长、楼主)在知道订阅数不足后,打开小程序来续订阅数,否则没法收到订阅消息。 [图片] 补充一: 关于勾选按钮,请注意话述是:“总是保持以上选择,不再询问”,而不是:“总是同意接收订阅消息”,不要幻想就成了永久性订阅消息; 相当于你打电话订外卖,对店家说“老样子”,店家只会马上送一次外卖,而不是会以后每天自动给你送外卖了。 勾选和不勾选的区别是什么呢? 区别仅仅是:不勾选时,必须点击订阅10次,弹窗10次;勾选后,仍然必须点击订阅10次,但是不弹窗。无论如何“订阅”这个点击n次的动作少不了。 补充二: 一旦勾选后,就不可逆了,没有任何办法恢复或取消勾选了,除非你小程序MP后台换一次消息模板号(删除模板,重新添加一次)。 补充三: 关于如何保存订阅数。 保存在数据库中,笔者用的是云开发,数据库表user结构如下: { _id:'openid1', nickName:'老张', msg:{ "tempId1":5, "tempId2":7, } } 补充四: 关于如何获取订阅数。两种方式: 一、wx.requestSubscribeMessage的回调success里获取; 二、消息推送机制获取;https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
2022-09-21 - 小程序同层渲染原理剖析
众所周知,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 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)也会在近期逐步上线。现在你就可以试试用「同层渲染」来优化你的小程序了。 支持同层渲染的原生组件 最低版本 video v2.4.0 map v2.7.0 canvas 2d(新接口) v2.9.0 live-player v2.9.1 live-pusher v2.9.1
2019-11-21 - 微信小程序swiper控件卡死的解决方法
微信小程序swiper控件,在使用过程中会偶尔出现卡死现象(不能再左右滑动),跟踪一下,归结原因可能是swiper组件内部问题,当无法响应用户快速翻动动作时,当前页变量current无法变更为正确页码索引,而是被置为0,所以,解决这个问题的思路如下: [代码]swiperchange: function (event) { if (event.detail.source == "touch") { //防止swiper控件卡死 if (this.data.current == 0 && this.data.preIndex>1 ) {//卡死时,重置current为正确索引 this.setData({ current: this.data.preIndex }); } else {//正常轮转时,记录正确页码索引 this.setData({ preIndex: this.data.current }); } } } [代码]
2020-02-16 - Taro + 小程序云开发实战|日语用例助手
原创: Evont 前言小程序开放了云开发能力,为开发者提供了一个可以很快速构建小程序后端服务的能力,作为一名对新技术不倒腾不快的前端,对此也是很感兴趣的。 Taro 是凹凸实验室推出的,基于React 语法规范的多端开发解决方案,较之于mpvue或者wepy,由于年轻,坑还比较多,但是很适合我这种倾向用React 开发的人。 我结合这两者,使用cheerio和superagent 抓取了用例.jp, 开发了一个《日语用例助手》。 入门踩坑1.云开发篇1.1 环境搭建云开发可以通过下列两种方式创建: 1.使用quickstart(云开发快速启动模版)创建项目:[图片]这种方式会在目录下同时创建名为miniprogram ,带有云开发调用范例的小程序基础模板和名为cloudfuntions 的存放云函数的目录, 由此即可开始全新的项目。 2.基于现有的小程序使用云开发: [图片] 1.2 云函数编写使用微信开发者工具在云函数目录下创建一个云函数时,会根据名称创建一个目录,目录中包含一个index.js 和package.json。 在小程序中使用如下方式调用云函数: [代码]wx.cloud.callFunction({[代码] [代码] name: '云函数名称',[代码] [代码] data: {[代码] [代码] key1: 'value1',[代码] [代码] key2: 'value2'[代码] [代码] }[代码] [代码]}).then((res) => {[代码] [代码] console.log(res);[代码] [代码]}).catch((e) => {[代码] [代码] console.log(e);[代码] [代码]});[代码] index.js的入口函数如下所示: [代码]//云函数入口函数[代码] [代码]exports.main = async (event, context) => {[代码] [代码] // 参数获取在event 中获取,如使用上面的调用函数后,获取data使用 event.key1、event.key2即可[代码] [代码] const { key1, key2 } = event;[代码] [代码] return { query: { key1, key2 } }[代码] [代码]}[代码] 每个云函数可视为一个单独的服务,如果需要安装第三方依赖,只需要在该目录点击右键,选择 [代码]在终端中打开[代码], 并 [代码]npm install[代码]依赖即可。 需要注意的是,每个云函数都是独立的,所需要的依赖都需要在对应的目录下进行 [代码]npm install[代码],但这样就会使得项目变得十分庞大且不优雅。因此,接下来我介绍一下tcb-router。 1.3 使用tcb-router管理路由tcb-router 是腾讯云团队开发的,基于 koa 风格的小程序·云开发云函数轻量级类路由库,主要用于优化服务端函数处理逻辑。 使用tcb-router的方法很简单: [代码]const TcbRouter = require('tcb-router');[代码] [代码]exports.main = (event, context) => {[代码] [代码] const app = new TcbRouter({ event });[代码] [代码] app.router('路由名称', async (ctx) => {[代码] [代码] //原有的event需要通过ctx._req.event 获取[代码] [代码] const { param1, param2 } = ctx._req.event;[代码] [代码] ctx.body = { key1: value1 };[代码] [代码] });[代码] [代码]})[代码] 此时小程序的调用方式也需要改成: [代码]wx.cloud.callFunction({[代码] [代码] name: '云函数名称',[代码] [代码] data: {[代码] [代码] $url: '路由名称',[代码] [代码] // 其他数据[代码] [代码] param1: 'test1',[代码] [代码] param2: 'test2'[代码] [代码] },[代码] [代码] success: res => {},[代码] [代码] fail: err => {[代码] [代码] console.error(`[云函数] [${action}] 调用失败`, err)[代码] [代码] }[代码] [代码]})[代码] 2.Taro篇2.1 环境搭建[代码]npm install -g @tarojs/cli[代码] [代码]taro init myApp[代码] 2.2 遇到的坑1.API支持不足由于Taro 对微信的一些新api 并没有支持到,比如使用云开发时需要用到 [代码]wx.cloud[代码],Taro 并没有支持,但亲测是可以直接使用 [代码]wx[代码] 变量,但是会被eslint 提醒,看着十分不悦,可以在 [代码].eslintrc[代码] 文件中增加以下代码: [代码]"globals": {[代码] [代码] "wx": true[代码] [代码]},[代码] 2.不能使用 Array#map 之外的方法操作 JSX 数组。 3.不允许在 JSX 参数(props)中传入 JSX 元素(taro/no-jsx-in-props)。 3.爬虫篇3.1 superagentsuperagent 是一个非常实用的http请求模块,用来抓取网页十分有用,使用也十分简单,以下是我在抓取 [代码]yourei.jp[代码] 时使用的代码: [代码]// const superagent = require('superagent');[代码] [代码]// ...[代码] [代码]function crawler(url, cb) {[代码] [代码] return new Promise((resolve, reject) => {[代码] [代码] superagent.get(url).set({[代码] [代码] 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'[代码] [代码] }).end(function (err, res) {[代码] [代码] if (err) {[代码] [代码] reject(err);[代码] [代码] return;[代码] [代码] }[代码] [代码] resolve(res);[代码] [代码] });[代码] [代码] });[代码] [代码]}[代码] 3.2 cheeriocheerio 是一个轻型灵活,类jQuery的对HTML元素分析操作的工具。在进行一些server端渲染的页面以及一些简单的小页面的爬取时,cheerio十分好用且高效。 在使用 [代码]superagent[代码] 抓取了网页内容后,可以使用如下方式解析页面代码: [代码] // const cheerio = require('cheerio');[代码] [代码] // ...[代码] [代码] const result = crawler(apiUrl).then((res) => {[代码] [代码] // 使用load 之后,$ 即可同jquery 一样使用选择器来选择元素了[代码] [代码] const $ = cheerio.load(res.text);[代码] [代码] const categories = [];[代码] [代码] $('[data-toggle]').each((i, ele) => {[代码] [代码] // 可以使用.text()、.html() 等方式获取元素的内容[代码] [代码] categories.push($(ele).attr('href'));[代码] [代码] });[代码] [代码] return {[代码] [代码] list: categories,[代码] [代码] };[代码] [代码] });[代码] 总结1.Taro如果你是React 开发者,需要开发多端小程序,或者原有React 项目想迁移到小程序,Taro 是个不错的选择,但还有很多坑没有填好,希望它的发展越来越好。 2.云开发如果你是个人开发者,想尝试小程序开发又不想或者难以自己搭建服务器,云开发是个好选择,容易上手且十分敏捷。
2019-03-25 - 【开箱即用】分享几个好看的波浪动画css效果!
以下代码不一定都是本人原创,很多都是借鉴参考的(模仿是第一生产力嘛),有些已忘记出处了。以下分享给大家,供学习参考!欢迎收藏补充,说不定哪天你就用上了! 一、第一种效果 [图片] [代码]//index.wxml <view class="zr"> <view class='user_box'> <view class='userInfo'> <open-data type="userAvatarUrl"></open-data> </view> <view class='userInfo_name'> <open-data type="userNickName"></open-data> , 欢迎您 </view> </view> <view class="water"> <view class="water-c"> <view class="water-1"> </view> <view class="water-2"> </view> </view> </view> </view> //index.wxss .zr { color: white; background: #4cb4e7; /*#0396FF*/ width: 100%; height: 100px; position: relative; } .water { position: absolute; left: 0; bottom: -10px; height: 30px; width: 100%; z-index: 1; } .water-c { position: relative; } .water-1 { background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0xPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMTMzLjAwMDAwMCkiIGZpbGwtb3BhY2l0eT0iMC4zIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPGcgaWQ9IndhdGVyLTEiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMS4wMDAwMDAsIDEzMy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLDcuNjk4NTczOTUgTDQuNjcwNzE5NjJlLTE1LDYwIEw2MDAsNjAgTDYwMCw3LjM1MjMwNDYxIEM2MDAsNy4zNTIzMDQ2MSA0MzIuNzIxMDUyLDI0LjEwNjUxMzggMjkwLjQ4NDA0LDcuMzU2NzQxODcgQzE0OC4yNDcwMjcsLTkuMzkzMDMwMDggMCw3LjY5ODU3Mzk1IDAsNy42OTg1NzM5NSBaIiBpZD0iUGF0aC0xIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-1 3.5s infinite linear; animation: wave-animation-1 3.5s infinite linear; } .water-2 { top: 5px; background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0yPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMjQ2LjAwMDAwMCkiIGZpbGw9IiNGRkZGRkYiPgogICAgICAgICAgICA8ZyBpZD0id2F0ZXItMiIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIxLjAwMDAwMCwgMjQ2LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsNy42OTg1NzM5NSBMNC42NzA3MTk2MmUtMTUsNjAgTDYwMCw2MCBMNjAwLDcuMzUyMzA0NjEgQzYwMCw3LjM1MjMwNDYxIDQzMi43MjEwNTIsMjQuMTA2NTEzOCAyOTAuNDg0MDQsNy4zNTY3NDE4NyBDMTQ4LjI0NzAyNywtOS4zOTMwMzAwOCAwLDcuNjk4NTczOTUgMCw3LjY5ODU3Mzk1IFoiIGlkPSJQYXRoLTIiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMC4wMDAwMDAsIDMwLjAwMDAwMCkgc2NhbGUoLTEsIDEpIHRyYW5zbGF0ZSgtMzAwLjAwMDAwMCwgLTMwLjAwMDAwMCkgIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-2 6s infinite linear; animation: wave-animation-2 6s infinite linear; } .water-1, .water-2 { position: absolute; width: 100%; height: 60px; } .back-white { background: #fff; } @keyframes wave-animation-1 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } @keyframes wave-animation-2 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } .user_box { display: flex; z-index: 10000 !important; opacity: 0; /* 透明度*/ animation: love 1.5s ease-in-out; animation-fill-mode: forwards; } .userInfo_name { flex: 1; vertical-align: middle; width: 100%; margin-left: 5%; margin-top: 5%; font-size: 42rpx; } .userInfo { flex: 1; width: 100%; border-radius: 50%; overflow: hidden; max-height: 50px; max-width: 50px; margin-left: 5%; margin-top: 5%; border: 2px solid #fff; } [代码] 二、第二种效果 [图片] [代码]//index.wxml <view class="waveWrapper waveAnimation"> <view class="waveWrapperInner bgTop"> <view class="wave waveTop" style="background-image: url('https://s2.ax1x.com/2019/09/26/um8g7n.png')"></view> </view> <view class="waveWrapperInner bgMiddle"> <view class="wave waveMiddle" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGZ38.png')"></view> </view> <view class="waveWrapperInner bgBottom"> <view class="wave waveBottom" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGuuQ.png')"></view> </view> </view> //index.wxss .waveWrapper { overflow: hidden; position: absolute; left: 0; right: 0; height: 300px; top: 0; margin: auto; } .waveWrapperInner { position: absolute; width: 100%; overflow: hidden; height: 100%; bottom: -1px; background-image: linear-gradient(to top, #86377b 20%, #27273c 80%); } .bgTop { z-index: 15; opacity: 0.5; } .bgMiddle { z-index: 10; opacity: 0.75; } .bgBottom { z-index: 5; } .wave { position: absolute; left: 0; width: 500%; height: 100%; background-repeat: repeat no-repeat; background-position: 0 bottom; transform-origin: center bottom; } .waveTop { background-size: 50% 100px; } .waveAnimation .waveTop { animation: move-wave 3s; -webkit-animation: move-wave 3s; -webkit-animation-delay: 1s; animation-delay: 1s; } .waveMiddle { background-size: 50% 120px; } .waveAnimation .waveMiddle { animation: move_wave 10s linear infinite; } .waveBottom { background-size: 50% 100px; } .waveAnimation .waveBottom { animation: move_wave 15s linear infinite; } @keyframes move_wave { 0% { transform: translateX(0) translateZ(0) scaleY(1) } 50% { transform: translateX(-25%) translateZ(0) scaleY(0.55) } 100% { transform: translateX(-50%) translateZ(0) scaleY(1) } } [代码] 三、第三种效果 [图片] [代码]//index.wxml <view class="container"> <image class="title" src="https://ftp.bmp.ovh/imgs/2019/09/74bada9c4143786a.png"></image> <view class="content"> <view class="hd" style="transform:rotateZ({{angle}}deg);"> <image class="logo" src="https://ftp.bmp.ovh/imgs/2019/09/d31b8fcf19ee48dc.png"></image> <image class="wave" src="wave.png" mode="aspectFill"></image> <image class="wave wave-bg" src="wave.png" mode="aspectFill"></image> </view> <view class="bd" style="height: 100rpx;"> </view> </view> </view> //index.wxss image{ max-width:none; } .container { background: #7acfa6; align-items: stretch; padding: 0; height: 100%; overflow: hidden; } .content{ flex: 1; display: flex; position: relative; z-index: 10; flex-direction: column; align-items: stretch; justify-content: center; width: 100%; height: 100%; padding-bottom: 450rpx; background: -webkit-gradient(linear, left top, left bottom, from(rgba(244,244,244,0)), color-stop(0.1, #f4f4f4), to(#f4f4f4)); opacity: 0; transform: translate3d(0,100%,0); animation: rise 3s cubic-bezier(0.19, 1, 0.22, 1) .25s forwards; } @keyframes rise{ 0% {opacity: 0;transform: translate3d(0,100%,0);} 50% {opacity: 1;} 100% {opacity: 1;transform: translate3d(0,450rpx,0);} } .title{ position: absolute; top: 30rpx; left: 50%; width: 600rpx; height: 200rpx; margin-left: -300rpx; opacity: 0; animation: show 2.5s cubic-bezier(0.19, 1, 0.22, 1) .5s forwards; } @keyframes show{ 0% {opacity: 0;} 100% {opacity: .95;} } .hd { position: absolute; top: 0; left: 50%; width: 1000rpx; margin-left: -500rpx; height: 200rpx; transition: all .35s ease; } .logo { position: absolute; z-index: 2; left: 50%; bottom: 200rpx; width: 160rpx; height: 160rpx; margin-left: -80rpx; border-radius: 160rpx; animation: sway 10s ease-in-out infinite; opacity: .95; } @keyframes sway{ 0% {transform: translate3d(0,20rpx,0) rotate(-15deg); } 17% {transform: translate3d(0,0rpx,0) rotate(25deg); } 34% {transform: translate3d(0,-20rpx,0) rotate(-20deg); } 50% {transform: translate3d(0,-10rpx,0) rotate(15deg); } 67% {transform: translate3d(0,10rpx,0) rotate(-25deg); } 84% {transform: translate3d(0,15rpx,0) rotate(15deg); } 100% {transform: translate3d(0,20rpx,0) rotate(-15deg); } } .wave { position: absolute; z-index: 3; right: 0; bottom: 0; opacity: 0.725; height: 260rpx; width: 2250rpx; animation: wave 10s linear infinite; } .wave-bg { z-index: 1; animation: wave-bg 10.25s linear infinite; } @keyframes wave{ from {transform: translate3d(125rpx,0,0);} to {transform: translate3d(1125rpx,0,0);} } @keyframes wave-bg{ from {transform: translate3d(375rpx,0,0);} to {transform: translate3d(1375rpx,0,0);} } .bd { position: relative; flex: 1; display: flex; flex-direction: column; align-items: stretch; animation: bd-rise 2s cubic-bezier(0.23,1,0.32,1) .75s forwards; opacity: 0; } @keyframes bd-rise{ from {opacity: 0; transform: translate3d(0,60rpx,0); } to {opacity: 1; transform: translate3d(0,0,0); } } [代码] wave.png(可下载到本地) [图片] 在这个基础上,再加上js的代码,即可实现根据手机倾向,水波晃动的效果 wx.onAccelerometerChange(function callback) 监听加速度数据事件。 [图片] [代码]//index.js Page({ onReady: function () { var _this = this; wx.onAccelerometerChange(function (res) { var angle = -(res.x * 30).toFixed(1); if (angle > 14) { angle = 14; } else if (angle < -14) { angle = -14; } if (_this.data.angle !== angle) { _this.setData({ angle: angle }); } }); }, }); [代码] 四、第四种效果 [图片] [代码]//index.wxml <view class='page__bd'> <view class="bg-img padding-tb-xl" style="background-image:url('http://wx4.sinaimg.cn/mw690/006UdlVNgy1g2v2t1ih8jj31hc0p0qej.jpg');background-size:cover;"> <view class="cu-bar"> <view class="content text-bold text-white"> 悦拍屋 </view> </view> </view> <view class="shadow-blur"> <image src="https://raw.githubusercontent.com/weilanwl/ColorUI/master/demo/images/wave.gif" mode="scaleToFill" class="gif-black response" style="height:100rpx;margin-top:-100rpx;"></image> </view> </view> //index.wxss @import "colorui.wxss"; .gif-black { display: block; border: none; mix-blend-mode: screen; } [代码] 本效果需要引入ColorUI组件库
2019-09-26 - 长按海报长图无法识别小程序码的解决方案
今天刚好看到 @沐绒。 提的一个问题《show-menu-by-longpress 在图片过长时,ios会识别不了小程序二维码》,于是就一本正经的慢悠悠的打开集齐BUG于一身让人又爱又恨的小程序开发工具,心里在想“呵,识别不了??我就不信了” 基础库:2.11.0 经过了漫长的长达300,000毫秒时间,新建了一个代码片段试了一番,各种预览,真机预览,真机调试,确实存在长按识别不出来的情况 是图片太长了?还是功能让人感觉有忽悠了? 最后发现,问题所在:只有在页面可视区域完全显示小程序码时,长按才能识别 所有在这个长按识码的功能变得更强大之前,解决方案如下:(不想多写代码,画海报的时候就把小程序码画在左上或者右上吧) 1、给图片绑定touchstart事件 2、使用wx.createSelectorQuery获取图片高宽位置信息 3、使用wx.pageScrollTo将页面滚动到图片底部对应页面的高度位置,注意 duration 默认为300ms,这里要设置duration为0,不然... js: touchstart: function (e) { wx.createSelectorQuery().select('.qrcodeImg').boundingClientRect(function (res) { wx.pageScrollTo({ scrollTop: res.top + res.height, duration:0, // 设置页面滚动所需的时间,这里设置为0 }) }).exec() } wxml: <image src="图片地址" bindtouchstart="touchstart" class="qrcodeImg" show-menu-by-longpress="{{true}}" mode="widthFix"></image> 最后猜测:长按识码,貌似是长按时,截取当前手机屏幕的图片进行识别的? 以下图片来自 问题《show-menu-by-longpress 在图片过长时,ios会识别不了小程序二维码》 第1张图,完整显示小程序码,可以识别 第2张图,小程序码显示不完整,无法识别 第3张图,移动vConsole到小程序码上,也无法识别 [图片][图片][图片] 代码片段:https://developers.weixin.qq.com/s/EGde0fm970h2
2020-05-10 - 小程序开发进阶之前端开发
4 节课,教你快速入门小程序前端。本系列视频,由腾讯课堂NEXT学院、微信学堂联合出品。
2021-12-14 - 小程序开发起步
学习 5 节课程,从 0 至 1 做第一个属于你的小程序,深入浅出了解小程序开发。本系列视频,由腾讯课堂 NEXT 学院、微信学堂联合出品。
2022-03-24 - 微信开放社区简介
微信开放社区,是一个为使用者提供交流、服务,以及专业学习的开放平台。 微信开放平台的社区交流专区,是小程序、小游戏、微信支付、企业微信等产品的官方交流平台。在这里,可以及时获取到官方资讯、最新的活动动态,便捷地搜索或提出在开发与运营过程中遇到的问题,更高效地获得官方解答与反馈。同时,还能阅读到丰富的文章和技术运营干货等。 服务平台,连接开发者、商家与服务商,犹如端的万能工具箱。平台内提供位置服务、内容安全、业务安全防御等多项接口能力、多类型资源包,帮助开发者快速接入小程序。后续还将开放第三方接口开发商接入,进一步丰富能力。 微信学院,是微信官方教育平台,面向微信生态从业者提供教育和帮助。在这里,可以了解到小程序最新产品能力、行业的最新玩法,以及获取到通用的开发、产品、运营等基础课程,还有不同行业的解决方案、优秀案例分享等。同时,在微信学院可进行服务商讲师认证,以鼓励优质服务商输出可复制的行业经验和创新玩法的课程,带动行业生态发展。另外,微信学院还提供了丰富的线下活动:开发者培训班、服务商workshop、开放社区沙龙、头部服务商交流等,满足不同人群的交流需求。 让我们一起,开放交流,共进创造! [视频]
2021-06-18 - 解决wx.previewImage不能对应显示(总显示第一张)
wxml页面代码: <view class="bg-img" wx:for="{{goodsDetail.goodsImgFileIDList}}" wx:key="index" > <image mode='aspectFill' src="{{item}}" data-index="{{index}}" bindtap="ViewImage" /> </view> js页面代码: //展示图片 ViewImage(e) { let index = e.currentTarget.dataset.index let imgAry = this.data.goodsDetail.goodsImgFileIDList //自己要显示图片的数组 wx.previewImage({ urls: imgAry, current: imgAry[index] }) },
2020-03-24 - 如何让previewImage支持索引
问题背景 若一个页面下有以下几张图片 [A, A, A, B] 通过 [代码]wx.previewImage[代码] 预览时,由于 [代码]current[代码] 只支持 [代码]string[代码] 类型,点击[代码]A[代码]图片时,不管是哪一张,都会被认为是第一张(即右滑时还是 [代码]A[代码] 图片) 要解决这个问题,必须使得相同 [代码]src[代码] 的图片能被区分开 方案一 主要思想 给图片链接后加一个没有用的参数使得图片链接各不相同 如上题中将链接变为 [A, A?id=1, A?id=2, B] 即可解决这个问题 缺陷 加上参数后每个图片都要被请求一次(不能被缓存),增加了请求次数 方案二 主要思想 在预览时删除重复的图片 如图片为 [A, B, A, C] ,则在预览第一张 [代码]A[代码] 图片时 [代码]urls[代码] 传入 [A, B, C],在预览第二张 [代码]A[代码] 图片时传入 [B, A, C] 缺陷 无法通过左右滑动看到重复的图片 若是开头的例子仍会出现不管点哪一张 [代码]A[代码] 图片都会是右滑后就是 [代码]B[代码] 图片,无法区分 方案三 主要思想 [代码]indexOf[代码] 是区分大小写的,而链接一般不区分大小写,因此可以把链接的一部分改为大写来区分 具体分析 [代码]wx.previewImage[代码] 只支持网络链接,网络链接的一般结构为 [代码][协议]://[域名]/[路径][代码] ,其中协议和域名是不区分大小写的,而路径则可能影响。因此可以将协议和域名的大小写重新组合实现链接的不同,但显示的是同一张图片,例如传入的 [代码]urls[代码] 为 [“http://example.com”, “http://example.com”] 可以修改为 [“http://example.com”, “Http://example.com”] 这样就可以区分开,通过协议+域名的大小写重新组合,一般可以提供足够多不同的 [代码]src[代码] (有 [代码]2^n[代码] 种) 优点 可以实现同方案一的效果,预览重复的图片不会定位错误 不更改图片链接,不会产生多余的请求 程序实现 [代码]function previewImage(object) { console.log("修改前:", object.urls); var urls = []; for (var i = 0; i < object.urls.length; i++) { var url = object.urls[i]; // 网络链接 if (/:\/\//.test(object.urls[i])) { // 有重复 while (urls.indexOf(url) != -1) { var j; url = ""; for (j = 0; j < object.urls[i].length; j++) { var c = object.urls[i][j]; // 这里直接用了random函数来确定大小写,因为协议+域名通常在10位以上,有大于1000种可能,重复概率低 if (/[a-zA-Z]/.test(c)) url += (Math.random() >= 0.5 ? c.toUpperCase() : c); else url += c; if (c == '/' && object.urls[i][j - 1] != '/' && object.urls[i][j + 1] != '/') break; } url += object.urls[i].substr(j + 1); //路径部分直接添加 } } urls.push(url); } console.log("修改后:", urls); wx.previewImage({ current: urls[object.current], urls, success: object.success, fail: object.fail }) } previewImage({ current: 1, urls: ["https://www.baidu.com/img/bd_logo1.png", "https://www.baidu.com/img/bd_logo1.png"] }) [代码] [图片] 这个函数可以实现 [代码]current[代码] 直接传入索引值,并且存在多张相同图片时,不会定位错误(如这个示例中设置了 [代码]current[代码] 为 [代码]1[代码],则预览时只能左滑查看第 [代码]0[代码] 张,无法右滑) ps: 如果只是在 [代码]wx.previewImage[代码] 中使用,协议和域名都不区分大小写,但是如果在 [代码]image[代码] 组件的 [代码]src[代码] 中使用,协议必须小写(否则无法显示),只能对域名进行修改
2019-10-31 - async await 异步转同步为什么没有实现?
async await 异步转同步为什么没有实现? 代码截图,预期是数据库操作执行完后 打印日志,但实际总是先打印日志,求指点,(其中数据库中的操作比较复杂,有3张表的联表查询,但这个应该和异步转同步没啥关系吧) [图片] [图片] 数据库的操作 [图片]
2020-04-01 - 小程序改造成async/await模式
补充:以下是原生用法: https://developers.weixin.qq.com/community/develop/article/doc/00028cbc2e04e0ddf549d535351c13 简单两步: 1、把这个文件下载并引用进来: https://github.com/facebook/regenerator/blob/master/packages/regenerator-runtime/runtime.js 2、在使用时声明一下: const regeneratorRuntime = require('./lib/runtime.js') 然后就可以使用async/await了。 补充如下:以上方案已经过期作废,小程序原生支持async/await了,(es6转es5别勾)
2020-04-01 - 怎么在api中调用自定义组件showModal,让自定义组件想wx.showModal一样使用
环境:我们为什么要在api中调用自定义组件的原因我就不说了,用得到的开发者自然用得到! 现在百度上有很多人都写了自定义组件showModal,但是有一个很致命的缺陷,不能像微信小程序的api那样使用(wx.showModal)。 话不多说上代码 css: .mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.4); z-index: 9999;} .modal-content { display: flex; flex-direction: column; width: 85%; padding: 10rpx; background-color: #fff; border-radius: 15rpx;} .title { font-size: 40rpx; text-align: center; padding: 15rpx;} .modal-btn-wrapper { display: flex; flex-direction: row; height: 100rpx; line-height: 100rpx; border-top: 2rpx solid rgba(7, 17, 27, 0.1);} .cancel-btn, .confirm-btn { flex: 1; height: 100rpx; line-height: 100rpx; text-align: center; font-size: 32rpx;} .cancel-btn { border-right: 2rpx solid rgba(7, 17, 27, 0.1);} .main-content { flex: 1; height: 100%; overflow-y: hidden;} wxml: <view class='mask' wx:if='{{show}}'> <view class='modal-content'> <view class="title">{{title}}</view> <view>{{content}}</view> <slot></slot> <view class='modal-btn-wrapper'> <view class='cancel-btn' bindtap='cancel' wx:if="{{showCancel}}" style="color:{{cancelColor}}">{{cancelText}}</view> <view class='confirm-btn' bindtap='confirm' style="color:{{confirmColor}}">{{confirmText}}</view> </view> </view></view> js:Component({ /** * 组件的属性列表 */ properties: { title: { type: String, value: '温馨提示' }, content: { type: String, value: '是否导入最近一次刷题记录?' }, //是否显示取消按钮 showCancel: { type: Boolean, value: true }, //取消按钮文字 cancelText: { type: String, value: '取消' }, //取消按钮颜色 cancelColor: { type: String, value: '#000000' }, //确定按钮的文字 confirmText: { type: String, value: '确定' }, //确定按钮的颜色 confirmColor: { type: String, value: '#FECC34' }, //是否显示modal show: { type: Boolean, value: false }, }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { // 取消函数 cancel() { this.setData({ show: false }) var res = {}; res["confirm"] = true; this.data.success && this.data.success(res); }, // 确认函数 confirm() { this.setData({ show: false }) var res = {}; res["confirm"] = false; this.data.success && this.data.success(res); }, showModal({ title, content, showCancel, //是否显示取消按钮 cancelText, //取消按钮文本 cancelColor, //取消按钮颜色 confirmText, //确定按钮文本 confirmColor, //确定按钮颜色 success }) { this.setData({ show: true }); if (title) { this.setData({ title: title }) } if (content) { this.setData({ content: content }) } if (showCancel) { this.setData({ showCancel: showCancel }) } if (cancelText) { this.setData({ cancelText: cancelText }) } if (cancelColor) { this.setData({ cancelColor: cancelColor }) } if (confirmText) { this.setData({ confirmText: confirmText }) } if (confirmColor) { this.setData({ confirmColor: confirmColor }) } this.data.success = success; } }})[图片] 以上是自定义组件的封装,因为CSS和html是随便百度复制的,太简单就不想改了,你们自己把样式改一下就OK 使用方法: [图片][图片] [图片] var toast = that.selectComponent('#toast'); toast.showModal({ title: '温馨提示', content: '是否导入最近一次刷题记录?', showCancel: true, confirmText: "导入", confirmColor: "#FECC34", success: function(result) { console.log(result) } });如果想自己的这个自定义组件在自己的挨批中使用就把对应页面的this传递到对应的api方法中去,然后在api中调用
2020-03-30 - 使用云开发接入阿里云短信SDK,实现自给自足!
发送手机短信验证码 按需求自行修改函数内容 1.前往阿里云申请短信服务 1.短信服务 > 国内消息 > 添加签名 [图片] 1.短信服务 > 国内消息 > 添加模板 [图片] 2.引入 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const Core = require('@alicloud/pop-core'); const accessKeyId = 'xxx' // 你的appid const accessKeySecret = 'xxx' // 你的secret const SignName = 'xxx' // 你的签名 const TemplateCode = 'xxx' // 你的模版CODE var client = new Core({ accessKeyId, accessKeySecret, endpoint: 'https://dysmsapi.aliyuncs.com', apiVersion: '2017-05-25' }) let params = { SignNameJson: JSON.stringify([SignName]), TemplateCode: TemplateCode, } cloud.init({ env: 'xxx' // 你的环境id }) // 云函数入口函数 /** * 发送模板消息 */ exports.main = async(event, context) => { let { OPENID, APPID, UNIONID } = cloud.getWXContext() const db = cloud.database() return new Promise(async(resolve, reject) => { try { if(!event.phone) throw {code: 7322, data: [],info: '手机不能为空!'} if(!/^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(event.phone)) throw {code: 7321, data: [],info: '手机号码格式错误!'} // 获取数据 let { data } = await db.collection('sms-record').where({ phone: event.phone, openid: OPENID, is_used: 1 }).orderBy('created_at', 'desc').skip(0).limit(1).get(), code = null // 计算时间 if(data.length != 0 && (Number(new Date()) - Number(new Date(data[0].created_at))) < 60000) { throw {code: 7323, data: [],info: '一分钟内,不能重复发送!'} } else if(data.length != 0 && (Number(new Date()) - Number(new Date(data[0].created_at))) < 1800000){ code = data[0].code } else { // 生成六位随机数 code = Math.floor(Math.random() * 900000) + 100000 } //发送短信 let { Code } = await client.request('SendBatchSms', Object.assign({ PhoneNumberJson: JSON.stringify([event.phone]), TemplateParamJson: JSON.stringify([{code}]) },params), { method: 'POST' }) if(Code !== 'OK') throw {code: 7321, data: [],info: '发送短信失败!'} // 新增数据 await db.collection('sms-record').add({ data: { phone: event.phone, code, openid: OPENID, is_used: 1, created_at: db.serverDate() } }) resolve({ code: 0, data: [], info: '操作成功!' }) } catch (error) { console.log(error) if(!error.code) reject(error) resolve(error) } }) } [代码] 3.参数 属性 类型 默认值 必填 说明 phone string 是 国内手机号码 4.使用 [代码] // 返回Promise wx.cloud.callFunction({ name: 'sendSms', data: { phone } }).then(res => { console.log(res) }) async sendSms(){ try { let { data } = await wx.cloud.callFunction({ name: 'sendSms', data: { phone } }) console.log(data) } catch (error) { console.log(error) } } [代码] 当然你也可以使用旧版的sdk 更多云函数模板 另外求个流量 和 star 模板使用云开发实现,接入百度AI平台API图像识别系统,无需另外搭建服务器,只需修改文件内配置项 一款方便快捷识别AI,可根据您拍摄或相册中照片识别出您所需要知道的物种(植物,动物,图文,菜品类型),相关知识,帮助您了解该物种,打开新世界! [图片]
2019-04-26 - 借助云开发轻松实现后台数据批量导出丨实战
小程序导出数据到excel表,借助云开发后台实现excel数据的保存 我们在开发小程序的过程中,可能会有这样的需求:如何将云数据库里的数据批量导出到excel表里? 这个需求可以用强大的云开发轻松实现! 这里需要用到云函数,云存储和云数据库。可以说通过这一个例子,把小程序云开发相关的知识都用到了。下面就来介绍如何实现 实现思路 1,创建云函数 2,在云函数里读取云数据库里的数据 3,安装node-xlsx类库(node类库) 4,把云数据库里读取到的数据存到excel里 5,把excel存到云存储里并返回对应的云文件地址 6,通过云文件地址下载excel文件 一、创建excel云函数 关于如何创建云开发小程序,这里我就不再做具体讲解。不知道怎么创建云开发小程序的同学,可以去翻看腾讯云云开发公众号内菜单【技术交流-视频教程】中的教学视频。 创建云函数时有两点需要注意的,给大家说下 1、一定要把app.js里的环境id换成你自己的 [图片] 2,你的云函数目录要选择你对应的云开发环境(通常这里默认选中的) 不过你这里的云开发环境要和你app.js里的保持一致 [图片] 二、读取云数据库里的数据 我们第一步创建好云函数以后,可以先在云函数里读取我们的云数据库里的数据。 1、先看下我们云数据库里的数据 [图片] 2、编写云函数,读取云数据库里的数据(一定要记得部署云函数) [图片] 3、成功读取到数据 [图片] 把读取user数据表的完整代码给大家贴出来。 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init({ env: "test-vsbkm" }) // 云函数入口函数 exports.main = async(event, context) => { return await cloud.database().collection('users').get(); } [代码] 三、安装生成excel文件的类库 node-xlsx 通过上面第二步可以看到我们已经成功的拿到需要保存到excel的源数据,我们接下来要做的就是把数据保存到excel 1、安装node-xlsx类库 [图片] 这一步需要我们事先安装node,因为我们要用到npm命令,通过命令行npm install node-xlsx[图片] 可以看出我们安装完成以后,多了一个package-lock.json的文件 [图片] 四、编写把数据保存到excel的代码, 下图是我们的核心代码: [图片] 这里的数据是我们查询的users表的数据,然后通过下面代码遍历数组,然后存入excel。这里需要注意我们的id,name,weixin要和users表里的对应。 [代码] for (let key in userdata) { let arr = []; arr.push(userdata[key].id); arr.push(userdata[key].name); arr.push(userdata[key].weixin); alldata.push(arr) } [代码] 还有下面这段代码,是把excel保存到云存储用的 [代码] //4,把excel文件保存到云存储里 return await cloud.uploadFile({ cloudPath: dataCVS, fileContent: buffer, //excel二进制文件 }) [代码] 下面把完整的excel里的index.js代码贴给大家,记得把云开发环境id换成你自己的。 [代码]const cloud = require('wx-server-sdk') //这里最好也初始化一下你的云开发环境 cloud.init({ env: "test-vsbkm" }) //操作excel用的类库 const xlsx = require('node-xlsx'); // 云函数入口函数 exports.main = async(event, context) => { try { let {userdata} = event //1,定义excel表格名 let dataCVS = 'test.xlsx' //2,定义存储数据的 let alldata = []; let row = ['id', '姓名', '微信号']; //表属性 alldata.push(row); for (let key in userdata) { let arr = []; arr.push(userdata[key].id); arr.push(userdata[key].name); arr.push(userdata[key].weixin); alldata.push(arr) } //3,把数据保存到excel里 var buffer = await xlsx.build([{ name: "mySheetName", data: alldata }]); //4,把excel文件保存到云存储里 return await cloud.uploadFile({ cloudPath: dataCVS, fileContent: buffer, //excel二进制文件 }) } catch (e) { console.error(e) return e } } [代码] 五、把excel存到云存储里并返回对应的云文件地址 经过上面的步骤,我们已经成功的把数据存到excel里,并把excel文件存到云存储里。可以看下效果。 [图片] 接着,就可以通过上图的下载地址下载excel文件了。 [图片] 其实到这里就差不多实现了基本的把数据保存到excel里的功能了,但是为了避免每次导出数据都需要去云开发后台下载excel的麻烦,接下来介绍如何动态获取下载地址。 六、获取云文件地址下载excel文件 [图片] 通过上图我们可以看出,我们获取下载链接需要用到一个fileID,而这个fileID在我们保存excel到云存储时,有返回,如下图。我们把fileID传给我们获取下载链接的方法即可。 [图片] 1、我们获取到了下载链接,接下来就要把下载链接显示到页面 [图片] 2、代码显示到页面以后,我们就要复制这个链接,方便用户粘贴到浏览器或者微信去下载。 [图片] 下面是完整代码: [代码]Page({ onLoad: function(options) { let that = this; //读取users表数据 wx.cloud.callFunction({ name: "getUsers", success(res) { console.log("读取成功", res.result.data) that.savaExcel(res.result.data) }, fail(res) { console.log("读取失败", res) } }) }, //把数据保存到excel里,并把excel保存到云存储 savaExcel(userdata) { let that = this wx.cloud.callFunction({ name: "excel", data: { userdata: userdata }, success(res) { console.log("保存成功", res) that.getFileUrl(res.result.fileID) }, fail(res) { console.log("保存失败", res) } }) }, //获取云存储文件下载地址,这个地址有效期一天 getFileUrl(fileID) { let that = this; wx.cloud.getTempFileURL({ fileList: [fileID], success: res => { // get temp file URL console.log("文件下载链接", res.fileList[0].tempFileURL) that.setData({ fileUrl: res.fileList[0].tempFileURL }) }, fail: err => { // handle error } }) }, //复制excel文件下载链接 copyFileUrl() { let that=this wx.setClipboardData({ data: that.data.fileUrl, success(res) { wx.getClipboardData({ success(res) { console.log("复制成功",res.data) // data } }) } }) } }) [代码] 梳理下上面代码的逻辑: 1、先通过getUsers云函数去云数据库获取数据。 2、把获取到的数据通过excel云函数把数据保存到excel,然后把excel保存的云存储。 3、获取云存储里的文件下载链接。 4、复制下载链接,到浏览器里下载excel文件。 到这里我们就完整的实现了把数据保存到excel的功能了。 文章有点长,知识点有点多,但是大家理解上述内容后,就可以对小程序云开发的云函数、云数据库、云存储有一个较为完整的了解过程。 如果你想要了解更多关于云开发CloudBase相关的技术故事/技术实战经验,请扫码关注【腾讯云云开发】公众号 ~ [图片]
2019-09-10 - 自定义navigationBar顶部导航栏,兼容适配所有机型(附完整案例)
前言 navigationBar相信大家都不陌生把?今天我们就来说说自定义navigationBar,把它改变成我们想要的样子(搜索框+胶囊、搜索框+返回按钮+胶囊等)。 思路 隐藏原生样式 获取胶囊按钮、状态栏相关数据以供后续计算 根据不同机型计算出该机型的导航栏高度,进行适配 编写新的导航栏 引用到页面 正文 一、隐藏原生的navigationBar window全局配置里有个参数:navigationStyle(导航栏样式),default=默认样式,custom=自定义样式。 [代码]"window": { "navigationStyle": "custom" } [代码] 让我们看看隐藏后的效果: [图片] 可以看到原生的navigationBar已经消失了,剩下孤零零的胶囊按钮,胶囊按钮是无法隐藏的。 二、准备工作 1.获取胶囊按钮的布局位置信息 我们用wx.getMenuButtonBoundingClientRect()【官方文档】获取胶囊按钮的布局位置信息,坐标信息以屏幕左上角为原点: [代码]const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); [代码] width height top right bottom left 宽度 高度 上边界坐标 右边界坐标 下边界坐标 左边界坐标 下面是官方给的示意图,方便大家理解几个坐标。 [图片] 2.获取系统信息 用wx.getSystemInfoSync()【官方文档】获取系统信息,里面有个参数:statusBarHeight(状态栏高度),是我们后面计算整个导航栏的高度需要用到的。 [代码]const systemInfo = wx.getSystemInfoSync(); [代码] 三、计算公式 我们先要知道导航栏高度是怎么组成的, 计算公式:导航栏高度 = 状态栏高度 + 44。 实例 【源码下载】 自定义导航栏会应用到多个、甚至全部页面,所以封装成组件,方便调用;下面是我写的一个简单例子: app.js [代码]App({ onLaunch: function(options) { const that = this; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); // 胶囊按钮位置信息 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 状态栏高度 + 44 that.globalData.navBarHeight = systemInfo.statusBarHeight + 44; that.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right; that.globalData.menuBotton = menuButtonInfo.top - systemInfo.statusBarHeight; that.globalData.menuHeight = menuButtonInfo.height; }, // 数据都是根据当前机型进行计算,这样的方式兼容大部分机器 globalData: { navBarHeight: 0, // 导航栏高度 menuRight: 0, // 胶囊距右方间距(方保持左、右间距一致) menuBotton: 0, // 胶囊距底部间距(保持底部间距一致) menuHeight: 0, // 胶囊高度(自定义内容可与胶囊高度保证一致) } }) [代码] app.json [代码]{ "pages": [ "pages/index/index" ], "window": { "navigationStyle": "custom" }, "sitemapLocation": "sitemap.json" } [代码] 下面为组件代码: /components/navigation-bar/navigation-bar.wxml [代码]<!-- 自定义顶部栏 --> <view class="nav-bar" style="height:{{navBarHeight}}px;"> <input class="search" placeholder="输入关键词!" style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{menuHeight}}px; left:{{menuRight}}px; bottom:{{menuBotton}}px;"></input> </view> <!-- 内容区域: 自定义顶部栏用的fixed定位,会遮盖到下面内容,注意设置好间距 --> <view class="content" style="margin-top:{{navBarHeight}}px;"></view> [代码] /components/navigation-bar/navigation-bar.json [代码]{ "component": true } [代码] /components/navigation-bar/navigation-bar.js [代码]const app = getApp() Component({ properties: { // defaultData(父页面传递的数据-就是引用组件的页面) defaultData: { type: Object, value: { title: "我是默认标题" }, observer: function(newVal, oldVal) {} } }, data: { navBarHeight: app.globalData.navBarHeight, menuRight: app.globalData.menuRight, menuBotton: app.globalData.menuBotton, menuHeight: app.globalData.menuHeight, }, attached: function() {}, methods: {} }) [代码] /components/navigation-bar/navigation-bar.wxss [代码].nav-bar{ position: fixed; width: 100%; top: 0; color: #fff; background: #000;} .nav-bar .search{ width: 60%; color: #333; font-size: 14px; background: #fff; position: absolute; border-radius: 50px; background: #ddd; padding-left: 14px;} [代码] 以下是调用页面的代码,也就是引用组件的页面: /pages/index/index.wxml [代码]<navigation-bar default-data="{{defaultData}}"></navigation-bar> [代码] /pages/index/index.json [代码]{ "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" } } [代码] /pages/index/index.js [代码]const app = getApp(); Page({ data: { // 组件参数设置,传递到组件 defaultData: { title: "我的主页", // 导航栏标题 } }, onLoad() { console.log(this.data.height) } }) [代码] 效果图: [图片] 好了,以上就是全部代码了,大家可以文中复制代码,也可以【下载源码】,直接到开发者工具里运行,记得appid用自己的或者测试哦! 下面附几张其它小程序的效果图,大家也可以尝试照着做: [图片][图片] 总结 本文写了自定义navigationBar的一些基础性东西,里面涉及组件用法、参数传递、导航栏相关。 由于测试环境有限,大家在使用时如果发现有什么问题,希望及时反馈,以供及时更新帮助更多的人! 大家有什么疑问,欢迎评论区留言!
2022-06-23 - 小程序准备做个钱包功能,能够充值,提现,支付小程序内下单服务的会影响审核吗?
你好, 产品打算在小程序内做一个钱包功能,能够用微信支付充值,提现到银行卡,支付小程序内的订单金额。小程序是做设备服务下单的,比如下一个安装一个广告屏幕的订单,维修一个电脑的订单,这算不算虚拟支付?会不会小程序影响审核通过? [图片]
2020-03-13 - 微信支付营销功能问券调查
为了后面更高效的让大家接入微信支付营销功能,我需要对大家平时遇到的问题进行讨论并且优化,辛苦大家抽出一分钟时间填写一下问券提出您们宝贵的意见 问券链接:https://wj.qq.com/s2/5242881/ea6d
2020-01-08 - 虚拟业务指南请收好。
在小程序生态中,基于苹果运营规范,小程序内暂不支持iOS端虚拟支付业务。为此小编为大家整理了一份虚拟支付业务指南,希望大家在做虚拟业务时有所帮助: [视频] 那么,到底什么是虚拟支付业务呢? 虚拟支付业务是指购买非实物商品。比如:VIP会员、充值、录制课程、录制音频视频等虚拟产品。目前iOS端暂不支持虚拟支付业务。 我们常见iOS虚拟支付的不合规示例有哪些呢? 示例一 :小程序内存在付费购买虚拟内容或道具。商品多体现为提前编辑好的、录制好的虚拟商品。如录制视频课程、游戏道具。 整改建议 :建议去除小程序内所有付费购买虚拟服务,并根据提示修改相关内容及文案,文案可参照“由于相关规范,iOS功能暂不可用”。 [图片] 示例二 :付费解锁优质服务。多体现为提供虚拟商品的小程序可通过支付购买、开通虚拟会员等形式,体验小程序付费服务。比如:支付阅读章节小说、同城生活服务平台付费发帖/付费置顶等。 整改建议 :建议可以关闭iOS端虚拟支付通道,并将【马上充值】更改为【由于相关规范,iOS功能暂不可用】,并不再提供iOS端会员服务。 [图片] 示例三 :关闭iOS端虚拟支付功能后,虚拟商品页面仍然保留货架价格标签展示、购买/付费/订阅等功能或按钮。 整改建议 :建议去除小程序中的虚拟商品的价格展示,并更改为【免费】;并将【订阅 ¥128】更改为【由于相关规范,iOS功能暂不可用】,并不再提供iOS端虚拟商品购买服务。 [图片] 示例四 :关闭iOS端虚拟支付功能后,提供引导用户前往其他支付的路径/文案,完成虚拟支付闭环。 整 改建议 :建议去除iOS端小程序内引导用户前往其他支付路径/文案,并不再提供iOS端虚拟商品购买服务。 [图片] 示例五 :小程序含需要付费的虚拟商品,并设置限时免费的服务,限时免费结束后需付费才能继续提供服务。 整改建议 :建议将iOS端小程序中所有虚拟付费内容更改为免费,并不再提供iOS端虚拟商品购买服务。 [图片] 示例六 :关闭iOS端虚拟支付功能后,小程序中虚拟产品页面不可以含有付费性质的关键字(如:购买、已购、付费、支付等),包括但不限于功能按钮、功能页面、支付提示及任何商品介绍等。 整改建议 :建议将小程序iOS端虚拟产品页面中的文案/按钮/功能tab含有限制的关键字更改为【免费】或删除。并不再提供iOS端虚拟商品购买服务。 [图片] 如小程序内存在以上不合规的虚拟支付内容,请开发者重视并及时整改。对于首次违规的小程序,平台将下发站内信整改通知,并给予三天整改时间,请开发者按照提示在限期内完成整改。平台将会对到期未完成整改的小程序进行搜索策略调整,并在小程序功能使用上进行一定的限制,直到小程序完成内容整改。
2020-04-23 - 社交红包类目用户购买红包的微信支付费率是多少?
我们想做一款社交红包小程序,想问下大家这种类目下,用户充值时,微信支付费率是多少?
2020-03-23 - 微信开放社区新手指引
欢迎您使用微信开放社区,您有微信支付方面的疑问,通过搜索找到全面的解决方案,如果没有找到,请发新帖描述您的问题。 第一步:先通过右上角的搜索; [图片] 第二步:查看列表是否有历史帖能解决您的疑问; 第三步:历史贴不能解决您的问题,请发新帖; 1、发新帖的时候请注意,如果您是普通开发者, 请选择【开发者专区】,如果您是服务商开发者,请选择【服务商专区】 2、选择发帖,需要您选择【问题求解】或【经验分享】,请特别注意:【经验分享】栏目用于您的经验分享给其他开发者不要在此栏目提问 3、微信支付官方有技术客服回复【问题求解】栏目的问题;
2019-06-24 - 请官方大大解答下我的几个疑问,关于局域网使用api的问题?
我开发的小程序是基于局域网使用的,但是微信的api接口时有问题出现,比如今天下午突然就ioswifi连接接口出现系统错误,, 既然小程序可以离线到本地使用,那我是不是可以选择某一版没有出现问题api进行上传,那用户还会遇到这样出错api的问题吗?这是第一个问题 第二个问题是,用户小程序缓存到本地后,遇到了错误api导致不能使用,过几天微信修复好了此漏洞,用户重新使用此小程序会恢复正常吗?还是说得删除此小程序重新加载?我怎么知道用户有没有出错,难道每个用户都提前通知下?有问题了删除重来
2020-03-23 - 多张图片上传(源码分享+实现分析)
本篇文章以小程序中的代表【微信小程序】为例,分享一下在微信小程序中实现多图上传的源码实现。 代码片段(可导入微信WEB开发者工具体验):https://developers.weixin.qq.com/s/DHrt69mk7af3 两种不同实现方法的优缺点,请查看我的 博客原创文章,在文章中有详细的说明 小程序 多张图片上传 文章地址:https://blog.csdn.net/u013350495/article/details/104326088。 源码: const app = getApp() Page({ data: { // 已选择上传的本地图片地址 urlArr:['helang.jpg','1846492969.jpg','web.jpg'] }, onLoad: function () { }, // 多图上传-回调式 uploadCallback(){ let index = 0; // 当前位置,标识已上传到第几张图片 let newUrls = []; // 上传成功后的图片地址数组 // 图片上传方法 let upload = ()=>{ let nowUrl = this.data.urlArr[index]; //当前待上传的图片地址 wx.showLoading({ title: '正在上传', }); /* 无图片上传接口,收setTimeout 模拟延迟状态 项目中替换为 wx.uploadFile 即可 */ // 假设每 1000ms 上传一张图片 setTimeout(()=>{ // 此处为已上传成功后的回调函数内容 let resUrl = `服务器返回上传后的地址 ${nowUrl}`; //假设这是上传成功后返回的地址 newUrls.push(resUrl); // 将上传后的地址添加到成功数组中 // 判断图片是否已经全部上传完成 if (index >= (this.data.urlArr.length-1)){ send(); }else{ //未全部上传完时标识位置+1并再次调用上传方法 index++; upload(); } },1000); } // 发送方法,用作图片上传完后,得到图片地址提交给其它接口或其它操作 let send = () => { // 关闭加载提示 wx.hideLoading(); wx.showToast({ title: '上传成功', icon:'success' }) // 输出已经上传完的图片地址,请查看控制台结果 console.log(newUrls); } // 调用上传方法 upload(); }, // 多图上传-Promise uploadPromise(){ /* Promise 对象数组 */ let p_arr = []; /* 新建 Promise 方法,nowUrl参数为当前上传的图片地址 */ let new_p = (nowUrl) => { return new Promise((resolve, reject) => { /* 无图片上传接口,收setTimeout 模拟延迟状态 项目中替换为 wx.uploadFile 即可 */ // 假设每 1000ms 上传一张图片 setTimeout(()=>{ // 此处为已上传成功后的回调函数内容 let resUrl = `服务器返回上传后的地址 ${nowUrl}`; //假设这是上传成功后返回的地址 resolve(resUrl); },1000); }) } // 遍历数据,创建相应的 Promise 数组数据 this.data.urlArr.forEach((item, index) => { let nowUrl = this.data.urlArr[index]; //当前待上传的图片地址 p_arr.push(new_p(nowUrl)); }); wx.showLoading({ title: '正在上传', }); /* 所有图片上传完成后调用该方法 */ Promise.all(p_arr).then((res) => { // 关闭加载提示 wx.hideLoading(); wx.showToast({ title: '上传成功', icon: 'success' }) // 输出已经上传完的图片地址,请查看控制台结果 console.log(res); }); } })
2020-02-15 - 想做红包小程序?这篇你一定要看看
如果你想在平台内做红包小程序,这些问题你一定需要了解。 [视频] 一、相关红包小程序可能需要选择社交红包类目。 1、哪些红包内容才需要设置社交红包类目呢? 这个问题小编早有准备,详情可参考:什么小程序需要社交红包类目? 2、社交红包类目具体申请流程如下: (1) 小程序涉及红包内容需选择社交红包类目,所需资质为:《增值电信业务经营许可证》,即ICP证。 (2) 在申请通过社交红包类目后,需按站内信指引开通安全红包商户号 (3) 商户号开通成功后并绑定小程序主体,再提交代码审核。 二、社交红包小程序有哪些内容是不能做的? 1、社交红包小程序内暂不支持红包广场业务场景。即社交红包小程序的红包发放与领取仅限在群和好友中进行,禁止在无好友或群关系的场景传播,如基于LBS的红包广场。 违规示例: [图片] 2、红包类活动暂不支持朋友圈传播。 违规示例: [图片] 3、社交红包小程序暂不支持“回赏”类红包玩法。即A发一个红包给任何用户,任何用户需回包一个红包给A,才能拆开A的红包。 违规示例: [图片] 4、个人主体暂不支持社交红包。 违规示例: [图片] 5、社交红包暂不支持单笔交易支付金额突破商户号额度(当前商户号额度为204元)。 违规示例: [图片] 三、社交红包小程序暂不支持自定义功能红包? 若小程序内涉及向用户提供含自定义功能的红包服务,表现形式包括但不限于:文本/图片/音频/视频等形式,请开发者接入微信公众平台内容安全API(imgSecCheck、msgSecCheck、mediaCheckAsync)能力,同时做好人工审核相关的内容过滤机制,校验用户输入的文本/图片/音频/视频等内容,拦截政治敏感、色情违法违规等相关信息内容,保证用户上传的内容安全,并及时处理违规内容/账户,降低被恶意利用导致传播恶意内容的风险。一经发现将根据违规程度对该小程序采取限制功能直至封号处理。 违规示例: [图片]
2020-04-23 - 「小程序·云开发」支持云调用等功能更新
各位开发者: 大家好 为了方便开发者进行功能开发并提高开发效率,「小程序·云开发」新增云调用和本地调试功能,并对云开发控制台进行了优化。开发者可通过下载最新 RC Build 版的开发者工具进行功能体验。 云调用 云调用 是云开发提供的基于云函数使用小程序开放接口的能力,目前覆盖服务端调用的场景,后续将会陆续开放开放数据调用、消息推送、支付等其他多种使用场景。 云调用需要在云函数中通过 wx-server-sdk 使用。在云函数中调用服务端接口不再需要换取 access_token,只要是在从小程序端触发的云函数中发起的云调用都经过微信自动鉴权,可直接在 SDK 中发起调用。目前支持云调用的服务端开放接口列表可在服务端接口列表页查看。 [图片] 云函数本地调试 IDE 新增了云函数本地调试功能,方便开发者在本地进行云函数调试。云函数开发可以不再需要频繁上传测试,只需用 IDE 在本地调试完成再上传到现网验证。开发者可在云函数根目录右键选择本地调试来开启本地调试界面。 详细内容可参考文档。 [图片] 全新云开发控制台 云开发控制台经过全新设计和改版,优化交互和视觉体验,功能分类更加清晰、各项功能更加易用。 [图片]
2019-04-16 - 图文版 “如何创建一个微信小程序”
如何创建一个微信小程序,这里有个图文版请收下: 1、登陆微信公众平台; 2、选择「立即注册」; 3、选择「小程序」; 4、填写邮箱,按提示在邮箱进行验证; 5、按提示选择主体类型进行注册。 就是这么简单,你学会了吗?注册一个微信小程序,仅仅只需要5分钟 [图片] [图片][图片][图片]
2020-03-10 - 点击客服,你可能要发送的小程序,截图问号?
https://developers.weixin.qq.com/community/develop/doc/0004009335c400833409f15bb56c00 您好我按文档添加了点击客服提示发送小程序功能,但是截图是个感叹号发出去后是有图的 [图片] 微信版本:7.0.7,手机:荣耀, 部分华为手机出现,截图加载中的图标,类似下图 [图片]
2019-10-29 - wxParser 插件更新,让你在小程序中更快速更完美部署富文本
作者:知晓云 - 小程序开发快人一步 [图片] wxParser 小程序插件时隔一年终于更新了。这次更新主要针对在 gitthub 上同学们提出的 issue 进行一些特性的更新,更新后的 wxParser 将支持更多可配置项,让富文本更完美地在小程序端显示。 新特性:image-lazy-load 属性,支持给所有图片设置懒加载;image-preview 属性设置图片是否支持点击放大,默认 true,点击放大;bindImgLoad 事件,监听小程序 image 标签的 bindload ,即图片加载完成的事件;组件生命周期的 ready, attached, detached 的监听事件,通 bind:ready,bind:attached, bind:detached 进行监听;支持外挂 class,可以在 wxss 文件给富文本内容添加自定义样式。插件能做什么?「wxParser」小程序插件由知晓云团队发布,经过对微信小程序富文本渲染引擎 wxParser 进行一层封装,解决了使用起来太过麻烦的问题。 使用「wxParser」插件并配合富文本编辑器,可以很方便地开发内容展示类小程序,使用知晓云富文本编辑器效果更佳。例如「知晓课堂」小程序中的微信小程序开发课程便是使用「wxParser」插件配合知晓云内容库完成的。 [图片] 以对在知晓云编写的富文本内容进行解析为例,进入到知晓云控制台后,点击左侧「内容」菜单项,进入内容库管理面板,添加内容,即可看见富文本编辑器。编辑的内容(左)即经过「wxParser」解析后的样式(右)如下: [图片][图片] [图片] 当然,并非一定要使用知晓云的内容库才能使用「wxParser」,例如你可以使用百度的 UEditor 富文本编辑器编写你的内容,然后将生成的 HTML 配置到「wxParser」即可。 如何接入「wxParser」插件?在小程序中使用「wxParser」,你需要在项目中引入「wxParser」的 JS 库,同时,需要在相应的 WXML、WXSS 和 JS 文件中引入「wxParser」的模板、样式文件和编写初始化代码,少了任何一步,程序都不能正常工作。 而在使用「wxParser」小程序插件后,不再需要引入「wxParser」JS 库了,你可以像使用普通组件一样使用「wxParser」,只需要对组件的属性进行配置即可,省去了引入多个库文件的操作。 1. 申请使用插件在「小程序管理后台 - 设置 - 第三方设置 - 插件管理」中查找插件名称「wxParser」(appid: wx9d4d4ffa781ff3ac),并申请使用。 [图片] 2. 引入插件代码version 表示目前插件版本为 0.3.0,provider 为该插件的 AppID,而 wxparserPlugin 为自定义的插件名称。 "plugins": { "wxparserPlugin": { "version": "0.3.0", "provider": "wx9d4d4ffa781ff3ac" } } 3. 在需要使用到该插件的小程序页面的 JSON 配置文件中,做如下配置: "usingComponents": { "wxparser": "plugin://wxparserPlugin/wxparser" } } 4. 设置你的富文本内容,定义为 richText:Page({ data: { richText: '<h1>Hello world!</h1>' } }) 最后在需要展示富文本内容的地方,使用「wxParser」组件,为 rich-text 属性赋值上你的富文本内容即可。 <wxparser rich-text="{{richText}}" /> 你是否正好有内容展示类的小程序,亦或准备开发一个?查看开发文档,立即体验「wxParser」小程序插件。 扫描下方二维码,体验文章所提及的「知晓课堂」小程序。并带你遨游在小程序开发的知识海洋中。 [图片]
2020-03-06 - 学习笔记-个人博客小程序1
昨晚听了犀牛鸟大咖白老师和娇老师的视频授课,闲来无事,希望接下来不在断断续续学习小程序,而是静下心来用心学。发现娇老师总结的三点不错:1、要多看书;2、要总结写学习笔记;3、有一个好的导师很重要;前两者相信大家都能做到,第三者就随缘了。 一直有关注:追风少年分享的个人博客小程序,自己刚好也想写个博客小程序,他也无私地把自己的代码分享出来了:https://github.com/husanfeng/hsf_blog.git;已给他留言,解读他的源码 应该不会被投诉吧?希望能够得到他的理解和支持! 我的目的很简单,希望对自己感兴趣的小程序源码的学习,快速提高自己的技能~ 下载好源码后,话不多说了,我们开始吧~ [图片] 我的天呐,小程序端就有21个页面,一看到就头大; 我们先看看tabBar: [图片] 四个关键的页面,分别是:pages/home/home、pages/special/special、pages/ranking/ranking、pages/me/me; 先从pages/home/home开始:[图片] 这里用view/scroll-view/swiper组件,然后引用了article-list-compent组件; compent引用方法在json中引进:[图片] 好的,下面我们结合小程序界面来研究下page/home/home里面的代码: 下面看两个界面,第一个是作者的,第二个是我们下载代码后的(我想应该是我们取不到数据,所以界面不一样,只是个框架): [图片][图片] 这个首页,我们从上往下看,貌似挺简单,上门一个轮播图,下面是作者发的博客文章标题,但是包括发布时间、评论、浏览和点赞的数量; 我们先看看这个轮播图的代码: [图片] [图片] indicator-dots"{{indicatorDots}}"(是否显示面板指示点,是个变量) indicator-active-color"#1296db"(当前选中的指示点颜色:蓝色) circular"true" (采用衔接滑动) autoplay"{{autoplay}}"(自动切换,变量) interval"{{interval}}" (自动切换时间间隔:变量)duration"{{duration}}(滑动动画时长:变量); block组件循环变量:imgUrls;swiper-item:item.url;这个swiper组件有7个变量,我们进入home.js看看这些变量:[图片] 有四个变量是设好固定数值的,我们重点看看imgUrls是个数组类型,通过查找发现imgUrls出现两处,另外一处是:[图片] 那我们看看这个initSwiper函数的关键代码:db.collection('top_images').orderBy("_id", 'asc').get({ success: function(res) { // res.data 包含该记录的数据 console.log(res.data) _this.setData({ imgUrls: res.data }) }, 查文档说明collection.orderBy的用法:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.orderBy.html[图片] [图片] 根据文档,我对这段代码的理解是:通过云开发调用作者数据库名为“top_images”的图片,排序的文件名字段为_id,order排列顺序为:asc?asc是字母越大越靠前? 再看看这个initSwiper函数在哪里开始执行: [图片] 饿了吃饭先,下午慢慢解析,理解的不对,欢迎指正 不要喷 Thanks♪(・ω・)ノ
2020-03-07 - 微信小程序class封装http
config.js [代码]var config = { base_api_url:"https://*****/" } export {config} [代码] utils/http.js [代码]import {config} from "../config"; class HTTP{ request(params) { if (!params.method) { params.method = "GET" } wx.request({ url: config.base_api_url + params.url, data: params.data, method:params.method, header: { 'Content-Type': 'json' }, success: function (res) { let statusCode = res.statusCode.toString(); if(statusCode.startsWith("2")){ params.success(res.data); }else{ wx.showToast({ title:"网络错误", icon:"none" }) } }, fail: function() { wx.showToast({ title:"错误", icon:"none" }) } }) } } export{ HTTP } [代码] models/movie.js [代码]import { HTTP } from "../utils/http"; const movie = "movie/"; class MovieModel extends HTTP { getData(callback) { this.request({ url: movie + "getData", success: res => { callback(res); } }) } } export { MovieModel } [代码] index.js 引用 [代码]import {MovieModel} from "../../models/movie" var movie = new MovieModel(); [代码]
2020-03-13 - 如何设置消息自动回复?
设置方法:通过在微信公众平台->功能->自动回复->消息自动回复,可设置的文字/语音/图片/视频为用户消息回复。 在微信公众平台设置消息自动回复后,会在粉丝给您平台发送微信消息时,平台会自动回复您设置的文字/语音/图片/视频给粉丝。 温馨提示: 1)消息自动回复:1个小时内回复1-2条内容; 2)暂不支持设置图文、网页地址消息回复; 3)消息自动回复只能设置一条信息回复。 [图片] 粉丝手机微信中展示效果: [图片]
2020-01-08 - 【必收】精心整理!小程序开发资源汇总(附带源码)
很多小伙伴想在春节放假期间学小程序,但是小程序学习的资源和教程可能不太好找。所以小助手精心整理了一期,全是干货!认真学,开启美妙的小程序开发之旅,做一个属于自己的微信小程序。有需要的小伙伴收藏好这期文章哦~ 本文收集整理了微信小程序开发资源,包括官方文档,云开发训练营文档,视频教程以及实战源码推荐,会不间断更新。。 欢迎添加云开发小助手CloudBase微信:Tcloudedu1 ,一起加入技术交流群~ 小程序云开发官方公众号 [图片] 目录 官方文档 云开发训练营 视频教程 小程序·云开发Demo 技术交流群 官方文档 小程序开发者工具 小程序设计指南 小程序开发教程 小程序框架 小程序组件 小程序API 小程序开发者工具 小程序云开发文档 云开发训练营 小程序开发入门 小程序与JavaScript 云开发快速入门 [图片] 视频教程 腾讯云云开发B站:https://space.bilibili.com/447496276 [图片] 小程序·云开发Demo 技术博客小程序 包括文章的发布及浏览、评论、点赞、浏览历史、分类、排行榜、分享、生成海报图等。 网盘小程序 兼具文件存储与分享功能的专属网盘小程序。 教务助手小程序 用完即走,查个成绩和课表,无需下载app或去翻看公众号内的历史内容。 功能日历小程序 既能查看日历又能备注事项,看云开发如何支持功能性日历小程序的快速开发。 客户业务需求收集小程序 用云开发快速制作客户业务需求收集小程序,教你用云开发实现小程序版“朋友圈”的发布与展示。 小程序朋友圈 把朋友圈装进小程序需要几步?借助云开发实现小程序朋友圈的发布与展示。 南苑导览 一款由学生独立开发的以地图为载体,提供中山大学南方学院具体地点的位置信息、导航、校园历史及文化介绍的小程序。 互动打卡小程序 用云开发轻松构建精美互动打卡小程序,交互式双人打卡,快乐加倍。 个性头像小程序 别再@官方啦!云开发教你轻松制作个性头像小程序,趣味挂件、个性icon。 二手书商城小程序 云开发轻松制作二手书交易商城小程序,让智慧延续,让温暖传递。 后台数据批量导出 小程序开发过程中如何将云数据库中的数据批量导出至excel。 发送邮件 初学者福音,手把手教你用小程序云开发实现邮件发送功能。 高考查分小程序 实现高考分数轻松查,小程序源码。 mini论坛 仅需两天轻松搭建mini论坛小程序。 运动圈小程序 打造运动圈小程序(以乒乓球为例),实现球友间高效互动。 心情日记小程序 我能想到最浪漫的事,可能就是“你的心事我全知晓”。 最美恋爱小程序 小程序前端用的是taro框架写的,后台用的云开发。教你用云开发为心爱的人做个小程。 校园约拍小程序 校园场景下,小程序·云开发大显身手,校园约拍小程序源码。 体重记录小程序 只想记录每日体重还得下个APP,不用那么麻烦!用云开发做个专属体重记录小程序,看看你每天瘦了多少。 口袋工具 口袋工具之历史上的今天。一个基于云开发的小程序,看看历史上的今天都发生了啥。 迷你微博 独立做个精简版微博出来让你刷刷刷吗?而且,它还兼具搜索、点赞、主页的功能 多媒体小程序 使用小程序·云开发构建多媒体小程序。 技术交流群 交流技术为主,开发学习工作中遇到问题可以在群内交流,欢迎有需要的朋友加群。 添加小助手微信(Tcloudedu1),回复“技术群”,即可加入云开发技术群。 最后 如果你有关于使用腾讯云云开发相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们~ 关注腾讯云云开发,后台回复【源码】,获取更多微信小程序云开发实战源码。 [图片] [图片] [图片] 关注「腾讯云云开发」,后台回复【 源码 】,获取更多微信小程序云开发实战源码。 持续更新中… [图片]
2020-01-16 - 小程序·云开发——正在悄悄改变小程序开发的模式
作者:Bestony 小程序·云开发已经上线数月有余,但是,不少开发者仍对小程序·云开发有疑虑,不知道自己适不适合用云开发,能不能用云开发,应不应该用云开发。 在此,你将再一次认识小程序·云开发。 什么是云开发? 小程序·云开发是微信团队联合腾讯云提供的 Serverless 无服务开发服务,帮助开发者快速构建适用于小程序的云端数据库、云端存储、云端计算。 云开发为什么改变小程序开发的模式 相比于传统的云计算架构 IaaS、PaaS、SaaS,小程序·云开发所代表的 Serverless 服务提供了更多适用于小程序开发者能力的封装。 [图片] 相比于自建后台,云开发的优势是什么? 从商业角度来看,使用小程序云开发以后: 快速上线项目,快速试错:对于一个初创项目来说,快速上线是极为必要的。小程序·云开发可以帮助你在最短时间上线应用,完成快速试错。 专注核心业务,放弃非核心逻辑:使用云开发以后,你只需编写最重要的“核心代码”,不再需要关心周边组件,极大地降低了服务架构搭建的复杂性,成本更低。 从技术角度来看,使用小程序·云开发以后: 你可以独自完成一个小程序的设计、开发、发布:在传统的开发模式下,你需要一个后端开发者来配合你完成整个小程序的开发。在小程序·云开发中,你只需要借助云开发提供的丰富的 API ,就可以实现数据的存储、文件的上传、结果的计算,大大的提升了工作的效率。 你无需学习一门新的语言:小程序·云开发目前支持 Node.js ,和进行小程序开发时使用的 JavaScript 同出一门,你可以以更低的学习成本来完成小程序的开发。 你无需关注系统运维:当应用上线后,运维就成为了一个大的问题,当海量流量来袭时,如何快速调整系统容量,确保业务的稳步运行就成为了一个问题。当你使用云开发后,云开发将为你接管运维层面的事务,让你更加关注应用本身。 对于一些需要快速实践、快速成长的项目来说,云开发再合适不过了。 云开发如何处理海量请求? [图片] 在传统的单体开发模式中,应用需要以应用、站点为单位进行伸缩,因为我们的开发是基于整个应用、整个站点进行开发,无法单独对某一个特定的功能进行伸缩。 后续,兴起了微服务模式,我们可以将一个服务拆分成为多个不同的服务,可以基于服务进行伸缩,大大提升了伸缩的效率和资源的利用率。但是,这样的伸缩力度依然比较大。 而云开发所采用的 Serverless 方案中的运算部分,是交给云函数来进行处理的,你的应用由一个个函数组成的,因此,在弹性伸缩方面,粒度进一步细化,针对特定功能的函数来进行伸缩,弹性效率更高,能够承载的请求量更大。 云开发如何保障用户的数据安全 相比于传统的自建的数据库,云开发在数据安全方面有其自己的优势。 云开发的数据库运维团队集结了腾讯的专业的 DBA 和安全人士,时刻保障数据库的安全,及时对数据库安全、数据库性能进行调校,确保数据的安全。 同时,云开发数据库还支持双机热备,多种故障检测机制,主机服务故障后,服务会秒级自动切换到备机,无感完成主备机切换。 小程序·云开发学习路线及资料 如果你想要学习并使用小程序云开发,可以跟着下述的路线,来进行小程序的学习和开发。 第一步:通读小程序的官方文档,确保你能够明确的知晓小程序应该如何开发。 第二步:通读小程序云开发的官方文档,了解小程序云开发的基本工作模式、各组件之间的关系。 第三步:学习小程序·云开发系列教程,可以了解许多基础的小程序功能如何用云开发实现。 第四步:跟随实战项目学习、了解小程序云开发的开发流程和套路。 第五步:学习课程「小程序·云开发中级课程」系列,掌握小程序云开发的各项高级用途。 在学习时,你可以先学习云数据库中级课程,再学习云存储中级课程,最后学习云函数中级课程。 延展阅读 IaaS:Infrastructure as a service,基础设施即服务 PaaS:Platform as a service,平台即服务 SaaS:Software as a service,软件即服务 4.小程序·云开发系列课程 https://cloud.tencent.com/developer/team/tcb/courses
2019-02-15 - 云开发新能力,支持 HTTP 调用 API
今天来上班打开电脑,总感觉微信开发文档哪里有点不太一样,研究了半天原来是云开发又多了神级功能——HTTP API! HTTP API是什么?简单来说就是通过云开发HTTP API,可以不需要通过微信小程序或云开发控制台,就能够管理云开发能力。 技能一 花式触发云函数 在此之前,云函数只能通过微信小程序、定时触发器或其他云函数触发。借助HTTP API,可以在微信小程序环境外随时触发云函数啦! 但是,需要注意的是:HTTP API 途径触发云函数不包含用户信息。 请求地址 [代码]POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME [代码] 请求参数 [图片] Tips 1.使用本API触发云函数,在云函数中无法获取OpenID等用户相关信息,无法使用涉及用户登录态的其他API。 2.注意 POST BODY 部分会传递给云函数作为输入参数。 3.由 HTTP API 触发的云函数可以使用云调用。 4.由 HTTP API 触发云函数的超时时间为5s,请注意云函数的执行时间不能过长。 技能二 数据库导入导出 近期有很多小伙伴问我们,为什么数据只能通过云开发控制台手动导出?太麻烦啦!这不,在开发哥哥的不懈努力下,支持通过API导入导出数据啦! 数据库导入 请求地址 [代码]POST https://api.weixin.qq.com/tcb/databasemigrateimport?access_token=ACCESS_TOKEN [代码] 请求参数 [图片] 数据库导出 请求地址 POST https : //api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN 请求参数 [图片] 技能三 管理云存储文件 现在可以便捷地在小程序前端快速实现文件上传/下载及管理功能,同时也可以在开发者工具「云开发」控制台内进行管理。 获取文件上传链接 请求地址 [代码]POST https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN [代码] 请求参数 [图片] 获取文件下载链接 请求地址 [代码]POST https://api.weixin.qq.com/tcb/batchdownloadfile?access_token=ACCESS_TOKEN [代码] 请求参数 [图片] 删除文件 请求地址 [代码]POST https://api.weixin.qq.com/tcb/batchdeletefile?access_token=ACCESS_TOKEN [代码] 如要详细了解,点击"https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/"查阅《小程序·云开发文档》 [图片] 如果你有关于使用云开发CloudBase相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们哦~比心!
2019-07-24 - 一场比赛,一窥小程序的有限与无限
本文参加2019年「大赛文章征集」征文活动 大家好,我是华南赛区二等奖「Resser 阅见」小程序的开发团队DeveSA的队长。继上一篇高校微信小程序大赛经验分享杂谈流水账般介绍小程序的开发流程,这次我们来谈谈更Deep一点的——小程序的有限与无限。 「有限」和「无限」乍一看是不是很玄乎?让我稍稍剧透一下,这里的「有限」指的是小程序开发和运营上受到的限制,而「无限」则是小程序与生俱来的裂变能力和发展前景。本文的目的不只是简单地罗列这些Pros&Cons,也会根据在比赛中汲取到的经验分享给大家如何越过这些Cons并利用好Pros。 技术层面 wx.request 我相信每一个开发过小程序的developer,都记得大明湖畔的[代码]wx.request[代码]。曾几何时,比赛群里都是大家讨论如何在ddl前搞好备案,又或是ddl前一天匆匆忙忙设置好调试模式。 [代码]wx.request[代码]是微信提供的网络请求接口,限制诸多,有: 只支持HTTPS协议 不能使用IP地址或localhost 不支持8080以外端口 请求的域名必须经过ICP备案 用来鉴权的[代码]api.weixin.qq.com[代码]不能配置为服务器域名 Header中不能设置User-Agent 20个域名限制 而我们开发的「阅见」本质上是RSS阅读器,请求的feed链接都是用户自定义的外链,这些外链有HTTP的,也有没有备案的,数量更是无限的。这不就完美和[代码]wx.request[代码]的限制撞车了吗? 在这些限制下,很多同学第一反应是匆忙给自己的服务器上证书、上备案,却也不免遇上使用的第三方开放API(如[代码]http://api.github.com[代码])没上备案的问题。这就引出了更高阶的解决方法——在自己已备案的域名上设置[代码]tracker[代码]域名转发。然而,还有一种更优雅的方法——云函数。 也就是,原本从小程序到目标服务器的过程(上图)中间加入了云函数(下图)。 [图片] [图片] 借助于云函数与小程序得天独厚的血缘关系,小程序配置和调用云函数都十分方便。 云函数使用原生的[代码]request[代码]对传入的网址进行请求: [代码]const request = require('request'); exports.main = (event, context) => { return new Promise((resolve, reject) => { request(event.options, (error, res, body) => { if (error) return reject(error); resolve(res); }) }); } [代码] 小程序端则将原本的[代码]wx.request[代码]封装一下,代码有点长,只给个思路: [代码]wx.vrequest = function (options) { //将options转发到params,过程省略 return new Promise((RES, REJ) => { wx.cloud.callFunction({ //请求云函数 name: 'v-request', data: { options: params }, success: res => { const { result } = res; options.success && options.success(result); RES(result); }, fail: err => { //错误回调 REJ(err); }, complete: options.complete }) }) } [代码] 具体部署可以参考Github上guren-cloud/v-request WXML与富文本解析 解决了[代码]wx.request[代码]的问题,「阅见」遇到的第二个问题就是很多网站不支持RSS全文输出,因此我们计划让「阅见」能得到文章原文的HTML内容输出,再经过富文本渲染,呈现在小程序上。 小程序采用WXML,微信设计的一套标签语言,然而,网页上的富文本都是通过HTML呈现出来的,这样的话,当小程序想显示网页内容时,只能调用[代码]web-view[代码]了,而[代码]web-view[代码]不支持个人类型的小程序…… Butttt,多亏了小程序良好的生态环境,有很多开发者为小程序编写插件和库,其中,有关富文本渲染的就有wxParse和Towxml。其中wxParse已经两年没有更新了,虽然也有各路大神fork改良,但由于RSS获取到的文章千差万别,支持性不是很好(比如代码显示以及[代码]ruby[代码]标签等)。这里我强烈推荐Towxml,除了这个渲染库比较新之外,Towxml还有许多优良特性,比如,官方号称的“极致的中文排版优化”。 Thanks to Towxml,在「阅见」中,用户可以看到排版优良的订阅内容,甚至还能看视频哦。 [图片] 除了Towxml,「阅见」还借助了URL2Article提供的API提取正文内容,其能精准识别网页的正文部分,提取的内容不含广告导航等非正文内容。 [图片] 资源占用 说起小程序,你能想到最合适的形容词是什么?对我来说,就是「小」,这个「小」不只提现在小程序「触手可及、用完即走」的轻量,还有对开发者实在的限制——打包体积不能超过2M,本地缓存上限10M。 [图片] 这对于在大内存时代动辄上百兆代码的coder们来说,无疑是极大的限制。Butttt,在实际编写小程序时,只要有良好的编程习惯,这2M和10M是完全够用的,因为代码包经过了GZIP压缩后压缩率可以达到80%。 话虽如此,在「阅见」中,主要操作都是在小程序端完成的,所以相应的逻辑代码很多,在开发后期我们也的确遇到了代码包超过2M的情况。下面给一些减小代码包的体积的Tips: 小程序中只保存图标需要的图片文件,除tabbar外图标都使用[代码]·svg[代码]矢量格式,不仅体积相比[代码].png[代码]等传统点阵格式小了很多,而且也有矢量图永远高清的优点。 其他静态图片保存在服务器上或托管在图床上,七牛云就是个不错的选择,每个月还有免费的10G CDN流量,访问速度比云开发中的云储存快了不少。 删除测试使用的[代码]console.log()[代码]语句 压缩CSS代码 如果使用了第三方UI框架库,可以删除没有使用到的组件 如果以上Tips还不能解决代码包体积超过限制的问题,可以使用分包。 大部分小程序都会由某几个功能组成,通常这几个功能之间是独立的,但会依赖一些公共的逻辑,并且这些功能通常会对应某几个独立的页面。那么小程序代码的打包,大可不必一定要打成一个,可以按照功能的划分,拆分成几个分包,当需要用到某个功能时,才加载这个功能对应的分包。 使用分包方式加载小程序,能使小程序承载更多的功能与服务;而对用户而言,可以获得更快的加载速度,同时在不影响启动速度前提下使用更多功能。 目前小程序分包大小有以下限制: 整个小程序所有分包大小不超过 8M 单个分包/主包大小不能超过 2M 除了代码包2M的限制外,别忘了还有10M的缓存限制。在刚开发小程序时,我在所有跨页面传参使用的都是[代码]wx.setStorageSync[代码]和[代码]wx.getStorageSync[代码],不仅拖慢了程序的运行速度,还占用了宝贵了n/10M。其实,对于不需要存储在本地的跨页面传参,完全可以用[代码]app.globalData[代码]来代替: [代码]app.globalData[代码]是全局变量,下次进入的时候,就要重新获取。 [代码]Storage[代码]是本地缓存,除非缓存被清除不需要重新获取。 运营规范 登陆 相信大家对下图的warning都不陌生: [图片] 在之前,小程序可以直接通过[代码]wx.getUserInfo[代码]调起授权弹窗,这一操作虽然很便利,但却被一些开发者滥用,有些小程序一打开就要求授权甚至不授权就不能使用。在考虑到这一点的基础上,[代码]wx.getUserInfo[代码]不再显示授权弹窗,只能用button组件的开放功能调用。引用官方的话: 用户使用登录功能就像“面基”,第一印象很重要。这几个小改动在提升小程序使用的流畅体验、避免用户对数据采集授权担忧的同时,也将驱动用户更乐意尝试使用小程序服务。 因此,好的选择是在用户第一次登陆时“微妙地提示”用户授权登陆,并且在用户未授权时也可以访问到小程序的公共内容。 模版消息 我们知道,iOS上有专门的消息推送机制,而安卓上只能通过让应用后台运行实现24小时接受消息。借助于微信的装机率和使用率,小程序不需要时刻保持后台运行也能让用户接受到消息,这靠的就是微信的模版消息功能。Butttt,为了防止此功能的滥用,模版消息的发送有两个触发条件: 支付:当用户在小程序内完成过支付行为,可允许开发者向用户在7天内推送有限条数的模板消息(1次支付可下发3条,多次支付下发条数独立,互相不影响) 提交表单:当用户在小程序内发生过提交表单行为且该表单声明为要发模板消息的,开发者需要向用户提供服务时,可允许开发者向用户在7天内推送有限条数的模板消息(1次提交表单可下发1条,多次提交下发条数独立,相互不影响) 所以开发者难免会遇到模版消息发送次数不够用的窘况。但根据第二点,小程序每获得一次[代码]formId[代码],相当于就多了一条模版消息的「命」。简单来说,我们可以将小程序的表单组件进行封装,把用户的交互点击的[代码]bindtap[代码]事件替换为[代码]bindsubmit[代码],乔装一下小程序中其他功能按钮,当用户点击这些按钮时,就能获取到更多的[代码]formId[代码]。 ⚠️ 不过即便如此,开发者也应该遵守小程序运营规范,不要滥用模板消息。 更新:根据7月25日「微信广告小程序流量主大会」消息,微信小程序将上线「一次性订阅消息」的能力,只要经用户确认,超过七天的模版消息也能送达用户。 总结 小程序脱胎于微信,出生时便自带「降低门槛、充实能力、场景流量、提高转化、交易变现」等强有力的Buff,在电商零售、政务平台多方面可以说是全面赋能,有无限的发展潜能。小程序在方便用户,降低用户使用门槛的同时,也为开发者们提供了诸多上手即用的API和组件。如此强有力的工具想要长青,自然需要在开发、运营上施加限制,此之谓「有限」,而施加限制的目的是建立友好的微信生态体系,实现用户、微信、开发者三方互利共赢,此之谓「无限」。
2019-07-26 - 初步使用云开发中数据库的能力
正如上篇所讲,数据库这块除了熟练使用几个 API,确实没啥可讲的,那么就来回答一下,在第 8 篇文章中,开发「历史上的今天」小工具时,留下来的思考题吧。题目如下: 由于类似的第三方数据接口,都是付费服务,这个接口虽然免费,但是限制调用次数。 如何通过改进代码,最低限度调用第三方接口? 当时给了两个思路: 借助缓存功能,每天每个用户只需要 1 次请求; 借助数据库功能,不管多少用户,每天只需要 1 次请求; 第一个思路,没啥好讲的,基本原理就是在启动页面的时候,检查缓存情况,如果有缓存,直接加载缓存;如果没有缓存,请求数据,并保存缓存。 但是,缓存是针对用户微信客户端的,有多少用户,就需要请求多少次,如果一个用户有 2 个微信客户端,还要另外再多请求一次。显然,这不是最佳的解决方案。 最佳的解决方案,当然是第二个思路,借助数据库。下面就以这个示例,直接进入开发实战,学会数据库的基本使用。 功能分析 代码先不着急写,我们先来分析一下具体功能,总体要求是:将请求的数据保存在云数据库中,用户打开页面,首先去云数据库中查找当天的数据,如果有,则在数据库中取;如果没有,再去接口请求。 这样就能保证,每天只需要请求一次数据,后面的用户直接在数据库中获取「历史上的今天」的数据。所以,云函数要实现的功能是: 请求第三方接口数据。(之前的云函数已经写过了,这里只需调用即可) 保存当天数据到数据库中。为什么是当天?这是业务的特殊性,「历史上的今天」每天的数据不一样 而页面逻辑层要实现的功能是:去云数据库中查找当天数据。 如果找到,返回数据,渲染页面即可; 如果没有,调用上述云函数; 数据库准备 打开「云开发」界面,选择「数据库」。 [图片] 新建集合 [代码]history[代码],设置集合权限为:所有人可读。如下图所示: [图片] 一人请求后,数据便写入云数据库,后续所有人都可以直接读取,而无需再次请求第三方接口。 代码实现:云函数 新建页面 [代码]cloud_database[代码] 用做演示,新建云函数 [代码]history_save[代码],目录结构如下: [图片] 首先编写云函数 [代码]history_save[代码],代码如下: [代码]// 引入模块 const cloud = require('wx-server-sdk') // 初始化云开发 cloud.init() // 云函数入口函数 exports.main = async (event, context) => { // 传入参数 today,env let today = event.today; let env = event.env; // 更新默认配置,将默认访问环境设为当前云函数所在环境 cloud.updateConfig({ env }); // 初始化数据库 const db = cloud.database(); // 获取 history 集合对象 const db_history = db.collection('history'); const res = await cloud.callFunction({ name: 'history' }) console.log(res); let lists = res.result.showapi_res_body.list; await db_history.add({ data: { date: today, lists: lists } }).then(res => { console.log(res); }) return { date: today, lists: lists }; } [代码] 这个云函数的逻辑就是调用 [代码]history[代码] 云函数,获得数据,保存到数据库中,然后返回数据。其中有 4 处地方,需要提一下: 传入参数,通过 [代码]event[代码] 对象获取,例如上面的 today 以及 env 更新云函数的环境变量,手动使其与小程序端保持一致,避免默认选项 调用其他云函数,直接使用 [代码]callFunction[代码] API 插入数据库,使用 [代码]add[代码] API 这些其实都是最基础的用法,在官方文档中都能找到出处,如果发现不清楚的地方,一定要去看官方文档,这里仅作为参考,给你一个思路。 下图为本地测试结果: [图片] 图中我已经标注非常清楚了,就不多做解释了。另外需要注意的一点是,本地测试的云函数,其中调用的其他云函数是在云端的。所以,务必确保调用的那个云函数在云端是没有问题的。 最后看一下数据库中,是否已经有了数据,如下图: [图片] 跟预想的一样。云端测试这里就不演示了,接下来就是小程序端的功能实现了。 代码实现:小程序端 打开页面 js 文件,在 [代码]onLoad[代码] 事件函数中,编写代码如下: [代码]onLoad: function (options) { // 初始化数据库 const db = wx.cloud.database(); // 获得 history 集合对象 const db_history = db.collection('history'); // 引入 dayjs const dayjs = require('dayjs'); const TODAY = dayjs().format('YYYY-MM-DD'); db_history.where({ date: TODAY }).get().then( res => { console.log(res); let result = res.data; if(result.length){ this.setData({ lists: result[0].lists }); }else{ wx.cloud.callFunction({ name: 'history_save', data: { today: TODAY, env: getApp().globalData.env }, complete: res => { console.log(res); this.setData({ lists: res.result.lists }); } }) } }) }, [代码] 简单说一下代码逻辑,首先根据当天日期,查找数据库,如果存在数据(即长度不为 0),则直接拿数据渲染页面;如果不存在数据,则调用 [代码]history_save[代码] 云函数,将返回的数据渲染页面。 总结 这篇文章有很多非常重要,却又很基础的知识点,然而,我并没有详细展开去讲,只是针对比较重要的点,单独都提出来了。例如:更新云函数环境变量这块。假如不更新的话,会出现什么结果?为什么要更新?还有没有其他的方式进行更新? 这些都需要自己去动手尝试,从结果出发,我只是给出了一条可行的道路,还有很多条不同的路,也可以获得同样的结果,但需要你去尝试。再例如:判断数据库是否存在数据的逻辑,是否可以一并放到云函数中实现?这样的话,页面逻辑就更简单了。 遇到不明白的地方,针对性的去查看官方文档,你将获得更多。结合基础的知识点,实际去解决问题,才是这个系列文章的初衷。希望能对你有一些帮助。 最后,还是留一个思考题: [图片] 如上图,云函数的调用也是有限额的,超了的话,也是需要付费的,能不能通过改进代码,实现最低限度的调用云函数?答案其实在之前的文章里,已经给过了。 更多文章:https://github.com/pengloo53/miniprogram-articles
2019-10-31 - 云函数插入数据为什么不成功?
[图片][图片][图片][图片] 这个功能是子评论功能,想要的效果是点击图一的提交时,能把数据插入到数据库的childContent里面,代码是这样的,但是为什么我这么操作了,没有提示报错,但是也没有插入成功,云函数我已经上传了,
2020-03-02 - 云函数搞一搞消息推送
分析: 1.传统客服消息都是利用button组件并指定open-type来拉起客服; 2.利用云函数可在任意元素上拉起客服,如:view,text,image;这个优势特别好,方便统一处理CSS(赞赞赞); 3.需要在小程序的全局配置中开启消息推送; 4.需要在云函数同级目录下创建开启权限的配置文件config.json; 5.云函数支持多种消息,如文本,图片,小程序等;文本类型的消息可以是静态文本;也可以是<a>链接.这个<a>可打开网页;也可打开小程序(指定小程序的appid和要打开的页面),二者选一.如果同时指定,则打开小程序;( 好象只能推送小程序本身.我试了下,推送另外一个个人小程序appid,是失败的.我用的小程序是企业小程序.不知道这里有没有特别的考虑.请大牛指点!!! ) 6.创建用于推送消息的云函数; 7.在小程序的全局配置中,添加消息推送,选择消息类型,环境并指定要处理的云函数(这个要提前创建好,可以先不着急写业务逻辑); 8.在云函数中编辑业务逻辑; 9.理解有限,其他更深入的应用也还没涉及.多多指教! (编辑更新)重点来了:这个函数特别容易报超时或订阅取消之类的错误(如下).MS没有发现解决的方法.请管理们和大牛们加油啊 openapi.customerServiceMessage.send:fail response out of time limit or subscription is canceled hint 下面以发送欢迎词为例 wxml:当用户点击这个view时,就会拉起客服.并自动收到云函数发送的问候词. <view bindtap="onSub">sub</view> js:事件处理函数(这个事件的目的是调用云函数,如果用传统的BUTTON客服,就不用写这个事件,系统会自动调用这个云函数) onSub() { wx.cloud.callFunction({ name: 'client-hello' }).then(res => { console.log(res) }) } js:云函数client-hello const cloud = require('wx-server-sdk') cloud.init() exports.main = async(event, context) => { let { OPENID, APPID, UNIONID } = cloud.getWXContext(); return await cloud.openapi.customerServiceMessage.send({ touser: OPENID, msgtype: 'text', text: { content: '欢迎光临,更多优惠请点点击跳小程序', } }) } js:配置文件(云函数同级目录下哦) { "permissions": { "openapi": [ "templateMessage.send" ] } }
2020-03-16 - 小程序的图片,一定要固定大小才能显示出来。那为什么携程的列表可以显示不同尺寸的大小呢?
最近在思考,写点什么呢?!想了很久,决定了 还是仿!!!这次我打算用原生的方式仿携程的酒店列表(未完)。 却无意中发现,携程的列表居然有不同大小的图片~~~~~~~~~~~~~~ 虽然,我不知道他的实现方式,是如何的。 但是我找到了解决的办法。也证实了我的想法。 那就是css里的flex。可能根据内容的伸缩改变图片的高度,代码简单如下 .p-item border-top: 6rpx solid #f5f5f5 background: #fff ss-display-flex(row nowrap, flex-start) overflow: hidden .item-pic flex: 1 image width: 240rpx height: 100% .item-content flex-grow: 1 padding: 25rpx 20rpx 15rpx 20rpx 实现效果: [图片] 真机效果可以搜索SAUI,或者扫以下码《实际项目之酒店列表》(刚推,正在审核。效果应该隔天才能看到) [图片]
2020-02-28 - 极致的scroll-view的下拉刷新扩展组件
不敢说是最好的,但是感觉也应该是性能和体验比较极致的下拉刷新扩展了,老规矩,代码片段放最后了~ 2020.2.22 修复了小程序基础库v2.10.2带来的不能滚动的问题,最新代码片段见scroll-view-extends 原理 其实原理很简单,和普通H5以及市面上有的下拉刷新没有特别大的区别,都是基于[代码]touch[代码]手势检测事件来实现下拉刷新的。[代码]touchstart[代码]的时候记录当前触摸点,[代码]touchmove[代码]的时候开始计算移动方向和移动距离, [代码]touchend[代码]的时候计算是否要进行下拉刷新操作。如图所示: [图片] 实现方法 调研了一些实现方法,目前大部分都是通过js计算,然后setData来改变元素的[代码]transform[代码]值实现下拉刷新。考虑到性能问题,此处使用了[代码]wxs[代码]的响应式能力来实现整个计算逻辑,不用通过逻辑层和视图层通信,直接在视图层进行渲染。具体文档请参考wxs响应事件。 这里在[代码]list[代码]组件(由[代码]scroll-view[代码]组成)下抽出了一个[代码]scroll.wxs[代码]作为响应事件的事件处理函数集合,源码基本上就在[代码]scroll.wxs[代码]和[代码]list[代码]组件。 [代码]scroll.wxs[代码]定义了如下变量和函数: [代码]var moveStartPosition = 0 //开始位置 var moveDistance = 0 //移动距离 var moveRefreshDistance = 60 //达到刷新的阈值 var moveMaxDistance = 100 //最大可滑动距离 var isRefreshMaxDown = false //是否达到了最大距离, 用来判断是否要震动提示 var loading = false //是否正在loading ... ... module.exports = { touchStart: touchStart, //手指开始触摸事件 touchMove: touchMove, //手指移动事件 touchEnd: touchEnd, //手指离开屏幕事件 loadingTypeChange: loadingTypeChange, //请求状态变化监听,监听刷新请求开始和请求完成 triggerRefresh: triggerRefresh //主动触发刷新操作,比如点击页面上一个按钮,重新刷新list,这就需要用到这个方法 } [代码] [代码]touchStart[代码]和[代码]touchMove[代码]就不用说了,代码注释都很明白,普通的监听移动和处理逻辑。 [代码]touchEnd[代码]主要是判断移动距离是否达到了阈值,然后根据结果,调用监听实例的[代码]callMethod[代码]方法触发[代码]refreshStart[代码]或者[代码]refreshCancel[代码]方法,这两个方法都是写到[代码]list[代码]组件里面的,用来触发刷新方法或者取消刷新。 [代码]loadingTypeChange[代码]方法主要是监听刷新是否完成,以此来触发动画效果。 [代码]triggerRefresh[代码]通过监听主动触发的变量来处理。如果需要主动触发刷新,则调用[代码]list[代码]组件内部的[代码]forceRefresh[代码]方法,具体使用示例在[代码]index/index/js[代码]的[代码]onLoad[代码]函数有: [代码]this.selectComponent('.list').forceRefresh()[代码] [代码]scroll.wxs[代码]里面还有一个未导出的方法,叫[代码]drawTransitionY[代码],这个方法主要是因为[代码]ios12[代码]对于[代码]transition[代码]动画效果支持的不好,所以自己写了个Y轴方向的动画([代码]linear[代码]线性的),大佬们可以自己往上添加各种[代码]ease-in-out[代码]效果。 里面具体的实现可以查看代码注释哦~ 使用 好了,前面讲了实现的原理和方法,那么在代码里面,应该怎么直接使用呢?如下代码所示: [代码]<!-- 使用示例 --> <list class="list" refresh-loading="{{refreshLoading}}" loading="{{loading}}" bindrefresh="initList" bindloadmore="loadmore"> <!-- your code --> </list> [代码] [代码]refresh-loading[代码]属性用来通过外部loading态来控制刷新动画的开始结束,因为每当变化[代码]refresh-loading[代码]的值时,会将变化同步到组件内的[代码]showRefresh[代码]属性,[代码]wxs[代码]通过监听[代码]showRefresh[代码]来处理动画逻辑。 [代码]loading[代码]属性是上拉加载更多的时候触发的loading态展示,跟刷新无关 [代码]bindrefresh[代码]是刷新触发时绑定的函数,下拉刷新动画成功开始后触发这个函数 [代码]bindloadmore[代码]透传[代码]scroll-view[代码]的加载更多方法 当然,源码里面也包含了一个[代码]list-item[代码]组件,这个跟本文没太大关系,是用来做瀑布流长列表内容太多时的内存不足问题解决方案的,具体请看解决小程序渲染复杂长列表,内存不足问题 干货 最后,上代码片段, 小程序代码片段 github地址
2020-02-22 - 「服务平台·疫情服务专区」新增开源代码模板
小程序,一直在行动! 新冠肺炎疫情牵动人心,为便于小程序开发者或服务商能够便捷、高效开发更多便民小程序,「服务平台·疫情服务专区」新增疫情小程序【开源代码模板】,开发者可根据需求接入使用。 同时,欢迎开发者报名入驻专区,开源分享自己开发的疫情小程序模板,为更多的开发者提供参考和支持!我们一同抗击疫情! 「扫码报名入驻」 [图片] 平台将对投稿的作品进行审核和筛选,优秀作品将会直接展示在「服务平台·疫情服务专区」中。平台还将联系并返回修改意见,开发者配合修改优化后,也有机会入驻专区。 请申请入驻专区的开发者详细阅读以下项目规范: 【小程序源码模板规范】小程序源码模板须符合“开源项目规范”、“项目部署手册规范”、“代码规范”,才能报名。 |开源项目规范 以下内容为本次开源项目征集规范 关于开源代码托管你可以根据自己的喜好,选择任一代码托管平台,官方对于代码托管平台不做限制。但,你的项目必须为公开可访问,不得设置任何限制。 关于开源项目许可证本次征集不限制项目的具体许可证。如果您没有明确选择,我们推荐您选择 Apache LICENSE 、MIT LICENSE 等常用许可证。 关于项目必备文件一个开源项目必须包含以下支持文件,确保你的项目完整提供服务: • README.md: 项目的介绍 • LICENSE: 项目的开源许可证 • code-of-conduct.md: 项目的行为准则 • contributing.md: 项目的贡献指南 • deployment.md: 部署说明 • changelog.md: 项目的更新日志 README.md 模版以下是 README.md 的模板,供你参考。 # 项目名称 项目简介 ## 特性 1. 产品特性1 2. 产品特性2 ## 依赖 - 产品运行依赖服务1 - 产品运行依赖服务2 ## 部署说明 这个项目具体的部署参考 deployment.md ## 开发说明 这个项目应该如何开发、如何贡献。 ## Bug 反馈 如果有 Bug ,请通过 XXX 反馈 ## 联系方式(非必须,但建议提供) 有任何问题,可以通过下方的联系方式联系我。 ## LICENSE 你选择的开源协议 |项目部署手册规范作为项目的开发者,你需要为你的用户撰写相应的部署手册,确保用户可以快速的将你的小程序模板应用在其自己的项目中。 必备要素在你的部署手册中,应当包含下述内容,以确保用户可以正常使用你的模板 • 如何下载代码 • 如何将代码导入到开发者工具 • 哪些参数需要修改 • 哪些云函数需要部署 • 涉及到的外部服务 • 云数据库中需要创建哪些数据 • 云存储中需要上传哪些文件 • 小程序后台需要配置哪些 HTTP 安全域名请求 • 小程序后台需要配置哪些服务 非必备要素以下内容非必须,但推荐你提供,以更好的帮助用户使用你的模板 • 数据维护指南 • 联系方式 交付方式推荐使用 PDF 交付部署手册 |代码规范[图片] 微信团队
2020-04-23 - 业务数据怎么查,我用云开发高级日志服务
业务错误怎么查,我用云开发高级日志服务 小程序·云开发作为小程序原生的后台开发能力,一直致力于可以更高效地帮助开发者构建性能更好的小程序。而云函数作为云开发的一项基础能力,承载着小程序所有的后台运算逻辑,它就像小程序的大脑一样,每天繁忙地工作着。 而对于开发者而言,如何尽快地定位和排查云函数使用过程中的问题,也成为保障小程序质量的必备功能。而有一部分开发者并不担心这个问题,因为他们选择了一种能力,只要通过简单的开启就可以高效且准确地定位到云函数中的问题。 这就是云开发提供的高级日志服务。那什么是高级日志服务,它又能做什么呢?接下来就让我们一探究竟。 什么是高级日志服务 很多开发者可能都会遇到这样一些问题: [代码]线上的小程序运行地好好的突然出问题了,怎么知道是哪里有异常呢? 根据线上表现大概猜到是哪个功能模块出现了异常,但是不知道上下文调用信息,要如何准确定位问题? [代码] 如果你恰巧使用的是云开发,那不必担心,因为云函数原生就带有日志服务。但是基于之前提供的旧的日志服务,开发者可能还是会遇到一些问题: [代码]我不知道具体的请求 ID 是什么,但是隐约记得一些关键字,这要怎么查询日志信息啊? 我想自定义一些信息打印到日志中,该怎么办? [代码] 小程序·云开发的高级日志服务就是为解决以上所有开发者遇到的问题应运而生的产品能力。基于高级日志服务提供的日志采集和日志检索功能,开发者可以更加高效地发现和解决云函数运行过程中的问题。 高级日志服务能做什么 让我们通过前端开发小 H 的故事来看看,高级日志服务到底能够做什么。 1. 旧框架下的日志服务 之前小 H 经常会为了做小程序而陷入苦恼当中,比如他需要将用户触发订阅消息时的一些数据存储到数据库中,用于后续的消息下发,这就需要有一个完整的服务端才行。但是作为一个前端开发,小 H 对于后台服务的搭建和部署并不熟悉,因此常常陷入困境。 后来他发现了云开发这样一个神器,一键开通就可以具备后台服务开发的能力了。那么现在当他想要存储相关的数据就变得非常简单了。 首先,他定义一个云函数 [代码]subscribe[代码],并在通过该云函数将用户订阅的消息信息存储到小程序·云开发的数据库中。 [代码]exports.main = async (event, context) => { try { const { OPENID } = cloud.getWXContext(); // 在数据库中记录用户的订阅信息 const result = await db.collection('messages').add({ data: { touser: OPENID, // 用户的openid page: 'index', // 订阅消息的页面路径 templateId: event.templateId, // 订阅消息模板ID }, }); return result; } catch (err) { console.log(err); return err; } } [代码] 然后在小程序端调起订阅消息界面的时候触发这个云函数并将对应的信息存储到数据库中。同时小 H 还可以通过云开发提供的原生的日志功能查看每次的调用是否成功,以及具体的调用信息。 [图片] 但是原生的日志中能够写入的数据是非常有限的。而且检索日志的时候只能通过开始时间、结束时间、状态和 [代码]requestID[代码] 进行检索。可是基于业务需求小 H 需要在 [代码]subscribe[代码] 云函数调用的时候需要再打入一些自定义的信息,而且他希望可以对日志按照 [代码]log[代码] / [代码]info[代码] / [代码]warn[代码] / [代码]error[代码] 进行分级,这样在日志查询的时候也可以快速定位到自己想要关注的日志,这该怎么办呢? 当然,小 H 并不只是一个人,正是看到很多开发者有类似的问题,今年我们推出了云开发高级日志的服务。接下来,让我们看看,小 H 是如何使用高级日志服务的。 2. 高级日志服务 首先,小 H 在定义 [代码]subscribe[代码] 函数的时候可以使用 [代码]wx-server-sdk[代码](1.5.0 或以上版本)提供的方法打入一些自定义的日志内容。具体流程为: 通过 [代码]logger()[代码] 方法取得 [代码]log[代码] 对象 调用 [代码]log[代码] 对象上的 [代码]log[代码] / [代码]info[代码] / [代码]warn[代码] / [代码]error[代码] (对应不同 level 的日志等级)方法,传入一个对象作为参数 对象的每一个 [代码]<key, value>[代码] 对都会成为日志一条记录中的一个可检索的键值对,其中 [代码]value[代码] 不论值是什么都会被转成字符串 按照上述改造后, [代码]subscribe[代码] 变成了下面这样: [代码]exports.main = async (event, context) => { const log = cloud.logger(); try { const { OPENID } = cloud.getWXContext(); // 在数据库中记录用户的订阅信息 const result = await db.collection('messages').add({ data: { touser: OPENID, // 用户的openid page: 'index', // 订阅消息的页面路径 templateId: event.templateId, // 订阅消息模板ID }, }); log.info({ action: 'addMessage', touser: OPENID, templateId: event.templateId, }); return result; } catch (err) { log.error({ type: err.name, message: err.message, }); return err; } } [代码] 此时,当这个 [代码]subscribe[代码] 被触发以后,我们就能在高级日志服务中看到这样一条日志记录: [代码]{ "level": "info", "function": "<function_name>", // 执行的云函数名 "requestId": "<request_id>", // Request ID "action": "addMessage", "touser": "<openid>", "templateId": "<template_id>", "src": "app" // logger 打的日志为 app,系统打的日志为 system } [代码] 有了日志以后,日志检索也会变得非常的简单。高级日志不仅提供了全文检索能力,还提供了通过键值检索约束查询范围,让日志的检索变得更加的简单和快捷。 比如,小 H 想知道 [代码]subscribe[代码] 函数的日志,就可以通过: 全文检索:在搜索框中输入 [代码]subscribe[代码] 键值检索:在搜索框中输入 [代码]function:subscribe[代码] 比如,小 H 想知道 [代码]subscribe[代码] 函数且 OPENID 为 [代码]popo[代码] 的日志,就可以通过: 在搜索框中输入 [代码]function:subscribe and touser:popo[代码] 又如,小 H 想知道 level 为 [代码]error[代码] 且错误信息中含有单词 [代码]defined[代码] 或以 [代码]mem[代码] 打头的单词的日志,就可以通过: 在搜索框中输入 [代码]function:subscribe and level:error and (message:defined or message:mem*)[代码] 当然,高级日志服务还提供丰富的查询语法,大家可通过《小程序·云开发高级日志服务》了解详细内容。 [图片] 除了新增的高级日志外,近期小程序·云开发还更新了—— 小程序·云开发能力更新 除了新增的高级日志外,近期小程序·云开发还更新了: 为帮助企业、政府、媒体及其他组织的小程序开发者在新冠肺炎疫情期间共度难关,小程序·云开发推出特殊类型代金券帮助大家以更低地资源成本完成小程序的功能迭代。详情可参考文档《小程序·云开发特殊代金券》 数据库安全规则:提供精细化的控制集合中所有记录的读、写权限的能力,自动拒绝不符合安全规则的前端数据库请求,保障数据安全 自定义告警:提供更加灵活的告警配置,可以使用告警指标、统计周期、比较条件、持续周期、告警频率等参数自由组合告警条件。如:统计周期 [5 分钟],当 [云函数错误次数] [>] [5 次] 且持续 [1] 个周期时告警,[每小时] 告警一次 数据库事务:可以方便开发者更加灵活地使用数据库能力,满足跨多个记录或跨多集合的原子操作的使用诉求,极大地方便了小程序的功能开发
2020-02-12 - wx.getUserInfo()获取用户头像和昵称后,打印值为空?
data: { //用户头像 avatarUrl:"", //用户昵称 nickName:"" }, getUserInfo(){ var that=this wx.getUserInfo({ success:function(res){ that.setData({ avatarUrl:res.userInfo.avatarUrl, nickName:res.userInfo.nickName }) } }) }, onLoad: function (options) { this.getUserInfo() console.log(this.data.nickName) console.log(this.data.avatarUrl) },
2020-02-24 - 10行代码实现小程序支付功能!丨实战
前面给大家讲过一个借助小程序云开发实现微信支付的,但是那个操作稍微有点繁琐,并且还会经常出现问题,今天就给大家讲一个简单的,并且借助官方支付api实现小程序支付功能。 传送门: 借助小程序云开发实现小程序支付功能 老规矩,先看本节效果图 [图片] 我们实现这个支付功能完全是借助小程序云开发实现的,不用搭建自己的服务器,不用买域名,不用备案域名,不用支持https。只需要一个简单的云函数,就可以轻松的实现微信小程序支付功能。 核心代码就下面这些: [图片] 一、创建一个云开发小程序 关于如何创建云开发小程序,这里我就不再做具体讲解。不知道怎么创建云开发小程序的同学,可以去翻看腾讯云云开发公众号内菜单【技术交流-视频教程】中的教学视频。 创建云开发小程序有几点注意的 1.一定不要忘记在app.js里初始化云开发环境。 [图片] 2.创建完云函数后,一定要记得上传 二、创建支付的云函数 1.创建云函数pay [图片] [图片] 三、引入三方依赖tenpay 我们这里引入三方依赖的目的,是创建我们支付时需要的一些参数。我们安装依赖是使用里npm 而npm必须安装node,关于如何安装node,我这里不做讲解,百度一下,网上一大堆。 1.首先右键pay,然后选择在终端中打开 [图片] 2.我们使用npm来安装这个依赖。 在命令行里执行 npm i tenpay [图片] [图片] [图片] 安装完成后,我们的pay云函数会多出一个package.json 文件 [图片] 到这里我们的tenpay依赖就安装好了。 四、编写云函数pay [图片] 完整代码如下 [代码]//云开发实现支付 const cloud = require('wx-server-sdk') cloud.init() //1,引入支付的三方依赖 const tenpay = require('tenpay'); //2,配置支付信息 const config = { appid: '你的小程序appid', mchid: '你的微信商户号', partnerKey: '微信支付安全密钥', notify_url: '支付回调网址,这里可以先随意填一个网址', spbill_create_ip: '127.0.0.1' //这里填这个就可以 }; exports.main = async(event, context) => { const wxContext = cloud.getWXContext() let { orderid, money } = event; //3,初始化支付 const api = tenpay.init(config); let result = await api.getPayParams({ out_trade_no: orderid, body: '商品简单描述', total_fee: money, //订单金额(分), openid: wxContext.OPENID //付款用户的openid }); return result; } [代码] 一定要注意把appid,mchid,partnerKey换成你自己的。 到这里我们获取小程序支付所需参数的云函数代码就编写完成了。 不要忘记上传这个云函数。 [图片] 出现下图就代表上传成功 [图片] 五、写一个简单的页面,用来提交订单,调用pay云函数。 [图片] 这个页面很简单: 1.自己随便编写一个订单号(这个订单号要大于6位) 2.自己随便填写一个订单价(单位是分) 3.点击按钮,调用pay云函数。获取支付所需参数。 下图是官方支付api所需要的一些必须参数。 [图片] 下图是我们调用pay云函数获取的参数,和上图所需要的是不是一样。 [图片] 六、调用wx.requestPayment实现支付 下图是官方的示例代码: [图片] 这里不在做具体讲解了,把完整代码给大家贴出来 [代码]// pages/pay/pay.js Page({ //提交订单 formSubmit: function(e) { let that = this; let formData = e.detail.value console.log('form发生了submit事件,携带数据为:', formData) wx.cloud.callFunction({ name: "pay", data: { orderid: "" + formData.orderid, money: formData.money }, success(res) { console.log("提交成功", res.result) that.pay(res.result) }, fail(res) { console.log("提交失败", res) } }) }, //实现小程序支付 pay(payData) { //官方标准的支付方法 wx.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, //统一下单接口返回的 prepay_id 格式如:prepay_id=*** signType: 'MD5', paySign: payData.paySign, //签名 success(res) { console.log("支付成功", res) }, fail(res) { console.log("支付失败", res) }, complete(res) { console.log("支付完成", res) } }) } }) [代码] 到这里,云开发实现小程序支付的功能就完整实现了。 实现效果 1.调起支付键盘 [图片] 2.支付完成 [图片] 3.log日志,可以看出不同支付状态的回调 [图片] 上图是支付成功的回调,我们可以在支付成功回调时,改变订单支付状态。 下图是支付失败的回调: [图片] 下图是支付完成的状态: [图片] 到这里我们就轻松的实现了微信小程序的支付功能了,是不是很简单啊。 源码地址: https://github.com/TencentCloudBase/Good-practice-tutorial-recommended 如果你有关于使用云开发CloudBase相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们哦~比心! [图片]
2019-08-15 - 如何实现从微信小程序跳转到别的微信小程序页面
例如上面所示 仔细研究过微信小程序开发文档的小伙伴应该知道,微信小程序跳转到微信小程序采用的是wx.navigateToMiniProgram接口。 最简示例: wx.navigateToMiniProgram({ appId: appId, path: mpPath }) 从上面的代码可以看出,要想实现小程序跳转,需要知道要跳转的微信小程序的appid,这个一般通过查看微信小程序的更多资料可以查到,而path如果不熟悉是不容易知道的,有的说可以通过生成小程序码查到已经可以显示页面的路径信息,但是有可能是个加密的变值(如京东商品的路径),对于定值是可以的。 此外还有一个地方需要加,就是在app.json的配置里面 "navigateToMiniProgramAppIdList": [ "wx467145181671b58c" ] 新版本已经不需要在app.json里面加这个配置了。 是以数组的形式填写的appid,也就是说要跳转的小程序的appid都要在这里配置一下,这个地方最多可以填10个,也就是允许向10个微信小程序跳转。 特别说明: 个人类型的微信小程序已经不允许商业服务推广,本来我做小程序是想推广京东的商品,但因为是个人类型账号,发布后审核没能通过,而企业类型小程序是可以正常发布的。 [图片]
2020-12-22 - 小程序支持跳转到应用宝或者第三方网页吗?
目前没有这个功能。小程序web-view只支持跳转到开发者自己的页面。
2020-01-14 - 微信小程序UI组件库合集
UI组件库合集,大家有遇到好的组件库,欢迎留言评论然后加入到文档里。 第一款: 官方WeUI组件库,地址 https://developers.weixin.qq.com/miniprogram/dev/extended/weui/ 预览码: [图片] 第二款: ColorUI:地址 https://github.com/weilanwl/ColorUI 预览码: [图片] 第三款: vantUI(又名:ZanUI):地址 https://youzan.github.io/vant-weapp/#/intro 预览码: [图片] 第四款: MinUI: 地址 https://meili.github.io/min/docs/minui/index.html 预览码: [图片] 第五款: iview-weapp:地址 https://weapp.iviewui.com/docs/guide/start 预览码: [图片] 第六款: WXRUI:暂无地址 预览码: [图片] 第七款: WuxUI:地址https://www.wuxui.com/#/introduce 预览码: [图片] 第八款: WussUI:地址 https://phonycode.github.io/wuss-weapp/quickstart.html 预览码: [图片] 第九款: TouchUI:地址 https://github.com/uileader/touchwx 预览码: [图片] 第十款: Hello UniApp: 地址 https://m3w.cn/uniapp 预览码: [图片] 第十一款: TaroUI:地址 https://taro-ui.jd.com/#/docs/introduction 预览码: [图片] 第十二款: Thor UI: 地址 https://thorui.cn/doc/ 预览码: [图片] 第十三款: GUI:https://github.com/Gensp/GUI 预览码: [图片] 第十四款: QyUI:暂无地址 预览码: [图片] 第十五款: WxaUI:暂无地址 预览码: [图片] 第十六款: kaiUI: github地址 https://github.com/Chaunjie/kai-ui 组件库文档:https://chaunjie.github.io/kui/dist/#/start 预览码: [图片] 第十七款: YsUI:暂无地址 预览码: [图片] 第十八款: BeeUI:git地址 http://ued.local.17173.com/gitlab/wxc/beeui.git 预览码: [图片] 第十九款: AntUI: 暂无地址 预览码: [图片] 第二十款: BleuUI:暂无地址 预览码: [图片] 第二十一款: uniydUI:暂无地址 预览码: [图片] 第二十二款: RovingUI:暂无地址 预览码: [图片] 第二十三款: DojayUI:暂无地址 预览码: [图片] 第二十四款: SkyUI:暂无地址 预览码: [图片] 第二十五款: YuUI:暂无地址 预览码: [图片] 第二十六款: wePyUI:暂无地址 预览码: [图片] 第二十七款: WXDUI:暂无地址 预览码: [图片] 第二十八款: XviewUI:暂无地址 预览码: [图片] 第二十九款: MinaUI:暂无地址 预览码: [图片] 第三十款: InyUI:暂无地址 预览码: [图片] 第三十一款: easyUI:地址 https://github.com/qq865738120/easyUI 预览码: [图片] 第三十二款 Kbone-UI: 地址 https://wechat-miniprogram.github.io/kboneui/ui/#/ 暂无预览码 第三十三款 VtuUi: 地址 https://github.com/jisida/VtuWeapp 预览码: [图片] 第三十四款 Lin-UI 地址:http://doc.mini.talelin.com/ 预览码: [图片] 第三十五款 GraceUI 地址: http://grace.hcoder.net/ 这个是收费的哦~ 预览码: [图片] 第三十六款 anna-remax-ui npm:https://www.npmjs.com/package/anna-remax-ui/v/1.0.12 anna-remax-ui 地址: https://annasearl.github.io/anna-remax-ui/components/general/button 预览码 [图片] 第三十七款 Olympus UI 地址:暂无 网易严选出品。 预览码 [图片] 第三十八款 AiYunXiaoUI 地址暂无 预览码 [图片] 第三十九款 visionUI npm:https://www.npmjs.com/package/vision-ui 预览码: [图片] 第四十款 AnimaUI(灵动UI) 地址:https://github.com/AnimaUI/wechat-miniprogram 预览码: [图片] 第四十一款 uView 地址:http://uviewui.com/components/quickstart.html 预览码: [图片] 第四十二款 firstUI 地址:https://www.firstui.cn/ 预览码: [图片]
2023-01-10 - Omi × 云开发『半天』搞定小程序 『markdown 内容发布系统』
原创:腾讯Omi团队 想要开发小程序,但是…没有后端!没有运维!没有 DBA!没有域名!没有证书!没有钱!没有时间!没有精力!怎么办??? 没有关系,小程序•云开发带你飞,会 javascript 就可以! 开发者可以使用「云开发」开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。「云开发」为开发者提供完整的云端支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时,这一能力同开发者已经使用的云服务相互兼容。 目前提供三大基础能力支持: 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写自身业务逻辑代码 云数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库 存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理 从0到1搭建markdown内容发布系统 本文将一步一步教你如何从0到1使用 「小程序•云开发 + Omip + Comi 」搭建一个支持 markdown 及代码高亮的「markdown 内容发布系统」。 预览: [图片] 一、初始化 1.建表 [图片] 操作路径: 微信开发者工具→云开发→数据库→添加集合 article 集合字段说明: 字段 说明 _id 数据的唯一 id,用户写入时系统自动生产 _openid 用户的唯一标识,用户写入时系统自动生产 createTime 文章创建时间 md 文章内容 order 文章的顺序 title 文章的标题 很明显,这个表用来存储所有的文章。然后设置表的读写权限: [图片] 因为后续将支持用户发表文章,所有设置成第一个。 2.初始化项目目录 [代码]$ npm i omi-cli -g $ omi init-cloud my-app $ cd my-app $ npm start [代码] [图片] 这里是使用 omip 作为脚手架,也支持 Omi mps-cloud 创建原生小程序的云开发的脚手架: [代码]$ npm i omi-cli -g $ omi init-mps-cloud my-app $ cd my-app/miniprogram $ npm install $ npm start [代码] 3.项目初始化 app.js [代码]import './app.css' import './pages/list/index' import { render, WeElement, define } from 'omi' define('my-app', class extends WeElement { config = { pages: [ 'pages/list/index', 'pages/detail/index', 'pages/import/index' ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'Omi Cloud', navigationBarTextStyle: 'black' } } install() { if (!wx.cloud) { console.error('请使用 2.2.3 或以上的基础库以使用云能力') } else { wx.cloud.init({ traceUser: true, }) this.globalData.db = wx.cloud.database({ env: 'test-06eb2e' }) } } render() { return ( <page-list /> ) } }) render(<my-app />, '#app') [代码] [图片] [代码]wx.cloud.database[代码] 代码参数里的 [代码]env[代码] 可以从上面获取到,一般创建两个环境,一个是用户测试环境,另一个用于生产环境。 pages/list/index 文章列表首页 pages/detail/index 文章详情页 pages/import/index 文章导入页(先简单通过代码导入 markdown,未提供 UI) 二、导入 markdown 数据 [代码]import { WeElement, define } from 'omi' import data from './test.md' const app = getApp() define('page-import', class extends WeElement { installed() { wx.cloud.callFunction({ name: 'login', data: {}, success: res => { app.globalData.openid = res.result.openid app.globalData.db.collection('article').add({ data: { md: data.md, title: 'test', createTime: app.globalData.db.serverDate() }, success: (res) => { console.log(res) }, fail: err => { console.error('[云函数] [login] 调用失败', err) } }) }, fail: err => { console.error('[云函数] [login] 调用失败', err) } }) } ... ... }) [代码] 注意以下四点: 1.通过 [代码]wx.cloud.callFunction[代码] 调用云函数进行登陆,且获取 openid,接着导入数据会自动带上提交该 openid。 2.通过 [代码]app.globalData.db.serverDate()[代码] 获取服务端时间,客户端时间不可靠 文章导入只由管理员负责 3.注意 [代码]importdatafrom'./test.md'[代码],这里通过修改 omip 里的 scripts 逻辑实现。 这里解释下 import markdown 原理: [代码]let code = fs.readFileSync(item).toString() if (path.extname(item) === '.md') { code = `export default { md: \`${code.replace(/`/g, '\\`').replace(/\$/g, '\\$')}\` }` } [代码] 检测到 md 后缀的文件,把文件里的 markdown 字符串对关键字进行转义然后变成一个 js 模块。 这也算是使用中间编译的好处之一吧,如果原生的小程序目前没办法 import markdown 文件,当然原生小程序 API 和周边生态在不断进化,腾讯 Omi 团队开发的 mps 框架 就是让你在原生小程序中使用 jsx 和 less。 上面的详细代码可以点击这里查看到。 三、列表页 [图片] 请求 list 数据: [代码] //先展示 loading wx.showLoading({ title: '加载中' }) //调用云函数获取 openid wx.cloud.callFunction({ name: 'login', data: {}, success: res => { app.globalData.openid = res.result.openid app.globalData.db.collection('article').field({ title: true, _id: true, order: true }).get().then(res => { this.data.list = res.data.sort(function (a, b) { return a.order - b.order }) this.update() wx.hideLoading() }) }, fail: err => { console.error('[云函数] [login] 调用失败', err) } }) [代码] 请求 list,通过 field 方法筛选字段,毕竟 list 不需要 md 字段,这样可以减少数据传输,节约带宽 通过 order 字段对 list 进行排序(这样管理员不需要发版本就可以手动调整 order 给 list 排序) 完整的代码: [代码]import { WeElement, define } from 'omi' import './index.css' import arrowPng from './arrow.png' //获取应用实例 const app = getApp() define('page-about', class extends WeElement { config = { navigationBarBackgroundColor: '#24292e', navigationBarTextStyle: 'white', navigationBarTitleText: 'Omi', backgroundColor: '#ccc', backgroundTextStyle: 'light' } data = { list: [] } installed() { wx.showLoading({ title: '加载中' }) wx.cloud.callFunction({ name: 'login', data: {}, success: res => { console.log('[云函数] [login] user openid: ', res.result.openid) app.globalData.openid = res.result.openid app.globalData.db.collection('article').field({ title: true, _id: true, order: true }).get().then(res => { this.data.list = res.data.sort(function (a, b) { return a.order - b.order }) this.update() wx.hideLoading() }) }, fail: err => { console.error('[云函数] [login] 调用失败', err) } }) } gotoDetail = (evt) => { wx.navigateTo({ url: '../detail/index?id=' + evt.currentTarget.dataset.id }) } render() { return ( <view class='ctn'> {list.map(item => ( <view class='item' data-id={item._id} bindtap={this.gotoDetail}> <text>{item.title}</text> <image src={arrowPng}></image> </view> ))} </view> ) } }) [代码] Omip 可以直接让你使用 jsx 书写 wxml 结构。编译出的 wxml 如下: [代码]<block> <view class="ctn"> <view class="item" data-id="{{item._id}}" bindtap="gotoDetail" wx:for="{{list}}" wx:for-item="item"><text>{{item.title}}</text> <image src="{{arrowPng}}"></image> </view> </view> </block> [代码] 这里需要注意,点击每一项跳转详情也一定要使用 [代码]evt.currentTarget.dataset.id[代码],而不能使用 [代码]evt.target.dataset.id[代码]。这样点击到文字或者image上获取不到 id。 四、文章详情展示 这里使用 Comi 进行 markdown 渲染! Comi 读 ['kəʊmɪ],类似中文 科米,是腾讯 Omi 团队开发的小程序代码高亮和 markdown 渲染组件。Comi 是基于下面几个优秀的社区组件进行二次开发而成。 wxParse remarkable html2json htmlparser prism 效果预览: [图片] [代码]import { WeElement, define } from 'omi' import './index.css' import comi from '../../components/comi/comi' //获取应用实例 const app = getApp() define('page-about', class extends WeElement { config = { navigationBarBackgroundColor: '#24292e', navigationBarTextStyle: 'white', navigationBarTitleText: ' ', backgroundColor: '#eeeeee', backgroundTextStyle: 'light' } install(options) { wx.showLoading({ title: '加载中' }) app.globalData.db.collection('article').doc(options.id).get().then(res=>{ comi(res.data.md, this.$scope) wx.hideLoading() }).catch(err => { console.error(err) }) } render() { return ( <view> <include src="../../components/comi/comi.wxml" /> </view> ) } }) [代码] 除了在 omip 中使用,原生小程序也可以使用 Comi: 先拷贝 此目录 到你的项目。 js: [代码]const comi = require('../../comi/comi.js'); Page({ onLoad: function () { comi(`你要渲染的 md!`, this) } }) [代码] wxml: [代码]<include src="../../comi/comi.wxml" /> [代码] wxss: [代码]@import "../../comi/comi.wxss"; [代码] 大功告成!So easy! 云函数与调试 云函数即在云端(服务器端)运行的函数。在物理设计上,一个云函数可由多个文件组成,占用一定量的 CPU 内存等计算资源;各云函数完全独立;可分别部署在不同的地区。开发者无需购买、搭建服务器,只需编写函数代码并部署到云端即可在小程序端调用,同时云函数之间也可互相调用。 一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中。当云函数被小程序端调用时,定义的代码会被放在 Node.js 运行环境中执行。我们可以如在 Node.js 环境中使用 JavaScript 一样在云函数中进行网络请求等操作,而且我们还可以通过云函数后端 SDK 搭配使用多种服务,比如使用云函数 SDK 中提供的数据库和存储 API 进行数据库和存储的操作,这部分可参考数据库和存储后端 API 文档。 云开发的云函数的独特优势在于与微信登录鉴权的无缝整合。当小程序端调用云函数时,云函数的传入参数中会被注入小程序端用户的 openid,开发者无需校验 openid 的正确性因为微信已经完成了这部分鉴权,开发者可以直接使用该 openid。 在本文的小程序里有个 todo 的案例,里面的 remove 使用了云函数,用于清空所有已完成的任务。 [图片] [代码]const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const _ = db.command exports.main = async (event, context) => { try { return await db.collection('todo').where({ done: true }).remove() } catch (e) { console.error(e) } } [代码] [图片] 不过最新的IED,云函数支持了本地调试功能,感兴趣的可以点击【阅读原文】了解下。 相关链接 源码地址: https://github.com/Tencent/omi/tree/master/packages/omi-cloud 官方教程: https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html 小程序端 API 文档 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/ 服务端 API 文档 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/
2019-04-11 - 微信小程序嵌入订阅号文章 点击阅读原文打不开
- 当前 Bug 的表现(可附上截图) - 预期表现 - 复现路径 - 提供一个最简复现 Demo 因为业务需要的原因,现在的小程序上面需要webview嵌入公众号的文章,但是这些公众号文章在小程序里面点击阅读原文之后,出现不支持打开非业务域名:https://mp.winxinbridge.com 请重新配置 请问这是微信小程序的bug吗
2019-04-08 - [kbone-ui]打通 H5/微信小程序 多端UI库
一开始做 kbone-ui 的初衷是为了减少 kbone 的上手难度,需要提供多端的样式统一的 UI 组件库。现在,微信这边已经有了 weui 公共样式库来支持 Web 端的对外展示,其中,小程序本身基础组件也是由 weui 重构团队来做的。所以,为了达到这个目标,kbone-ui 的方式是以小程序内置组件和拓展组件为对齐目标, 使用 weui 样式提供 H5 和 小程序体验一致的跨端 UI 组件库。 [图片] 主要思路是通过 [代码]Page[代码] 和 [代码]Component[代码] 特有环境变量来区分 小程序 和 H5 的环境: [代码]// 判断小程序端 const ismp = typeof Page === “function” && typeof Component === “function” [代码] 对外按照小程序组件,以 [代码]K[代码] 为前缀暴露一个统一的组件名称,内部H5 端通过 weui 样式来适配,而小程序端直接使用内置组件。比如: [代码]// 对外暴露: <KButton> // 小程序端: <wx-button> // H5 端: <button> + weui [代码] 快速上手 kbone-ui 的第一期工作,已经基本完成。为了让用户快速上手,已经提供如下可以参考的基本信息: kbone-ui 文档 wechat-miniprogram.github.io/kbone/docs/ui/intro/quickstart.html kbone-ui 示例 wechat-miniprogram.github.io/kboneui/ui/ kbone-ui 仓库 github.com/wechat-miniprogram/kbone-ui 现在的 UI 库是基于 Vue,考虑点主要是优先满足团队内部的基础开发和使用。后续会随着生态完善,计划提供对应 React 版本。 kbone-ui 和市面上大部分的其它 UI 库类似,提供了 codeSplit 和全局引用两种方式。 加载全部组件内容,并引入 weui 样式库: [代码]import KboneUI from 'kbone-ui' import 'kbone-ui/lib/weui/weui.css' Vue.use(KboneUI) [代码] 使用按需引入: [代码]import KButton from 'kbone-ui/lib/KButton.js' import KView from 'kbone-ui/lib/KView.js' import 'kbone-ui/lib/weui/weui.css' Vue.use(KButton) Vue.use(KView) [代码] UI 原理 kbone-ui 目前是基于 kbone 来实现的小程序和 Web 端的同构方案。基本方案是通过 [代码]KView[代码] 组件来模拟大部分交互 UI 的功能组件内容,比如像 [代码]KActionSheet[代码]、[代码]KToast[代码]、[代码]KToptips[代码] 等。 另外,考虑到 Web 端和小程序端的差异,kbone-ui 需要对三类组件来进行跨平台实现。 KView(div) 组件: 直接通过 KView + css 的方式来模拟一些常用组件,比如像 [代码]progress[代码]、[代码]rich-text[代码]、[代码]icon[代码]、[代码]ActionSheet[代码] 等。这些实现起来比较容易,可以直接通过 KView 实现两端复用 表单组件: 对于一些表单组件,kbone 默认是直接支持 [代码]input[type=“xx”][代码] 来模拟,不过,像 switch, editor 等还是需要通过 [代码]wx-xxx[代码] 组件来针对特有平台调用。 视图组件: swiper, picker-view 这类在 View 层具有强 UI 交互的组件,很难做到两端通用。主要小程序是双线程模型,用户自定义组件无法独立运行在 View 层。对于这类组件,只能通过 Web 端向小程序端同属性适配的方式来做。 最近 kbone-ui 的版本更新还加上了,比较重要的三个视图层组件:swiper、movable-view、scroll-view,以及其他交互组件 slider、dialog [图片] 整体来说,kbone-ui 切入的角度和 taro、mpvue 等跨端式的方式不太一样,使用 kbone-ui 可以在不脱离已有框架(Vue, React)下,实现多端开发目的,而不需要像 taro/mpvue 之类需要重新学一遍语法和框架。目前 kbone-ui 还处于比较早期状态,前期打算是对齐微信小程序实现好用易用组件,后续,也会持续维护提供更多更好用的组件。
2020-01-20 - 针对新手很容易出现理解误区的微信小程序订阅消息模块
1. 写在前面 微信小程序下架了模板消息功能,取而代之的是订阅消息功能。这个订阅消息目前又分为「一次性订阅」和「永久订阅」。使用订阅消息也有一段时间了,感觉对新手订阅消息很容易让新开发者进入一个理解的误区,这里觉得有必要说出来 2. 理解误区 很多新手认为,只要用户勾选了小程序端订阅消息弹出时底部的「总是保持以上选择…」后,就可以「为所欲为」的不限次数的推送订阅消息给用户了。如下图: [图片] 3. 正确理解 如果你使用的「一次性订阅」模板(目前发现绝大多数开发者都是只能用一次性的,因为永久性的订阅消息申请门槛太高),那么勾选底部的「总是…」这个并不代表以后可以直接推送了。官方原话wx.requestSubscribeMessage的介绍里是这样写的: 3.1 官方说明 wx.requestSubscribeMessage(Object object) 基础库 2.8.2 开始支持,低版本需做兼容处理。 调起客户端小程序订阅消息界面,返回用户订阅消息的操作结果。当用户勾选了订阅面板中的“总是保持以上选择,不再询问”时,模板消息会被添加到用户的小程序设置页,通过 wx.getSetting 接口可获取用户对相关模板消息的订阅状态。 注意事项 一次性模板 id 和永久模板 id 不可同时使用。 低版本基础库2.4.4~2.8.3 已支持订阅消息接口调用,仅支持传入一个一次性 tmplId / 永久 tmplId。 2.8.2 版本开始,用户发生点击行为或者发起支付回调后,才可以调起订阅消息界面。 2.10.0 版本开始,开发版和体验版小程序将禁止使用模板消息 fomrId。 3.2 重点关注 这里重点关注第7条:「用户发生点击行为或者发起支付回调后,才可以调起订阅消息界面。」这就意味着你需要在用户主动点击某个组件是触发调用wx.requestSubscribeMessage方法再次订阅,订阅后,你才可以「为所欲为」推送一次模板消息,注意只能一次。下次再想推送时,需要用户再次点击触发wx.requestSubscribeMessage。 4. 破局方案 目前订阅消息功能,就是这么个情况,所以针对这个情况的替代方案有以下 4.1 永久性订阅消息 如果能达到申请「永久性订阅」消息的模板的门槛,那自然是极好的,直接用永久性模板「为所欲为」。 4.2 使用服务号的模板消息替代 比较常用的是使用公众号服务号的模板消息代替小程序的订阅消息功能,公众号的模板消息功能限制就比订阅号好多了,基本上可以「为所欲为」的推送。但是这个方案有个致命的运营成本:必须要用户关注公众号,还有小程序要跟公众号同一主体并绑定在开放平台下。同时开发成本有所增加,要采用unionId机制来打通小程序跟公众号的openId。这个具体的实现方案,大家有兴趣的话可以讨论下。笔者目前就是用这种方案的。 5. 几个注意点 5.1 官方提示 订阅消息如果选择选择‘总是保持以上选择,"不再询问"后的设置问题: 目前是选择‘总是保持以上选择,"不再询问"后,可以在设置中开启或拒绝接收,但不会再次拉起授权弹窗 6. 长期性订阅消息 请参考官方最新文档: 小程序模板消息能力调整通知 | 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/00008a8a7d8310b6bf4975b635a401 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 7.题外话 鉴于被戴上各种「刷赞,冲级,让社区点赞“通货膨胀”」等等一些恶毒字眼(最近多了个职业回复的「雅称」),各种帽子戴得,做一个开发爱好者积极分享和解决各种问题太难了,姑且不论咱写一篇文章需要截图多少,单单排版就得废掉俺多少时间哈,很受伤,所以本人决定在微信开放者社区封笔。你看到是俺最后一篇发表在微信开放社区的文章。如果你想继续查看俺的一些文章可以私聊我。我会在其他平台保持继续创作。bye-bye~ 8. 最最重要的来了 看完后觉得有用记得点赞~~ ↓点赞处↓
2020-09-04 - CSV/Excel文件一键转成云开发数据库的json文件
使用Excel导入云开发的数据库,数据量比较大的时候会出现一些问题,我们可以将Excel转成CSV文件,让CSV的第一行为字段名(要是英文哦),然后使用以下代码将CSV文件转成json文件。 第一步,安装Nodejs环境,然后使用vscode新建一个 csv2json.js 的文件,将下面的代码拷贝进来; 第二步,在vscode的资源管理器里右键csv2json.js,在终端中打开,然后输入命令 npm install csvtojson replace-in-file 第三步,把要转化的csv文件放在同一个目录,这里换成你的文件即可,也就是下面的china.csv换成你的csv文件; 第四步,后面的代码都不用管,然后打开vscode终端,输入 node csv2json.js 执行,就会生成两个文件,一个是json文件,一个是可以导入到云开发数据库的data.json //用vscode打开文件之后,npm install csvtojson replace-in-file const csv=require('csvtojson') const replace = require('replace-in-file'); const fs = require('fs') const csvFilePath='china.csv' //把要转化的csv文件放在同一个目录,这里换成你的文件即可 //后面的代码都不用管,然后打开vscode终端,就会生成两个文件,一个是json文件,一个是可以导入到 csv() .fromFile(csvFilePath) .then((jsonObj)=>{ // console.log(jsonObj); var jsonContent = JSON.stringify(jsonObj); console.log(jsonContent); fs.writeFile("output.json", jsonContent, 'utf8', function (err) { if (err) { console.log("保存json文件出错."); return console.log(err); } console.log("JSON文件已经被保存为output.json."); fs.readFile('output.json', 'utf8', function (err,data) { if (err) { return console.log(err); } var result = data.replace(/},/g, '}\n').replace(/\[/,'').replace(/\]/,'') fs.writeFile('data.json', result, 'utf8', function (err) { if (err) return console.log(err); }); }); }); })
2019-12-25 - 升级订阅消息必知的 5 个细节|附实战教程
作者:李碧成 [图片] 前言微信官方为提升小程序模板消息的使用体验,调整了模板消息的下发条件。原有的模板消息将升级为「订阅消息」,而模板消息接口于 2020 年 4 月 10 日下线(在 2020 年 1 月 10 日以后新发布的小程序只能使用订阅消息),届时将无法再使用原接口推送模板消息,因此需要开发者及时进行调整。 [图片] 模板消息与订阅消息的区别无论是模板消息,还是现在新的订阅消息,发送小程序消息都是通过三步完成: 获取模板 ID(即创建模板)获取下发的权限发送消息从步骤来看,只有「获取下发的权限」这一步是有变化的,其余都是相同的。 模板消息当用户在小程序内完成特定的交互行为(支付或提交表单行为)来收集 formid,后续利用该 formid 可以在 7 天内任意时间给该用户推送模板消息。 发送模板消息一定要携带 formid推送的有效时间为 7 天用户只能被动选择接收,下发的权利掌握在开发者手上订阅消息当用户在小程序内点击特定按钮后会弹出申请订阅弹窗,同意后小程序可在后续任意时间给该用户推送服务通知。 发送订阅消息需要用户先进行授权授权一次可发送一条服务通知,可以重复授权,每一次授权都会单独保存为一条记录推送时间不受限制用户自主选择接受,下发的权利掌握在用户手上小结之前的「模板消息」下发是不需要用户授权的,理论上可以设计成用户点击一次就获得一次权限,这个过程中用户是无感知的,只要有了 formid,在公共模板库里有的都能下发,且数据类型没有限制,用户收到什么消息完全取决于开发者。 现在的「订阅消息」更像是一个开关,需要用户主动点击授权之后才能获取下发消息权限,用户接不接收消息,接收什么订阅消息,决定权在用户手里。另外订阅消息还严格控制了数据类型和长度,不符合要求的将无法下发。(血泪教训啊,并且你只有在添加模板之后才能知道这个字段是属于什么类型,对于带变量的模板消息千万要注意变量是否符合字段要求) 订阅消息使用场景「模板消息」与「订阅消息」虽然都是为了召回用户以及推送消息提醒,但由于模板消息推送的时间限制过于严格,相对于一些服务周期较长的小程序来说,7 天的限制不能提供完整的服务。 例如机票类的小程序,用户从订票到出行,这间隔很大可能是超过 7 天的,如果想给用户发送“航班延误提醒”等消息,很有可能是无法实现的。因此,对于低频、长线服务的小程序来说,「订阅消息」是非常重要的。 另外,有少量的小程序会把「模板消息」当成营销工具,用来推送广告,诱导用户点击,这极大影响用户的使用体验。「订阅消息」的上线完美的弥补了这两个问题。 与「模板消息」不同,现在是需要用户授权订阅后才能下发消息,对于刚需的服务场景,例如外卖小程序的“外卖派送消息”以及电商小程序的“到货通知”等场景,订阅率都是比较高的。 而非刚需的服务场景,则可以对用户进行“引导订阅”,所以首先要让用户主动触发订阅。例如,对电商小程序来说,可以引导用户订阅心仪商品的“降价通知”;而内容类小程序,可以引导用户订阅其感兴趣的话题等。 升级订阅消息的防坑指南1.调起订阅弹窗触发用户订阅,微信小程序提供的 api 是:wx.requestSubscribeMessage,触发条件必须是用户点击行为(bindtap 事件)或发起支付回调后才能调起订阅消息界面。像 form 表单的 bindsubmit 事件就不行,如果原先在 form 表单中获取 formid 要升级成订阅消息,这里提供两个思路参考: 将 form 表单的 bindsubmit 事件改成 bingtap 事件。手动获取 form 表单的数据,通过 bindtap 事件调起订阅消息界面,完成订阅后再提交表单。(适合表单数据量较少时用)在 bindsubmit 事件中增加引导用户订阅的弹窗。例如,使用 wx.showModal(回调函数是 bindtap 事件),通过用户是否确认来调起订阅消息界面,当完成订阅后再完成表单提交。2.调起订阅弹窗同时跳转页面当用户点击按钮触发订阅弹窗的时候,同时跳转页面的话,在安卓上是能正常在已跳转的页面显示订阅弹窗,在 iOS 上则不会正常显示,而是在上一个页面显示弹窗。所以从用户体验角度以及为了能正常收集到订阅记录,必须等完成订阅后才进行跳转。 3.用户勾选“总是保持以上选择,不再询问”当用户勾选了“总是保持以上选择,不再询问”时,那么将再也不会唤起这个弹窗。同时,如果选择“取消”,那么以后每次调用这个 api 的时候,都会自动拒绝;如果选择“允许”,则每次都会自动允许授权。 目前,可以使用 wx.getSetting() 来获取用户订阅消息的订阅状态,详情可查看官方文档。所需基础版本库为 2.10.0 ,即微信版本 7.0.9 及以上。 在一些特定元素触发申请订阅权限的条件下(比如上述通过 wx.showModal 来唤起订阅界面),可以根据订阅状态来判断是否触发。使用 wx.getSetting() ,可以获取用户是否有勾选“总是保持以上选择,不再询问”以及对该条订阅消息的订阅状态。当 wx.getSetting() 返回用户拒绝订阅该条订阅消息就不显示 wx.showModal 的引导订阅弹窗。 4.一次订阅可以收集几条订阅消息?wx.requestSubscribeMessage 一次调用最多可订阅 3 条消息,即 tmplIds 最多为 3 个。iOS 客户端 7.0.6 版本、Android 客户端 7.0.7 版本之后的一次性订阅/长期订阅才支持多个模板消息,iOS 客户端 7.0.5 版本、Android 客户端 7.0.6 版本之前的一次订阅只支持一个模板消息。详情可看官方文档 5.发送订阅消息的参数限制发送订阅消息必须严格按照「订阅消息参数值内容限制说明」来进行填写。 [图片] 创建模板的时候,根据选择的关键字不同,在发送订阅消息的时候,需按照上述表格的说明进行填写内容,如不符合参数限制的,则会发送失败。例如,图上的 name.DATA 是10 个以内的纯汉字或 20 个以内的纯字母或符号,如果参数值是 知晓云 2020 则会报 data.name1.value invalid 错误;phrase.DATA 的参数值如果超过 5 个汉字同样是报错。 通过以上的防坑指南,可以让小程序开发者少走些弯路,在 deadline 的最后一刻,也能轻松快速完成升级。接下来将通过知晓云的「知晓推送」,手把手教你如何接入订阅消息。 知晓推送打通不同平台间的消息推送障碍,运营者可以同时将通知推送至微信小程序、QQ 小程序、支付宝小程序、Android、iOS 等多个平台。无需编码,开箱即用,一站式完成用户触达。轻松完成消息推送、用户转化、数据分析等多个层面的工作。 接入订阅消息开发者可以在知晓推送页面进入订阅消息接入向导: [图片] 前三步与接入微信类似,详情可查看微信小程序接入,在「接入 SDK」处需要填入 AppScret,AppSecret 用于开通模板消息服务,在小程序后台“设置 - 开发设置”中查看。[图片] [图片] 根据引导进行上报订阅消息。[图片] 通过校验后即完成接入。[图片] 获取模板 ID[图片] [图片] 在模板库中搜索合适的模板,点击选用,选择需要的关键词后提交即可完成模板创建。 [图片] 在「我的模板」可以查看、添加和删除模板,在这里获取模板 ID。 获取下发权限微信小程序提供了 wx.requestSubscribeMessage 接口来发起申请订阅权限界面。 上报订阅状态的接口知晓云提供了 wx.BaaS.subscribeMessage(options) SDK 来进行收集订阅状态。 参数说明[图片] Subscription [图片] 用户发生点击行为或者发起支付回调后,调起订阅消息界面,通过 SDK 上报订阅状态。 示例代码在函数内我们会调用微信 API wx.requestSubscribeMessage 申请发送订阅权限,弹窗出来后,我们会收到 success 回调,当用户在弹窗同意订阅后,即上报订阅结果为 accept ,将订阅的 template_id (模板 ID)和 subscription_type(订阅类型)存入 subscription 数组中,最后在调用 SDK 进行上报即可。 [图片] 目前只支持一次性订阅需要上报订阅结果为 accept 的模版 ID[图片] 在「订阅记录」查看和操作收集到的订阅记录。 发送订阅消息在线发送通过触发器触发云函数发送在线发送[图片] 通过在「订阅消息」给用户在线发送订阅消息。填入所需的内容,可以选择立即发送或定时发送。 智能过滤[图片] 在筛选目标用户时,建议开发者使用智能过滤服务,以保证模板消息的触达率和转化率。 通过触发器触发创建触发器[图片] 点击「引擎」-「触发器」即可创建新的触发器。 触发类型:即触发源,每个触发器都只能选定一种触发类型,不同的触发类型可执行的动作选项也不一样。 设置条件[图片] 当选定触发类型后,开发者需要指定触发条件来触发后续动作的执行,触发条件与触发类型一一相关,当满足触发条件的时候即可触发该触发器。 设置动作[图片] 动作类型选择「发送订阅消息」,与在线发送类似,填入相关内容,通过模板校验后即可完成触发器创建。如果所示,updated_at 存的是字符串,可以使用 {{updated_at | date:"Y年m月d日 H:i"}} 这种 filter 形式进行转换。另外可以点击「添加动作」继续设置动作。 更多内容可查看知晓云-触发器教程 通过云函数发送创建云函数[图片] 通过「引擎」-「云函数」-「添加」即可完成云函数的创建。 示例代码[图片] 编写完云函数后点击保存即完成云函数的修改。 通过触发器搭配云函数使用 [图片] 当需要发送的订阅消息逻辑较为复杂时,可以通过编写云函数进行触发。 详情可参考开发文档中的章节《使用云函数发送订阅消息》 注意事项发送订阅消息必须严格按照「订阅消息参数值内容限制说明」来进行填写,「在线发送」以及「触发器」设置动作中已有模板对应的参数值限制。如参数值错误,将导致发送失败,发送详情可在「知晓推送」-「日志」处查看。 访问「知晓云」,让你的小程序开发快人一步 扫描下方二维码,关注知晓云公众号,回复「学技术」,即可获得更多小程序开发教程。 [图片]
2020-01-13 - 微信小程序答题页——swiper渲染优化及swiper分页实现
前言 swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案 这里实现了如下功能和细节: 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验示例动图 [图片] 截图 [图片] [图片] 问题原因 当swiper-item数量很多的时候,会出现性能问题 我实现了一个答题小程序,在一次性加载100个swipe-item的时候,低端手机页面渲染时间达到了2000多ms 也就是说在进入答题页的时候,会卡顿2秒多去加载这100个swiper-item 思考问题 那我们能不能让他先加载一部分,然后滑动以后再去改变item的数据,让swiper一直保持一定量的swiper-item? 注意到官方文档有这么两个属性可以利用,我们可以开启衔接滑动,然后再bindchange方法中去修改data [图片] 1、保证swiper-item的数量固定,加载大量数据时,优化渲染效率 假设我们请求到的数据的为list,实际渲染的数据为swiperList 我们现在给他就固定3个swiper-item,前后滑动的时候去替换数据 正向滑动的时候去替换滑动后的下一页数据,反向滑动的时候去替换滑动后的上一页数据 当我们知道了要替换的条件,我们便可以去替换数据了 但是我们应该考虑到临界值的问题,如果当前页是list第一项和最后一项该怎么办,向左向右滑是不是得禁止啊 这边是判断没数据会让它再弹回去 2、记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页 有很多时候,我们是从某一项直接进来的,比如说上次答题答到了第五题,我这次进来要直接做第六题 那么我们需要去初始化这个swiperList,让它当前页、上一页、下一页都有数据 3、答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验 从答题卡选择index,那就不仅仅是滑动上下页了,它可以跳转到任何页,所以也采用类似初始化swiperList的方法 swiper切换动画我这边是默认250ms,但是发现有时候从答题卡点击回来,你在答题卡点击的下一项不知道会从左还是从右滑过来 体验真的很差,一开始不知道怎么禁掉动画,其实在跳转到答题卡页的时候把duration设为0就可以了 然后在答题卡页的unload方法中恢复 关键点: 在固定3个swiper-item的同时,要保证我们可以有办法来替代微信自带swiper的current属性和change方法 swiper-limited-load使用方法及说明: 将components中的swiper-limited-load复制到您的项目中在需要的页面引用此组件,并且创建自己的自定义组件item-view在初始化数据时,为你的list的每一项指定index属性具体可以参照项目目录start-swiper-limited-load中的用法说明:其它属性和swiper无异,你们可以自己单独添加你们需要的属性总结 一开始很头疼,为什么微信小程序提供的这个swiper,没去考虑这方面 然后在网上和社区找也没有一个特别好的解决方案。 后来想想,遇到需求就静下来解决吧。 项目地址:https://github.com/pengboboer/swiper-limited-load 如果错误,欢迎指出。 如有新的需求也可以提出来,如果有时间的话,我会帮你们完善。 如果能帮到你们,记得给一个star,谢谢。 ---补充 有很多朋友在评论区提到了分页的需求,抽时间写了一个分页的Demo和大家分享一下。 还是以答题为例,比如我们一共有500条数据,一页20条,可能需要如下功能,乍一看不就加了个分页,挺简单的,其实实现起来挺麻烦的,下面说一下思路和一些需要特别注意的点: 1、从其他页面跳转到答题页时,不光只能默认在第一题,可以是任意一题,比如第80题。 跳转到任意一题,那么需要我们根据index算出该数据在第几页,然后需要请求该页数据,最后显示对应的index。我的思路更注重用户体验,不可能是上滑或者下滑才开始去请求数据,一定是要用户滑动前提前请求好数据。所以起码要保证左右两侧在初始化那一刻都有数据。如果此题和它的上一题下一题都在同一页,那么我们只需要请求一页数据(第15题,那么只需请求第1页数据)。如果此题和它的上一题或者下一题不在同一页,那么我们可能需要请求两页数据。(第20题,那么需要请求第1页和第2页数据) 2、左滑、右滑没数据时,都可以加载新数据。直到滑到第一题或者最后一题。 如果我们初始化时是第24题,那么我们左滑到第21题时,就应该去请求第一页的数据。那么用户在看完21题时,再滑到20题,可能就根本不会感知到通过网络请求了数据。但是如果用户此刻滑动特别快:滑到21题时请求了网络,请求还没成功,就又向左滑了。那么我们需要限制用户的滑动,给用户一个提示:数据正在加载中。 3、从答题卡点击任意一题可以跳转到相应的题目,并且左右滑动显示正常数据 比如我们初始化是跳转到了第80题,不一会点击答题卡又要跳转到200题,一会又跳转到150题。各种无序操作,你也不知道用户要往哪里点。 一开始是想着维护一个主list,点到哪道题往list中添加这道题所在的当页的数据,但是还得判断这一页或者左滑右滑请求新一页的数据得往list的哪个位置添加。这来回来去乱七八糟的判断就很麻烦了,很容易出bug。而且list长度太长了以后insert的性能也不好。 后来就去想,要不答题卡点击任意一题都清空旧的list,然后请求新的数据,左右滑动没数据了再请求新的数据呗。但是这样很浪费资源,并且用户体验也不好,用户已经从第1题答到第200题了,这时用户从答题卡选择了一个25题,还得重新请求网络。而且200道题的数据都没了,那再选个26题,再重新请求网络?网络有延时不说,还浪费资源。 最后转念一想,这时候就需要弄一个缓存了。所以最终的解决方法就出来了:我们维护一个map,在网络请求成功后,在map中保存对应页的数据,同时我们维护一个主list来显示对应的题目。当我们在答题卡选择某一题目,就清空list,然后判断map中有没有该页的数据,如果有就直接拿来,没有就再去网络请求。这个处理方式,写法相对来说简单,不需要乱七八糟的判断,也不浪费资源,用户体验也很不错。 总结 以上就是一些思路和要注意的地方。这个Demo断断续续花了好几天时间写出来的。可能我说的比较啰嗦比较细,只是想让需要用到这个分页Demo的同学能理解我是如何实现的。 如果觉得能帮到你,记得给一个star,谢谢。同时如果这个demo有bug或者你们有新想法,欢迎提出来。
2021-01-07 - 小程序模板消息能力知多少
今天我们来聊聊小程序的开放能力——模板消息。 对于开发者说,模板消息并不陌生,它是官方提供的一个高效触达服务通知的渠道,能实现更佳的服务闭环。 为了更好地解决日常运营模板消息的困扰,小编收集了常见的FAQ供大家参考,请各位老板搬好板凳前排围观啦~ Q1:使用模板消息邀请或推荐浏览过、参与过的用户来点击/参与/报名新活动、新品上架、优惠资讯、转发开抢等 [图片] A1:涉嫌广告营销,属于违规。模板消息需要用户与业务之间存在实际交互行为,即时触发即时触达相应服务结果,没有用户上行的行为下发,骚扰用户是不可以的; Q2:使用模板消息提醒用户卡券即将到期,尽快核销使用等内容 [图片] A2:涉嫌卡券营销,属于违规。模板消息是一个即时通道,请接入官方提供的卡券接口,实现券、卡,积分相关的功能。 Q3:使用模板消息实现小程序内用户与用户之间的会话需求、点赞、评论等 [图片] A3:涉嫌骚扰倾向,属于违规。会话需求并不适用于模板消息,存在一定的言论风险及骚扰性质,且模板消息仅作为一种反馈相关服务结果的最终凭证依据。建议后续通过小程序聊天功能实现触达。 Q4:使用模板消息下发一些节日祝福、相册、视频、贺卡、笑话、段子、八卦等内容 [图片] A4:涉嫌骚扰倾向,属于滥用。图文/视频/相册/小说/祝福/段子/八卦更新类等属于持续不稳定触达的信息,伤害用户体验,目前已经在逐步回收存在违规行为账号的接口能力; Q5:使用模板消息持续推送当前进度、项目更新情况 [图片] A5:此类模板消息频率推送过高,存在骚扰用户倾向,我们会对该类模板消息监管,一经发现违规推送,或收到用户投诉,将会严厉惩处,轻则删除下架处理,重则封禁模板接口处理。 Q6:使用模板消息提醒用户上传步数、运动打卡、明星打榜、助力、守护等 [图片] A6:涉嫌骚扰倾向,属于滥用。此类模板消息频率推送过高,存在诱导用户倾向,我们会对该类模板消息监管,一经发现违规推送,或收到用户投诉,将会严厉惩处,轻则删除下架处理,重则封禁模板接口处理。 目前平台模板消息接口处罚采用阶梯制,初次违规核实后仅删模板id以示警告,开发者可自行通过公共模板库添加,若再次违规我们会按照梯度处罚规则进行限制新增模板能力或封禁模板接口能力,直至帐号完成整改,规范使用接口能力。
2019-11-21 - 小程序给图片加水印
1.效果(位置+当前时间) [图片][图片] 2.代码实现 <canvas class='canvas' style="width:{{canvasWidth}}px;height:{{canvasHeight}}px;top:{{canvasHeight*2}}px;"canvas-id"firstCanvas"></canvas> [图片][图片]
2020-01-07 - we.request请求封装
第一步在utils下面创建一个http.js, let sUrl = "https://www.baidu.com"; function getData (url, data, nb) { wxrequest({ url: sUrl + url, data: data, method: 'post', header: { // "ContentType": "json", //get请求时候"ContentType": "applicationwwwformurlencoded", //POST请求的时候这样写 'token': wx.getStorageSync('token') }, success: functionres) { returntypeof nb == "function" && nb(res.data) }, fail: functionres) { returntypeof nb == "function" && nb(res.data) } }) } module.exports = { req: getData } 第二步在app.js引入 let http = require ('utils/http.js') //封装请求res: { req: http.req //这里配置我们需要的方法 }, 然后在你需要的页面直接应用 let data = { }//data是你要传的参数 app.res.req('app-web/userproject/list', data, (res) => { }) 代码片段https://developers.weixin.qq.com/s/Pk6Pffmq7Gey
2020-01-09 - html-canvas 生成小程序分享图
简介 基于 HTML 和 CSS 实现 Canvas 绘图。 项目地址 代码片段:https://developers.weixin.qq.com/s/9zFHKdmh7De2 原理 构建虚拟DOM 树,依据 CSS 规范计算样式,使用 CSS 盒模型对 DOM 进行布局,计算出所有元素的位置。最后将 DOM 树通过 Canvas Api 进行绘制。 小程序开发工具内运行 demo [代码]git clone https://github.com/alexayan/html-canvas.git npm i npm run watch [代码] 已支持的 CSS 属性 margin,margin-left,margin-top,margin-right,margin-bottom,padding,padding-left,padding-top,padding-right,padding-bottom,width,height,border,border-left,border-top,border-right,border-bottom,border-width,border-style,border-color,border-left-style,border-left-color,border-left-width,border-top-style,border-top-color,border-top-width,border-right-style,border-right-color,border-right-width,border-bottom-style,border-bottom-color,border-bottom-width,color,display,background-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-left-radius,border-bottom-right-radius,box-sizing,font,font-style,font-variant,font-weight,font-stretch,font-size,line-height,font-family,text-align,position,overflow,overflow-x,overflow-y,top,left,right,bottom,z-index demo canvas-draw.html [图片]
2020-01-08 - Wxml2Canvas -- 快速生成小程序分享图通用方案
Wxml2Canvas库,可以将指定的wxml节点直接转换成canvas元素,并且保存成分享图,极大地提升了绘制分享图的效率。目前被应用于微信游戏圈、王者荣耀、刺激战场助手等小程序中。 github地址:https://github.com/wg-front/wxml2canvas 一、背景 随着小程序应用的日渐成熟,多处场景需要能够生成分享图便于用户进行二次传播,从而提升小程序的传播率以及加强品牌效应。 对于简单的分享图,比如固定大小的背景图加几行简短文字构成的分享小图,我们可以利用官方提供的canvas接口将元素直接绘制, 虽然繁琐了些,但能满足基本要求。 对于复杂的分享图,比如用户在微信游戏圈发表完话题后,需要将图文混排的富文本内容生成分享图,对于这种长度不定,内容动态变化的图片生成需求,直接利用官方的canvas接口绘制是十分困难的,包括但不限于文字换行、表情文字图片混排、文字加粗、子标题等元素都需要一一绘制。又如王者荣耀助手小程序,需要将十人对局的详细战绩绘制成分享图,包含英雄数据、装备、技能、对局结果等信息,要绘制100多张图片和大量的文字信息,如果依旧使用官方的接口一步一步绘制,对开发者来说简直就是一场噩梦。我们急需一种通用、高效的方式完成上述的工作。 在这样的背景下,wxml2cavnas诞生了,作为一种分享图绘制的通用方案,它不仅能快速的绘制简单的固定小图,还能直接将wxml元素真实地转换成canvas元素,并且适配各种机型。无论是复杂的图文混排的富文本内容,还是展现形式多样的战绩结果页,都可以利用wxml2cavnas完美地快速绘制并生成所期望的分享图片。 二、Wxml2Canvas介绍及示例 1. 介绍 Wxml2Cavnas库,是一个生成小程序分享图的通用方案,提供了两种绘制方式: 封装基础图形的绘制接口,包括矩形、圆形、线条、图片、圆角图片、纯文本等,使用时只需要声明元素类型并提供关键数据即可,不需要再关注canvas的具体绘制过程; wxml直接转换成canvas元素,使用时传入待绘制的wxml节点的class类名,并且声明绘制此节点的类型(图片、文字等),会自动读取此节点的computedStyle,利用这些数据完成元素的绘制。 2. 生成图示例 下面是两张极端复杂的分享图。 2.1 游戏圈话题 [图片] 点击查看完整长图 2.2.2 王者荣耀战绩 [图片] 点击查看完整大图 三、小程序的特性及局限 小程序提供了如下特性,可供我们便捷使用: measureText接口能直接测量出文本的宽度; SelectorQuery可以查询到节点对应的computedStyle。 利用第一条,我们在绘制超长文本时便于文本的省略或者换行,从而避免文字溢出。 利用第二条,我们可以根据class类名,直接拿到节点的样式,然后将style转换成canvas可识别的内容。 但是和html的canvas相比,小程序的canvas局限性很多。主要体现在如下几点: 不支持base64图片; 图片必须下载到本地后才能绘制到画布上; 图片域名需要在管理平台加入downFile安全域名; canvas属于原生组件,在移动端会置于最顶层; 通过SelectorQuery只能拿到节点的style,而无法获取文本节点的内容以及图片节点的链接。 针对以上问题,我们需要将base64图片转换jpg或png格式的图片,实现图片的统一下载逻辑,并且离屏绘制内容。针对第五条,好在SelectorQuery可以获取到节点的dataset属性,所以我们需要在待绘制的节点上显示地声明其类型(imgae、text等),并且显示地传入文本内容或图片链接,后文会有示例。 四、Wxml2Canvas使用方式 1. 初始化 首先在wxml中创建canvas节点,指定宽高: [代码] <canvas canvas-id="share" style="height: {{ height * zoom }}px; width: {{ width * zoom }}px;"> </canvas> [代码] 引入代码库,创建DrawImage实例,并传入如下参数: [代码] let DrawImage = require('./wxml2canvas/index.js'); let zoom = this.device.windowWidth / 375; let width = 375; let height = width * 3; let drawImage = new DrawImage({ element: 'share', // canvas节点的id, obj: this, // 在组件中使用时,需要传入当前组件的this width: width, // 宽高 height: height, background: '#161C3A', // 默认背景色 gradientBackground: { // 默认的渐变背景色,与background互斥 color: ['#17326b', '#340821'], line: [0, 0, 0, height] }, progress (percent) { // 绘制进度 }, finish (url) { // 画完后返回url }, error (res) { console.log(res); // 画失败的原因 } }); [代码] 所有的数字参数均以iphone6为基准,其中参数width和height决定了canvas画布的大小,规定值是在iphone6机型下的固定数值; zoom参数的作用是控制画布的缩放比例,如果要求画布自适应,则应传入 windowWidth / 375,windowWidth为手机屏幕的宽度。 2. 传入数据,生成图片 执行绘制操作: [代码] drawImage.draw(data, this); [代码] 执行绘制时需要传入数据data,数据的格式分为两种,下面展开介绍。 2.1 基础图形 第一种为基础的图形、图文绘制,直接使用官方提供接口,下面代码是一个基本的格式: [代码] let data = { list: [{ type: 'image', url: 'https://xxx', class: 'background_image', // delay: true, x: 0, y: 0, style: { width: width, height: width } }, { type: 'text', text: '文字', class: 'title', x: 0, y: 0, style: { fontSize: 14, lineHeight: 20, color: '#353535', fontFamily: 'PingFangSC-Regular' } }] } [代码] 如上,type声明了要元素的类型,有image、text、rect、line、circle、redius_image(圆角图)等,能满足绝大多数情况。 class类名指定了使用的样式,需要在style中写出,符合css样式规范。 delay参数用来异步绘制元素,会把此元素放在第二个循环中绘制。 x,y用来指定元素的起始坐标。 将css样式与元素分离的目的是便于管理与复用。 此种方式每个元素都相互独立,互不影响,能够满足自由度要求高的情况,可控性高。 2.2 wxml转换 第二种方式为指定wxml元素,自动获取,下面是示例: [代码] let data = { list: [{ type: 'wxml', class: '.panel .draw_canvas', limit: '.panel' x: 0, y: 0 }] } [代码] 如上,type声明为wxml时,会查找所有类名为draw_canvas的节点,并且加入到绘制队列中。 class传入的第一个类名限定了查询的范围,可以不传,第二个用来指定查找的节点,可以定义为任意不影响样式展现的通用类名。 limit属性用来限定相对位置,例如,一个文本的位置(left, top) = (50, 80), class为panel的节点的位置为(left, top) = (20, 40),则文本canvas上实际绘制的位置(x, y) = (50 - 20, 80 -40) = (30, 40)。如果不传入limit,则以实际的位置(x, y) = (50, 80)绘制。 由于小程序节点元素查询接口的局限,无法直接获取节点的文本内容和图片标签的src属性,也无法直接区分是文本还是图片,但是可以获取到dataset,所以我们需要在节点上显示地声明data-type来指明类型,再声明data-text传入文字或data-url传入图片链接。下面是个示例: [代码] <view class="panel"> <view class="panel__img draw_canvas" data-type="image" data-url="https://xxx"></view> <view class="panel__text draw_canvas" data-type="text" data-text="文字">文字</view> </view> [代码] 如上,会查询到两个节点符合条件,第一个为image图片,第二个为text文本,利用SelectorQuery查询它们的computedStyle,分别得到left、top、width、height等数据后,转换成canvas支持的格式,完成绘制。 除此之外,下面的示例功能更加丰富: [代码] <view class="panel"> <view class="panel__text draw_canvas" data-type="background-image" data-radius="1" data-shadow="" data-border="2px solid #000"></view> <view class="panel__text draw_canvas" data-type="text" data-background="#ffffff" data-padding="2 3 0 0" data-delay="1" data-left="10" data-top="10" data-maxlength="4" data-text="这是个文字">这是个文字</view> </view> [代码] 如上,第一个data-type为background-image,表示读取此节点的背景图片,因为可以通过computedStyle直接获取图片链接,所以不需要显示传入url。声明data-radius属性,表示要将此图绘成乘圆形图片。data-border属性表示要绘制图片的边框,虽然也可以通过computedStyle直接获取,但是为了避免非预期的结果,还是要声明传入,border格式应符合css标准。此外,图片的box-shadow等样式都会根据声明绘制出来。 第二个文本节点,声明了data-background,则会根据节点的位置属性给文字增加背景。 data-padding属性用来修正背景的位置和宽高。data-delay属性用来延迟绘制,可以根据值的大小,来控制元素的层级,data-left和data-top用来修正位置,支持负值。data-maxlength用来限制文本的最大长度,超长时会截取并追加’…’。 此外,data-type还有inline-text,inline-image等行内元素的绘制,其实现较为复杂,会在后文介绍。 五、Wxml2Canvas实现原理 1. 绘制流程 整个绘制流程如下: [图片] 因为小程序的限制,只能在画布上绘制本地图片,所以统一先对图片提前下载,然后再绘制,为了避免图片重复下载,内部维护一个图片列表,会对相同的图片链接去重,减少等待时间。 2. 基本图形的实现 基础图形的绘制比较简单,内部实现只是对基础能力的封装,使用者不用再关注canvas的绘制过程,只需要提供关键数据即可,下面是一个图片绘制的实现示例: [代码] function drawImage (item, style) { if(item.delay) { this.asyncList.push({item, style}); }else { if(item.y < 0) { item.y = this.height + item.y * zoom - style.height * zoom; }else { item.y = item.y * zoom; } if(item.x < 0) { item.x = this.width + item.x * zoom - style.width * zoom; }else { item.x = item.x * zoom; } ctx.drawImage(item.url, item.x, item.y, style.width * zoom, style.height * zoom); ctx.draw(true); } } [代码] 如上,x,y值坐标支持传入负值,表示从画布的底部和右侧计算位置。 3. Wxml转Canvas元素的实现 3.1 computedStyle的获取 首先需要获取wxml的样式,代码示例如下: [代码] query.selectAll(`${item.class}`).fields({ dataset: true, size: true, rect: true, computedStyle: ['width', 'height', ...] }, (res) => { self.drawWxml(res); }) [代码] 3.2 块级元素的绘制 对于声明为image、text的元素,默认为块级元素,它们的绘制都是独立进行的,不需要考虑其他的元素的影响,以wxml节点为圆形的image为例,下面是部分代码: [代码] if(sub.dataset.type === 'image') { let r = sub.width / 2; let x = sub.left + item.x * zoom; let y = sub.top + item.y * zoom; let leftFix = +sub.dataset.left || 0; let topFix = +sub.dataset.top || 0; let borderWidth = sub.borderWidth || 0; let borderColor = sub.borderColor; // 如果是圆形图片 if(sub.dataset.radius) { // 绘制圆形的border if(borderWidth) { ctx.beginPath() ctx.arc(x + r, y + r, r + borderWidth, 0, 2 * Math.PI) ctx.setStrokeStyle(borderColor) ctx.setLineWidth(borderWidth) ctx.stroke() ctx.closePath() } // 绘制圆形图片的阴影 if(sub.boxShadow !== 'none') { ctx.beginPath() ctx.arc(x + r, y + r, r + borderWidth, 0, 2 * Math.PI) ctx.setFillStyle(borderColor); setBoxShadow(sub.boxShadow); ctx.fill() ctx.closePath() } // 最后绘制圆形图片 ctx.save(); ctx.beginPath(); ctx.arc((x + r), (y + r) - limitTop, r, 0, 2 * Math.PI); ctx.clip(); ctx.drawImage(url, x + leftFix * zoom, y + topFix * zoom, sub.width, sub.height); ctx.closePath(); ctx.restore(); }else { // 常规图片 } } [代码] 如上,块级元素的绘制和基础图形的绘制差异不大,理解起来也很容易,不再多述。 3.3 令人头疼的行内元素的绘制 当wxml的data-type声明为inline-image或者inline-text时,我们认为是行内元素。行内元素的绘制是一个难点,因为元素之前存在关联,所以不得不考虑各种临界情况。下面展开细述。 3.3.1 纯文本换行 对于长度超过一行的行内元素,需要计算出合适的换行位置,下图所示的是两种临界情况: [图片] [图片] 如上图所示,第一种情况为最后一行只有一个文字,第二种情况最后一行的文字长度和宽度相同。虽然长度不同,但都可通过下面代码绘制: [代码] let lineNum = Math.ceil(measureWidth(text) / maxWidth); // 文字行数 let sinleLineLength = Math.floor(text.length / lineNume); // 向下取整,保证多于实际每行字数 let currentIndex = 0; // 记录文字的索引位置 for(let i = 0; i < lineNum; i++) { let offset = 0; // singleLineLength并不是精确的每行文字数,要校正 let endIndex = currentIndex + sinleLineLength + offset; let single = text.substring(currentIndex, endIndex); // 截取本行文字 let singleWidth = measureWidth(single); // 超长时,左移一位,直至正好 while(singleWidth > maxWidth) { offset--; endIndex = currentIndex + sinleLineLength + offset; single = text.substring(currentIndex, endIndex); singleWidth = measureWidth(single); } currentIndex = endIndex; ctx.fillText(single, item.x, item.y + i * style.lineHeight); } // 绘制剩余的 if(currentIndex < text.length) { let last = text.substring(currentIndex, text.length); ctx.fillText(last, item.x, item.y + lineNum * style.lineHeight); } [代码] 为了避免计算太多次,首先算出大致的行数,求出每行的文字数,然后移位索引下标,求出实际的每行的字数,再下移一行继续绘制,直到结束。 3.3.2 非换行的图文混排 [图片] 上图是一个包含表情图片和加粗文字的混排内容,当使用Wxml2Canvas查询元素时,会将第一行的内容分为五部分: 文本内容:这是段文字; 表情图片:发呆表情(非系统表情,image节点展现); 表情图片:发呆表情; 文本内容:这也; 加粗文本内容:是一段文字,这也是文字。 对于这种情况,执行查询computedStyle后,会返回相同的top值。我们把top值相同的元素聚合在一起,认为它们是同一行内容,事实也是如此。因为表情大小的差异以及其他影响,默认规定top值在±2的范围内都是同一行内容。然后将top值的聚合结果按照left的大小从左往右排列,再一一绘制,即可完美还原此种情况。 3.3.3 换行的图文混排 当混排内容出现了换行情况时,如下图所示: [图片] 此时的加粗内容占据了两行,当我们依旧根据top值归类时,却发现加粗文字的left值取的是第二行的left值。这就导致加粗文字和第一部分的文字的top值和left值相同,如果直接绘制,两部分会发生重叠。 为了避免这种尴尬的情况,我们可以利用加粗文字的height值与第一部分文字的height值比较,显然前者是后者的两倍,可以得知加粗部分出现了换行情况,直接将其放在同组top列表的最后位置。换行的部分根据lineHeight下移绘制,同时做记录。 最后一部分的文本内容也出现了换行情况,同样无法得到真正的起始left值,并且其top值与上一部分换行后的top值相同。此时应该将他的left值追加加粗换行部分的宽度,正好得到真正的left值,最后再绘制。 大多数的行内元素的展现形式都能以上述的逻辑完美还原。 六、总结 基于基础图形封装和wxml转换这两种绘制方式,可以满足绝大多数的场景,能够极大地减少工作量,而不需要再关注内部实现。在实际使用中,二者并非孤立存在,而更多的是一起使用。 [图片] 如上图所示,对于列表内容我们利用wxml读取绘制,对于下部的白色区域,不是wxml节点内容,我们可以使用基础图形绘制方式实现。二者的结合更加灵活高效。 目前Wxml2Canvas已经在公司内部开源,不久会放到github上,同时也在不断完善中,旨在实现更多的样式展现与提升稳定性和绘制速度。 如果有更好的建议与想法,请联系我。
2019-02-28 - 小程序流量主、小程序广告、分成策略、结算方式介绍
小程序广告分类 小程序广告按照不同的展示形式分为:banner广告、视频广告、激励式广告、插屏广告 小程序banner广告 产品特点 支持多种广告样式与推广目标 与公众号底部广告一致的样式标准,便于在小程序流量场景中高效投放 流量主结合不同小程序特点,自定义广告展现场景,提升广告的场景融合度 适用场景 banner广告展现场景由小程序流量主自定义,根据各自小程序的特点,灵活设置展现页面与位置。banner广告的常见展现场景为:文章页 - 文章末尾、详情页 - 页面底部、信息流 - 信息流顶部或信息流之间。 示例 [图片] 小程序激励式广告 产品特点 视频默认有声播放 支持视频素材(横屏/竖屏)投放 流量主结合不同小程序(包含小游戏类目)特点,涉及与小程序产品流程、小游戏情节及玩法深度结合的互动广告形式,并通过激励合理引导用户查看视频广告 适用场景 激励式广告展现场景由小程序流量主自定义,根据各自小程序的特点,深度结合场景与情节,用户在查看广告后可获得相应激励奖励。 激励式广告的常见展现场景为:积分/金币奖励、解锁新功能、通关/进阶、道具体验。 示例 [图片] 小程序插屏广告 产品特点 插屏广告,是指小程序在特定场景切换时以卡片方式弹出的广告形式 当用户触发流量主指定场景时,插屏广告就会自动向用户展现,同时支持用户随时关闭插屏广告 适用场景 插屏广告展现场景由小程序流量主自定义,无论是竖屏小程序还是横屏小程序,都可以基于用户实际的操作场景接入插屏广告组件。 插屏广告的常见展现场景为:切换tab时、流程结束页面、视频播放停顿页面等。 示例 [图片] 小程序广告分成策略 不同广告类型的分成模式不同具体如下所示 Banner广告的分成模式如下: 根据用户的每一次广告点击,平台收取广告费用并与流量主分成。100W元以下广告流水,按照5:5分成,100W元以上广告流水,按照3:7分成(流量主3成),分成不设封顶。 实际分成以后台“数据统计”页面展示为准 视频广告、插屏广告、激励视频广告 按照每一千次有效的广告曝光,平台收取广告费用并与流量主分成。100W元以下广告流水,按照5:5分成,100W元以上广告流水,按照3:7分成(流量主3成),分成不设封顶 实际分成以后台“数据统计”页面展示为准。 小程序广告收入 小程序广告当天收入会在第二天推送到微信中 [图片] 单个小程序收入明细(mp后台可查看) [图片] [图片] 当天收入汇总(mp后台可查看) [图片] 小程序广告收入结算方式 为提升流量主收入结算效率,从2019年11月收入结算开始,做出以下调整: 1. 流量主收入结算周期从1个月缩短至半个月,即每月结算2次,出具2份结算单。 2. 我们将在次月1号和次月15号之前,分别发送上半月和下半月的结算单。 3. 企业、组织等非个人主体还未开具发票的收入(包括历史月份),须按结算单金额分别开具发票,若是相同月份,则可合并开票。 如:10月结算单500元,11月结算单上半月300元、11月下半月300元。 流量主可按结算单金额开具3张发票,也可按月分别开具500元和600元两张发票,但不可合并开具一张1100元的发票。 备注 以上多参考微信官方 https://mp.weixin.qq.com/promotion/readtemplate?t=notice/detail_page&time=1575340587¬ice_id=634169
2020-04-14 - 【优化】利用函数防抖和函数节流提高小程序性能
大家好,上次给大家分享了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[代码]
2019-02-22 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 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即可。
2022-01-20 - 留言、原创等多项功能介绍
1、原创功能 一直以来,微信都希望真正有价值的内容能够出现在微信公众平台里面。此次,微信公众平台上线原创声明功能,是提供给原创作者保护原创的机会。原创声明实为文章原创者在微信公众平台的主动自发行为。原创文章在原创声明成功后,微信公众平台会对原创文章添加“原创”标识,当其他用户在微信公众平台发布已进行原创声明的文章时,系统会为其注明出处。 申请方法: 国内注册公众号逐步开放原创功能,陆续覆盖所有公众号,请您耐心等待。查看方法:登录公众平台后,左侧会出现“原创声明”功能入口,无需开通直接可以使用。 2、留言功能 评论功能可以使公众帐号内文章被关注用户留言,加强公众帐号与关注用户之间的互动。 3、赞赏功能 公众号图文消息小费只支持个人类型公众号,且目前属于内测阶段,暂不支持申请,建议您后续留意公众平台相关功能上线情况,感谢您的支持! 4、页面模版功能 页面模版功能,是给公众号创建行业网页的功能插件。公众号可选择行业模版,导入控件和素材生成网页,对外发布。 申请方法: 目前页面模版可通过公众平台=》功能=》添加功能插件=》点击“页面模版”进行申请并获得使用权限后,即可开始使用。 使用方法: 在“页面模版”页添加模版,设置好页面模版内容后可将链接复制到自定义菜单进行发布。 5、模版消息 通过模板消息接口,公众号能向关注其账号的用户发送预设模板的消息。模板消息仅用于公众号向用户发送重要服务通知,如信用卡刷卡通知,商品购买成功通知等。请勿使用模板发送垃圾广告或造成骚扰,请勿使用模板发送营销类消息,请在符合模板要求的场景时发送模板。 申请方法: 目前支持微信认证的服务号可使用模板消息功能。 进入公众平台=》功能=》添加功能插件=》点击“模板消息”进行申请并获得使用权限后,即可开始使用。 6、设备功能 设备功能是微信硬件平台为智能硬件厂商提供的接入能力,通过设备功能,智能硬件可以使用微信硬件平台提供的各种服务,实现如微信运动、微信控制、微信配网等场景。 申请开通设备功能需满足以下条件:必须是通过微信认证的公众号。 温馨提示: 1)微信认证资质审核通过后,即可申请硬件设备功能(接入微信硬件,暂不需要缴费); 2)开发者中心设备功能接口需申请后才可获得(公众号通过资质认证后即可拥有申请的资格)。 7、城市服务 目前的开放范围:目前仅支持已经认证过的政府/事业单位公众号。这两类公众号可于公众平台管理端看到城市服务插件的申请入口。 8、摇一摇电视 微信摇电视,是微信在电视场景下的行业解决方案,通过摇一摇实现用户和电视的双向连接、实时互动。微信摇电视支持电视台节目进行实时互动、内容运营、效果统计等,可帮助电视台: 1) 提升收视率;微信平台,连接亿级观众。 2) 延展电视内容;多屏互动,丰富节目内容。 3)实现商业价值;移动终端,拓展商业空间。 更详情请点击帮助中心查看:点击这里 申请方法: 通过电脑端登录:点击这里,注册申请开通摇电视账号。 9、语音功能 语音功能,可在文章中添加录制好的语音,且突破一分钟限制。目前该功能还属于内测阶段,暂不支持申请,建议您后续留意微信公众平台相关功能上线情况,感谢您对微信的支持! 10、公众平台移动版功能 微信公众平台手机版是手机上管理公众号的工具,目前只支持收发用户消息、管理文章留言、管理文章赞赏。公众平台公告上有详细通知,您也可以直接点击这里查阅即可,希望能帮您,谢谢。
01-19 - 开源的垃圾分类小程序
1.第一阶段了解 一开始了解小程序不知道是在某年某月的新闻中看见的,感觉这是一个流量入口,自己仅仅是了解,没有真的动手去学习开发,主要是工作时期比较多一直没有时间。现在想想是真的来迟了,好在是我没有忘记 2. 第二阶段学习 学习的时候感觉和vue 有一点点相似的感觉。 花了一段时间看的开发文档,感觉文档清晰,思路简洁。很多人喜欢看视频,我到是不是很喜欢。因为看视频的速度特别慢。只要文档足够细基本看完就差不多了,当然了论坛我也是常来的那种,看看大家都出问题在什么地方,一些坑的问题只要是开发程序就会有。哈哈 3. 第三阶段开发 开发都是照着例子,比如在学习云开发的时候,默认的那个项目就完全把主要的云开发内容演示了。我基本都是照壶画瓢。 4. 沉入其中 现在做项目之前我首先想想要不要开发app.公司现在我首先说服领导优先开发小程序。公司的其他可以改的我基本都说服大家优先小程序。我们公司点饭系统就被我改为小程序版本(内部订餐)。 5. 我的开源小程序 代码本身不太具有多少价值,程序员么开源精神,哈哈哈哈哈 地址github Garbage
2020-05-09 - 微信开放社区成长中心
[图片] [图片]
2019-12-23 - 为什么图片链接可正常访问但image组件加载不出来图片?
因为 image 控件的图片拉取本质上是 web 上的 backgroundImage,很多时候是由于图片不规范(content-type / length / 是否302跳转等 )导致拉取不成功,最终表现为加载不出图片。关于这一块我们在持续优化中
2021-12-17 - 微信公众号:订阅号与服务号的区别对比
[图片] 很多人在申请公众号时都不知道该申请订阅号还是服务号,下面我们就为大家详细介绍下这两种公众号的区别: [图片] 不同微信公众号对比 订阅号是定位于消息内容资讯的发布,所以他每天都能群发一条消息,但是只能集合在订阅号消息文件夹里展示,用户需要点击文件夹才能看到具体的消息。 服务号是定位于需要有交互能力服务内容的企业,他的接口开发能力比较强,一个月只能群发4条消息,但消息是直接展示在用户列表里,同时认证服务号还能进行模板消息的发送。 订阅号与服务号的展示区别: [图片] 谁可以申请订阅号、服务号 [图片] 不同主体的公众号申请数量 [图片] 微信订阅号服务号功能、能力区别: [图片] 通过上图我们可以了解,没有认证前的订阅号和服务号的能力是差不多的,只是在外链方面,普通服务号可以在菜单里设置外链,而普通订阅号只能通过在菜单里设置微信图文消息,然后用微信图文消息的“阅读原文”方式访问外链。 而认证之后的订阅号和服务号的区别就很大了,比如说网页获取用户信息(识别用户)、模板消息(给用户发通知)、获取地理位置(LBS服务)、微信支付(在线交易),这些订阅号都是不支持的。 需要注意的是,现在订阅号已经不能升级成服务号了,所以需要我们在申请前就考虑好申请订阅号还是服务号。 本文由云梁网络(https://www.yunliangwang.com)原创发布,转载请注明出处。
2019-12-19 - setData设置数组时,模板中绑定到标签属性的数组长度不会改变
为什么一定要提问题或者发文章,我就想报BUG wxml代码 [代码]<view class="container"> <view wx:for="{{arr}}" wx:key="{{index}}" data-length="{{arr.length}}" bindtap="onTap">{{item}} {{arr.length}}</view> </view> [代码] js代码 [代码]Page({ data: { arr: [1, 2, 3] }, onTap: function() { this.setData({ 'arr[3]': 1 }) console.log(this.data.arr) } }) [代码] 可以看到点击之后标签内的arr.length被更新了,但是前三个标签的[代码]data-length[代码]属性仍然为3
2019-12-20 - 如何实现一个自定义数据版省市区二级、三级联动
社区可能有其他的方案了,但是再分享下吧,给有需要的童鞋。 效果图: [图片] 额,这个视频转GIF因为社区上传不了大图,所以剪了一部分,具体的效果还是直接工具打开代码片段预览吧~ 第一步:你的页面JSON引入该组件: [代码]{ "usingComponents": { "city-picker": "/components/cityPicker/index" } } [代码] 第二步:你的页面WXML引入该组件 [代码]<city-picker visible="{{visible}}" column="2" bind:close="handleClick" bind:confirm="handleConfirm" /> [代码] 第三步:你的页面JS调用 [代码]// 显示/隐藏picker选择器 handleClick() { this.setData( visible: !this.data.visible }) }, // 用户选择城市后 点击确定的返回值 handleConfirm(e) { const { detail: { provinceName = '', provinceId = '', cityName, cityId='', areaName = '', areaId = '' } = {} } = e this.setData({ cityId, cityName, areaId, areaName, provinceId, provinceName }) } [代码] 组件属性 属性 默认值 描述 visible false 是否显示picker选择器 column 3 显示几列,可选值:1,2,3 values [0, 0, 0] 必填,默认回填的省市区下标,可选择具体省市区后查看AppData的regionValue字段 close function 点击关闭picker弹窗 confirm function 点击选择器的确定返回值 confirm: 属性 默认值 描述 provinceName 北京市 省份名称 provinceId 110000 省份ID cityName 市辖区 城市名称 cityId 110100 城市ID areaName 东城区 区域名称 areaId 110000 区域Id 至于怎么获取你想默认城市的下标,可以滑动操作下选中省市区后,点击确定后查看appData里的regionValue的值。 以上就是一个自定义数据版本的省市区二级、三级联动啦,老规矩,结尾放代码片段。 https://developers.weixin.qq.com/s/F9k9cTmT7LAz
2022-07-20 - 小程序-SAUI-首页改版-仿筛选功能
在这,先谢谢某设计师的免费赞助设计~~~ 哈哈 先不说其它,直接上图先。 [图片] [图片] 下面是仿携程,同程做的筛选部分。实现了基本功能,还在继续优化当中… [图片] [图片] 以上,算是最近的重大更新吧。 对于改版,用的时间不多,因为我的样式是通过配置样式表去整体控制的,大概如下。我希望做到的是,通过简单的配置文件,就可能改变整个网站的风格 [图片] 在筛选的功能上,用的精力比较多。 筛选目前是用list组件来生成的,弹出层通过update来显示跟隐藏。目前弹出层做了两种形式,a 纯粹的列表组件 b tabs组件,简单的数据存储 [代码]const screenListData = [ { title: { title: '筛选', itemClass: 'item-arrow' }, dot: [{ ......... itemClass: 'ss-scrore-list-pop', aim: 'onClosePop?id=1' }], tap: 'onScrennItem?btn=true&id=1', }, { title: '价格星级' }, { title: { title: '智能排序', itemClass: 'item-arrow' }, dot: [{ '@list': { $$id: 'listx', data: [], itemClass: 'item-check', listClass: 'bg-fff ss-left' }, itemClass: 'ss-scrore-list-pop', aim: 'onClosePop?id=3' }], tap: 'onScrennItem?id=3', } ] Pager({ data: { screenList: Pager.list({ data: screenListData, listClass: 'ss-scrore-list color-333', itemClass: 'flex-1' }) }, onScrennItem: function (e, query, inst) { //点击筛选列表 头部 const $screenList = this.getElementsById('screenList') const $screenPop = this.getElementsById('screenPop') const $id = query['id'] $screenList.reset().findAndUpdate(e, function(res) { res.itemClass += ' active' res.dot[0].itemClass += ' active' return res }) if ($id == 1){ const $testtabs = this.getElementsById('test-tabs') const $tabsbtn = this.getElementsById('tabs-btn') $testtabs.update({ data: app.hooks.getItem('saveScreenData') != undefined ? adapter.tabsFunc(data.tabsData, app.hooks.getItem('saveScreenData')) : adapter.tabsFunc(data.tabsData) }) $tabsbtn.removeClass('disN') } else { const $listx = this.getElementsById('listx') $listx.update({ data: app.hooks.getItem('saveScreenListData') != undefined ? adapter.listxFunc(data.listx, app.hooks.getItem('saveScreenListData'), 'radio') :adapter.listxFunc(data.listx, '', 'radio') }) } }, }) [代码] [图片]
2019-09-05 - 使用 MobX 来管理小程序的跨页面数据
在小程序中,常常有些数据需要在几个页面或组件中共享。对于这样的数据,在 web 开发中,有些朋友使用过 redux 、 vuex 之类的 状态管理 框架。在小程序开发中,也有不少朋友喜欢用 MobX ,说明这类框架在实际开发中非常实用。 小程序团队近期也开源了 MobX 的辅助模块,使用 MobX 也更加方便。那么,在这篇文章中就来介绍一下 MobX 在小程序中的一个简单用例! 在小程序中引入 MobX 在小程序项目中,可以通过 npm 的方式引入 MobX 。如果你还没有在小程序中使用过 npm ,那先在小程序目录中执行命令: [代码]npm init -y [代码] 引入 MobX : [代码]npm install --save mobx-miniprogram mobx-miniprogram-bindings [代码] (这里用到了 mobx-miniprogram-bindings 模块,模块说明在这里: https://developers.weixin.qq.com/miniprogram/dev/extended/functional/mobx.html 。) npm 命令执行完后,记得在开发者工具的项目中点一下菜单栏中的 [代码]工具[代码] - [代码]构建 npm[代码] 。 MobX 有什么用呢? 试想这样一个场景:制作一个天气预报资讯小程序,首页是列表,点击列表中的项目可以进入到详情页。 首页如下: [图片] 详情页如下: [图片] 每次进入首页时,需要使用 [代码]wx.request[代码] 获取天气列表数据,之后将数据使用 setData 应用到界面上。进入详情页之后,再次获取指定日期的天气详情数据,展示在详情页中。 这样做的坏处是,进入了详情页之后需要再次通过网络获取一次数据,等待网络返回后才能将数据展示出来。 事实上,可以在首页获取天气列表数据时,就一并将所有的天气详情数据一同获取回来,存放在一个 数据仓库 中,需要的时候从仓库中取出来就可以了。这样,只需要进入首页时获取一次网络数据就可以了。 MobX 可以帮助我们很方便地建立数据仓库。接下来就讲解一下具体怎么建立和使用 MobX 数据仓库。 建立数据仓库 数据仓库通常专门写在一个独立的 js 文件中。 [代码]import { observable, action } from 'mobx-miniprogram' // 数据仓库 export const store = observable({ list: [], // 天气数据(包含列表和详情) // 设置天气列表,从网络上获取到数据之后调用 setList: action(function (list) { this.list = list }), }) [代码] 在上面数据仓库中,包含有数据 [代码]list[代码] (即天气数据),还包括了一个名为 [代码]setList[代码] 的 action ,用于更改数据仓库中的数据。 在首页中使用数据仓库 如果需要在页面中使用数据仓库里的数据,需要调用 [代码]createStoreBindings[代码] 来将仓库中的数据绑定到页面数据中,然后就可以在页面中直接使用仓库数据了。 [代码]import { createStoreBindings } from 'mobx-miniprogram-bindings' import { store } from './store' Page({ onLoad() { // 绑定 MobX store this.storeBindings = createStoreBindings(this, { store, // 需要绑定的数据仓库 fields: ['list'], // 将 this.data.list 绑定为仓库中的 list ,即天气数据 actions: ['setList'], // 将 this.setList 绑定为仓库中的 setList action }) // 从服务器端读取数据 wx.showLoading() wx.request({ // 请求网络数据 // ... success: (data) => { wx.hideLoading() // 调用 setList action ,将数据写入 store this.setList(data) } }) }, onUnload() { // 解绑 this.storeBindings.destroyStoreBindings() }, }) [代码] 这样,可以在 wxml 中直接使用 list : [代码]<view class="item" wx:for="{{list}}" wx:key="date" data-index="{{index}}"> <!-- 这里可以使用 list 中的数据了! --> <view class="title">{{item.date}} {{item.summary}}</view> <view class="abstract">{{item.temperature}}</view> </view> [代码] 在详情页中使用数据仓库 在详情页中,同样可以使用 [代码]createStoreBindings[代码] 来将仓库中的数据绑定到页面数据中: [代码]import { createStoreBindings } from 'mobx-miniprogram-bindings' import { store } from './store' Page({ onLoad(args) { // 绑定 MobX store this.storeBindings = createStoreBindings(this, { store, // 需要绑定的数据仓库 fields: ['list'], // 将 this.data.list 绑定为仓库中的 list ,即天气数据 }) // 页面参数 `index` 表示要展示哪一条天气详情数据,将它用 setData 设置到界面上 this.setData({ index: args.index }) }, onUnload() { // 解绑 this.storeBindings.destroyStoreBindings() }, }) [代码] 这样,这个页面 wxml 中也可以直接使用 list : [代码]<view class="title">{{list[index].date}}</view> <view class="content">温度 {{list[index].temperature}}</view> <view class="content">天气 {{list[index].weather}}</view> <view class="content">空气质量 {{list[index].airQuality}}</view> <view class="content">{{list[index].details}}</view> [代码] 完整示例 完整例子可以在这个代码片段中体验: https://developers.weixin.qq.com/s/YhfvpxmN7HcV 这个就是 MobX 在小程序中最基础的玩法了。相关的 npm 模块文档可参考 mobx-miniprogram-bindings 和 mobx-miniprogram 。 MobX 在实际使用时还有很多好的实践经验,感兴趣的话,可以阅读一些其他相关的文章。
2019-11-01 - 仿Ant Design的异步请求工具
小程序提供了异步请求的函数wx.request({}),很方便,但在实际使用上代码还是不够灵活。于是想起之前阿里大佬写的antd模板里面的例子就挺不错的,所以山寨了一份下来。 有些复杂的逻辑被我忽略了,有欠缺部分欢迎提出指点😁😁😁 首先是全局配置文件:根目录/config.js [代码]export default { tabs: ['home', 'article', 'profile'], // 远端地址,上线切换 // serverPath: 'https://xxx.com/xx/', // 本地地址 serverPath: 'http://127.0.0.1:9090/wm/', // 图片服务器 imagePath: 'https://xxx.com/xximg/', } [代码] 请求工具:utils/request.js [代码]import configs from '../config' // 普通请求,文件下载请求类似这样写一个即可 export default function request(options) { /** * @param {object} data 传参 * @param {string} method 请求方法 * @param {string} url * @param {object} etcs request函数的其他属性,我暂时没用到 */ const { data, method, url, server, etcs } = options return new Promise(function (resolve, reject) { wx.request({ // 如果是config之外的服务器地址,则自定义传入 url: server ? server + url : configs.serverPath + url, method: method, ...etcs, data: data, success(response) { resolve({ success: true, statusCode: response.statusCode, ...response.data }) }, fail(errors) { reject({ error: true, success: false, statusCode: errors.statusCode }) } }) }) } [代码] 统一api集合:services/api.js [代码]export default { queryArtical: 'api/wx/artical', getArtical: 'api/wx/artical/:id', updateArtical: 'PUT api/wx/artical/:id', createArtical: 'POST api/wx/artical', deleteArtical: 'DELETE api/wx/artical/:id' } [代码] 请求构造器:services/index.js [代码]import request from '../utils/request' import api from './api' /** * 用法 */ // import api from '../../services/index' // const { queryArtical } = api; // const params = {}; // queryArtical({ ...params }).then(obj => { // console.log(obj); // }) /** * 匹配URL中的动态传参,并转换为body中对应的值 * @param {string} url * @param {object} body */ function catchDynamicParam(url, body) { const reg = new RegExp(/:(\w+)/g); const matches = url.match(reg); let keys = []; if (matches) { for (let param of matches) { const key = param.split(':')[1]; const value = body[key]; url = url.replace(param, value ? value : 'undefined'); keys.push(key); } } const result = { url, keys }; return result; }; /** * 异步请求函数构造器 * @param {string} queryUrl 请求路径 * @param {object} params 传参 */ function call(queryUrl, params) { const matches = queryUrl.split(' '); let method; let url; if (matches.length > 1) { method = matches[0]; url = matches[1]; } else { method = 'GET'; url = matches[0]; } const catcher = catchDynamicParam(url, params); url = catcher.url; const catchKeys = catcher.keys; for (const key of catchKeys) { if (key in params) { delete params[key]; } } return request({ url: url, method: method, data: params }); } // 生成上面api的请求函数 const APIFunction = {} for (const key in api) { APIFunction[key] = function (params) { return call(api[key], params) } } export default APIFunction [代码] 使用 [代码]import api from '../../services/index' const { queryArtical } = api; const params = { userId: 1 }; queryArtical({ ...params }).then(obj => { console.log(obj); }) [代码]
2020-07-31 - 微信小程序图表组件 wx-f2,源于 F2,专为移动而生
F2,专为移动而生的可视化解决方案,特为大家提供了微信小程序端版本,体积小巧,性能卓越,底层基于图形语法,可以提供非常丰富的图表类型。 可以使用微信扫描以下二维码先体验一番: [图片]
2018-04-16 - 微信开放社区运营规范
微信开放社区运营规范 微信团队一直致力于将微信打造成一个强大的、全方位的服务工具。通过全面开放的能力,连接更多的开发者和用户。微信开放社区是微信为用户提供的内容问答社区。使用微信开放社区服务(以下简称“微信开放社区”或本“功能”),微信开放社区用户(以下简称“你”)必须阅读并遵守《微信公众平台服务协议》、《微信小程序平台服务条款》、《微信小程序平台运营规范》以及腾讯为此制定的专项规则等。 本《微信开放社区运营规范》(以下简称,本“运营规范”)是在上述协议及规则基础上进行解释和说明,相关内容和举例旨在帮助开发者更加清晰地理解和遵守相关协议和规则,以便能够更加顺利地在微信开放社区进行运营,而不是修改或变更上述协议及规则中的任何条款。 一、发布内容规范: 1. 你不得在微信开放社区发布、分享传播国家法律法规禁止的以下内容: 1.1 反对宪法所确定基本原则,危害国家安全、泄露国家秘密、颠覆国家政权、破坏国家统一、损害国家荣誉和利益。 1.2 反政府、反社会,或存在煽动性的涉政言论、散布谣言,扰乱社会秩序,破坏社会稳定。 1.3 煽动民族仇恨、民族歧视、破坏民族团结、破坏国家宗教政策、宣扬邪教和封建迷信。 1.4 展示人或动物被杀戮、致残、枪击、针刺或其他伤害的真实图片,描述暴力或虐待儿童的,或包含宣扬暴力血腥。 1.5 淫秽、色情或低俗信息,包括但不限于: 1.5.1 应用中含有淫秽、色情内容,如招嫖、寻找一夜情、性伴侣等内容; 1.5.2 传播以色情为目的的情色文字、情色视频、情色漫画等形式的内容; 1.5.3 传播非法色情交易的信息; 1.5.4 直接或隐晦表现性行为、具有挑逗性或者侮辱性内容,或以带有性暗示、性挑逗的语言描述性行为、性过程、性方式的; 1.5.5 应用中传播非法性药品、性保健品、性用品和性病治疗营销信息等相关内容的; 1.5.6 应用中传播相关部门禁止传播的色情和有伤社会风化的文字、音视频内容的。 1.6 赌博、竞猜和抽奖类信息,包括但不限于: 1.6.1 应用中传播以虚拟货币或真实货币直接进行押输赢、竞猜、参与赌博等内容的; 1.6.2 应用运营过程中可将游戏分数或金币等兑换成真实货币或实物奖励的; 1.6.3 应用运营过程中可根据玩家输赢结果进行抽水、分成等后果的; 1.6.4 其他被认定为宣扬赌博色彩的行为。 1.7 含有虚假、欺诈或冒充类内容,包括但不限于虚假红包、虚假活动、虚假宣传,仿冒腾讯官方或他人业务,可能造成微信用户混淆等内容的。 1.8 任何召集、鼓动犯罪或有明显违背社会善良风俗行为的内容。 1.9 任何违反《计算机信息网络国际联网安全保护管理办法》、《互联网信息服务管理办法》、《互联网电子公告服务管理规定》、《维护互联网安全的决定》或其他国家法律法规规定的内容。 2. 你不得在微信开放社区发布、分享和传播侵犯他人合法权利的信息: 2.1 包含公然侮辱或者诽谤他人,损害他人名誉或商誉内容的。 2.2 包含使用或揭露他人身份信息、照片、隐私,侵害他人肖像权、隐私权合法权益的。 2.3 未经授权,擅自使用他人商标、著作权以及其他侵犯他人知识产权内容的。 2.4 未经授权,擅自使用他人拥有商标权的标识、图像等内容。 2.5 未经授权,使用或传播他人的原创图文消息或其他有合法著作权权利的内容,侵犯他人知识产权。 2.6 依靠抄袭、模仿等手段使用他人拥有商标权或著作权权益的内容,侵犯他人权益的。 3. 你不得在微信开放社区发布、分享和传播违反平台相关规则的信息: 3.1 内容主要为营销、互推或广告用途,包括但不限于空白广告位、招商广告位或作为第三方平台通过应用中的营销、广告模块盈利等。 3.2 内容包含多级分销信息,发布分销信息诱导用户进行分享、传播或直接参与。 3.3 对用户产生误导、严重破坏用户体验,损害用户利益的谣言类内容。 3.4 传播骚扰信息、恶意营销和垃圾信息等内容。 3.5 其他涉及违法违规或违反平台相关协议、规则的内容。 二、使用行为规范 你不得在微信开放社区从事以下行为: 1. 恶意破坏微信开放社区正常秩序,包括但不限于恶意灌水、踩贴、刷流量、刷评论、利用自定义栏目或其他形式传播病毒、垃圾广告、非法信息等。 2. 未经腾讯许可或授权,擅自转载、或爬取微信开放社区文章和内容。 3. 重复发布干扰正常用户体验的内容。包括但不限于: 3.1 重复发表同一文章的; 3.2 重复的回答内容多次发布在不同问题下的; 3.3 频繁发布难以辨识涵义影响阅读体验的字符、数字等无意义乱码的; 3.4 骚扰他人,以评论、@他人、私信等方式对他人反复发送重复或者相似的诉求。 4. 发表不符合版面主题,或者无内容的灌水内容、或者发表色情,猥亵,谩骂、包含人身攻击,诽谤等的内容。 5. 使用不雅或不恰当ID和昵称,头像,个性签名等。 6. 从事违反与腾讯签订的、任何形式的服务协议、平台协议、功能协议的行为。 7. 从事违反腾讯为相关软件、服务、功能等而制定的管理、运营规范、规则的行为。 8. 从事非法商业活动或任何违反国家法律法规的行为。 三、服务商规范 1. 服务平台入驻规则 1.1 服务平台服务商,指为微信小程序提供模版开发、定制化开发、插件、借口能力等服务的服务提供方,包括但不限于具有第三方平台的平台型服务商和定制型服务商。 1.2 任何有意愿为小程序提供开发服务的开发者/企业都可以申请成为小程序服务商。目前阶段,满足以下条件的服务商,经平台审核后可接入微信开放社区-服务平台,小程序用户即可在服务平台中获取关服务商信息: (1)符合上述1.1条定义; (2)在微信开放社区中具有关联第三方平台的企业主页; (3)两个自然周内,单个服务商累计7天符合要求,可在下两个自然周内被搜出。 上述条件中,“第三方平台”[1]为全网发布并经审核通过,并应满足以下条件: (1)服务商业务活跃性:短期内授权一定数量的小程序,并发布上线; (2)小程序活跃度:已上线的小程序保证持续用户活跃; (3)服务商小程序预审能力:无不良审核记录。 1.3 平台有权根据产品策略和业务发展需求,对上述服务商接入的要求和规范进行不时地调整。对于不满足条件的服务商,微信有权进行动态调整。 2. 服务商行为规范 2.1 服务商入驻微信开放社区服务平台,不视为微信向你提供任何授权或进行任何合作。你不得以微信、微信代理商或微信官方合作伙伴的名义从事任何经营活动。你保证,不得以微信或微信代理商的名义拓展合作伙伴或代理商,未经微信同意,不得对外使用“微信”、“微信公众平台”、“微信开放平台”和“微信开放社区”的品牌和LOGO,或进行任何造成或可能造成微信用户混淆的对外宣传行为。 2.2 服务商不得从事或间接协助从事以下任一活动,否则平台可能会对你作出处理,终止合作,追究服务商的法律责任: 2.2.1 在企业主页等位置存在夸大、虚假、承诺性、绝对化等违反广告法等法律法规的描述的; 2.2.2 以微信、微信代理商或微信官方合作伙伴的名义从事经营活动或对外宣传的; 2.2.3 将你在平台的技术资源等转包、分包、转让、有偿或无偿提供给其他主体,或者以代理、加盟等形式拓展合作伙伴并收取费用的; 2.2.4 把服务商资源用于《微信开放平台公众帐号第三方平台开发者服务协议》和本规则约定范围以外的用途的; 2.2.5 服务态度恶劣,陈述有误引起小程序商户误解,或未向商户提供经承诺的服务的; 2.2.6 虚构事实,隐瞒真相,出现违规行为时不配合或阻碍平台正常调查的; 2.3 服务商不得在主页等位置,发布任何干扰微信公众平台正常运营,或侵犯其他用户或第三方合法权益的内容,否则平台可能会对你作出处理。 2.4 服务商使用服务平台向小程序用户提供服务,须严格遵守适用的法律法规。 2.5 服务商入驻服务平台,不视为服务商获得平台的任何授权,也不代表平台对服务商的能力、商誉进行任何形式、任何种类的明示或暗示的推荐或担保,包括但不限于商业适售性、特定用途适用性等。服务商对于其在服务平台向小程序用户提供服务的行为必须自行承担相应法律责任和风险。 四、违规处理 1. 如你违反上述使用规范,微信开放社区有权视开发者的违规程度给予警告、删帖、暂停账号使用、注销帐号等处罚措施,并依法追究你的法律责任。 2. 如服务商违反上述服务商规范,在平台收到对你的违规投诉或平台知悉你存在违规情形后,平台将向你发送电子邮件或站内信或通过其他有效通知方式,服务商应在平台发出通知之日起3个工作日内提供书面回复,如服务商认为违规行为不成立或已整改,应提供相应证明;如服务商未能在限定期间内书面回复,或虽提出异议但未能提供充分证据,视为违规行为成立。平台有权依据相关事实及服务商书面回复内容(如有)进行独立判断,并以电子邮件形式将认定结果进行告知。[T1] 五、免责声明 1. 微信开放社区不对你发表的内容、回答或评论的正确性进行保证。你在微信开放社区发表的内容仅表明其个人的立场和观点,并不代表微信的立场或观点。作为内容的发表者,你需自行对所发表内容负责,因所发表内容引发的一切纠纷,由你承担全部法律及连带责任。微信不承担任何法律及连带责任。 2. 微信开放社区知识库内容(即带有微信官方标识的内容)为微信开放社区官方发布内容。你清楚了解并同意,该等内容为微信开放社区根据平台运营情况或相关行业经验,在特定时间针对某具体问题提供的参考性内容,该等内容非实时更新,亦可能过期失效,仅供用户参考。未经腾讯许可或授权,用户不得擅自转载或爬取微信开放社区知识库内容。 3. 服务商入驻服务平台,不视为服务商获得平台的任何授权,也不代表平台对服务商的能力、商誉进行任何形式、任何种类的明示或暗示的推荐或担保,包括但不限于商业适售性、特定用途适用性等。服务商必须自行对其通过服务平台提供的服务承担相应法律责任和风险。 4. 你理解并同意,在使用微信开放社区时,需自行承担如下腾讯不可掌控的风险,包括但不限于: 4.1 由于受到计算机病毒、木马或其他恶意程序、黑客攻击的破坏等不可抗拒因素可能引 起的信息丢失、泄漏等损失和风险; 4.2 用户或腾讯的电脑软件、系统、硬件和通信线路出现故障导致的服务终端、数据丢失以及其他的损失和风险; 4.3 用户操作不当或通过非腾讯授权的方式使用本服务带来的损失和风险; 4.4 用户发布的内容被他人转发、分享,因此等传播可能带来的风险和责任; 4.5 由于网络信号不稳定、网络服务中断或其他原因所引起的无法使用微信开放社区、页面打开速度慢等风险; 4.6 其他腾讯无法控制或合理预见的情形。 六、遵守当地法律监管 1. 你在使用微信开放社区服务的过程中应当遵守当地相关的法律法规,并尊重当地的道德和风俗习惯。如果你的行为违反了当地法律法规或道德风俗,你应当为此独立承担责任。 2. 你应避免因使用本服务而使腾讯卷入政治和公共事件,否则腾讯有权暂停或终止对你的服务。 七、动态文档 这是一份动态更新的文档,我们会根据新出现的问题、相关法律法规更新或产品运营的需要来对其内容进行修改并更新,制定新的规则,保证微信用户的体验。你应能反复查看以便获得最新信息,请定期了解更新情况。 微信团队
2019-12-27 - 周末6月30号,日活跃已经超过2K用户了,为啥还没开通流量主?
请问,周末6月30号,日活跃已经超过2K用户了,为啥还没开通流量主?另外开通视频广告需要什么条件?
2018-07-02 - 微信对话开放平台有奖活动
「微信对话开放平台」是以对话交互为核心, 为有客服需求的个人、企业和组织提供智能业务服务与用户管理能力的技能配置平台, 技能开发者可利用我们提供的工具自主完成客服机器人的搭建。 为了让更多开发者了解「微信对话开放平台」的服务能力,帮助大家在平台搭建AI机器人;同时,向其他开发者展示更多实用、有趣的对话落地场景。我们诚邀各位参与「微信对话开放平台」的首次有奖活动,并向用户及行业展示“对话即服务”的理念。 参与方式(二选一):奖品设置 完成调查问卷填写之后,写一篇关于介绍微信对话平台的文章,字数不限。 完成调查问卷填写之后,将微信对话平台的插件添加到您开发的小程序中。 调查问卷地址: https://wj.qq.com/s2/4796311/b56b 评奖方式 我们将根据作品的人气(浏览量、收藏、点赞、评论数等)及创意来评选获奖作品。 作品要求 内容不违反法律法规,规章及规范性文件,符合微信容规定与其他适用的协议、规则或规范要求; 内容必须为原创,严禁抄袭或洗稿; 投稿篇数不限,但雷同内容不能重复投稿。 奖品设置 特别奖1名,最佳文章奖5名,最佳小程序奖5名,创意奖15名,流量奖15名,参与奖50名。 特别奖:微信相框 [图片] 最佳文章奖/最佳小程序奖:茶海+玻璃垫套装 [图片] 创意奖/流量奖:微信U型枕 [图片] 参与奖:微信对话徽章 [图片] 活动周期 活动时间:2019年11月11日-11月29日 获奖公布时间:2019年12月2日 作品提交方式 请将最终作品以邮箱形式发送至:wechatopenai@tencent.com 邮件主题命名为:微信对话开放平台有奖活动 邮件内容包括: 文章链接或小程序二维码; 作者信息:姓名、微信号、QQ号、手机号码、奖品邮寄地址(一定要填写哦,否则获奖无法寄出奖品) 活动参考资料 开发者参考文档:https://developers.weixin.qq.com/doc/aispeech/platform/INTRODUCTION.html 小程序示例: [图片] 微信团队
2019-11-21 - 获取用户信息昵称,emoji显示有问题
- 当前 Bug 的表现(可附上截图) [图片] 昵称获取有问题,第一个图标应该是 🐷 [图片] - 预期表现 - 复现路径 获取有emoji图标的用户昵称,部分会有问题 - 提供一个最简复现 Demo
2018-12-21 - 关于微信授权获取昵称含Emoji表情引发的乱码问题总结
做过微信授权的小伙伴都可能会遇到获取用户昵称乱码问题,那是因为微信昵称中的含有SoftBank版本的Emoji表情。 如我的微信昵称: 正常显示为[图片]; 未处理Softbank及微信自定义表情显示为[图片]; 处理Softbank后显示为[图片]。 [图片] Emoji表情有很多种版本,其中包括Unified、DoCoMo、KDDI、SoftBank和Google,不同版本的Unicode代码并不一定相同。经研究,微信昵称中的Emoji表情截止目前(2019.12.10)已知支持三种版本: 1、SoftBank版本(网上一般称之为SB Unicode),如😂为E412; 2、Unified版本,如😂为1F602; 3、自定义表情版本,如[捂脸]。 举个例子,😂(喜极而泣)的各种编码如下: SoftBank:0000E412 Unified:0001F602(U+1F602) DoCoMo:0000E72A KDDI:0000EB64 Google:000FE334 UTF-8:F09F9882(%F0%9F%98%82) UTF-16BE:FEFFD83DDE02(\uD83D\uDE02) UTF-16LE:FFFE3DD802DE UTF-32BE:0000FEFF0001F602 UTF-32LE:FFFE000002F60100 Emoji表情代码表参阅:http://punchdrunker.github.io/iOSEmoji/table_html/index.html 对于SoftBank及微信自家定义的表情,需要做映射处理转换成标准的Unified版本的Emoji表情才能正常显示,否则就可能乱码。具体解决方案参见https://github.com/gzu-liyujiang/UnicodeEmoji SoftBank版本编码与Unified版本编码对应关系 { “E150”: “0001F68F”, “E030”: “0001F338”, “E151”: “0001F6BB”, “E152”: “0001F46E”, “E031”: “0001F531”, “E032”: “0001F339”, “E153”: “0001F3E3”, …省略… } SoftBank版本编码与标准Unicode编码对应关系 { “E150”: “\uD83D\uDE8F”, “E030”: “\uD83C\uDF38”, “E151”: “\uD83D\uDEBB”, “E152”: “\uD83D\uDC6E”, “E031”: “\uD83D\uDD31”, “E032”: “\uD83C\uDF39”, “E153”: “\uD83C\uDFE3”, …省略… } SoftBank版本编码与标准的Emoji字符表情的对应关系 { “E150”: “🚏”, “E030”: “🌸”, “E151”: “🚻”, “E152”: “👮”, “E031”: “🔱”, “E032”: “🌹”, “E153”: “🏣”, …省略… }
2019-12-11 - 小程序奇技淫巧之 -- 日志能力
日志与反馈 前端开发在进行某个问题定位的时候,日志是很重要的。因为机器兼容性问题、环境问题等,我们常常无法复现用户的一些bug。而微信官方也提供了较完整的日志能力,我们一起来看一下。 用户反馈 小程序官方提供了用户反馈携带日志的能力,大概流程是: 开发中日志打印,使用日志管理器实例 LogManager。 用户在使用过程中,可以在小程序的 profile 页面(【右上角胶囊】-【关于xxxx】),点击【投诉与反馈】-【功能异常】(旧版本还需要勾选上传日志),则可以上传日志。 在小程序管理后台,【管理】-【反馈管理】,就可以查看上传的日志(还包括了很详细的用户和机型版本等信息)。 这个入口可能对于用户来说过于深入(是的,官方也发现这个问题了,所以后面有了实时日志),我们小程序也可以通过[代码]button[代码]组件,设置[代码]openType[代码]为[代码]feedback[代码],然后用户点击按钮就可以直接拉起意见反馈页面了。利用这个能力,我们可以监听用户截屏的操作,然后弹出浮层引导用户主动进行反馈。 [代码]<view class="dialog" wx:if="{{isFeedbackShow}}"> <view>是否遇到问题?</view> <button open-type="feedback">点击反馈</button> </view> wx.onUserCaptureScreen(() => { // 设置弹窗出现 this.setData({isFeedbackShow: true}) }); [代码] LogManager 关于小程序的 LogManager,大概是非常实用又特别低调的一个能力了。它的使用方式其实和 console 很相似,提供了 log、info、debug、warn 等日志方式。 [代码]const logger = wx.getLogManager() logger.log({str: 'hello world'}, 'basic log', 100, [1, 2, 3]) logger.info({str: 'hello world'}, 'info log', 100, [1, 2, 3]) logger.debug({str: 'hello world'}, 'debug log', 100, [1, 2, 3]) logger.warn({str: 'hello world'}, 'warn log', 100, [1, 2, 3]) [代码] 打印的日志,从管理后台下载下来之后,也是很好懂: [代码]2019-6-25 22:11:6 [log] wx.setStorageSync api invoke 2019-6-25 22:11:6 [log] wx.setStorageSync return 2019-6-25 22:11:6 [log] wx.setStorageSync api invoke 2019-6-25 22:11:6 [log] wx.setStorageSync return 2019-6-25 22:11:6 [log] [v1.1.0] request begin 2019-6-25 22:11:6 [log] wx.request api invoke with seq 0 2019-6-25 22:11:6 [log] wx.request success callback with msg request:ok with seq 0 2019-6-25 22:11:6 [log] [v1.1.0] request done 2019-6-25 22:11:7 [log] wx.navigateTo api invoke 2019-6-25 22:11:7 [log] page packquery/pages/index/index onHide have been invoked 2019-6-25 22:11:7 [log] page packquery/pages/logs/logs onLoad have been invoked 2019-6-25 22:11:7 [log] [v1.1.0] logs | onShow | | [] 2019-6-25 22:11:7 [log] wx.setStorageSync api invoke 2019-6-25 22:11:7 [log] wx.setStorageSync return 2019-6-25 22:11:7 [log] wx.reportMonitor api invoke 2019-6-25 22:11:7 [log] page packquery/pages/logs/logs onShow have been invoked 2019-6-25 22:11:7 [log] wx.navigateTo success callback with msg navigateTo:ok [代码] LogManager 最多保存 5M 的日志内容,超过5M后,旧的日志内容会被删除。基础库默认会把 App、Page 的生命周期函数和 wx 命名空间下的函数调用写入日志,基础库的日志帮助我们定位具体哪些地方出了问题。 实时日志 小程序的 LogManager 有一个很大的痛点,就是必须依赖用户上报,入口又是右上角胶囊-【关于xxxx】-【投诉与反馈】-【功能异常】这么长的路径,甚至用户的反馈过程也会经常丢失日志,导致无法查问题。 为帮助小程序开发者快捷地排查小程序漏洞、定位问题,微信推出了实时日志功能。从基础库 2.7.1 开始,开发者可通过提供的接口打印日志,日志汇聚并实时上报到小程序后台。 使用方式如下: 使用 wx.getRealtimeLogManager 在代码⾥⾯打⽇志。 可从小程序管理后台【开发】-【运维中心】-【实时日志】进入日志查询页面,查看开发者打印的日志信息。 开发者可通过设置时间、微信号/OpenID、页面链接、FilterMsg内容(基础库2.7.3及以上支持setFilterMsg)等筛选条件查询指定用户的日志信息: [图片] 由于后台资源限制,实时日志使用规则如下: 为了定位问题方便,日志是按页面划分的,某一个页面,在onShow到onHide(切换到其它页面、右上角圆点退到后台)之间打的日志,会聚合成一条日志上报,并且在小程序管理后台上可以根据页面路径搜索出该条日志。 每个小程序账号每天限制500万条日志,日志会保留7天,建议遇到问题及时定位。 一条日志的上限是5KB,最多包含200次打印日志函数调用(info、warn、error调用都算),所以要谨慎打日志,避免在循环里面调用打日志接口,避免直接重写console.log的方式打日志。 意见反馈里面的日志,可根据OpenID搜索日志。 setFilterMsg 可以设置过滤的 Msg。这个接口的目的是提供某个场景的过滤能力,例如[代码]setFilterMsg('scene1')[代码],则在 MP 上可输入 scene1 查询得到该条日志。比如上线过程中,某个监控有问题,可以根据 FilterMsg 过滤这个场景下的具体的用户日志。FilterMsg 仅支持大小写字母。如果需要添加多个关键字,建议使用 addFilterMsg 替代 setFilterMsg。 日志开发技巧 既然官方提供了 LogManager 和实时日志,我们当然是两个都要用啦。 log.js 我们将所有日志的能力都封装在一起,暴露一个通用的接口给调用方使用: [代码]// log.js const VERSION = "0.0.1"; // 业务代码版本号,用户灰度过程中观察问题 const canIUseLogManage = wx.canIUse("getLogManager"); const logger = canIUseLogManage ? wx.getLogManager({level: 0}) : null; var realtimeLogger = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null; /** * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function DEBUG(file, ...args) { console.debug(file, " | ", ...args); if (canIUseLogManage) { logger!.debug(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } /** * * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function RUN(file, ...args) { console.log(file, " | ", ...args); if (canIUseLogManage) { logger!.log(`[${VERSION}]`, file, " | ", ...args); } realtimeLogger && realtimeLogger.info(`[${VERSION}]`, file, " | ", ...args); } /** * * @param {string} file 所在文件名 * @param {...any} arg 参数 */ export function ERROR(file, ...args) { console.error(file, " | ", ...args); if (canIUseLogManage) { logger!.warn(`[${VERSION}]`, file, " | ", ...args); } if (realtimeLogger) { realtimeLogger.error(`[${VERSION}]`, file, " | ", ...args); // 判断是否支持设置模糊搜索 // 错误的信息可记录到 FilterMsg,方便搜索定位 if (realtimeLogger.addFilterMsg) { try { realtimeLogger.addFilterMsg( `[${VERSION}] ${file} ${JSON.stringify(args)}` ); } catch (e) { realtimeLogger.addFilterMsg(`[${VERSION}] ${file}`); } } } } // 方便将页面名字自动打印 export function getLogger(fileName: string) { return { DEBUG: function(...args) { DEBUG(fileName, ...args); }, RUN: function(...args) { RUN(fileName, ...args); }, ERROR: function(...args) { ERROR(fileName, ...args); } }; } [代码] 通过这样的方式,我们在一个页面中使用日志的时候,可以这么使用: [代码]import { getLogger } from "./log"; const PAGE_MANE = "page_name"; const logger = getLogger(PAGE_MANE); [代码] autolog-behavior 现在有了日志组件,我们需要在足够多的地方记录日志,才能在问题出现的时候及时进行定位。一般来说,我们需要在每个方法在被调用的时候都打印一个日志,所以这里封装了一个 autolog-behavior 的方式,每个页面(需要是 Component 方式)中只需要引入这个 behavior,就可以在每个方法调用的时候,打印日志: [代码]// autolog-behavior.js import * as Log from "../utils/log"; /** * 本 Behavior 会在小程序 methods 中每个方法调用前添加一个 Log 说明 * 需要在 Component 的 data 属性中添加 PAGE_NAME,用于描述当前页面 */ export default Behavior({ definitionFilter(defFields) { // 获取定义的方法 Object.keys(defFields.methods || {}).forEach(methodName => { const originMethod = defFields.methods![methodName]; // 遍历更新每个方法 defFields.methods![methodName] = function(ev, ...args) { if (ev && ev.target && ev.currentTarget && ev.currentTarget.dataset) { // 如果是事件类型,则只需要记录 dataset 数据 Log.RUN(defFields.data.PAGE_NAME, `${methodName} invoke, event dataset = `, ev.currentTarget.dataset, "params = ", ...args); } else { // 其他情况下,则都记录日志 Log.RUN( defFields.data.PAGE_NAME, `${methodName} invoke, params = `, ev, ...args); } // 触发原有的方法 originMethod.call(this, ev, ...args); }; }); } }); [代码] 我们能看到,日志打印依赖了页面中定义了一个[代码]PAGE_NAME[代码]的 data 数据,所以我们在使用的时候可以这么处理: [代码]import { getLogger } from "../../utils/log"; import autologBehavior from "../../behaviors/autolog-behavior"; const PAGE_NAME = "page_name"; const logger = getLogger(PAGE_NAME); Component({ behaviors: [autologBehavior], data: { PAGE_NAME, // 其他数据 }, methods: { // 定义的方法会在调用的时候自动打印日志 } }); [代码] 页面如何使用 Behavior 看看官方文档:事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应[代码]json[代码]文件中包含[代码]usingComponents[代码]定义段。 完整的项目可以参考wxapp-typescript-demo。 参考 LogManager 实时日志 Component构造器 behaviors 结束语 使用自定义组件的方式来写页面,有特别多好用的技巧,behavior 就是其中一个比较重要的能力,大家可以发挥自己的想象力来实现很多奇妙的功能。
2019-12-10 - 小程序流量主、广告位类型和广告收益分析
小程序流量主、广告位类型和广告收益分析 ## 本文介绍 最近在小程序的几个微信群,经常有朋友问到以下几个问题 1、小程序怎么盈利 2、小程序流量主是什么以及怎么开通 3、小程序广告有哪些类型,哪种广告类型相对收益最大 4、 ## 小程序如何盈利 目前对个人小程序开发者而言,只有通过开通流量主,并且按照官方规范要求添加广告位,才能获取收益,当然打赏除外。 ## 什么是流量主如何开通 流量主是微信对外提供的一个服务,通过开通流量主,就可以在小程序合适的位置引入广告位,进而实现收益 登录公众号后台( https://mp.weixin.qq.com/ )在左侧菜单中,找到 推广-流量主,点击进去会看到如下截图 [图片] 小程序流量主: 1.开通条件:小程序累计独立访客(UV)1000以上,且无违规记录,即可开通流量主功能。 温馨提示:如满足条件仍无法开通,可能是数据同步问题,建议等待1-2个工作日后再试。 2.申请方法:进入微信公众平台小程序后台,点击左侧面板“流量主”,满足开通门槛的小程序开发者点击“开通”,提交财务资料,待审核通过后成功开通流量主功能,即可创建相应的广告位。 3.广告接入指引: 广告接入可查看: 微信小程序广告接入指引 在开通流量主的过程中,会绑定个人银行卡,以方便进行后续的广告收益结算,目前结算每月两次,具体官方公告可以查阅 [流量主结算周期及开票规则调整说明][2019-12-03发布] https://mp.weixin.qq.com/promotion/readtemplate?t=notice/detail_page&time=1575340587¬ice_id=634169 ## 广告类型有哪些 Banner激励式视频插屏视频广告前贴视频 以下为各广告类型,截图示例, [banner广告] [图片] [插屏广告] [图片] 视频广告 [图片] 由于插屏广告会影响用户体验,所以不建议放太多场景使用。 具体不同类型广告体验,可以扫码 [图片] 首页模块-->>插屏广告使用说明-->>视频广告关于我们-->>banner广告 ## 哪种广告类型收益相对最大 [图片] 在10月30号,将banner广告同一替换为激励式视频广告和视频广告,收益很明显从30元上升到90元、150元 可以看到视频广告相对于banner广告,对于收益增加是有用的。 下图是某小程序12月4号一天的收益数据 [图片] 12月4号一天,不同广告类型,收益分析 总收益 194.74+23.27+147.82=365.83 具体分拆来看 广告类型点击量总收益单个点击收益(元)banner1956194.740.099插屏广告6223.270.375激励式视频广告152147.820.972 通过上图我们对比分析,不难得出以下结论:激励式视频广告单个点击的收益最大、 当然我们不能通过单一维度来了解哪种收益最好,还要综合考虑,比如哪种广告对用户影响最小,毕竟不管哪种方式,广告的接入肯定会带来交互体验上的障碍, 我们必须在交互体验和广告收益这两者之间做好权衡。 ## 系统公告 激励式广告于7月31日支持30秒视频素材,广告流量将逐步放开,MP后台-广告位管理模块可支持选择6-15秒视频或6-30秒视频素材的功能,请流量主根据产品进行调整。程序视频广告已于9月4日正式全量上线,开通后即按广告曝光获得分成收入,进一步提升流量变现收益。小程序视频前贴广告组件已于8月30日正式全量上线,开通后即按广告曝光获得分成收入,进一步提升流量变现收益。## 官方文档 小程序广告组件流量主操作指引https://wximg.qq.com/wxp/pdftool/get.html?id=BJSyDkLqz&pa=14&name=miniprogramAds_supplier_manual应用规范https://wxa.wxs.qq.com/mpweb/delivery/legacy/pdftool/get.html?id=rynYA8o3f&pa=10&name=miniprogramAds_supplier_guidance小程序流量主应用规范https://wximg.qq.com/wxp/pdftool/get.html?id=rynYA8o3f&pa=10&name=miniprogramAds_supplier_guidance处罚标准https://wxa.wxs.qq.com/mpweb/delivery/legacy/pdftool/get.html?id=BkTGkbs2G&pa=1&name=miniprogramAds_supplier_regulation小程序视频广告流量主指引https://wximg.qq.com/wxp/pdftool/get.html?post_id=1317小程序视频前贴广告流量主指引https://wximg.qq.com/wxp/pdftool/get.html?post_id=1318## 总结三点 从纯收益的角度来讲,在各种广告类型中,视频广告(包含激励式视频广告、视频广告、视频前贴广告)要比banner广告要好,而且好很多从用户体验来讲,插屏广告是首次打开带插屏广告的页面强制弹出的,但是广告过后,在页面是不占空间的,这是区分与其他广告的地方,banner广告、激励式视频广告、视频广告、视频前贴广告都是在页面中占固定的空间的,这一点要小程序运营同学权衡。Banner广告是按点击,激励式视频、视频广告、插屏广告都是按照曝光来收取广告费用的,这一点非常重要,难怪我每次手工点击我的视频广告没有见流量的增加[哭脸.jpg]。[感谢 @ 仙森 补充于2019年12月9号] 虽然对个人开发者而言,我们开发小程序的目的是为了收益(当然也有为了情怀而开发),在了解如何收益的情况下,我们还是应该尽量把精力放在小程序本身的开发上面。 感谢 在此特别感谢,小程序运营讨论群的两位小伙伴,微信号中间两位已打码 1、@迭戈 (yang_##chun) 2、@风猫 (cs##26)
2020-12-25 - wx.request偶尔一直pending,不返回
- 当前 Bug 的表现(可附上截图) 我们的小程序wx.request一个API的时候,偶尔会出现一直在pending,复制这个API直接在浏览器里打开的时候是正常的有数据,各地区用户都有反馈这个问题,排除了网络不行,排除手机问题,服务器和域名我们也检查了,没有发现问题,用户如果把小程序浏览记录删除掉,重新搜索小程序打开,又恢复正常了,一般是第一个接口出现这种情况,其他的接口也就这样了,在开发工具里测试一直正常,但是真机调试的时候,就会偶尔出现这种情况了!代码里我们检查了,没有多余的东西,只是简单的调用了一个api接口,全GET格式。这个问题也是最近反馈比较多,之前一直都没有出现过! getAuditStatus:function(fn){ wx.request({ url: this.domain + '/index.php?g=Wap&m=Wxa&a=get_audit_status&id=' + this.commit_id + '&token=' + this.token, success: function (res) { if (fn) { fn(res) } } }) }, - 预期表现 - 复现路径 - 提供一个最简复现 Demo
2019-05-30 - 组件halfscreendialog有bug
[图片] 非常明显的错误,自定义desc的时候,真不知道你们做完测试不测试。 非常明显的错误,自定义desc的时候,真不知道你们做完测试不测试。 非常明显的错误,自定义desc的时候,真不知道你们做完测试不测试。 非常明显的错误,自定义desc的时候,真不知道你们做完测试不测试。 非常明显的错误,自定义desc的时候,真不知道你们做完测试不测试。
2019-12-08 - 关于小程序审核的机制
请问能不能给开发者提交两个审核版本的机会?比如我一个功能审核提交了,但是审核周期很长,现在线上发现了一个严重的bug,需要修改,假如提交的审核时间过去一半了,难道我要先撤下审核?感觉很不好啊,如果能提交两个审核版本的话就可以很容易解决这个问题,我就不需要撤下来已经等待很久的审核了,
2019-12-05 - 小程序内用户帐号登录规范调整和优化建议
为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范。本公告所称“帐号登录功能”是指开发者在小程序内提供帐号登录功能,包括但不限于进行的手机号登录,getuserinfo形式登录、邮箱登录等形式。具体规范要求如下: 1.服务范围开放的小程序 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 包括但不限于打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,都属于违反上述要求的情况; 以下反面示例,在用户打开小程序后立刻弹出授权登录页; [图片] 建议修改为如下正面示例形式:在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项。 [图片] 2.服务范围特定的小程序 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序; 下图案例为正面示例:校友管理系统,符合规范要求。 [图片] 3.仅提供注册功能小程序 对于线上仅提供注册功能,其他服务均需以其他方式提供的小程序,可在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或帐号登录。如ETC注册申请、信用卡申请; 如下反面示例,用户在进入时未获取任何信息,首页直接强制弹框要求登录注册ETC,这是不符合规范的。 [图片] 建议修改为如下正面示例所示形式:允许在首页说明注册功能后,提供登录或注册按钮供用户主动选择点击登录。 [图片] 4.提供可取消或拒绝登录选项 任何小程序调用帐号登录功能,应当为用户清晰提供可取消或拒绝的选项按钮,不得以任何方式强制用户进行帐号登录。 如下图所示反面示例,到需要登录环节直接跳转登录页面,用户只能选择点击登录或退出小程序,这不符合登录规范要求。 [图片] 建议修改为下图正面示例形式,在需帐号登录的环节,为用户主动点击登录,并提供可取消按钮,不强制登录。 [图片] 针对以上登录规范要求,平台希望开发者们能相应地调整小程序的帐号登录功能。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。
2019-07-20 - [有点炫]自定义navigate+分包+自定义tabbar
自定义navigate+分包+自定义tabbar,有需要的可以拿去用用,可能会存在一些问题,根据自己的业务改改吧 大家也可以多多交流 代码片段:在这里 {"version":"1.1.5","update":[{"title":"修复 [复制代码片段提示] 无法使用的问题","date":"2020-06-15 09:20","imgs":[]}]} 更新日志: 2019-11-25 自定义navigate 也可以调用wx.showNavigationBarLoading 和 wx.hideNavigationBarLoading 2019-11-25 页面滚动条显示在自定义navigate 和 自定义tabbar上面的问题(点击“体验custom Tabbar” [图片] [图片] 其他demo: 云开发之微信支付:代码片段
2020-06-15 - 获取access_toen返回45009,调用次数超限?请客服帮忙重置
小程序获取access_token 返回45009错误, AccessToken调用次数超限问题,请管理员帮忙重置 APPID:wxd263e8b582c49331
2019-12-05 - 小程序遇到45009 AccessToken调用次数超限问题,请管理员帮忙重置。
APPID:wx4567a7455a8be503 麻烦了,多谢!
2019-04-10 - weui内置扩展库使用步骤
更新最新的 nightly 版开发者工具 在app.json里新增 “useExtendedLib”: { “weui”: true } 在使用的页面json文件应用组件,比如在index.json里 { “navigationStyle”:“custom”, “usingComponents”: { “mp-navigation-bar”:“weui-miniprogram/navigation-bar/navigation-bar” } } wxml文件使用组件,比如在index.wxml里 <mp-navigation-bar title=“首页”></mp-navigation-bar> 验证有无生效。 示例代码片段:https://developers.weixin.qq.com/s/zB6mFrmc7elr 备注: 文档里说目前暂不支持在分包中引用,但据代码测试,分包也能使用。 各位觉得有用的话,给个赞呗。
2020-10-28 - 为什么拼多多还是能判断我没有真的点分享?
我点分享出去,在选择好友界面,点左上角的“关闭”,拼多多小程序就提示分享失败,我真点了某个好友,则显示分享成功。
2019-11-29 - 云函数获取openid
代码如下: app.js: //如果担心openid的安全,就用这个函数 getCloudOpenid: async function () { return this.openid = this.openid || (await wx.cloud.callFunction({name: 'login'})).result.OPENID }, //最佳方案。 getOpenid: async function () { (this.openid = this.openid || wx.getStorageSync('openid')) || wx.setStorageSync('openid', await this.getCloudOpenid()) return this.openid }, 任何page: onLoad: async function () { console.log(this.openid = await getApp().getOpenid()) }, //在本page的其他函数里获得openid。 yourFunc: function(){ console.log(this.openid) } 云函数login: const cloud = require('wx-server-sdk') cloud.init() exports.main=async()=>{return cloud.getWXContext()}
2020-10-18 - 小程序如何加入redux数据状态管理
目录结构 [图片] common 项目公共组件,授权组件 config 项目配置文件 filter 项目wxs处理filter images 项目图片文件夹 pages 小程序页面 redux 处理请求reducer utils 公共方法 wechart-redux 小程序redux核心文件 使用方法 入口app.js [图片] 引入redux核心包,createStore,Provider 创建store 将Redux Store绑定到App上(Provider是用来把Redux的store绑定到App上) redux/app.redux.js [图片] 自己偷懒把action和reducer写到一起了 pages/index.js 页面如何调用 [图片] 熟悉react redux的一眼就会看出来是如何使用的,原理参考react-redux 1) 在页面的定义上使用connect,绑定redux store到页面上 2) 定义要映射哪些state到页面 3) 使用connect将上述定义添加到pageConfig中 完成上述两步之后,你就可以在this.data中访问你在mapStateToData定义的数据了。 wechat-redux 核心包 [图片] 由于内容比较多,原理类似于redux,直接上图 1) connect.js [图片] [图片] createStore.js [图片] provider.js [图片] wrapActionCreators.js [图片] index.js [图片] 总结: 以上主要是借鉴了react-redux实现了小程序redux数据状态管理,目前还没有正式在项目中使用,后期还需要优化,如有不对还请各位大佬指点,谢谢
2019-11-20 - video组件在小米手机中不能正常播放视频?
小程序页面的代码如下: <video id="videoplayer" src="https://vod.weimobwmc.com/92bc998dvodtransgzp1252328573/286d6c585285890794938822901/v.f30.mp4?t=5dce777b&exper=23&us=1573801291&sign=30ce0bc946649056a795abca20d9f5ad" object-fit="fill" autoplay="{{true}}" show-play-btn custom-cache="{{false}}" > </video> 问题表现:1、iOS能正常播放;2、小米9不能播放,一直转圈圈;另外,直接将此链接用微信的会话打开是可以播放的。 备注:该视频是一个试看视频。正常的视频(https://vod.weimobwmc.com/92bc998dvodtransgzp1252328573/286d6c585285890794938822901/v.f30.mp4?t=5dce78f7&us=1573801671&sign=fd0bfabcf00e7c928e346dbf8df6c768)在小米手机是能正常播放的。。。。 大家能否帮忙看下是什么原因?
2019-11-15 - Android真机不支持env(safe-area-inset-bottom)?
该变量是IOS 系统内核提供的,在IOS上正常使用;而安卓和开发工具上用的是 Chromium 内核,没有这个变量,所以不支持
2019-10-17 - 【干货】微信内置浏览器缓存清理
之前做过很多公众号的项目,项目写完后给客户看项目,客户一而再再而三的修改元素向左挪1px,向右挪2px。改好之后让客户看,客户说我特泽发克,你啥都没有修改,你竟然骗我!!! 这其实就是微信内置浏览器的缓存在作祟啦,那么如何清理微信内置浏览器的缓存呢? 你们是否知道 ios版微信 和 android版微信 的内置浏览器的内核是不一样的呢? android版微信内置浏览器(X5内核) 在安卓版微信内打开链接 http://debugx5.qq.com 拉到调试页面的最底端,勾选上所有的缓存项目,点击清除。 [图片] 点击确定之后即可完成清除微信浏览器缓存的操作。 ios版微信内置浏览器(WKWebView) ios版微信内置浏览器内核并不是 X5内核,而是使用的ios的浏览器内核WKWebView,所以安卓手机的那种方案对ios手机用户不生效,因为那个链接压根打不开 只要微信用户退出登录,然后重新登录,ios版微信内置浏览器内核即可清除,不行的话,你们回来打我 有人说了:“IOS中 设置—通用----存储空间 就会看到“正在计算空间”计算完了会清理一点清理即可”,这种办法当然也可以,但是这种办法不光是清理微信内置浏览器的缓存,同时也清理其他的一些数据,比如朋友圈的视频图片和聊天记录等等缓存,而且容易误删某些想留下的数据,对于开发而言,我认为退出重新登录是最好的解决办法。
2020-01-08 - ios 微信内置浏览器什么时候能支持webRTC?
求官网给个回答。
2018-11-21 - 实战分享: 小程序云开发玩转订阅消息(一)
[图片] 微信官方为提升小程序模板消息能力的使用体验,对模板消息的下发条件进行了调整。原有的小程序模板消息接口于 2020 年 1 月 10 日下线,届时将无法使用旧的小程序模板消息接口发送模板消息,取而代之的是新的一次性订阅消息和长期订阅消息。 订阅消息给小程序开发者带来了更好的触达用户的能力,在具体实施过程中,开发者如何把模板消息换成新的订阅消息,是否需要购买服务器来实现服务器鉴权,怎样才能在用户订阅之后一段时间后,给用户发送长期或一次性订阅消息呢? 小程序·云开发最近支持了通过云调用免 access_token 发送订阅消息,还新增支持了在定时触发器中实现云调用,这些能力可以帮助开发者轻松玩转小程序订阅消息。 我们今天会利用小程序·云开发进行一个小程序中实现订阅开课提醒的实战,帮助大家了解如何基于小程序·云开发快速接入小程序订阅消息。 [图片]整体时序图[图片]开课提醒订阅消息时序图环境准备注册小程序帐号[1]开通云开发服务[2]获取订阅消息模板 ID在微信小程序管理后台中,新增一个订阅消息的模板,这里我们新增了一个开课提醒的模板。 [图片]新增模板引导用户订阅微信小程序提供了[代码]wx.requestSubscribeMessage[代码] 接口来发起申请订阅权限界面。 [图片]微信申请订阅权限界面在 "订阅开课提醒" 的按钮上绑定 tap 事件,事件处理器我们这里用的 [代码]onSubscribe[代码] index.wxml [代码]<button class="btn" data-item="{{ item }}" bindtap="onSubscribe" hover-class="btn-hover" > 订阅开课提醒 </button> [代码]在 [代码]onSubscribe[代码] 函数内,我们会调用微信 API [代码]wx.requestSubscribeMessage[代码] 申请发送订阅消息权限,当用户在弹窗同意订阅之后,我们会收到 [代码]success[代码] 回调,将订阅的课程信息调用云函数 [代码]subscribe[代码] 存入云开发数据库,云函数 [代码]subscribe[代码] 的实现在下文会讲。 index.js [代码]onSubscribe: function(e) { // 获取课程信息 const item = e.currentTarget.dataset.item; // 调用微信 API 申请发送订阅消息 wx.requestSubscribeMessage({ // 传入订阅消息的模板id,模板 id 可在小程序管理后台申请 tmplIds: [lessonTmplId], success(res) { // 申请订阅成功 if (res.errMsg === 'requestSubscribeMessage:ok') { // 这里将订阅的课程信息调用云函数存入云开发数据 wx.cloud .callFunction({ name: 'subscribe', data: { data: item, templateId: lessonTmplId, }, }) .then(() => { wx.showToast({ title: '订阅成功', icon: 'success', duration: 2000, }); }) .catch(() => { wx.showToast({ title: '订阅失败', icon: 'success', duration: 2000, }); }); } }, }); [代码][代码] },[代码] 文章字数超出 50000 字,后半部分链接 《实战分享: 小程序云开发玩转订阅消息(二)》
2019-10-23 - 定时后的订阅消息,能够为企业产生多大价值?
10 月 12 日,微信官方发布消息,将使用一次性订阅消息和长期订阅消息,对于广大开发者而言,半喜半忧。喜的是长期订阅消息终于发布了,以后可以以更加简单的方式去通知用户了,而无需再使用大量的表单去收集 Form ID,担心 FormID 失效无法使用了。 而昨天,云开发发布了新的能力,支持在定时器触发的云函数中调用订阅消息,这个能力的开放,对于广大企业和开发者来说,可以以更低的成本,来接入订阅消息。 长期订阅消息能够为企业带来什么? 一直以来,小程序的模板消息都饱受批评,原因是其限制过于死板,例如一个 FormID 只能使用一次,而微信支付的 prepayId 也只能推送两次消息。而时间上,FormID 仅能在 7 天内使用,超过 7 天就必须重新获取,使得借助模板消息来促活的工作变得十分困难。 此外,一些合情合理的场景,也因为模板消息本身的限制,而不得不冒着被封号的风险,使用大量采集 FormID 的方式,来实现通知用户的目的。 新的长期订阅消息,给了企业和开发者一个机会,可以名正言顺的触达到用户,当然,长期订阅消息的开放也并非完全开放给所有开发者,目前仅为政务民生、医疗、交通、金融、教育等线下公共服务开放,后续会逐渐支持到其他类目的服务。 长期订阅消息 x 政务民生 ##对于政务民生领域的小程序来说,如何将自己原有的业务与长期订阅消息相结合,以提升原本流程中低效的部分,进而更好的服务大众,是十分重要的。 这里,我们以活动比较多的工会为例,对于各基层工会来说,工作的一大重心是带领工会成员组织各项党建活动。那么对于工会的场景来说,原本需要口口相传的活动通知方式,可以转为由小程序统一发布长期订阅消息,进一步来说,工会的小程序可以由统一发布订阅消息,加入一些自定义关注的能力,工会成员可以自行关注自己感兴趣的活动类型,并根据活动情况进行选择性的推送,让工会成员可以快速获取关键信息。 ##此外,一些流程较长的业务,例如房产证过户,需要涉及多个部门、多个单位办理时,可以借助于小程序的长期订阅消息,来实现流转状态的通知,这样可以在一个单位办结的时候,主动提醒群众,前往柜台领取资料等。 长期订阅消息 x 医疗 ##我们去医院诊疗时,多数情况下都不是一蹴而就的,可能需要定期去复查、购药等,在此场景下,往往会有病患在完成了第一次诊疗以后即忘记或记错第二次诊疗的时间而错过复查的情况发生,而小程序的长期订阅消息的诞生有望有效解决这一痛点,开发者可以借助长期订阅消息,实现面向医疗场景下的定期提醒,例如说,定期提醒患者吃药、定期提醒患者前往医院复查、定期提醒患者购买新药等等。引入了长期订阅消息以后,医生和医院可以以更低的成本,来完成诊断结果的落地,借助种种提醒,帮助患者早日康复。 ##此外,小程序长期订阅消息还能在医疗活动的其他流程中发挥效用,例如病患可以在小程序中关注某位专家,以及时获取专家坐诊时间等。 长期订阅消息 in 云开发 为了帮助开发者更加平滑的完成从模板消息到一次性订阅消息,以及新需求长期订阅消息的接入,云开发团队在订阅消息发布的第一时间,就实现了对订阅消息的支持。更是在随后的几天里实现了定时触发器情况下的长期订阅消息的触发。 对于开发者来说,实现长期订阅消息的成本,从过去需要数百行代码,自行维护 AccessToken,转变为无需维护 AccessToken 和只需一行代码就可以完成调用。 如何在自己的小程序中实现长期订阅消息 想要在自己的小程序中实现长期订阅消息,则需要符合以下两个条件: 企业帐号开通的小程序:目前长期订阅消息仅支持企业小程序帐号,个人小程序仅能使用一次性订阅消息。 政务民生、医疗、交通、金融、教育等类目:目前长期订阅消息仅针对一些公共服务场景提供,如果你不是对应场景,也无法申请相关的模板。 如果你满足上面的要求,则可以继续进行后续的操作, 在微信小程序后台添加长期订阅消息模板,并复制其模板 ID ,将其放在你的云函数中。 在你的云函数中配置模板所需要的关键词等信息 配置云函数的定时触发器,由定时触发器自行完成触发,并发送订阅消息给用户。 ##在使用的时候,你需要注意,一般而言,为了确保我们的消息可以被准时发出,我们的定时触发器一般定为每分钟触发一次。 长期订阅消息 x More 对于存在的订阅消息的场景来说,他们都会需要用到长期订阅消息,通过和现有行业的结合,长期订阅消息将获得更多不一样的能力。
2019-10-22 - banner广告审核问题
[图片]请问一下这个到底什么问题,用这个款手机试了也没问题啊,改了好几轮了。有显示不完整的问题你们能不能截个屏啊,都不知道问题出在哪里。
2019-02-22 - 一个通用request的封装
小程序内置了[代码]wx.request[代码],用于向后端发送请求,我们先来看看它的文档: wx.request(OBJECT) 发起网络请求。使用前请先阅读说明。 OBJECT参数说明: 参数名 类型 必填 默认值 说明 最低版本 url String 是 - 开发者服务器接口地址 - data Object/String/ArrayBuffer 否 - 请求的参数 - header Object 否 - 设置请求的 header,header 中不能设置 Referer。 - method String 否 GET (需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT - dataType String 否 json 如果设为json,会尝试对返回的数据做一次 JSON.parse - responseType String 否 text 设置响应的数据类型。合法值:text、arraybuffer 1.7.0 success Function 否 - 收到开发者服务成功返回的回调函数 - fail Function 否 - 接口调用失败的回调函数 - complete Function 否 - 接口调用结束的回调函数(调用成功、失败都会执行) - success返回参数说明: 参数 类型 说明 最低版本 data Object/String/ArrayBuffer 开发者服务器返回的数据 - statusCode Number 开发者服务器返回的 HTTP 状态码 - header Object 开发者服务器返回的 HTTP Response Header 1.2.0 这里我们主要看两点: 回调函数:success、fail、complete; success的返回参数:data、statusCode、header。 相对于通过入参传回调函数的方式,我更喜欢promise的链式,这是我期望的第一个点;success的返回参数,在实际开发过程中,我只关心data部分,这里可以做一下处理,这是第二点。 promisify 小程序默认支持promise,所以这一点改造还是很简单的: [代码]/** * promise请求 * 参数:参考wx.request * 返回值:[promise]res */ function requestP(options = {}) { const { success, fail, } = options; return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success: res, fail: rej, }, )); }); } [代码] 这样一来我们就可以使用这个函数来代替wx.request,并且愉快地使用promise链式: [代码]requestP({ url: '/api', data: { name: 'Jack' } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 注意,小程序的promise并没有实现finally,Promise.prototype.finally是undefined,所以complete不能用finally代替。 精简返回值 精简返回值也是很简单的事情,第一直觉是,当请求返回并丢给我一大堆数据时,我直接resolve我要的那一部分数据就好了嘛: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { res(r.data); // 这里只取data }, fail: rej, }, )); }); [代码] but!这里需要注意,我们仅仅取data部分,这时候默认所有success都是成功的,其实不然,wx.request是一个基础的api,fail只发生在系统和网络层面的失败情况,比如网络丢包、域名解析失败等等,而类似404、500之类的接口状态,依旧是调用success,并体现在[代码]statusCode[代码]上。 从业务上讲,我只想处理json的内容,并对json当中的相关状态进行处理;如果一个接口返回的不是约定好的json,而是类似404、500之类的接口异常,我统一当成接口/网络错误来处理,就像jquery的ajax那样。 也就是说,如果我不对[代码]statusCode[代码]进行区分,那么包括404、500在内的所有请求结果都会走[代码]requestP().then[代码],而不是[代码]requestP().catch[代码]。这显然不是我们熟悉的使用方式。 于是我从jquery的ajax那里抄来了一段代码。。。 [代码]/** * 判断请求状态是否成功 * 参数:http状态码 * 返回值:[Boolen] */ function isHttpSuccess(status) { return status >= 200 && status < 300 || status === 304; } [代码] [代码]isHttpSuccess[代码]用来决定一个http状态码是否判为成功,于是结合[代码]requestP[代码],我们可以这么来用: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { const isSuccess = isHttpSuccess(r.statusCode); if (isSuccess) { // 成功的请求状态 res(r.data); } else { rej({ msg: `网络错误:${r.statusCode}`, detail: r }); } }, fail: rej, }, )); }); [代码] 这样我们就可以直接resolve返回结果中的data,而对于非成功的http状态码,我们则直接reject一个自定义的error对象,这样就是我们所熟悉的ajax用法了。 登录 我们经常需要识别发起请求的当前用户,在web中这通常是通过请求中携带的cookie实现的,而且对于前端开发者是无感知的;小程序中没有cookie,所以需要主动地去补充相关信息。 首先要做的是:登录。 通过[代码]wx.login[代码]接口我们可以得到一个[代码]code[代码],调用后端登录接口将code传给后端,后端再用code去调用微信的登录接口,换取[代码]sessionKey[代码],最后生成一个[代码]sessionId[代码]返回给前端,这就完成了登录。 [图片] 具体参考微信官方文档:wx.login [代码]const apiUrl = 'https://jack-lo.github.io'; let sessionId = ''; /** * 登录 * 参数:undefined * 返回值:[promise]res */ function login() { return new Promise((res, rej) => { // 微信登录 wx.login({ success(r1) { if (r1.code) { // 获取sessionId requestP({ url: `${apiUrl}/api/login`, data: { code: r1.code, }, method: 'POST' }) .then((r2) => { if (r2.rcode === 0) { const { sessionId } = r2.data; // 保存sessionId sessionId = sessionId; res(r2); } else { rej({ msg: '获取sessionId失败', detail: r2 }); } }) .catch((err) => { rej(err); }); } else { rej({ msg: '获取code失败', detail: r1 }); } }, fail: rej, }); }); } [代码] 好的,我们做好了登录并且顺利获取到了sessionId,接下来是考虑怎么把sessionId通过请求带上去。 sessionId 为了将状态与数据区分开来,我们决定不通过data,而是通过header的方式来携带sessionId,我们对原本的requestP稍稍进行修改,使得它每次调用都自动在header里携带sessionId: [代码]function requestP(options = {}) { const { success, fail, } = options; // 统一注入约定的header let header = Object.assign({ sessionId: sessionId }, options.header); return new Promise((res, rej) => { ... }); } [代码] 好的,现在请求会自动带上sessionId了; 但是,革命尚未完成: 我们什么时候去登录呢?或者说,我们什么时候去获取sessionId? 假如还没登录就发起请求了怎么办呢? 登录过期了怎么办呢? 我设想有这样一个逻辑: 当我发起一个请求的时候,如果这个请求不需要sessionId,则直接发出; 如果这个请求需要携带sessionId,就去检查现在是否有sessionId,有的话直接携带,发起请求; 如果没有,自动去走登录的流程,登录成功,拿到sessionId,再去发送这个请求; 如果有,但是最后请求返回结果是sessionId过期了,那么程序自动走登录的流程,然后再发起一遍。 其实上面的那么多逻辑,中心思想只有一个:都是为了拿到sessionId! 我们需要对请求做一层更高级的封装。 首先我们需要一个函数专门去获取sessionId,它将解决上面提到的2、3点: [代码]/** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { login() .then((r1) => { res(r1.data.sessionId); }) .catch(rej); } } else { res(sessionId); } }); } [代码] 好的,接下来我们解决第1、4点,我们先假定:sessionId过期的时候,接口会返回[代码]code=401[代码]。 整合了getSessionId,得到一个更高级的request方法: [代码]/** * ajax高级封装 * 参数:[Object]option = {},参考wx.request; * [Boolen]keepLogin = false * 返回值:[promise]res */ function request(options = {}, keepLogin = true) { if (keepLogin) { return new Promise((res, rej) => { getSessionId() .then((r1) => { // 获取sessionId成功之后,发起请求 requestP(options) .then((r2) => { if (r2.rcode === 401) { // 登录状态无效,则重新走一遍登录流程 // 销毁本地已失效的sessionId sessionId = ''; getSessionId() .then((r3) => { requestP(options) .then(res) .catch(rej); }); } else { res(r2); } }) .catch(rej); }) .catch(rej); }); } else { // 不需要sessionId,直接发起请求 return requestP(options); } } [代码] 留意req的第二参数keepLogin,是为了适配有些接口不需要sessionId,但因为我的业务里大部分接口都需要登录状态,所以我默认值为true。 这差不多就是我们封装request的最终形态了。 并发处理 这里其实我们还需要考虑一个问题,那就是并发。 试想一下,当我们的小程序刚打开的时候,假设页面会同时发出5个请求,而此时没有sessionId,那么,这5个请求按照上面的逻辑,都会先去调用login去登录,于是乎,我们就会发现,登录接口被同步调用了5次!并且后面的调用将导致前面的登录返回的sessionId过期~ 这bug是很严重的,理论上来说,登录我们只需要调用一次,然后一直到过期为止,我们都不需要再去登录一遍了。 ——那么也就是说,同一时间里的所有接口其实只需要登录一次就可以了。 ——也就是说,当有登录的请求发出的时候,其他那些也需要登录状态的接口,不需要再去走登录的流程,而是等待这次登录回来即可,他们共享一次登录操作就可以了! 解决这个问题,我们需要用到队列。 我们修改一下getSessionId这里的逻辑: [代码]const loginQueue = []; let isLoginning = false; /** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { loginQueue.push({ res, rej }); if (!isLoginning) { isLoginning = true; login() .then((r1) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().res(r1); } }) .catch((err) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().rej(err); } }); } } else { res(sessionId); } }); } [代码] 使用了isLoginning这个变量来充当锁的角色,锁的目的就是当登录正在进行中的时候,告诉程序“我已经在登录了,你先把回调都加队列里去吧”,当登录结束之后,回来将锁解开,把回调全部执行并清空队列。 这样我们就解决了问题,同时提高了性能。 封装 在做完以上工作以后,我们都很清楚的封装结果就是[代码]request[代码],所以我们把request暴露出去就好了: [代码]function request() { ... } module.exports = request; [代码] 这般如此之后,我们使用起来就可以这样子: [代码]const request = require('request.js'); Page({ ready() { // 获取热门列表 request({ url: 'https://jack-lo.github.io/api/hotList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 request({ url: 'https://jack-lo.github.io/api/latestList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); }, }); [代码] 是不是很方便,可以用promise的方式,又不必关心登录的问题。 然而可达鸭眉头一皱,发现事情并不简单,一个接口有可能在多个地方被多次调用,每次我们都去手写这么一串[代码]url[代码]参数,并不那么方便,有时候还不好找,并且容易出错。 如果能有个地方专门记录这些url就好了;如果每次调用接口,都能像调用一个函数那么简单就好了。 基于这个想法,我们还可以再做一层封装,我们可以把所有的后端接口,都封装成一个方法,调用接口就相对应调用这个方法: [代码]const apiUrl = 'https://jack-lo.github.io'; const req = { // 获取热门列表 getHotList(data) { const url = `${apiUrl}/api/hotList` return request({ url, data }); }, // 获取最新列表 getLatestList(data) { const url = `${apiUrl}/api/latestList` return request({ url, data }); } } module.exports = req; // 注意这里暴露的已经不是request,而是req [代码] 那么我们的调用方式就变成了: [代码]const req = require('request.js'); Page({ ready() { // 获取热门列表 req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 req.getLatestList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); } }); [代码] 这样一来就方便了很多,而且有一个很大的好处,那就是当某个接口的地址需要统一修改的时候,我们只需要对[代码]request.js[代码]进行修改,其他调用的地方都不需要动了。 错误信息的提炼 最后的最后,我们再补充一个可轻可重的点,那就是错误信息的提炼。 当我们在封装这么一个[代码]req[代码]对象的时候,我们的promise曾经reject过很多的错误信息,这些错误信息有可能来自: [代码]wx.request[代码]的fail; 不符合[代码]isHttpSuccess[代码]的网络错误; getSessionId失败; … 等等的一切可能。 这就导致了我们在提炼错误信息的时候陷入困境,到底catch到的会是哪种[代码]error[代码]对象? 这么看你可能不觉得有问题,我们来看看下面的例子: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 假如上面的例子中,我想要的不仅仅是[代码]console.log(err)[代码],而是想将对应的错误信息弹窗出来,我应该怎么做? 我们只能将所有可能出现的错误都检查一遍: [代码]req.getHotList({ page: 1 }) .then((res) => { if (res.code !== 0) { // 后端接口报错格式 wx.showModal({ content: res.msg }); } }) .catch((err) => { let msg = '未知错误'; // 文本信息直接使用 if (typeof err === 'string') { msg = err; } // 小程序接口报错 if (err.errMsg) { msg = err.errMsg; } // 自定义接口的报错,比如网络错误 if (err.detail && err.detail.errMsg) { msg = err.detail.errMsg; } // 未知错误 wx.showModal({ content: msg }); }); [代码] 这就有点尴尬了,提炼错误信息的代码量都比业务还多几倍,而且还是每个接口调用都要写一遍~ 为了解决这个问题,我们需要封装一个方法来专门做提炼的工作: [代码]/** * 提炼错误信息 * 参数:err * 返回值:[string]errMsg */ function errPicker(err) { if (typeof err === 'string') { return err; } return err.msg || err.errMsg || (err.detail && err.detail.errMsg) || '未知错误'; } [代码] 那么过程会变成: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { const msg = req.errPicker(err); // 未知错误 wx.showModal({ content: msg }); }); [代码] 好吧,我们再偷懒一下,把wx.showModal也省去了: [代码]/** * 错误弹窗 */ function showErr(err) { const msg = errPicker(err); console.log(err); wx.showModal({ showCancel: false, content: msg }); } [代码] 最后就变成了: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch(req.showErr); [代码] 至此,一个简单的wx.request封装过程便完成了,封装过的[代码]req[代码]比起原来,使用上更加方便,扩展性和可维护性也更好。 结尾 以上内容其实是简化版的[代码]mp-req[代码],介绍了[代码]mp-req[代码]这一工具的实现初衷以及思路,使用[代码]mp-req[代码]来管理接口会更加的便捷,同时[代码]mp-req[代码]也提供了更加丰富的功能,比如插件机制、接口的缓存,以及接口分类等,欢迎大家关注mp-req了解更多内容。 以上最终代码可以在这里获取:req.js。
2020-08-04 - 路由的封装
小程序提供了路由功能来实现页面跳转,但是在使用的过程中我们还是发现有些不方便的地方,通过封装,我们可以实现诸如路由管理、简化api等功能。 页面的跳转存在哪些问题呢? 与接口的调用一样面临url的管理问题; 传递参数的方式不太友好,只能拼装url; 参数类型单一,只支持string。 alias 第一个问题很好解决,我们做一个集中管理,比如新建一个[代码]router/routes.js[代码]文件来实现alias: [代码]// routes.js module.exports = { // 主页 home: '/pages/index/index', // 个人中心 uc: '/pages/user_center/index', }; [代码] 然后使用的时候变成这样: [代码]const routes = require('../../router/routes.js'); Page({ onReady() { wx.navigateTo({ url: routes.uc, }); }, }); [代码] query 第二个问题,我们先来看个例子,假如我们跳转[代码]pages/user_center/index[代码]页面的同时还要传[代码]userId[代码]过去,正常情况下是这么来操作的: [代码]const routes = require('../../router/routes.js'); Page({ onReady() { const userId = '123456'; wx.navigateTo({ url: `${routes.uc}?userId=${userId}`, }); }, }); [代码] 这样确实不好看,我能不能把参数部分单独拿出来,不用拼接到url上呢? 可以,我们试着实现一个[代码]navigateTo[代码]函数: [代码]const routes = require('../../router/routes.js'); function navigateTo({ url, query }) { const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&'); wx.navigateTo({ url: `${url}?${queryStr}`, }); } Page({ onReady() { const userId = '123456'; navigateTo({ url: routes.uc, query: { userId, }, }); }, }); [代码] 嗯,这样貌似舒服一点。 参数保真 第三个问题的情况是,当我们传递的参数argument不是[代码]string[代码],而是[代码]number[代码]或者[代码]boolean[代码]时,也只能在下个页面得到一个[代码]string[代码]值: [代码]// pages/index/index.js Page({ onReady() { navigateTo({ url: routes.uc, query: { isActive: true, }, }); }, }); // pages/user_center/index.js Page({ onLoad(options) { console.log(options.isActive); // => "true" console.log(typeof options.isActive); // => "string" console.log(options.isActive === true); // => false }, }); [代码] 上面这种情况想必很多人都遇到过,而且感到很抓狂,本来就想传递一个boolean,结果不管传什么都会变成string。 有什么办法可以让数据变成字符串之后,还能还原成原来的类型? 好熟悉,这不就是json吗?我们把要传的数据转成json字符串([代码]JSON.stringify[代码]),然后在下个页面把它转回json数据([代码]JSON.parse[代码])不就好了嘛! 我们试着修改原来的[代码]navigateTo[代码]: [代码]const routes = require('../../router/routes.js'); function navigateTo({ url, data }) { const dataStr = JSON.stringify(data); wx.navigateTo({ url: `${url}?jsonStr=${dataStr}`, }); } Page({ onReady() { navigateTo({ url: routes.uc, data: { isActive: true, }, }); }, }); [代码] 这样我们在页面中接受json字符串并转换它: [代码]// pages/user_center/index.js Page({ onLoad(options) { const json = JSON.parse(options.jsonStr); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] 这里其实隐藏了一个问题,那就是url的转义,假如json字符串中包含了类似[代码]?[代码]、[代码]&[代码]之类的符号,可能导致我们参数解析出错,所以我们要把json字符串encode一下: [代码]function navigateTo({ url, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } // pages/user_center/index.js Page({ onLoad(options) { const json = JSON.parse(decodeURIComponent(options.encodedData)); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] 这样使用起来不方便,我们封装一下,新建文件[代码]router/index.js[代码]: [代码]const routes = require('./routes.js'); function navigateTo({ url, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } function extract(options) { return JSON.parse(decodeURIComponent(options.encodedData)); } module.exports = { routes, navigateTo, extract, }; [代码] 页面中我们这样来使用: [代码]const router = require('../../router/index.js'); // page home Page({ onLoad(options) { router.navigateTo({ url: router.routes.uc, data: { isActive: true, }, }); }, }); // page uc Page({ onLoad(options) { const json = router.extract(options); console.log(json.isActive); // => true console.log(typeof json.isActive); // => "boolean" console.log(json.isActive === true); // => true }, }); [代码] route name 这样貌似还不错,但是[代码]router.navigateTo[代码]不太好记,[代码]router.routes.uc[代码]有点冗长,我们考虑把[代码]navigateTo[代码]换成简单的[代码]push[代码],至于路由,我们可以使用[代码]name[代码]的方式来替换原来[代码]url[代码]参数: [代码]const routes = require('./routes.js'); function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const url = routes[name]; wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } function extract(options) { return JSON.parse(decodeURIComponent(options.encodedData)); } module.exports = { push, extract, }; [代码] 在页面中使用: [代码]const router = require('../../router/index.js'); Page({ onLoad(options) { router.push({ name: 'uc', data: { isActive: true, }, }); }, }); [代码] navigateTo or switchTab 页面跳转除了navigateTo之外还有switchTab,我们是不是可以把这个差异抹掉?答案是肯定的,如果我们在配置routes的时候就已经指定是普通页面还是tab页面,那么程序完全可以切换到对应的跳转方式。 我们修改一下[代码]router/routes.js[代码],假设home是一个tab页面: [代码]module.exports = { // 主页 home: { type: 'tab', path: '/pages/index/index', }, uc: { path: '/pages/a/index', }, }; [代码] 然后修改[代码]router/index.js[代码]中[代码]push[代码]的实现: [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; if (route.type === 'tab') { wx.switchTab({ url: `${route.path}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${route.path}?encodedData=${dataStr}`, }); } [代码] 搞定,这样我们一个[代码]router.push[代码]就能自动切换两种跳转方式了,而且之后一旦页面类型有变动,我们也只需要修改[代码]route[代码]的定义就可以了。 直接寻址 alias用着很不错,但是有一点挺麻烦得就是每新建一个页面都要写一个alias,即使没有别名的需要,我们是不是可以处理一下,如果在alias没命中,那就直接把name转化成url?这也是阔以的。 [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; const url = route ? route.path : name; if (route.type === 'tab') { wx.switchTab({ url: `${url}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } [代码] 在页面中使用: [代码]Page({ onLoad(options) { router.push({ name: 'pages/user_center/a/index', data: { isActive: true, }, }); }, }); [代码] 注意,为了方便维护,我们规定了每个页面都必须存放在一个特定的文件夹,一个文件夹的当前路径下只能存在一个index页面,比如[代码]pages/index[代码]下面会存放[代码]pages/index/index.js[代码]、[代码]pages/index/index.wxml[代码]、[代码]pages/index/index.wxss[代码]、[代码]pages/index/index.json[代码],这时候你就不能继续在这个文件夹根路径存放另外一个页面,而必须是新建一个文件夹来存放,比如[代码]pages/index/pageB/index.js[代码]、[代码]pages/index/pageB/index.wxml[代码]、[代码]pages/index/pageB/index.wxss[代码]、[代码]pages/index/pageB/index.json[代码]。 这样是能实现功能,但是这个name怎么看都跟alias风格差太多,我们试着定义一套转化规则,让直接寻址的name与alias风格统一一些,[代码]pages[代码]和[代码]index[代码]其实我们可以省略掉,[代码]/[代码]我们可以用[代码].[代码]来替换,那么原来的name就变成了[代码]user_center.a[代码]: [代码]Page({ onLoad(options) { router.push({ name: 'user_center.a', data: { isActive: true, }, }); }, }); [代码] 我们再来改进[代码]router/index.js[代码]中[代码]push[代码]的实现: [代码]function push({ name, data }) { const dataStr = encodeURIComponent(JSON.stringify(data)); const route = routes[name]; const url = route ? route.path : `pages/${name.replace(/\./g, '/')}/index`; if (route.type === 'tab') { wx.switchTab({ url: `${url}`, // 注意tab页面是不支持传参的 }); return; } wx.navigateTo({ url: `${url}?encodedData=${dataStr}`, }); } [代码] 这样一来,由于支持直接寻址,跳转home和uc还可以写成这样: [代码]router.push({ name: 'index', // => /pages/index/index }); router.push({ name: 'user_center', // => /pages/user_center/index }); [代码] 这样一来,除了一些tab页面以及特定的路由需要写alias之外,我们也不需要新增一个页面就写一条alias这么麻烦了。 其他 除了上面介绍的navigateTo和switchTab外,其实还有[代码]wx.redirectTo[代码]、[代码]wx.navigateBack[代码]以及[代码]wx.reLaunch[代码]等,我们也可以做一层封装,过程雷同,所以我们就不再一个个介绍,这里贴一下最终简化后的api以及原生api的映射关系: [代码]router.push => wx.navigateTo router.replace => wx.redirectTo router.pop => wx.navigateBack router.relaunch => wx.reLaunch [代码] 最终实现已经在发布在github上,感兴趣的朋友可以移步了解:mp-router。
2019-04-26 - 新能力解读:页面间通信接口
在 2019 年 7 月 2 日的小程序基础库版本更新 v2.7.3 中,小程序新增了一个页面间通讯的接口,帮助我们的小程序完成不同页面间数据同步的功能。 页面间通信接口能干嘛? 在 v2.7.3 之前,小程序不同页面间的大批量数据传递主要有两种: 借助诸如 Mobx 、Redux 等工具,来实现不同页面间的数据传递。 借助小程序提供的 storage ,来完成在不同页面间数据的同步。 前者需要引入一些第三方工具库,从而提升了整个应用的大小,同时,引入的工具也带来了学习生本。而后者则是基于小程序提供的存储,先将数据存入存储,再到另外一个页面去读取,如果数据涉及到了多个页面,则可能会导致数据的紊乱。 新的页面间通信接口则直接解决了上述的两个问题,你可以直接使用 API 在两个页面之间传递数据,再也无需担心数据的紊乱。 新增的页面间通信接口应当如何使用? 关于页面间通信接口的使用非常简单。 这里,我们假设存在 A 和 B 两个页面,其中 A 是首页,B是详情页。 A 向 B 传递数据 如果你需要从首页向详情页传递数据,则可以这样操作。 在页面 A 执行代码 [代码]wx.navigateTo({ url: 'test?id=1' success: function(res) { // 通过eventChannel向被打开页面传送数据 res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' }) } }) [代码] 这样,当 A 跳转到 B 时,就会出发 B 当中定义的 acceptDataFromOpenerPage,并将后续的数据传递过去。 在 B 中,你可以在 onLoad 去定义 eventChannel 的相关方法 [代码]Page({ onLoad: function(option){ // 监听acceptDataFromOpenerPage事件,获取上一页面通过eventChannel传送到当前页面的数据 let eventChannel = this.getOpenerEventChannel(); eventChannel.on('acceptDataFromOpenerPage', function(data) { console.log(data) }) } }) [代码] B 向 A 传递数据 如果需要被打开的页面向打开的页面传递数据,则可以使用如下代码: 在 A 中的跳转时,加入 events 的定义,定义你自己的函数,以及对应的处理函数。 [代码]wx.navigateTo({ url: 'test?id=1', events: { someEvent: function(data) { console.log(data) } }, }) [代码] 然后在 B 中,调用如下代码来发信息 [代码]Page({ onLoad: function(option){ const eventChannel = this.getOpenerEventChannel() eventChannel.emit('someEvent', {data: 'test'}); } }) [代码] 这样,就可以在 B 页面将数据传回到 A 页面了。 页面间通信接口使用注意事项? 在使用页面间通信接口时需要注意两点: 该功能从基础库 2.7.3 开始支持,低版本需做兼容处理。
2019-09-21 - 探店 | 小程序每日客流近1W,菜菜美食日记的绝招真多!
自媒体布局电商业务,应该怎么做?「菜菜美食日记」(以下简称「菜菜」)可以回答你。 作为 2014 年开始自媒体创业的元老级选手,如今的菜菜已经不仅仅是一个超过 160 万粉丝的公众号,还拥有月均销售额近千万的「一口幸福」有赞店铺。一路走来积攒的经验,菜菜今天全盘分享。 [图片] 一、谨慎尝试电商收获惊喜,定制+跑产地促成交 2014 年,5 个对美食怀揣热爱且拥有文案、制图等专业能力的年轻人聚在一起,创建了微信公众号「菜菜美食日记」。2016年,菜菜开始团队化运营。到 2017 年,菜菜的粉丝数突破百万,眼看着社交电商和公众号变现的风潮越来越旺,菜菜决定抓住红利期尝试电商转型,但他们的起点非常谨慎: 菜菜先是提前在粉丝微信群中做了一个调查,询问粉丝能不能接受他们卖货。结果显示,大部分粉丝愿意为他们推荐的美食买单,之前优质内容所打下的基础,让粉丝相信菜菜的眼光。 接着,考虑到业内口碑和稳定性,菜菜选择了有赞提供技术支持,成立了店铺「一口幸福」。 最后,他们选择了一家口碑较佳、和自己调性相符的供货商,并把第一份商品定为非生鲜类、运输风险较小的绿豆糕。出乎意料地,绿豆糕推文的阅读量竟超过了平时最火的菜谱推文,销量在当时高达几百份。如此,菜菜更加坚定了电商转型之路。 [图片] 从 2017 年的第一个商品,到现在近千个 SKU,都是菜菜通过有赞分销市场等方式不断寻找的。为了提升商品销量,菜菜还利用「菜菜」这个已具有一定流量的 IP 创建了 2 款栏目: 栏目一:菜菜跑团。 水果和食材是用户的刚需品,但因为不能提前见到实物,用户会对商品质量、口感保持疑虑。为此,菜菜成立了「菜菜跑团」深入原产地帮粉丝「探地」,通过亲身考察确保食物质量,并附上菜菜员工或栏目名卡片在原产地的照片,促使粉丝放心购买。近期菜菜跑团栏目中售卖的广东荔枝、山东樱桃都纷纷爆单。 栏目二:菜菜定制。 考虑到将 IP 更大化地利用,菜菜成立了「菜菜定制」栏目,用品牌附加值和性价比更高的商品吸引粉丝到店购买。比如去年年底定制的富平柿饼,一天就产生了近 30 万销售额。 [图片] 二、开通小程序,引流到店效果惊人 菜菜的公众号中每天都会有商品推荐文章,而这些文章中的商品卡片以及最后的店铺引流全部都链接到了有赞小程序店铺。这么做主要有 3 个好处: 首先,菜菜作为订阅号,不能在文章中添加超链接,但小程序商品卡片可以直接插入文中。这样一来,用户在看到心动商品时,能够直接点击进入商品购买页面,一篇文章可以有多个引流触发点,可抓住每一个用户产生购买欲的时刻。 [图片] 其次,小程序入口更多。微信小程序有包括我的小程序、扫码、发现、附近小程序等十几个不同的入口,方便用户进店。比如菜菜的粉丝如果想要进入「一口幸福」小程序店铺,直接搜索或在聊天界面下拉中找到「一口幸福」的小程序即可。 [图片] 最后,小程序更利于与用户产生长久的连接。菜菜还设置了小程序收藏有礼³,让用户将菜菜的小程序添加到「我的小程序」中,时不时就能进店看看。至今已累积 7 千多人添加了「一口幸福」小程序到常驻入口,每日进店的顾客稳定在 1 万人左右。 三、店铺活动+社群运营,销量黏性两不误 除了公众号文章每日推送商品外,菜菜还给有赞小程序店铺设置了一系列活动: 1、进店有礼:针对第一次进店的新人,菜菜会发放 4 张不同面额的新人优惠券,针对老客,则会在 618、母亲节、吃货节等特殊节点发放进店优惠券。进店有礼⁴使许多老客养成进店习惯并带动新客进店,促进店铺的活跃度; 2、2人拼团:菜菜把每周一、周四定为「菜菜拼团日」,每次上新 3 到 5 款应季、常用、客单价较低的食品或日用品,比如龟苓膏、驱蚊水等,发起2人拼团活动。拼团吸引顾客进入商城,2 人的设置让成团简单、快速,效果较好的商品在推文当天可以创造上千的销量。 [图片] 另外,像秒杀、满减/送等有赞微商城营销插件,菜菜也会经常使用,促进粉丝活跃度。 除商城活动外,菜菜还会让粉丝加入各类主题社群,例如瘦身群,品茶群等,持续和用户互动,并经常在群内调查用户对商品的意见以及对新品的期望,不但增加了粉丝的参与感,也增强了粉丝黏性,为同品类商品的推广积累了核心用户群。据悉,菜菜的月平均复购率已接近 70%。 如今,菜菜越来越重视商品的选择和有赞店铺的经营,因为只有拥有了更好的产品和更有竞争力的活动,才能获取更多的顾客,从而产生更好的销量。 [图片] 「敲黑板啦」 担心有些小伙伴没有耐心看完整篇文章,但是又迫切的希望流量变现。所以特意整理了「菜菜美食日记」小程序每日客流近1万的绝招如下,记得拿笔记录下来哟~ 1、前期调研,了解粉丝的意愿,以及选品方向 2、选择稳定的开店系统,当然推荐自家有赞啦~微商城+小程序,双管齐下 3、如果前期没有自己的供应链,可以先从有赞分销市场选品 4、优质内容的定时更新,不要单纯卖货,结合有趣的内容,效果更棒哟 5、利用有赞丰富的营销工具创建一系列的活动,提升拓客、复购 6、社群运营,提升粉丝黏性
2019-06-28 - 具名的slot显示不出来
<!-- 组件模板 --> <view class="wrapper"> <slot name="a"></slot> </view> <!-- 引用组件的页面模版 --> <view> <my-component> <!-- 这部分内容将被放置在组件 <slot> 的位置上 --> <view slot="a">这里是插入到组件slot中的内容</view> </my-component> </view> 官方片段改的,也不显示了
2019-08-26 - 小程序原生高颜值组件库--ColorUI
[图片] 简介 ColorUI是一个Css类的UI组件库!不是一个Js框架。相比于同类小程序组件库,ColorUI更注重于视觉交互! 浏览GitHub:https://github.com/weilanwl/ColorUI [图片] 如何使用? 先下载源码包 → Github 引入到我的小程序 将 /demo/ 下的 colorui.wxss 和 icon.wxss 复制到小程序的根目录下 在 app.wxss 引入两个文件 [代码]@import "icon.wxss"; @import "colorui.wxss"; [代码] 使用模板全新开发 复制 /template/ 文件夹并重命名为你的项目,微信开发者工具导入为小程序就可以使用ColorUI了 体验沉浸式导航 [图片] App.js 获取系统参数并写入全局参数。 [代码]//App.js App({ onLaunch: function() { wx.getSystemInfo({ success: e => { this.globalData.StatusBar = e.statusBarHeight; let custom = wx.getMenuButtonBoundingClientRect(); this.globalData.Custom = custom; this.globalData.CustomBar = custom.bottom + custom.top - e.statusBarHeight; } }) } }) [代码] Page.js 页面配置获取全局参数。 [代码]//Page.js const app = getApp() Page({ data: { StatusBar: app.globalData.StatusBar, CustomBar: app.globalData.CustomBar, Custom: app.globalData.Custom } }) [代码] Page.wxml 页面构造导航。更多导航样式请下载Demo查阅 操作条组件。 [代码]<view class="cu-custom" style="height:{{CustomBar}}px;"> <view class="cu-bar fixed bg-gradual-pink" style="height:{{CustomBar}}px;padding-top:{{StatusBar}}px;"> <navigator class='action border-custom' open-type="navigateBack" delta="1" hover-class="none" style='width:{{Custom.width}}px;height:{{Custom.height}}px;margin-left:calc(750rpx - {{Custom.right}}px)'> <text class='icon-back'></text> <text class='icon-homefill'></text> </navigator> <view class='content' style='top:{{StatusBar}}px;'>操作条</view> </view> </view> [代码] 自定义系统Tabbar [图片] 按照官方 自定义 tabBar 配置好Tabbar (开发工具和版本库请使用最新版)。 使用ColorUI配置Tabbar只需要更改 Wxml 页的内容即可。 更多Tabbar样式请下载Demo查阅 操作条组件。 /custom-tab-bar/index.wxml [代码] <view class="cu-bar tabbar bg-white shadow"> <view class="action" wx:for="{{list}}" wx:key="index" data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab"> <view class='icon-cu-image'> <image src='{{selected === index ? item.selectedIconPath : item.iconPath}}' class='{{selected === index ? "animation" : "animation"}}'></image> </view> <view class='{{selected === index ? "text-green" : "text-gray"}}'>{{item.text}}</view> </view> </view> [代码] 作者叨叨 ColorUI是一个高度自定义的Css样式库,包含了开发常用的元素和组件,元素组件之间也能相互嵌套使用。我也会不定期更新一些扩展到源码。 其实大家都在催我写文档,但这个库源码就在这,所见即所得,粘贴复制就可以得到你想要的页面。当然,文档我还是要写的,也希望大家多多提意见。 现在前端的开发方向基本都是奔着Js方向的,布局和样式大家讨论的有点少。以后我会在开发者社区多聊一聊关于开发中的布局和样式。 [图片] 感谢阅读。
2019-02-26