云开发练手项目:音乐播放器(中)

本文目录:

  • 1.单个页面的顶部文字设置
  • 2.组件内调用字体图标的三种方法
  • 3.slot插槽
  • 4.判断授权情况并获取用户授权
  • 5.上传图片到临时路径并实现本地预览
  • 6.将博客存储到数据库
  • 7.模糊查询
  • 8.云数据库中1对n关系的设计方式
  • 9.云函数消息模板推送
  • 10.分享当前页面
  • 11.获取用户信息的不同方式
  • 12.小程序端的数据存储
  • 13.生成小程序码

1.单个页面的顶部文字设置

在app.json的window属性中设置的navigationBarTitleText等内容都是全局生效的,可以在每个页面的json文件中再次设置进行覆盖.

在小程序中使用字体图标的时候可以使用专属的标签,增强语义。

给placeholder设置样式,在标签中增加placeholder-class属性placeholder-class="in-bar",然后在in-bar中添加样式代码

.in-bar{
  color: #999;
}

2.组件内调用字体图标的三种方法

在页面文件中可以自由调用字体图标样式,但是因为组件有样式隔离,所以在组件内部不能直接通过样式去使用字体图标。
解决方法一:
在组件文件夹里复制一个字体wxss文件,然后在组件内的wxss文件中通过import导入,这样就可以在组件内使用字体图标样式了
解决方法二:
调用组件的标签进行外部传入


组件中定义属性,是一个数组

  externalClasses:[
    'iconfont',
    'icon-sousuo'
  ],

注意:外部传入的样式,组件内部无法进行修改

第三种方式:在组件的js文件中增加属性

  options: {
    styleIssolation: 'apply-shared'
  },

这样做的好处就是可以直接对外部样式进行修改。

组件接收外部属性,如果定义了布尔类型的话,系统默认值就是false

3.slot插槽

小程序的组件也有slot插槽,如果是要设置多个slot,则要在options开启多个插槽的选项

options: {
  styleIsolation: 'apply-shared',
  multipleSlots: true 
}

同时使用具名插槽,如
引入这个组件的地方页面标签内部的元素也需要添加上slot属性,如

4.判断授权情况并获取用户授权

点击发布的时候,需要先判断用户是否已授权,下面这个会自动返回当前用户的授权信息,如果用户授权了,authSetting的scope.userInfo属性为true(未授权的话authSetting为空),然后再用微信提供的getUserInfo接口去获取到用户信息。

onPublish() {
  wx.getSetting({
    success: (res) => {
      // 如果用户授权过,我们就去获取用户的信息
      if (res.authSetting['scope.userInfo']) {
        wx.getUserInfo({
          success: (res) => {
            this.onLoginSuccess({
              detail: res.userInfo
            })
          }
        })
      } else {
        this.setData({
          modelShow: true
        })
      }
    }
  })
},

当还没有获得用户授权的时候,可以使用小程序button组件自带的开放能力open-type="getUserInfo"来触发获取授权的弹窗,同时给按钮绑定获取用户的属性,bindgetUserinfo绑定的事件可以自定义用户允许或者拒绝后的处理事件,,在触发的事件中判定const userInfo = event.detail.userInfo,如果userInfo存在,则代表用户点击的是同意授权,反之则是拒绝。拒绝之后以弹窗的形式提示用户

wx.showModal({
  title: '授权用户才能发布博客',
  content: '',
})

textarea是微信的原生组件,具有一些很特殊的属性,比如不能通过zindex改变层级,会始终覆盖在页面的最高层级,并且绑定事件bind后面不能写冒号(非原声组件bind后面的冒号可写可不写),maxlength设置成-1则可以突破textarea默认的140字限制。通过绑定bindinput事件,并且通过事件参数的detail里面的value可以获取到textarea输入的值
给textarea添加auto-focus属性可以自动获取焦点,这里有一个逻辑:底部的发布按钮所在footer部分本来的位置是fixed并且bootom为0定位在页面底部的。当textarea获取焦点的时候,键盘弹起,footer的bottom应该发生改变,失去焦点后,bottom应该恢复成0
给footer动态绑定样式style="bottom:{{footerBottom}}px"
这时候问题就变成了通过监听textarea的bindfocus和bindblur事件来动态的改变footerBottom数据就可以了
bindfocus的事件参数里面的detail里有一个属性height就是键盘的高度。注意:这个高度只有移动端真机才能看出来,模拟器中的height始终为0

