- 小程序用户头像昵称获取规则调整公告
调整背景在小程序内,开发者可以通过 wx.login 接口直接获取用户的 openId 与 unionId 信息,实现微信身份登录,支持开发者在多个小程序或其它应用间匿名关联同一用户。 同时,为了满足部分小程序业务中需要创建用户的昵称与头像的诉求,平台提供了 wx.getUserProfile 接口,支持在用户授权的前提下,快速使用自己的微信昵称头像。 但实践中发现有部分小程序,在用户刚打开小程序时就要求收集用户的微信昵称头像,或者在支付前等不合理路径上要求授权。如果用户拒绝授权,则无法使用小程序或相关功能。在已经获取用户的 openId 与 unionId 信息情况下,用户的微信昵称与头像并不是用户使用小程序的必要条件。为减少此类不合理的强迫授权情况,作出如下调整。 调整说明自 2022 年 10 月 25 日 24 时后(以下统称 “生效期” ),用户头像昵称获取规则将进行如下调整: 自生效期起,小程序 wx.getUserProfile 接口将被收回:生效期后发布的小程序新版本,通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。自生效期起,插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回:生效期后发布的插件新版本,通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响,但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。「头像昵称填写能力」支持获取用户头像昵称:如业务需获取用户头像昵称,可以使用「头像昵称填写能力」(基础库 2.21.2 版本开始支持),具体实践可见下方《最佳实践》。小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.21.2 以下版本的头像昵称获取需求:上述「头像昵称填写能力」从基础库 2.21.2 版本开始支持(覆盖微信 8.0.16 以上版本)。对于来自更低版本的基础库与微信客户端的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将返回用户头像昵称,开发者可继续使用以上能力做向下兼容。对于上述 3,wx.getUserProfile 接口、wx.getUserInfo 接口、头像昵称填写能力的基础库版本支持能力详细对比见下表: [图片] *针对低版本基础库,兼容处理可参考 兼容文档 请已使用 wx.getUserProfile 接口的小程序开发者和已使用 wx.getUserInfo 接口的插件开发者尽快适配。小游戏不受本次调整影响。 最佳实践小程序可在个人中心或设置等页面使用头像昵称填写能力让用户完善个人资料: [图片] 微信团队 2022年5月9日
05-10 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 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即可。
01-20 - video-swiper短视频轮播,解决方案2(增加动态加载数据)
实现短视频小程序指定到某个视频开始轮播,方案1已经解析过,这里就不多说了,不懂的可以请移步到这里查看:https://developers.weixin.qq.com/community/develop/article/doc/000c2e0afc8cc8b2a96b36d665b413 这里主要讲的是,如何进行动态加载数据问题 第一步,在获取数据列表中加个条件判断,如果超过你设置的长度就算二次获取数据,进行数据切割加到将要预览的数组里面,代码如下(主要看条件判断,这里以10个数据为例): videoList: { type: Array, value: [], observer: function observer() { var newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; if (newVal.length) { newVal.map((item, index) => { return item.idxKey = index + 1; }); if (newVal.length<=10) { this._videoListChanged(newVal); } else { // 重点是这里的条件判断 // 防止当前数组被污染 let arr = JSON.parse(JSON.stringify(newVal)); // 去掉已有的数据 let nextArr = arr.splice(this.data.total); this.data.nextQueue.push(...nextArr); } this.setData({total: newVal.length}) } } } 第二步,在每次滑动视频时,判断下当前视频总数和剩下视频个数,满足条件即可请求加载数据,代码如下: // 判断总数据是否大于等于10,并且下滑剩下4个视频开始请求接口拿数据;这里大小可以根据自己需求修改 if (total>=10 && nextQueue.length < 5) { this.triggerEvent('UpdataVideo', {}); } 就加这两步,轻松完成一个短视频,从定位到某个视频开始播放,到数据没有时进行预加载视频。 有什么问题,欢迎随时咨询。 完整版代码片段:https://developers.weixin.qq.com/s/Ikk98ymm7tph
2021-04-14 - 微信小程序前端开发踩坑——引入weui组件库
前言 今天在写微信小程序前端页面,想引入weui组件库来完成开发。结果按着官方文档来遇到了一堆问题,最后靠着不断百度查资料才最终解决。所以将过程记录一下,避免后面再遇到这类坑。 注意:本文默认读者已知道怎么使用npm 1. 初始化 以管理员身份运行命令行窗口(cmd),在cmd中进入项目的根目录。然后输入以下命令: [代码]npm init [代码] 后面一路按回车健即可,最终会在项目的根目录中创建出一个名为package.json的文件。 2. 安装weui组件库 在cmd中紧接着输入以下命令: [代码]npm install weui-miniprogram [代码] 命令执行完毕后会多出来一个node_modules文件夹,里面包含了weui组件库。 3. 构建npm 在微信开发者工具中,选择“工具”->“构建npm”。如无意外会出现类似“没有找到可以构建的NPM包……”这样的报错。 这时就需要在项目根目录找到package.config.json文件,修改相关的配置如下: [代码]{ ... "setting": { ... "packNpmManually": true, "packNpmRelationList": [ { "packageJsonPath": "./package.json", "miniprogramNpmDistDir": "./" } ] } } [代码] 继续在开发者工具中的“详情”->“本地设置”里检查是否勾选上“使用npm模块”选项,若没勾选则勾选上。 完成上述配置后,重新构建npm,即可构建完成。 4. 重启项目 在开发者工具中“项目”->“重新打开此项目”,完成对项目的重启。 注意:这一步非常重要!!!否则引入组件会提示找不到文件!!! 5. 引入wxss 在app.wxss中,引入weui库的wxss文件 [代码]@import 'miniprogram_npm/weui-miniprogram/weui-wxss/dist/style/weui.wxss'; [代码] 引入时要根据实际情况调整路径,但最长后缀均为 [代码]/weui-miniprogram/weui-wxss/dist/style/weui.wxss [代码] 6. 引入组件 在想要使用组件的页面对应的js文件中,对组件进行的引入。一定要注意自己项目的目录结构!!! [图片] 而官方文档的写法是 [图片] 如果直接照搬官方文档的写法,则忽略了目录结构,会报错!! 接着在要使用组件的页面对应的wxml文件中使用该组件即可 [代码]<mp-dialog title="test" show="{{true}}" bindbuttontap="tapDialogButton" buttons="{{[{text: '取消'}, {text: '确认'}]}}"> <view>test content</view> </mp-dialog> [代码] 效果如下: [图片] 后记 不得不说,前端开发的坑实在是太多了,上面记录的过程我摸索了一个多小时。看来平时一定要多注意总结才行,不然真的非常消耗时间!!! 创作不易,觉得有用麻烦点个赞,谢谢~~~
01-11 - 【改进版】如何从零实现上拉无限加载瀑布流组件
前言 之前分享过一篇瀑布流如何实现的文章,经过时间的证明,之前的做法并不好,性能上会有问题,所以还是不投机取巧了,老老实实的实现。 回顾: 通过grid-auto-rows的特性实现 item通过grid-row设置高度 js获取节点高度计算span的值 通过wxs设置css的变量实现修改样式 痛点: grid-auto-rows数值越大,span计算准确度越低。 谷歌浏览器、微信开发工具,如果界面高度超过[代码]1000 * grid-auto-rows[代码]的高度,那么后面的内容就不会显示了,谷歌解释说是为了不过渡消耗性能。 因为性能问题,超过1000的item就不会显示了,全会挤压在最下面,导致页面非常卡,开发工具能直接卡崩溃,手机上还没发现这个问题,之前也忽视了这个问题,后面调试的时候就非常恼火,开发工具跟真机上效果不一致。 为了保证span计算的准确度高,grid-auto-rows一般设置成1-10px,1px准确就等于view的高度,但是超过1000px就卡没了。 实现思路 通过selectAllComponents获取所有的子节点 通过getComputedStyle获取节点的高度 简单的排序算法计算节点位置 设置节点的样式 通过wxs的[代码]getState[代码]储存每屏节点渲染的数据 触发[代码]image[代码]组件的[代码]load[代码]事件重新计算并渲染节点位置 创建组件 需要开启抽象节点 [代码]// waterfall/index.json { "componentGenerics": { "selectable": true } } [代码] 利用wxs响应事件获取页面的节点 [代码]<view class="waterfall" views="{{ views.length }}" data-option="{{ {span} }}" change:views="{{ wxs.init }}" > <!-- 嵌套遍历views二维数组 --> <block wx:for="{{ views }}" wx:key="item" wx:for-index="i" > <selectable class="item view-{{ i }}" wx:for="{{ item }}" wx:key="item" value="{{ item }}" /> </block> </view> [代码] 创建item的x,y边距变量 [代码]--span[代码] [代码].waterfall { --span: 5px; width: 100%; position: relative; .item { width: calc(50% - var(--span)); position: absolute; } } [代码] 创建 [代码]index.wxs[代码],核心业务代码都写在这里 [代码]// 当views被setData的时候会被触发 module.export = { init: function(newValue, oldValue, ownerInstance, instance) { console.log(newValue, oldValue, ownerInstance, instance) } } [代码] 业务逻辑 步骤一:获取所有节点 [代码]function init(length, oldValue, ownerInstance, instance) { // 加个判断,避免views长度为0时,或者长度为发生变化时也会执行业务代码 // 只有当views被push新的内容才会执行下面的业务 if (!length || length === oldValue) return // index 其实就是views的长度减一,就等于当前的数组下标 var index = length - 1 var views = ownerInstance.selectAllComponents('.view-' + index) console.log(JSON.stringify(views)) } [代码] [图片] [图片] 步骤二:遍历views获取节点的高度 [代码]views.forEach(function(v, k){ var viewStyle = v.getComputedStyle(['width', 'height']) // 获取高度 var height = viewStyle.height console.log(viewStyle) // [WXS Runtime info] {"width":"182.5px", "height":"242px"} }) [代码] 步骤三:计算view的位置信息 [代码]var LH = 0 var RH = 0 views.forEach(function (v, k) { var viewStyle = v.getComputedStyle(['width', 'height']) // 格式化高度,将px去掉 var height = fixUnit(viewStyle.height) var style = {} if (LH <= RH) { style = { left: 0, top: LH + 'px' } LH += height } else if (RH < LH) { style = { right: 0, top: RH + 'px' } RH += height } // 设置view的样式 v.setStyle(style) }) [代码] 此时,页面的节点会根据position自动排列好 [图片] 步骤四:储存LH,RH到局部变量 [代码]function init(length, oldValue, ownerInstance, instance) { if (!length || length === oldValue) return // 获取局部变量 var state = ownerInstance.getState() // 获取当前节点的dataset var dataset = instance.getDataset() var index = length - 1 state.option = dataset.option state.page = length // 创建并生成记录左侧、右侧高度 // 用二维数组来记录 if (!state.heights) { state.heights = [[0, 0]] } // 记录初次渲染时间戳 if (!state.timeouts) { state.timeouts = [] } // 获取时间戳,并且加上3000毫秒,用于后面计算图片loaded完是否超时 state.timeouts[index] = getDate().getTime() + 3000 refreshViews(index, ownerInstance, state) } function refreshViews(index, ownerInstance, state) { var views = ownerInstance.selectAllComponents('.view-' + index) var span = state.option.span var LH = state.heights[index][0] // 左侧 var RH = state.heights[index][1] // 右侧 views.forEach(function (v, k) { var viewStyle = v.getComputedStyle(['width', 'height']) var height = fixUnit(viewStyle.height) var style = {} if (LH <= RH) { style = { left: 0, top: LH + 'px' } LH += height + span[0] } else if (RH < LH) { style = { right: 0, top: RH + 'px' } RH += height + span[0] } v.setStyle(style) // 保存LH, RH的值到state.heights // 当前的LH,RH其实就是下屏开始的坐标 state.heights[index + 1] = [LH, RH] console.log('渲染', index, k) }) } [代码] 步骤五:图片加载完重新计算位置 [代码]// waterfall/index.js Component({ properties: { views: Array, span: { type: Array, value: [10, 10], }, }, methods: { onLoaded({ detail: { width, height, pIndex, index } }) { this.setData({ [`views[${pIndex}][${index}].loaded`]: { width, height }, }) }, }, }) [代码] [代码]function loaded(value, oldValue, ownerInstance, instance) { if (!value.item.loaded || !oldValue) return // 获取局部变量 var state = ownerInstance.getState() // 判断加载是否超时,如果超时则不触发计算渲染事件 // 让该节点保持当前的位置及高度 var timeout = state.timeouts[value.pIndex] if (timeout < getDate().getTime()) { console.log('加载超时') return } var view = instance.selectComponent('.loaded-view') var viewWidth = view.getComputedStyle(['width']).width // 设置虚拟节点card组件里的loaded-view高度 view.setStyle({ height: computedHeight( viewWidth, value.item.loaded.width, value.item.loaded.height ) + 'px', }) // 加个函数防抖,因为图片加载快的情况下,会并发触发事件 // 尽可能的少触发计算,渲染事件,保证性能 ownerInstance.clearTimeout(timer) timer = ownerInstance.setTimeout(function () { // 渲染当前图片加载完后面的所有views // for循环处理当前图片所在的views,以及后面所有的views // 因为有些图片过大,可能会加载5s左右,但是用户如果上拉又加载了 // 一屏内容并且也通过计算渲染了,这时候上一屏又触发了计算渲染 // 那么可能位置信息就会发生变化,导致被遮挡,或者有空白,这时候只能 // 计算触发事件的图片以及后面的图片,保证位置信息是正确的 for (var i = value.pIndex; i < state.page; i++) { console.log('需要渲染', i) refreshViews(i, ownerInstance, state) } }, 300) } [代码] [图片] 优化 瀑布流最好后台会返回图片的尺寸信息,然后初次渲染的时候就计算好节点的长宽比例,这样就不用监听图片loaded事件了,瀑布流组件代码也不会频繁触发计算渲染,性能也好,方法也简单。 [代码]<image src="xxxx" style="{{ wxs.computed({width, height}) }}" /> [代码] [代码]// wxs function computed(option) { // 节点宽度自己去计算 var viewWidth = 375 / 2 var width = option.width var height = option.height return (viewWidth / width) * height + 'px } [代码] 完整代码 打开代码片段https://developers.weixin.qq.com/s/SO5q6UmF7doL,可直接运行。 https://github.com/liziwork/li-ui github 如果打不开,请切换到码云,gitee.com,代码同步更新的,觉得有用动动您的小手点个Star。 扫码查看更多组件 [图片]
2021-03-19 - 使用可视化组件,movable-area的子view点击事件不响应?
组件:movable-area和movable-view 基础库:2.8.3 对可移动控件进行组件化,使用<slot></slot> 插入其他视图,但子view的点击事件不响应,请问如何解决? [代码]<[代码][代码]movable-area[代码] [代码]class[代码][代码]=[代码][代码]"custom-class"[代码] [代码]style[代码][代码]=[代码][代码]"pointer-events: none;height: {{moveViewHeight}};width: 100%;left:0px;top:0px;"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]""[代码] [代码]style[代码][代码]=[代码][代码]"height:100%;overflow-y:scroll;"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]movable-view[代码] [代码]wx:if[代码][代码]=[代码][代码]"{{show}}"[代码] [代码]direction[代码][代码]=[代码][代码]"all"[代码] [代码]x[代码][代码]=[代码][代码]"{{moveViewX}}"[代码] [代码]y[代码][代码]=[代码][代码]"{{moveViewY}}"[代码] [代码]animation[代码][代码]=[代码][代码]"{{false}}"[代码] [代码]style[代码][代码]=[代码][代码]"pointer-events: auto; width: 40px;height:56px;z-index: 999;"[代码] [代码]bindtap[代码][代码]=[代码][代码]"onHome"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"img-view"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]image[代码] [代码]src[代码][代码]=[代码][代码]'/images/home.png'[代码] [代码]class[代码][代码]=[代码][代码]"home-img"[代码][代码]></[代码][代码]image[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"home-txt"[代码][代码]>返回首页</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]movable-view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]slot[代码][代码]></[代码][代码]slot[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]movable-area[代码][代码]>[代码][代码]<[代码][代码]movable-custom-view[代码] [代码]show[代码][代码]=[代码][代码]"{{true}}"[代码] [代码]moveViewHeight[代码][代码]=[代码][代码]"100%"[代码] [代码]moveViewX[代码][代码]=[代码][代码]"{{moveViewLeft}}"[代码] [代码]moveViewY[代码][代码]=[代码][代码]"{{moveViewTop}}"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"content-view"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]image[代码] [代码]src[代码][代码]=[代码][代码]"{{imgSrc}}"[代码] [代码]mode[代码][代码]=[代码][代码]"widthFix"[代码] [代码]bindtap[代码][代码]=[代码][代码]"onPreviewImage"[代码] [代码]data-value[代码][代码]=[代码][代码]"{{imgSrc}}"[代码][代码]></[代码][代码]image[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]movable-custom-view[代码][代码]>[代码] 代码片段:https://developers.weixin.qq.com/s/JdkCxHml7mbR
2019-09-27