微信小程序入门最佳实践案例-一键绘制国庆头像

一、前言

本篇文章的篇幅会很长,但是如果你正在学习或者准备学习微信小程序开发,请认真看下去,会对你有很大的帮助。

文章会通过一个项目案例,完整的将小程序+微信云开发+CloudBase CMS(后台)的基础使用展现出来,可以让你在短时间内入门微信小程序开发。

文末有完整代码包下载,但是我希望你看文章自己动手实现,而不是直接拿着代码包导入后作为自己的项目,只有自己动手敲的,才是你的。

看不懂不要紧,如果能够提起你的兴趣就好,看多几次,不懂的或者有问题,评论区等你。

二、案例介绍

案例简介

本次实践的项目是开发一个一键绘制国庆头像的小程序,有部分同学们可能已经使用过这类的小程序了,简单来说就是获取用户的微信头像,然后给用户生成一个带国旗或者其他一些带有国庆元素的新头像,用户可以保存下来使用。

目前的热度以及新鲜度没有前两年刚刚出来时那么的高了,但是作为一个实践案例还是很值得去玩一玩的,难度中等,涉及的接口也比较常用,也具有一定的娱乐性和实用性。

技术栈介绍

前端:原生微信小程序

服务端:微信云开发

管理端:CloudBase CMS(微信云开发提供的内容管理系统)

小程序案例展示

接下来我们先看看最终要实现的效果。

因为案例代码目前还没有重写,所以我先拿公司的线上项目作为效果展示。

项目最终实现功能基本一致,部分功能会去除(例如关注公众号的)。

这里附上公司的线上小程序码,有兴趣的同学可以前往查看。

在这里插入图片描述

首页

在这里插入图片描述

申请获取用户头像

在这里插入图片描述

绘制

在这里插入图片描述

保存、转发等操作

在这里插入图片描述

内容管理后台(CMS)展示

在这里插入图片描述
在这里插入图片描述

三、项目准备工作

准备工作

前往下载项目所需要用到的相关资源。电脑浏览器里点击前往下载

新建云开发小程序项目

这个没什么难度,如果没有注册过小程序账号的话,自行百度先注册一个,APPID不能使用测试号的,因为需要开通云开发。

已经有APPID的同学,直接新建一个项目就行,后端服务记得勾选开通微信云开发。

在这里插入图片描述

创建相关数据库表

创建好后,进入云开发控制台,创建两个数据库表。

1、makeAvatarBg:存储头像框数据。

2、makeAvatarType:存储头像框分类数据。

在这里插入图片描述

修改一下数据库表的读写权限。这里很重要,别漏了,不然小程序端将请求不到数据。

在这里插入图片描述
在这里插入图片描述

创建CMS

进入创建内容管理系统。

在这里插入图片描述

因为我已经开通过了,所以这是开通成功的页面,如果没有开启过的,会显示开启的按钮,点击等待初始化完成就行。

初始化需要一点时间,耐心等待即可。

在这里插入图片描述

搭建CMS

创建完成后,点击CMS的访问地址,前往浏览器打开后输入设置的账号密码,点击登录。

在这里插入图片描述

创建一个CMS的管理项目。

在这里插入图片描述

创建完成后,点击进入,开始搭建一下CMS。

在这里插入图片描述

导入模型

如果你想自己手动创建也可以,自己研究吧。

在这里插入图片描述

在这里插入图片描述

模型的文件在这里。

在这里插入图片描述

上传数据

因为项目比较简单,需求也已经清晰了,所以我们直接上数据,小程序编写的时候,数据直接从数据库取。

在这里插入图片描述

在这里插入图片描述

前往云控制台,上传一下页面的素材,页面素材加起来有500多K,小程序单包大小最大是1M,所以素材需要放到云存储里,避免随着项目的迭代而超出限制。

在这里插入图片描述

在这里插入图片描述

四、项目开发

目录结构在这里插入图片描述

代码实现

json代码

这里要引入一个组件,用来绘制头像。

