Applet cloud development

Pay attention to knowledge, doubt and good ideas

Heard:

Chapter I Course Introduction

Cloud development (music player project)

Chapter II cloud development introduction and construction project

2.1 traditional development mode

What is serverless (micro service): it can break the physical isolation (front-end and back-end, mobile terminal and server)

Function as a service

Earlier, html css was later extended to dom (jquary), from dom to mvc, and then to mvvm. At the same time, many frameworks (vue react...) were launched

2.2 cloud development and serverless

2.3 cloud development advantages

Fast online

Focusing on core business does not need to consider issues such as joint commissioning, operation and maintenance (why not consider operation and maintenance, because cloud development will have elastic scalability)

Develop a small program independently

data security

In fact, the cloud function is equivalent to nodejs

2.4 opening

By default, there are two environments. One is the formal environment, which is the environment after the launch, and the other is the test environment, which is the environment during development

It is recommended that the test environment be named test and the formal environment be named release. The system will automatically assign the environment suffix.

Then after a while, the file became like this

About expenses https://developers.weixin.qq.com/miniprogram/dev/wxcloud/billing/adjust-payment.html

2.5 initialization

Cloud function:

Front end:

Note that the environment id is filled in

So the problem is, where can I find the environment id

We can fill it in here

At that time, the official online id can be changed to the official online id

The following traceUser is true, which indicates whether the accessed users are recorded in the console and displayed in reverse order

And this GlobalData = {} you can set global properties or methods

Project Profile:

Create three pages: playList, blog and profile

I really don't know what's wrong here

Be sure to remember that it's written like this

One thing to note: Comments cannot be written in json files

2.6 code specification

Knowledge point: take a look at GitHub open source project: Airbnb https://github.com/airbnb/javascript

Change the habitual var into let (scope problem) and use const reasonably

Like this

    const obj=new Object()
    const arr=new Array()

It becomes const obj={} const arr = []

There are also suggestions

onReady() {...} instead of onReady:function() {...}

Then there are abbreviations. For example, username:username can be abbreviated to username, and the abbreviations are best written above and below to write non abbreviated ones

The arrow function is more convenient

Chapter III playlist

3.1 carousel chart components

The wiper component is very simple to use. Here is a knowledge point: block

The teacher wrote like this. I don't know why I want to add a block

<swiper>
  <block wx:for="{{imageUrls}}" wx:key="{{item}}">
    <swiper-item>
      <image src="{{item.url}}"></image>
    </swiper-item>
  </block>
</swiper>
  • Several mode attributes of image (not all columns):
    • scaleToFill: zooming does not maintain the horizontal and vertical scale, which can make the picture fill the container

    • aspectFit pictures are displayed in proportion without overlapping

        + widthFix The height will change automatically, and the width height ratio will remain unchanged (in line with the current demand)
      

3.2 component development

Many front-end frameworks are using component-based development

It is not unique to the front end

It is the encapsulation of independent reusable interaction elements in the field of user interface development

  • The significance of component development
    . Componentization is the layering of implementation, which is a more effective way of code combination
    . Componentization is the reorganization and optimization of resources, so as to make the project resource management more reasonable

    . Componentization is conducive to unit testing
    . Componentization is more friendly to refactoring

Principle: high cohesion, low coupling, single responsibility, avoid too many parameters (a piece of code should solve one demand as much as possible, components should be relatively independent, and rely on other components as little as possible)

What components do we use in the project

song sheet

Song list component

Progress bar component

Lyrics component

Blog card component

Blog control component

Bottom pop-up window assembly

Login component (because in many cases, it is necessary to judge whether the user has logged in)

Search component

3.3 custom song list components

Let's give an example of how to use 1 Playlist file under components: This is a component 2 The json file of playlist under pages is introduced. Give him a name. His specification is generally XX-XXX, and then the colon is followed by path 3 Introduce a name on the playlist page

<view class="playListContainer" wx:for="{{playList}}">
  <s-playList playlist={{item}}>
  </s-playList>
</view>

At this time, how does the attribute value playlist of s-playList receive item s? We need to learn the structure of x-playList in components

/**
   * List of properties for the component
   */
  properties: {

  },

  /**
   * Initial data of components
   */
  data: {

  },

  /**
   * Method list of components
   */
  methods: {
     //For example, when you click the song list, click to jump to the song list, and you need to put the processing function here
  }

Let's sort it out again

Here, how can we transfer data into the playlist attribute of the component

<view class="playListContainer" wx:for="{{playList}}">
  <s-playList playlist="{{item}}">
  </s-playList>
</view>

So you can receive it

  properties: {
    playlist:{
      type:Object
    }
  },

Then it will be displayed on the page

 <view>
   <image src="{{playlist.picUrl}}"></image>
 </view>

The background image of our applet can only be in local or base64 format

I tried to copy the code of Alibaba library to app Wxss cannot be used in components, so we can only update with local images or base6 format. We can directly create a file in the component and put it in the component

Replace more than two lines with ellipsis

