微信小程序云开发
一、云开发介绍
1.1 创建项目
- 创建项目需要使用真实 AppID
- 后端服务选择微信云开发
1.2 云开发项目目录介绍
- cloudfunctions 文件夹:存放云函数
- miniprogram 文件夹:与之前所学的项目类似
1.3 创建云开发环境
-
点击云开发–设置
-
第一次免费一个月的试用期限
-
如果不是第一次,点击环境名称,创建环境,购买最便宜的一个月19.9元
-
复制云环境id,修改app.js 中的 env
-
修改 env 之后,重启微信开发者工具,会发现项目的环境已经更换为新创建的环境
二、基础概念
2.1 云数据库
JSON 数据库,每条记录都是一个 JSON 格式的对象,一个数据库可以有多个集合(相当于关系型数据中的表)
2.2 云存储
云端存储图片,返回存储路径
2.3 云函数
三、云数据库
3.1 集合的初始化
- 手动创建集合,导入导出json数据
3.2 数据库查询操作
-
根据id查询数据
-
home.js
/** * 查询图书列表 */ bookList(){ //1. 数据库初始化 var db = wx.cloud.database(); //doc("id"):找到id对应的这行数据 //get():查询 db.collection("book").doc("de90ae1f64be1725008fd66276e4f140") .get({ //success:查询成功的回调函数 res:返回的结果 success:res=>{ console.log("hello world"); console.log(res); } }) },
注意:
- 直接查询手动插入的数据没有结果,需要修改集合的权限
-
-
修改集合的权限
-
云开发 ---- 存储 ---- 存储权限
-
也可修改为所有用户可读可写
-
选择自定义安全规则
-
{ "read": true, "write": true }
-
-
-
无条件查询所有图书
bookList(){ var db = wx.cloud.database(); db.collection("book").get({ success:res=>{ console.log(res.data); this.setData({ bookList:res.data }) } }) },
-
根据输入名称查询图书
/** * 根据输入的图书名称查询 */ bookByName(){ db.collection("book").where({ name:this.data.inputName }).get({ success:res=>{ console.log(res.data); this.setData({ bookList:res.data }) } }) },
3.3 添加图书
/**
* 添加图书
*/
addBook(){
db.collection("book").add({
data:{ //新增的数据
name:"三国演义",
publish:new Date()
},
success:res=>{
console.log(res);
}
})
},
3.4 修改图书
/**
* 修改图书
*/
updateBook(){
db.collection("book").doc("8043219a64be1f5800002a350892f70f").update({
data:{
name:"ABC123",
publish:"2023-07-24 15:02:03"
},
success:res=>{
console.log(res);
}
})
},
四、存储
4.1 上传头像
/**
* 上传图片
*/
uploadImage(){
//选择图片或视频
wx.chooseMedia({
count:1,//上传图片或视频的个数,
mediaType: ['image','video'],//文件类型
sourceType: ['album', 'camera'],//来源
camera: 'back',
success:res=>{
//获取选择图片的临时路径
var tempFilePath = res.tempFiles[0].tempFilePath;
//将临时文件路径上传到云存储中
wx.cloud.uploadFile({
//获取日期时间作为文件名称
cloudPath: new Date().toLocaleString()+".png",//云存储中的路径
filePath:tempFilePath,//临时路径
success:res=>{
this.setData({
imgSrc:res.fileID
})
}
})
}
})
},
五、云函数
5.1 云函数介绍
一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中,云函数后端 SDK 搭配使用多种服务,比如使用云函数 SDK 中提供的数据库和存储 API 进行数据库和存储的操作
-
传统的服务端开发: 客户端、浏览器、小程序----接口发请求---->后端【服务端】----->数据库
-
小程序云开发:小程序--------->运行在云端 Node.js 的云函数------>云数据库
5.2 创建并调用云函数
- 右键 cloudfunctions 文件夹,并选择新建 Node.js 云函数
- config.json:云函数的一些配置
- index.js:函数核心内容
- package.json:描述云函数的信息
- 修改完 index.js 文件后,右键该文件选择云函数增量上传:更新文件
-
编写查询所有的云函数
//初始化数据库 const修饰的变量不能被修改 const db = cloud.database(); // 云函数入口函数 exports.main = async (event, context) => { //查询book表所有数据并返回 //这里不使用success接收,而是直接返回,供调用此函数的小程序接收 return await db.collection("test").get() }
- 修改完云函数一定记得更新文件
-
小程序中调用
/** * 调用云函数的查询 */ cloudFunction(){ //云函数调用 wx.cloud.callFunction({ name:"getBook",//云函数的名称 //云函数调用成功执行的方法 complete:res=>{ console.log(res); } }) },
-
给云函数传参
-
getBookById
//初始化数据库 const db = cloud.database(); // 云函数入口函数 exports.main = async (event, context) => { //根据id查询test //传递的参数都在event中 return await db.collection("test").doc(event.id).get(); }
-
home.js
/** * 云函数传参 */ cloudFunctionArgs(){ wx.cloud.callFunction({ name:"getBookById", data:{ id:'14a8deea64bc98de00798182710429c8' }, complete:res=>{ console.log(res); } }) },
-
-
调用云函数添加数据库
-
addBook
const db = cloud.database() // 云函数入口函数 exports.main = async (event, context) => { return await db.collection("test").add({ data:{ name:event.name, author:event.author, price:event.price, date:event.date } }) }
-
home.js
/** * 调用云函数添加数据库 */ addCloudFunction(){ wx.cloud.callFunction({ name:"addBook", data:{ name:"红楼梦", author:['曹雪芹'], price:3.8, date:new Date() }, complete:res=>{ console.log(res); } }) },
- 添加之后也会返回新增的id
-
Example:商城项目
环境配置
- 在app.js修改云环境id
- 修改表的数据权限【所有用户都可读写】
注册
-
注册验证
/** * 当点击注册时触发的函数 * 1. 先校验输入的数据是否满足要求 * 手机号满足,用户名、密码不能为空,密码和确认密码必须一致 */ submit(){ //手机号:以1开头,第二位:3 4 5 6 7 8 9 第三位-第十一位:普通数字 //正则表达式:通常被用来检索、替换那些符合某个模式(规则)的文本 // ^:以什么开头 [3-9]:3到9中的任意一个 \d:等价于[0-9] // $:以什么结尾 var reg = /^1[3-9]\d{9}$/; if(!reg.test(this.data.phone)){ //给出提示 wx.showToast({ title: '手机号格式错误', icon:'error' }) }else if(this.data.pass!=this.data.confirmPass){ wx.showToast({ title: '密码前后不一致', icon:'error' }) //js:空字符串放在boolean中就是false }else if(this.data.username && this.data.pass){ //1. 注册校验 this.registCheck(); }else{ wx.showToast({ title: '表单输入不正确', icon:'error' }) } },
-
注册插入
/** * 注册校验的函数 * 1. 先根据手机号查询数据是否存在 * 2. 如果存在给出提示,如果不存在插入数据 */ registCheck(){ wx.cloud.callFunction({ name:"getUserByPhone", data:{ phone:this.data.phone }, complete:res=>{ if(res.result.data.length!=0){ //给出手机号重复的提示 wx.showToast({ title: '手机号已存在', icon:"error" }) }else{ //可以注册 wx.cloud.callFunction({ name:"regist", data:{ phone:this.data.phone, username:this.data.username, pass:this.data.pass }, complete:res=>{ if(res.result._id){ wx.showToast({ title: '注册成功', icon:"success", duration:1500,//图标显示时长 }) setTimeout(()=>{ wx.navigateBack()//返回上一页 },1500) } } }) } } }) },
登录
登录成功之后将用户信息存到 storage 中
-
login.js
/** * 登录验证 */ login(){ wx.cloud.callFunction({ name:"login", data:{ phone:this.data.phone, pass:this.data.pass }, complete:res=>{ console.log(res); if(res.result.data.length==0){ wx.showToast({ title: '账号密码不正确', icon: "error", }) }else{ //将登录的用户信息存到storage中 wx.setStorageSync('user', res.result.data[0]) //打印查询的对象 wx.showToast({ title: '登录成功', icon:"success", duration:1500, }) setTimeout(()=>{ wx.navigateBack() },1500) } } }) },
-
mine.js
在 onshow 中校验用户是否登录,因为 onLoad 只会初始化一次
onShow() { //获取storage中的user var user = wx.getStorageSync('user'); if(user){ //登录成功 this.setData({ isLogin:true, user:user }) }else{ //没有登录 this.setData({ isLogin:false, user:{} }) } },
6.3 导入其他js库的函数
-
util.js
const formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` } //定义的函数如果想要被别人调用的话,必须要exports导出 module.exports = { formatTime }
-
home.js
var {formatTime} = require("../../utils/util.js") formatTime(new Date());
头像上传
-
mine.js
/** * 上传头像 */ choosePhoto(){ wx.chooseMedia({ count:1,//上传个数 mediaType: ['image','video'], sourceType: ['album', 'camera'], camera: 'back', success:(res)=>{ wx.cloud.uploadFile({ cloudPath: Date.now()+'.png', // 上传至云端的路径 filePath: res.tempFiles[0].tempFilePath, // 小程序临时文件路径 success: res => { // 返回文件 ID:图片存储的云路径 //将图片云路径赋值给user的头像 this.setData({ //修改user的头像路径:目的都是修改完立刻更新 "user.avatarurl":res.fileID }) //修改storage中的头像:重新显示mine页面时图片路径改变 wx.setStorageSync('user', this.data.user); //修改数据库的头像路径:退出登录之后再次登录时头像也会改变 wx.cloud.callFunction({ name:"updatePhoto", data:{ id:this.data.user._id, avatarurl:this.data.user.avatarurl } }) } }) } }) },
商城后台管理
-
后台管理的账号:admin 密码:123
-
商品表、分类表:分类和商品是一对多关系
-
添加分类 (addCategory.js)
/** * 添加分类 */ addCategory(){ //1. 先验证分类是否存在 wx.cloud.callFunction({ name:"getCategoryByName", data:{ categoryName:this.data.inputName },complete:res=>{ if(res.result.data.length!=0){ wx.showToast({ title: '分类已存在', icon:"error" }) }else{ //添加分类 wx.cloud.callFunction({ name:"addCategory", data:{ categoryName:this.data.inputName },complete:res=>{ if(res.result._id){ wx.showToast({ title: '添加成功', icon:"success", duration:1500 }) setTimeout(()=>{ wx.navigateBack() },1500) } } }) } } }) },
-
添加商品页面 (add_product)
/** * 生命周期函数--监听页面加载 */ onLoad(options) { //查询所有的分类并展示在页面上 wx.cloud.callFunction({ name:"categoryList", complete:res=>{ this.setData({ categoryList:res.result.data }) } }) },
使用 picker 组件实现滑动选择分类
<!-- 滑动选择的组件 --> <picker range-key="name" model:value="{{index}}" range="{{categoryList}}"> <view class="picker"> {{categoryList[index].name|| '请选择'}} </view> </picker>
首页查询数据
-
查询默认的 banner
/** * 获取轮播图 */ getBanner(){ wx.cloud.callFunction({ name:"getBanner", complete:res=>{ this.setData({ banner:res.result.data }) } }) },
-
查询前9件商品
/** * 查询默认商品 */ goodsList(){ wx.cloud.callFunction({ name:"getDefaultProduct", complete:res=>{ this.setData({ goodsList:res.result.data }) } }) },
分类页面
-
切换分类
- 选择分类变色
<!-- 小程序向函数中传参数,使用data-,例如data-id,data-name --> <view class="{{currentIndex==index?'current-category category-item':'category-item'}}" bindtap="changeCategory" data-index="{{index}}" wx:for="{{categoryList}}" > {{item.name}} </view>
/** * 切换分类 */ changeCategory(e){ //接收data-传递的参数 var index = e.currentTarget.dataset.index; this.setData({ currentIndex:index }) },
-
获取所有分类
/** * 获取所有分类 */ getCategoryList(){ wx.cloud.callFunction({ name:"categoryList", complete:res=>{ this.setData({ categoryList:res.result.data }) } }) },
商品添加到购物车
-
首先对用户登录进行校验
/** * 添加购物车 * 1. 验证用户是否登录 * 2. 先根据用户id和商品id查询购物车数据是否存在 * 如果查得到,说明之前购买过,只更新数量 * 如果查不到,没有购买过,直接插入 * 购物车:id、productId(商品id【name、price】)、 * buyCount(购买数量)、userId(用户id) */ addCart(e){ var user = wx.getStorageSync('user') if(user){ var userId = user._id;//用户id var productId = e.currentTarget.dataset.id;//商品id wx.cloud.callFunction({ name:"getCartByUserAndProduct", data:{ userId:userId, productId:productId },complete:res=>{ if(res.result.data.length!=0){ //之前购买过,只更新数量 this.updateCartNum(res.result.data[0]) }else{ //没购买过,插入数据 this.insertCart(userId,productId); } } }) }else{ wx.showToast({ title: '用户未登录', icon:"error", duration:1500 }) } },
-
当前用户第一次购买此商品
-
先插入购物车数据
/** * 插入数据 * _id[自增]、productId、userId、buyCount[默认为1] */ insertCart(userId,productId){ wx.cloud.callFunction({ name:"addCart", data:{ userId:userId, productId:productId },complete:res=>{ if(res.result._id){ wx.showToast({ title: '添加成功', icon:'success' }) //根据商品减库存 this.reduceProductCount(productId); } } }) },
-
再减少此商品的库存
/** * 对商品减库存 */ async reduceProductCount(productId){ //根据商品id查询商品对象=====>获取商品的数量 var oldProduct = await this.getProductById(productId); console.log(">>>",oldProduct); if(oldProduct.count>=1){ wx.cloud.callFunction({ name:"reduceProductCount", data:{ productId:productId,//商品id currentCount:oldProduct.count//当前商品库存 },complete:res=>{ //重新查询当前分类的商品数据 this.getProductListByCategory(); } }) }else{ wx.showToast({ title: '库存不足', }) } },
-
根据 商品id 查询商品对象
/** * 根据商品id查询商品对象 */ getProductById(productId){ return new Promise((resolve,reject)=>{ wx.cloud.callFunction({ name:"getProductById", data:{ productId:productId },complete:res=>{ console.log("根据商品id查询>>>>",res); resolve(res.result.data); } }) }) },
-
将减库存这个函数由异步改为同步,并将 getProductById 函数修改为 Promise
-
-
用户已经购买过此商品
-
更新购物车数量
/** * 更新数量 * oldCart:之前购买过的这一条购物车数据对应的对象 * _id,productId,userId,buyCount */ async updateCartNum(oldCart){ //新数量 var newNum = oldCart.buyCount+1; //1. 根据商品id查询商品的库存 //根据商品id查询商品对象=====>查询商品的库存 var oldProduct = await this.getProductById(oldCart.productId); //验证newNum<=此商品库存 if(newNum<=oldProduct.count){ //调用更新的函数 wx.cloud.callFunction({ name:"updateCartNum", data:{ cartId:oldCart._id, buyCount:newNum },complete:res=>{ console.log("修改数量>>>>>",res); wx.showToast({ title: '添加成功', icon:'success' }) //根据商品减库存 this.reduceProductCount(oldCart.productId); } }) }else{ wx.showToast({ title: '库存不足', icon:"error" }) } },
-
减库存代码同上
-
分类查询补充
第一种方法:使用递归实现
- 查询当前商品被当前用户购买的数量
/**
* 根据分类查询对应的商品
*/
getProductListByCategory(){
...
complete:res=>{
...
//对所有的商品进行遍历
var productList = this.data.productList;
//不要在for循环中写异步请求!!!!!
//解决办法:1.改成promise语法(async await) 2. 递归
this.getBuyCount(0,productList,productList.length);
}
})
},
/**
* 递归查询 每件商品在当前用户下被加入到购物车的数量
* 参数:1. 下标 2. 商品列表 3. 商品列表的长度
*/
getBuyCount(i,productList,length){
db.collection("cart").where({
productId:productList[i]._id,
userId:wx.getStorageSync('user')._id
}).get({
success:res=>{
if(res.data.length!=0){
//买过
productList[i].buyCount = res.data[0].buyCount;
}else{
//没买过
productList[i].buyCount = 0;
}
this.setData({
productList:productList
})
if(++i<length){
this.getBuyCount(i,productList,length);
}
}
})
},
第二种方法:异步变同步
/**
* 获取每个商品被当前用户购买的数量
*/
async getCountByProduct(productList){
for(var i=0;i<productList.length;i++){
var buyCount = await this.getBuyCountByProduct(productList[i]._id);
productList[i]['buyCount']=buyCount;
}
this.setData({
productList:productList
})
},
/**
* 根据商品id和用户id查询购物车的数量
*/
getBuyCountByProduct(productId){
return new Promise((resolve,reject)=>{
db.collection("cart").where({
productId:productId,
userId:wx.getStorageSync('user')._id
}).get({
success:res=>{
var buyCount = 0;
if(res.data.length!=0){
buyCount = res.data[0].buyCount;
}
resolve(buyCount);
}
})
})
},
购物车查询[cart.js]
/**
* 查询当前用户的购物车
*/
getCartListByUser(){
db.collection("cart").where({
userId:wx.getStorageSync('user')._id
}).get({
success:res=>{
this.getProductByCart(res.data);
}
})
},
/**
* 通过购物车获取对应的商品
*/
async getProductByCart(cartList){
for(var i=0;i<cartList.length;i++){ // 0 1 2 3 4
var product = await this.getProductById(cartList[i].productId);
//每一个购物车对象都包含一个商品属性
cartList[i]['product'] = product;
}
this.setData({
cartList:cartList
})
},
/**
* 根据商品id查询商品对象
*/
getProductById(productId){
return new Promise((resolve,reject)=>{
wx.cloud.callFunction({
name:"getProductById",
data:{
productId:productId
},complete:res=>{
resolve(res.result.data);
}
})
})
},
购物车全选
/**
* 点击全选按钮
* 勾选、取消勾选
*/
selectAll(){
// 获取当前全选按钮的状态
var selectAllStatus = this.data.selectAllStatus;
//获取点击后的全选按钮状态
selectAllStatus = !selectAllStatus;
//获取当前用户的购物车列表
var cartList = this.data.cartList;
if(selectAllStatus){
//全选
//给每个购物车都添加check属性设置为true
for(var i=0;i<cartList.length;i++){
cartList[i].check = true;
}
}else{
//取消全选
//给每个购物车都添加check属性设置为false
for(var i=0;i<cartList.length;i++){
cartList[i].check = false;
}
}
this.setData({
selectAllStatus:selectAllStatus,
cartList:cartList
})
//调用计算总金额的方法
this.calcTotalPrice();
},
购物车反选
/**
* 单选按钮
*/
selectOne(e){
//接收点击的下标
var index = e.currentTarget.dataset.index;
var cartList = this.data.cartList;
cartList[index].check = !cartList[index].check
//默认定义全选按钮的状态为true
var selectAllStatus = true;
for(var i=0;i<cartList.length;i++){
//只要购物车对象有一个check是false,selectAllStatus变为false
if(!cartList[i].check){
//至少一个单选按钮不是选中状态
selectAllStatus = false;
break;
}
}
this.setData({
cartList:cartList,
selectAllStatus:selectAllStatus
})
//调用计算总金额的方法
this.calcTotalPrice();
},
结算金额
/**
* 总金额计算
* 只计算选中的金额: check==true
*/
calcTotalPrice(){
var cartList = this.data.cartList;
//定义总金额
var totalPrice = 0;
for(var i=0;i<cartList.length;i++){
if(cartList[i].check){
totalPrice+=cartList[i].buyCount*cartList[i].product.price;
}
}
this.setData({
//toFixed(1):保留1位小数
totalPrice:parseFloat(totalPrice.toFixed(1))
})
},
减少购物车数量
/**
* 减少当前购物车的数量
*/
reduceCount(e){
//接收点击的下标
var index = e.currentTarget.dataset.index;
var cartList = this.data.cartList;
//判断当前购物车的数量大于等于1
if(cartList[index].buyCount<1){
return;//1. 返回值 2. 方法结束
}
var newCount = cartList[index].buyCount-1;
//修改当前点击的数量
cartList[index].buyCount = newCount;
this.calcTotalPrice();
if(newCount==0){
//重点:把购物车中数量为0的那个购物车的id拿到,以便后面删除该购物车
var removeId = cartList[index]._id;
//数量为0,就删掉界面上列表那一个购物车
cartList.splice(index,1);
//删除购物数为0的购物车
db.collection("cart").doc(removeId).remove()
}else{
//更新数据库中的数量
db.collection("cart").doc(cartList[index]._id).update({
data:{
buyCount:newCount
},success:res=>{
this.setData({
cartList:cartList
})
}
})
}
},