前言
小程序快速入门开发可看:《微信小程序开发快速入门之码仔备忘录系列教程》
从上次完成了码仔备忘录本地版本后,码仔就养成了每天记录备忘录的好习惯,每周早上会记录下自己要做的任务,然后晚上在复盘一下今天的计划是否完成。
有一天,码仔看到它最喜欢的码妞在一旁愁眉苦脸。
码仔:“怎么了?”
码妞:“工作事物太多了,总是忘记工作上的一些事情”
码仔心里暗喜,这不是和我之前一样吗?让她用用我的码仔备忘录,她一定会觉得我很厉害,hahahaha。
码仔:“我教你一个方法”
码妞:“什么方法?”
码仔:“用我的码仔备忘录”
于是码妞用上了码仔备忘录进行工作日志记录,突然有一天码妞非常生气的跑到码仔身边。
码妞大声骂道:“死猴子,我就知道你写的东西不靠谱,我今天换了个手机,然后再次打卡你的备忘录小程序,结果数据都没有了!”
码仔解释道:“目前只支持单机版,换手机小程序本地缓存就没了,所以的记录就没有了。”
码仔为了解决码妞数据问题,当天码仔熬夜查资料。
终于找到了小程序云开发!码仔打算将把备忘录的本地数据存储在云端。
在使用云开发之前,让我们先来了解一下云开发的一些基本信息,以便我们后续更好的学习云开发。
什么是云开发?
云开发是可以帮助我们快速成为全栈的一种后端云服务,采用的是Serverless的架构。开发者无须搭建服务器,可直接使用其中的云数据库、云存储、云函数等云服务基础功能。
那么这个时候你可能会想,这和现在的传统开发模式有什么区别呢?下面通过现实生活中盖房子的过程来了解传统开发模式与云开发模式的区别。
如果传统方式来盖房子需要6步:
1)将房子的地基打好,设计整理结构。(后端云服务基础架构)
2)打地梁,地梁也是决定房子是否稳固的关键。(搭建云数据库)
3)主体的砌筑,将房子的基本结构盖好。(搭建云存储)
4)在步屋内填上土,砸夯,将地面砸实。(搭建云函数)
5)在屋顶铺上水泥,封实。(提供静态托管和扩展能力)
6)装修成自己喜欢的风格。(编写业务需求逻辑代码)
而云开发模式只有一步,即装修成自己喜欢的风格。可以看到图1-1展示的是传统开发需要管理的基础资源与云开发需要管理的基础资源的对比,可以帮助我们更清晰的了解两者之间的差距。
图1-1 传统开发需要管理的基础资源VS云开发需要管理的基础资源
就这一步即可。云开发提供完整的后端云服务,提供数据库、存储、函数、静态托管等基础能力,以及扩展能力,无须管理基础架构。相比较传统的开发模式,云开发至少可节省50%的人力成本、交付效率提升70%。
实战云开发改造
码仔为了获取码妞肯定,码仔下定决心,决定通宵也要搞定小程序云开发!
云开发模式调整
在此之前我们需要把原来码仔备忘录项目改成云开发项目,所以需要先把项目从项目管理中先删除。(如果是直接新建的云开发项目可以忽略这一步)
首先,先进入小程序项目管理页面:
然后点击右上角「管理」按钮进入该页面的管理状态。
然后选中你之前的单机版本勾选进行删除即可。
接下来我们在导入项目界面对云开发进行勾选即可。
这个时候进入我们会发现小程序开发工具自动给我们生成了一套云开发Demo,我们先直接处理掉。
首先进入 project.config.json 文件,把 “miniprogramRoot”: “miniprogram/” 修改成 “miniprogramRoot”: “/”,这样小程序的页面目录就是从根目录读取。然后我们会看到日志区域会出现错误日志。
意思就是找不到小程序云开Demo代码的里面的一些模块,这个不用理会直接先把Demo代码全部删除。
删除后我们就可以看到正常的页面渲染了。
回到正常状态下,我们接下来要改造的就是数据操作这块的业务。
数据缓存改造云开发
在改造之前我们先要梳理思路。
- 首先我们先通过之前学习过抽象方法。
- 把所有和数据操作相关的方法都抽取到一个js类里面,这样便于维护。
- 将所有的方法替换成云数据库的操作函数。
抽象数据操作API
我们在根目录下面新增 api 的文件夹,然后在其文件夹下面新增一个 memo.js 用于存放所有备忘录的操作API。
梳理下有哪些方法:
- 获取备忘录列表
- 获取备忘录详情
- 新增备忘录信息
- 删除备忘录信息
- 修改备忘录信息
先给相关的方法取好函数名称,然后进行导出声明。
memo.js 完整代码
// 获取备忘录列表
function getMemoList() {
}
// 获取备忘录详情
function getMemoInfo() {
}
// 新增备忘录信息
function addMemoInfo() {
}
// 删除备忘录信息
function deleteMemoInfo() {
}
// 修改备忘录信息
function updateMemoInfo() {
}
// 导出声明
export {
getMemoList,
getMemoInfo,
addMemoInfo,
deleteMemoInfo,
updateMemoInfo
}
首先我们来实现获取备忘录列表,在列表页面 onShow 函数中会被调用。
// 初始化数据
onShow() {
let list = wx.getStorageSync('list') || []
this.udpateList(list)
},
// 更新列表数据
udpateList(list){
this.setData({
list: list,
isEmpty:!list.length>0
})
},
在这个时候setData是页面操作函数,所以我们要抽象的部分是:
wx.getStorageSync('list') || []
接下来我们就把这段代码放在memo.js里面去。
memo.js 关于 getMemoList 的代码片段
// 获取备忘录列表
function getMemoList() {
return wx.getStorageSync('list') || []
}
然后替换成列表页面的调用,先引入memo的获取 getMemoList 函数。
list.js 头部引入代码片段
import {
getMemoList
} from '../../api/memo.js'
引入后在 onShow 函数中使用
在 onShow 所有代码片段
onShow() {
let list = getMemoList()
this.udpateList(list)
}
采取同样的方式,我们可以把之前的获取详情、删除、修改都抽象到 memo.js 中:
memo.js 所有代码
// 获取备忘录列表
function getMemoList() {
return wx.getStorageSync('list') || []
}
// 获取备忘录详情
function getMemoInfo(index) {
const list = getMemoList()
return list[index]
}
// 新增备忘录信息
function addMemoInfo(data) {
const list = getMemoList()
list.unshift(data)
wx.setStorageSync('list', list)
}
// 删除备忘录信息
function deleteMemoInfo(index) {
const list = getMemoList()
list.splice(index, 1)
wx.setStorageSync('list', list)
return list
}
// 修改备忘录信息
function updateMemoInfo(index,data) {
const list = getMemoList()
list.splice(index, 1)
wx.setStorageSync('list', list)
addMemoInfo(data)
}
// 导出声明
export {
getMemoList,
getMemoInfo,
addMemoInfo,
deleteMemoInfo,
updateMemoInfo
}
在相应业务场景进行调用,调用之前都要先引入方法才行。由于引入代码相对重复,下面就不贴引入代码了,只贴使用代码。
删除代码方法调用:
list.js 中 del 函数所有代码
// 删除
del(event) {
let that = this
let index = event.currentTarget.dataset.index
wx.showModal({
title: '提示',
content: '你确定删除?',
success(res) {
if (res.confirm) {
// 修改部分
const list = deleteMemoInfo(index)
that.udpateList(list)
}
}
})
}
查看备忘录详情调用:
edit.js 中 onLoad 函数所有代码
onLoad: function (options) {
if (options.index) {
// 修改部分
let item = getMemoInfo(options.index)
this.setData({
title: item.title,
content: item.content,
nowDate: item.date,
nowTime: item.time,
index: options.index
})
}
},
新增和删除方法调用:
edit.js 中 save 函数中的关于保存和修改的代码片段
// 修改部分
if (this.data.index) {
// 修改逻辑
updateMemoInfo(this.data.index,data)
}else{
// 新增逻辑
addMemoInfo(data)
}
到这里我们就完成了第一步,先抽取数据操作对象,然后我们要进行云数据库到操作了。
操作云数据库
初始化数据库
首先我们先进入云开发控制台。
然后选中「数据库」。
云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。
我们会发现没有可用的集合,再次新建一个集合 memo,用于存放备忘录数据。
新增一条数据
我们先手动新增一条测试数据看看,点击「添加记录」。
{
"title": "我是标题",
"content": "我是内容",
"date": "2021-07-22",
"time": "03:56"
}
我们用以上测试数据添加进去
除此之外我们可以看到可以添加多种不同类型的数据,云开发数据库提供以下几种数据类型:
- String:字符串
- Number:数字
- Object:对象
- Array:数组
- Bool:布尔值
- Date:时间
- Geo:多种地理位置类型
- Null
我们先默认都用 string 类型的数据,新增完成之后就可以做memo集合中看到一条数据了。
数据表已经有数据了,那么我们怎么获取?
获取数据库数据
我们回到小程序编辑台,对我们的获取数据方法进行下改造。
// 获取备忘录列表
function getMemoList() {
return wx.getStorageSync('list') || []
}
这个是获取本地的数据方法,下面是获取数据库方法
// 获取备忘录列表
function getMemoList() {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
db.collection('memo').get({
success: function (res) {
// 3. 使用数据
console.log(res)
}
})
}
当我们去列表页面进行调用的时候发现报错:
原来是在此之前还需要进行云API的初始化。
那么现在问题来了,我们后续会遇到大量的调用云API,那么都需要一开始初始化,并且这个初始化只需要一次,这个时候初始化我们应该在哪里进行调用呢?
当一个函数应用一开始就需要调用的时候并且只需要调用一次,我们可以在app.js的 onLaunch 生命周期中调用。
app.js 的 onLaunch 函数代码片段
// app.js
App({
onLaunch() {
wx.cloud.init()
}
})
接下来在看调用看看,在这里告诉大家一个调试技巧,使用调试器中的Network面板,然后选中Cloud标签进行过滤,在这里我们就可以刚才调用API请求。
先看看请求状态,从状态来看是成功的请求。
然后我们再来看看返回的数据:
明明有一条数据,为什么获取不到呢?
因为云开发数据库集合读取权限问题。默认我们新建的集合读取权限是仅创建则可以读写,第一条测试数据是我们手动录入的,所以没有创建者,默认权限于是就读取不到。
我们可以来到云开发控制面板,然后找到 数据库=> 数据权限。
我们把权限从「仅创建者可读写」修改为「所有用户可读,仅创建者可读写」试试看。修改完成之后再次调用获取方法,看到返回结果中出现了这条测试数据。
虽然数据出来了,但是我们会发现还有一个错误日志。
具体代码块:
原因:在没有使用云API之前我们使用的缓存操作API是同步操作返回了具体数据,而修改后的获取数据方法是异步函数,没有给到数据到列表页面进行使用。最终导致list数据对象为空,list的length自然就出现了undefined的错误提示。
从数据库获取数据除了可以用以上的实现 success 方法,同样还支持可以用 Promise 风格调用:
可以简单理解成将包裹的回调函数调用方式改成了链式调用的回调函数
db.collection('todos').doc('todo-identifiant-aleatoire').get().then(res => {
// res.data 包含该记录的数据
console.log(res.data)
})
基于 Promise 风格调用,我们可以将函数拆解成两部分。接下来再改造一下,传递数据的部分代码:
- 将查询代码在 getMemoList 完成
// 获取备忘录列表
function getMemoList() {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
return db.collection('memo').get()
}
- 将查询结果在列表页面进行列表渲染
onShow() {
getMemoList().then(res => {
this.udpateList(res.data)
})
}
这样我们就成功的将数据库的数据显示出来了
接下来,我们就来把所有方法进行云API的实现。
查看数据详情
在这里我们就不能用index下标来,通常与后端交互我们都会采用ID来进行查询,每条数据都会生成一个ID字段,字段名称为_id。
那么我们就需要需改之前组件传递参数以及获取的字段和页面之前的参数字段:
list.wxml 代码片段,其中 data-id="{{item._id}}" 为修改部分
<view bindtap="toEdit" bindlongtap="del" data-id="{{item._id}}" class="list" wx:for="{{list}}">
<view>
<text class="list-title">{{item.title}}</text>
<text class="list-date">{{item.date}} \n {{item.time}}</text>
</view>
<view class="list-content">{{item.content}}</view>
<view class="line"></view>
</view>
获取id进行页面传递
// 去编辑
toEdit(event) {
let id = event.currentTarget.dataset.id
if (id) {
// 从列表点击
wx.navigateTo({
url: '/pages/edit/edit?id=' + id,
})
} else {
// 从新建按钮点击,省略相关代码
}
}
查询方法改造,由于通过id查询是非常常用的方法,所以云API直接有个doc方法传入id即可查询:
memo.js 中 getMemoInfo 函数代码
// 获取备忘录详情
function getMemoInfo(id) {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
return db.collection('memo').doc(id).get()
}
查询方法调用:
edit.js 中 onLoad 函数代码
onLoad: function (options) {
if (options.id) {
// 显示已有数据
getMemoInfo(options.id).then(res=>{
console.log(res)
const item = res.data
this.setData({
title: item.title,
content: item.content,
nowDate: item.date,
nowTime: item.time,
id: options.id
})
})
}
}
新增备忘录数据
新增方法改造:
memo.js 中 addMemoInfo 函数
// 新增备忘录信息
function addMemoInfo(data) {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
return db.collection('memo').add({data})
}
调用方法:
edit.js 中 save 函数新增逻辑代码片段
// 新增逻辑
addMemoInfo(data).then(res=>{
console.log(res)
wx.navigateBack()
})
新增成功后会返回新增数据的id信息。
然后在数据库当中就可以看到新增的数据了。
我们会发现手动新增的数据和使用云API新增的函数新增的数据不一样,这条数据多了一个“_openid”的字段。这个 _openid 是代表当前用户的唯一识别,每个用户在一个小程序中 _openid 就是代表这个用户,在不同小程序中 _openid 是不一样的。
小知识:小程序也是通过这个_openid字段来区别这条数据是不是当前用户的,我们可以做个实验。我们将memo集合的数据库权限调整成「仅创建者可以读写」 。
然后在看下列表返回的数据,只有自己新增的那条数据了。
修改备忘录数据
修改方法改造:
memo.js 中 updateMemoInfo 函数。
// 修改备忘录信息
function updateMemoInfo(id, data) {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
return db.collection('memo').doc(id).update({data})
}
之前修改的逻辑是采用的删除之后再新增做的“假”修改,这次我们直接采用修改方法。
edit.js 中 save 函数修改逻辑代码片段
// 修改逻辑
updateMemoInfo(this.data.id,data).then(res=>{
console.log(res)
wx.navigateBack()
})
返回结果中的 updated 是代表修改成功数据的数量
删除备忘录数据
删除方法改造:
memo.js 中 deleteMemoInfo 函数
// 删除备忘录信息
function deleteMemoInfo(id) {
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 找到集合获取数据
return db.collection('memo').doc(id).remove()
}
调用方法:
list.js 中 del 函数
// 删除
del(event) {
let that = this
let id = event.currentTarget.dataset.id
wx.showModal({
title: '提示',
content: '你确定删除?',
success(res) {
if (res.confirm) {
const list = deleteMemoInfo(id).then(res=>{
console.log(res)
getMemoList().then(res => {
that.udpateList(res.data)
})
})
}
}
})
}
在这里要注意的是删除之后还需要调用查询所有数据方法对列表更新。
返回值 removed 为删除数量。
总结
在本小节我们使用云API在小程序端操作了云数据库,学习了以下函数:
- 使用 get() 进行了数据库的查询
- 使用 add() 进行了数据添加
- 使用 update() 进行了数据修改
- 使用 remove() 进行了数据删除
在这里我们就已经完成了使用云数据库来存储备忘录数据,码仔以后再也不用数据丢失问题了。于是码仔拿着这个云数据版本的备忘录给码妞使用去了,得到了码妞的赞许。
这里有个问题要请教了,比如提交的时候,如果有一些校验规则,这些逻辑本来是要在服务端完成的,那目前是不是只能写在js这一侧了,比如一天最多创建10个备忘
大佬,对不住了,跑评论这来请教,帮忙看下这个问题,由于画布需要缩小到30%,导致旋转角度出现问题,不知怎么弄了,麻烦给指点一下!
<!--wxml --> <view style="width:414px;height:500px;position: absolute;" bindtap="cancel"> <view id="test" style="background-color:#1ea5c7;width: {{1080}}px;height: {{1080}}px;position: relative;transform: scale({{0.3}});transform-origin:center;left:50%;margin-left:-{{540}}px;top:50%;margin-top: -{{540}}px;" catchtap=""> <view class='contentWarp'> <block wx:for="{{itemList}}" wx:key="index"> <view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; z-index:{{item.active?100:1}}'> <view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg); border: {{item.active?0*item.oScale:0}}rpx #fff dashed;"> <image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd'></image> <image class='o' src='../../images/o.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='oTouchStart' bindtouchmove='oTouchMove' bindtouchend='WraptouchEnd'></image> </view> </view> </block> </view> </view> </view> let index = 0, items = [], flag = true, itemId = 1; Page({ data: { itemList: [], }, onLoad: function (options) { items = this.data.itemList; this.setDropItem({ url: '/images/2.png' }); }, // 初始化数据 setDropItem(imgData) { let data = {} wx.getImageInfo({ src: imgData.url, success: res => { data.width = res.width; //宽度 data.height = res.height; //高度 data.image = imgData.url; //地址 data.id = ++itemId; //id data.top = 300; //top定位 data.left =300; //left定位 //圆心坐标 // data.x=0; // data.y=0; data.x = data.left + data.width / 2; data.y = data.top + data.height / 2; data.scale = 1; //scale缩放 data.oScale = 1; //方向缩放 data.rotate = 0; //旋转角度 data.active = true; //选中状态 items[items.length] = data; this.setData({ itemList: items }) } }) }, // 放大旋转初始 oTouchStart(e) { //找到点击的那个图片对象,并记录 //获取作为移动前角度的坐标 items[index].tx = e.touches[0].clientX; items[index].ty = e.touches[0].clientY; //移动前的角度 items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty) //获取图片半径 }, // 放大旋转移动 oTouchMove: function (e) { //记录移动后的位置 items[index]._tx = e.touches[0].clientX; items[index]._ty = e.touches[0].clientY; //移动后位置的角度 items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty) //角度差 items[index].new_rotate = items[index].angleNext - items[index].anglePre; //叠加的角度差 items[index].rotate += items[index].new_rotate; items[index].angle = items[index].rotate; //赋值 //用过移动后的坐标赋值为移动前坐标 items[index].tx = e.touches[0].clientX; items[index].ty = e.touches[0].clientY; items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty) console.log(items[index].anglePre) //赋值setData渲染 this.setData({ itemList: items }) }, /* *参数1和2为图片圆心坐标 *参数3和4为手点击的坐标 *返回值为手点击的坐标到圆心的角度 */ countDeg: function (cx, cy, pointer_x, pointer_y) { var ox = pointer_x - cx; var oy = pointer_y - cy; var to = Math.abs(ox / oy); var angle = Math.atan(to) / (2 * Math.PI) * 360; // console.log("ox.oy:", ox, oy) if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系 { angle = -angle; } else if (ox <= 0 && oy >= 0) //左下角,3象限 { angle = -(180 - angle) } else if (ox > 0 && oy < 0) //右上角,1象限 { angle = angle; } else if (ox > 0 && oy > 0) //右下角,2象限 { angle = 180 - angle; } return angle; } })
附上代码片段
https://developers.weixin.qq.com/s/YVXlXWmQ7XJf