.playlist-name{
  width: 220rpx;
  font-size: 26rpx;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

3.4 detailed processing of playback quantity

observers are used here

Note that the following attributes of the listening object should be written as []

When the attribute changes, the following method is automatically executed

  observers:{
    ['playlist.playCount'](val){
      console.log(val);
    }
  },

method:

    // Less than 6 digits, 6-8 digits and more than 8 digits
    // 12345.444   1251232.12  123654789
    // 1 direct rounding 
    // 2. The decimal part is equal to the length of the string - 4 to - 4 + the length to be saved, and then the integer part is equal to the number / 10000, rounded, spliced and returned
    //3 decimal: length - 8 to length - 8+decimal integer: / 100000000
    _tranNumber(num, point) {
      let numStr = num.toString().split('.')[0]
      if (numStr.length < 6) {
        return numStr
      } else if (numStr.length >= 6 && numStr.length <= 8) {
        let decimal = numStr.substring(numStr.length - 4, numStr.length - 4 + point)
        return parseFloat(parseInt(num / 10000) + '.' + decimal) +
          'ten thousand'
      } else if (numStr.length > 8) {
        let decimal = numStr.substring(numStr.length - 8, numStr.length - 8 + point)
        return parseFloat(parseInt(num / 100000000) + '.' + decimal) + 'Hundred million'
      }
    }

Notice toString substring parseInt parseFloat

Now we can print it out, but how to display it on the page

We know that there is a setdata that can modify the contents of playlist. Can you write it like this

The answer is no, it will form an endless cycle, and the developer tools will get stuck

To explain why, obervers is a listener used to listen to the playcount attribute under the playlist object. When the attribute changes, the corresponding method will be executed. When the playcount is assigned, the value of the first playcount changes. When it changes, the function will be triggered and the loop will continue.

What should we do? We can't assign values to playcount in playlist, but we can assign values to others. We can define a count in data and then display the value of count in wxml

Note the meaning of underline (a specification)

3.5wx:key

<view class="list" wx:for="{{list}}">
  <view><checkbox></checkbox> {{item}}</view>
</view>
<button bindtap="change">Click exchange</button>
  change(){
    let length=this.data.list.length
    for(let i=0;i<3;i++){
      let x=Math.floor(Math.random()*length)
      let y=Math.floor(Math.random()*length)
      let temp=this.data.list[x]
      this.data.list[x]=this.data.list[y]
      this.data.list[y]=temp
    }
    this.setData({
      list:this.data.list
    })
   }

At this time, it is found that √ it does not move with the one we selected. At this time, we can add a wx:key = "* this"

If it is an object, you can select a unique attribute, such as id to wx:key, and pay attention not to add {}

3.6, 3.7js asynchronous operation management

promise

Take an example

//event loop  
onLoad: function (options) {
    setTimeout(() => {
      console.log(1);
    }, 1000);
    console.log("2");
  },  It will print out 2 first and then 1

event loop

When the settimeout is executed, he finds that it is an asynchronous task. He will put it in the task queue and then execute the task of the main thread (output 2). When it is found that the main thread is idle and the settimeout time reaches 1s, the task queue will be executed

Of course, there is more than one example. Requests will be sent later. Sometimes the network is good, sometimes the network is poor, and the processing speed of the server and the size of data packets will affect the efficiency. At this time, it is asynchronous, but what about the requested data later

Here is an example: wait for 1s output 1, wait for 2s output 2, wait for 3s output 3

    setTimeout(() => {
      console.log(1);
      setTimeout(() => {
        console.log(2); 
        setTimeout(() => {
          console.log(3);
        }, 3000);
      }, 2000);
    }, 1000);

In this way, I fell into hell

Then there is an asynchronous solution: promise. He has three states: pending fully rejected pending: a person's birthday, I want to give him a gift. He doesn't know whether I will give a gift or not
Fully: if I give it, his status will become fully
rejected: I'm too busy to deliver
Once the state becomes one of them, it cannot be changed

https://www.jianshu.com/p/1b63a13c2701

    new Promise((resolve,reject)=>{
      setTimeout(() => {
        console.log("1");
        resolve()
      }, 100);
    }).then(()=>{
      setTimeout(() => {
        console.log("2");
      }, 2000);
    })

Knowledge point: promise all([ ])

  let p1=new Promise((resolve,reject)=>{
      setTimeout(() => {
        console.log("p1");
        resolve()
      }, 2000);
    })
    
   let p2=new Promise((resolve,reject)=>{
      setTimeout(() => {
        console.log("p2");
        resolve()
      }, 1000);
    })
   let p3=new Promise((resolve,reject)=>{
      setTimeout(() => {
        console.log("p3");
        resolve()
      }, 3000);
    })
  //Wait until all three promise tasks are completed
  Promise.all([p1,p2,p3]).then((res)=>{
    console.log("All completed");
  }).catch((err)=>{
    console.log("fail");
  })
  //Output p2 p1 p3 all completed

There's a question. What happens if one of them fails

This means that one task has failed and the whole task has failed, but it will not prevent the following tasks from continuing

Another method is promise race

It can be seen that even if a task is completed, it will not affect the subsequent execution

So where are these two methods used
For example, after the photos such as blogging are uploaded successfully, perform the following operations
How to judge the timeout? You can use race to open a timer. If the timer is executed successfully and the request is not successful, you can know that the request has timed out

async and await(ES7)

Call foo()
If there is no async awiat, res will be printed first, but the value of res has not been obtained
If so, wait for line 57 to finish

ES6 generator

https://www.jianshu.com/p/83da0901166f

8.3 insert and read song list data into cloud database

Why put it in the cloud database

We set up a trigger to fetch data regularly every day to ensure that the latest data is fetched every day. We also need to compare with the old data and insert the missing data into the database

Knowledge point: how to insert data into the database by timing trigger cloud function

Send request: request promise, axios and other third-party libraries

One note: if it is determined that node has been installed, but the wechat developer tool is not displayed as an internal command when installing the package, you can right-click the administrator to open the wechat developer tool

Official example of request promise Library

rp('http://www.google.com')
    .then(function (htmlString) {
        // Process html...
    })
    .catch(function (err) {
        // Crawling failed...
    });

What do we mean by that?

If the request is successful, return res to playlist, and then we print out playlist

Can we print it to the debugger we usually use? No, the cloud function code belongs to the back-end code. The back-end log will not be printed to our debugger, but will be printed to the cloud function log

Then right-click the folder to upload and deploy

We found that the input is of string type, and what we need is json format

Then we can learn how to put it into the database

Create a new collection first

Then insert data into the database. Note that only a single insert is allowed, so we need to traverse the array and insert one by one

  • Supplement wechat official reference code:
    • https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.html
    • https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.collection.html
    • https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.add.html
    • https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.serverDate.html
  • Three point operator
    • https://blog.csdn.net/wilie_C/article/details/109133238

complete

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()
const db=cloud.database() //Cloud database initialization
const rp=require('request-promise')
const URL = 'http://musicapi.leanapp.cn/top/playlist/highquality/%E5%8D%8E%E8%AF%AD'

// Cloud function entry function
exports.main = async (event, context) => {
  const playlist=await rp(URL).then((res)=>{
    return JSON.parse(res).playlists.splice(0,6) //Because there are 20 items in total, we only need 6 items (more than 6 items are needed if it is wrong, so split should not be added)
  })
  // console.log(playlist);
  for(let i=0;i<playlist.length;i++){
    await db.collection('playlist').add({    //Get the collection of playlist s. / / Note asynchronous operation
      data:{
        ...playlist[i],          //Note the use of the three-point operator
        creatTime:db.serverDate()     //Get server time
      }
    }).then((res)=>{
      console.log("Insert successful");
    }).catch((err)=>{
      console.log("Insert failed");
    })
  }
}

After the deployment, I went to the database and found that there were data (but not 6). I don't know why I wondered about the update: it was indeed 6. It may have been accumulated because I saved it too many times

3.9 de duplication of song sheet data

If we read the song list information again, he will put this information into the database, so that there will be more and more things in the database and they will be repeated

At this time, we need to judge whether the pre save exists (according to the id) before entering the database
Let's get the data in the previous database

  const list=db.collection("playlist").get()

However, there are restrictions on the data obtained, and only 100 pieces can be obtained at most; Get up to 20 pieces of data from the small program side. How to break through the limit? Let's learn in the next class (I went to see it and updated it. Now it's 1000 pieces, but I'll learn it)

Idea: first, get the list in the database and the playlist obtained by sending a request, and define an array newData to store the new data

Before cloud function entry: const playlistcollection = dB collection('playlist')

const list = await playlistCollection.get() //Get the existing data of the song list in the database for de duplication
const playlist = await rp(URL).then((res) => { //Get request data
   return JSON.parse(res).playlists.splice(0, 6) //Because there are 20, we only need 6
  })
  // console.log(playlist);
const newData = [] //Store new data
  for (let i = 0; i < playlist.length; i++) {
    let flag=true   //The flag to judge whether it is repeated. true indicates that it is not repeated
    for (let j = 0; j <list.data.length;j++ ){     //Note that this is data      
      if(playlist[i].id===list.data[j].id){
        flag=false
        break
      }
    }
    if(flag){
      newData.push(playlist[i])
    }
  }

Therefore, when inserting the database, change the playlist to newData,... playlist[i] to... newData[i]

3.10 break through the limit of data acquisition

  • Wechat official documents

    • limit:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.limit.html
    • skip:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.skip.html
    • Timing trigger https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/triggers.html
    • Number of fetched databases https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.count.html
  • reduce:

  • https://www.cnblogs.com/amujoe/p/11376940.html

  • https://www.liaoxuefeng.com/wiki/1022910821149312/1024322552460832

    An example

var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25
  • concat

We can get up to 100 pieces of cloud database data. How to optimize it

const list = await playlistCollection.get() needs to be changed

  1. Don't forget that it is an asynchronous operation to obtain the total number of data, because this value is required for calculation later
  2. playlistConllection.count() gets an object, and the number of objects to get is more total
    https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/collection/Collection.count.html
  3. Then you need to judge how many times you want to take it. First define a constant Max on it_ Limit = 100. If the total is 221, it needs to be taken three times. We define a batchTimes=total/MAX_LIMIT is then rounded up to get the number of times
  4. Then push it into an array, so that there are multiple promise objects in tasks
for(let i=0;i<batchTimes;i++){
 //From 0 to 100, from 100 to 200, from 200 to 221
   let promise= playlistCollection.skip(i*MAX_LIMIT).limit(MAX_LIMIT).get()
   tasks.push(promise) 
  } 
  1. Then define an array data [] in a list. Note why a data array should be defined, because we also saw in the last lesson that what is returned is actually the things in data (if(playlist[i].id===list.data[j].id)). At the beginning, we are the data transferred into the database. Under the data, there is the data requesting the network and a system time

  if(task.length>0){
      //Do the following after you've finished
    list=(await Promise.all(task)).reduce((acc,cur)=>{
      return {
        data:acc.data.concat(cur.data)
      }
    })
  }

  1. Review: first, get the total number of entries in the set, but return an object, so pass through the object Total gets the total number of pieces of current data, and then the total number / maximum number of times is rounded up to get the number of times of fetching by times. Then, define a task and put the set of each promise. When fetching, skip the corresponding data each time by i * maximum data and fetch it by times. When all tasks are completed, iterate the data and give the data to the list; After that, read the link to obtain the data, then compare it for de duplication, and then insert the corresponding data into the database

Number limit code completed

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()
const db = cloud.database() //Cloud database initialization
const rp = require('request-promise')
const URL = 'http://musicapi.leanapp.cn/top/playlist/highquality/%E5%8D%8E%E8%AF%AD'
const playlistCollection = db.collection('playlist')
const MAX_LIMIT = 10//Maximum number of information obtained

// Cloud function entry function
exports.main = async (event, context) => {
  // const list = await playlistCollection.get() / / get the existing data of the song list in the database for de duplication
  // Because of the limitation of obtaining up to 100 data in the database, we need to optimize
  const countResult = await playlistCollection.count() //This is an object
  const total = countResult.total //In this way, the number of database information is obtained
  const batchTimes = Math.ceil(total / MAX_LIMIT) //Number of data fetches
  const task = []       //An array of promise objects
  for(let i=0;i<batchTimes;i++){
    let promise=playlistCollection.skip(i*MAX_LIMIT).limit(MAX_LIMIT).get()
    task.push(promise)
  }
  //Notice why it's written here
  let list={
    data:[]
  }

  if(task.length>0){
      //Do the following after you've finished
    list=(await Promise.all(task)).reduce((acc,cur)=>{
      return {
        data:acc.data.concat(cur.data) //I still don't understand what this means here. Is it the value of list=data, or is there a data under the list
      }
    })
  }

  const playlist = await rp(URL).then((res) => { //Get request data
   // return JSON.parse(res).playlists.splice(0, 6) / / because there are 20 pieces in total, we only need 6 pieces (take back this sentence or a little more data)
       return JSON.parse(res).playlists
  })
  // console.log(playlist);
  const newData = [] //Store new data
  for (let i = 0; i < playlist.length; i++) {
    let flag = true //The flag to judge whether it is repeated. true indicates that it is not repeated
    for (let j = 0; j < list.data.length; j++) { //Note that this is data      
      if (playlist[i].id === list.data[j].id) {
        flag = false
        break
      }
    }
    if (flag) {
      newData.push(playlist[i])
    }
  }
  for (let i = 0; i < newData.length; i++) {
    await playlistCollection.add({
      data: {
        ...newData[i], //Note the use of the three-point operator
        creatTime: db.serverDate() //Get server time
      }
    }).then((res) => {
      console.log("Insert successful");
    }).catch((err) => {
      console.log("Insert failed");
    })
  }
  return newData.length
}

Timed trigger cloud function

Official documents: https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/triggers.html

In config JSON

{
 "triggers":[
   {
     "name":"myTrigger",
     "type":"timer",
     "config":"0 0 10,14,16 * * * *"
   }
 ]
}

Be sure to upload triggers

You can set the timeout of cloud function to be longer

3.11 pull up load pull-down refresh

We create a new music folder in cloudfunction for data retrieval

We need to know that the new song list should be in the front, and the old song list should be in the back,

So what sort should be used? orderBy("corresponding field", "desc") (desc is in reverse order, and the default is positive order)

We need to understand this first

exports.main = async (event, context) => {
  cloud.database().collection('playlist')
  .skip(event.start).limit(event.count)     //From which article to which article
  .orderBy("creatTime",'desc')
  .get()
  .then((res)=>{
    return res
  })
}

You know, this is an asynchronous operation, so cloud Database()... Add await before it, and then return await cloud database(). collection(‘playlist’)……

At this time, we need to go to the playlist folder in the miniprogram to retrieve the data in it

  onLoad: function (options) {
    wx.cloud.callFunction({
      name:'music',
      data:{
        start:this.data.playList.length,    //The usage here is wonderful
        count:MAX_LIMIT                    //The value defined above is 15
      }
    }).then((res)=>{
      console.log(res);
    })
  },
      

perfect

 onLoad: function (options) {
    wx.showLoading({
      title: 'Loading',
    })
    wx.cloud.callFunction({
      name:'music',
      data:{
        start:this.data.playList.length,
        count:MAX_LIMIT
      }
    }).then((res)=>{
      console.log(res);
      this.setData({
        playList:res.result.data
      })
      wx.hideLoading()
    })
  },

Slide to bottom loading:

Write to this hook function onReachBottom: function() {},

We will find that we still need requests, but we need to copy and paste a copy of the request code. We need to optimize the encapsulation of sending requests into a function

However, we also found that there is a problem when we send a request in the bottom function, because we are requesting the last 15 data of the current length, and we need to splice the first 15 data

 _getPlaylist(){
    wx.showLoading({
      title: 'Loading',
    })
    wx.cloud.callFunction({
      name:'music',
      data:{
        start:this.data.playList.length,
        count:MAX_LIMIT
      }
    }).then((res)=>{
      console.log(res);
      this.setData({
        playList:this.data.playList.concat(res.result.data)
      })
      wx.hideLoading()
    })
  },

What about pull-down refresh? There is another problem with setting the playlist blank before sending the request. There are three points in the pull-down refresh. The point where the data request is completed is still there, so a blank sentence should be added to the request data

3.12 route optimization TCB router

  • A user can only create 50 cloud functions in a cloud environment

  • Similar requests are classified into the same cloud function for processing

    • For example, music is used to obtain the song list information. This is a function. In the later class, we also need to obtain the songs in the current song list and the url address corresponding to the song. We also need to obtain the song lyrics information. Do we need to create a file independently for these functions? No, similar can be put in one file. We can put all these in music files. For blogs, we will create a folder to put blogs
    • TCB router is needed at this time
    • Related links:
      • https://blog.csdn.net/hongxue8888/article/details/104599794/
      • https://github.com/TencentCloudBase/tcb-router
      • https://www.v2ex.com/t/491897
  • TBC router is a koa style (a framework of node) cloud function routing library

After the first layer is executed, the second layer is executed. After the second layer is executed, the third layer will not be executed back until the last middleware is executed

Now let's take an example. Two buttons are written in the demo. When clicked, the same cloud function is called, but one is to obtain music information and the other is to obtain movie information. That is to say, two different routing requests are received in the same cloud function. getMusicInfo is to obtain personal information, music name and music type. getMovieInfo is also personal information, movie name and movie type

exports.main = async (event, context) => {     
  const app=new TcbRouter({event})
  //This is applicable to all routes
  app.use(async(ctx,next)=>{
    ctx.data={},
    ctx.data.userInfo=event  //We need to know that the event in the cloud function entry function has personal information
    await next()
  })
  //This is for music only
  app.router('music',async(ctx,next)=>{
    ctx.data.musicName="Count ducks",
    await next()
  },async(ctx,next)=>{       //After musicName gets the data, execute the following steps
    ctx.data.musicType="Nursery rhyme" 
  //At this time, the data can be returned
    // ctx.body returns data to applet
    ctx.body={
      data:ctx.data
    }
  })

  //This is for movie only
  app.router('movie',async(ctx,next)=>{
    ctx.data.movieName="Spirited away",
    await next()
  },async(ctx,next)=>{       //After movieName gets the data, execute the following steps
    ctx.data.movieType="Nursery rhyme" 
  //At this time, the data can be returned
    ctx.body={
      data:ctx.data
    }
  })

  //Finally, don't forget to return app serve();
  return app.serve();
}
  //Get music information
  getMusicInfo(){
    wx.cloud.callFunction({
      name:'tcbRouter',  //The cloud function file is called tcbRouter
      data:{
        $url:'music'
      }
    }).then((res)=>{
      console.log(res);
    })
  },
  //Getting movie information is the same

So let's change the code under music:

// Cloud function entry file
const cloud = require('wx-server-sdk')
const TcbRouter=require('tcb-router')
cloud.init()
//Music related databases are put here
// Cloud function entry function
exports.main = async (event, context) => {
  const app=new TcbRouter({event})
  app.router('playlist',async(ctx,next)=>{
    ctx.body=await cloud.database().collection('playlist')  //ctx. After the body is to return
    .skip(event.start).limit(event.count)
    .orderBy("creatTime",'desc')
    .get()
    .then((res)=>{
      return res
    })
  })
  return app.serve()
}

Here, we also need to add a parameter $url to the data, because it was only written in which cloud function. Now we use tcbrouter to specify the route name in it

3.13.14 user defined list components

Togolist event should be bound in our component

Then jump to musiclist (a new page, which is used to click the song list and jump to the music list page). At this time, you need to transfer parameters

url: '.../.../pages/musiclist/musiclist?playlistId='+this.properties.playlist.id,

Then receive the parameters in the onload of musiclist

Then we need to get the music list corresponding to the playlistid

API interface: https://music.163.com/api/playlist/detail?id= Pay attention to the ID in the song list. If you don't log in, you can't get all the data without cookie s

  music In cloud function
    //Get the song corresponding to the song list according to the id
  url='https://music.163.com/api/playlist/detail?id='+parseInt(event.playlistId)
  let options = {
    method:"POST",
    url,
    headers: {
    	cookie:'MUSIC_U=XXXXXX'          //It can't be released due to security problems. In the future, cookie s will be obtained according to login and placed here
    },
  }	
  app.router('musiclist',async(ctx,next)=>{
    ctx.body=await rp(options)
    .then((res)=>{
      return JSON.parse(res)
    })
  })
  return app.serve()
//musiclist

  onLoad: function (options) {
    console.log(options);  //Get id
    wx.cloud.callFunction({
      name:'music',
      data:{
        playlistId:options.playlistId,          //Pass the playlistId into the event in the cloud function
        $url:'musiclist'
      }
    }).then((res)=>{
      console.log(res);
  })
  },

So we can get the data

Then you can render to the page

This place uses transparency, filter level, background map and other knowledge points

The background is a Gaussian Blur effect. We tile the photo, then make a blur effect, and then cover it with a black mask

Then the following songs should also be written as a component like the last song list, but in order to show that there is more than one method, another method is used this time
(what was it like last time? The original page circulates, and then the component page gets a piece of data to write the style of a piece of data, and then circulates to the end. Finally, each piece of data finally goes to the component page, and finally shows that all data are written by components
This time, we sent all the data to the component page and circulated on the component page)

There is a pit here: after the flex layout, flex:1 is still beyond the box. What should we do at this time
https://blog.csdn.net/qq_41075132/article/details/82865248

There is a wonderful place: if a page jumps over from the previous page and we focus on writing the following page, we know that the compilation mode can be changed, but we can't do without parameters. We can add startup parameters below (I don't know why the string behind doesn't need to add "" and pay attention to the equal sign, not:)

Click to turn red. The basic operation is not to paste the code. The idea is to click to pass the parameter and write the parameter in data. Then judge that the current id is not equal to the id in data on the page, and add the style with ternary operator

There is a knowledge point that I haven't noticed before: I only know that currentTarget is accurate before. Why does the receiving parameter currentTarget have a value but target doesn't? Because the event is bound to the container. When clicking a song, there is an event mechanism (bubble) in js. Because js propagates layer by layer from the event source to the parent element, when clicking name, it is equivalent to clicking container and triggering the event processing function, but the real click is name

Three elements of events: event source (who triggers) event handler function event object (like event) event type (tap). We need to distinguish who the current event source is. If you click name, the real event source is actually name. Target refers to the real event source, but there is no data XX above name. Currenttarget refers to the element binding the event, which is our container element

3.15 summary

Chapter IV play page

4.1

4.2 layout of playing page

After our musiclist is finished, we find that the data required for playing the page is in musiclist (this is the case of the teacher. His api is different from mine, so I don't have a music url). Of course, we can use the calling api to obtain information according to the id (because there are more requests than the previous method, the user experience will be bad)

Let's first look at the teacher's method. This method can be stored locally, and then taken out of the local cache on the playback page. There is no need to worry about the accumulation of data here, because if the key is the same, the value value will be overwritten

Knowledge point: if you want to pass multiple parameters between pages, you can use the & symbol to connect

At this time, we should take out the cache and put it in a global variable, because it does not need to be displayed in the interface, and the index being played does not need to be displayed on the page

// pages/player/player.js
let musiclist = []
// index of the song being played
let nowPlayingIndex = 0
Page({

  /**
   * Initial data of the page
   */
  data: {
   
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function(options) {
    console.log(options)
    musiclist = wx.getStorageSync('musiclist')
    nowPlayingIndex:options.options.index
  },

Then you can define a special method to get the current information and put it in onload after writing

It's really hard to find an api here. I can't find an interface to play music. I want to buy a server myself, learn how to build it, and then it can be unobstructed

Insert: Server

After registration

Reset instance password

Security group rules:

The teacher added these three

Install ssh client software

Client: filezilla: Terminal: xshell

I found the api on the Internet again... I won't learn to build a server first

4.2 follow 4.2 above

I like to put some things into data. Some things about the interface display can be put into data. Some things can be put directly on the outside or in any function. There is no need to put data

When we click on the list page, we need to transfer two parameters to the player page. One is index, the other is ID, which is to get the url and lyrics of the song, and the other is index, which is to judge which piece of data in the cache

My question is, why should we put the cache? Can't we directly pass the reference (song name, singer, cover) when jumping?

Learn how the picture fills the whole screen

Our blur effect:

.container{
  /* background: url(http://p4.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg); */
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  filter: blur(40rpx);
  opacity: 0.7;
  z-index: -1;
}
.mask{
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #222;
  z-index: -2;
}

The singing arm here is added with:: after

4.3 iconfont Font Icon

be careful

4.4 function realization of music control panel

Pay attention to the use of BASE_URL to simplify code

Get the globally unique background music player (key words: unique): equivalent to the singleton mode in js

It was in the player I think it makes sense to see the teacher's request for data in the cloud function. Then the cloud function requests url and lyric at the same time and returns only after they are successful. However, I don't think it can be written like this next time, because some of them return slowly and others return quickly. If the url has requested data, there is no need to wait for the lyrics, We can't increase the waiting time in order to reduce the amount of code. Then there's another problem. Sometimes the official lyrics can't be requested. We need to judge whether there are lyrics at this time, otherwise there will be an error when returning. If there are no lyrics, just return empty.

I haven't noticed this before,

Note single quotation marks:

<text class="iconfont {{isplaying?'icon-bofang':'icon-bofang1'}}" bind:tap="togglePlaying"></text>

4.5 function realization of music control panel (2)

One thing is very good. Our last video is because we click on the previous song and the next song on the current page and need to publish and subscribe. In this case, because we use the cache and take it out of the cache, it is very convenient to implement the previous song and the next song

There are also details to pay attention to. For example, it is suspended at the beginning. After obtaining the data, it will be played. When playing the last song, it will return to the first one. When playing the first song, it will return to the last one

4.6 progress bar components

Be sure to remember the usual centering methods

  left: 50%;
  transform: translateX(-50%);

Our progress bar should be made by ourselves. Why not give it to the official? We need to contact a case. For example, if you slide left, the data will be deleted on the right. Click delete, and the data will be deleted (the scope of application of the slider is relatively small)

This time I learned sliding components and movable components

About movable Animation:

https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.html

direction damping x

How do we get the information of elements in js

    _getMovableDis(){
      //Now it's in the component, so use this instead of wx
      const query=this.createSelectorQuery()
      query.select('.movable-area').boundingClientRect()
      query.select('.movable-view').boundingClientRect()
      query.exec((rect)=>{
        console.log(rect);
        movableAreaWidth=rect[0].width
        movableViewWidth=rect[1].width
        console.log(movableAreaWidth,movableViewWidth);
      })
    },

Knowledge point: how to judge whether it is undefined? Can XXX==undefined? No, because null is also equal to undefined, which is not strict

The third sign can be either typeof XXX = = 'undefined'

Then the format time

    _dateFormat(second){
      const minute=Math.floor(second/60)
      second= Math.floor(second%60)
      return {
        "min":minute,
        "sec":second
      }
    }
    //Format time
    _dateFormat(second){
      const minute=Math.floor(second/60)
      second= Math.floor(second%60)
      return {
        "min":this.parse0(minute),
        "sec":this.parse0(second)
      }
    },
    //Zero filling operation
    parse0(sec){
      return sec<10?'0'+sec:sec
    }
 //Time of progress bar
    _setTime(){
      const duration=backgroundAudioManager.duration
      console.log(duration);
      const durationFMT=this._dateFormat(duration)
      console.log(durationFMT);
      this.setData({
        "showTime.totalTime":`${durationFMT.min}:${durationFMT.sec}`
        //Here, the teacher said that brackets should be added outside. I didn't add them, so I gave them value
      })
      console.log(this.data.showTime.totalTime);
    },
    //Format time
    _dateFormat(second){
      const minute=Math.floor(second/60)
      second= Math.floor(second%60)
      return {
        "min":this.parse0(minute),
        "sec":this.parse0(second)
      }
    },
    //Zero filling operation
    parse0(sec){
      return sec<10?'0'+sec:sec
    }

4.8 progress bar and playback time linkage

Here, the width we obtained earlier comes in handy

But,

We don't need to set data so many times. It's OK to trigger once a second. So how can we trigger only once a second? We can get the previous seconds. If the seconds have been set, we won't set them anymore

      backgroundAudioManager.onTimeUpdate(() => {
        const currentTime=backgroundAudioManager.currentTime
        const duration=backgroundAudioManager.duration
        //The string is divided according to the point. If it is not equal to currentSec, setdata is used
        
        if(currentTime.toString().split('.')[0]!=currentSec){
          console.log(currentTime);//Are there fewer times to test setdata
          const currentTimeFmt=this._dateFormat(currentTime)
          this.setData({
            //Playing time, slider distance, progress
            "showTime.currentTime":`${currentTimeFmt.min}:${currentTimeFmt.sec}`,
            movableDis: (movableAreaWidth - movableViewWidth) * currentTime / duration,
            progress: currentTime / duration * 100,
          })
          currentSec=currentTime.toString().split('.')[0]
        }

4.9 drag progress bar (applet component)

At this time, you need to add an event handler function to the slider

bindtouchend is triggered when the touch is completed

 <movable-view direction="horizontal" class="movable-view"
      damping="1000" x="{{movableDis}}" bindchange="onChange" bindtouchend="onTouchEnd">
 </movable-view>

We found that the onChange event will also be triggered when it is not touched. It has a source attribute. When it is triggered without touching it, the attribute is empty, but when it is triggered by dragging, the source is touch, so we need to judge whether the souce is touch

    onChange(e){
      // console.log(e);
      //Judge whether it is triggered by dragging
      if(e.detail.source==="touch"){
        console.log(e);
        //Note that we don't have setdata. Why? Because if so, it will frequently assign setdata to progress and movableDis, and the applet won't move. We should assign the value when the user lets go. Here, save the value and give it to the onTouchEnd event handler later
        // Question: what does progress and movableDis mean here?
        this.data.progress=e.detail.x/(movableAreaWidth-movableViewWidth)*100
        this.data.movableDis=e.detail.x
      }
    },
    onTouchEnd(){
      const currentTimeFmt=this._dateFormat(Math.floor(backgroundAudioManager.currentTime))
      this.setData({
        progress:this.data.progress,
        movableDis:this.data.movableDis,
        "showTime.currentTime":currentTimeFmt.min+':'+currentTimeFmt.sec
      })
      console.log(this.data.progress);
      backgroundAudioManager.seek(duration*this.data.progress/100)
    },

4.10 automatically play the next song (with component communication) and some optimization

We found that this will happen in the end. We should deal with it. After playing, it is onEnded

Knowledge point: we should now call onNext() in the player in the component. How should we call it

Component communication is used here

Trigger event ("XXX") and then bind the event (custom event) bindXXX="onNext" to the calling component

We'll also find

He sometimes flashes back. Why

There was a conflict

How to solve it? We can add a lock (knowledge point). We globally define a variable ismoving (whether the flag is dragging). If it is dragging, onTimeUpdate will not be executed

Set it to true in onChange, set it to false in onTouchEnd, and then judge in onTimeUpdate. If it is false, execute the code we wrote before

But the applet still has a hole: according to our thinking, after we release our hand, this variable should be false, but the applet has the probability to trigger onchange again to turn it into true

How can we solve it? Every time we play, we will trigger onplay, and we will set false again

We should pay attention not to frequent setdata in small programs, and we should add more judgment to avoid it

4.11-12 lyrics component (regular)

Note that we know that the lyrics component will be displayed by clicking, but would you like to use if else or hidden Use hidden frequently

A question: why should we pass the state into the lyrics component instead of judging directly in the player

So we can listen to the lyrics

We need to deal with it

Let's split according to the newline first,

 methods: {
    _parseLrc(sLrc){
      let line=sLrc.split('\n')
      let _lrcList=[]
      // console.log(line);
      line.forEach((elem)=>{
        let time = elem.match(/\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]/g)
        if(time!=null){
          // console.log(time); [00:00.39]
          let lrc=elem.split(time)[1]
          // console.log(lrc);   // Leave only the lyrics
          let timeReg=time[0].match(/(\d{2,}):(\d{2})(?:\.(\d{2,3}))?/)
          // console.log(timeReg); 
          // 0: "02:03.26"
          // 1: "02"
          // 2: "03"
          // 3: "26
          //Convert time into seconds
          let time2Seconds=parseInt(timeReg[1])*60+parseInt(timeReg[2])+parseInt(timeReg[3])/1000
          //Each line of lyrics represents the time and the corresponding text
          _lrcList.push({
            lrc,
            time:time2Seconds
          })
        }
      })
      this.setData({
        lrcList:_lrcList
      })
      console.log();
    }
    
  },

4.13 lyrics linkage (component to component communication + scroll top)

What is the idea of this? We need to get the time in onTimeUpdate of progressbar and match it with the time of lrcList in our lyrics component, so we can know the corresponding lyrics, and then turn the lyrics into highlighted ones,

Therefore, we need to know how to transfer the value of one component to another, and the last component communication is used. However, it is different from the last one. This time, it is the value transmitted and the communication between components. Question: what is the difference between this communication and publish subscribe mode?

The third figure is selected according to the class name

You can see that the lyrics can be printed in lyric

Let's consider how to match

update(currentTime){
      console.log(currentTime);
      let lrcList=this.data.lrcList
      if(lrcList.length==0){
        return 
      }
      for(let i=0,len=lrcList.length;i<len;i++){
        if(currentTime<=lrcList[i].time){
          this.setData({
            nowLyricIndex:i-1
          })
          break
        }
     }
    },

We can see that when a lyrics is played, the current lyrics will turn red, but we usually listen to music, and the lyrics being played are scrolled to the middle of the page

At this time, the scroll top in the scrollview is used, and the unit is px, and our lyric is rpx, so how to convert them

Get mobile information:

wx.getSystemInfo

Then you can get the px unit of the mobile phone, divide it by 750 to get the size of 1rpx, and then multiply it by the lyrics height (64) we set to get the px size corresponding to a lyrics

Here's another problem

Why didn't you move

If you drag it later, it will always be longer than the time corresponding to the lyrics, so you won't execute the following sentence

Let's see how to solve it

  update(currentTime) {
      // console.log(currentTime)
      let lrcList = this.data.lrcList
      if (lrcList.length == 0) {
        return
      }
      //Solve the problem that the lyrics do not slide to the bottom when the progress bar is dragged to the end
      if (currentTime > lrcList[lrcList.length - 1].time) {
        if (this.data.nowLyricIndex != -1) {
          this.setData({
            nowLyricIndex: -1, //No lyrics to highlight
            scrollTop: lrcList.length * lyricHeight
          })
        }
      }
      for (let i = 0, len = lrcList.length; i < len; i++) {
        if (currentTime <= lrcList[i].time) {
          this.setData({
            nowLyricIndex: i - 1,
            scrollTop: (i - 1) * lyricHeight
          })
          break
        }
      }
    },

Then solve the problem of displaying lyrics of songs without lyrics

  observers: {
    lyric(lrc) {
      if (lrc == 'No lyrics') {
        this.setData({
          lrcList: [{
            lrc,
            time: 0,
          }],
          nowLyricIndex: -1
        })
      } else {
        this._parseLyric(lrc)
      }
      // console.log(lrc)
    },
  },

414 million bug fixes

Click the previous or next song on the play music page to return to the list page or highlight the previously clicked song

Let's think about it first. The highlight is determined by the index setting in the playlist. The previous song and the next song are implemented by onNext and onPrev executed in the player

At this time, we need to transfer values again. This time, we use the global app js

Idea: save the current musicId in player and then app Save this id in JS, and then musicList from app JS

app,js:

  onLaunch: function () {
    this.globalData={
      playingMusicId:-1
    }
  },
  setPlayingMusicId(musicId){
    this.globalData.playingMusicId=musicId
  },
  getPlayingMUsicId(){
    return this.globalData.playingMusicId
  },

player.js:

  app.setPlayingMusicId(musicId)

musicList:

  pageLifetimes:{
    show(){
      let playingId=app.getPlayingMUsicId()
      this.setData({
        playingId
      })
    }
  },

The system playback pause is out of sync with the applet

How to solve it? We found that clicking the system play pause will trigger the online and online events in the progress bar

At this time, we can set up the player The problem of displaying in JS now becomes how to trigger onpay and onpause. After setting the value of displaying in another component, the knowledge points of communication between components are used

progress-bar:

It can be further improved

Exit and play the same concert again

Episode: heard:

4.15 next chapter notice

Chapter V discovery page

5.1

5.2 search component

Alibaba icon library can also use i tag

Details: small icon click area? Then give him a big box and click the box to trigger the event

then

Here we take it out separately to make components

We can write the placeholder in the search component directly on the page, but it may not be applicable if we refer to other places, so we'd better write the placeholder in the logical layer

Then, there is a search icon in our component. You can create a file in the component and import the Alibaba Icon Library Icon

Another method: component external style class

But there is a hole to note. We find that the style cannot be modified (style isolation). We can add another class name

5.3 bottom pop-up layer (with a knowledge point slot)

Let's take a look at the third reference style method

Link:

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html

The modalshow data passed into the component is false, and then it becomes true after clicking the Publish button. If the component judges true, it is not hidden, and if false, it is hidden. Then there is the cross. If you click, it will not be displayed

Then how is this function realized

The contents are not written dead - slot s are used, which is equivalent to placeholders

Occupy the pit in the sub assembly and fill the pit in the parent assembly.

After using the slot, the use of components will be more flexible

5.4 authorized components

wx.getsetting:

https://developers.weixin.qq.com/miniprogram/dev/api/open-api/setting/wx.getSetting.html

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html

Judge whether the user has authorization through the above getsetting. If so, get the user information through getUserInfo. If not, pop up our bottom pop-up window to let the user log in.

For this bottom pop-up window, we specially make a bottom modal to put the shell of the pop-up window, because it is not only used here

First, we create a login component and pass it into modalShow. Then the parent element is a component, and the child element is to be in the pop-up component at the bottom (fill the slot). Previously, the pop-up component at the bottom has a slot named modal content, and the content written in login will be displayed in the pop-up window.

Then write a button in the login component to obtain user information through the button,

5.5 edit page

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-1rukesyg-1613176474138) (C: \ users \ administrator \ appdata \ roaming \ typora \ typora user images \ image-20210205202017759. PNG)]

There is a small function here. When inputting, there will be the number of input words, and then the maximum number of words is 140 words,

It should also be noted that our simulator can see the following footer, but on the real machine, the input method will cover the footer. We need to solve this problem

Basic layout:

Note that native components, such as textarea camera, are separated from webView. They have the highest level. Non-native components can use z-index to set the level and adjust the hierarchical relationship. However, native components, no matter how large the z-index of other elements is, cannot cover the native components. In addition, native components cannot be displayed in containers, such as scroll view, wiper and movable view. Also, he does not support css animation.

We can't set absolute positioning for textarea here. There's also a hole: bind can't be used for bind:tap can be used for bindtap

  onInput(event) {
    // console.log(event.detail.value)
    let wordsNum = event.detail.value.length
    if (wordsNum >= MAX_WORDS_NUM) {
      wordsNum = `The maximum number of words is ${MAX_WORDS_NUM}`
    }
    this.setData({
      wordsNum
    })
    content = event.detail.value
  },

Then we will solve the problem that the input method on the mobile phone will block the footer. textarea has an attribute auto focus. Let's add two more attributes

Change the bottom of the footer because it is absolutely positioned. So just change the value of bottom

5.6 picture selection logic

We set the maximum number of pictures to 9, and select all 9 plus signs to disappear

Add an event handler function on the icon of the selected picture, which uses Wx There is a count in chooseimage

We can define an array. After selecting the pictures, we can save them into the array, and then judge the number of pictures that can be selected according to the length of the array

If splice is used for deletion, then the plus sign should come out again after deletion

Insert the content of the previous section

We don't add height to the footer here, which is directly supported by elements

.footer {
  display: flex;
  align-items: center;
  position: fixed;
  bottom: 0;
  padding: 20rpx;
  width: 100%;
  box-sizing: border-box;
  background: #f1f1f1;
}

5.7 multi file upload cloud storage (knowledge point cloud storage, regular)

We put the data into the cloud database,

Then our pictures will be put into the cloud storage. The cloud storage will have a fileID, which is the ID of our unique cloud file

Therefore, our database stores the fileId of the content and picture, as well as the openid, nickname, avatar and time of the user,

1. Picture - > cloud storage fileID cloud file ID

2. Data - > cloud database

Picture upload:

wx.cloud.uploadFile

https://developers.weixin.qq.com/minigame/dev/api/network/upload/wx.uploadFile.html

Let's create a new file blog in cloud storage

then

publish(){
   // Because cloud storage is a single file upload, we need to upload multiple files, so we need to add a cycle
    for (let i = 0, len = this.data.imgArr.length; i < len; i++) {
      let item = this.data.imgArr[i]
      // File extension
      let suffix = /\.\w+$/.exec(item)[0]
      wx.cloud.uploadFile({
        //This is to prevent duplicate names. If the names are duplicated, the previous pictures will be overwritten
        cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
        filePath: item,
        success: (res) => {
          console.log(res)
          resolve()
        },
        fail: (err) => {
          console.error(err)
          reject()
        }
      })
    }
  },

Regular there \ w: Alphanumeric underscore +: more than one $: ends with this

Store knowledge in cloud database (8.5 to 3 points)

We need to know that uploading to cloud storage is an asynchronous process, and storing in cloud database requires the fileID returned by cloud storage

After multiple image upload tasks are completed, we need to splice the fileId into an array, and then store the array in the database

At this time, promise is used again all

Idea: first define a promiseArr array to store the promise object obtained in each cycle
After the cycle, promise All (promise ARR) then Then there are some operations stored in the database
At this time, we go to the database to create a collection blog, and then go to the collection

  1. Initialize the database const DB = Wx cloud. database()
  2. Then in promise It passes inside Insert the corresponding data (content picture, user id, user nickname and avatar time) into the collection in the way of add
    You can get the picture in handleInput if you want the content
  3. One thing to note here is that if the input is empty, there is no need to return. Return directly
  4. Then we have many fileids. We store them in the array and use concat
  5. openId comes with it
  6. The nickname avatar can be obtained in onload. Because userinfo is an object, what should I do if I want to insert every attribute? I use the three-point operator
  7. Time: there are two methods: one is to obtain the client time, and the other is to obtain the server time
    The client time may or may not be synchronized with the network
    We should take the server time dB serverDate()
  8. Then you can load at the beginning Then the pop-up window is published successfully and hide loading then catch pop-up publishing failed hideloading

The id and openid here are automatically generated

A code for punch in post

// miniprogram/pages/blog-edit/blog-edit.js
let TEXT_NUM_MAX = 100
let IMG_MAX = 9
const db=wx.cloud.database()
let content='' //Enter text content
let userInfo={}
Page({

  /**
   * Initial data of the page
   */
  data: {
    textCount: 0, //Count (input box)
    imgArr:[],     //Picture list
    showAddIcon: true, //Show plus sign
    footerBottom: 0, //Height of footer from bottom when entering

  },
  //Input word number display
  handleInput(e) {
    let textCount = e.detail.value.length
    this.setData({
      textCount
    })
    if (textCount >= TEXT_NUM_MAX) {
      this.setData({
        textCount: `Maximum input ${TEXT_NUM_MAX}Word`
      })
    }
    content=e.detail.value
  },
  //Add picture
  addImg() {
    //An array of pictures
    let count = 0
    let imgArr = []
    wx.chooseImage({
      count: IMG_MAX - this.data.imgArr.length,
      success: (e) => {
        console.log(e);
        imgArr = e.tempFilePaths
        this.setData({
          imgArr: this.data.imgArr.concat(imgArr)
        })
        let length = this.data.imgArr.length
        if (length >= IMG_MAX) {
          this.setData({
            showAddIcon: false
          })
        }
      },

    })

  },
  //Delete picture
  deleteImg(e){
    let index=e.currentTarget.dataset.index
    let imgArr=this.data.imgArr
    imgArr.splice(index,1)
    this.setData({
      imgArr
    })
    this.setData({
      showAddIcon:true
    })
    
  },
  // Change the height of the footer from the bottom
  onFocus(e) {
    console.log(e.detail.height);
    let height = e.detail.height
    this.setData({
      footerBottom: height
    })
  },
  onBlur() {
    this.setData({
      footerBottom: 0
    })
  },
  // Publish button
  send(){
    let promiseArr=[] //Save promise each object
    let fileIds=[] //Save fileId
    for(let i=0;i<this.data.imgArr.length;i++){
      //Each cycle creates a new promise object
      let p=new Promise((resolve,reject)=>{
        let item=this.data.imgArr[i]
        wx.cloud.uploadFile({
          cloudPath:"blog/"+Date.now()+'.jpg',
          filePath:item,
          success:(res)=>{
            console.log(res);
            fileIds=fileIds.concat(res.fileID)
            resolve()
          },
          fail:(err)=>{
            console.log(err);
            reject()
          }
        })
      })
     promiseArr.push(p)
    }
    Promise.all(promiseArr).then((res)=>{
      // Save to database
      db.collection("blog").add({
        data:{
          ...userInfo,
          content,
          img:fileIds,
          creatTime:db.serverDate()
        }
      }).then((res)=>{
        wx.showToast({
          title: 'Published successfully',
        })
      })
    })
   
  },
  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {
    console.log(options);
    // Not here for the time being. Let's write the layout and style first
    userInfo=options
  },

})

5.9 blog card assembly

Here we are going to take out the contents of the database,

This is a bit like the previous song list. Let's review how the song list is made

We receive start and count in the cloud function. Start is the length of the playlist array stored in date, and count is how many are loaded each time
Then it is triggered once in onload, and it is triggered every time it touches the bottom

  1. Create a blog cloud function and install a TCB router

  2. preparation

    // Cloud function entry file
    const cloud = require('wx-server-sdk')
    
    cloud.init()
    const TcbRouter=require("tcb-router")
    const db=cloud.database()   //Import database
    // Cloud function entry function
    exports.main = async (event, context) => {
      const app=new TcbRouter({
        event
      })
    
      return app.serve()
    }
    
  3. In the cloud function

// Cloud function entry file
const cloud = require('wx-server-sdk')
cloud.init()
const TcbRouter=require("tcb-router")
const db=cloud.database()   //Import database
const blogCollection= db.collection("blog")
// Cloud function entry function
exports.main = async (event, context) => {
  const app=new TcbRouter({
    event
  })
  app.router("list", async(ctx,next)=>{
   let blogList=await blogCollection.skip(event.start)
    .limit(event.count)
    .orderBy("date",'desc')
    .get()
    .then((res)=>{
      return res
    })
    ctx.body=blogList
  })
  return app.serve()
}
  1. Go back to the blog in pages js
    Encapsulate one by yourself_ Loadloglist method

     _loadBlogList(){
        console.log(111);
        wx.cloud.callFunction({
          name:"blog",
          data:{
            $url:'list',
            start:this.data.blogList.length,
            count:1     //Because there is little data, one is loaded every time the pull-down refresh is performed
          }
        }).then((res)=>{
          let data=res.result.data
    
          this.setData({
            blogList:this.data.blogList.concat(data)
          })
        })
      },
    
  2. Card assembly

5.10 time format processing

Note that because the data of our database is transferred into the component, the time at this time is the server-side time, which is so long
Mon Feb 08 2021 00:11:07 GMT+0800 (China standard time) (cloud database type)
When we new Date(Mon Feb 08 2021 00:11:07 GMT+0800), it will become js type time
Result: 2021-02-07T16:11:07.000Z

Then we need to deal with it in the component: observers monitoring is used again. We can

However, we can use this in general, so we can put it in utils

 observers:{
    ["blog.date"](val){
      let time=formatTime(val)
      // console.log(time);
      this.setData({
        time
      })
    }
  }

Notice why a new time is defined here
Because it's monitoring blogs Change of date. If you change the date, it will cause an endless loop

5.11 detail processing (how the sub page of knowledge calls the parent page method)

Last time, start was assigned according to the length of the array. Now the second idea is:

 onLoad: function(options) {
    console.log(options.scene)
    this._loadBlogList()  //The default value is 0
    }
 onPullDownRefresh: function() {
    this.setData({
      blogList: []
    })
    this._loadBlogList(0)
  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function() {
    this._loadBlogList(this.data.blogList.length)
  },

Also, click on the image to enlarge and use onPreviewImg

Click to enter the details page and click to jump to < s-blog-card blog = "{item}}" bindtap = "gocomment" > < / s-blog-card >
Then create a new blog comment

Then there is a bug. When we click on the image, we will find that it also jumps. This is no good. Clicking on the image should be to enlarge the image. Why? Our component uses bindtap, which has a bubbling mechanism. We need to change it to bindcatch

Another thing to note is that if we want to add a mask below when publishing, what can we do to prevent users from randomly clicking? We can add a mask in showloading

After editing, jump back to the blog page and refresh, write the corresponding code under the navigator back, so how to control the onPullDownRefresh of the blog page on the blog edit page
These involve knowledge points, how to call the functions of the parent interface in the child interface

https://developers.weixin.qq.com/miniprogram/dev/reference/api/getCurrentPages.html getCurrentPage

Episode: there are bug s found in ancient times here

This is for the first login and later login to jump to the editing page
Therefore, the values passed in by both of them should be the same. That's what I wrote in the component at that time

then

So they can call this function

However, the e in the component is printed like this. I thought I didn't see it at that time. I should use userInfo again Where's detail

Then I put it in the assembly

But this is not the case. This detail is added automatically, so we

You should get userinfo (there is a nickname and so on) (e.detail) first, and then call loginSuccess

This is a method,

There's another one

5.12 fuzzy query

Here, in order to make the search component more reusable, we don't operate in the component
Pass the string entered in the component to the blog and put it in a global variable
Then trigger event sends out the keywords to where? To the place where the calling component is sent
Save keywords into a global variable in the search function of the blog page
We call this function in search, but we need to change it and pass in another keyword

Then you can receive keywords in the cloud function

Regular:
i case insensitive
m cross line matching; Let the start matching character ^ or the end matching character $match the beginning and end of the line in addition to the beginning and end of the string
s let You can match all characters, including line breaks

Cloud database query knowledge points (regular): https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.RegExp.html

app.router("list",async(ctx,next)=>{
    const keywords = event.keywords
    let w={}
    if(keywords.trim()!=''){
      w={
        content:new db.RegExp({
          regexp: keywords,
          options: 'i',
        })
      }
    }
    let blog=await blogList.where(w).skip(event.start)
      .limit(event.count)
      .orderBy("date",'desc')   //Reverse time order
      .get() 
      .then((res)=>{
        return res
      })
    ctx.body=blog
  })

In order to be more efficient, we can build an index and trade space for time

5.13 cloud database permissions

We found that music, blog query and database operations are carried out in cloud functions

Why in cloud functions instead of applets

  1. Easy code management

The content of music is in the music cloud function, so it is easier to manage the code
If not, three cloud functions will be created, and the code is difficult to manage

  1. The applet side has a limit of 20 times for database requests, while the cloud function side has a breakthrough of 1000 times: multiple queries and splicing

  2. If we query on the applet side, the results we get can only be published by ourselves, but we can change the permissions in the database

  3. In the applet side, the time to read the database is an object

    Solution: become a string

5.14 summary

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.RegExp.html](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/database/Database.RegExp.html)

app.router("list",async(ctx,next)=>{
    const keywords = event.keywords
    let w={}
    if(keywords.trim()!=''){
      w={
        content:new db.RegExp({
          regexp: keywords,
          options: 'i',
        })
      }
    }
    let blog=await blogList.where(w).skip(event.start)
      .limit(event.count)
      .orderBy("date",'desc')   //Reverse time order
      .get() 
      .then((res)=>{
        return res
      })
    ctx.body=blog
  })

In order to be more efficient, we can build an index and trade space for time

5.13 cloud database permissions

We found that music, blog query and database operations are carried out in cloud functions

Why in cloud functions instead of applets

  1. Easy code management

The content of music is in the music cloud function, so it is easier to manage the code
If not, three cloud functions will be created, and the code is difficult to manage

  1. The applet side has a limit of 20 times for database requests, while the cloud function side has a breakthrough of 1000 times: multiple queries and splicing

  2. If we query on the applet side, the results we get can only be published by ourselves, but we can change the permissions in the database

  3. In the applet side, the time to read the database is an object

    Solution: become a string

5.14 summary

Tags: Mini Program

Posted by sandeepjayan on Tue, 19 Apr 2022 09:08:01 +0930