- 一个通用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 - 微信小程序this.setData如何修改对象、数组中的值
在微信小程序的前端开发中,使用this.setData方法修改data中的值,其格式为 this.setData({ '参数名1': 值1, '参数名2': 值2 )} 需要注意的是,如果是简单变量,这里的参数名可以不加引号。 经过测试,可以使用3种方式对data中的对象、数组中的数据进行修改。 假设原数据为: data: { user_info:{ name: 'li', age: 10 }, cars:['nio', 'bmw', 'wolks'] }, 方式一: 使用['字符串'],例如 this.setData({ ['user_info.age']: 20, ['cars[0]']: 'tesla' }) 方式二: 构造变量,重新赋值,例如 var temp = this.data.user_info temp.age = 30 this.setData({ user_info: temp }) var temp = this.data.cars temp[0] = 'volvo' this.setData({ cars: temp }) 方式三: 直接使用字符串,此种方式之前不可以,现在可以了,估计小程序库升级了。 注意和第一种方法的对比,推荐还是使用第一种方法。 this.setData({ 'user_info.age': 40, 'cars[0]': 'ford' }) 完整代码: Page({ /** * 页面的初始数据 */ data: { user_info:{ name: 'li', age: 10 }, cars:['nio', 'bmw', 'wolks'] }, change_data: function(){ console.log('对象-修改前:', this.data.user_info) this.setData({ ['user_info.age']: 20 }) console.log('对象-修改后1:', this.data.user_info) var temp = this.data.user_info temp.age = 30 this.setData({ user_info: temp }) console.log('对象-修改后2:', this.data.user_info) this.setData({ 'user_info.age': 40 }) console.log('对象-修改后3:', this.data.user_info) console.log('数组-修改前:', this.data.cars) this.setData({ ['cars[0]']: 'tesla' }) console.log('数组-修改后1:', this.data.cars) var temp = this.data.cars temp[0] = 'volvo' this.setData({ cars: temp }) console.log('数组-修改后2:', this.data.cars) this.setData({ 'cars[0]': 'ford' }) console.log('数组-修改后3:', this.data.cars) } }) 效果: [图片]
2020-08-26 - 动手打造更强更好用的微信开发者工具-编辑器扩展篇
1. 写在前面 1.1 微信开发者工具现状 具备一些基本的通用IDE功能,但是第三方的支持扩展需要加强。 1.2 开发者工具自带的编辑器扩展功能 可能很多老铁没用过官方的微信开发者工具的编辑器扩展(我一般称为编辑器插件)。官方把这块功能也隐藏得很深,也没有相关文档介绍,但是预留了相关的入口。合理利用第三方编辑器插件,可以极大的提升开发效率。下面先来看看官方预留的编辑器插件入口: [图片] (图一) 2. 几个不错插件安装效果 2.1 标签高亮插件-vincaslt.highlight-matching-tag [图片] 功能:可以把当前行对应的标签开头和结尾高亮起来,让开发者一目了然 2.2 小程序开发助手插件-overtrue.miniapp-helper [图片] 功能:必须要说的这个是纯国产的插件,里面的代码片段功能很全,具体介绍:小程序开发助手 - Visual Studio Marketplace https://marketplace.visualstudio.com/items?itemName=overtrue.miniapp-helper 2.3 minapp插件-qiu8310.minapp-vscode [图片] 功能:这个是今天的明星插件,里面的跳转功能很强,可以在wxml里CMD+点击对应变量/方法和CSS样式名称直接跳转到对应的js/wxss文件对应的地方。具体的下面是官方介绍: 标签名与属性自动补全 根据组件已有的属性,自动筛选出对应支持的属性集合 属性值自动补全 点击模板文件中的函数或属性跳转到 js/ts 定义的地方(纯 wxml 或 pug 文件才支持,vue 文件不完全支持) 样式名自动补全(纯 wxml 或 pug 文件才支持,vue 文件不完全支持) 在 vue 模板文件中也能自动补全,同时支持 pug 语言 支持 link(纯 wxml 或 pug 文件才支持,vue 文件不支持) 自定义组件自动补全(纯 wxml 文件才支持,vue 或 pug 文件不支持) 模板文件中 js 变量高亮(纯 wxml 或 pug 文件才支持,vue 文件不支持) 内置 snippets 支持 emmet 写法 wxml 格式化 3. DIY添加适合自己的插件 3.1 添加插件功能简介 仔细研究过微信开发者工具的人可能知道或者了解,其实微信开发者工具编辑器跟微软的开源编辑器vsCode「颇有渊源」。再深入研究发现,vsCode的插件完全可以无缝移植到微信开发者工具编辑器里来,所以今天的内容就是移植vsCode的插件到微信开发者工具。咱们先看看微信开发者工具自带的「管理编辑器扩展」功能(图1标注为2的地方) [图片](图二) 3.2 插件添加具体步骤 3.2.1 安装插件,获取插件文件 安装vsCode并安装你需要移植的插件,必须要说的是vsCode的插件非常多,好的插件也很多。相关安装,搜索插件教程建议大家百度相关教程。或者直接下载vsCode亲自体验,插件安装过程还是非常简单的。 3.2.2 复制插件文件夹 找到vsCode相关插件的安装文件夹: 操作系统 安装路径 windows %USERPROFILE%.vscode\extensions macOS ~/.vscode/extensions Linux ~/.vscode/extensions 复制对应插件文件夹到微信开发者工具的「打开编辑器扩展目录」(图一标注为1的地方) 3.2.3 添加插件配置文件 新版开发者工具直接进入图形设置,扩展设置里勾选对应插件即可。如下图: [图片] 旧版操作方法:进入微信开发者工具的「管理编辑器扩展」功能页面,在尾端加入对应添加的插件名称。以以上3个介绍的插件为例,在原来的尾端加入: “vincaslt.highlight-matching-tag”, “overtrue.miniapp-helper”, “qiu8310.minapp-vscode” 3.2.4 见证奇迹 重启微信开发者工具,见证插件带来的编码便利吧! 4 需要注意的 vsCode的插件很多,小程序相关的也越来越多了,但是插件质量参差不齐,所以安装时建议选择「标星」star比较多的插件。
2020-05-02 - 小程序前端调用云函数上传图片到云存储不失败,请问是什么原因 ?
小程序前端调用云函数上传图片到云存储不失败,请问是什么原因 ?[图片][图片]
2020-05-03 - 开源的抽奖助手小程序
发现开源小程序之美三-抽奖助手小程序 发现开源小程序之美一,个人博客小程序 https://developers.weixin.qq.com/community/develop/article/doc/000a40e13ec550274e2a9addd56413发现开源小程序之美二,微慕WordPress小程序 https://developers.weixin.qq.com/community/develop/article/doc/000c44945dc728ab9c2aff2a55b013发现开源小程序之美三,抽奖助手小程序 https://developers.weixin.qq.com/community/develop/article/doc/0002846854056847b66a2d13451013发现开源小程序之美四,在线答题小程序 https://developers.weixin.qq.com/community/develop/article/doc/00040af07005609a223acee0151413发现开源小程序之美五,营销组件库 https://developers.weixin.qq.com/community/develop/article/doc/000c4235c98740a1dc2a1a6045b013发现开源小程序之美六,酱茄小程序 https://developers.weixin.qq.com/community/develop/article/doc/00040ede6d0388082a3aeb49b57813发现开源小程序之美七,二手书商场 https://developers.weixin.qq.com/community/develop/article/doc/0006ceb61a87182a4b3a1b32a5bc13发现开源小程序之美八,我要戴口罩https://developers.weixin.qq.com/community/develop/article/doc/0006a047b0cee0d5713ad731f5b813发现开源小程序之美九,失物招领小程序 https://developers.weixin.qq.com/community/develop/article/doc/000ca6a3b28ce8857b5a8bb3351c13发现开源小程序之美十,旅游攻略方面的微信小程序 https://developers.weixin.qq.com/community/develop/article/doc/000cc694e9c790ce755aee41556013 这个小程序是身边小伙伴开发的,基于云开发的一个抽奖助手小程序,我今天clone下代码,花了不到10分钟就运行成功了, 值得推荐给大家 先上截图大家参观下 [图片] 1 [图片] 2 [图片] 2 码云地址 https://gitee.com/xiaofeiyang3369/wechatlottery 我在调试过程中做了略微的改动 部署步骤建议按照下面三步走 第一步:创建集合,并将集合权限设置为:所有人可读,仅创建者可读写 第二步:将data里面的lottery.json文件导入到lottery集合 第三步:部署云函数 如没有意外就可以正常运行了,部署过程中遇到任何问题,请评论席留言。 更新记录 2020-07-20 重写了核心逻辑 ①开奖逻辑 ②抽奖逻辑 开奖逻辑目前是按照时间维度,到了时间不管人数有没有凑够都会进行开奖,开奖五分钟后,进行抽奖,确定中奖名额。 具体规则: (1)每个整点的1分去检测,根据当前时间检测是否有需要开奖的 (2)每个整点的5分去检测,是否有开奖未抽奖的,如果有,确定中奖名额 ~~
2021-01-25 - 开源小程序-头像加口罩
来吧,请不要吝啬你的star [图片] 1、我不是作者,但是作者同意我在社区发帖,想联系作者的私聊我吧 2、开发环境:基于uniapp使用VUE快速实现 3、这个小程序从起名字到运营,基本走的是我的运营思路,2月份的时候我说过这个小程序,目前衍生比较完整,每月差不多4位数收益,累计用户10W+ [图片] 4、有图片安全检测,可放心使用 5、部分功能预览 [图片] [图片] https://github.com/infinityu/mina-wear-mask 不要吝啬你的STAR
2020-07-05 - Half Screen Dialog半屏弹窗内部可以自定义吗
本文主要解决了以下两个问题 1、weui如何引入 2、半屏弹窗如何使用 3、半屏弹窗是否支持内部自定义 如果对上面三个问题感兴趣可以继续阅读 Half Screen Dialog半屏弹窗,辅助完成当前页面任务时;提醒用户并引导用户的下一步操作;用户主动发起的任务时。 https://developers.weixin.qq.com/miniprogram/dev/extended/weui/half-screen-dialog.html 1 [图片] 2 [图片] 3[图片] [图片] 3 如何引入Weui,有两种方式,本文分别说一下, 1、传统方式 首先node npm xx,然后把里面的dist目录移出来。 官方文档 https://developers.weixin.qq.com/miniprogram/dev/extended/weui/quickstart.html 2、扩展方式,当然扩展方式有优势,但是有时候不是那么容易调试出来。 参考社区帖子 https://developers.weixin.qq.com/community/develop/article/doc/000224381788e8e5bb89426f55e413 更新最新的 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>验证有效。 4 [图片] 5 6 我把评论区大佬的代码片段提上来了,非常感谢。 https://developers.weixin.qq.com/s/yWl6plmy7ghb 相关代码我都放评论区域了,需要的话,可用自取。
2020-05-15 - 微信同声传译插件第一次调用manager.onRecognize没有返回值
微信同声传译插件第一次调用manager.onRecognize没有返回值,以后再调用就有了,请问是怎么回事,我应该怎么解决
2018-06-11 - 生成分享卡片 | Canvas 初使用
这是微信小程序开发频率最高的功能,绝大部分的小程序都会做这个,因为小程序页面不可以分享到朋友圈。所以,为了达到分享的目的,大家几乎都是通过生成分享卡片的方式实现。 其本质就是将页面元素,生成一张图片,并且保存到相册中。这块功能的实现需要借助 canvas 画布的 API。这也是「计算日子」这个小工具,一个关键的功能点,最终生成卡片效果如下图所示: [图片] 下面将以它的实现代码为例进行介绍。首先在 pages 目录下,创建 canvas 目录,新建 Page,命名 canvas,目录如下图所示: [图片] 编写 canvas.wxml 文件,结构很简单,一个画布元素,一个按钮,代码如下: [代码]<canvas style="width: 100%; height: 200px;" canvas-id="firstCanvas"></canvas> <view class="bottom_btn"> <button type="warn" bindtap="saveToImg" loading='{{ isLoading }}' disabled='{{ isDisable }}'>保存卡片</button> </view> [代码] 接下来,就是最主要的部分了,使用 js 在画布上画画了,在写代码之前,先来分析一下画布上显示的内容,我们从示例图中,可以发现,有以下几个元素: 矩形白色卡片,带阴影 4 行文本:标题(新中国成立)、数字(25540)、描述(已过天数)以及目标日 一张二维码图片(已经放在 canvas 目录下了) 我们分别将这些元素的绘制方法,放在单独的函数中,打开 canvas.js 文件,在 Page() 函数之前,定义如下几个函数: [代码]const app = getApp(); // 获取屏幕宽度 const windowWidth = wx.getSystemInfoSync().windowWidth; // 使用 wx.createContext 获取绘图上下文 context let context = wx.createCanvasContext('firstCanvas'); function setTitle(title) { // 绘制标题 context.setFontSize(20); context.setFillStyle('#333'); context.fillText(title, 40, 60); } function setLabel(numberLabel) { // 绘制 desc context.setFontSize(10); context.setFillStyle('#999') context.fillText(numberLabel, 43, 95); } function setNumber(number, numberColor) { // 绘制数字 context.setFontSize(40); context.setFillStyle(numberColor) context.fillText(number, 40, 135); } function setDate(date) { // 绘制目标日 context.setFontSize(10); context.setFillStyle('#999') context.fillText('目标日:' + date, 43, 165); } function setRectAndQrcode() { // 绘制背景 context.setFillStyle('#fff'); context.setShadow(5, 5, 12, '#999'); context.fillRect(20, 20, windowWidth - 40, 160); context.setShadow(0, 0, 0, '#fff'); // 绘制二维码 context.drawImage('./bytefactory.jpg', windowWidth - 145, 55, 100, 100); } [代码] 画布定义高度为 200 px,宽度为屏幕宽度(通过 [代码]wx.getSystemInfoSync().windowWidth[代码] API 获取),使用 [代码]wx.createCanvasContext[代码] 创建画布对象,然后就是使用具体的 API 进行绘制了,具体 API 的使用,请打开官方文档,对照查看。 上面的几个函数,只是 context 对象的一些设置,接下来再定义一个函数 [代码]drawCard()[代码] 用来绘制图形,代码如下: [代码]function drawCard(title, date, number, isPast) { var numberLabel = isPast === true ? '已过天数' : '剩余天数'; var numberColor = isPast === true ? '#3cc51f' : '#e64340'; setRectAndQrcode(); setTitle(title); setLabel(numberLabel); setNumber(number, numberColor); setDate(date); // 绘制 context.draw(); } [代码] 将一些变量提取出来,通过参数形式传入。 最后,在 [代码]onShow[代码] 页面监听函数中,加一行代码就搞定了,当显示页面的时候,执行 [代码]drawCard[代码] 函数,绘制图形。 [代码]onShow: function () { drawCard(this.data.title, this.data.date, this.data.number, this.data.isPast); }, [代码] 绘制完卡片后,canvas 有一个导出图片的 API,然后调用保存图片到相册的 API,就可以了。视图中有一个按钮,绑定了 saveToImg 的函数,代码如下: [代码]saveToImg: function () { this.setData({ isLoading: true }); wx.canvasToTempFilePath({ canvasId: 'firstCanvas', fileType: 'jpg', quality: 1, success: res => { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: res => { this.setData({ isLoading: false }); wx.showToast({ title: '保存成功' }) } }); } } [代码] 导出文件 API:wx.canvasToTempFilePath 保存相册 API:wx.saveImageToPhotosAlbum 具体使用方法,请点击查看官方文档,学会查看文档尤其重要。 最后,留一个思考题,假设,我不想在卡片上显示目标日,该如何实现定制卡片的需求? 给个提示:尝试使用 clearRect,当然页面也要有对应的修改。最终效果如下图: [图片] [图片] 假设尝试过了,还是不行,点这里查看源代码。
2019-09-04 - 用百度文字识别接口,对图片进行base64编码和encodeurl,还是不对?
[图片] 控制台显示filePath 应该是字符串,我用的本地图片,直接用chooseimage把路径存在tempFilePaths,不知道哪里错了。 控制台还显示图片格式错误,我都根据百度要求来了不知道哪里不对 [图片] [图片]
2019-08-25