每次重新编译小程序都会回到首页,如果是深层的页面则显得很麻烦,这时候可以增加模式并且自定义编译条件,让每次渲染都停留在指定的地方,其中启动参数每个参数的属性和值用等号链接,参数之间用逗号链接

循环数组的时候,通过wx:key="*this"可以让数组循环快速绑定自身作为key值

5.上传图片到临时路径并实现本地预览

给选择图片的icon绑定tap事件bindtap="onChooseImage"

onChooseImage() {
  // 这是微信提供的上传图片的接口
  wx.chooseImage({
    count: MAX_IMG_NUM - this.data.images.length,
    // 初始值/压缩
    sizeType: ['original', 'compressed'],
    // 从手机相册选/拍照选择
    sourceType: ['album', 'camera'],
    success: (res) => {
      // 把上传成功的图片链接赋值给data中预先定义好的变量
      this.setData({
        images: this.data.images.concat(res.tempFilePaths)
      })
      // 控制添加图片的元素是否显示
      let max = MAX_IMG_NUM - this.data.images.length
      this.setData({
        selectPhoto: max <= 0 ? false : true
      })
    },
  })
},

给删除图片的icon元素绑定删除图片的事件
data-index传递的index在事件处理函数的event参数中可以快速拿到event.target.dataset.index
this.data.images.splice(event.target.dataset.index, 1)这样可以快速删除指定index的那条数据,但是要注意splice方法的返回值是删除的数组项。

微信提供的预览图片的接口

wx.previewImage({
  urls: this.data.images,
  current: event.target.dataset.imagesrc
})

第一个参数是预览的图片的数组,
第二个参数是当前要查看的第一张图片

6.将博客存储到数据库

上面实现了图片的上传和预览,接下来要实现的是点击“发布”按钮的功能。
1.将图片地址存放到云数据库中=>先将图片上传到云存储中,云存储中的每个文件都有一个独立的fileID,数据库中存储的是图片资源的fileID
云开发天然自带上传资源进云存储的接口,但是要注意这个接口每次只能上传一张图片

wx.cloud.uploadFile({
  cloudPath:,
  filePath:,
  success:(res)=>{
  },
  fail:(err)=>{
  }
})

cloudPath表示图片的存储地址,为了更好的管理,我们可以去云开发的“存储”中建立一个文件夹去专门存放指定位置的图片。
取文件拓展名的正则表达式:let suffix = /\.\w+$/.exec(item)[0]
filePath表示的文件的临时路径
2.储存博客数据:每条博客动态的数据库设计:
博客的文字内容
图片fileID:fileID是文件存储云储存之后,返给开发者的标识id
openid:在小程序当中,openid是用户的唯一标识
昵称,头像,发布时间
这时候还涉及到一个问题:当用户一条动态有多个图片的时候,每个图片上传到云储存中的时间肯定不同,储存博客数据到云数据库中的时间肯定是要等到所有图片上传到云存储完成之后再去执行,所以这里要用promise和promise.all对上传图片到云存储然后存储博客数据到云数据库进行优化,完整的点击“发布”按钮触发的事件代码如下
const db = wx.cloud.database()先初始化数据库

