商业级 微信小程序开发 从基础到实战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kingrome2017/article/details/83105993

了解一个概念,微信公众号其实是一个大类,包含服务号、订阅号、小程序、企业微信 学习小程序的重点要放在微信开放能力的开发上

本篇博客围绕以下几点开讲

  1. 微信小程序的基础知识
  2. Flex 机制属性详解
  3. Wx.request 请求
  4. 生命周期
  5. 数据绑定
  6. 列表渲染、条件渲染、数据渲染、事件
  7. ES6语法应用
  8. 面向对象思维构建业务模型、箭头函数、Promise、Class
  9. Getter和setter

json

重点再说一下json

json对象的属性必须加双引号,这是严格要求的一项使用json.stringifiy实现过滤修改 但是这个方法在火狐浏览器3.5、3.6有一个bug,4.0修复了这个bug

 <script>
        var book = {
            "title": "pop",
            "authors": [
                "ls",
                "zrar",
                "bzrar"
            ],
            "edition": 3,
            "year": 2011
        }
        var jsonText = JSON.stringify(book, function(key, value){
            switch(key){
                case "authors":
                return value.join('/');
                case "year":
                return 2018;
                default:
                return value;
            }
        })
        console.log(jsonText,'json')
        //{"title":"pop","authors":"ls/zrar/bzrar","edition":3,"year":2018} json
    </script>

还有就是 json字符串和数组互转 json格式化html常用的操作下面的链接讲解的很详细

https://blog.csdn.net/kingrome2017/article/details/82178661

重点说一下弹性布局flex

如何确定主轴和交叉轴,当flex-direction为row是水平方向就是主轴,垂直方向就是交叉轴,相反就不说了很好理解,一般配合使用justify-content和aligin-items实现水平垂直居中,下面的链接有详细的介绍

https://blog.csdn.net/kingrome2017/article/details/76528098

点赞组件开发

如何在页面中引用组件?通过在json中定义key/value的形式引入组件 ,如何在css中定义全局样式?使用page对象去定义,因为页面渲染后会自动生成一个page标签。只有font、color可以在组件外继承使用,可以通过line-height方法消除文字空白间距,关于let的使用她是定义块级变量的

<!--components/like/like-cmp.wxml-->
<view class="container" bind:tap="onLike">
  <image src="{{like?yes_url:no_url}}" />
   <text>{{count}}</text> 
</view>
// components/like/like-cmp.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    like: Boolean,
    count: Number,
    readOnly:Boolean
  },

  data: {
    yes_url: 'images/like.png',
    no_url: 'images/[email protected]'
  },

  methods: {
    onLike: function (event) {
      if(this.properties.readOnly){
        return
      }
      let count = this.properties.count
      count = this.properties.like ? count - 1 : count + 1
      this.setData({
        count: count,
        like: !this.properties.like
      })
      let behavior = this.properties.like ? 'like' : 'cancel'
      this.triggerEvent('like', {
        behavior: behavior
      }, {})
    }
  }
})
/* components/like/like-cmp.wxss */

.container{
  display: flex;
  flex-direction: row;
  /* 必须指定宽度,否则会出现移动 */
  /* width:120rpx;  */
  padding:10rpx;
}

.container text{
  font-size:24rpx;
  color: #bbbbbb;
  line-height:24rpx;
  position:relative;
  bottom:10rpx;
  left:6rpx; 
}

.container image{
  width:32rpx;
  height:28rpx;
}

下面是引入组件的方法,采用的绝对路径而不是相对与当前文件的相对路径。

{
  "usingComponents": {
    "navi-cmp": "/components/navi/navi",
    "epsoide-cmp": "/components/epsoide/epsoide",
    "like-cmp": "/components/like/index",
    "movie-cmp":"/components/classic/movie/movie",
    "music-cmp": "/components/classic/music/music",
    "essay-cmp": "/components/classic/essay/essay",
    "img-btn-cmp": "/components/image-button/index"
  }
}

封装HTTP请求

在onload阶段调用数据是因为它早于onready,promise主要是解决异步且套问题所谓的回调地狱通过return的方式实现,尽量不要乱用promise,复杂页面拆分成低耦合的模块有助于维护开发