{
  "usingComponents": {
    "painter":"/components/painter/painter"
  },
  "navigationStyle": "custom",
  "disableScroll": true
}
JS代码

代码里面都已经额外添加了很多注释,同学们可以下载代码包进行查看。

// pages/makeAvatar/makeAvatar.js
const db = wx.cloud.database()
const posterViewjs = require("../../posterViewjs/posterView")
Page({

  /**
   * 页面的初始数据
   */
  data: {

    paintPallette: null, // 绘制头像的的数据

    // 页面素材网址,记得修改成自己上传时的素材地址
    leftTop: "cloud://cloud1-3gpkfcgu3d6c85da.636c-cloud1-3gpkfcgu3d6c85da-1309850448/makeAvatar/left-top.png",
    rightTop: "cloud://cloud1-3gpkfcgu3d6c85da.636c-cloud1-3gpkfcgu3d6c85da-1309850448/makeAvatar/right-top.png",
    leftCenter: "cloud://cloud1-3gpkfcgu3d6c85da.636c-cloud1-3gpkfcgu3d6c85da-1309850448/makeAvatar/left-center.png",
    rightBottom: "cloud://cloud1-3gpkfcgu3d6c85da.636c-cloud1-3gpkfcgu3d6c85da-1309850448/makeAvatar/right-bottom.png",

    typeList: [], // 分类列表
    nowTypeIndex: 0, // 当前选择的分类下标
    nowSelectAvatarBg: "", // 当前选择的头像框url
    avatarBgDocId: null, // 当前头像框的id
    userAvatarUrl: null, // 用户微信头像url

  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad() {
    this.initData()
  },

  /**
   * 初始化数据
   */
  async initData() {
    wx.showLoading({
      title: "加载中",
      mask: true
    })
    const nowTypeIndex = this.data.nowTypeIndex
    // 获取头像框分类数据并初始化分类列表
    const typeList = await this.getTypeData()
    for (let i = 0; i < typeList.length; i++) {
      typeList[i].avatarBgList = []
    }
    // 获取当前选择分类的头像框数据
    const nowTypeName = typeList[nowTypeIndex].typeName
    const avatarBgList = await this.getAvatarBgData(nowTypeName)
    typeList[nowTypeIndex].avatarBgList = avatarBgList
    this.data.avatarBgDocId = avatarBgList[0]._id
    // 动态绑定数据
    this.setData({
      typeList: typeList,
      nowSelectAvatarBg: avatarBgList[0].avatarBg,
    })
    wx.hideLoading()
  },

  /**
   * 分类栏点击监听
   */
  async typeBarTap(res) {
    // 获取当前点击的分类下标
    const index = res.currentTarget.dataset.index
    const nowIndex = this.data.nowTypeIndex
    // 判断是否点击同一个分类
    if (index === nowIndex) return
    this.setData({
      nowTypeIndex: index
    })
    const typeList = this.data.typeList
    let avatarBgList = typeList[index].avatarBgList
    // 判断该分类是否已经获取过头像框数据,如果已经获取了,就不再重复获取
    if (avatarBgList.length > 0) return
    // 获取新分类中的头像框数据
    const typeName = typeList[index].typeName
    wx.showLoading({
      title: "获取中",
      mask: true
    })
    avatarBgList = await this.getAvatarBgData(typeName)
    const key = "typeList[" + index + "].avatarBgList"
    this.setData({
      [key]: avatarBgList
    })
    wx.hideLoading()
  },

  /**
   * 头像框点击监听
   */
  avatarBgTap(res) {
    const {
      docid,
      bgurl
    } = res.currentTarget.dataset
    this.data.avatarBgDocId = docid
    this.setData({
      nowSelectAvatarBg: bgurl
    })
  },

  /**
   * 从图库选择图片
   * 因为有使用最近(202210)的新接口(图片剪裁wx.cropImage())
   * 需要基础库>=2.26.0才能用
   * 且android中有BUG,只能在iOS上使用,所以有做系统以及基础库的判断
   * 期待后续微信小程序团队的修复,如果你看这份代码的时候
   * BUG已经修复了,可以将系统判断代码去掉,仅判断基础库版本即可
   */
  async getPhotoTap() {
    // 获取系统相关信息
    const systemInfo = wx.getSystemInfoSync()
    const system = systemInfo.system // 手机操作系统
    const SDKVersion = systemInfo.SDKVersion // 基础库版本
    let tempFilePath = null // 临时图片路径

    // android或基础库版本 < 2.26.0 执行以下逻辑
    if (!system.includes("iOS") || this.compareVersion("2.26.0", SDKVersion) > 0) {
      // 弹出手动剪裁的提示
      const modalRes = await this.showAndroidTips()
      if (!modalRes) return
      // 选择相册图片
      const tempObj = await this.chooseImg(1, ["original"], ["album"])
      if (!tempObj) return
      tempFilePath = tempObj.tempFilePaths[0]
      this.setData({
        userAvatarUrl: tempFilePath
      })
      return
    }

    // 系统为iOS且基础库 >= 2.26.0 执行以下逻辑
    // 选择相册图片
    const tempObj = await this.chooseImg(1, ["original"], ["album"])
    if (!tempObj) return
    tempFilePath = tempObj.tempFilePaths[0]
    const that = this
    // 剪裁图片
    wx.cropImage({
      src: tempFilePath,
      cropScale: '1:1',
      success(res) {
        tempFilePath = res.tempFilePath
        that.setData({
          userAvatarUrl: tempFilePath
        })
      },
      fail(err) {
        console.log(err)
      },
    })
  },

  /**
   * 获取用户头像点击监听
   */
  async getUserProfileTap() {
    wx.showLoading({
      title: "获取中",
      mask: true
    })
    // 获取用户头像信息
    const userProfileRes = await this.getUserProfile()
    console.log("获取用户信息结果 ===>", userProfileRes)
    wx.hideLoading()
    if (userProfileRes.errMsg !== "getUserProfile:ok") {
      wx.showToast({
        title: "获取头像失败,请重试",
        icon: "none"
      })
      return
    }

    let avatarUrl = userProfileRes.userInfo.avatarUrl
    console.log("转换前的头像网址 ===>", avatarUrl)
    // 默认返回132*132尺寸的图片,将其转换成640*640, 不然很模糊
    const avatarUrlPrefix = avatarUrl.substring(0, avatarUrl.length - 3)
    avatarUrl = avatarUrlPrefix + "0"
    console.log("转换后的头像网址 ===>", avatarUrl)
    this.setData({
      userAvatarUrl: avatarUrl
    })
  },
  /**
   * 获取分类信息
   */
  async getTypeData() {
    const typeDataObj = await this.getDBDataByWhere("makeAvatarType", {
      isShow: "show"
    }, {}, 20, 0, "index", "asc")
    return typeDataObj.data
  },

  /**
   * 获取头像框数据
   * @param {String} typeName 分类名称
   * @param {Number} limit 获取数量
   * @param {Number} skip 跳过的文档数
   */
  async getAvatarBgData(typeName, limit = 20, skip = 0) {
    const avatarBgObj = await this.getDBDataByWhere("makeAvatarBg", {
      typeName: typeName,
      isShow: "show",
    }, {}, limit, skip, "index", "asc")
    return avatarBgObj.data
  },


  /** 绘制完成后的回调函数*/
  onImgOK(res) {
    const docId = this.data.avatarBgDocId
    wx.hideLoading()
    // 这个路径就可以作为保存图片时的资源路径
    console.log("新头像临时路径", res.detail.path)
    // 保存、收藏、转发头像
    wx.showShareImageMenu({
      path: res.detail.path,
      success(res) {
        console.log(res)
        this.callCloudFun("makeAvatarFun", {
          type: "saveCount",
          docId: docId
        })
      },
      fail(err) {
        console.log(err)
      },
    })
  },

  /**
   * 绘制头像
   */
  makeAvatar() {
    if (!this.data.userAvatarUrl) {
      wx.showToast({
        title: "请先获取头像",
        icon: "none"
      })
      return
    }
    wx.showLoading({
      title: "生成中",
    })
    // 更新头像框的使用次数
    const docId = this.data.avatarBgDocId
    this.callCloudFun("makeAvatarFun", {
      type: "useCount",
      docId: docId
    })
    // 绘制海报所用到JSON数据
    const userAvatarUrl = this.data.userAvatarUrl
    const nowSelectAvatarBg = this.data.nowSelectAvatarBg
    const viewList = posterViewjs.getPosterView01(userAvatarUrl, nowSelectAvatarBg)
    this.setData({
      paintPallette: viewList
    })
  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {
    return {
      title: "一键制作国庆专属头像",
      path: "pages/makeAvatar/makeAvatar"
    }
  },


  /**
   * 显示剪裁提示的弹窗
   */
  showAndroidTips() {
    return new Promise((resolve, reject) => {
      wx.showModal({
        title: "系统提示",
        content: "选取图片时,请将图片剪切为1:1的正方形,否则会影响生成的效果",
        confirmText: "好的",
        cancelText: "取消",
        success(res) {
          if (res.confirm) {
            resolve(true)
          } else {
            resolve(false)
          }
        }
      })
    })
  },

  /**
   * 执行头像框使用或保存的次数更新
   * @param {String} cloudFunName 云函数名称
   * @param {Object} eventData 云函数入参
   */
  callCloudFun(cloudFunName, eventData) {
    wx.cloud.callFunction({
        name: cloudFunName,
        data: eventData
      })
      .then(res => {
        console.log(`云函数${cloudFunName}执行成功===>`, res)
      })
      .catch(err => {
        console.error(`云函数${cloudFunName}执行失败===>`, err)
      })
  },

  /**
   * 获取数据库数据的封装函数
   * @param {String} collectionName 集合名
   * @param {Object} whereObj 查询条件
   * @param {Object} field 过滤
   * @param {Number} limit 查询文档数(小程序端最多一次查询20条)
   * @param {Number} skip 跳过文档数
   * @param {String} orderByName 排序字段名
   * @param {String} orderByType 排序方式
   */
  async getDBDataByWhere(collectionName, whereObj, field = {}, limit = 20, skip = 0, orderByName = "_id", orderByType = "desc") {
    return await db.collection(collectionName)
      .orderBy(orderByName, orderByType)
      .where(whereObj)
      .field(field)
      .limit(limit)
      .skip(skip)
      .get()
      .then(res => {
        return res
      })
      .catch(err => {
        return err
      })
  },

  /** 
   * 选择图片封装函数
   * @param {Number} count 可选择的照片数量, 默认可选择1张
   * @param {Array} sizeType 照片的质量, 默认 ['original', 'compressed']
   * @param {Array} sourceType 照片来源, 默认 ['album', 'camera']
   * wx.chooseImage接口在2.21.0废弃不再维护
   * 大家如果可以,自己换成新的接口,当一个小作业
   */
  async chooseImg(count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera']) {
    return new Promise((resolve) => {
      wx.chooseImage({
        count: count,
        sizeType: sizeType,
        sourceType: sourceType,
        success(res) {
          resolve(res)
        },
        fail(err) {
          console.log(err)
          resolve(false)
        }
      })
    })
  },

  /**
   * 版本比较
   * v1 >= v2 返回 0或1 否则 -1
   * @param {String} v1
   * @param {String} v2 
   */
  compareVersion(v1, v2) {
    v1 = v1.split('.')
    v2 = v2.split('.')
    const len = Math.max(v1.length, v2.length)

    while (v1.length < len) {
      v1.push('0')
    }
    while (v2.length < len) {
      v2.push('0')
    }

    for (let i = 0; i < len; i++) {
      const num1 = parseInt(v1[i])
      const num2 = parseInt(v2[i])

      if (num1 > num2) {
        return 1
      } else if (num1 < num2) {
        return -1
      }
    }

    return 0
  },

  /**
   * 获取用户头像、昵称信息封装函数
   * @param {String} desc 获取头像的理由,实际上没有任何意义,只是接口必须要传
   */
  async getUserProfile(desc = "获取用户头像") {
    return new Promise((resolve, reject) => {
      wx.getUserProfile({
        desc: desc,
        success(res) {
          resolve(res)
        },
        fail(err) {
          resolve(err)
        }
      })
    })
  },

})
WXML代码
<!--pages/makeAvatar/makeAvatar.wxml-->
<view class="page-box">
	<view class="page-bg-box">
		<view class="left-top-box">
			<image style="height: 500rpx; width: 500rpx;" src="{{leftTop}}" mode="scaleToFill"></image>
		</view>

		<view class="right-top-box">
			<image style="height: 800rpx; width: 800rpx;" src="{{rightTop}}" mode="scaleToFill"></image>
		</view>

		<view class="left-center-box">
			<image style="height: 850rpx; width: 850rpx;" src="{{leftCenter}}" mode="scaleToFill"></image>
		</view>

		<view class="right-bottom-box">
			<image style="width: 800rpx;height: 800rpx;" src="{{rightBottom}}" mode="scaleToFill"></image>
		</view>

	</view>
</view>

<view class="avatar-type-box">

	<scroll-view class="type-bar-scroll-box" scroll-x="true">
		<view class="type-bar-box flex">
			<view class="type-item flex-ac {{nowTypeIndex == index ? 'type-item-select' : ''}}" wx:for="{{typeList}}" wx:key="_id" bindtap="typeBarTap" data-index="{{index}}">
				{{item.typeName}}
			</view>
		</view>
	</scroll-view>

	<scroll-view class="avatar-bg-scroll" scroll-y="true" wx:if="{{typeList[nowTypeIndex].avatarBgList.length > 0}}">
		<view class="avatar-bg-box flex-jb">
			<view class="avatar-bg-img-box flex-jc-ac {{nowSelectAvatarBg == item.avatarBg ? 'avatar-bg-img-box-select':''}}" wx:for="{{typeList[nowTypeIndex].avatarBgList}}" wx:key="_id" bindtap="avatarBgTap" data-docid="{{item._id}}" data-bgurl="{{item.avatarBg}}">
				<image src="{{item.avatarBg}}" class="avatar-bg-img" mode="widthFix"></image>
			</view>
			<view wx:key="index" wx:for="{{typeList[nowTypeIndex].avatarBgList.length%4}}" style="height: 135rpx; width: 135rpx;"></view>
		</view>
	</scroll-view>
	<view wx:else>
		<view class="no-data-tips flex-jc-ac">插画师正在加班加点设计中...</view>
	</view>

</view>


<view class="make-avatar-box flex-jb-ac">
	<view class="perview-avatar-box">
		<image class="bg-avatar-img" src="{{nowSelectAvatarBg}}" mode="widthFix"></image>
		<image class="user-avatar-img" src="{{userAvatarUrl}}" mode="scaleToFill"></image>
	</view>
	<view class="make-avatar-btn-box">
		<view class="btn-class flex-jc-ac" bindtap="getUserProfileTap">获取微信头像</view>
		<view class="btn-class flex-jc-ac" bindtap="getPhotoTap">从相册中选择</view>
		<view class="btn-class flex-jc-ac" bindtap="makeAvatar">保存新头像</view>
	</view>
</view>
<view class="tips-text">温馨提示:如果生成的头像比较模糊,可以从相册中选择图片进行生成</view>

<!-- canvas隐藏 -->
<painter customStyle='position: absolute; left: -9999rpx;' palette="{{paintPallette}}" bind:imgOK="onImgOK" use2D="{{true}}" scaleRatio="2" />
<!-- canvas隐藏 -->
WXSS代码
/* pages/makeAvatar/makeAvatar.wxss */
.page-box {
	position: fixed;
	width: 100vw;
	height: 100vh;
	top: 0rpx;
	left: 0rpx;
	z-index: -10;
}

.page-bg-box {
	position: relative;
	width: 100vw;
	height: 100vh;
	background-image: linear-gradient(to right bottom,
			#ff8b67,
			#ff895c,
			#fe8a54,
			#fb7e4c,
			#f47150,
			#ec5944,
			#e65642,
			#d0402c);
}

.left-top-box {
	position: absolute;
	top: 0rpx;
	left: -70rpx;
}

.right-top-box {
	position: absolute;
	top: 0rpx;
	right: -50rpx;
}

.left-center-box {
	position: absolute;
	left: -230rpx;
	margin-top: 200rpx;
}

.right-bottom-box {
	position: absolute;
	bottom: 0rpx;
	right: -50rpx;
}

.avatar-type-box {
	background-color: rgba(255, 255, 255, 0.5);
	width: 90%;
	height: 350rpx;
	margin: auto;
	margin-top: 30%;
	border-radius: 20rpx;
}

.type-bar-scroll-box {
	width: 100%;
	height: 75rpx;
	white-space: nowrap;
}

.type-bar-box {
	height: 100%;
}

.type-item {
	height: 100%;
	padding: 0rpx 25rpx;
	font-size: 28rpx;
}

.type-item-select {
	font-size: 30rpx;
	font-weight: bold;
	color: #d04a35;
}

.avatar-bg-scroll {
	height: 75%;
	width: 90%;
	margin: auto;
}

.avatar-bg-box {
	flex-wrap: wrap;
	width: 100%;
	height: 100%;
}

.avatar-bg-img-box {
	background-color: #ffffff;
	width: 135rpx;
	height: 135rpx;
	border-radius: 10rpx;
	margin-top: 15rpx;
}
.avatar-bg-img-box-select{
	box-shadow: rgba(231, 86, 66, 0.9) 0rpx 10rpx 10rpx;
}

.avatar-bg-img {
	width: 125rpx;
	height: 125rpx;
}

.make-avatar-box {
	margin: auto;
	margin-top: 55rpx;
	padding: 25rpx 35rpx;
	padding-bottom: 35rpx;
	background-color: rgba(255, 255, 255, 0.6);
	width: 80%;
	border-radius: 20rpx;
}

.perview-avatar-box {
	position: relative;
}

.bg-avatar-img {
	width: 220rpx;
	height: 220rpx;
	position: absolute;
	top: 0rpx;
	left: 0rpx;
}

.user-avatar-img {
	width: 220rpx;
	height: 220rpx;
	border-radius: 20rpx;
	background-color: #ffffff;
}

.btn-class {
	width: 280rpx;
	height: 85rpx;
	border-radius: 20rpx;
	margin-top: 25rpx;
	background-image: linear-gradient(to right bottom,
			#ff8b67,
			#ff895c,
			#fe8a54,
			#fb7e4c,
			#f47150,
			#ec5944,
			#e65642,
			#d0402c);
	font-size: 30rpx;
	font-weight: bold;
	color: #f6f6f6;
}

.tips-text {
	font-size: 26rpx;
	color: #f6f6f6;
	margin-top: 15rpx;
	padding: 0rpx 35rpx;
}

.about-public-box {
	background-color: rgba(255, 255, 255, 0.6);
	width: 90%;
	margin: auto;
	margin-top: 25rpx;
	padding: 15rpx 25rpx;
	border-radius: 20rpx;
}


.public-title {
	font-size: 30rpx;
	font-weight: bold;
}

.public-desc {
	font-size: 24rpx;
	color: #515151;
	margin-left: 20rpx;
}

.public-btn {
	font-size: 26rpx;
	font-weight: bold;
	width: 300rpx;
	height: 75rpx;
	margin: auto;
	margin-top: 25rpx;
	background-image: linear-gradient(to right bottom,
			#ff8b67,
			#ff895c,
			#fe8a54,
			#fb7e4c,
			#f47150,
			#ec5944,
			#e65642,
			#d0402c);
	color: #f6f6f6;
	border-radius: 10rpx;
}

.no-data-tips{
	font-size: 28rpx;
	margin-top: 35rpx;
}

/* 以下部分其实我是封装起来的,但是为了降低代码的耦合度,我就抽出来,略显冗余 */
.flex,
.flex-jc,
.flex-ac,
.flex-jb,
.flex-jc-ac,
.flex-jb-ac,
.flex-jc-col,
.flex-je-ac,
.flex-jc-ae,
.flex-jc-ac-col {
  display: flex;
}

.flex-jc,
.flex-jc-ac,
.flex-jc-col,
.flex-jc-ae,
.flex-jc-ac-col {
  justify-content: center;
}

.flex-jb {
  justify-content: space-between;
}

.flex-ac,
.flex-jc-ac,
.flex-jb-ac,
.flex-je-ac,
.flex-jc-ac-col {
  align-items: center;
}

.flex-je-ac {
  justify-content: flex-end;
}

.flex-jb-ac {
  justify-content: space-between;
}

.flex-jc-col,
.flex-jc-ac-col {
  flex-flow: column;
}
.flex-jc-ae{
  align-items: flex-end;
}
绘制头像的一些额外代码

其实单独抽出来有点难理解,但是直接跟着目录结构,创建文件夹以及文件,粘贴代码进去就行。(主要是因为原项目中很多地方都用到了绘制海报、头像之类的功能,所以会抽离出来)
在这里插入图片描述

/**
 * 
 * @param {*} avatarUrl 用户头像网址
 * @param {*} avatarBgUrl 头像框网址
 */
const getPosterView01 = (avatarUrl, avatarBgUrl) => {
  const poster = {
    "width": "1000rpx",
    "height": "1000rpx",
    "background": avatarUrl,
    "views": [
      {
        "type": "image",
        "url": avatarBgUrl,
        "css": {
          "width": "1000rpx",
          "height": "1000rpx",
          "top": "0px",
          "left": "0px",
          "rotate": "0",
          "borderRadius": "",
          "borderWidth": "",
          "borderColor": "#000000",
          "shadow": "",
          "mode": "scaleToFill"
        }
      },
    ]
  }
  return poster
}


module.exports = {
  getPosterView01,
}
云函数代码

云函数名称:makeAvatarFun
如果没有的话,新建一下。
在这里插入图片描述
如果是直接下载代码导入的话,就上传一下这个云函数。
在这里插入图片描述

可能会出现的报错

因为小程序请求第三方链接需要配置一下,所以可能会报下面这个错误。
在这里插入图片描述
解决方法:
前往微信公众管理平台,进行域名配置。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输入网址:https://thirdwx.qlogo.cn
这个是用户微信头像的一级网址。
保存并提交后,刷新一下项目配置然后重新编译一下项目即可。
在这里插入图片描述

五、项目上线

上线前请确保通过真机的测试,没有出现异常。
小程序上线的细节自行搜索相关的文章,文章篇幅太长,就不详细讲了。
简单讲一下两个步骤。

上传代码

在这里插入图片描述

提交审核

在这里插入图片描述
在这里插入图片描述

六、结语

项目中做了很多封装,虽然在这个小项目里面显得有点多余,大家在做正式项目的时候,可以自己学着封装一些公共的函数到模块里,这样可以减少很多重复代码,特别是数据库请求的代码。

项目代码包,点击下载(请在电脑浏览器中点击链接)

最后来一下常规结语:

分享的是思维不是技术。所以很多地方写得并不是很严谨,仅仅是把逻辑跑了一遍。(大佬们手下留情,谢谢)

实际开发中的其他逻辑就不写了,这里只是最简单的实现。

有任何疑问可以在评论区留下。我每天都会进行回复,私聊不回。(为了刷积分)

以上均是本人开发过程中的一些经验总结与领悟,如果有什么不正确的地方,希望大佬们评论区斧正。

💥最后!!!不管这篇文章对你有没有用,既然都看到最后了。
👍赞一个!!!
🤩当然,顺带收藏就最好了。
😎欢迎转载,原创不易,转载请注明出处✍。

😊如果你对小程序开发有兴趣或者正在学习小程序开发,可以关注我。每一篇都是原创,每一篇都是干货噢~。
————————————————
版权声明:本文为CSDN博主「super–Yang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44702572/article/details/127293443

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

super--Yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值