send() {
  if (content.trim() === '') {
    wx.showModal({
      title: '请输入内容',
      content: '',
    })
    return
  }
  wx.showLoading({
    title: '发布中',
    mask: true
  })
  //  数据=>云数据库
  let promiseArr = []
  let fileIds = []
  // 1.图片上传,将图片存到云存储中
  for (let i = 0, len = this.data.images.length; i < len; i++) {
    let p = new Promise((resolve, reject) => {
      let item = this.data.images[i]
      //文件扩展名
      let suffix = /\.\w+$/.exec(item)[0]
      wx.cloud.uploadFile({
        cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 10000000 + suffix,
        filePath: item,
        success: (res) => {
          fileIds = fileIds.concat(res.fileID)
          resolve()
        },
        fail: (err) => {
          reject()
        }
      })
    })
    promiseArr.push(p)
  }
  // 存入到云数据库中
  Promise.all(promiseArr).then((res) => {
    db.collection('blog').add({
      data: {
        ...userInfo,
        content,
        img: fileIds,
        creatTime: db.serverDate() //服务端时间
      }
    }).then((res) => {
      wx.hideLoading()
      wx.showToast({
        title: '发布成功',
      })
      // 返回blog页面,并且刷新
      wx.navigateBack()
      const pages = getCurrentPages()
      const prevPage = pages[pages.length - 2]
      prevPage.onPullDownRefresh()
    })
  }).catch((err) => {
    wx.hideLoading()
    wx.showToast({
      title: '发布失败',
    })
  })
},

这里直接小程序端往云数据库中插入数据,一大好处就是自带openid
一个界面如何去调用另外一个界面的事件?
取到当前小程序的界面以及之前界面的信息
const pages = getCurrentPages()
取到上一个界面的
const prevPage = pages[pages.length-2]
刷新上一个界面
prevPage.onPullDownRefresh()

接下来要实现的查询数据库中的博客数据并显示到博客列表中:
查询功能在云函数中实现,在云函数中新建一个blog云函数,然后右键在终端中打开,执行npm install tcb-router --save
引入tcb-router并且初始化数据库并定义一个查询到指定数据库的变量(因为每次查询操作都需要先找到对应的数据库)

const TcbRouter = require('tcb-router')
const db = cloud.database()
const blogCollection = db.collection('blog')

查询博客列表的云函数代码如下:

exports.main = async(event, context) => {
  const app = new TcbRouter({
    event
  })
  app.router('list', async(ctx, next) => {
    let bloglist = await blogCollection.where(w).skip(event.start).limit(event.count)
      .orderBy('creatTime', 'desc').get().then((res) => {
        return res.data
      })
    ctx.body = bloglist
  })
  return app.serve()
}

在博客列表中定义方法:

_loadBlogList(start = 0) {
  wx.showLoading({
    title: '拼命加载中',
  })
  wx.cloud.callFunction({
    name: 'blog',
    data: {
      keyword,
      start,
      count: 10,
      $url: 'list',
    }
  }).then((res) => {
    this.setData({
      blogList: this.data.blogList.concat(res.result)
    })
    wx.hideLoading()
    wx.stopPullDownRefresh()
  })
},

云数据库中拿到的时间显示不是自己期望的格式。
格式化时间的代码我们写在utils文件夹中,相当于是一个工具箱
导出:
module.exports=()=>{ }
在需要使用的地方用import进行引用
import formatTime from '../../utils/formatTime.js'
通过observers监听器去监听blog数据下的createTime字段

小程序的每个页面的HTML都是被包裹在page标签中,改变页面背景颜色等操作可以直接给page添加

定义页面的下拉刷新:
首先需要在页面的json文件中定义"enablePullDownRefresh":true
然后监听页面的下拉事件,并定义相应的事件

  onPullDownRefresh: function() {
    this.setData({
      blogList: []
    })
    this._loadBlogList(0)
  },

注意:微信不会自动监测到何时终止下拉事件,所以我们在加载数据完成后应该是通过代码主动的终止下拉刷新的动画
wx.stopPullDownRefresh()

如果打算在子组件的整体上面绑定一个事件,应该让调用者去定义事件,可以让事件不是耦合在组件中。如:
当给组件整体绑定了事件之后,组件内部的一些元素也会相应的事件绑定,但是不希望冒泡,这时候可以用catch:tap="xxx"去绑定,效果和bind一样,但是会阻止事件冒泡。