// config文件
const config = {
  api_blink_url: 'http://pro/v1/',
  appkey: '获取appkey'
}

export { config }
import { config } from '../config.js'
//import { config as loop } from '../config.js' 可以使用as指代别名一但修改用的地方都要使用别名要统一

class HTTP {
  constructor() {
    this.baseRestUrl = config.api_blink_url
  }

  //http 请求类, 当noRefech为true时,不做未授权重试机制
  request(params) {
    var that = this
    var url = this.baseRestUrl + params.url;

    if (!params.method) {
      params.method = 'GET';
    }
    wx.request({
      url: url,
      data: params.data,
      method: params.method,
      header: {
        'content-type': 'application/json',
        'appkey':config.appkey
      },
      success: function (res) {
        // 判断以2(2xx)开头的状态码为正确
        // 异常不要返回到回调中,就在request中处理,记录日志并showToast一个统一的错误即可
        var code = res.statusCode.toString();
        var startChar = code.charAt(0);
        if (startChar == '2') {
          params.success && params.success(res.data);
        } else {
          params.error && params.error(res);
        }
      },
      fail: function (err) {
        params.fail && params.fail(err)
      }
    });
  }
};

export { HTTP };

从这里开始不在页面中写HTTP请求而是引入model再对HTTP做一次封装

<view class='container'>
  <view class='header'>
    <epsoide-cmp class="epsoide" index="{{classic.index}}" />
    <view class="like-container">
      <like-cmp bind:like="onLike" class="like" like="{{like}}" count="{{count}}" />
      <img-btn-cmp class="share-btn" open-type="share">
        <image class="share" slot="img" src="/images/icon/share.png" />
      </img-btn-cmp>
    </view>
  </view>
  <movie-cmp wx:if="{{classic.type==100}}" img="{{classic.image}}" content="{{classic.content}}" />
  <music-cmp wx:if="{{classic.type==200}}" img="{{classic.image}}" content="{{classic.content}}" src="{{classic.url}}" title="{{classic.title}}" />
  <essay-cmp wx:if="{{classic.type==300}}" img="{{classic.image}}" content="{{classic.content}}" />
  <navi-cmp class="navi" latest="{{latest}}" first="{{first}}" catch:left="onNext" catch:right="onPrevious" title="{{classic.title}}" />
</view>
<!-- <navigator target="miniProgram" app-id="wx8ffc97ad5bcccc89" open-type="navigate">sdasd</navigator> -->
<!-- <button bind:tap="onIP">IP</button> -->

model的实现

import { HTTP } from '../utils/http.js'
import {ClassicStorage} from '../models/classic-storage.js'

class ClassicModel extends HTTP{
  prefix = 'classic'

  constructor() {
    super()
  }

  getLatest(sCallback){
    this.request({
      url:'classic/latest',
      success:(data)=>{
          // 如果不用箭头函数,this将指代不正确
          let key = this._fullKey(data.index)
          wx.setStorageSync(key, data)
          this._setLatestIndex(data.index)
          sCallback(data)
        }
    })
  }

  getPrevious(index, sCallback){
    this._getClassic(index,'previous',sCallback)
  }

  getNext(index, sCallback) {
    this._getClassic(index, 'next', sCallback)
  }

  getById(cid, type, success){
    let params = {
      url:'classic/'+type+'/' + cid,
      success:success
    }
    this.request(params)
  }

  isLatest(index){
    let key = this._fullKey('latest-' + index)
    let latestEpsoide = wx.getStorageSync(key)
    if(latestEpsoide){
      if (index == latestEpsoide){
        return true
      }
    }
    else return false
  }

  isFirst(index){
    if (index==1){
      return true
    }
    else return false
  }

  getMyFavor(success){
    let params={
      url:'classic/favor',
      success:success
    }
    this.request(params)
  }

