What is the project about?


First of all, it is a website for the free exchange of goods between the owners of the community. The users can get 7 beans by sharing their own goods, and then exchange 7 beans for daily necessities on the website.
In the project, I realized that I could access the same background through the PC side and wechat mini program, and realized the interaction between the PC side users and the mini program users. However, due to the personal development of this project and the limitations of Tencent, it was temporarily impossible to log in with wechat account on the PC side.

Another article, an introduction to the front end of the Web: 7 Enjoy web





Technology stack


  • Wechat applets

  • vant-ui





The project architecture

| app | | - API request / / for all kinds of interface | | - common/store/public style or js files or text | | | - components / / small program components | - image / / static image | | - models / / processing receive back data | | - pages / / small program page | | - util / / storage of packaged tools | app. Js | app. Json. | app WXSS | config. Js / / store SettingsCopy the code

Projects show



You can also visit the PC website

routing


Wx.getsetting () is used to check whether the user has logged in or not, but it does not provide a route guard like vue-route. At first I wanted to use this method to determine if the user is logged in on each page onShow(), but once you have too many pages, it’s really inconvenient to import this method every time.

App.js is the only instance of invoking app method to register small program, which is shared by all pages. Developers can obtain globally unique APP instance through getApp method, obtain data on app or call functions registered by developers on app. I can also achieve global use by encapsulating a routing monitor in app.js.

  navigateTo(arg) {
    globalData.url = arg.url
    globalData.success = arg.success
  }
Copy the code

This is the reference function, and the reference function is passed your path and callback internally through the ARG and placed in a variable globalData, but this variable is not reactive. Make variables reactive using the well-known object.defineProperty () method to detect changes in the incoming path and act accordingly. Why do we use this API instead of proxy? Because Proxy belongs to ES6 language, ES6 language is often compiled into ES5 language when packaging, which makes the volume of packaging larger. In order to optimize small programs, try to write in ES5 language, this has become a way to optimize small programs.

proxy () {
    Object.defineProperty(globalData, 'url', {
      get: function (val) {
        return val
      },
      set: function (newValue) {
        wx.getSetting({
          success: (res) => {
            if (res.authSetting['scope.userInfo']) {
              wx.navigateTo({
                url: newValue,
                success: globalData['success']
              })
            } else {
              wx.navigateTo({
                url: '/pages/login/login',
              })
            }
          }
        })
      }
    })
  },
Copy the code

I put access to the user in set, and when I detect that the user is not authorized, I go straight to the login page.

const app = getApp()
 onWeather() {
    const that = this
    app.navigateTo({
      url: '/pages/weather/weather',
      success: (res) => {
        res.eventChannel.emit('sendTempFormMain', {
          data: that.data.temp
        })
      }
    })
  },

Copy the code

Call this method directly on the page where you want to redirect routes to realize route monitoring.

The user


In the user login page, once the user is authorized, the temporary login credential code is obtained through the Wx. login interface and sent to the background server to obtain the user’s token and cache it.

onGotUserInfo() {
    wx.login({
      success: (res) => {
        if (res.code) {
          getToken(res.code).then((res) => {
            wx.setStorage({
              key: 'token',
              data: res.token
            })
          }).then(() => {
            wx.navigateBack()
          })
        }
      }
    })
  }
Copy the code

The wx.getUserInfo interface is used to get the user’s nickname and avatar information, which encapsulates an asynchronous function to get the information after user authorization and get the action prompt of a series of classes without user.