7.模糊查询

搜索框组件的搜索功能应该让自己触发父组件的search事件,然后把自己拿到的value值传给父组件,这样可以提高组件的复用性。
如果通过云数据库实现模糊查询?
小程序里面支持正则表达式的写法,注意一下,正则表达式里面是不能写变量的,所以小程序提供可以用变量去进行匹配的写法。
引用了tcb-ruter,并且加入模糊查询之后的云函数写法:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const TcbRouter = require('tcb-router')
const db = cloud.database()
const blogCollection = db.collection('blog')
// 云函数入口函数
exports.main = async(event, context) => {
  const app = new TcbRouter({
    event
  })
  app.router('list', async(ctx, next) => {
    const keyword = event.keyword
    // 定义一个变量去存放查询条件
    let w = {}
    if (keyword.trim() != "") {
      w = {
        content: db.RegExp({
          regexp: keyword,
         //模式
          options: 'i'
        })
      }
    }
    let bloglist = await blogCollection.where(w).skip(event.start).limit(event.count)
      .orderBy('creatTime', 'desc').get().then((res) => {
        return res.data
      })
    ctx.body = bloglist
  })
  return app.serve()
}

云函数如果只是改了单个的文件,可以只点击修改的那个文件,然后右键增量上传,如果修改的文件比较多,则右键整个云函数进行全部上传。

options:i 忽略大小写
最后在查询语句上添加上where(查询条件)
为了提高数据库查询的速度,我们可以在数据库中添加索引,索引会增大数据库的体积,对于我们来说,就相当于用空间去换时间。

小程序端进行数据操作和云函数进行数据操作的区别:
1.读取数量的区别,小程序端一次读20,云函数一次100
2.权限区别,小程序端受限制,可在云开发界面中进行改变

setData有第二个参数,是一个回调函数,相当于第一个参数完成后再去完成第二个参数的函数,形成一个前后顺序。

textarea的问题之一:就算把textarea放在一个fixed定位的容器中,textarea也要加上个fixed="true"属性才行,否则的话textarea还是会随着页面而滚动=>目前存在的bug之一,后期可能会被修复。

8.云数据库中1对n关系的设计方式

小程序的云数据库是一个非关系型的数据库,所以会涉及到非常多的1对N关系,三种设计模式,
第一种当n不是很多的时候(100以内),把n设计成数组。
第二种,1和n是存在不同的集合中,比如说一个产品对应着几十几百个零件,每个零件也都有自己的很多数据。产品为1,零件为n,我们可以把零件集合的id存到对应产品的一个字段数组中去。这样做的,想要查询某个产品的某个零件,则需要进行两次查询。
第三种模式,n的数据达到成千上万。比说一条博客下面有10万条评论,我们把评论放入blog-comment集合中去,对应的每一条评论我们可以给其增加一个blogid字段。也就是从n那里存对应的1的信息。

获取数据的操作代码适合写在云函数端,但是插入数据库的操作可以写在小程序端,而且在小程序端还可以自动插入openid字段,在云函数要通过event.openid才能获取到

云调用:通过云函数去调用服务端的一些开放接口

9.云函数消息模板推送

模板消息规定我们在想要推送消息的地方必须使用form,必须要配备属性report-submit="true",给点击提交的地方配备属性form-type="submit",这样当我们点击对应按钮的时候,就会去提交按钮所在的form表单,当form提交的时候,会去触发form标签上的bind:submit事件,我们可以把按钮的点击触发事件绑定在form标签的bind:submit上,这样的话 bind:input="onInput"实时获取评论内容的事件也可以去掉




我们通过bind:submit的event参数就可以自动获取`event.detail.value`,同时这个event里面的detail还有一个formId,formId在真机测试中会获取一个真正的id
模板消息的自动推送都是根据这个formId进行推送,有效期7天,每次提交表单都会生成一个唯一的formId

onSend(event) {
// 插入数据库
let content = event.detail.value.content
let formId = event.detail.formId
}

