- 云开发基础NodeJS
云函数的运行环境是 Node.js,我们可以在云函数中使用 Nodejs 内置模块以及使用 npm 安装第三方依赖来帮助我们更快的开发。借助于一些优秀的开源项目,避免了我们重复造轮子,相比于小程序端,能够大大扩展云函数的使用 云函数与 Nodejs由于云函数与 Nodejs 息息相关,需要我们对云函数与 Node 的模块以及 Nodejs 的一些基本知识有一些基本的了解。下面只介绍一些基础的概念,如果你想详细深入了解,建议去翻阅一下 Nodejs 的官方技术文档: 技术文档:Nodejs API 中文技术文档 Nodejs 的内置模块在前面我们已经接触过 Nodejs 的 fs 模块、path 模块,这些我们称之为 Nodejs 的内置模块,内置模块不需要我们使用 npm install 下载,就可以直接使用 require 引入: const fs = require('fs') const path = require('path') Nodejs 的常用内置模块以及功能如下所示,这些模块都是可以在云函数里直接使用的: fs 模块:文件目录的创建、删除、查询以及文件的读取和写入,下面的 createReadStream 方法类似于读取文件,path 模块:提供了一些用于处理文件路径的 APIurl 模块:用于处理与解析 URLhttp 模块:用于创建一个能够处理和响应 http 响应的服务querystring 模块:解析查询字符串until 模块 :提供用于解析和格式化 URL 查询字符串的实用工具;net 模块:用于创建基于流的 TCP 或 IPC 的服务器crypto 模块:提供加密功能,包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装在云函数中使用 HTTP 请求访问第三方服务可以不受域名限制,即不需要像小程序端一样,要将域名添加到 request 合法域名里;也不受 http 和 https 的限制,没有域名只有 IP 都是可以的,所以云函数可以应用的场景非常多,即能方便的调用第三方服务,也能够充当一个功能复杂的完整应用的后端。不过需要注意的是,云函数是部署在云端,有些局域网等终端通信的业务只能在小程序里进行。 常用变量module、exports、require require 用于引入模块、 JSON、或本地文件。 可以从 node_modules 引入模块,可以使用相对路径(例如 ./、)引入本地模块或 JSON 文件,路径会根据 __dirname 定义的目录名或当前工作目录进行处理。 node 模块化遵循的是 commonjs 规范,CommonJs 定义的模块分为: 模块标识(module)、模块导出(exports) 、模块引用(require)。 在 node 中,一个文件即一个模块,使用 exports 和 require 来进行处理。 exports 表示该模块运行时生成的导出对象。如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、 .json 或 .node 拓展名再加载。 .js 文件会被解析为 JavaScript 文本文件, .json 文件会被解析为 JSON 文本文件。 .node 文件会被解析为通过 process.dlopen() 加载的编译后的插件模块。以 '/' 为前缀的模块是文件的绝对路径。 例如, require('/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。以 './' 为前缀的模块是相对于调用 require() 的文件的。 也就是说, circle.js 必须和 foo.js 在同一目录下以便于 require('./circle') 找到它。 module.exports 用于指定一个模块所导出的内容,即可以通过 require() 访问的内容。 // 引入本地模块: const myLocalModule = require('./path/myLocalModule'); // 引入 JSON 文件: const jsonData = require('./path/filename.json'); // 引入 node_modules 模块或 Node.js 内置模块: const crypto = require('crypto'); wx-server-sdk 的模块tcb-admin-node、protobuf、jstslib 第三方模块Nodejs 有 npm 官网地址 Nodejs 库推荐:awesome Nodejs 当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录,比如 wx-server-sdk 就加载自 node_modules 文件夹: const cloud = require('wx-server-sdk') Lodash 实用工具库Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库,通过降低 array、number、objects、string 等数据类型的使用难度从而让 JavaScript 变得更简单。Lodash 的模块化方法非常适用于:遍历 array、object 和 string;对值进行操作和检测;创建符合功能的函数。 技术文档:Lodash 官方文档、Lodash 中文文档 使用开发者工具新建一个云函数,比如 lodash,然后在 package.json 增加 lodash 最新版 latest 的依赖: "dependencies": { "lodash": "latest" } 在 index.js 里的代码修改为如下,这里使用到了 lodash 的 chunk 方法来分割数组: const cloud = require('wx-server-sdk') var _ = require('lodash'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { //将数组拆分为长度为2的数组 const arr= _.chunk(['a', 'b', 'c', 'd'], 2); return arr } 右键 lodash 云函数目录,选择“在终端中打开”,npm install 安装模块之后右键部署并上传所有文件。我们就可以通过多种方式来调用它(前面已详细介绍)即可获得结果。Lodash 作为工具,非常好用且实用,它的源码也非常值得学习,更多相关内容则需要大家去 Github 和官方技术文档里深入了解。 在awesome Nodejs页面我们了解到还有 Ramba、immutable、Mout 等类似工具库,这些都非常推荐。借助于 Github 的 awesome 清单,我们就能一手掌握最酷炫好用的开源项目,避免了自己去收集收藏。 moment 时间处理开发小程序时经常需要格式化时间、处理相对时间、日历时间以及时间的多语言问题,这个时候就可以使用比较流行的 momentjs 了。 技术文档:moment 官方文档、moment 中文文档 使用开发者工具新建一个云函数,比如 moment,然后在 package.json 增加 moment 最新版 latest 的依赖: "dependencies": { "moment": "latest" } 在 index.js 里的代码修改为如下,我们将 moment 区域设置为中国,将时间格式化为 十二月 23 日 2019, 4:13:29 下午的样式以及相对时间多少分钟前: const cloud = require('wx-server-sdk') const moment = require("moment"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { moment.locale('zh-cn'); time1 = moment().format('MMMM Do YYYY, h:mm:ss a'); time2 = moment().startOf('hour').fromNow(); return { time1,time2} } 不过云函数中的时区为 UTC+0,不是 UTC+8,格式化得到的时间和在国内的时间是有 8 个小时的时间差的,我们可以给小时数+8,也可以修改时区。云函数修改时区我们可以使用 timezone 依赖(和 moment 是同一个开源作者)。 技术文档:timezone 技术文档 在 package.json 增加 moment-timezone 最新版 latest 的依赖,然后修改上面相应的代码即可,使用起来非常方便: const moment = require('moment-timezone'); time1 = moment().tz('Asia/Shanghai').format('MMMM Do YYYY, h:mm:ss a'); 获取公网 IP有时我们希望能够获取到服务器的公网 IP,比如用于 IP 地址的白名单,或者想根据 IP 查询到服务器所在的地址,ipify 就是一个免费好用的依赖,通过它我们也可以获取到云函数所在服务器的公网 IP。 技术文档:ipify Github 地址 使用开发者工具新建一个 getip 的云函数,然后输入以下代码,并在 package.json 的”dependencies”里新增 "ipify":"latest" ,即最新版的 ipify 依赖: const cloud = require('wx-server-sdk') const ipify = require('ipify'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { return await ipify({ useIPv6: false }) } 然后右键 getip 云函数根目录,选择在终端中打开,输入 npm install 安装依赖,之后上传并部署所有文件。我们可以在小程序端调用这个云函数,就可以得到云函数服务器的公网 IP,这个 IP 是随机而有限的几个,反复调用 getip,就能够穷举所有云函数所在服务器的 ip 了。 可能你会在使用云函数连接数据库或者用云函数来建微信公众号的后台时需要用到 IP 白名单,我们可以把这些 ip 都添加到白名单里面,这样云函数就可以做很多事情啦。 Buffer 文件流const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/cloudbase/1576500614167-520.png' const res = await cloud.downloadFile({ fileID: fileID, }) const buffer = res.fileContent return buffer.toString('base64') } getServerImg(){ wx.cloud.callFunction({ name: 'downloadimg', success: res => { console.log("云函数返回的数据",res.result) this.setData({ img:res.result }) }, fail: err => { console.error('云函数调用失败:', err) } }) } "400px" height="200px" src="data:image/jpeg;base64,{{img}}">image> Buffer String Buffer JSON 图像处理 sharpsharp 是一个高速图像处理库,可以很方便的实现图片编辑操作,如裁剪、格式转换、旋转变换、滤镜添加、图片合成(如添加水印)、图片拼接等,支持 JPEG, PNG, WebP, TIFF, GIF 和 SVG 格式。在云函数端使用 sharp 来处理图片,而云存储则可以作为服务端和小程序端来传递图片的桥梁。 技术文档:sharp 官方技术文档 使用开发者工具新建一个 const cloud = require('wx-server-sdk') const fs = require('fs') const path = require('path') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) const sharp = require('sharp'); exports.main = async (event, context) => { //这里换成自己的fileID,也可以在小程序端上传文件之后,把fileID传进来event.fileID const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/1572315793628-366.png' //要用云函数处理图片,需要先下载图片,返回的图片类型为Buffer const res = await cloud.downloadFile({ fileID: fileID, }) const buffer = res.fileContent //sharp对图片进行处理之后,保存为output.png,也可以直接保存为Buffer await sharp(buffer).rotate().resize(200).toFile('output.png') // 云函数读取模块目录下的图片,并上传到云存储 const fileStream = await fs.createReadStream(path.join(__dirname, 'output.png')) return await cloud.uploadFile({ cloudPath: 'sharpdemo.jpg', fileContent: fileStream, }) } 也可以让 sharp 不需要先 toFile 转成图片,而是直接转成 Buffer,这样就可以直接作为参数传给 fileContent 上传到云存储,如: const buffer2 = await sharp(buffer).rotate().resize(200).toBuffer(); return await cloud.uploadFile({ cloudPath: 'sharpdemo2.jpg', fileContent: buffer2, }) 连接数据库 MySQL公网连接数据库 MySQL技术文档:Sequelize const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', //数据库地址,默认本机 port:'3306', dialect: 'mysql', pool: { //连接池设置 max: 5, //最大连接数 min: 0, //最小连接数 idle: 10000 }, }); 无论是MySQL,还是PostgreSQL、Redis、MongoDB等其他数据库,只要我们在 私有网络连接 MySQL默认情况下,云开发的函数部署在公共网络中,只可以访问公网。如果开发者需要访问腾讯云的 Redis、TencentDB、CVM、Kafka 等资源,需要建立私有网络来确保数据安全及连接安全。 连接数据库 Redisconst cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) const Redis = require('ioredis') const redis = new Redis({ port: 6379, host: '10.168.0.15', family: 4, password: 'CloudBase2018', db: 0, }) exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const cacheKey = wxContext.OPENID const cache = await redis.get(cacheKey) if (!cache) { const result = await new Promise((resolve, reject) => { setTimeout(() => resolve(Math.random()), 2000) }) redis.set(cacheKey, result, 'EX', 3600) return result } else { return cache } } 二维码 qrcode技术文档:node-qrcode Github 地址 邮件处理技术文档:Nodemailer Github 地址、Nodemailer 官方文档 使用开发者工具创建一个云函数,比如 nodemail,然后在 package.json 增加 nodemailer 最新版 latest 的依赖: "dependencies": { "nodemailer": "latest" } 发送邮件服务器:smtp.qq.com,使用 SSL,端口号 465 或 587 const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { 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: "344169902@qq.com", //你的邮箱账号 pass: "你的QQ邮箱授权码" //邮箱密码,QQ的需要是独立授权码 } }); let message = { from: '来自李东bbsky <344169902@qq.com>', //你的发件邮箱 to: '你要发送给谁', //你要发给谁 // cc:'', 支持cc 抄送 // bcc: '', 支持bcc 密送 subject: '欢迎大家参与云开发技术训练营活动', //支持text纯文字,html代码 text: '欢迎大家', html: '你好:' + '欢迎欢迎', attachments: [ //支持多种附件形式,可以是String, Buffer或Stream { filename: 'image.png', content: Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/' + '//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U' + 'g9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', 'base64' ), }, ] }; let res = await transporter.sendMail(message); return res; } Excel 文档处理Excel 是存储数据比较常见的格式,那如何让云函数拥有读写 Excel 文件的能力呢?我们可以在 Github 上搜索关键词“Node Excel”,去筛选 Star 比较多,条件比较契合的。 Github 地址:node-xlsx 使用开发者工具新建一个云函数,在 package.json 里添加 latest 最新版的 node-xlsx: "dependencies": { "wx-server-sdk": "latest", "node-xlsx": "latest" } 读取云存储的 Excel 文件 const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) const xlsx = require('node-xlsx'); const db = cloud.database() exports.main = async (event, context) => { const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/china.csv' const res = await cloud.downloadFile({ fileID: fileID, }) const buffer = res.fileContent const tasks = [] var sheets = xlsx.parse(buffer); sheets.forEach(function (sheet) { for (var rowId in sheet['data']) { console.log(rowId); var row = sheet['data'][rowId]; if (rowId > 0 && row) { const promise = db.collection('chinaexcel') .add({ data: { city: row[0], province: row[1], city_area: row[2], builtup_area: row[3], reg_pop: row[4], resident_pop: row[5], gdp: row[6] } }) tasks.push(promise) } } }); let result = await Promise.all(tasks).then(res => { return res }).catch(function (err) { return err }) return result } 将数据库里的数据保存为 CSV 技术文档:json2CSV HTTP 处理got、superagent、request、axios、request-promise 尽管云函数的 Nodejs 版本比较低(目前为 8.9),但绝大多数模块我们都可以使用 Nodejs 12 或 13 的环境来测试,不过有时候也要留意有些模块不支持 8.9,比如 got 10.0.1 以上的版本。node 中,http 模块也可作为客户端使用(发送请求),第三方模块 request 对其使用方法进行了封装,操作更方便!所以来介绍一下 request 模块 get 请求const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) const rp = require('request-promise') exports.main = async (event, context) => { const options = { url: 'https://news-at.zhihu.com/api/4/news/latest', json: true, method: 'GET', }; return await rp(options) } post 请求结合文件流request('https://www.jmjc.tech/public/home/img/flower.png').pipe(fs.createWriteStream('./flower.png')) // 下载文件到本地 加解密 Cryptocrypto 模块是 nodejs 的核心模块之一,它提供了安全相关的功能,包含对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。由于 crypto 模块是内置模块,我们引入它是无需下载,就可以直接引入。 使用开发者工具新建一个云函数,比如 crypto,在 index.js 里输入以下代码,我们来了解一下 crypto 支持哪些加密算法,并以 MD5 加密为例: const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) const crypto = require('crypto'); exports.main = async (event, context) => { const hashes = crypto.getHashes(); //获取crypto支持的加密算法种类列表 //md5 加密 CloudBase2020 返回十六进制 var md5 = crypto.createHash('md5'); var message = 'CloudBase2020'; var digest = md5.update(message, 'utf8').digest('hex'); return { "crypto支持的加密算法种类":hashes, "md5加密返回的十六进制":digest }; } 将云函数部署之后调用从返回的结果我们可以了解到,云函数 crypto 模块支持 46 种加密算法。 发短信“qcloudsms_js”: “^0.1.1” const cloud = require('wx-server-sdk') const QcloudSms = require("qcloudsms_js") const appid = 1400284950 // 替换成您申请的云短信 AppID 以及 AppKey const appkey = "a33b602345f5bb866f040303ac6f98ca" const templateId = 472078 // 替换成您所申请模板 ID const smsSign = "统计小助理" // 替换成您所申请的签名 cloud.init() // 云函数入口函数 exports.main = async (event, context) => new Promise((resolve, reject) => { /*单发短信示例为完整示例,更多功能请直接替换以下代码*/ var qcloudsms = QcloudSms(appid, appkey); var ssender = qcloudsms.SmsSingleSender(); var params = ["1234", "15"]; // 获取发送短信的手机号码 var mobile = event.mobile // 获取手机号国家/地区码 var nationcode = event.nationcode ssender.sendWithParam(nationcode, mobile, templateId, params, smsSign, "", "", (err, res, resData) => { /*设置请求回调处理, 这里只是演示,您需要自定义相应处理逻辑*/ if (err) { console.log("err: ", err); reject({ err }) } else { resolve({ res: res.req, resData }) } } ); }) 使用开发者工具 wx.cloud.callFunction({ name: 'sendphone', data: { // mobile: '13217922526', mobile: '18565678773', nationcode: '86' }, success: res => { console.log('[云函数] [sendsms] 调用成功') console.log(res) }, fail: err => { console.error('[云函数] [sendsms] 调用失败', err) } })
2021-09-10 - 数据库原子操作和事务讲解
使用更新指令(如 inc、mul、addToSet)可以对云数据库的一条记录和记录内的子文档(结合反范式化设计)进行原子操作,但是如果要跨多个记录或跨多个集合的原子操作时,就需要使用云数据库的事务能力。 12.6.1 更新指令的原子操作关系型数据库是很难做到通过一个语句对数据强制一致性的需求来表示的,只能依赖事务。但是云开发数据库由于可以反范式化设计内嵌子文档,以及更新指定可以对单个记录或同一个记录内的子文档进行原子操作,所以通常情况下,云开发数据库不必使用事务。 比如调整某个订单项目的数量之后,应该同时更新该订单的总费用,我们可以设计采用如下方式设计该集合,比如订单的集合为 order: { "_id": "2020030922100983", "userID": "124785", "total":117, "orders": [{ "item":"苹果", "price":15, "number":3 },{ "item":"火龙果", "price":18, "number":4 }] } 客户在下单的时候经常会调整订单内某个商品比如苹果的购买数量,而下单的总价又必须同步更新,不能购买数量减少了,但是总价不变,这两个操作必须同时进行,如果是使用关系型数据库,则需要先通过两次查询,更新完数据之后,再存储进数据库,这个很容易出现有的成功,有的没有成功的情况。但是云开发的数据库则可以借助于更新指令做到一条更新来实现两个数据同时成功或失败: db.collection("order") .doc("2020030922100983") .update({ data: { "orders.0.number": _.inc(1), total: _.inc(15), }, }); 这个操作只是在单个记录里进行,那要实现跨记录要进行原子操作呢?更新指令其实是可以做到事务仿真的,但是比较麻烦,这时就建议用事务了。 12.6.2 事务与 ACID事务就是一段数据库语句的批处理,但是这个批处理是一个 atom(原子),多个增删改的操作是绑定在一起的,不可分割,要么都执行,要么回滚(rollback)都不执行。比如银行转账,需要做到一个账户的钱汇出去了,那另外一个账户就一定会收到钱,不能钱汇出去了,但是钱没有到另外一个的账上;也就是要执行转账这个事务,会对 A 用户的账户数据和 B 用户的账户数据做增删改的处理,这两个处理必须一起成功一起失败。 1、ACID一般来说,事务是必须满足 4 个条件(ACID): Atomicity(原子性)、Consistency(稳定性)、Isolation(隔离性)、Durability(可靠性): 原子性:整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中一部分操作,一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库都必须处于一致性状态。换句话说,事务的执行结果必须是使数据库从一个一致性状态转变到另一个一致性状态。比如在执行事务前,A 用户账户有 50 元,B 用户账户有 150 元;执行 B 转给 A 50 元事务后,两个用户账户总和还是 200 元。隔离性:事务的隔离性是指在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间事务之间,互不干扰。比如在线银行,同时转账的人虽然很多,但是不会出现影响 A 与 B 之间的转账;可靠性:即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态,已提交事务的更新不会丢失。 2、云函数事务注意事项01不支持批量操作,只支持单记录操作 在事务中不支持批量操作(where 语句),只支持单记录操作(collection.doc, collection.add),这可以避免大量锁冲突、保证运行效率,并且大多数情况下,单记录操作足够满足需求,因为在事务中是可以对多个单个记录进行操作的,也就是可以比如说在一个事务中同时对集合 A 的记录 x 和 y 两个记录操作、又对集合 B 的记录 z 操作。 02云数据库采用的是快照隔离 对于两个并发执行的事务来说,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据;不可重复读:在一个事务内两次读到的数据是不一样的,受到另一个事务修改后提交的影响,因此称为是不可重复读幻读:第一个事务对表进行读取,当第二个事务对表进行增加或删除操作事务提交后,第一个事务再次读取,会出现增加或减少行数的情况云开发的数据库系统的事务过程采用的是快照隔离(Snapshot isolation),可以避免并发操作带来数据不一致的问题。 事务期间,读操作返回的是对象的快照,而非实际数据事务期间,写操作会:1. 改变快照,保证接下来的读的一致性;2. 给对象加上事务锁事务锁:如果对象上存在事务锁,那么:1. 其它事务的写入会直接失败;2. 普通的更新操作会被阻塞,直到事务锁释放或者超时事务提交后,操作完毕的快照会被原子性地写入数据库中 12.6.3 事务操作的两套 API云开发数据库的事务提供两种操作风格的接口,一个是简易的、带有冲突自动重试的 runTransaction 接口,一个是流程自定义控制的 startTransaction 接口。通过 runTransaction 回调中获得的参数 transaction 或通过 startTransaction 获得的返回值 transaction,我们将其类比为 db 对象,只是在其上进行的操作将在事务内的快照完成,保证原子性。transaction 上提供的接口树形图一览: transaction |-- collection 获取集合引用 | |-- doc 获取记录引用 | | |-- get 获取记录内容 | | |-- update 更新记录内容 | | |-- set 替换记录内容 | | |-- remove 删除记录 | |-- add 新增记录 |-- rollback 终止事务并回滚 |-- commit 提交事务(仅在使用 startTransaction 时需调用) 1、通过 runTransaction 回调获得 transaction以下提供一个使用 runTransaction 接口的,两个账户之间进行转账的简易示例。事务执行函数由开发者传入,函数接收一个参数 transaction,其上提供 collection 方法和 rollback 方法。collection 方法用于取数据库集合记录引用进行操作,rollback 方法用于在不想继续执行事务时终止并回滚事务。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const _ = db.command; exports.main = async (event) => { try { const result = await db.runTransaction(async (transaction) => { const aaaRes = await transaction.collection("account").doc("aaa").get(); const bbbRes = await transaction.collection("account").doc("bbb").get(); if (aaaRes.data && bbbRes.data) { const updateAAARes = await transaction .collection("account") .doc("aaa") .update({ data: { amount: _.inc(-10), }, }); const updateBBBRes = await transaction .collection("account") .doc("bbb") .update({ data: { amount: _.inc(10), }, }); console.log(`transaction succeeded`, result); return { aaaAccount: aaaRes.data.amount - 10, }; } else { await transaction.rollback(-100); } }); return { success: true, aaaAccount: result.aaaAccount, }; } catch (e) { console.error(`事务报错`, e); return { success: false, error: e, }; } }; 事务执行函数必须为 async 异步函数或返回 Promise 的函数,当事务执行函数返回时,SDK 会认为用户逻辑已完成,自动提交(commit)事务,因此务必确保用户事务逻辑完成后才在 async 异步函数中返回或 resolve Promise。 2、通过 startTransaction 获得 transactiondb.startTransaction(),开启一个新的事务,之后即可进行 CRUD 操作;db.startTransaction().transaction.commit(),提交事务保存数据,在提交之前事务中的变更的数据对外是不可见的;db.startTransaction().rollback(),事务终止并回滚事务,例如,一部分数据更新失败,对已修改过的数据也进行回滚。const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const db = cloud.database({ throwOnNotFound: false, }); const _ = db.command; exports.main = async (event) => { try { const transaction = await db.startTransaction(); const aaaRes = await transaction.collection("account").doc("aaa").get(); const bbbRes = await transaction.collection("account").doc("bbb").get(); if (aaaRes.data && bbbRes.data) { const updateAAARes = await transaction .collection("account") .doc("aaa") .update({ data: { amount: _.inc(-10), }, }); const updateBBBRes = await transaction .collection("account") .doc("bbb") .update({ data: { amount: _.inc(10), }, }); await transaction.commit(); return { success: true, aaaAccount: aaaRes.data.amount - 10, }; } else { await transaction.rollback(); return { success: false, error: `rollback`, rollbackCode: -100, }; } } catch (e) { console.error(`事务报错`, e); } }; 也就是说对于多用户同时操作(主要是写)数据库的并发处理问题,我们不仅可以使用原子更新,还可以使用事务。其中原子更新主要用户操作单个记录内的字段或单个记录里内嵌的数组对象里的字段,而事务则主要是用于跨记录和跨集合的处理。
2021-09-10 - 只有三行代码的神奇云函数的功能之三:100%成功获取unionid
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之三:100%成功获取unionid: 保证100%成功获取unionid,需要用户信息授权。 强调一下:这个100%是指必须绑定了开放平台,那么不管用户是什么情况,不管有没有关注公众号,一定100%能获取到unionid。 依然需要符合unionid机制:第1条。 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html js: getUserInfo: function (e) { app.globalData.userInfo = e.detail.userInfo if (!app.globalData.unionid ) { wx.cloud.callFunction({ name: 'login', data: { weRunData: wx.cloud.CloudID(e.detail.cloudID) } }).then(res => { app.globalData.unionid = res.result.weRunData.data.unionId }) } }, 其他功能: 神奇功能之四:获取电话号码: 还是这三行代码,获取用户的电话号码。 https://developers.weixin.qq.com/community/develop/article/doc/0006a8ec7ac860c94bf90a34f5d813 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, https://developers.weixin.qq.com/community/develop/article/doc/000ea802c00f70894cf9fe72556013 神奇功能之一:获取openid: https://developers.weixin.qq.com/community/develop/article/doc/00080c6e3746d8a940f9b43e55fc13 神奇功能之二:不用授权获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/000a0c6b580338e947f9db0c65b813 [图片]
2020-10-25 - 希望云开发数据库支持 distinct 去重
- 需求的场景描述(希望解决的问题) 现在只能自己取出所有数据,一条条循环比对才能去重,太过消耗资源浪费时间 - 希望提供的能力 希望云开发数据库支持 distinct 去重
2019-07-07 - 开源的智能垃圾分类小程序代码详解
如果没有记错,我应该在社区陆陆续续发布了3个版本的开源垃圾分类小程序 其中一个是基于php的自建服务器版本,另外两个是基于云开发的垃圾分类小程序版本,按照云开发版本顺序, 今天开源第三个基于云开发的垃圾分类小程序,暂且命名为垃圾分类小程序v3,目前功能完善,实现了以下三个核心功能 1、拍照识别 2、垃圾分类查询 3、垃圾分类测评 截图如下所示 1 [图片] 2 [图片] 3 [图片] 4 [图片] 5 [图片] 6 这份代码,我陆陆续续跟大家一起读读吧 第一个模块识别位于pages/ai/index.wxml文件里面 [图片] 7 点击自动识别调用下面的事件 [图片] 8 下面的意思是先判断是否授权拍照,如果没有授权那么调用授权拍照接口,之后跳转到新页面进行拍照 [图片] 9 到拍照模块 [图片] 点击拍照按钮,调用逻辑为takePhoto [图片] 9 这块代码的逻辑是:拍照,将拍照的临时图片转成base64,然后调用了百度ai接口进行识别 [图片] [图片] 9 扫码体验 [图片] 9 9
2020-06-12 - 长按海报长图无法识别小程序码的解决方案
今天刚好看到 @沐绒。 提的一个问题《show-menu-by-longpress 在图片过长时,ios会识别不了小程序二维码》,于是就一本正经的慢悠悠的打开集齐BUG于一身让人又爱又恨的小程序开发工具,心里在想“呵,识别不了??我就不信了” 基础库:2.11.0 经过了漫长的长达300,000毫秒时间,新建了一个代码片段试了一番,各种预览,真机预览,真机调试,确实存在长按识别不出来的情况 是图片太长了?还是功能让人感觉有忽悠了? 最后发现,问题所在:只有在页面可视区域完全显示小程序码时,长按才能识别 所有在这个长按识码的功能变得更强大之前,解决方案如下:(不想多写代码,画海报的时候就把小程序码画在左上或者右上吧) 1、给图片绑定touchstart事件 2、使用wx.createSelectorQuery获取图片高宽位置信息 3、使用wx.pageScrollTo将页面滚动到图片底部对应页面的高度位置,注意 duration 默认为300ms,这里要设置duration为0,不然... js: touchstart: function (e) { wx.createSelectorQuery().select('.qrcodeImg').boundingClientRect(function (res) { wx.pageScrollTo({ scrollTop: res.top + res.height, duration:0, // 设置页面滚动所需的时间,这里设置为0 }) }).exec() } wxml: <image src="图片地址" bindtouchstart="touchstart" class="qrcodeImg" show-menu-by-longpress="{{true}}" mode="widthFix"></image> 最后猜测:长按识码,貌似是长按时,截取当前手机屏幕的图片进行识别的? 以下图片来自 问题《show-menu-by-longpress 在图片过长时,ios会识别不了小程序二维码》 第1张图,完整显示小程序码,可以识别 第2张图,小程序码显示不完整,无法识别 第3张图,移动vConsole到小程序码上,也无法识别 [图片][图片][图片] 代码片段:https://developers.weixin.qq.com/s/EGde0fm970h2
2020-05-10 - 构建npm时出现没有找到可以构建的npm包?
npm i @vant/weapp -S --production npm init -y 这两条命令都执行了,还是不行
2020-03-31 - db.RegExp如何用在多字段or操作里?
看文档里说明db.RegExp不支持用在db.command里,但目前我的需求是,在match阶段,针对3个字段or操作,正则匹配一个关键词,应该如何写? 报错如下: [代码]errMsg: [FailedOperation] (Location15983) An object representing an expression must have exactly one field: { $regex: [代码][代码]"关键词"[代码][代码], $options: [代码][代码]"i"[代码] [代码]}; ; at cloud.callFunction api[代码] 我大致的需求是这样的: [代码]const reg = [代码][代码]new[代码] [代码]db.RegExp({[代码][代码] [代码][代码]regexp: [代码][代码]'关键词'[代码][代码],[代码][代码] [代码][代码]options: [代码][代码]'i'[代码][代码],[代码][代码] [代码][代码]})[代码] [代码]...[代码][代码].match(_.expr([代码][代码] [代码][代码]$.or([[代码][代码] [代码][代码]$.eq([[代码][代码]'$nickName'[代码][代码], reg]),[代码][代码] [代码][代码]$.eq([[代码][代码]'$deliverInfo.userName'[代码][代码], reg]),[代码][代码] [代码][代码]$.eq([[代码][代码]'$deliverInfo.telNumber'[代码][代码], reg])[代码][代码] [代码][代码]])[代码][代码] [代码][代码]))[代码] 我尝试不用$.or,先匹配一个字段也会报错 [代码]...[代码][代码].match(_.expr([代码][代码] [代码][代码]$.eq([[代码][代码]'$nickName'[代码][代码], reg]),[代码][代码] [代码][代码]))[代码]
2019-11-06 - 云函数调用数据库后返回小程序的result为null
云函数: [代码]exports.main = async (event, context) => [代码][代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]db.collection([代码][代码]'cardList'[代码][代码]).add({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]name : [代码][代码]'name'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码]() {[代码][代码] [代码][代码]resolve({status : 1})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码]})[代码]小程序: [代码]wx.cloud.callFunction({[代码][代码] [代码][代码]name: [代码][代码]'addCard'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]name : [代码][代码]'name'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]complete: res => {[代码][代码] [代码][代码]console.log([代码][代码]'callFunction test result: '[代码][代码], res)[代码][代码] [代码][代码]},[代码][代码] [代码][代码]success : res => {[代码][代码] [代码][代码]console.log(res)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码]小程序中打印出来的是: {errMsg: "cloud.callFunction:ok", result: null, requestID: "e474bbe7-10e1-11e9-9884-525400192d0e"} 请问result为何返回的是 {status : 1} 但是官网的例子是可以输出的值 [代码]exports.main = (event, context) => [代码][代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]setTimeout(() => {[代码][代码] [代码][代码]resolve(3333)[代码][代码] [代码][代码]}, 3000)[代码][代码] [代码][代码]})[代码] {errMsg: "cloud.callFunction:ok", result: 3333, requestID: "e474bbe7-10e1-11e9-9884-525400192d0e"}
2019-01-06 - 云函数中两个集合关联条件查询怎么写?
[代码]table1 {[代码][代码] {[代码] [代码] _id:1a2b,[代码] [代码] [代码][代码]hit:2,[代码][代码] [代码][代码]point: [114,25],[代码] [代码] }[代码] [代码]}[代码][代码]table2 {[代码][代码] {[代码] [代码] _id:1a3d,[代码] [代码] [代码][代码]tid:1a2b,[代码][代码] [代码][代码]get[代码][代码]: 2,[代码] [代码] }[代码] [代码]}[代码]如上两个数据表集合,table2中的tid关联table1中的_id,我需要查询两种情况: 1、table2中满足hit>0,get>0,point在10公里范围内的随机3个结果; 2、table2中满足hit>0,get>0,point在10公里范围内的数量; 以上两种要怎么查询?聚合运算看不太懂,两个表中都有筛选条件的不知道怎么加上去
2019-10-31 - 微信小程序 如何把使用MD5加密emjoy字符串,比如'Stefan👣'
最近在获取到微信的昵称后,需要使用MD5加密传给后台,但是发现小程序直接使用可以加密中英文的md5.js来加密还有emjoy字符串的昵称,与服务器的加密不一致。有想法把emjoy字符串转成base64,但是发现没有emjoy字符串base64转换的js,所以想咨询一下,emjoy字符串在小程序中,改如果进行MD5加密或者改怎么转换成base64
2018-09-12 - # 使用小程序云开发API更新数组中的单个数组元素
使用小程序云开发API更新数组中的单个数组元素 看了看mongoDB的更新数据方式,找到了解决办法,解决方法如下,亲测可用: 第一种方法:使用位置操作符$ [代码]代码,条件更新写在云函数中 [代码] [图片] [代码]test_api集合原始数据如下 [代码] [图片] [代码]在云函数中执行1中的代码,数组users中id为1001的用户添加了一个新的属性test [代码] [图片] [代码]原理分析 [代码] where条件是查找数组中id属性为1001的用户 update中的使用’users.$.test’: ‘test’ 注意里面的$符号,在mongoDB中,这个符号叫做位置操作符,代表数组的下标,如下引自《mongoDB实战》 [图片] 第二种方法:直接使用数组下标 云函数代码 [图片] test_api集合原始数据如下 [图片] 代码执行后 [图片] 相对于第一种方法,这种方法更加简单,只不过users.1.test这种写法有点颠覆js和java中的属性书写规则,让人感觉怪怪的,在mongoDB中,也支持点数字这种写法。 一个可能的疑惑 可不可以写作’users[1].test’:‘test’,测试结果如下: [图片] [图片] 可以看到’user[1]'无法被识别为数组的第二个元素,而是作为了属性名新增了一个属性,结论:必须写成”点数字“不能写成“中括号” 结论: 经过测试,使用这两种种方法可以更新数组中的一个元素。 方法一适合在不知道数组元素下标的情况下根据查询条件更新元素; 方法二适合在知道数组元素下标的情况下更新元素; 当然也存在既知道元素下标也可以通过属性查到的情况,想用哪个就看心情了-.- 但是暂未找到查询返回数组中的一个元素的方法,再探索探索吧 ——。——
2019-03-06 - 如何只使用一个云函数搞定一个庞大而复杂的系统
吐槽 翻遍社区的文章,关于云开发的干货,少之又少,大部分都还是官方文档的搬来搬去,没啥营养,是时候放出一点技术"干货"了(有经验的开发者都能想到的方案)! 正题 小程序云开发的云函数的最大限制是 [代码]50[代码] 个,假设每个接口都使用 [代码]1[代码] 个云函数的话,有 [代码]10[代码] 张表,每张表都有 [代码]增删改查[代码] 四个接口,那么就会有 [代码]40[代码] 个接口,再加上一些其他接口,差不多刚刚好够用,那如果有 [代码]20[代码] 张表,甚至更多的表、更多的接口呢?对于中小型的小程序来说足够使用,那如果一个非常庞大而复杂的系统该怎么办呢? 而且每一个云函数的运行环境是独立的,想要共享一些数据也不是特别方便,那么有没有什么办法突破这样的限制呢? 其实解决方案很简单,只需要一点点的 [代码]OOP[代码] 思想和利用 [代码]JavaScript[代码] 的特性,一个云函数就可以搞定所有的接口。 具体的实现请往下看。 思路 云函数的运行环境是 [代码]Nodejs[代码] , 那么使用的语言就是 [代码]JavaScript[代码] ,可以充分的利用 [代码]JavaScript[代码] 的特性。 [代码]JavaScript[代码] 中的 [代码]属性访问表达式[代码] 有两种语法 [代码]expression . identifier expression [ expression ] [代码] 第一种写法是一个表达式后跟随一个句点 [代码].[代码] 和一个标识符。表达式指定对象,标识符则指定需要访问的属性的名称。 第二种写法是使用方括号 [代码][][代码],方括号内是另一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。 不管使用哪种形式的属性访问表达式,在 [代码].[代码] 和 [代码][][代码] 之前的表达式总是会首先计算。 虽然 [代码].[代码] 的写法更加简单,但这种方式只适用于要访问的属性名称的合法标识符,并需要准确知道访问的属性的名字,如果属性的名称是一个保留字或者包含空格和标点符号,或者是一个数字(对于数组来说),则必须使用方括号 [代码][][代码] 的写法。当属性名是通过运算得出的值而不是固定值的时候,这时也必须使用方括号 [代码][][代码] 写法。 感谢社区大神 @卢霄霄 提供参考资料,详见 [代码]JavaScript权威指南[代码] (犀牛书)4.4章节。 可以使用 [代码][][代码] 的形式来完成动态的属性访问。具体实现请往下看。 实现 上面说了太多废话了,下面直接开干吧。 新建云函数 在云开发目录中新建一个云函数,我这里命名为 [代码]cloud[代码]。 打开 [代码]index.js[代码] 文件你会看到下面这段代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } [代码] 这个云函数仅作为入口使用,上面提到了云函数的运行环境是 [代码]Nodejs[代码] 那么 [代码]Nodejs[代码] 的特性也是可以使用的,这里主要用到的是全局对象 [代码]global[代码],详见文档 在文件中,写入一些必要的全局变量,主要还是云数据库方面的,方便后面使用。 在初始化后面插入代码 [代码]global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate [代码] 这样就可以在同一个云函数环境中直接访问这些全局变量。 创建公共类 然后新建一个文件夹,我这里命名为 [代码]controllers[代码] ,这个文件夹用于存放所有的接口。 在 [代码]controllers[代码] 中新建一个 [代码]base-controller.js[代码] 文件,创建一个叫做 [代码]BaseController[代码] 的类,用于提供一些公用的方法。 内容如下: [代码]class BaseController { /** * 调用成功 */ success (data) { return { code: 0, data } } /** * 调用失败 */ fail (erroCode = 0, msg = '') { return { erroCode, msg, code: -1 } } } module.exports = BaseController [代码] 看到这里大家可能有点没看懂在做什么,那么请继续往下看。 创建接口 假设创建一些要操作用户相关的的接口,可以在 [代码]controllers[代码] 文件夹中新建一个 [代码]user-controller.js[代码] 的文件,创建一个名为 [代码]UserController[代码] 的类,并继承上面的 [代码]BaseController[代码] 类,内容如下: [代码]const BaseController = require('./base-controller.js') class UserController extends BaseController { // ... } module.exports = UserController [代码] 可以在这个类中编写所有关于 [代码]user[代码] 的接口方法。 编写接口 假设要分页查询用户信息,可以在 [代码]UserController[代码] 类中创建一个 [代码]list[代码] 方法。 代码如下: [代码]async list (data) { const { pageIndex, pageSize } = data let result = await db.collection('users') .skip((pageIndex - 1) * pageSize) .limit(pageSize) .get() .then(result => this.success(result.data)) .catch(() => this.fail([])) return result } [代码] 由于上面已经定义了全局变量 [代码]db[代码] 所以在 [代码]UserController[代码] 中无需引入 [代码]wx-server-sdk[代码] 引入接口类 写到这里接口已经完成了,还需要再引入这些接口类才可以进行访问。在 [代码]index.js[代码] 中引入 [代码]user-controller.js[代码] [代码]const User = require('./controllers/user-controller.js') [代码] 然后创建一个 [代码]api[代码] 变量,[代码]new[代码] 一个 [代码]User[代码] 实例 [代码]const api = { user: new User() } [代码] 在 [代码]main[代码] 方法中调用 [代码]UserController[代码] 中的方法。 [代码]exports.main = async (event, context) => { const { data } = event let result = await api['user']['list'](data) return result } [代码] 写到这里基本已经完成了接口的调用,但想要一个云函数动态调用所有接口还需要做一些改动。 动态调用接口 刚开始的时候介绍了 [代码]属性访问表达式[代码],限制稍微改动一下 [代码]main[代码] 方法 [代码]exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } [代码] 在小程序调用云函数时,需要传入 [代码]controller[代码]、[代码]action[代码] 和 [代码]data[代码] 参数即可 [代码]const result = await wx.cloud.callFunction({ name: 'cloud', data: { controller, action, data } }) [代码] 完整 [代码]index.js[代码] 文件的代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const User = require('./controllers/user-controller.js') const api = { user: new User() } // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate // 云函数入口 exports.main = async (event, context) => { exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } } [代码] 其他实现 云开发官方团队打造的轮子 tcb-router
2020-05-26 - excel转json批量导入云开发数据库
知识科普 云开发数据库支持导入导出的文件格式是json和csv,这两种格式导出的文件如下 [图片] CSV文件,注意CSV文件不能支持字段为对象和数组的解析 [图片] 云开发,数据库,写了个读取excel转成json文件的小公举、 做过云开发的同学,对云开发数据库的能力那是深恶痛绝的,基于此有两种方案 1、http api 2、写个批量导入的工具 我采用的就是后者,目前已完成读取excel,根据云开发数据库格式转成对应的json文件,并手工导入 excel都是原子数据,要按照云开发数据库的不同字段类型转成对应的对象,数组,json字符串 后面完善后会写成一个通用工具,供大家使用,敬请期待,遇到同样问题的同学让我听到你们的声音 20200310-- 目前进度,目前已经能完整原版根据excel导出json, 由于云开发需要_id信息,目前这个_id是按照如下算法生成的 并且添加了云开发的_id,具体_id的生成逻辑为日期时分秒+行号,行号左边补0对齐,暂定行号为3位数,考虑到一次导入太多,云开发导入也很慢。 比如今天的第二行的数据,_id为20200310131010002 [图片] --20200311 我成功了, 自动读取excel,并且根据excel部分列进行解析转成数据库对应的对象字段、数组字段 其中下面options字段是列3,列4,列5,列6提取组装的 [图片] 占位 上面excel文件模板为 [图片] 占位 --20200315 今天又完善了一点,生成的json直接可以导入,不需要再替换某个列了,完美的一个版本。 占位
2020-03-15 - 云开发经验总结(展示两种增删改查的方法)
开发工具mpvue官方文档 云开发初始化[代码] [代码] [代码] wx.cloud.init({[代码] [代码] [代码][代码]env: [代码][代码]'wedding-10c111'[代码][代码] })[代码] [代码] [代码] 上面这段代码配置在src目录下的main.js文件中 数据库API(不使用云函数进行增删改查)以下说明均写在对应代码注释里,不清楚的请查看相关注释 查(获取数据)[代码] [代码] [代码] // 获取轮播图列表[代码] [代码] getBannerList () {[代码][代码] [代码][代码]// 获取数据库引用[代码][代码] [代码][代码]const db = wx.cloud.database()[代码][代码] [代码][代码]// 获取名为“banner”的集合引用[代码][代码] [代码][代码]const banner = db.collection([代码][代码]'banner'[代码][代码])[代码][代码] [代码][代码]// 获取集合(Promise 风格)[代码][代码] [代码][代码]banner.get().then(res => {[代码][代码] [代码][代码]this[代码][代码].list = res.data[0].bannerList[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 对应实例如下: [图片] 注意:之所以数据库只有一条数据,而把banner列表当成这条数据的一个字段存储,其目的是为了自己后续换图操作的方便 增(添加数据)[代码][代码][代码] [代码] [代码] // 添加用户[代码] [代码] addUser () {[代码][代码] [代码][代码]// 获取数据库引用[代码][代码] [代码][代码]const db = wx.cloud.database()[代码][代码] [代码][代码]// 获取名为“user”的集合引用[代码][代码] [代码][代码]const user = db.collection([代码][代码]'user'[代码][代码])[代码][代码] [代码][代码]// 向“user”集合中添加一条数据(Promise 风格)[代码][代码] [代码][代码]user.add({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]user: that.userInfo,[代码][代码] [代码][代码]// 构造一个服务端时间的引用,我的项目中都是取自己转化后的时间,[代码][代码] [代码][代码]// 取这个时间更加合理,可用于查询条件、更新字段值或新增记录时的字段值[代码][代码] [代码][代码]time: db.serverDate()[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]// 添加成功后重新查询列表[代码][代码] [代码][代码]that.getUserList()[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 对应实例如下: [图片] 注意: 可以看出_id和_openid是添加完自动生成的属性 改(修改数据)[代码] [代码] [代码] // 改变某条留言的显示隐藏[代码] [代码] switchMessage (e) {[代码][代码] [代码][代码]// 获取数据库的引用[代码][代码] [代码][代码]const db = wx.cloud.database()[代码][代码] [代码][代码]// 获取名为“message”的集合的引用[代码][代码] [代码][代码]const message = db.collection([代码][代码]'message'[代码][代码])[代码][代码] [代码][代码]// 这里的id是拿到当前操作项对应的id,[代码][代码] [代码][代码]// 这里的show对应change事件传递过来的值[代码][代码] [代码][代码]message.doc(e.mp.target.dataset.id).update({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]show: e.mp.detail.value[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]console.log(res)[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 对应实例如下: [图片] 注意:这个界面在你们使用的小程序中是看不到的,只有本人才有权限查看 [图片] [代码] [代码] [代码] <[代码][代码]switch[代码] [代码]class[代码][代码]=[代码][代码]"switch"[代码] [代码]:data-id[代码][代码]=[代码][代码]"item._id"[代码] [代码]:checked[代码][代码]=[代码][代码]"item.show"[代码] [代码]@[代码][代码]change[代码][代码]=[代码][代码]"switchMessage"[代码][代码]></[代码][代码]switch[代码][代码]>[代码] [代码] [代码] 注意:上面我们之所以能得到e.mp.target.dataset.id是因为在<switch>标签上加了`:data-id="item._id"`,不然取不到对应id 删(删除数据)正好对应的上图有删除操作 [代码] [代码] [代码] deleteItem (id) {[代码] [代码] [代码][代码]// 记录this指向[代码][代码] [代码][代码]const that = [代码][代码]this[代码][代码] [代码][代码]// 这里之所以使用wx.showModal是防止误操作[代码][代码] [代码][代码]wx.showModal({[代码][代码] [代码][代码]title: [代码][代码]'提示'[代码][代码],[代码][代码] [代码][代码]content: [代码][代码]'你确定要删除这条留言?'[代码][代码],[代码][代码] [代码][代码]success (res) {[代码][代码] [代码][代码]if[代码] [代码](res.confirm) {[代码][代码] [代码][代码]// 获取数据库的引用[代码][代码] [代码][代码]const db = wx.cloud.database()[代码][代码] [代码][代码]// 获取名为“message”集合的引用[代码][代码] [代码][代码]const message = db.collection([代码][代码]'message'[代码][代码])[代码][代码] [代码][代码]// 删除操作(Promise 风格)[代码][代码] [代码][代码]message.doc(id).remove().then(res => {[代码][代码] [代码][代码]// 删除成功后再次请求列表,达到刷新数据的目的[代码][代码] [代码][代码]if[代码] [代码](res.errMsg === [代码][代码]'document.remove:ok'[代码][代码]) {[代码][代码] [代码][代码]that.getList()[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 使用云函数进行增删改查 查(获取数据)[代码] [代码] [代码] // 云函数初始化[代码] [代码] const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码][代码] // 由于文章开始已经讲过初始化步骤,这里init(options)的options可以省略[代码][代码] // options参数定义了云开发的默认配置,该配置会作为之后调用其他所有云 API 的默认配置[代码][代码] cloud.init()[代码][代码] // 获取数据库的引用[代码][代码] const db = cloud.database()[代码][代码] exports.main = async (event, context) => {[代码][代码] [代码][代码]// 将集合名定义成一个变量,方便后续调用[代码][代码] [代码][代码]const dbName = [代码][代码]'message'[代码][代码] [代码][代码]// filter为指定的筛选条件,配合where()使用[代码][代码] [代码][代码]const filter = event.filter ? event.filter : [代码][代码]null[代码][代码] [代码][代码]// pageNum如果小程序端未传入则默认为1[代码][代码] [代码][代码]const pageNum = event.pageNum ? event.pageNum : 1[代码][代码] [代码][代码]// pageSize如果小程序端未传入则默认是10[代码][代码] [代码][代码]const pageSize = event.pageSize ? event.pageSize : 10[代码][代码] [代码][代码]// 数据库满足filter条件的数据总条数[代码][代码] [代码][代码]const countResult = await db.collection(dbName).where(filter).count()[代码][代码] [代码][代码]const total = countResult.total[代码][代码] [代码][代码]// 共多少页[代码][代码] [代码][代码]const totalPage = Math.ceil(total / pageSize)[代码][代码] [代码][代码]// 是否有下一页[代码][代码] [代码][代码]let hasMore[代码][代码] [代码][代码]if[代码] [代码](pageNum >= totalPage) {[代码][代码] [代码][代码]hasMore = [代码][代码]false[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]hasMore = [代码][代码]true[代码][代码] [代码][代码]}[代码][代码] [代码][代码]// 等待所有,orderBy()通过创建时间排序,查询单页数据[代码][代码] [代码][代码]return[代码] [代码]db.collection(dbName).orderBy([代码][代码]'time'[代码][代码], [代码][代码]'desc'[代码][代码]).where(filter).skip((pageNum - 1) * pageSize).limit(pageSize).get().then(res => {[代码][代码] [代码][代码]// 返回结果中顺带注入hasMore和total方便小程序端判断[代码][代码] [代码][代码]res.hasMore = hasMore[代码][代码] [代码][代码]res.total = total[代码][代码] [代码][代码]return[代码] [代码]res[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] [代码] [代码] [代码] getList () {[代码] [代码] [代码][代码]// 记录this指向[代码][代码] [代码][代码]const that = [代码][代码]this[代码][代码] [代码][代码]// 每次调用getList时重新从第一页开始[代码][代码] [代码][代码]that.pageNum = 1[代码][代码] [代码][代码]// 每次调用getList时,先将authorityList置空[代码][代码] [代码][代码]that.authorityList = [][代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]// 云函数名[代码][代码] [代码][代码]name: [代码][代码]'authorityList'[代码][代码],[代码][代码] [代码][代码]// 传入云函数的参数[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]// 查询的默认筛选条件,这里可以参考下面留言审核对应的两张图来看,左上角有个switch开关[代码][代码] [代码][代码]// 当开关开启时,filter:{show:false}生效[代码][代码] [代码][代码]filter: that.checkFlag ? {[代码][代码] [代码][代码]show: [代码][代码]false[代码][代码] [代码][代码]} : [代码][代码]null[代码][代码],[代码][代码] [代码][代码]// 查询页数[代码][代码] [代码][代码]pageNum: that.pageNum,[代码][代码] [代码][代码]// 每页条数[代码][代码] [代码][代码]pageSize: that.pageSize[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]// 配合下拉刷新使用,作用是停止刷新事件[代码][代码] [代码][代码]wx.stopPullDownRefresh()[代码][代码] [代码][代码]// 以下动作为赋值操作[代码][代码] [代码][代码]const temp = res.result[代码][代码] [代码][代码]that.total = temp.total[代码][代码] [代码][代码]that.hasMore = temp.hasMore[代码][代码] [代码][代码]that.authorityList = temp.data[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 上面代码对应实例如下:1.查询未通过审核的留言;2.查询全部的留言 [图片][图片] 增(添加数据)[代码] [代码] [代码] // 前面讲解过的注释之后的代码将不重复说明[代码] [代码] const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码][代码] cloud.init()[代码][代码] const db = cloud.database()[代码][代码] exports.main = async (event, context) => {[代码][代码] [代码][代码]const dbName = [代码][代码]'message'[代码][代码] [代码][代码]// 添加数据[代码][代码] [代码][代码]return[代码] [代码]db.collection(dbName).doc(event.id).add({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]desc: event.desc,[代码][代码] [代码][代码]type: event.type,[代码][代码] [代码][代码]show: event.show,[代码][代码] [代码][代码]time: event.time,[代码][代码] [代码][代码]url: event.url,[代码][代码] [代码][代码]name: event.name[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] [代码][代码][代码] [代码] [代码] sendMessage () {[代码] [代码] [代码][代码]const that = [代码][代码]this[代码][代码] [代码][代码]if[代码] [代码](that.desc) {[代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]// 云函数名[代码][代码] [代码][代码]name: [代码][代码]'addMessage'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]desc: that.desc,[代码][代码] [代码][代码]type: [代码][代码]'message'[代码][代码],[代码][代码] [代码][代码]show: [代码][代码]false[代码][代码],[代码][代码] [代码][代码]time: utils.getNowFormatDate(),[代码][代码] [代码][代码]url: that.userInfo.avatarUrl,[代码][代码] [代码][代码]name: that.userInfo.nickName[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]// 关闭所有页面,打开到应用内的某个页面,跳转到留言列表页[代码][代码] [代码][代码]wx.reLaunch({[代码][代码] [代码][代码]url: [代码][代码]'/pages/message/main'[代码][代码] [代码][代码]})[代码][代码] [代码][代码]})[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]tools.showToast([代码][代码]'说点什么吧~'[代码][代码])[代码][代码] [代码][代码]}[代码][代码] }[代码] [代码] [代码] [代码][代码] 对应实例如下: [图片][图片] 改(修改数据)[代码] [代码] [代码] const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码] [代码] cloud.init()[代码][代码] const db = cloud.database()[代码][代码] exports.main = async (event, context) => {[代码][代码] [代码][代码]const dbName = [代码][代码]'message'[代码][代码] [代码][代码]return[代码] [代码]db.collection(dbName).doc(event.id).update({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]show: event.show[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] [代码] [代码] [代码] switchMessage (e) {[代码] [代码] [代码][代码]const that = [代码][代码]this[代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]name: [代码][代码]'switchMessage'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]id: e.mp.target.dataset.id,[代码][代码] [代码][代码]show: e.mp.detail.value[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]if[代码] [代码](res.result.errMsg === [代码][代码]'document.update:ok'[代码][代码]) {[代码][代码] [代码][代码]that.getList()[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 对应实例如下:(前面没使用云函数也实现了相同的功能,感兴趣的可以对比查阅) [图片] 删(删除数据)[代码] [代码] [代码] const cloud = require([代码][代码]'wx-server-sdk'[代码][代码])[代码] [代码] cloud.init()[代码][代码] const db = cloud.database()[代码][代码] exports.main = async (event, context) => {[代码][代码] [代码][代码]const dbName = [代码][代码]'message'[代码][代码] [代码][代码]return[代码] [代码]db.collection(dbName).doc(event.id).remove()[代码][代码] }[代码] [代码] [代码] [代码] [代码] [代码] deleteItem (id) {[代码] [代码] [代码][代码]// 记录this指向[代码][代码] [代码][代码]const that = [代码][代码]this[代码][代码] [代码][代码]// 这里之所以使用wx.showModal是防止误操作[代码][代码] [代码][代码]wx.showModal({[代码][代码] [代码][代码]title: [代码][代码]'提示'[代码][代码],[代码][代码] [代码][代码]content: [代码][代码]'你确定要删除这条留言?'[代码][代码],[代码][代码] [代码][代码]success (res) {[代码][代码] [代码][代码]if[代码] [代码](res.confirm) {[代码][代码] [代码][代码]wx.cloud.callFunction({[代码][代码] [代码][代码]name: [代码][代码]'deleteMessage'[代码][代码],[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]id[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}).then(res => {[代码][代码] [代码][代码]if[代码] [代码](res.result.errMsg === [代码][代码]'document.remove:ok'[代码][代码]) {[代码][代码] [代码][代码]that.getList()[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] }[代码] [代码] [代码] 对应实例如下: [图片] 总结掌握上面两种对应的增删改查后,相信大家对云开发会有一个更清晰的认识,也希望大家多多使用云开发做出更多好玩的小程序作品; 如果觉得看完这篇文章让你有想尝试使用云开发的冲动,请不要吝啬你的赞,有什么问题欢迎留言,一起交流学习。 对应小程序 欢迎大家体验: [图片] 日记小程序 [图片] 小程序订制加本人微信:huangbin910419 可按给定UI图订制
2019-11-06 - 【小程序取值和传值】—你也可能遇到的坑系列
前言 小程序真的很好用,非常的便捷,并且我们可以很轻松的开发属于自己的一款小程序,但是在我们开发写代码的时候难免会遇到一些小坑,然后就是各种的疑问???我整理一些我遇到的坑,说不定你也遇到过哈哈哈 取值和传值 在我们开发一个程序的时候,大概率会涉及到要得到一些节点的值或是需要在页面跳转的时候传一些值过去以完成一些事情,我总结三点 普通的取值 页面传值 from表单取值 虽然看上去很简单但是偶尔会有一些小坑等着我们 普通的取值 通常情况下我们都是先给组件绑定事件,按照文档的说法,如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。 比如我们给一个组件绑定了一个点击事件叫getValue,然后我们有一个函数如下,其中event就是事件对象 [代码]<view bindtap="getValue"></view> [代码] [代码]getValue:function(event){ // do someing } [代码] 这个event事件对象是基本的对象事件他其中包括了一些属性,对于我们获得想要的值很重要 [图片] 🆗,到这之后我们可以知道target,和currentTarget对我们很重要了,所以接下来继续看看文档给了我们什么信息 target [图片] currentTarget [图片] 初看可能觉得两个没什么区别,但是这就是坑啊,这两个是有一些区别的,target是触发事件的源组件,也就是说你在button上绑定一个事件那么target就是指向这个button不会变的,而currentTarget就不一样,它指向的是触发事件监听的对象, 注意理解 触发事件监听”的对象与“添加(注册)监听事件”的对象是不一样的!前者是能够触发该事件但没有绑定事件,后者指绑定了事件 填坑 1、如果绑定的事件所在组件没有子元素,则用e.target===e.currentTarget一样; 2、如果事件绑定在父元素中,且该父元素有子元素,当用e.currentTarget时,不管点击父元素所在区域还是子元素(当前事件),都正确执行,若用e.target时,点击父元素所在区域无错,点击子元素区域,执行报错,报错的原因是事件没绑定在子元素上,是在父元素上,子元素要用e.currentTarget才正确 上面内容中有引用此博客 https://blog.csdn.net/syleapn/article/details/81289337 官方文档传送 https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html 上面说了很多,但是还没有说到底怎么取值,其实看完上面也差不多了,下面需要提的是dataset 文档的解释: 在组件中可以定义数据,这些数据将会通过事件传递给 SERVICE。 书写方式: 以data-开头,多个单词由连字符-链接,不能有大写(大写会自动转成小写)如data-element-type,最终在 event.currentTarget.dataset 中会将连字符转成驼峰elementType。 简单的理解就是我们可以把想要获取的值通过data-定义到组件上,当我们在触发一个事件的时候我们可以通过event.currentTarget.dataset.XX来获取 [代码]// 像这样 <view> <button bindtap='info' data-value='5555'>点击</button> </view> [代码] [代码]// e和event等价 info:function(e){ console.log('value:'+e.currentTarget.dataset.value) } [代码] 结果:当我们点击按钮的时候就可以得到我们预先定义好的值了 页面传值 页面传值的使用还是比较多的,特别是我们需要做一些详情页面的时候,经常涉及到需要将上一个页面的某一些值带到第二个页面来。这个坑要少一点,我们一步一步来说吧 首先说跳转吧 仅有两种,其他方法欢迎讨论 wx.navigateTo(Object object) navigator 传参数 虽然有两种跳转的方式,但是它们传递参数的方式是一样的,url后拼接?id(参数名字)=要传递的值 注意 (如果多个参数用&分开 &name=value&…….) [代码]<navigator url="pages/detail/detail?value="123"> 跳转 </navigator> [代码] [代码]wx.navigateTo({ url:"pages/detail/detail?value="123", success: function (res) { // success }, fail: function () { // fail }, complete: function () { // complete } }) [代码] 获得参数 在跳转到的界面的一些生命周期的函数中有一个options,它是包含url地址中参数的对象,可以通过它直接点获取。 [代码]onLoad:function(options){ console.log(options.value) } [代码] from表单取值 这里首先要铺垫一下deatil deatil是event事件对象的一个属性,它包括一些额外的信息 ok,接下来要说取值了,常规的做法是通过 <form bindsubmit=“formSubmit”> 与 <button formType=“submit”> 标签配合使用,然后给input一个name属性,我们在js中就可以使用e.deatil.value.name来获取了 [代码]<form bindsubmit="formSubmit"> <input name="detail" placeholder="详情地址" /> <input name="realname" placeholder="收件人姓名" /> <input name="mobile" placeholder="手机号码" type="number"/> <button formType="submit" type="primary">Submit</button> </form> [代码] 对新手来说这里有一点小坑,新手可能会有疑惑 为什么没有给button绑定事件呢,是不是需要在绑定一个事件,其实不用,<form bindsubmit=“formSubmit”> 已经绑定了事件,我们在 js 中只需要写一个叫 formSubmit 的函数就好了 [代码]formSubmit: function(e) { // detail var detail = e.detail.value.detail; // realname var realname = e.detail.value.realname; // mobile var mobile = e.detail.value.mobile; } [代码] 此处有引用 https://www.cnblogs.com/lrgupup/p/7609118.html 结语 好记性不如烂笔头,记录一下自己犯过的一些小错❤
2020-03-24 - 怎么在api中调用自定义组件showModal,让自定义组件想wx.showModal一样使用
环境:我们为什么要在api中调用自定义组件的原因我就不说了,用得到的开发者自然用得到! 现在百度上有很多人都写了自定义组件showModal,但是有一个很致命的缺陷,不能像微信小程序的api那样使用(wx.showModal)。 话不多说上代码 css: .mask { position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.4); z-index: 9999;} .modal-content { display: flex; flex-direction: column; width: 85%; padding: 10rpx; background-color: #fff; border-radius: 15rpx;} .title { font-size: 40rpx; text-align: center; padding: 15rpx;} .modal-btn-wrapper { display: flex; flex-direction: row; height: 100rpx; line-height: 100rpx; border-top: 2rpx solid rgba(7, 17, 27, 0.1);} .cancel-btn, .confirm-btn { flex: 1; height: 100rpx; line-height: 100rpx; text-align: center; font-size: 32rpx;} .cancel-btn { border-right: 2rpx solid rgba(7, 17, 27, 0.1);} .main-content { flex: 1; height: 100%; overflow-y: hidden;} wxml: <view class='mask' wx:if='{{show}}'> <view class='modal-content'> <view class="title">{{title}}</view> <view>{{content}}</view> <slot></slot> <view class='modal-btn-wrapper'> <view class='cancel-btn' bindtap='cancel' wx:if="{{showCancel}}" style="color:{{cancelColor}}">{{cancelText}}</view> <view class='confirm-btn' bindtap='confirm' style="color:{{confirmColor}}">{{confirmText}}</view> </view> </view></view> js:Component({ /** * 组件的属性列表 */ properties: { title: { type: String, value: '温馨提示' }, content: { type: String, value: '是否导入最近一次刷题记录?' }, //是否显示取消按钮 showCancel: { type: Boolean, value: true }, //取消按钮文字 cancelText: { type: String, value: '取消' }, //取消按钮颜色 cancelColor: { type: String, value: '#000000' }, //确定按钮的文字 confirmText: { type: String, value: '确定' }, //确定按钮的颜色 confirmColor: { type: String, value: '#FECC34' }, //是否显示modal show: { type: Boolean, value: false }, }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { // 取消函数 cancel() { this.setData({ show: false }) var res = {}; res["confirm"] = true; this.data.success && this.data.success(res); }, // 确认函数 confirm() { this.setData({ show: false }) var res = {}; res["confirm"] = false; this.data.success && this.data.success(res); }, showModal({ title, content, showCancel, //是否显示取消按钮 cancelText, //取消按钮文本 cancelColor, //取消按钮颜色 confirmText, //确定按钮文本 confirmColor, //确定按钮颜色 success }) { this.setData({ show: true }); if (title) { this.setData({ title: title }) } if (content) { this.setData({ content: content }) } if (showCancel) { this.setData({ showCancel: showCancel }) } if (cancelText) { this.setData({ cancelText: cancelText }) } if (cancelColor) { this.setData({ cancelColor: cancelColor }) } if (confirmText) { this.setData({ confirmText: confirmText }) } if (confirmColor) { this.setData({ confirmColor: confirmColor }) } this.data.success = success; } }})[图片] 以上是自定义组件的封装,因为CSS和html是随便百度复制的,太简单就不想改了,你们自己把样式改一下就OK 使用方法: [图片][图片] [图片] var toast = that.selectComponent('#toast'); toast.showModal({ title: '温馨提示', content: '是否导入最近一次刷题记录?', showCancel: true, confirmText: "导入", confirmColor: "#FECC34", success: function(result) { console.log(result) } });如果想自己的这个自定义组件在自己的挨批中使用就把对应页面的this传递到对应的api方法中去,然后在api中调用
2020-03-30 - 制作自己的模态弹窗组件 - vue篇
起因 H###s:#满,微信小程序有模态弹窗吗? #满:有啊,喏。wx.showModal(随手甩出一个链接) 短暂的几十秒后… H###s:不对啊,我要的不是这种 #满:那你要哪种? H###s:就是那种组件,可以自己在里面写wxml代码的。 #满:组件?那没有 H###s:那难顶哦,有这个需求 祺##:自己写一个呗,又不难 因为我一般使用 kbone - vue ,所以这里也拿 vue 做示例。另外,使用 typescript 语法,请使用 javascript 的童鞋自行理解~ 最终效果 [图片] 编写过程 我们先创建一个空的 vue 组件,我这里命名为 Modal [图片] [图片] 组件创建好了,随便写点东西,然后引入到页面,看看对不对吧 组件: [图片] 页面: [图片] 实际效果: [图片] OK,组件引入正确,但是这也只是一个组件,并不是模态弹窗。众所周知,模态弹窗,是一个弹出框,应该浮动到最上层,那就加一点 css 吧 组件: [图片] 实际效果: [图片] emmmm,有点丑,不过意思是这个意思,接下来,让弹出框水平垂直居中,再给点宽度高度吧 组件: [图片] 实际效果: [图片] 是不是有内味儿了?但毕竟是模态弹窗组件,显示什么内容应该由页面来决定,所以,slot 走起! 组件: [图片] 页面: [图片] 实际效果: [图片] OK,那既然是模态弹窗,那就代表可以关闭打开,其实我个人推荐的是在页面里面使用 v-if / v-show 来控制,让组件干净一点 页面: [图片] 实际效果: [图片] [图片] OK,既然打开有了,接下来就是关闭了 组件: [图片] 页面: [图片] 行了,关闭打开也有了,再搞点小东西 组件: [图片] 页面: [图片] 实际效果: [图片] 完成 简单的模态框做完啦!怎么样?似8似很简单! **考虑到会有一些组件的层级,并且设置 z-index 无效,组件的外部标签可修改为 [代码]cover-view[代码] ** [图片] 结束 H###s:真的诶!超简单的,感谢你的组件了(白嫖成功)!
2019-12-26 - 将小程序原生异步函数promisify后,在async/await中使用
目前,小程序中支持使用async/await有三种模式: 1、不勾选es6转es5,不勾选增强编译;该模式是纯es7的async/await,需要基础库高版本。 2、勾选es6转es5,勾选增强编译;一般是因为调用了第三方的es5插件,通过增强编译支持async/await。 3、勾选es6转es5,不勾选增强编译;手工引入runtime.js支持async/await。 据最近更新情况,原生的函数已经大部分同时原生支持同步化了,不需要本方案转化了,直接加上await即可;比如wx.chooseImage、wx.showModal。。。具体有哪些,可以自己试。 如果只是wx.request的同步化,可参考: https://developers.weixin.qq.com/community/develop/article/doc/0004cc839407a069f77a416c056813 app.js代码: function promisify(api) { return (opt, ...arg) => { return new Promise((resolve, reject) => { api(Object.assign({}, opt, { success: resolve, fail: reject }), ...arg) }) } } App({ globalData: {}, chooseImage: promisify(wx.chooseImage), request: promisify(wx.request), getUserInfo: promisify(wx.getUserInfo), onLaunch: function () { }, }) 某page的index.js代码: const app = getApp() testAsync: async function(){ let res = await app.chooseImage() console.log(res) res = await app.request({url:'url',method:'POST',data:{x:0,y:1}}) console.log(res) }, [图片]
2020-10-20 - 小程序能否在 数据库查询时,在 success 中同时嵌套 更新update 或 新增add操作?
有一个应用要统计要对 用户记录实现 积分更新,如果是 新用户积分表中没有该用户记录则需新增。 能否实现按用户名 where查询,如果 get 后 seccess,则对该用户的相应积分查行更新。 若查询不到该用户名的记录,则直接新建一条 该用户的积分记录。 若 小程序的 collect 的 get方法不能直接 嵌套 对同一数据表 实现更新 和 新增记录的操作。 那么小程中,较为合理的实现流程应怎样处理,能否提供 伪算法。 我目前的算法里,为解决异步问题,在 页首的 data中放入一个标记量表示 该用户是否存在, 第1步先根据用户名,做一次查询,设置用户是否存在标记量 第2步如果用户存在,则要再访问一次数库,进行 更新用户记录,若用户不存在则 新建用户记录。 这样实现须要进行 2次数据库访问,请问我目前的这种操作是否 必须,能否改进? [图片]
2020-04-22