微信小程序|开发实战篇之一

前言

实战篇内容参考:
1、小程序开发实战:https://coding.imooc.com/class/chapter/251.html#Anchor
2、ssc在路上博主

1、通用方法的封装

这个主要是来优化请求的方法,我们把一些通用的数据抽离出来,来写成公共的方法,供许多模块调用。config.js像java中的properties文件。
在这里插入图片描述

ES6语法中一个js就是一个模块,官方导入js使用require,但可以使用导入module的方式。

(1)封装的config.js代码:

// export导出config
// const关键字是声明不变的值的时候来用的,相当于Java中静态变量

export const config = {
  api_base_url: 'http://xxx, 
  appkey: 'K0LDaSADSDLWWbF'
}

// 导出function函数方法
export let fun1 = function(){ }

// export {const, fun1} 这种写法就不用每一个都写export关键字了,一定要使用花括号包裹

(2)ES6封装HTTP类:

// 导入的时候需要使用相对路径
import {config} from '../config.js';

// 使用别名,导入其他函数
//import {config as cfg, fun1} from '../config.js';

// 错误的具体的提示信息 根据接口中error_code
const tips = {
  1:'抱歉,出现了一个错误',
  1005:'appKey不正确',
  3000:'期刊不存在'
}

class HTTP{
  request(params){
    if(!params.method){
      params.method = 'GET';
    }
    wx.request({
      url: config.api_base_url + params.url,
      method:params.method,
      data:params.data,
      header:{
        'content-type':'application/json',
        'appkey': config.appkey
      },
      success:(res) => {
        // 来判断请求是否成功 以2开头就是成功 这个是在Number类型的,          // 需要装换成string类型
        var code = res.statusCode.toString();
        // ES6中 startsWith 和 endsWith
        if(code.startsWith('2')){
          params.sucess(res.data);
        }else{
          // 错误信息的提示
          var error_code = res.data.error_code;
          this._show_error(error_code);
        }
      },
      fail:(err) => { //api调用失败
        this.error_code(1);
      }
    })
  }

  // 错误信息的提示方法
  _show_error(error_code){
    if(!error_code){
      error_code = 1;
    }
    wx.showToast({
      title: tips[error_code],
      icon: 'none',
      duration: 2000
    })
  }
}

// 使得类外部可以访问
export {HTTP}

(3)classic.js使用http.js

// pages/classic/classic.js
import {HTTP} from '../../util/http.js'
let http = new HTTP()

...
onLoad: function (options) {
    http.request({
      url: 'classic/latest',
      success:(res) => {
        console.log(res.data)
      }
    })
  },
...


2、数据获取方法的封装

上述文章将http请求和常用配置封装,继续获取数据的方法也封装起来。

2.1 抽离classic.js中的request()方法

创建models文件夹,来将classic.js中的代码继续分离出来,来创建一个classicModel类。
进一步进行代码的抽离,代码如下:

// model文件夹下classic.js代码如下
import{
  HTTP
}from '../util/http.js'

// 新建ClassicModel类来从服务器获取数据
class ClassicModel extends HTTP {
  getLatest(sCallBack) {
    this.request({
      url: 'classic/latest',
      success: (res) => {
        // 调用回调函数 来传递数据!!!
        sCallBack(res);
      }
    })
  }
}

// export ClassicModel
export {
  ClassicModel
}

注意:这里的getLatest()函数是一个异步函数,我们无法直接获取它的返回值。必须通过success()中调用回调函数sCallBack(res)传递数据!

2.2 组件数据传递

无法使用组件的data属性,因为该属性是私有的。
classic文件夹中classic.wxml页面中调用组件的时候。可以把数据存放到组件的属性中,利用数据绑定来传递数据

<v-like like='{{classic.like_status}}' count='{{classic.fav_nums}}' />

注意:这里面的 this.setData({classic: res}) 这个是用来做数据更新的,当data中的数据改变之后,只有通过setData才能进行数据的更新。

3、movie组件的创建

3.1 组件代码开发

组件wxml

<!--pages/classic/movie/index.wxml-->
<view class="classic-container">
  <image class="classic-img" src="{{img}}"></image>
  <image class="tag" src="images/movie@tag.png"></image>
  <text class="content">{{content}}</text>
