- 刚收到通知获取手机号收费开始了?
[图片] https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getRealtimePhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/paymentManage.html 未来:旧版本接口依然可以使用,只是需要收费而已,不想做改动的交钱就行了。如果想用新API方法就去改吧,多花一分钱。 可能支持的省钱办法: 授权手机号后,服务端将openId、手机号进行绑定。用户onLaunch打开小程序的时候通过wx.login获取code去解密openId,同时由于服务端已经绑定过手机号,所以可以使用该手机号进行登录,并同步返回token、jwtToken等登录态。这样可以做到用户冷启动小程序时自动登录上,减少使用授权的逻辑。业务按钮点击后 先调用wx.login,如果返回token则进行后续业务,如果没返回则弹出自定义弹窗,弹窗内点击按钮再进行手机号授权。(也可以在部分页面onLoad里wx.login),这个场景因为会延长流程,所以产品说不考虑,先直接打开页面就登录上,你们的各自看各自的业务场景吧。然后有四个疑问: 充值购买次数后会,如果小程序被封禁了,充值的金额是否可退款。购买数量是否支持按量付费?如果次数用完了,未购买新的次数,用户端的表现是什么?如果次数用完了,之前文档说的余量20%、10%、5%时会发模板消息提醒,文档相关现在已经删除了,是否还会发?[图片] ———————————————————————————————————————————————— 今天看了下文档做了改动: 退款规则:若购买有误,且未正式开始使用资源包前,可以在支付成功后的7天内申请退款。款项将在3-5个工作日内从原支付路径返回;若资源包已经开始使用(使用1次及以上),则不能申请退款;若支付成功后超过7天,未发起退款申请,亦不能再申请退款。 那么小程序被封了应该是不退的。不确定,等官方回复次数用完了,用户授权不会弹出授权弹窗,会返回一个errNo:1400001,用户判断等于这个errNo的时候跳转到自己的账密登录页面。不确定,等官方回复———————————————————————————————————————————————— 据了解老版本的快速验证组件(获取手机号),180天才会发送短信验证一次,为啥能每次授权都收费0.03元。 社区搜了一张图,180天没验证的应该会弹这个,不是说是短信运营成本么?为啥不是第180天验证那次费用让我们付,而是每次授权都付? [图片] 手机号授权改造后的效果: 打开职位详情页:优先调用接口判断openId是否绑定过。 如果未绑定:使用button的open-type=“getPhoneNumber”,点击报名弹出手机号授权,授权成功后与openId进行绑定落库。 如果已绑定,页面通过变量判断使用wx.login静默授权,同时服务端拿到绑定的手机号后进行登录操作,同步返回登录态(token/jwtToken)。 退出登录页面增加解绑操作(服务端解除openId与手机号的绑定),此时用户再次点击报名,就会弹出手机号授权,方便用户切换手机号。 [视频]
2023-07-27 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 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 - 如何实现圣诞节星星飘落效果
圣诞节快到啦~🎄🎄🎄🎄咱们也试着做做小程序版本的星星✨飘落效果吧~ 先来个效果图: [图片] 484听起来很叼,看起来也就那样。 来咱们上手开始撸 页面内容wxml,先简单切个图吧。 [代码]<view class="container"> <image src="https://qiniu-image.qtshe.com/merry_001.jpg" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_02.jpg" alt="" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_03.jpg" alt="" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_04.jpg" alt="" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_05.jpg" alt="" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_06.jpg" alt="" mode="widthFix"/> <image src="https://qiniu-image.qtshe.com/merry_07.jpg" alt="" mode="widthFix"/> </view> <canvas canvas-id="myCanvas" /> [代码] 页面样式wxss,因为切片用的不太熟练,图片之间有个2rpx的空隙。 [代码].container { height: 100%; box-sizing: border-box; min-height: 100vh; } image { width: 100%; display: block; margin-top: -2rpx; //不会切图造的孽 } canvas { width: 100%; min-height:100vh; position: fixed; top: 0; z-index: 888; } [代码] 重点JS: [代码]//获取应用实例 const app = getApp() // 存储所有的星星 const stars = [] // 下落的加速度 const G = 0.01 const stime = 60 // 速度上限,避免速度过快 const SPEED_LIMIT_X = 1 const SPEED_LIMIT_Y = 1 const W = wx.getSystemInfoSync().windowWidth const H = wx.getSystemInfoSync().windowHeight var starImage = '' //星星素材 wx.getImageInfo({ src: 'https://qiniu-image.qtshe.com/WechatIMG470.png', success: (res)=> { starImage = res.path } }) Page({ onLoad() { this.setAudioPlay() }, onShow() { this.createStar() }, createStar() { let starCount = 350 //星星总的数量 let starNum = 0 //当前生成星星数 let deltaTime = 0 let ctx = wx.createCanvasContext('myCanvas') let requestAnimationFrame = (() => { return (callback) => { setTimeout(callback, 1000 / stime) } })() starLoop() function starLoop() { requestAnimationFrame(starLoop) ctx.clearRect(0, 0, W, H) deltaTime = 20 //每次增加的星星数量 starNum += deltaTime if (starNum > starCount) { stars.push( new Star(Math.random() * W, 0, Math.random() * 5 + 5) ); starNum %= starCount } stars.map((s, i) => { //重复绘制 s.update() s.draw() if (s.y >= H) { //大于屏幕高度的就从数组里去掉 stars.splice(i, 1) } }) ctx.draw() } function Star(x, y, radius) { this.x = x this.y = y this.sx = 0 this.sy = 0 this.deg = 0 this.radius = radius this.ax = Math.random() < 0.5 ? 0.005 : -0.005 } Star.prototype.update = function() { const deltaDeg = Math.random() * 0.6 + 0.2 this.sx += this.ax if (this.sx >= SPEED_LIMIT_X || this.sx <= -SPEED_LIMIT_X) { this.ax *= -1 } if (this.sy < SPEED_LIMIT_Y) { this.sy += G } this.deg += deltaDeg this.x += this.sx this.y += this.sy } Star.prototype.draw = function() { const radius = this.radius ctx.save() ctx.translate(this.x, this.y) ctx.rotate(this.deg * Math.PI / 180) ctx.drawImage(starImage, -radius, -radius * 1.8, radius * 2, radius * 2) ctx.restore() } }, setAudioPlay() { let adctx = wx.createInnerAudioContext() adctx.autoplay = true adctx.loop = true adctx.src = 'https://dn-qtshe.qbox.me/Jingle%20Bells.mp3' adctx.onPlay(() => { console.log('开始播放') adctx.play() }) } }) [代码] 以上只是简单实现了一个星星飘落的效果,预览的时候需要开启不校验合法域名哦~ 目前还有更优的h5版本,使用Three.js实现的,在小程序内使用Three.js对于我来说有点打脑壳,先把效果分享出来吧。 h5版本,手机看效果最佳 h5源码可直接右键查看:https://qiniu-web.qtshe.com/merryChrimas.html
2023-12-07 - 如何实现一个简单的http请求的封装
好久没发文章了,最近浏览社区看到比较多的请求封装,以及还有在使用原始请求的童鞋。为了减少代码,提升观赏性,我也水一篇吧,希望对大家有所帮助。 默认请求方式,大家每次都这样一些写相同的代码,会不会觉得烦,反正我是觉得头大 😂 [代码]wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '', y: '' }, header: { 'content-type': 'application/json' // 默认值 }, success (res) { console.log(res.data) } }) [代码] 来,进入正题吧,把这块代码封装下。 首先新建个request文件夹,内含request.js 代码如下: [代码]/** * 网络请求封装 */ import config from '../config/config.js' import util from '../util/util.js' // 获取接口地址 const _getPath = path => (config.DOMAIN + path) // 封装接口公共参数 const _getParams = (data = {}) => { const timestamp = Date.now() //时间戳 const deviceId = Math.random() //随机数 const version = data.version || config.version //当前版本号,自定或者取小程序的都行 const appKey = data.appKey || config.appKey //某个小程序或者客户端的字段区分 //加密下,防止其他人随意刷接口,加密目前采用的md5,后端进行校验,这段里面的参数你们自定,别让其他人知道就行,我这里就是举个例子 const sign = data.sign || util.md5(config.appKey + timestamp + deviceId) return Object.assign({}, { timestamp, sign, deviceId, version, appKey }, data) } // 修改接口默认content-type请求头 const _getHeader = (headers = {}) => { return Object.assign({ 'content-type': `application/x-www-form-urlencoded` }, headers) } // 存储登录态失效的跳转 const _handleCode = (res) => { const {statusCode} = res const {msg, code} = res.data // code为 4004 时一般表示storage里存储的token失效或者未登录 if (statusCode === 200 && (code === 4004)) { wx.navigateTo({ url: '/pages/login/login' }) } return true } /** * get 请求, post 请求 * @param {String} path 请求url,必须 * @param {Object} params 请求参数,可选 * @param {String} method 请求方式 默认为 POST * @param {Object} option 可选配置,如设置请求头 { headers:{} } * * option = { * headers: {} // 请求头 * } * */ export const postAjax = (path, params) => { const url = _getPath(path) const data = _getParams(params) //如果某个参数值为undefined,则删掉该字段,不传给后端 for (let e in data) { if (data[e] === 'undefined') { delete data[e] } } // 处理请求头,加上最近比较流行的jwtToken(具体的自己百度去) const header = util.extend( true, { "content-type": "application/x-www-form-urlencoded", 'Authorization': wx.getStorageSync('jwtToken') ? `Bearer ${wx.getStorageSync('jwtToken')}` : '', }, header ); const method = 'POST' return new Promise((resolve, reject) => { wx.request({ url, method, data, header, success: (res) => { const result = _handleCode(res) result && resolve(res.data) }, fail: function (res) { reject(res.data) } }); }) } [代码] 那么如何调用呢? [代码]//把request的 postAjax注册到getApp()下,调用时: const app = getApp() let postData = { //这里填写请求参数,基础参数里的appKey等参数可在这里覆盖传入。 } app.postAjax(url, postData).then((res) => { if (res.success) { //这里处理请求成功逻辑。 } else { //wx.showToast大家觉得麻烦也可以写到util.js里,调用时:util.toast(msg) 即可。 wx.showToast({ title: res.msg || '服务器错误,请稍后重试', icon: "none" }) } }).catch(err => { //这里根据自己场景看是否封装到request.js里 console.log(err) }) [代码] config.js 主要是处理正式环境、预发环境、测试环境、开发环境的配置 [代码]//发版须修改version, env const env = { dev: { DOMAIN: 'https://dev-api.weixin.com' }, test: { DOMAIN: 'https://test-api.weixin.com', }, pro: { DOMAIN: 'https://api.qtshe.com' } } module.exports = { ...env.pro } [代码] 以上就是简单的一个request的封装,包含登录态失效统一跳转、包含公共参数的统一封装。 老规矩,最后放代码片段,util里内置了md5方法以及深拷贝方法,具体的我也不啰嗦,大家自行查看即可~ https://developers.weixin.qq.com/s/gbPSLOmd7Aft
2020-04-03 - 如何使用云开发实现发送邮件功能
新手:因为是刚接触云开发,所以有说的不对的地方,大佬帮忙指正。 背景:摆脱每周五晚写周报的烦恼。 目标:本次小目标就先实现小程序云函数发送邮件功能。 终极目标,写一个todolist小程序,每周五汇总本周工作,下周工作,使用云函数定时器触发,定时发送邮件。 [图片] 首先给大家介绍一个发邮件的第三方模块,nodemailer Nodemailer Github地址 Nodemailer官方文档 找到官方文档最下面,有一串示例代码。这次我们要用到的就是它了。 使用nodemailer我们需要使用到的传输方式:SMTP 文档如下:https://nodemailer.com/smtp/ 这里有介绍如何使用,示例代码我就不给大家复制过来了。 何为SMTP? 这里给大家百度了一发: SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。SMTP独立于特定的传输子系统,且只需要可靠有序的数据流信道支持,SMTP的重要特性之一是其能跨越网络传输邮件,即“SMTP邮件中继”。使用SMTP,可实现相同网络处理进程之间的邮件传输,也可通过中继器或网关实现某处理进程与其他网络之间的邮件传输。 [图片] 怎么开启SMTP服务? 自己搭建邮件服务器是非常麻烦的,我们可以借助于QQ邮箱、Gmail、163个人邮件系统或企业邮件系统开启SMTP服务,SMTP也就是简单邮件传输协议,通过它可以控制邮件的中转方式,帮助计算机在发送或中转信件时找到下一个目的地,也就是发送邮件。 不同的邮件系统有着不同的smtp发送邮件服务器,端口号也会有所不同。 我这边以QQ邮箱为示例操作下。 在QQ邮箱的设置>账户 [图片] [图片] 我们可以看到IMAP/SMTP的开启和关闭,这里我们先开启,然后你会得到一串授权码,保存下这个授权码,我们后续会用到。QQ邮箱的发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587。 [图片] 使用云函数发送邮件 首先我们创建一个云函数,比如就叫sendMail 吧。在该云函数的package.json里添加如下代码。右键点击sendMail云函数,在终端中打开,或者本地终端cd到该云函数文件夹内也可。输入npm install 安装nodemailer最新版依赖。 [代码] "dependencies": { "wx-server-sdk": "~2.1.2", "nodemailer": "latest" } [代码] 然后在index.js中添加如下代码: [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event) => { const nodemailer = require("nodemailer"); let transporter = nodemailer.createTransport({ host: "smtp.qq.com", //SMTP服务器地址 port: 465, //端口号,通常为465,587,25,不同的邮件客户端端口号可能不一样 secure: true, //如果端口是465,就为true;如果是587、25,就填false auth: { user: "8662054@qq.com", //这是我的邮箱账号,改为你们的即可。 pass: "myukmlybjotjbghh" //这里是我们上面说要用到的授权码,不是QQ邮箱的密码 } }); let postMsg = { from: event.from, //发件邮箱 to:event.to, //收件人 subject: event.subject, text: event.text, html: event.html // text 和 html只能同时存在一个。 }; let res = await transporter.sendMail(postMsg); return res; } [代码] 右键部署上传云函数。 到这里我们的发送邮件云函数就完成啦。 [图片] 如何调用发送邮件云函数? 调用方式也很简单: [代码]<input type="text" placeholder="请输入收件人邮件地址" bindinput="handleToAddress"/> <input type="text" placeholder="请输入邮件主题" bindinput="handleSubject"/> <textarea placeholder="请输入邮件内容" auto-height bindinput="handleTextarea" /> <button bindtap="handleClick">发送邮件</button> [代码] [图片] 这里因为我们绑定了 发件人是我的固定邮箱,所以没有增加发送人的输入框。 textarea 我有尝试使用editor组件生成富文本进行操作,最后发现弄出来样式太龊,就干掉了。 [代码]对了差点忘了: 邮件是支持: html:富文本 cc: 支持 抄送 bcc: 支持 密送 attachments: 支持多种附件形式,可以是String, Buffer或Stream 目前我这边用不到,就不展示了,有需要的可查看官方文档,或者评论给你示例代码。 [代码] 至于我们调用的地方: 首先拿到输入的值,然后调用云函数发送。 [代码]Page({ data: { }, handleToAddress(e) { this.setData({ toAddress: e.detail.value }) }, handleSubject(e) { this.setData({ toSubject: e.detail.value }) }, handleTextarea(e) { this.setData({ toTextArea: e.detail.value }) }, handleClick() { wx.showLoading({ title: '发送中' }) const {toAddress, toSubject, toTextArea} = this.data wx.cloud.callFunction({ name: 'sendMail', data: { from: '8662054@qq.com', to: toAddress, subject: toSubject, text: toTextArea // html: '<p><b>你好:</b><img src=""></p>' +'<p>欢迎欢迎<br/></p>' }, success: res => { wx.hideLoading() console.log(res) wx.showToast({ title: '发送成功' }) }, fail: err =>{ wx.hideLoading() console.log(err) } }) } }) [代码] 实现效果图 [图片] [图片] [图片] 以上就完成了使用云开发实现发送邮件功能。 有不懂的欢迎评论留言。 老规矩结尾的代码片段 没了 😂 。 代码仓库地址:https://github.com/minchangyong/wx-cloud-demo 最后感谢李东bbsky 大佬指导。
2020-07-09 - 如何实现加入购物车的抛物线效果
一、场景分析 在一些如商城、点餐小程序中实现购物车抛物线效果可以提升界面趣味性增加小程序用户体验。 二、效果预览 效果图压缩后速度有点快,请下载代码片段预览 [图片] 三、实现原理 当用户点击物品时记录当前触摸点,根据触摸点计算抛物线运动的顶点位置,通过触摸点、顶点、购物车的位置计算出抛物线运动轨迹,然后控制 icon 运动。 计算购物车在当前手机内的位置 [代码]/** 设置购物车的坐标位置 **/ wx.getSystemInfo({ success: (res) => { let busPos = {} // x y 坐标分别取屏幕百分之八十的位置 busPos['x'] = res.windowWidth * 0.8 busPos['y'] = res.windowHeight * 0.8 this.setData({ busPos }) } }) [代码] 商品点击事件的处理 点击物品后记录点击的位置,然后根据点击位置计算出抛物线的顶点位置,计算方式为点击位置的上方+150,右边+150(需要根据点击位置是否在购物左边还是右边进行判断)。 根据点击,顶点,购物车三个位置计算出抛物线运动轨迹 以3个控制点为例,点A、B、C、AB 上设置点 D、BC 上设置点 E、DE 连线上设置点 F,则最终的贝塞尔曲线是点F的坐标轨迹; 计算相邻控制点间距; 根据完成时间,计算每次执行时 D 在AB方向上移动的距离,E 在 BC 方向上移动的距离; 时间每递增 100ms,则 D、E 在指定方向上发生位移,F 在 DE 上的位移则可通过 AD/AB = DF/DE 得出; 根据 DE 的正余弦值和 DE 的值计算出F的坐标。 开启定时器,依次按照贝塞尔曲线位置做动画位移 使用定时器将抛物线运动轨迹做动画位移。 定时器执行完动画后将购物车角标+1 老规矩,结尾放代码片段 https://developers.weixin.qq.com/s/PnYfitmG7Hxv
2022-03-04