  _getClassic(index, next_or_previous, sCallback){
    let key = next_or_previous == 'next' ? this._fullKey(index + 1):
      this._fullKey(index-1)
    let classic = wx.getStorageSync(key)
    if (!classic) {
      let params = {
        url: 'classic/' + index + '/' + next_or_previous,
        success:(data)=>{
          let key = this._fullKey(data.index)
          wx.setStorageSync(key, data)
          sCallback(data)
        }
      }
      this.request(params)
    }
    else{
      sCallback(classic)
    }
  }

  /**
   * 在缓存中存放最新一期的期数
   */
  _setLatestIndex(index){
    let key = this._fullKey('latest-' + index)
    wx.setStorageSync(key, index)
  }

  _getLatestEpsoide(index){
    let key = this._fullKey(index)
    return wx.getStorageSync(key)
  }

  _fullKey(partKey){
    let key = this.prefix + '-' + partKey
    return key
  }
}

export {ClassicModel}
// pages/classic/classic.js
import {ClassicModel} from '../../models/classic.js'
import { LikeModel } from '../../models/like.js'
let classicModel = new ClassicModel()
let likeModel = new LikeModel()

Page({

  /**
   * 页面的初始数据
   */
  data: {
    classic:null,
    latest:true,
    first:false,
    like:false,
    count:0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    classicModel.getLatest((data)=>{
      this._getLikeStatus(data.id, data.type)
      this.setData({
        classic:data
      })
    })
  },

  onPrevious:function(event){
    let index = this.data.classic.index
    classicModel.getPrevious(index, (data)=>{
      if(data){
        this._getLikeStatus(data.id, data.type)
        this.setData({
          classic:data,
          latest: classicModel.isLatest(data.index),
          first: classicModel.isFirst(data.index)
        })
      }
      else{
        console.log('not more classic')
      }
    })
  },

  onNext:function(event){
    let index = this.data.classic.index
    classicModel.getNext(index, (data)=>{
      if (data) {
        this._getLikeStatus(data.id, data.type)
        this.setData({
          classic: data,
          latest: classicModel.isLatest(data.index),
          first: classicModel.isFirst(data.index)
        })
      }
      else {
        console.log('not more classic')
      }
    })
  },

  onLike:function(event){
    let like_or_cancel = event.detail.behavior
    likeModel.like(like_or_cancel, this.data.classic.id, this.data.classic.type)
  },

  _getLikeStatus:function(cid, type){
    likeModel.getClassicLikeStatus(cid, type, (data)=>{
      this.setData({
        like:data.like_status,
        count:data.fav_nums
      })
    })
  },

  onShareAppMessage(){

  }
})

由于异步调用无法使用return,通过回调函数把数据传出去,以及自定义事件triggerEvent,因为点赞的功能不需要传递success函数从而报错,在HTTP.js里面做了闭空处理

在这里插入图片描述

/* components/epsoide/epsoide-cmp.wxss */
.index{
  font-size:30px;
  /* line-height:20px;  */
  font-weight:800;
  margin-right:7px;
}

.vertical-line{
  margin-right:7px;
  height:22px;
  border-left:1px solid black;
}

.plain{
  font-size:16px;
}

.container{
  display:inline-flex;
  flex-direction: row;
  line-height: 30px;
  /* justify-content: flex-end; */
   /* align-items: baseline;       */
}

.index-container{
  display:flex;
  flex-direction: row;
  align-items: baseline;      
}

.date{
  display: flex;
  flex-direction: column;
  justify-content: center; 
  /* align-items: baseline; */
   /* line-height:30px;   */
}

.month{
  font-size:12px;
  line-height:12px; 
  padding-bottom: 2px; 
  margin-right:2rpx;
  /* padding-right:4rpx; */
}

.year{
  font-size:10px;
  line-height:10px; 
  padding-bottom:3px;   
}

对照上图实现css,注意几点一个是inline-flex,还有一个就是基线对齐

导航组件navi

其实就是选项卡的实现,既然是导航就有点击区域,一定要注意点击区域考虑用户体验,因为她是一个组件引用到父组件,所以就涉及到自定义事件的监听上,还有一个behavior叫行为定义,相当于vue里面的mixin