</view>

组件样式

/* pages/classic/movie/index.wxss */
.classic-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.classic-img{
  width: 750rpx;
  height: 500rpx;
}

.tag {
  width: 46rpx;
  height: 142rpx;
  position: relative;
  right: 310rpx;
  bottom: 58rpx;
}

.content {
  font-size: 36rpx;
  max-width: 550rpx;
}

组件js

properties: {
    img: String,
    content: String
  },

3.2 classic.wxml应用组件

// classic.json
{
  "usingComponents": {
    "v-like":"/components/like/index",
    "v-movie": "/components/classic/movie/index"
  }
}
// classic.wxml文件中加入组件标签
<v-movie img='{{classic.image}}' content='{{classic.content}}' />

4、自定义事件的应用,传递like组件状态

这个主要是在点赞功能的上面进行的进一步的完善的功能,我们是如何将点赞的状态发送给服务端的,在这里最好的解决办法是在组件中进行自定义事件的开发。

4.1 在likes组件中定义behavior状态:like和cancel;然后以islike事件,把behavior传递给页面classic。

 // 自定义事件 传递一个string
var behavior = this.properties.isLike?'like':'cancel';

this.triggerEvent('islike',{behavior: behavior},{});

4.2 页面绑定islike事件并监听onClickLike事件。

<v-like bind:islike="onClickLike" isLike="{{classic.like_status}}" count="{{classic.fav_nums}}" ></v-like>

在调用like组件的页面classic.js声明onClickLike函数。

  • 在事件e中detail中可获取triggerEvent传递的参数behavior;
  • 调用封装的likeModel对象的like(String behaivor, String id, String type)函数。
    (behavior表示喜欢与否,id表示喜欢的东西的id号,type表示该物体的类型)
onClickLike: function(e){
    console.log(e);
    var behavior = e.detail.behavior;
    likeModel.like(behavior, this.data.classic.id, this.data.classic.type);
  },

4.3 封装request向服务器传递状态函数。

新建like.js 文件在modules文件夹下面,这个文件主要是创建LikeModel,将数据的更新方法写到这个model中,使得代码更好维护,模块化更加清楚。

like.js将request函数进行封装,然后对外暴露接口。

import {
  HTTP
} from '../util/http.js'

// 创建LikeModel继承HTTP类
class LikeModel extends HTTP {
  like(behavior, artId, category) {
    var url = behavior == 'like' ? 'like' : 'like/cancel';
    this.request({
      url: url,
      type: 'POST',
      data: {
        art_id: artId,
        type: category
      }
    })
  }
}

export {
  LikeModel
}
// classic.js显示likes组件
// 导入封装函数
import {
  ClassicModel
} from '../../models/classic.js'
import {
  LikeModel
} from '../../models/like.js';

// 实例化HTTP类
var classic = new ClassicModel();
var likeModel = new LikeModel();