接下来我们在小程序的后台配置消息模板,每个模板都有对应的模板id
新建一个云函数sendMessage,然后建立一个config.json文件(名字是固定的),配置对应的权限

{
"permissions":{
"openapi":[
"templateMessage.send"
]
}
}

只要添加上面的权限后,云函数才有权限调用相关接口,进行模板消息的推送
下面是云函数中调用推送模板消息接口的代码
page配置的是用户点击模板消息后自动打开的页面
data配置的是消息内容,属性名称在小程序后台模板消息详情中的关键词部分可以看到。
templateId对应的就是模板消息的id

// 云函数入口函数
exports.main = async (event, context) => {
const {OPENID} = cloud.getWXContext()
const result = await cloud.openapi.templateMessage.send({
touser:OPENID,
page:/pages/blog-comment/blog-comment?blogid=${event.blogid},
data:{
thing2:{
value:event.content
},
time4:{
value:'刚刚'
}
},
templateId:'0m3J1_zRBBTcGz2MUgdVL06iqCDaKj-3hO8y85us-8I',
formId:event.formId
})
return result
}

接下来就是在小程序端去调用这个云函数

// 推送模板消息
wx.cloud.callFunction({
name: 'sendMessage',
data: {
content,
formId,
blogid: this.properties.blogid
}
}).then((res) => {
console.log(res)
})


子组件触发父组件的事件
子组件内代码
`this.triggerEvent('refreshCommentList')`
父组件调用子组件的标签中监听事件
`bind:refreshCommentList="_getBlogDetail"`

### 10.分享当前页面
分享所点击的博客功能
小程序规定必须是带有open-type="share"的button才可以触发分享事件,用户点击后就会自动触发页面的onShareAppMessage,在事件中我们需要定义转发时标题以及用户点击来时的页面路径path

onShareAppMessage: function(event) {
let blogObj = event.target.dataset.blog
return {
title:blogObj.content,
path:/pages/blog-comment/blog-comment?blogid=${blogObj._id}
// imageUrl:''
}
}

imageUrl对应的是分享卡片上的图片,不设置这个参数会默认截图当前小程序页面的上半部分作为分享图片
注意:开发工具中无法进行分享测试

### 11.获取用户信息的不同方式
#### 第一种方式
想要获取用户的头像直接显示在页面上

想要获取用户的昵称直接显示在页面上

国家
城市
以上方式不需要授权,但是只能显示到页面上,不能用js进行数据操作。
#### 第二种方式
获取已授权用户的信息

wx.getUserInfo({
success:(res)=>{
}
})

#### 第三种方式 
弹出授权框,让用户授权


onGetUserInfo(event){
console.log(event)
}

