- 隐私协议相关接口实际使用方式
隐私协议相关接口官方在回滚了多天之后终于滚回来并且提供了demo。 我这边实际研究demo后总结了实际的三种使用方法 首先要在设计到隐私协议相关功能的页面中增加隐私协议弹窗,wxml代码如下,wxss可以参考官方的demo [代码]<view wx:if="{{showPrivacy}}" class="privacy"> <view class="popup"> <view>隐私弹窗内容....</view> <view bindtap="openPrivacyAgreement">点击查看隐私协议</view> <button id="disagreeBtn" bindtap="disagreePrivacy">不同意</button> <button id="agreeBtn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="agreePrivacy">同意</button> </view> </view> [代码] 1、全局控制 页面加载时自动弹窗,同意后可以使用对应功能,不同意退出页面或者隐藏相关功能。 这种情况下,需要在onLoad里使用wx.getPrivacySetting获取隐私授权情况,没授权时弹出隐私弹窗。完整代码如下 [代码]Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 这里是不同意隐私协议的后续操作,比如退出页面、隐藏相关功能等 }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 这里是同意隐私协议的后续操作,比如展示被隐藏的相关功能 }, onLoad() { if (wx.getPrivacySetting) { wx.getPrivacySetting({ success: res => { if (res.needAuthorization) { // 打开隐私弹窗 this.setData({ showPrivacy: true }) } else { // 用户已经同意过隐私协议,直接执行同意隐私协议的后续操作,比如展示被隐藏的相关功能 } } }) } } }) [代码] 2、按需使用 在使用到隐私接口时弹出隐私弹窗,同时隐私接口的流程会被挂起,用户同意后会继续执行,不同意则中止执行。 这种情况下,需要在onLoad里定义好监听隐私接口时的授权事件,也就是wx.onNeedPrivacyAuthorization,然后在用户点击同意或者不同意后调用回调接口 resolve 进行上报,完整代码如下 [代码]Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 上报用户不同意隐私协议,隐私接口操作会被自动中止 this.resolvePrivacyAuthorization({ buttonId: 'disagreeBtn', event: 'disagree' }) }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) // 上报用户同意隐私协议,隐私接口操作会被自动继续执行 this.resolvePrivacyAuthorization({ buttonId: 'agreeBtn', event: 'agree' }) }, onLoad() { if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization(resolve => { // 打开隐私弹窗 this.setData({ showPrivacy: true, }) // 定义上报方法 this.resolvePrivacyAuthorization = resolve }) } } }) [代码] 3、全局控制和按需使用结合使用 这种情况是上面两种方式的结合,页面加载时弹出隐私弹窗,不管用户同意还是不同意都不需要做其他操作。然后用户在用到隐私接口时,根据用户是否同意再按需决定是否再次弹窗。 这种情况下,需要在onLoad里使用wx.getPrivacySetting获取隐私授权情况,没授权时弹出隐私弹窗。同时定义好监听隐私接口时的授权事件,也就是wx.onNeedPrivacyAuthorization,然后在用户点击同意或者不同意后调用回调接口 resolve 进行上报,完整代码如下 [代码]let pageOnload = true // 是否页面加载时弹窗 Page({ data: { showPrivacy: false // 控制隐私弹窗是否展示 }, openPrivacyAgreement() { // 查看隐私协议 wx.openPrivacyContract() }, disagreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) if (!pageOnload) { // 上报用户不同意隐私协议,隐私接口操作会被自动中止 this.resolvePrivacyAuthorization({ buttonId: 'disagreeBtn', event: 'disagree' }) } else { pageOnload = false } }, agreePrivacy() { // 关闭隐私弹窗 this.setData({ showPrivacy: false }) if (!pageOnload) { // 上报用户同意隐私协议,隐私接口操作会被自动继续执行 this.resolvePrivacyAuthorization({ buttonId: 'agreeBtn', event: 'agree' }) } else { pageOnload = false } }, onLoad() { if (wx.getPrivacySetting) { wx.getPrivacySetting({ success: res => { if (res.needAuthorization) { // 打开隐私弹窗 this.setData({ showPrivacy: true }) } } }) } if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization(resolve => { // 打开隐私弹窗 this.setData({ showPrivacy: true, }) // 定义上报方法 this.resolvePrivacyAuthorization = resolve }) } } }) [代码] 4、open-type按钮藕合使用 这个是目前最简单的方式,按照官方文档,在用open-type方式调用隐私功能的按钮的open-type里加上[代码]|agreePrivacyAuthorization[代码]就可以了,js部分代码基本不用做任何修改,示例如下 [代码]<button open-type="getPhoneNumber|agreePrivacyAuthorization" bindgetphonenumber="handleGetPhoneNumber">同意隐私协议并授权手机号</button> [代码] 这种方式,不需要上报同意事件,不需要设计隐私协议弹窗,不需要“不同意”按钮。当然,我建议在现有页面上增加一个查看隐私协议的入口。
2023-09-08 - 微信搜一搜优化教程
一、如何让公众号文章获取更多搜索流量?https://mp.weixin.qq.com/s/v00sCcW0BKchxgkRf3_bwg 二、为什么微信公众号文章搜不到?https://mp.weixin.qq.com/s/FTNvYMAYvvgfg0qtH4QsGQ 三、昵称、描述、菜单,提升帐号流量需注意这3点!https://mp.weixin.qq.com/s/FElAvnGI7jx52gp7hSmb9g 四、保持帐号活跃,并提供稳定服务。https://mp.weixin.qq.com/s/wLYDghkTafpUn_N83VMGwQ 官方出品必是精品。
2023-01-13 - 小程序云函数本地调试无法获取到环境变量?
版本 1.02.2003250 系统 Windows10 [图片]
2020-05-10 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 小程序云开发挑战赛|我是如何从初赛走到决赛
起因今年9月初的时候无意中在「微信开放社区」看到了《正在报名:小程序云开发挑战赛》这个活动。 当时的想法是:试一试呗,好歹之前也用云开发业余做过几款小程序,去验证下自己的学习成果。 由于比赛规则可以两人组队,我问了问我媳妇: “媳妇,我们参加个比赛做个小程序呗?” “想做就做呗!” “好嘞” 说完开始马不停蹄的准备了,当时离比赛只有两周时间了。 初赛我们遇到的第一个问题就是到底做什么产品,参加比赛的产品需要是重新想产品方案。 这个时候我突然想到了最近我媳妇送给我的一个小礼物「爱情兑换券」,这是套卡券里面有情侣之间做一些事情有趣的事情,可以促进了伴侣双方的感情。 [图片] 爱情兑换券 它是一个实体物品,我们可以将它做成小程序,这样用的时候也不会用随身携带了,不仅如此我们还可以加一个回忆功能,让伴侣之间用完了后还可以记录自己的美好回忆。 我说出了这个想法,媳妇也觉得不错。于是就开始整了,我负责产品与开发,她负责设计与交互。 由于我是开发出身没做过产品原型,自己就随便找了个在线画图网站画起了原型图,大概 1 个小时的功能原型就出来了。 [图片] 情侣券 v1.0 原型图 然后跟媳妇讲解功能需求,结果不出所料原型图被媳妇吐槽了。不过还好媳妇脑补能力强,在我的解释和补充说明中理解了完整的功能需求。 由于我媳妇是专业设计,仅花了一个下午就出了第一个版本的设计稿。 [图片] 情侣券 v1.0 设计稿 进入开发阶段,白天我在公司上班,做着自己的本职工作。 下班后到家大概 10 点半左右每天花两个小时左右,一共经历了 8 天的时间完成了情侣券小程序 v1.0 版本。 [视频] 情侣券 v1.0 操作视频 小程序开发完成之后,还需要:录制操作视频=>编写部署文档=>发布参赛文章。 以上的内容一共花了两天的下班时间,终于在截止提交的前一天进行的比赛资料的提交。 提交完成后的心情就放松下来了,想着终于可以休息了,在准备比赛的这段时间几乎都是12点后才睡觉。单是职业赛道就有1000多人,进复赛的希望渺茫,不过这段时间也学习到很多新的技术点,挺不错。 在这段休息的时间里我总结了 4 篇技术文章: 《如何优雅的调用云函数?》《css不规范导致的布局显示问题》《微信小程序如何引入npm包?》《情侣券-领取动画分析》复赛时间到了9月30号,突然在一个群里面我被@了,于是我点开了群消息发现群友告知我进复赛了,我当时有点不敢相信。我马上去看了下官方公告《小程序云开发挑战赛 | 初赛结果公示》真的进复赛了。 在公布结果到复赛比赛时间还有一段时间。当时我们想了很多优化的点,但是评估需要做出来然后还需要做PPT时间来不及。 我就通过需求价值和开发成本进行了一个优先级排序,从 16 个优化点中只选择了 6 个优化点来做。 [图片] 所有优化点梳理 [图片] 进行优先级排序 [图片] 最终确认要实现的需求 在这个讨论的过程中,我学到了有时候少就是多。在时间有限的情况下必须作出选择否则会想要做太多会导致都做不好。最终确认要做的东西不多,花了 4 天就做完了。 接下来复赛还需要准备PPT进行 10 分钟线上直播路演 + 5 分钟答疑,这对我来说是一个挑战,让我写代码还凑合但是做PPT这个确实不在行。于是我媳妇想到了一个点子,既然PPT不擅长其实可以把产品介绍做成视频,这样可以给PPT加分。 于是按照这个思路来做,最后找了个视频模版套用了下,出来的效果我们都非常满意。 [视频] 情侣券介绍视频 当视频出来都时候,进决赛的信心增加了不少。 复赛比赛的时间转眼间就到了。在路演过程中由于演讲稿子提前已经反复练习了很多次了内容都熟记于心。在分享的过程发挥都很稳定,但是到了评委提问有个产品定位的问题没有回答上来,因为我之前一直没有思考过,只想着把功能做好,不断迭代功能就行了。 这次复赛让我学习到了思考不要局限于自身技术思维,而是多从产品都角度去思考。 决赛因为有一个问题没有回答好,当时以为自己没戏了,准备好好休息一下。 结果10月20日《小程序云开发挑战赛 | 复赛结果公示》出来后,又一次出乎了我的意料,进决赛了得到前往深圳腾讯滨海大厦参与线下决赛的机会。 从10月20日到决赛在这个过程中我每天都在思考如何将这个产品的核心功能做的更深入,而不仅仅只是将线下「爱情兑换券」线上化了。在这段时间我看了大量情侣之间如何更好相处的书籍、问答、专栏。 于是想到了新增三种不同卡券类型,为了覆盖更多伴侣生活场景: 新增行动券,内容是情侣之间的100件小事。新增以及知彼券,内容是快速拉近感情的36件个问题。新增话题券,内容是一些情侣之间聊天的话题。以及针对复赛评委的提问,我们更加明确了产品的定位和用户群体,做了相关的竞品调查和用户人群画像分析。 比赛前两天我们从长沙来到来了深圳。 第一天,官方人员带领所有参赛选手参观了深圳腾讯大厦 第二天,决赛开始来,因为前期准备比较充分,所以整个路演,评委提问的过程都非常的稳。 [视频] [图片][图片] 最终获得来职业组第一名。《小程序云开发挑战赛 | 决赛结果公示》 总结1. 想做就去做,迈出自己的舒适区,无论结果如何,过程肯定有收获。 2. 程序员要有产品思维,技术思维是优势但是只有技术思维就是劣势。 3. 多参加官方活动,可以认识很多优秀人才,后续将会有更多可能性。 最后,感谢小程序云开发,为所有开发者提供了一种既简单又高效的开发模式。
2020-12-17 - 微信小程序云开发教程-云函数入门(1)-开发步骤
本小节我们将学习云函数,无论是接收前端的网络请求、还是操作数据库,都需要使用云函数。 [图片] 我们首先学习云函数的格式。 看右边的代码,这是一个云函数,实现的功能是求a+b的和。 前三行是固定的代码,前两行代表引入一个sdk代码包,并将其初始化;第三行是主函数入口,这些都不需要修改。 我们真正需要写的代码是红色括起来的这部分。代表将a=1和b=2作加法,并将结果return回去。 [图片] 那么云函数的开发步骤是怎样的呢? 第一步,我们需要新建一个Node.js云函数,比如说命名为sum,这时候我们得到的是一个文件夹。 [图片] 第二步,在刚才新建的云函数文件夹里,我们需要在index.js文件里编写代码,代码的格式如右边所示; [图片] 第三步,我们需要通过命令行界面安装wx-server-sdk,因为代码里面引用了它; [图片] 第四步,我们对代码本地调试,保证代码可以正确运行; [图片] 第五步,将代码上传并部署到云端,选择的模式是云端安装依赖。 请同学们注意,上传代码到云端是必须做的,只有这样小程序前端才能成功调用该函数。 下面,请根据教学视频进行学习和操作。
2020-08-30 - #小程序云开发挑战赛#-趣婚礼-趣婚礼
<h3 align=“center”> <a href=“https://github.com/wforguo/wedding-app” title=“趣婚礼”>趣婚礼</a> </h3> <p align=“center”> <img alt=“趣婚礼 Logo” src=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/qrcode.jpeg” width=“180”> </p> <p align=“center”> <a href=“https://github.com/wforguo/wedding-app” title=“趣婚礼”>基于Taro2 + 云开发 打造婚礼邀请函</a> </p> 项目名称 趣婚礼 基于[代码]Taro2[代码] + 云开发 打造婚礼邀请函 Taro2 云开发 项目介绍 结婚的时候婚礼邀请函是一道必不可少的程序,但是没法去很好的留存我们的数据和回忆(除非有后端支持)。 最近刚好在学习[代码]Taro[代码],所有就尝试基于[代码]Taro[代码] + 云开发快速的搭建一个婚礼邀请函小程序。 也有人会问,使用了云开发,怎么去管理数据呢,不用担心,云开发[代码]CMS[代码]帮你搞定,支持文本、富文本、图片、文件、关联类型等多种类型的可视化编辑。 CMS 概览 项目效果截图 模块划分 邀请 =》邀请函信息 相册 =》相册展示 导航 =》婚礼举办地 祝福 =》婚礼视频及弹幕留言,留言保存至留言列表 留言 =》好友留言 目录结构 [代码]├── config # 配置文件 ├── cloud # 云函数存放 ├── dist # 打包文件 ├── node_modules # 依赖的模块包 ├── package.json # 项目基本信息 ├── src # 项目的核心组件 │ ├── service # 资源文件(css、image、config) │ ├── common # 资源文件(css、image、config) │ ├── components # 公共组件 │ ├── store # 状态管理(redux) | ├── pages # 页面文件目录 | | ├── Index # index页面目录 | | | ├── index.jsx # index页面逻辑 | | | └── index.scss # index页面样式 | | | └── index.config.js # index页面配置(小程序page.json内容) │ ├── util # 公共方法(util.js、globalData.js) │ ├── app.jsx # 入口文件 │ ├── app.scss # 公共样式 │ ├── index.html # 主页模板 ├── static # 静态资源(CDN) ├── README.md # 项目描述信息 [代码] 效果预览 [图片] 视频演示 视频演示 部署 clone [代码]clone[代码]该项目,并在[代码]project.config.json[代码]下修改自己的小程序[代码]appid[代码] 开通云开发 [图片] 首先需要你在小程序的控制台去开通云开发,并拿到环境名称 在[代码]src/service/config[代码]下修改[代码]DBID[代码]为你自己申请的环境ID`; 新建数据库并导入 表的设计 wedding_invite:婚礼信息 wedding_msgs:留言祝福 wedding_photos:相册 wedding_video:视频 数据库文件存放在[代码]static/db[代码]下,按照文件名新建数据库集合,并导入数据文件即可完成数据库创建。 项目启动 使用[代码]yarn[代码] 安装依赖 [代码]yarn [代码] 编译和打包 [代码] yarn dev:weapp yarn build:weapp [代码] 使用[代码]npm[代码] 安装依赖 [代码] npm install [代码] 编译和打包 [代码] npm run dev:weapp npm run build:weapp [代码] 具体可查看[代码]Taro[代码]教程 到此你就可以看到效果了… 云开发CMS的使用 用于管理云开发数据的CMS CMS文档 CMS截图 CMS Web端 CMS端需要建立与数据库对应的内容模型,才能在列表正常展示对应数据库数据。 可直接导入内容模型,位于 static/schema 模型管理 [图片] 内容管理 [图片] 开发者工具端 [图片] 圈出来的部分是和小程序云开发控制台数据库所对应的. 技术及框架 1.小程序 具体入门和使用,请访问官方文档。 小程序文档 小程序云开发 开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。包含云函数 、数据库、存储和云调用。 2.Taro2 + Redux Taro2 Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5 等应用。 项目中使用了最新版本[代码]Taro2[代码],由于[代码]Taro3[代码]使用期间不是很丝滑,所以选择了[代码]Taro2[代码] 所以本项目可以作为Taro的学习入门,也可以作为小程序云开发的一个入门Demo 3.TaroUI 基于Taro2的UI框架 TaroUI 云开发的使用 官方文档 需要在控制台去开启云开发,并获取DBID(数据库初始化用到) 云开发入口 [图片] 数据库配置 [图片] 数据库 可以在项目中的service中去查看数据库的CURD代码。 有数据库基础的很容易就上手了,小程序的数据库其实就是一个[代码]JSON[代码],类似于[代码]MongoDb[代码]。 顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。 操作 初始化 在开始使用数据库 API 进行增删改查操作之前,需要先获取数据库的引用。 [代码]const db = Taro.cloud.database({ env: 'DBID' }); [代码] 数据库操作 要操作一个集合,需先获取它的引用。在获取了数据库的引用后,就可以通过数据库引用上的 collection 方法获取一个集合的引用了 [代码]db.collection('wedding_msgs') .orderBy('createTime', 'desc') // 时间升序 .skip(current * 10) .limit(pageSize) .get() [代码] 这样每次都比较麻烦,所以我做了统一的处理,都写在[代码]service/cloud/index.js[代码]当中,并export,只需要按需引入,并传入要操作的数据库名称即可。 [图片] Tips:如果发现数据库有数据,但是拿不到所有数据,那应该就是数据库的权限问题了,改成仅创建者可写,所有人可读就可以了 云函数 官方文档 使用云函数去获取用户信息就变得简单多了,具体可翻阅文档! 项目中云函数所在目录,[代码]cloud/functions.js[代码],项目中使用到了一个云函数,留言的内容过滤功能[代码]msgSecCheck[代码], 具体使用方法:security.msgSecCheck 声明 [代码]/** * @Description: 文本内容过滤; */ // 云函数入口文件 const cloud = require('wx-server-sdk'); cloud.init(); // 云函数入口函数 exports.main = async function (event, context) { console.log(event); let opts = { content: event.content || '' }; let fun = cloud.openapi.security.msgSecCheck(opts); return fun.then(res => { return res; }).catch(err => { return err; }); }; [代码] 调用 [代码] // 云调用内容安全过滤 Taro.cloud.callFunction({ name: 'msgCheck', data: { content: data.userMsg }, }).then(res => { if (res && res.result && res.result.errCode === 0) { Taro.showLoading({ title: '发送中...', mask: true }); // 数据库插入留言数据 cloud.add('wedding_msgs', data).then(msgRes => { resolve(msgRes); }, (err) => { console.log(err); reject(err); }); } else { reject(res.result); } }).catch(err => { console.log(err); reject(err); }); [代码] ToDos 朋友圈海报生成… … 这次的版本使用了Taro + 云开发,后面打算出一版[代码]Taro + Antd + koa2 + MongoDb[代码]的版本,初步内容已经差不错了,详见下面项目地址 Taro + Antd + koa2 + MongoDb Taro3的坑 redux使得下拉刷新和上拉加载冲突 无法阻止事件冒泡 无法使用 小程序的[代码]selectComponent[代码],获取组件实例 [代码]const barrageComp = this.selectComponent('.barrage')[代码] 使用 [代码]Taro.createRef();[代码] 代替 关于 <h3 align=“center”> <a href=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/wechart.jpg” title=“微信:iforguo”>微信:iforguo</a> </h3> <p align=“center”> <img alt=“微信:iforguo” src=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/wechart.jpg” width=“480”> </p> 个人主页 CSDN 掘金 本项目仅供学习和交流,部分素材来源于网络,如有侵权联系删除。 项目有需改进也请留言和Issues,如有合作请在博客留言或wx:([代码]iforguo[代码])。 编码不易,感谢各位大佬的吐槽和GitHub的Star
2021-12-10 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13 - 微信开发者工具下载的 sourcemaps 怎么用。
什么是 Sourcemaps uglifyjs、bable 等工具会对 源代码 进行编译处理生成编译后的代码(下称目标代码),而 sourcemaps 就是保留了目标代码在源代码中的 位置信息 --------- 大神分割线 --------- 如何解读 Sourcemaps Sourcemaps 是一个 json [代码]{ "version": 3, "sources": ["a.js", "b.js"], // 源文件列表,这个表示是由 a.js 和 b.js 合并生成 "names": ["myFn", "test"], // 如果开启了变量名混淆,这里会保留变量名在源文件中名字信息 "sourcesContent: [], // 可选项,保存源码信息,顺序与 sources 字段对应,chrome 的 sources 面板中源码使用了这个字段的内容进行展示 "sourceRoot": "", // 源文件所在的目录信息 "file": "dist.js", // 可选,编译后的文件名 "mappings": "" // 这个是重点,是目标代码和源文件的位置的映射关系 } [代码] mappings 目标文件"行"的信息 mappings 是使用 ; 分隔的,每个部分对应目标代码的行 如: “;AAAA;AAAA,BBBB;;” 本例子目标文件有 4 行 第 0 行和第 3 行没有源文件对应信息,所以这两行是编译过程中加入的代码 目标文件的"列"信息 如: “AAAA,CAEA,CAEA;” ‘,’ 表示行内的位置信息分隔符 本例表示目标文件的这一行有三个有效的位置信息。 位置信息的第一位表示目标文件的列的 偏移 信息 本例中,表示列的信息是 ‘A’、‘C’、‘C’,对应的数字为 0、+1、+1,(vlq 编码,在线编解码工具) 注意,这个是偏移信息; 列数从 0 开始,依次累加偏移值可以算出当前的位置信息对应的真正的列 所以本例中表示的是目标文件的第 n 行中的第 0 列,第 1 列,第 2 列(没错是第 2 列) 源文件的信息 如:‘AAAA;ACAA;ADAA;’ 位置信息的第二位表示源文件的信息,本例子中是 ‘A’、‘C’、‘D’,对应数字是 0、+1、-1 如果 sourcemaps 中的 sources 字段只有一个文件的话,那么位置信息中第二位一直是 A(不需要偏移) 假设 sourcemaps 中 sources: [‘a.js’, ‘b.js’] 本例的意思是 AAAA: 目标文件第 0 行第 0 列 对应 第 0 个文件 a.js ACAA; 目标文件第 1 行第 0 列 对应 第 1 个文件 b.js ADAA; 目标文件第 2 行第 0 列 对了 第 0 个文件 a.js (偏移是 -1 又回到了 a.js) 源文件的行信息 位置信息的第三位表示源文件中的行的信息, 理解了位置偏移的概念,我们很容易理解 如:‘AACA,CACA;AACA;‘ 那么 AACA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 1 行 CACA: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 1+1 行 AACA:目标文件的第 1 行第 0 列 对应 第 0 个文件的第 1 行 (注意:’;’ 后的行列偏移信息归 0) 源文件中的列信息 位置信息的第四位表示源文件中的列的信息 如:'AAAA,CAAC;' 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列 CAAC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列 位置信息的第五位 第五位表示变量的偏移,对应 sourcemaps 中的 names 字段,表示目标文件中的变量名对应域源文件中的变量 如:’AAAA,CAACC;AAAAD;' sourcemaps 中 names 字段是 [‘a’, ‘b’] 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,没有变量的信息 CAACC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列,有变量信息,变量在源文件中是 ‘b’ (0+1=1) AAAAD: 目标文件的第 1 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,有变量信息,变量在源文件中是 ‘a’ (1-1=0) --------- 大神分割线 --------- 怎么使用 Sourcemaps Q: 线上小程序报错,我怎么通过 sourcemaps 还原到源代码中? A: 如报错 appservice.js 1:15000, 表示目标文件第一行 第 15000 列位置报错。根据上文介绍的,通过 mappings 字段算。 Q: 不会。 A: 如果你会写代码的话,参考下边 [代码]import fs = require('fs') import {SourceMapConsumer} from 'source-map' async function originalPositionFor(line, column) { const sourceMapFilePath = '如果你不真的替换的成 sourcemaps 在硬盘中的位置,那你还是放弃自己写代码吧。 ' const sourceMapConsumer = await new SourceMapConsumer(JSON.parse(fs.readFileSync(sourceMapFilePath, 'utf8'))) return sourceMapConsumer.originalPositionFor({ line, column, }) } originalPositionFor(出错的行,出错的列) [代码] Q: 不会写代码 A: 下载最新版的开发者工具,菜单-设置-拓展设置-调试器插件 [图片] [图片] Q: 为啥都是 null? A: 每个小程序版本都应该对应一个sourcemap文件。 运营中心那里下载的 sourcemap 是对应线上最新的小程序版本。但运营中心的报错集合了多个小程序版本。拿旧小程序版本的报错信息,和最新版本的 sourcemap,是匹配不出的。开发者工具和ci 上传的时候,会提示下载对应版本的 sourcemap 信息,可以自助保存。 [图片] Q: 怎么确定有没有版本对应上 A: 下载的 sourcemap 中有个 wx 字段,标明了该 sourcemap 文件对应小程序版本号。 [图片] [图片] 前提 1.确保发生错误的小程序版本和下载回来的 sourcemap 版本是一致的。 a. 下载 sourceMap 文件,可在 mp 后台或开发者工具上传成功弹窗下载 2.确保 map 文件和发生错误的 js 文件是对应的。sourcemap 的目录和文件说明 a. APP 是主包,FULL 是整包(仅在不支持分包的低版本微信中使用),其他目录是分包 b. 每个分包下都有对应的 app-service.js.map 文件。 c. 如果是使用了按需注入特性(app.json中配置了lazyCodeLoading),那么每个分包下还会有 appservice.app.js.map(对应分包下非页面的js),和所有页面的 xxx.js.map 以上事情都确保正确之后,还是出现行列号匹配不出来的情况。那就需要进一步排查。 线上运行的小程序 sourcemap 文件是怎么生成的? 处理流程:源码 [ a.js a.js.map b.js b.js.map ] -> 开发者工具(JS转 ES5,压缩)-> 微信后台(合并 js 文件)[ appservice.app.js appservice.app.js.map]。 注意:如果源码在交给工具之前是经过了 webpack 等打包工具的处理,那源码这里需要有 map 文件。否则不需要存在 map 文件。 可以看出,map 文件经过三个步骤的处理,每个步骤都有可能导致出错,因此开发者需要先排查,是否是前两个步骤出错导致的 map 文件失效的。 如何排查前两个步骤产生的 map 文件是否有问题。 1.排查 a.js.map 文件是否有问题。 a. 可以在 a.js 的代码中写一下 throw new Error(‘test sourcemap’)。 b. 使用了 webpack 的情况下,要构建为生产环境的版本。 c. 在开发者工具模拟器中运行对应的页面,看看控制台中的报错,错误行列号是否能正常映射到源文件。 2.排查 开发者工具(JS转 ES5,压缩)步骤是否有问题。 在排查完第一步的基础上,点击预览,用微信上扫码预览,并打开调试 vConsole 功能,检查 vConsole 中是否有报错信息,检查报错信息中的行列号是否能正常映射到源文件。 如何排查 微信后台(合并 js 文件)是否有问题。 a. 一定要先排查完前两个步骤再来排查这一步,一般情况下,这一步是不会出错的。 b. 如果有问题,也只会导致 map 文件中的行号信息出现偏移。比如 Error 信息中显示报错地址是 100: 200,行号是 100。那么你可能直接用 100: 200 在 map 文件中搜索不出信息,但是如果 用 150: 200 就可以搜索出来,说明行号偏移了 50。那其他报错也可以偏移 50 后再进行搜索就找到结果。 c. 怎么排查偏移了多少?可以结合 error.message 的内容,初步判断大概错误的内容是什么。把对应的 map 文件放到这个网站上 source-map-visualization 进行搜索,找出哪些相同列号的地方。再结合 error.message 的内容进行判断。 d. 如果排查到是这一步导致的问题,请在社区上联系我们,我们会在后续版本进行修复。 依旧排查不出原因? 先整理一下按照上述步骤排查的结论,再在社区上联系我们协助
2023-02-10 - 小程序识别身份证,银行卡,营业执照,驾照
最近老是有同学问我小程序ocr识别的问题,就趁机研究了下,实现了小程序识别身份证,银行卡,驾照,营业执照,图片文字的功能。今天来给大家讲讲详细的实现流程。 先画一张流程图出来 [图片] 第一次看到这个流程图,可能有点萌,什么云开发,云函数。。。。 不要着急,我们接下来会一步步带大家实现。 先看下我们的页面和效果图。 [图片] 功能其实很简单,就是我们点对应的按钮后,去拍照或者去相册选择对应的图片。然后把图片上传到云存储,会有一个对应的图片url,然后把这个图片url传递到云函数,然后云函数里使用小程序的开发ocr能力,来识别图片,返回对应的信息回来。如下图所示,我们识别银行卡(身份证什么的就不演示了,涉及到石头哥个人隐私) [图片] 接下来就是代码的实现了。 一,首先要创建一个云开发的小程序项目 这里我前面文章有讲解过,就不再细说了,不会的同学去翻看下我之前的文章。或者看下我录制的 讲解视频 这里有一点需要注意的给大家说下 [图片] 二,创建一个简单的小程序页面 1,index.wxml如下 [图片] 2,index.js完整代码如下 [代码]Page({ //身份证 shenfenzheng() { this.photo("shenfenzheng") }, //银行卡 yinhangka() { this.photo("yinhangka") }, //行驶证 xingshizheng() { this.photo("xingshizheng") }, //拍照或者从相册选择要识别的照片 photo(type) { let that = this wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success(res) { // tempFilePath可以作为img标签的src属性显示图片 let imgUrl = res.tempFilePaths[0]; that.uploadImg(type, imgUrl) } }) }, // 上传图片到云存储 uploadImg(type, imgUrl) { let that = this wx.cloud.uploadFile({ cloudPath: 'ocr/' + type + '.png', filePath: imgUrl, // 文件路径 success: res => { console.log("上传成功", res.fileID) that.getImgUrl(type, res.fileID) }, fail: err => { console.log("上传失败", err) } }) }, //获取云存储里的图片url getImgUrl(type, imgUrl) { let that = this wx.cloud.getTempFileURL({ fileList: [imgUrl], success: res => { let imgUrl = res.fileList[0].tempFileURL console.log("获取图片url成功", imgUrl) that.shibie(type, imgUrl) }, fail: err => { console.log("获取图片url失败", err) } }) }, //调用云函数,实现OCR识别 shibie(type, imgUrl) { wx.cloud.callFunction({ name: "ocr", data: { type: type, imgUrl: imgUrl }, success(res) { console.log("识别成功", res) }, fail(res) { console.log("识别失败", res) } }) } }) [代码] 上面代码注释讲解的很清楚了,再结合我们的流程图,相信你可以看明白。 [图片] 三,重头戏来了,识别的核心代码是下面这个云函数 [图片] 云函数的完整代码也给大家贴出来 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async(event, context) => { let { type, imgUrl } = event switch (type) { case 'shenfenzheng': { // 识别身份证 return shenfenzheng(imgUrl) } case 'yinhangka': { // 识别银行卡 return yinhangka(imgUrl) } case 'xingshizheng': { // 识别行驶证 return xingshizheng(imgUrl) } default: { return } } } //识别身份证 async function shenfenzheng(imgUrl) { try { const result = await cloud.openapi.ocr.idcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } //识别银行卡 async function yinhangka(imgUrl) { try { const result = await cloud.openapi.ocr.bankcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } //识别行驶证 async function xingshizheng(imgUrl) { try { const result = await cloud.openapi.ocr.vehicleLicense({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } [代码] 其实没什么特别的,就是用一个switch方法,根据用户传入的不同的type值,来实现不同的识别效果。 如用传入的type是‘ yinhangka’,我们就调用银行卡识别 [代码]try { const result = await cloud.openapi.ocr.bankcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } [代码] 进而把识别的结果返回给小程序端,如下图 [图片] 到这里我们就完整的实现了,小程序识别身份证,银行卡,行驶证的功能。至于别的更多的ocr识别,可以去看小程序官方文档,结合着我的这篇文章,相信你也可以轻松实现更多的图片识别。 [图片] 源码其实在上面都已经贴给大家了,如果你觉得不完整,想要完整的源码可以在文章底部留言或者私信我。
2019-10-30 - 【开箱即用】分享几个好看的波浪动画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 - 复制任意微信小程序页面路径
以下以微信小程序“虎牙直播”为例,演示如何复制微信小程序页面的路径。 1.进入小程序的“关于虎牙直播”页面 [图片] 2.点击右上角的“…”进入“更多资料”页面 [图片] [图片] [图片] 3.复制AppID:wx74767bf0b684f7d3 4.进入小程序后台输入appid并搜索,然后点下一步 [图片] 5.鼠标移动到“获取更多页面路径”,在弹出窗口输入当前登陆的小程序的任意开发者微信号,然后点击开启,出现顶部的“开启入口成功”就可以使用手机访问“虎牙直播”任意页面进行复制了 [图片] 6.某个直播间的页面路径:pages/main/liveRoom/index.html?anchorUid=1678113423&source=search[图片] PS:复制出来的页面路径在小程序里使用的时候记得删除 .html 才能正常访问。
2020-01-16 - 云服务器调用security.imgSecCheck完成代码分享
云服务器代码: // 云函数入口文件 const cloud = require(‘wx-server-sdk’) cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const {value} = event; try { const res = await cloud.openapi.security.imgSecCheck({ media: { header: {‘Content-Type’: ‘application/octet-stream’}, contentType: ‘image/png’, value:Buffer.from(value) } }) return res; } catch (err) { return err; } } 本地函数: wx.chooseImage({count: 1}).then((res) => { if(!res.tempFilePaths[0]){ return; } console.log(JSON.stringify(res)) if (res.tempFiles[0] && res.tempFiles[0].size > 1024 * 1024) { wx.showToast({ title: ‘图片不能大于1M’, icon: ‘none’ }) return; } wx.getFileSystemManager().readFile({ filePath: res.tempFilePaths[0], success: buffer => { console.log(buffer.data) wx.cloud.callFunction({ name: ‘checkImg’, data: { value: buffer.data } }).then( imgRes => { console.log(JSON.stringify(imgRes)) if(imgRes.result.errorCode == ‘87014’){ wx.showToast({ title:‘图片含有违法违规内容’, icon:‘none’ }) return }else{ //图片正常 } [代码] } ) }, fail: err => { console.log(err) } } ) 我相信做出来的人很多,但是没有分享出来,我今天分享出来就是为了避免更多程序员不要在这种简单的问题上,浪费太多的时间,我就浪费了很多时间,兼职太坑爹了[代码]
2019-07-26 - 云开发·云调用生成小程序码
云开发·云调用生成小程序码 小程序云开发已经支持云调用,开放了很多接口,一直想要的获取小程序码也支持了。这下轻量的小程序也可以有自定义小程序码的功能。 1. 需求 获得一个带参数的小程序码,传播出去以后,用户扫码进入指定页面,根据参数做不同的处理。本文只讲小程序码生成、存储、展示部分。参数处理不多介绍,可以查看 项目代码 了解更多。 2. 开通云开发 新建小程序可以从开发工具的云开发模板初始化项目,根据云开发操作指引新建项目即可。 但是这里有个问题,已发布小程序的页面才能生成小程序码。如果现有的小程序没有开通云开发,需要做以下几步: 开发工具开通云开发,设定云开发的环境; 将原来的代码(除了[代码]project.config.json[代码]以外的所有文件)放到新建的 [代码]miniprogram[代码] 目录; 新增 [代码]cloudfunctions[代码] 目录; [代码]app.json[代码] 新增配置 [代码]"cloud": true[代码]; [代码]project.config.json[代码] 配置 [代码]"miniprogramRoot":"miniprogram/"[代码] 和 [代码]"cloudfunctionRoot":"cloudfunctions/"[代码]; 修改小程序基础库版本,最低要 2.3.0 [代码]"libVersion": "2.3.0"[代码]。 3. 生成小程序码 下面可以开始写代码开发了,开始之前,建议先看完官方教程。特别是开发工具的使用步骤,开发和调试时如果遇到奇怪的问题,可以尝试重启开发工具、重装开发工具,也可以去微信开放社区发帖。(重启和重装都是我在社区中发现的答案,能解决各种不应该存在的问题)。 3.1 准备文件 在 [代码]cloudfunctions[代码]目录右键新建Node.js云函数 [代码]getqr[代码]。 生成小程序码需要单独指定权限。在 [代码]getqr[代码] 目录新建 [代码]config.json[代码] ,里面写以下内容: [代码]{ "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } [代码] 小程序码的获取方式有三种,这里只用到了接口 getUnlimited,选择这个接口的原因是漂亮的圆形小程序码,数量无限制。具体区别可以去 获取小程序码官方文档查看详情。 正常情况下,这个时候云函数可以部署测试了。如果遇到部署不成功、各种权限问题,可以尝试本地部署上传所有文件、重启试试。 3.2 生成小程序码 生成小程序码的代码如下,可以指定页面和页面参数 [代码]scene[代码],还有小程序码的尺寸。 注意这里的 [代码]scene[代码] 有限制: 最大32个可见字符; 只支持数字,大小写英文以及部分特殊字符:[代码]!#$&'()*+,/:;=?@-._~[代码]; 注意参数格式:下面实例代码生成小程序码后,扫码获得 [代码]pages/demo/demo?scene=id%3D6[代码] 。 [代码]try { const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/demo/demo', scene: 'id=6', width: 240, }) console.log(result) return result } catch (err) { console.log(err) return err } [代码] 直接调用,比服务端调用少了 access_token 参数。 3.3 上传到云存储 返回值中的 buffer 就是图片内容,直接上传到云存储: [代码]const uploadResult = await cloud.uploadFile({ cloudPath: 'shareqr/' + qr_name_hash + '.jpg', fileContent: result.buffer, }); [代码] 我在云存储新建了 [代码]shareqr[代码] 目录保存小程序码; 图片名根据参数取md5摘要; [代码]getUnlimited[代码] 返回的图像是 [代码]jpeg[代码] 格式,后缀硬编码写 [代码].jpg[代码]。 3.4 获取图片临时路径 直接上代码 [代码]getURLReault = await cloud.getTempFileURL({ fileList: [uploadResult.fileID] }); fileObj = getURLReault.fileList[0] return fileObj [代码] 3.5 直接从存云存储获取 生成过以后图片已经保存在云存储,用同样的参数第二次调用没必要再生成一次,去掉一次网络请求,可以节省不少时间。 前面说到文件名使用请求参数摘要,知道了目录和文件名,再加上文件bucket前缀就可以拼出来 [代码]fileID[代码],用[代码]fileID[代码] 可以查询云存储的文件。 比如我刚刚生成的 fileID 是 [代码]cloud://dev-xxxx.8888-dev-xxxx/qr/44ea42f05091c3bec771123e6e8cd4c2.jpg[代码], 前缀就是 [代码]cloud://dev-xxxx.8888-dev-xxxx/[代码]。再拼上目录、文件名、后缀就是 [代码]fileID[代码]。 注:此处的 [代码]fileID[代码]拼接方法并不是来自官方文档,只是在使用中发现这个前缀不会变。还需要官方解释说明[代码]fileID[代码]规则。 如果会改变,就需要再用云数据库存储[代码]fileID[代码],更麻烦一些。 3.6 云函数完整代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk'); const crypto = require('crypto'); const bucketPrefix = 'cloud://dev-xxxx.8888-idc-4d11a4-1257831628/qr/'; // env: 'dev-xxxx' // 云函数入口函数 exports.main = async (event, context) => { const full_path = event.page + '?' + event.scene; const qr_name_hash = crypto.createHash('md5').update(full_path).digest('hex'); const temp_id = bucketPrefix + qr_name_hash + '.jpg'; // return { // full_path, // qr_name_hash, // temp_id // } try { // 先尝试获取文件,存在就直接返回临时路径 let getURLReault = await cloud.getTempFileURL({ fileList: [temp_id] }); // return getURLReault; let fileObj = getURLReault.fileList[0]; if (fileObj.tempFileURL != '') { fileObj.fromCache = true; return fileObj; } // 生成小程序码 const wxacodeResult = await cloud.openapi.wxacode.getUnlimited({ scene: event.scene, page: event.page, width: 280 //二维码的宽度,单位 px,最小 280px,最大 1280px }) // return wxacodeResult; if (wxacodeResult.errCode != 0) { // 生成二维码失败,返回错误信息 return wxacodeResult; } // 上传到云存储 const uploadResult = await cloud.uploadFile({ cloudPath: 'qr/' + qr_name_hash + '.jpg', fileContent: wxacodeResult.buffer, }); // return uploadResult; if (!uploadResult.fileID) { //上传失败,返回错误信息 return uploadResult; } // 获取图片临时路径 getURLReault = await cloud.getTempFileURL({ fileList: [uploadResult.fileID] }); fileObj = getURLReault.fileList[0]; fileObj.fromCache = false; // 上传成功,获取文件临时url,返回临时路径的查询结果 return fileObj; } catch (err) { return err } } [代码] 4. 小程序页面调用 调用页面就比较简单了,在小程序新建一个 [代码]pages/share/share[代码] 在 [代码]onLoad[代码] 函数调用云函数。 [代码]// 使用前记得先初始化云函数,一版放到 app.js onLaunch() 中 // wx.cloud.init({env: 'dev-8888'}) wx.cloud.callFunction({ name: 'getqr', data: { page: 'pages/demo/demo', scene: 'id=6', } }).then(res => { console.log(res.result); if (res.result.status == 0) { _this.setData({ qr_url: res.result.tempFileURL }) }else{ wx.showToast({ icon: 'none', title: '调用失败', }) } }).catch(err => { console.error(err); wx.showToast({ icon: 'none', title: '调用失败', }) }) [代码] 至此完整的调用过程已经全部完成,详细代码可以到 项目代码 查看。 代码中还对入口页面和share页面的参数做了包装,云函数可以直接使用,小程序可以稍做修改适应自己业务。 写在最后 小程序云开发已经开放了很多功能,除了这次提到的生成小程序码,云调用还可以发送模板消息。有需要的开发者又一个理由可以快速上线新功能了。 云开发还开放了[代码]HTTP API[代码],也就是用自己的服务器调用云函数。以前看完云开发介绍文章最大的疑问就是,你说的都很好,可是后台数据怎么管理呢?不能跟自己的服务器结合,只能放一些轻量的小程序。有了 [代码]HTTP API[代码] 以后就可以用自己的服务器做管理后台了。这时候你要问,都用上服务器了,还需要云开发做什么。首先,云开发免费;其次,免费功能已经够强,就差不能做Web管理后台了;最后,获取access_token(小程序及小游戏调用不要求IP地址在白名单内。)
2020-07-10