Page({

  /**
   * 页面的初始数据
   */
  data: {
    classic: null
  },
   // 自定义的onClickLike事件
   onClickLike: function(e){
    	console.log(e);
    	var behavior = e.detail.behavior;
    	likeModel.like(behavior, this.data.classic.id, this.data.classic.type);
  },

5、开发期刊组件episode

在这里插入图片描述
分析: 期刊号数据需要由页面传入组件,发行日期是组件内部数据。

5.1 设置episode.js中的属性及内部数据

这里需要注意:

  • properties中的属性值,data中的数据名不能相同。不然小程序会覆盖数据!
  • 分清业务逻辑的归属问题,属于组件还是page?不要将逻辑都糅合到page里。

(1)发行日期直接在组件初始化时,使用时间函数获取即可。

这里更新data,组件数据时,需要使用到组件生命周期函数attached,该函数是在组件初始化时可以调用的。
(2)刊号index由页面外部传入,使用observer函数监听外部变化,然后更新组件内_index数据(考虑到不可以直接修改index属性,不然会导致死循环调用observer函数,产生内存溢出

在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。
详细:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html

// components/epsoide/index.js
Component({
  /**
   * 组件的属性列表 属性中的变量不能和data中的变量同名
   */
  properties: {
    // index: Number
    index: {
      type: Number,
      observer: function(newVal, oldVal, changedPath){ // 监听者模式
        let val = newVal < 10?'0'+ newVal:newVal
        this.setData({
          _index: val
        })
      }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    // data中数据的赋值,不能像properties中那样,直接给初始值就行
     months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
     year: 0,
     month: '',
     _index: ''
  },

// 组件生命周期函数attached 在组件实例进入页面节点树时执行,还没渲染页面
  attached: function(){
    let date = new Date();
    let year = date.getFullYear();
    let month = date.getMonth();

    this.setData({
      year: year,
      month: this.data.months[month]
    })
  },

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

  }
})

5.2 创建页面及css样式

该样式比较讲究,将刊号撑大。右边是月数汉字在上,年份在下。
(1)No、刊号index、分割符 划为一个view内(index-container);发行时间整体在一个view内(date-container)。

// index.wxml
<view class="container">
  <view class="index-container">
    <text class="plain">No.</text>
    <text class="index">{{_index}}</text>
    <view class="line"></view>
  </view>
  <view class="date-container">
    <text class="month">{{month}}</text>
    <text class="year">{{year}}</text>
  </view>
</view>

(2)大容器是flex布局,并且是横向的,高度与index的高度相同。期刊布局中的三个元素靠下对齐使用baseline。分割符直接使用view的border-left: 1px solid black;使其成为一条线。

.container {
  /* 根据08的高度 */
 height: 60rpx; 
 display: inline-flex;
 flex-direction: row;
}

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

.plain{
  font-size: 32rpx;
}

.index {
  font-size: 60rpx;
  /* 限制行高 */
  line-height: 60rpx;
  font-weight: 800;
  margin-right: 14rpx;
}

.line {
  height: 44rpx;
  margin-right: 14rpx;
  border-left: 1px solid black;
}

(3)日期布局date,flex垂直布局,设置下日期大小即可。

.date-container {
  display: flex;
  flex-direction: column;
  margin-top: 5rpx;
}

.month {
  font-size: 24rpx;
  line-height: 24rpx;
}

.year {
  font-size: 20rpx;
}

(4)最后是classic页面布局。episode组件和like组件同属于一个布局header。

/* header */
.header {
  display: flex;
  flex-direction: row;
  height: 100rpx;
  align-items: center;
  // 四周空一点
  justify-content: space-between;
  // 上下有分割线
  border-top: 1px solid #f5f5f5;
  border-bottom: 1px solid #f5f5f5;
}

.episode {
  margin-left: 20rpx;
  margin-top:  4rpx;
}

.like {
  margin-top: 6rpx;
}

5.3 在展示页面引入

// classic.json 中引入组件
{
  "usingComponents": {
    "v-like":"/components/like/index",
    "v-movie": "/components/classic/movie/index",
    "v-episode": "/components/episode/index"
  }
}

// classic.wxml中显示组件
<v-episode index='{{classic.index}}' />

6、开发导航组件navi

在这里插入图片描述
分析:
该组件比较简单,由两个image、text组成,整体在一个view中。navi组件中的标题和刊号是需要外部传入的数据。

6.1 导航组件的属性和数据

  1. 设置组件中的属性,这里有title、first、latest属性;
  2. 设置组件中变量,这里有图片的路径,disLeftSrc、leftSrc、disRightSrc、rightSrc。

navi.js文件中

 // navi.js
 /**
   * 组件的属性列表
   * first:是否最早期刊
   * latest: 是否最新期刊
   */
  properties: {
    title: String,
    first: Boolean,
    latest: Boolean
  },

  /**
   * 组件的初始数据
   */
  data: {
    disLeftSrc: 'images/triangle.dis@left.png',
    leftSrc: 'images/triangle@left.png',
    disRightSrc: 'images/triangle.dis@right.png',
    rightSrc: 'images/triangle@right.png'
  },

6.2 导航组件的布局和样式

navi.wxml中

// navi.wxml组件静态页面
<view class="container"> 
	<image class="icon" src="{{latest?disLeftSrc:leftSrc}}"></image>
	<text class="title" />{{title}}</text>
	<image class="icon" src="{{first?disRightSrc:rightSrc}}"/></image>
</view>

// navi.wxss组件样式,首先整体处在flex布局,按行排列,两边对齐。
// 这里布局必须给个宽度,不然会导致组件不能占满屏幕。
.container {
	width: 600rpx;
	height: 80 rpx;
	display:flex;
	flex-direction: row;
	justify-content: space-between; // 两边对齐
  	align-items: center;
  	background-color: #f7f7f7;
 	border-radius: 2px;
}

.icon {
	height: 80rpx;
  	width: 80rpx;
}

.title {
	font-size: 28rpx;
}

6.3 导航组件的应用

在classic.json中注册

{
  "usingComponents": {
    "v-navi": "/components/navi/navi"
  } 
}

对导航组件在classic中进行排版,让其为绝对布局距离底部40rpx。

// classic.wxml 使用navi组件,传入自定义title,是否起始页、最新页参数。
<v-navi class="navi" title="123" first="{{first}}" latest="{{latest}}"/>

// classic.wxss
.navi {
	// 绝对布局,距离底部40rpx
	position: absolute;
	bottom: 40rpx;
}

// classic.js

data : {
	latest: true, // 最新期刊,默认是最新期刊
    first: false // 最早期刊。默认不是最早
}

6.4 导航组件的左右切换func

  1. 给navi组件编写自定义函数onLeft、onRight,点击图片给页面传递自定义事件名称;
  2. 给页面绑定监听事件并做切换处理。

PS:自定义事件的传递
在这里插入图片描述
navi组件中

// navi.wxml
<view class="container">
  <image bind:tap="onLeft" class="icon" src="{{first?disLeftSrc:leftSrc}}"></image>
  <text class="title">{{title}}</text>
  <image bind:tap="onRight" class="icon" src="{{latest?disRightSrc:rightSrc}}"></image>
</view>

// navi.js
methods: {

    onLeft: function(event){
      console.log("left");
      // 如果不是最早的期刊,点击了向左的按钮会响应。
      if(!this.properties.first){
        this.triggerEvent('left',{} , {});
      }
      
    },
    onRight: function(event){
      console.log("right");
      // 如果不是最早的期刊,点击了向左的按钮会响应。
      if(!this.properties.latest){
        this.triggerEvent('right',{} , {});
      }
    }
  }

页面的监听和声明处理函数

// classic.wxml
<v-navi bind:left="onPrevious" bind:right="onNext" class="navi" title="《饮食男女》" first="{{first}}" latest="{{latest}}"></v-navi>

// classic.js
// 自定义的onLeft-onPrevious和onRight-onNext函数
  onPrevious: function(e){
    var behavior = e.detail.behavior;
  },
  onNext: function(e){
    var behavior = e.detail.behavior;
  },

7、music、essay组件开发并完善(behavior)

7.1 music组件

music组件主要由播放、暂停图片和音乐背景图,还有一句内容词组成。
由外部传入的只有img和content。

// music.wxml
<view>
  <image src="{{img}}"></image>
  <image src="{{playSrc}}"></image>
  <image src="image/music@tag.png"></image>
  <text>{{content}}</text>
</view>

// music.js
properties: {
    img: {
      type: String
    },
    content:String
  },

  /**
   * 组件的初始数据
   */
  data: {
    pauseSrc: 'images/player@pause.png',
    playSrc: 'images/player@play.png'
  },

7.2 essay组件(文章组件)

essay组件主要由句子背景图,还有一句内容词组成。
由外部传入的只有img和content。
所以可以使用behavior对相同代码抽取出来,进行公用。
(1)定义behavior对象classic-beh.js,抽取公共代码

// behavior 定义组件中共有的属性 主要作用是抽离出相同的代码
// Behavior 与index.js中的Component是类似的

let classicBeh = Behavior({
  properties: {
    img: {
      type: String
    },
    content:String
  },

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

})

export {classicBeh}

(2)组件中的使用behavior 同时将music组件以及movie组件中替换

替换essay组件中的properties。

// components/classic/essay/essay.js
import {classicBeh} from '../classic-beh.js'

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

  },

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

  },

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

  }
})