第三种方式的缺点获取不到用户的openid
#### 第四种方式
传统方式下(服务器用的是自己的)
通过微信服务器调用wx.login获取code(code时效只有五分钟)
,用户端小程序调用wx.reuqest将code传递给后端服务器
后端服务器使用code从微信服务器获取openid和session_key,得到后将用户标识发送给小程序本地存储。
![传统模式.png](https://upload-images.jianshu.io/upload_images/16934985-0b6fc6da9cda7f5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

云开发模式:
用户点击对应按钮,这个按钮可以是一个普通按钮,我们通过触发云函数中的代码来获取用户信息,小程序端调用云函数时,云函数端就可以很简单的获得用户信息,
然后将用户信息返送给小程序端,小程序端可以将用户信息存储到云数据库中去。
![云开发模式.png](https://upload-images.jianshu.io/upload_images/16934985-ba8aecce4942f61f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
实际操作:
在小程序页面放置一个普通的按钮
``
在js中定义事件处理函数

getOpenid(){
wx.cloud.callFunction({
name:'login'
}).then((res)=>{
console.log(res)
})
}

我们可以在用户不知情的情况下获取到用户的openid,但是获取用户信息必须要弹框,用户同意才可以获取。

有些页面我们不想要其滚动,可以在页面的json文件中添加上属性
`“disableScroll: true”`

原来的点击跳转页面是view组件,然后绑定tap事件实现页面跳转,但是小程序提供了组件navigator,点击整个区域,点击跳转界面,界面也是需要在app.json进行配置,然后添加url属性设置跳转页面,添加hover-class设置点击样式,我们可以设置`hover-class:”none“`来取消点击样式。

如果要给小程序的样式设置背景图片,那么不能使用本地图片,解决方法之一是使用链接形式的网络图片,解决方法之二是将图片转换成base64格式。
在页面的css文件中给page标签设置样式就可以对当前的整个页面起作用。

### 12.小程序端的数据存储
在app.js文件中有个onLaunch,每次小程序启动的时候都会自动调用这个回调函数。在这里我们可以定义一个方法,每次小程序启动的时候都先通过调用云函数把当前用户的OPENID获取到,并且存在手机的本地存储中,
wx.setStorage异步的,参数对应的是两个字符串
wx.setStorageSync同步的,参数对应的是一个对象

在云函数中通过
`const wxContext = cloud.getWxcontext()  `然后
wxContext.OPENID就可以直接拿到用户的openid

获取小程序的全局变量:
取到app里的全局变量,首先需要在页面的js文件中取到app
`const app =getApp()`
然后就可以通过app.的形式去拿app中的全局变量了,
如`app.globalData.openid`

在小程序端初始化数据库,
`const db = wx.cloud.database()`
在云函数中初始化数据库,
`const db = cloud.database()`
从小程序端直接拿云数据库中的数据,时间是需要进行格式化处理的,如:日期格式转字符串格式
云函数中根据OPENID查询数据库的代码

const wxContext = cloud.getWXContext()
app.router('getListByOpenid', async(ctx, next) => {
ctx.body = await blogCollection.where({
_openid: wxContext.OPENID
}).skip(event.start).limit(event.count).orderBy('creatTime', 'desc').get().then((res) => {
return res.data
})
})

这个查询操作我们也可以不通过云函数,而直接在小程序端去查询数据库获取相关数据,首先将数据库的规则改为仅创建者可操作,这样我们不用传OPENID,因为只能查到自己创建的数据

_getListByMiniprogram(){
wx.showLoading({
title:'加载中'
})
db.collection('blog').skip(this.data.blogList.length)
.limit(MAX_LIMIT).get().then((res)=>{
console.log(res)
})
}


### 13.生成小程序码
新创建的云函数,如果打算调用对应的开放能力,需要在云函数文件夹中新建一个config.json文件,然后添加上相应的调用权限,比如当前生成小程序码,需要在config.json文件中添加以下代码

{
"permissions":{
"openapi":{
"wxacode.getUnlimited"
}
}
}

调用云函数成功后,云函数返回的是一个buffer二进制数据,这个二进制数据我们是没法用来页面显示的,这时候我们在云函数里面调用云存储,通过buffer数据存储和转化为图片之后再把对应二维码图片的存储位置,也就是fileID,返回到小程序端

exports.main = async(event, context) => {
const wxContext = cloud.getWXContext()
const result = await cloud.openapi.wxacode.getUnlimited({
scene: wxContext.OPENID,
// lineColor: {
// 'r': 211,
// 'g': 60,
// 'b': 57
// },
//背景透明
// isHyaline: true
// page:'pages/blog/blog'
})
// console.log(result)
const upload = await cloud.uploadFile({
cloudPath: 'qrcode/' + Date.now() + '-' + Math.random() + '.png',
fileContent: result.buffer
})
return upload.fileID
}

小程序上线之后,在云函数中可以配置pages属性,加入pages设置识别二维码跳转的是blog页面,那么我们在blog页面的onLoad生命周期的options中通过.scene就可以拿到传递过来的scene参数,这个参数的设置可以帮助我们辨别扫码入口的场景。

接下来在小程序端调用云函数,此时能够拿到小程序码的图片
上传图片之后进行自动预览

wx.previewImage({
urls: [res.result],
current: res.result
})

你可能感兴趣的:(云开发练手项目:音乐播放器(中))