<!--components/navi/navi.wxml-->
<view class='container'>
  <image class="navi-icon navi-left" bind:tap="onLeft"          
    src="{{latest?disLeftSrc:highLeftSrc}}" />
  <text class="title">{{title}}</text>
  <image class="navi-icon navi-right" disable="{{!first}}" bind:tap='onRight"
    src="{{first?'images/[email protected]':'images/[email protected]'}}" />
</view>

// components/navi/navi.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    title:{
      type:String,
      value:'...',
    },
    latest:{
      type:Boolean,
      value:false,
      observer:function(){
        // console.log('111111')
        // this.setData({
        //   latest:this.properties.latest
        // })
      }
    },
    first:{
      type:Boolean,
      value:false,
      observer:function(){

      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    disLeftSrc: 'images/[email protected]',
    highLeftSrc: 'images/[email protected]'
  },

  /**
   * 组件的方法列表
   */
  methods: {
    onLeft:function(){
      if(!this.properties.latest){
        this.triggerEvent('left', {}, {})
      }
    },
    onRight:function(){
      if(!this.properties.first){
        this.triggerEvent('right', {}, {})
      }   
    }
  }
})

下面是定义behavior,以及在组件中的引用,它相比es6生命周期她是多继承的,引用的时候是个数组,可以引用多个,举个列子,关于图片引用,如果组件有这个变量他会覆盖引入的变量,如果组件中没有,而引用方引入了多个,那么最后引入的会生效,但是生命周期是不会覆盖的,引入几个生命周期就会执行几次。

let classicBehavior = Behavior({
  properties: {
    type:String,
    img:String,
    content:String
  },
  data: {
  }
})

export { classicBehavior }
// components/classic/movie/movie.js
import {classicBehavior} from '../classic-beh.js'
Component({
  /**
   * 组件的属性列表
   */
  behaviors:[classicBehavior],
  
  properties: {

  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {

  }
})

由于当前项目,初始化获取的是最新一期的数据,所以操作向后切换的时候,要请求数据把当前的index传入,具体实现会放到model里,但是如果频繁切换会加大服务器开销,用户体验也会很差,所以要加入到缓存里,加缓存的思路就是先从缓存里取,如果没有就调取接口

// pages/classic/classic.js
import {ClassicModel} from '../../models/classic.js'
import { LikeModel } from '../../models/like.js'
let classicModel = new ClassicModel()
let likeModel = new LikeModel()

Page({

  /**
   * 页面的初始数据
   */
  data: {
    classic:null,
    latest:true,
    first:false,
    like:false,
    count:0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    classicModel.getLatest((data)=>{
      this._getLikeStatus(data.id, data.type)
      this.setData({
        classic:data
      })
    })
  },

  onPrevious:function(event){
    let index = this.data.classic.index
    classicModel.getPrevious(index, (data)=>{
      if(data){
        this._getLikeStatus(data.id, data.type)
        this.setData({
          classic:data,
          latest: classicModel.isLatest(data.index),
          first: classicModel.isFirst(data.index)
        })
      }
      else{
        console.log('not more classic')
      }
    })
  },

  onNext:function(event){
    let index = this.data.classic.index
    classicModel.getNext(index, (data)=>{
      if (data) {
        this._getLikeStatus(data.id, data.type)
        this.setData({
          classic: data,
          latest: classicModel.isLatest(data.index),
          first: classicModel.isFirst(data.index)
        })
      }
      else {
        console.log('not more classic')
      }
    })
  },

  onLike:function(event){
    let like_or_cancel = event.detail.behavior
    likeModel.like(like_or_cancel, this.data.classic.id, this.data.classic.type)
  },

  _getLikeStatus:function(cid, type){
    likeModel.getClassicLikeStatus(cid, type, (data)=>{
      this.setData({
        like:data.like_status,
        count:data.fav_nums
      })
    })
  },

  onShareAppMessage(){

  }
})

因为加缓存,所以点赞功能会受到影响,为此点赞功能要调用其他接口实现

import {
  HTTP
} from '../utils/http.js'
class LikeModel extends HTTP {
  constructor() {
    super()
  }

  getClassicLikeStatus(cid, type, success) {
    var params = {
      url: 'classic/' + type + '/' + cid + '/favor',
      success: success
    }
    this.request(params)
  }

  getBookLikeStatus(bid, type, success) {
    var params = {
      url: 'book/' + type + '/' + bid + '/favor',
      success: success
    }
    this.request(params)
  }

  like(like_or_cancel, art_id, type) {
    let url = like_or_cancel === 'cancel' ? 'like/cancel' : 'like'
    this.request({
      url: url,
      method: 'POST',
      data: {
        art_id: art_id,
        type: type
      },
      success: (data) => {
        console.log(data)
      }
    })
  }
}

export {
  LikeModel
}

这次新版课程用的背景音乐播放API,因为每次切换Tab的时候希望音乐的状态能够暂停,或者播放,但是兄弟组件通信在小程序里实现是比较困难的,所以采用结合wx:if以及,组件的生命周期去实现,当然为了实现音乐总控和组件播放状态的一致,用到API的方法

<view class="classic-container">
  <image src="{{img}}" class="classic-img {{playing?'rotation':''}}" />
  <image class="player-img" bind:tap = "onPlay"
   src="{{playing?playingUrl:waittingUrl}}" />
   <image class='tag' src="images/[email protected]" /> 
  <text class="content">{{content}}</text>
</view>

.classic-img {
  width: 422rpx;
  height: 422rpx;
  margin-top: 60rpx;
  /* margin-bottom: 120rpx; */
  border-radius: 70%;
  /* margin-bottom: -50px; */
  overflow: hidden;
}

@-webkit-keyframes rotation {
  from {
    -webkit-transform: rotate(0deg);
  }

  to {
    -webkit-transform: rotate(360deg);
  }
}

.tag{
  width:44rpx;
  height:127rpx;
  position: relative;
  bottom: 160rpx;
  right:310rpx; 
}

.rotation {
  -webkit-transform: rotate(360deg);
  animation: rotation 12s linear infinite;
  -moz-animation: rotation 12s linear infinite;
  -webkit-animation: rotation 12s linear infinite;
  -o-animation: rotation 12s linear infinite;
}

.player-img {
  width: 120rpx;
  height: 120rpx;
   position: relative; 
   bottom: 271rpx; 
}

.content {
  display: block;
  /* width:275px; *//* margin:0 auto; */
  max-width: 550rpx;
  font-size: 36rpx;
  margin-top: -90rpx;
}

.classic-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}