8、完善并优化-导航栏组件的切换

分析:在classic页面中,我们给向前和向后按钮设置了初始状态。在点击过程中index期刊号会随之变化,内容也会变化,因此我们需要将页面的数据进行动态变化,也就是setData进行渲染。

8.1 将判断latest、first期刊号方法,封装进model的classicModel中

// 判断当前期刊是否首期
  isFirst(index){
    return index == 1 ? true:false;
  }

  // 判断当前期刊是否最新
  isLatest(index){
    let latestIndex = this._getLatestIndex();
    return latestIndex == index ? true:false;
  }

  // 缓存最新index期刊号
  _setLatestIndex(index) {
    wx.setStorageSync('latest', index);
  }
  _getLatestIndex(){
    let index =  wx.getStorageSync('latest');
    return index;
  }

8.2 将后台获取上一期、下一期内容方法,同样封装进classicModel中

需要以【当前index期刊号,回调函数传结果】为参数,向后台发送请求。

  // 获取当前上一期数据的函数
  getPrevious(index, sCallBack){
    this.request({
      url: 'classic/'+ index + '/previous',
      success: (res) =>{
        sCallBack(res)
      }
    })
  }

  // 获取当前下一期数据的函数
  getNext(index, sCallBack){
    this.request({
      url: 'classic/'+ index + '/next',
      success: (res) =>{
        sCallBack(res)
      }
    })
  }