function _getUserInfo () { return new Promise((resolve, reject) => { wx.getSetting({ success: res => { if (res.authSetting['scope.userInfo']) { wx.getUserInfo({ success: Res => {resolve(res)}})} else {wx.showmodal ({title: 'prompt ', content:' If you don't authorize wechat login, you can't use applets. Click the "profile picture" button and allow "user info" to be used normally. ', showCancel: false, confirmText: 'know ', success: (res => {})})}}, fail: rej => {wx.showmodal ({title: 'Error ', Content:' can't get personal info ', showCancel: false, confirmText: 'know ', success: (res => {})})})})})})}Copy the code

Component part


In the scroll part, I encapsulated the official scrollview component of the small program for the second time. After the second encapsulation, I could pass in data, total and Page from outside to determine whether more loading and customize my own loading animation, instead of the one provided by the official website. And this component became the most used component in my applet. Scroll components

On the shopping page, I encapsulate the second shop-Scroll component. In shop-Scroll, I use a lot of WXS to make up for the fact that JavaScript cannot directly structure a page. Officially, WXS (WeiXin Script) is a set of scripting language for small programs, which can be combined with WXML to build the structure of a page. WXS is a different language from JavaScript and has its own syntax, which is not consistent with JavaScript. But can use the grammar is not enough, basically meet their own needs, hope to increase more in the future.

<wxs module="normalTool"> var tool = function(data, type) { var temp = [] for (var i = 0; i < data.length; i++) { var d = data[i] if (d.type === type) { temp.push(d) } } return temp } var shopCartComputeNum = function(shopCart,  id) { var num = 0 for (var i = 0; i < shopCart.length; i++) { var currentCart = shopCart[i] if (id === currentCart.id) { num = currentCart.num break } } return num } var id = Function (type) {switch (type) {case 'veg' : return 'veg'; break; Case 'fruit ': return 'fru'; break; }} var type = [' exports ', 'exports '] module. Exports = {tool: tool, shopCartComputeNum: shopCartComputeNum, type: type, id: cartcomputenum id } </wxs>Copy the code

In fact, THERE are many more components developed by myself, I just picked out the representative components to talk about, the rest of the components are simple types I won’t go into detail here, if you are interested, you can go to my code repository.

Data request


For data request and retrieval, INSTEAD of using the well-known AXIos component, I used the Wx. Request (Object Object) component provided by the official website to initiate HTTPS network requests. The component itself is good enough for daily use, but it needs to write a bunch of stuff every time and there is no asynchrony, which is far from satisfying the business needs, so I wrapped it a second time to make the API better to use. Http.js I use class methods to encapsulate this API, and use the PROMISE language in ES6 for asynchronous processing. I only encapsulate get and POST modules according to the business situation, and set a limit to prevent repeated submission.

class HTTP { get({url}) { return new Promise((resolve, reject) => { this._request(resolve, reject, url, "GET") }) } post({url,data}){ return new Promise((resolve,reject)=>{ this._request(resolve,reject,url,"POST",data) }) } _request(resolve, reject, url, method, Data = {}) {let user = wx.getStoragesync ('token') // Prevent multiple commits if (method === 'POST') {wx.showloading ({title: Request ({url: config.api_base_url + URL, method: method, data: data, header: { 'content-type': 'application/json', 'Authorization': 'Basic ' + Base64.encode(user + ':') }, success: (res) => { if (res.data.error_code) { let error_code = res.data.error_code if (error_code === 1003) { setTimeout( ()=> {  wx.navigateTo({ url: '/pages/login/login', }) }, 500) } reject(tips[error_code]) } else { resolve(res.data) } if (method === 'POST') { wx.hideLoading() } }, fail: (error) => {wx.showModal({title: 'failed ', content:' Server has a problem, please try again later ', showCancel: false, success: (res => { reject(res) }) }) } }) } }Copy the code

Error codes that are returned when data is requested from the server. I put them in Tips so I can add error codes to them at any time in my day.

Const tips = {1000: 'authentication failed ', 1001:' username already registered ', 1002: 'Note already registered ', 1003: 'token is invalid ', 1004:' room already registered ', 1005: 'Access denied ', 1006:' nothing found ', 1007: 'parent, duplicate submitted ', 1008:' parent, found identical item '}Copy the code

The data processing

In terms of data processing, the data returned by the background may not meet the requirements of the front end, so I will do some optimization of those data and have reached the requirements of the front end. What I usually do is write a class, write the methods of various classes in the methods of the class, and then convert the data directly in the class to improve the reuse rate of the code. The enclosed pending. Js

Of course, this is not perfect, because the data returned through the background will be more beautiful after the respective class processing, but these classes are only a single processing, if there are more than one? Each time we have to do something like this with the data I get from the background.

  _pending(page, type) {
    pending(page, type).then((res) => {
      let pending = this.data.pending.concat(this.normalPending(res.data))
      this.setData({
        select: type,
        pending,
        page: page,
        total: res.total,
        pages: res.pages
      })
    })
  },
  normalPending(data){
    let temp = []
    data.forEach((d)=>{
      temp.push(createPending(d))
    })
    return temp
  },
Copy the code

This seems like a lot of trouble, but does it make the code more flexible and elegant? The answer is to write a wrapper function on normalPending,

export function normallArray (fn) { const wrapped = function (arg) { let temp = [] arg.forEach((d) => { temp.push(fn(d))  }) return temp } return wrapped }Copy the code

NormalArray itself is a function that accepts FN as a function and returns a new wrapped function. When wrapped is executed, the wrapped function is automatically executed by passing in data

const normalPending = normallArray(createPending)
 
_pending(page, type) {
    pending(page, type).then((res) => {
      let pending = this.data.pending.concat(normalPending(res.data))
      this.setData({
        select: type,
        pending,
        page: page,
        total: res.total,
        pages: res.pages
      })
    })
  },
Copy the code

This reduces the number of code reuses each time, making it easier to write.

The weather animation


In the development of Canvas, particle system is often mentioned. Abstract visual effects such as fire, fog, cloud, snow, dust and smoke can be simulated by using particle system. In my small program, I used particle system to make rain and fog effects, and other weather effects such as sunshine, stars and clouds were formed by using Canvas.

I start by placing the canvas on the page I want to display

<canvas type="2d" id="effect"></canvas>
Copy the code

Then, create an instance of the canvas

const query = wx.createSelectorQuery()
query.select('#effect')
.fields({ node: true, size: true })
.exec((res)=>{
   const canvas = res[0].node
   const dpr = wx.getSystemInfoSync().pixelRatio
   canvas.width = res[0].width * dpr
   canvas.height = res[0].height * dpr
   const ctx = canvas.getContext('2d')
   ctx.scale(dpr, dpr)
   this.judge(ctx, this.data.air.weather)
})      
Copy the code

The particle system consists of base classes and subclasses. Particle is the base class that defines subclass uniform methods, such as run(), stop(), and so on. The base class is responsible for the maintenance of the entire particle system animation cycle and flow, while the subclass is responsible for the specific particle effects, such as the effect of rain with fog and sun is realized by the subclass, while the switch of rain with sun and public processing flow are controlled by the base class.

The base class implements the following:

const STATUS_STOP = 'stop' const STATUS_RUNNING = 'running' const INTERVAL = true class Particle{ constructor(ctx,width,height,options){ this._timer = null this.options = options || {} this.ctx = ctx this.width = width This. height = height this.status = STATUS_STOP Interval = interval this.particles = [] // This is used to move objects, This.step =0 this._init()} _init(){// the first method to execute when instantiating; } _draw() {} _draw() {} _draw() {} _draw() { } run(){// set timer, execute _draw(), implement animation cycle} stop(){// stop animation}}Copy the code

The animation execution part I put in run(), where I set whether the animation effect switch can be executed, and also set to check if it is in the middle of execution so that the animation is not executed multiple times.

run() { if (this.status ! == STATUS_RUNNING) { if (this.interval === true) { this._timer = setInterval(() => { this._draw() }, 30) } else { this._draw() } this.status = STATUS_RUNNING } return this }Copy the code

In the animation stop section, I set the animation execution status mentioned above to false so that it will be executed the next time the animation executes

stop() {
    this.status = STATUS_STOP
    clearInterval(this._timer)
    return this
  }
Copy the code

The rain effect

First, I put the calling code on the page that needs to be called

rain(ctx,amount) {
    let {width, height} = this.data
    let rain = new Rain(ctx, width, height, {
      amount: amount,
      speedFactor: 0.03
    })
    rain.run()
},
Copy the code

The size of the canvas is obtained directly through the API wx.getSystemInfo of the applet.

  wx.getSystemInfo({
      success: (res)=>{
        let width = res.windowWidth
        let height = res.windowHeight
        this.setData({
          width,
          height
        })
      }
  })
Copy the code

A subclass inherits the properties of its base class, places initialization in _init, sets the number of particles to the size of the rain, and puts the particles it produces into this.Particles.

Class Rain extends Particle {// initialize rainbow_init (){let width = this.width let height = this.height // Let amount = this.height This. The options. The amount | | 100 / / the rain let speedFactor = this. Options. SpeedFactor | | 0.03 let speed = height * speedFactor let ps = this.particles for (let i=0; i<amount; i++){ let p = { x: width, y: height, l: 2*Math.random(), xs: -1, ys: 10*Math.random() + speed, color: 'rgba(255, 255, 255, 5)'} ps.push(p)} this.particles = ps} _draw(){let ps = this.particles let CTX = this.ctx // first clean the content on the canvas ctx.clearRect(0, 0 , this.width,this.height) for(let i =0; i<ps.length; i++){ let s = ps[i] ctx.beginPath() ctx.moveTo(s.x, s.y) ctx.lineTo(s.x+s.l*s.xs, s.y+s.l*s.ys) ctx.strokeStyle=s.color ctx.stroke() } return this._update() } _update() { let {width, height, particles} = this let ps = particles for (let i=0; i<ps.length; i++){ let s = ps[i] s.x += s.xs s.y += s.ys if (s.x> width|| s.y>height){ s.x = Math.random()*width s.y = -5 } } } }Copy the code

Among them:

  • X and y represent the positions of individual particles, where the raindrop begins to draw

  • L is the length of the raindrop

  • Xs and ys respectively represent the acceleration in the x and Y directions, that is, the falling speed and Angle of raindrops

  • The _draw() method is to empty the canvas, then iterate through the this.Particles array to draw a single raindrop, then call a separate implementation of _update() to recalcuate the position of the individual raindrop, and the effect will appear.

Effect of white clouds

The white cloud effect is also written in the same way as the raindrop effect above. First, I will set the starting height of a white cloud and draw more white clouds according to the situation.

Class Cloud extends Particle{_init(){let width = this.width let height = this.height let cloudWidth = this.width*0.25 / / the number of cloud amount = this. The options. The amount | | 1 let ps = this. Particles for (let I = 0; i<amount; I++){let p = {x: math.random ()*width, y: height* math.random ()*0.5, cw: cloudWidth, CloudWidth *0.6, // start sangle: 0, // end 'rgba(255, 255, 255, 0.5)'} ps.push(p)} this.particles = ps}}Copy the code

The height of a white cloud came out. I drew the shape of a white cloud through 5 circles, and then made the cloud move slowly by changing the value of Cx each time

_draw(){this.step+= math.random () let ps = this.particles let CTX = this.ctx // first clean the contents of the canvas ctx.clearRect(0, 0, this.width,this.height) for(let i =0; i<ps.length; i++){ let s = ps[i] let cx = (s.x+this.step)%this.width ctx.beginPath() ctx.fill(); Arc (cx + s.w * 0.08, s.y-S.H * 0.3, s.w * 0.11, s.angle, s.angle) CTX. Arc (Cx + s.w * 0.08, s.y-s.H * 0.3, s.w * 0.11, s.angle, s.eangle); Arc (cx + s.w * 0.3, s.y-S.H * 0.25, s.w * 0.25, s.angle, s.angle); arc(cx + s.w * 0.3, s.y-s.H * 0.25, s.angle, s.angle); Arc (cx + s.w * 0.6, s.y, 80 * 0.21, s.angle, s.angle); arc(cx + s.w * 0.6, s.y, 80 * 0.21, s.angle, s.angle); Arc (cx + s.cn * 0.3, s.y-S.CN * 0.1, s.cn * 0.28, s.angle, s.angle); arc(cx + s.cn * 0.3, s.y-s.CN * 0.1, s.cn * 0.28, s.angle, s.angle); ctx.closePath(); ctx.fillStyle=s.color ctx.fill(); }}Copy the code

Snow effect

The main difference is the recycle part of the animation, where I used the color gradient effect to make the bottom of the snow disappear.

_update() {
    let {
      height,
      particles
    } = this
    let ps = particles
    for (let i = 0; i < ps.length; i++) {
      let p = ps[i]
      p.color = `rgba(255, 255, 255, ${1 - p.y/height})`
      p.y += p.ys
      if (p.y > height) {
        p.y = 0
        p.color = 'rgba(255, 255, 255, 1)'
      }
    }
  }
Copy the code

Write here, I use the small program canvas API to achieve the rain, cloud, snow, fog, clear, stars (meteor) weather effect, here I do not show, interested can see my code warehouse

Lucky guy

I wrote a small article about rotary part, we directly click on the links to view the tutorial | in WeChat small program to achieve a lucky guy

To optimize the

Data cache

There are two places I use the cache in the project. One is the directory where I store my tokens and items. These things only need to be loaded once at the beginning of the project to reduce the stress on the backend server.

App({onLaunch: function () {getAllSubs()},})Copy the code

Mechanism of the subcontract

Here, I use the subcontracting mechanism built into the small program. Because the size limit of the package of the small program has been increased to 2MB, but in order to improve the speed of the user’s initial loading and enhance the user’s experience, I use the subcontracting mechanism of the small program here.

  "pages": [
    "pages/main/main",
    "pages/my/my",
    "pages/drift/drift"
  ],
  "subpackages": [
    {
      "root": "mainpages",
      "name": "main",
      "pages": [
        "pages/help/help",
        "pages/hot/hot",
        "pages/shop/shop",
        "pages/askForHelp/askForHelp",
        "pages/use/use",
        "pages/weather/weather"
      ]
    },
    {
      "root": "mypages",
      "name": "my",
      "pages": [
        "pages/room/room",
        "pages/mobile/mobile",
        "pages/email/email",
        "pages/addGood/addGood",
        "pages/addWish/addWish",
        "pages/myWish/myWish",
        "pages/myGoods/myGoods"
      ]
    },
    {
      "root": "driftpages",
      "name": "drift",
      "pages": [
        "pages/comment/comment"
      ]
    }
  ],
Copy the code

After the above configuration, content in Pages is packaged into the main package, and content in subPackages is packaged into subPackages. When the small program is opened, the small program using the subcontracting mechanism will first download the main package to show the contents of the home page, which greatly improves the opening speed of the small program, and I will divide those businesses with strong relevance into the same package.

The deployment of

Those with deployment experience can float through this chapter.

When you’re done with your development, you can click “Debug real” to do a real simulation and see if there’s anything wrong with the applet you wrote.

When you feel that the test is ok, then you can click on the top of the programming software “tools” to find the “upload”, and then follow the prompts to complete the upload.

Landing WeChat public website, login account and password when you register the program, after the success, in the catalog to find “management” on the left side of the page, and then find “version management”, in the page “development version”, you can see you just upload the project, then click “submit review” on the right, and waiting for tencent’s review.

Tencent review completed, will prompt you to review, and then you go back to the “version management” page, release your small program.

conclusion


This is my first time to write micro channel small program, there are a lot of built-in API, goose factory has helped me, and then with the UI framework, write more easily, I thought of using MPVue at first, but listen to friends said not to recommend the use of native API is not fragrant?

Or the same words, read more documents, to be very careful to see, because he has some places to write very hidden, once you see the miss, some programs can not run normally, although there are errors, but some errors you will not understand.

Thank you again for seeing the end, my little brother’s writing is too bad, please forgive me for my bad writing. Finally, share my repository and don’t forget to donate your star. weapp

Don’t forget to add your thumbs up to another web front end tutorial