// components/classic/music/music.js
import {
  classicBehavior
} from '../classic-beh.js'

let mMgr = wx.getBackgroundAudioManager()

Component({
  /**
   * 组件的属性列表
   */
  behaviors: [classicBehavior],

  properties: {
    src: String,
    title:String
  },

  /**
   * 组件的初始数据
   */
  data: {
    playing: false,
    waittingUrl: 'images/[email protected]',
    playingUrl: 'images/[email protected]'
  },

  attached: function() {
    this._recoverPlaying()
    this._monitorSwitch()
  },

  detached: function() {
    // wx.pauseBackgroundAudio()
  },

  /**
   * 组件的方法列表
   */
  methods: {
    onPlay: function(event) {
      if (!this.data.playing) {
        this.setData({
          playing: true,
        })
        if(mMgr.src == this.properties.src){
          mMgr.play()
        }
        else{
          mMgr.src = this.properties.src
        }
        mMgr.title = this.properties.title
      } else {
        this.setData({
          playing: false,
        })
        mMgr.pause()
      }
    },

    _recoverPlaying: function() {
      if (mMgr.paused) {
        this.setData({
          playing: false
        })
        return
      }
      if (mMgr.src == this.properties.src) {
        if (!mMgr.paused) {
          this.setData({
            playing: true
          })
        }
      }
    },

    _monitorSwitch: function() {
      mMgr.onPlay(() => {
        this._recoverPlaying()
      })
      mMgr.onPause(() => {
        this._recoverPlaying()
      })
      mMgr.onStop(() => {
        this._recoverPlaying()
      }),
      mMgr.onEnded(()=>{
        this._recoverPlaying()
      })
    }

  }
})

猜你喜欢

转载自blog.csdn.net/kingrome2017/article/details/83105993
今日推荐