- 如何查看并复制小程序的页面路径?
1.需求: 在发布微信公众号文章时,我们可以添加微信小程序的入口,默认是跳转到小程序的首页,但是首页远远不是我们想要的,我们想要复制任何一个页面的页面路径,让读者可以跳转任意页面,所以我们就需要复制小程序的页面路径,微信官方也提供了复制页面路径的方法,接下来请按照以下步骤操作,即可完成。 2.步骤: 公众号添加微信小程序 登陆微信公众号平台,打开编写图文消息的页面,点击小程序,弹出选择小程序弹窗,这里需要我们根据小程序名称/AppID/帐号原始ID去搜索。 [图片] 如何获取小程序AppId 找到你想要跳转的小程序,点击右上角三个点,然后弹出小程序信息的弹窗,进入小程序的页面,点击更多资料,即可以看到小程序的AppID和原始ID。然后复制AppID或原始ID到步骤一去搜索。下面我用微信开放社区小程序举个例子,告诉大家如何找到微信小程序的AppID。 [图片] 打开小程序的复制路径的权限,在第一步中我们输入小程序AppID,搜索出微信开放社区小程序,然后点击下一步,进入填写详细信息弹窗中,默认小程序路径为小程序首页路径,这里我们选择【获取更多页面路径】,然后右侧弹出窗口,将你的微信号输入进去即可开启复制页面路径的入口。[图片] 去小程序中获取制定页面路径:打开微信开放社区小程序,进入到你想要复制页面路径的页面,点击右上角三个点,弹出弹窗,在第二排又一个按钮【复制页面路径】,点击即可复制。[图片] 以此类推,我们可以复制任何其他小程序的链接,但是如果复制出的链接中包含一些参数,导致跳转出了问题,这就要弄清楚参数的含义了。注意⚠️:复制出来的页面路径在小程序里使用的时候记得删除 .html 才能正常访问
2021-02-27 - 微信小程序前端开发踩坑——引入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> [代码] 效果如下: [图片] 后记 不得不说,前端开发的坑实在是太多了,上面记录的过程我摸索了一个多小时。看来平时一定要多注意总结才行,不然真的非常消耗时间!!! 创作不易,觉得有用麻烦点个赞,谢谢~~~
2022-01-11 - 编程基础:学习编程并完成 5 个小项目
小节数:26节|技术知识点:变量与数组、响应函数、WXML、WXSS
01-06 - 小程序开发新能力解读
这个月小程序释放了什么新能力?又有哪些新规则?收藏课程,及时了解小程序开发动态,听官方为你解读新能力。
2023-01-17 - 他人无法通过微信号搜索添加自己
将微信ID发给对方,显示搜索不到账号,在隐私界面确认过可以通过微信号搜索到[图片]
2021-04-09 - 域名新注册新备案,但目前被限制微信访问,怎么办? 万分感谢
新备案4个域名:guanxiaotongl.top, luhanl.top, baiyul.top,tansongyul.top现在微信上都给出的提示:非微信官方网页,请确认是否继续访问。遇到的情况,域名在微信端不能访问。目前的实际情况如下:1、目前域名已在微信端申请恢复,但是被驳回并没有违反规则,域名是本月刚注册的,2、域名注册时间为:2021年3月29日,已经通过了工信部的企业备案,备案通过时间是:2021年4月7日;3、域名在注册前的相关信息与本机构无关。请核实和恢复网站在微信端的访问。[图片][图片][图片]
2021-04-09 - 程序不能被搜索提示违规,但是又没有发现有任何违规记录,或者站内消息,请问该怎么申诉?
APPID:wx0fedd71c72cb7f3b 问题:小程序不能被搜索,提示违规,但是又没有发现有任何违规记录,或者站内消息,请问该怎么申诉?如图 [图片]
2021-04-08 - 小程序流量主获取广告收益接口,一般都是每天的几点更新上一天的收益数据?
小程序流量主获取广告收益接口 一般都是每天的几点更新上一天的收益数据
2021-04-08 - 微信扫一串字符跳转网页是怎么实现的?
如题,有一串字符生成二维码后,用微信扫一扫能跳转到一个页面,这个功能怎么实现?样例如下: [图片]
2021-04-07 - 用户怎么设置小程序订阅消息长期有效?
我是小程序开发者 背景: 我们有2B和2C 二种小程序,2B对应的是车主端小程序,2C对应的是用户端小程序 需求: 现在想用户在用户端租车后,将租车信息推送给车主端小程序 问题: 车主端只能有到一次订阅消息,这样不太合理,有没有版本能让车主端的订阅消息长期有效,或者是让车主端人员怎么设置
2021-03-19 - 小程序发布之后,通过微信搜一搜输入全称搜不到,且已设置可被搜索,这是为什么?名字:外卖券大管家
[图片] APPID:wx515eb13bfb60d923 麻烦帮忙解决一下,谢谢~
2021-03-14 - 教你怎么监听小程序的返回键
更新:2020年7月28日08:51:11 基础库2.12.0起,可以调用wx.enableAlertBeforeUnload监听原生右上角返回、物理返回以及wx.navigateBack时弹框提示 AIP详情请看: https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.enableAlertBeforeUnload.html //======================================== 怎么监听小程序的返回键? 应该有很多人想要监听用户的这个动作吧,但是很遗憾,小程序不会给你这个API的,那是不是就没辙了? 幸好我们还可以自定义导航栏,这样一来我们就可以监听用户的这一动作了。 什么?这你已经知道啦? 那好咱们就不说自定义导航栏的返回监听了,说一下物理返回和左滑?右滑?(不管了,反正是滑)返回上一页怎么监听。 监听物理返回 首先说一下这个监听方法的缺点,虽说是监听,但是还是无法真正意义上的监听并拦截来阻止页面跳转,页面还是会返回上一页,而后重新载入刚刚的页面,如果这不是你想要的,那可以不用往下看了 其次说一下用到什么东西: wx.onAppRoute、wx.showModal 最后是一些主要代码: 重写wx.showModal,主要是加个confirmStay参数和使wx.showModal Promise化 [代码]const { showModal } = wx; Object.defineProperty(wx, 'showModal', { configurable: false, // 是否可以配置 enumerable: false, // 是否可迭代 writable: false, // 是否可重写 value(...param) { return new Promise(function (rs, rj) { let { success, fail, complete, confirmStay } = param[0] param[0].success = (res) => { res.navBack = (res.confirm && !confirmStay) || (res.cancel && confirmStay) wx.setStorageSync('showBackModal', !res.navBack) success && success(res) rs(res) } param[0].fail = (res) => { fail && fail(res) rj(res) } param[0].complete = (res) => { complete && complete(res) (res.confirm || res.cancel) ? rs(res) : rj(res) } return showModal.apply(this, param); // 原样移交函数参数和this }.bind(this)) } }); [代码] 使用wx.onAppRoute实现返回原来的页面 [代码]wx.onAppRoute(function (res) { var a = getApp(), ps = getCurrentPages(), t = ps[ps.length - 1], b = a && a.globalData && a.globalData.pageBeforeBacks || {}, c = a && a.globalData && a.globalData.lastPage || {} if (res.openType == 'navigateBack') { var showBackModal = wx.getStorageSync('showBackModal') if (c.route && showBackModal && typeof b[c.route] == 'function') { wx.navigateTo({ url: '/' + c.route + '?useCache=1', }) b[c.route]().then(res => { if (res.navBack){ a.globalData.pageBeforeBacks = {} wx.navigateBack({ delta: 1 }) } }) } } else if (res.openType == 'navigateTo' || res.openType == 'redirectTo') { if (!a.hasOwnProperty('globalData')) a.globalData = {} if (!a.globalData.hasOwnProperty('lastPage')) a.globalData.lastPage = {} if (!a.globalData.hasOwnProperty('pageBeforeBacks')) a.globalData.pageBeforeBacks = {} if (ps.length >= 2 && t.onBeforeBack && typeof t.onBeforeBack == 'function') { let { onUnload } = t wx.setStorageSync('showBackModal', !0) t.onUnload = function () { a.globalData.lastPage = { route: t.route, data: t.data } onUnload() } } t.onBeforeBack && typeof t.onBeforeBack == 'function' && (a.globalData.pageBeforeBacks[t.route] = t.onBeforeBack) } }) [代码] 改造Page [代码]const myPage = Page Page = function(e){ let { onLoad, onShow, onUnload } = e e.onLoad = (() => { return function (res) { this.app = getApp() this.app.globalData = this.app.globalData || {} let reinit = () => { if (this.app.globalData.lastPage && this.app.globalData.lastPage.route == this.route) { this.app.globalData.lastPage.data && this.setData(this.app.globalData.lastPage.data) Object.assign(this, this.app.globalData.lastPage.syncProps || {}) } } this.useCache = res.useCache res.useCache ? reinit() : (onLoad && onLoad.call(this, res)) } })() e.onShow = (() => { return function (res) { !this.useCache && onShow && onShow.call(this, res) } })() e.onUnload = (() => { return function (res) { this.app.globalData = Object.assign(this.app.globalData || {}, { lastPage: this }) onUnload && onUnload.call(this, res) } })() return myPage.call(this, e) } [代码] 在需要监听的页面加个onBeforeBack方法,方法返回Promise化的wx.showModal [代码]onBeforeBack: function () { return wx.showModal({ title: '提示', content: '信息尚未保存,确定要返回吗?', confirmStay: !1 //结合content意思,点击确定按钮,是否留在原来页面,confirmStay默认false }) } [代码] 运行测试,Oj8K 是不是很简单,马上去试试水吧,效果图就不放了,静态图也看不出效果,动态图懒得弄,想看效果的自己运行代码片段吧 代码片段 https://developers.weixin.qq.com/s/hc2tyrmw79hg
2020-07-28 - 微信开发者工具下载的 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 - 日销量翻9倍,订单转化率提高7.5倍!来看看“别人家的小程序”
线下门店利用小程序日销翻9倍的秘诀~ 作者丨Suvi [图片] 如今,人人都在讲“数字化”。到底,什么才是数字化运营呢?开通了公众号,开发了小程序,就一定会有流量吗?投放了广告,发放了优惠券,就一定会有转化吗?这些数字化的工具,究竟该怎么用,才能效益最大化? 今天,给大家带来服装品牌“三福时尚”的数字化转型案例。它们通过对“触点”的优化,聚焦用户获取、转化与留存,让三福小程序UV环比提升了50%以上,订单转化率和GMV双双实现三位数增长,小程序日销量提升了整整9倍。 01 用户获取:全触点布局是关键 何为触点?简单来说,就是触达用户的渠道。小程序在微信生态的触点超过60个。而据三福时尚数字营销部总经理江平介绍,三福小程序目前部署了多达三十几个触点。触点布局的优化,是三福小程序短时间内销售额增势迅猛的关键。 譬如,针对公众号菜单栏这一入口,三福根据其活动的进度和节奏,定制了不同阶段的菜单。在铺设活动的同时,及时更新菜单栏,这个小小的举动大大提高了三福对新老用户的唤醒能力。 [图片] 很多人做了小程序,却等不到流量。那么,不妨想一想:小程序都有哪些入口?这些入口你都好好利用了吗?这些入口如何触达用户?面对不同的人群和不同的场景,如何进行区别化设计? [图片] 在流量越来越贵,越来越难获取的今天,我们愈发要对流量来源进行甄别、筛选和优化。三福小程序经过一段时间的摸索,发现对他们来说,最有效的触点是公众号菜单、公众号图文、朋友圈广告。因此,针对这些核心触点,他们进行了精准化、个性化的运营。 例如,在投放朋友圈广告时,三福会发放出小程序的线上专享券,引流用户进入小程序,实现短链转化。此外,三福还集中了线下导购的力量,发挥成千上万私人导购的“辐射能力”,在导购朋友圈进行推文分发,针对用户一对一回复,促进购买。经过优化设计之后,朋友圈广告投放的优惠券核销率高达33%。 [图片] 02 用户转化:玩转营销组合拳 对于电商来说,刺激新客户成交第一单,最有效的策略无疑是活动优惠。优惠力度越大,时间限定越短,越能提升新客的转化率。但活动怎么设置,优惠怎么发放,也是需要运营技巧的。 三福的策略是,用丰富多样的营销玩法,多维度刺激新老用户。在活动期间,三福小程序设置了免单日、返券日、拔草日、超级品类日等系列活动,配合积分、抽奖、返钱、免单等多种玩法,以串联起整个营销周期。 [图片] 三福的拟人化品牌形象——福宝 当然,在这个过程中,也少不了对触点的优化。譬如,三福充分利用公众号后台自带的功能,利用分组,给不同的用户推送不同的图文内容,发送不同的优惠券。这样的精准推送,大大提高了三福公众号图文的点击率与转化率。活动中,公众号点击率环比提升4.3%,三福小程序UV环比递增50%,订单转化率提高了7.5倍。 对于三福来说,光有线上的流量还不够,实现线上带动线下,才是最终目的。针对线上的各类活动,三福在线下也打造了相应的活动氛围:通过商城会场、线下POP等全渠道,大肆渲染活动狂欢气氛,以充分刺激用户的消费心理。 [图片] 三福数字营销部总经理江平 而在线下门店的管理环节中,不仅仅要考虑到顾客,更要考虑到各个门店的导购与店长。因此,三福举行了区域销售占比PK赛,每日播报各区最新销售业绩,通过虚拟广告金+现金激励,以充分调动门店店员的主观能动性。活动期间,排名前两名的区域小程序销售日环比增长分别为4123%和2990%,小程序销售占比递增575%和394%。 03 用户留存:给用户一个“回头”的理由 对于一款电商小程序来说,用户留存可以分为两部分:首先,吸引用户持续回到小程序,保持一定的活跃度;其次,刺激用户完成复购,成为“回头客”。而在用户留存方面,三福也颇有心得。 自去年年底,三福实现了“六通”——会员通,渠道通,订单通,促销通,服务通,商品通。这些环节的数据打通,为三福的数字化运营打下了基础。 许多商家在做小程序的时候,运用的还是“平台思维”,对基础的数据建设不够重视,总认为用上了“数字化工具”就等于“数字化转型”了。但是,只有真正去运用数据、分析数据,对各个环节的用户行为数据、交易数据进行深度解剖,才能得出最适合自己的运营策略。 三福从用户的“消费动线”出发,对用户从萌生购物想法,到实施购物行为,再到持续活跃、复购,乃至主动传播的全链路进行了精心设计和扩展,形成了一个完整的闭环体验。 [图片] 为了让用户持续回到小程序中,三福在小程序首页加入了许多实用功能板块,如:超值拼团、上新播报、热卖排行。当然,吸引留存的重中之重乃是优惠券中心与福币商城。三福小程序的优惠券中心设计简洁,还能将券按照使用渠道进行区分。而福币商城中的积分,有着兑券、抽奖等多种玩法。这两个板块都放在了首页相当显眼的地方,能够有效地刺激用户产生复购冲动。 在腾讯智慧倍增行动的第三期颁奖典礼上,三福凭借其公众号+小程序+朋友圈广告+门店导购大赛四大板块的操盘运营,拿下了当期的第一名。 [图片] 很多时候,他人的”增长秘诀“,其实并不是什么新颖的营销策略,也不是什么刁钻的活动规则。相反,拼团、秒杀、店庆……这些设计和玩法,并不是秘密。如何让这些活动的效果最大化,才是关键。 但难点就在于,思维层面的东西,无法一成不变地照搬。各种触达用户的渠道,已经不再有原始的“红利流量”,开了个公众号什么文章都不发就“蹭蹭”涨粉的时代已经过去了。但是这些渠道还在,触点还在,用户也还在。 不妨,从现在起,开始布局属于你的“全链路闭环”。针对经营全流程,从拉新到激活,从转化到留存,从回头复购到主动传播,对每一个环节进行梳理和优化,加强高质量的渠道,砍掉无效、低效的环节,针对客户的兴趣点去引导转化,试着实现属于你的“翻倍增长”吧~。
2019-09-12 - 如何实现小程序的强制更新
大家都知道小程序提交审核发布以后是不会马上更新版本的,用户需要下次使用才会更新到新的版本,这就是冷更新。 那么如果要做到及时生效怎么办呢?这时候就要做处理了,将下面的代码添加到app.js,提交审核,发布就会生效了 [代码]onLaunch: [代码][代码]function[代码] [代码](options) {[代码] [代码] [代码][代码]this[代码][代码].autoUpdate()[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]autoUpdate: [代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码] [代码] [代码][代码]// 获取小程序更新机制兼容[代码] [代码] [代码][代码]if[代码] [代码](wx.canIUse([代码][代码]'getUpdateManager'[代码][代码])) {[代码] [代码] [代码][代码]const updateManager = wx.getUpdateManager()[代码] [代码] [代码][代码]//1. 检查小程序是否有新版本发布[代码] [代码] [代码][代码]updateManager.onCheckForUpdate([代码][代码]function[代码] [代码](res) {[代码] [代码] [代码][代码]// 请求完新版本信息的回调[代码] [代码] [代码][代码]if[代码] [代码](res.hasUpdate) {[代码] [代码] [代码][代码]//检测到新版本,需要更新,给出提示[代码] [代码] [代码][代码]wx.showModal({[代码] [代码] [代码][代码]title: [代码][代码]'更新提示'[代码][代码],[代码] [代码] [代码][代码]content: [代码][代码]'检测到新版本,是否下载新版本并重启小程序?'[代码][代码],[代码] [代码] [代码][代码]success: [代码][代码]function[代码] [代码](res) {[代码] [代码] [代码][代码]if[代码] [代码](res.confirm) {[代码] [代码] [代码][代码]//2. 用户确定下载更新小程序,小程序下载及更新静默进行[代码] [代码] [代码][代码]self.downLoadAndUpdate(updateManager)[代码] [代码] [代码][代码]} [代码][代码]else[代码] [代码]if[代码] [代码](res.cancel) {[代码] [代码] [代码][代码]//用户点击取消按钮的处理,如果需要强制更新,则给出二次弹窗,如果不需要,则这里的代码都可以删掉了[代码] [代码] [代码][代码]wx.showModal({[代码] [代码] [代码][代码]title: [代码][代码]'温馨提示'[代码][代码],[代码] [代码] [代码][代码]content: [代码][代码]'本次版本更新涉及到新的功能添加,旧版本可能无法正常访问哦'[代码][代码],[代码] [代码] [代码][代码]showCancel: [代码][代码]false[代码][代码],[代码][代码]//隐藏取消按钮[代码] [代码] [代码][代码]confirmText: [代码][代码]"确定更新"[代码][代码],[代码][代码]//只保留确定更新按钮[代码] [代码] [代码][代码]success: [代码][代码]function[代码] [代码](res) {[代码] [代码] [代码][代码]if[代码] [代码](res.confirm) {[代码] [代码] [代码][代码]//下载新版本,并重新应用[代码] [代码] [代码][代码]self.downLoadAndUpdate(updateManager)[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码] [代码] [代码][代码]// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示[代码] [代码] [代码][代码]wx.showModal({[代码] [代码] [代码][代码]title: [代码][代码]'提示'[代码][代码],[代码] [代码] [代码][代码]content: [代码][代码]'当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 下载小程序新版本并重启应用[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]downLoadAndUpdate: [代码][代码]function[代码] [代码](updateManager) {[代码] [代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码] [代码] [代码][代码]wx.showLoading();[代码] [代码] [代码][代码]//静默下载更新小程序新版本[代码] [代码] [代码][代码]updateManager.onUpdateReady([代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]wx.hideLoading()[代码] [代码] [代码][代码]//新的版本已经下载好,调用 applyUpdate 应用新版本并重启[代码] [代码] [代码][代码]updateManager.applyUpdate()[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]updateManager.onUpdateFailed([代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]// 新的版本下载失败[代码] [代码] [代码][代码]wx.showModal({[代码] [代码] [代码][代码]title: [代码][代码]'已经有新版本了哟'[代码][代码],[代码] [代码] [代码][代码]content: [代码][代码]'新版本已经上线啦,请您删除当前小程序,重新搜索打开哟'[代码][代码],[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码]
2019-06-07 - 如何实现一个自定义导航栏
自定义导航栏在刚出的时候已经有很多实现方案了,但是还有大哥在问,那这里再贴下代码及原理: 首先在App.js的 onLaunch中获取当前手机机型头部状态栏的高度,单位为px,存在内存中,操作如下: [代码]onLaunch() { wx.getSystemInfo({ success: (res) => { this.globalData.statusBarHeight = res.statusBarHeight this.globalData.titleBarHeight = wx.getMenuButtonBoundingClientRect().bottom + wx.getMenuButtonBoundingClientRect().top - (res.statusBarHeight * 2) }, failure() { this.globalData.statusBarHeight = 0 this.globalData.titleBarHeight = 0 } }) } [代码] 然后需要在目录下新建个components文件夹,里面存放此次需要演示的文件 navigateTitle WXML 文件如下: [代码]<view class="navigate-container"> <view style="height:{{statusBarHeight}}px"></view> <view class="navigate-bar" style="height:{{titleBarHeight}}px"> <view class="navigate-icon"> <navigator class="navigator-back" open-type="navigateBack" wx:if="{{!isShowHome}}" /> <navigator class="navigator-home" open-type="switchTab" url="/pages/index/index" wx:else /> </view> <view class="navigate-title">{{title}}</view> <view class="navigate-icon"></view> </view> </view> <view class="navigate-line" style="height: {{statusBarHeight + titleBarHeight}}px; width: 100%;"></view> [代码] WXSS文件如下: [代码].navigate-container { position: fixed; top: 0; width: 100%; z-index: 9999; background: #FFF; } .navigate-bar { width: 100%; display: flex; justify-content: space-around; } .navigate-icon { width: 100rpx; height: 100rpx; display: flex; justify-content: space-around; } .navigate-title { width: 550rpx; text-align: center; line-height: 100rpx; font-size: 34rpx; color: #3c3c3c; font-weight: bold; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } /*箭头部分*/ .navigator-back { width: 36rpx; height: 36rpx; align-self: center; } .navigator-back:after { content: ''; display: block; width: 22rpx; height: 22rpx; border-right: 4rpx solid #000; border-top: 4rpx solid #000; transform: rotate(225deg); } .navigator-home { width: 56rpx; height: 56rpx; background: url(https://qiniu-image.qtshe.com/20190301home.png) no-repeat center center; background-size: 100% 100%; align-self: center; } [代码] JS如下: [代码]var app = getApp() Component({ data: { statusBarHeight: '', titleBarHeight: '', isShowHome: false }, properties: { //属性值可以在组件使用时指定 title: { type: String, value: '青团公益' } }, pageLifetimes: { // 组件所在页面的生命周期函数 show() { let pageContext = getCurrentPages() if (pageContext.length > 1) { this.setData({ isShowHome: false }) } else { this.setData({ isShowHome: true }) } } }, attached() { this.setData({ statusBarHeight: app.globalData.statusBarHeight, titleBarHeight: app.globalData.titleBarHeight }) }, methods: {} }) [代码] JSON如下: [代码]{ "component": true } [代码] 如何引用? 需要引用的页面JSON里配置: [代码]"navigationStyle": "custom", "usingComponents": { "navigate-title": "/pages/components/navigateTitle/index" } [代码] WXML [代码]<navigate-title title="青团社" /> [代码] 按上面步骤操作即可实现一个自定义的导航栏。 如何实现通栏的效果默认透明以及滚动更换title为白色背景,如下图所示: [图片] [图片] [图片] [图片] 最后代码片段如下: https://developers.weixin.qq.com/s/wi6Pglmv7s8P。 以下为收集到的社区老哥们的分享: @Yunior: 小程序顶部自定义导航组件实现原理及坑分享 @志军: 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能 @✨o0o有脾气的酸奶💤 [有点炫]自定义navigate+分包+自定义tabbar @安晓苏 分享一个自适应的自定义导航栏组件
2020-03-10 - 2019-03-12
- (4)获取用户信息
背景 我们发现大部分小程序都会使用 [代码]wx.getUserInfo[代码] 接口,来获取用户信息。原本设计这个接口时,我们希望开发者在真正需要用户信息的情况下才去调取这个接口,但很多开发者会直接调用这个接口,导致用户在使用小程序的时候产生困扰,归结起来有几点: 开发者在小程序首页直接调用 [代码]wx.getUserInfo[代码] 进行授权,弹框获取用户信息,会使得一部分用户点击“拒绝”按钮。 在开发者没有处理用户拒绝弹框的情况下,用户必须授权头像昵称等信息才能继续使用小程序,会导致某些用户放弃使用该小程序。 用户没有很好的方式重新授权,尽管我们增加了[代码]设置[代码]页面,可以让用户选择重新授权,但很多用户并不知道可以这么操作。 此外,我们发现开发者默认将 [代码]wx.login[代码] 和 [代码]wx.getUserInfo[代码] 绑定使用,这个是由于我们一开始的设计缺陷和实例代码导致的([代码]wx.getUserInfo[代码] 必须通过 [代码]wx.login[代码] 在后台生成 [代码]session_key[代码]后才能调用)。同时,我们收到开发者的反馈,希望用户进入小程序首页便能获取到用户的 [代码]unionId[代码],以便识别到用户是否以前关注了同主体公众号或使用过同主体的App 。 为了解决以上问题,针对获取用户信息我们更新了三个能力: 1.使用组件来获取用户信息 2.若用户满足一定条件,则可以用[代码]wx.login[代码] 获取到的[代码]code[代码]直接换到[代码]unionId[代码] 3.[代码]wx.getUserInfo[代码] 不需要依赖 [代码]wx.login[代码] 就能调用得到数据 获取用户信息组件介绍 [代码][代码] 组件变化: [代码]open-type [代码]属性增加 [代码]getUserInfo[代码] :用户点击时候会触发 [代码]bindgetuserinfo[代码] 事件。 新增事件 [代码]bindgetuserinfo[代码] :当 [代码]open-type[代码]为 [代码]getUserInfo[代码] 时,用户点击会触发。可以从事件返回参数的 [代码]detail[代码] 字段中获取到和 [代码]wx.getUserInfo[代码] 返回参数相同的数据。 示例: [代码]<button open-type="getUserInfo" bindgetuserinfo="userInfoHandler"> Click me button>[代码]和 [代码]wx.getUserInfo[代码] 不同之处在于: 1.API [代码]wx.getUserInfo[代码] 只会弹一次框,用户拒绝授权之后,再次调用将不会弹框; 2.组件 [代码][代码][代码][代码] 由于是用户主动触发,不受弹框次数限制,只要用户没有授权,都会再次弹框。 通过获取用户信息的组件,就可以解决用户再次授权的问题。 直接获取unionId开发者申请 [代码]userinfo[代码] 授权主要为了获取 [代码]unionid[代码],我们鼓励开发者在不骚扰用户的情况下合理获得[代码]unionid[代码],而仅在必要时才向用户弹窗申请使用昵称头像。为此,凡使用“获取用户信息组件”获取用户昵称头像的小程序,在满足以下全部条件时,将可以静默获得 [代码]unionid[代码]: 1.在微信开放平台下存在同主体的App、公众号、小程序。 2.用户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权。 这样可让其他同主体的App、公众号、小程序的开发者快速获得已有用户的数据。 不依赖登录的用户信息获取某些工具类的轻量小程序不需要登录行为,但是也想获取用户信息,那么就可以在 [代码]wx.getUserInfo[代码] 的时候加一个参数 [代码]withCredentials: false[代码] 直接获取到用户信息,可以少一次网络请求。 这样可以在不给用户弹窗授权的情况下直接展示用户的信息。 最佳实践 1.调用 [代码]wx.login[代码] 获取 [代码]code[代码],然后从微信后端换取到 [代码]session_key[代码],用于解密 [代码]getUserInfo[代码]返回的敏感数据。 2.使用 [代码]wx.getSetting[代码] 获取用户的授权情况 1) 如果用户已经授权,直接调用 API [代码]wx.getUserInfo[代码] 获取用户最新的信息; 2) 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。 3.获取到用户数据后可以进行展示或者发送给自己的后端。 One More Thing 除了获取用户方案介绍之外,再聊一聊很多初次接触微信小程序的开发者所不容易理解的一些概念: 1.关于OpenId和UnionId [代码]OpenId[代码] 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。 [代码]UnionId[代码] 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过[代码]UnionId[代码],实现多个小程序、公众号、甚至APP 之间的数据互通了。 同一个用户的这两个 ID 对于同一个小程序来说是永久不变的,就算用户删了小程序,下次用户进入小程序,开发者依旧可以通过后台的记录标识出来。 2.关于 getUserInfo 和 login 很多开发者会把 [代码]login[代码] 和 [代码]getUserInfo[代码] 捆绑调用当成登录使用,其实 [代码]login[代码] 已经可以完成登录,[代码]getUserInfo[代码] 只是获取额外的用户信息。 在 [代码]login[代码] 获取到 [代码]code[代码] 后,会发送到开发者后端,开发者后端通过接口去微信后端换取到 [代码]openid[代码] 和[代码]sessionKey[代码](现在会将 [代码]unionid[代码] 也一并返回)后,把自定义登录态 [代码]3rd_session[代码]返回给前端,就已经完成登录行为了。而 [代码]login[代码] 行为是静默,不必授权的,用户不会察觉。 [代码]getUserInfo[代码] 只是为了提供更优质的服务而存在,比如展示头像昵称,判断性别,开发者可通过 [代码]unionId[代码] 和其他公众号上已有的用户画像结合来提供历史数据。因此开发者不必在用户刚刚进入小程序的时候就强制要求授权。 可以在官方的文档中看到 [代码]login[代码] 的最佳实践: [图片] Q & A Q1: 为什么 login 的时候不直接返回 openid,而是要用这么复杂的方式来经过后台好几层处理之后才能拿到? A: 为了防止坏人在网络链路上做手脚,所以小程序端请求开发者服务器的的请求都需要二次验证才是可信的。因为我们采取了小程序端只给 [代码]code[代码] ,由服务器端拿着 [代码]code[代码] 和 [代码]AppSecrect[代码] 去微信服务器请求的方式,才会给到开发者对应的[代码]openId[代码] 和用于加解密的 [代码]session_key。[代码] Q2: 既然用户的[代码]openId[代码] 是永远不变的,那么开发者可以使用[代码]openId[代码] 作为用户的登录态么? A: 不行,这是非常危险的行为。因为 [代码]openId[代码] 是不变的,如果有坏人拿着别人的 [代码]openId[代码] 来进行请求,那么就会出现冒充的情况。所以我们建议开发者可以自己在后台生成一个拥有有效期的 [代码]第三方session[代码] 来做登录态,用户每隔一段时间都需要进行更新以保障数据的安全性。 Q3: 是不是用户每次打开小程序都需要重新[代码]login[代码]? A: 不必,可以将登录态存入[代码]storage[代码]中,用户再次登录就可以拿[代码]storage[代码] 里的登录态做正常的业务请求,只有当登录态过期了之后才需要重新[代码]login[代码] 。这样子做一则可以减少用户等待时间,二则可以减少网络带宽。 目前微信的[代码]session_key[代码] 有效期是三天,所以建议开发者设置的登录态有效期要小于这个值。
2018-08-17