8.3 在页面classic.js中使用classicModel定义方法

// 自定义的onLeft-onPrevious和onRight-onNext函数
  onPrevious: function(e){

    // 当前index号
    let index = this.data.classic.index;
    classicModel.getPrevious(index, (res)=>{
      // 异步返回上一期数据
      // 渲染新数据
      this.setData({
        classic: res,
        latest: classicModel.isLatest(res.index),
        first: classicModel.isFirst(res.index)
      })

    })
  },
  onNext: function(e){
    let index = this.data.classic.index;
    classicModel.getNext(index, (res)=>{
      this.setData({
        classic: res,
        latest: classicModel.isLatest(res.index),
        first: classicModel.isFirst(res.index)
      })
    })
  },

9、onNext和onPrevious方法重构

onNext方法和onPrevious方法的逻辑基本相同,所以将两个方法进行合并。将不同的地方使用参数进行传递。

9.1 classicModel中的getNext()和getPrevious()整合成getClassic(x,x,x)方法

/***代码重构***/
// 获取期刊数据
 getClassic(index, nextOrPrevious, sCallBack){
   this.request({
     url: 'classic/' + index + "/" + nextOrPrevious,
     success: (res)=>{
       sCallBack(res);
     }
   })
 }

9.2 页面classic.js中的getNext()和getPrevious()整合成私有方法_updateClassic(…)

// 重构getNext()、getLatest()代码
_updateClassic: function(nextOrPrevious){
  let index = this.data.classic.index;
  classicModel.getClassic(index, nextOrPrevious, (res)=>{
    this.setData({
      classic: res,
      latest: classicModel.isLatest(res.index),
      first: classicModel.isFirst(res.index)
    })
  })
},
 // 自定义的onLeft-onPrevious和onRight-onNext函数
  onPrevious: function(e){
    _updateClassic("previous");
   
  },
  onNext: function(e){
    _updateClassic("next");
    
  },

10、getClassic()方法使用本地缓存(提高select效率各方面)

 /***代码重构***/
  // 获取期刊数据
  getClassic(index, nextOrPrevious, sCallBack){
    // 缓存中寻找 or API 
    // key 确定key
    let key = nextOrPrevious == "next" ? 
        this._getKey(index + 1):this._getKey(index - 1)
    let classic = wx.getStorageSync(key);
    if(!classic){
      this.request({
        url: 'classic/' + index + "/" + nextOrPrevious,
        success: (res)=>{
          wx.setStorageSync(this._getKey(res.index), res);
          // 返回res
          sCallBack(res);
        }
      })
    }else {
      sCallBack(classic);
    }
  }
 
// 自定义key名称区分于index即可
  _getKey(index) {
    return "classic-" + index; 
  }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值