开发那点事(十)组件化开发微信小程序案例

开发背景
最近在优化一个两三年的老项目,奈何UI小姐姐出的图太高大上,用了好久的微信自带网络加载api,没法满足需求,但是在每个页面上写太多相同的setData太low,于是下定决心封装一个。

插件实现功能

  • 1 网络加载框
  • 2 选择对话框
  • 3 吐司对话框

本文要点

  • 1 插件效果演示(amazing)
  • 2 插件集成使用(import)
  • 2 小程序插件的开发与使用(component)
  • 3 基于插件相关功能Util类封装(loadToastUtil)
  • 4 总结(summary)

上干货

插件效果演示(amazing)

效果演示.gif

插件集成使用(import)

插件下载地址在文末,欢迎下载,欢迎star😄😄😄
在小程序页面的json文件中引入该插件

home.json

{
  "usingComponents": {
    "jkDialog": "../../view/jk-dialog/jk-dialog"
  }
}

在js文件中引入jkDialogUtil,注意构造方法中的三个参数

home.js

import {
  jkDialogUtil
} from "../../view/jk-dialog/dialogUtil";
let dialogUtil;
//三个参数分别对应page对象,网络请求基础url,对应插件id
dialogUtil = new jkDialogUtil(this, 'https://www.baidu.com', '#loadToast');
//调用吐司对话框
dialogUtil.showToast('提示用户\n换行');
//调用选择对话框
dialogUtil.showSelectDialog({
      selectTip: '测试',
      selectContent: '测试选择对话框\njust soso',
    }, (res) => {
      dialogUtil.showToast(res == 'confirm' ? '确认操作' : '取消操作');
    });
//显示隐藏网络加载框
dialogUtil.showLoading();
dialogUtil.hiddenLoading();

原理分析

小程序插件的开发与使用(component)
在项目文件中定义view文件夹,用来存放自定义组件
右键新建Component,以我的为例取名为jk-dialog
这个时候开发者工具就会生成四个相应的文件
话不多说,直接上代码

jk-dialog.json
json文件,这里需要注意的就是component字段,声明这个模块是一个插件

{
  "component": true, //声明组件
  "usingComponents": {}
}

jk-dialog.wxml
wxml文件,插件页面文件,这个分成三个部分,网络加载框 选择对话框 吐司对话框,分别用三个boolean变量控制显隐

<view wx:if="{{showLoading}}" class="load-container">
    <image src="./img/loading.png"></image>
</view>

<view wx:if="{{showSelect}}" class="pop-container">
    <view class="delay-con column-center">
        <view class="delay-title">
            {{selectTip}}
        </view>
        <view class="delay-content column-center">
            <text>{{selectContent}}</text>
        </view>
        <view class="delay-bottom row-center">
            <view data-state="confirm" bindtap="hiddeenSelectDialog" style="border-right:1px solid #d6d6d6">{{confirmText}}</view>
            <view data-state="cancel" bindtap="hiddeenSelectDialog">{{cancelText}}</view>
        </view>
    </view>
</view>

<view wx:if="{{showToast}}" style="background: transparent;" class="load-container">
    <view class="toast-con">
        <text>{{toastContent}}</text>
    </view>
</view>

jk-dialog.wxss
wxss文件,页面样式,其中用到了一些动画效果,有想法的可以看看,虽然我也是在网上找的代码。😄😄😄

.load-container {
  pointer-events: auto;
  color: #000;
  font-size: 20px;
  z-index: 9999;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.load-container image {
  width: 50px;
  height: 50px;
  animation: loading-rotate 1s linear infinite;
}

@keyframes loading-rotate {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

.pop-container {
  pointer-events: auto;
  color: #000;
  font-size: 20px;
  z-index: 10;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.row-center {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

.delay-con {
  width: 80%;
  border-radius: 25rpx;
  background: white;
  font-size: 32rpx;
  margin-bottom: 200rpx;
  -webkit-animation: fadeleftIn .4s;
  animation: fadeleftIn .4s;
  -webkit-animation-name: popIn;
  animation-name: popIn;
}


.delay-title {
  padding-top: 30rpx;
  padding-bottom: 20rpx;
}

.column-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.delay-content {
  padding-bottom: 30rpx;
  line-height: 45rpx;
  text-align: center;
}

.delay-bottom {
  width: 100%;
  border-top: 1px solid #d6d6d6;
  color: #027bfe;
}

.delay-bottom view {
  width: 50%;
  padding-top: 25rpx;
  padding-bottom: 25rpx;
  text-align: center;
}

.toast-con {
  width: calc(85% - 60px);
  padding-top: 20px;
  padding-bottom: 20px;
  padding-left: 30px;
  padding-right: 30px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.8);
  margin-bottom: 200px;
  color: white;
  text-align: center;
  font-size: 17px;
  -webkit-animation: fadelogIn .2s;
  animation: fadelogIn .2s;
}

@-webkit-keyframes popIn {
  0% {
    -webkit-transform: scale3d(0, 0, 0);
    transform: scale3d(0.5, 0.5, 0.5);
    opacity: 0;
  }

  50% {
    -webkit-animation-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715);
    animation-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715);
  }

  100% {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
    -webkit-animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    opacity: 1;
  }
}

@keyframes popIn {
  0% {
    -webkit-transform: scale3d(0, 0, 0);
    transform: scale3d(0.5, 0.5, 0.5);
    opacity: 0;
  }

  50% {
    -webkit-animation-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715);
    animation-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715);
  }

  100% {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
    -webkit-animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    opacity: 1;
  }
}

@keyframes fadelogIn {
  0% {
    -webkit-transform: translate3d(0, 100%, 0);
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }

  100% {
    -webkit-transform: none;
    transform: none;
  }
}

@-webkit-keyframes fadelogIn {
  0% {
    -webkit-transform: translate3d(0, 100%, 0);
  }

  100% {
    -webkit-transform: none;
  }
}

jk-dialog.js
js文件,封装了三个对话框的显示隐藏

let systemInfo, selectCallBack;
import {
  selectLabel
} from './config'
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    toastText: {
      type: String,
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    statusBarHeight: 0,
    showLoading: false,
    showToast: false,
    toastContent: '',
    showSelect: false,
    selectTip: '提示',
    selectContent: '该操作不可逆\n是否继续',
    confirmText: '确认',
    cancelText: '取消'
  },
  ready: function () {
    let that = this;
    that.initData();
  },
  /**
   * 组件的方法列表
   */
  methods: {
    /*用户提示对话框
    \n表示换行
     */
    showToast: function (content, duration) {
      let that = this;
      that.setData({
        showLoading: false,
        showSelect: false,
        showToast: true,
        toastContent: content
      });
      let timer = setTimeout((res) => {
        that.setData({
          showToast: false,
        });
        clearTimeout(timer);
      }, duration == undefined ? 1500 : duration)
    },
    /* 展示自定义加载框
     */
    showLoading: function () {
      this.setData({
        showSelect: false,
        showToast: false,
        showLoading: true,
      })
    },
    /* 隐藏自定义加载框
     */
    hiddenLoading: function () {
      this.setData({
        showLoading: false,
      })
    },
    /* 初始化数据
     */
    initData: function () {
      let that = this;
      wx.getSystemInfo({
        success: function (res) {
          console.log(res, 'system');
          if (res.errMsg === 'getSystemInfo:ok') {
            that.setData({
              statusBarHeight: res.statusBarHeight
            });
            res.lang = res.language.indexOf('zh') !== -1 ? 'zh' : 'en';
            systemInfo = res;
          }
        },
      });
    },
    /* 展示选择对话框
     */
    showSelectDailog: function (params, callBack) {
      let that = this;
      that.setData({
        showToast: false,
        showLoading: false,
        showSelect: true,
        selectTip: params.selectTip != undefined ? params.selectTip : selectLabel[systemInfo.lang]['selectTip'],
        selectContent: params.selectContent != undefined ? params.selectContent : selectLabel[systemInfo.lang]['content'],
        confirmText: params.confirmText != undefined ? params.confirmText : selectLabel[systemInfo.lang]['confirmText'],
        cancelText: params.cancelText != undefined ? params.cancelText : selectLabel[systemInfo.lang]['cancelText'],
      });
      selectCallBack = callBack;
    },

    hiddeenSelectDialog: function (e) {
      let that = this;
      that.setData({
        showSelect: false
      });
      selectCallBack(e.currentTarget.dataset.state);
    }
  }
})

基于插件相关功能Util类封装(loadToastUtil)
插件页面基本已经好了,还差一个工具类,调用比较方便。
dialogUtil.js
我是直接封装成了一个class类,在构造方法中传入小程序Page对象,baseUrl以及插件id,简单暴露了一些方法供每一个页面调用。
需要注意的是小程序中的一个api selectComponent
通过这个获取到插件对象,以此来调用插件js文件中写好的方法

const app = getApp();
export class jkDialogUtil {
  /* 
  构造方法 
  that:page对象
  baseUrl:网络请求基础url
  loadToast:对应插件id
  */
  constructor(that, baseUrl, loadToast) {
    this.that = that;
    this.baseUrl = baseUrl;
    this.loadToast = that.selectComponent(loadToast);
    if (this.loadToast != null)
      console.log('插件加载成功');
    else
      console.log('插件加载失败');
  }

  /*展示网络加载框  */
  showLoading() {
    if (this.loadToast != null)
      this.loadToast.showLoading();
  }
  /*隐藏网络加载框  */
  hiddenLoading() {
    if (this.loadToast != null)
      this.loadToast.hiddenLoading();
  }
  /**
   * 网络请求
   *let params = {
              url: '/authorization/v1/token/decode',
              data: data,
              method: 'POST',
              isCommon: true,
              isLoad: true
            }
          successCallback:成功回调
          errorCallback:失败回调
   */
  baseRequest(params, successCallback, errorCallback) {
    if (this.loadToast == null) {
      return;
    }
    let that = this;
    let realUrl = that.baseUrl + params.url;
    if (params.type == 'realUrl') {
      realUrl = params.realUrl;
    }
    if (params.isLoad) {
      that.loadToast.showLoading();
    };
    let token = wx.getStorageSync('accessToken');
    let header = {
      'Authorization': 'Bearer ' + token,
      'X-TrackingId': that.getXTrackingId() + ''
    };
    if (params.header != undefined) {
      header = Object.assign({}, params.header, header);
    }
    wx.request({
      url: realUrl,
      method: params.method,
      header: header,
      data: params.data,
      success: res => {
        if (params.isLoad) {
          that.loadToast.hiddenLoading();
        }
        if (res.data.responseCode === undefined) {
          that.baseToast('未知错误');
          return;
        }
        if (res.data.responseCode === -2) {
          wx.login({
            success: res => {
              jKLogin(res.code, function () {
                that.baseRequest(params, successCallback, errorCallback)
              })
            }
          });
          return;
        }
        if (!params.isCommon) {
          successCallback(res.data);
          return;
        }
        if (res.data.responseCode != undefined) {
          if (res.data.responseCode === 0) {
            successCallback(res.data.data);
            return;
          } else {
            that.baseToast(res.data.responseMessage);
            errorCallback == undefined ? '' : errorCallback(res.data);
          }

          return;
        }

        baseToast('未知错误');
      },
      error: res => {
        that.loadToast.hiddenLoading();
        console.log(res, 'error');
        that.baseToast('未知错误')

      }
    })

  }
  /*展示选择对话框  */
  showSelectDialog(params, callBack) {
    if (this.loadToast != null)
      this.loadToast.showSelectDailog(params, callBack);
  }
  /* 展示吐司对话框 */
  showToast(content) {
    if (this.loadToast != null)
      this.loadToast.showToast(content);
  }
  baseToast(content) {
    wx.showToast({
      title: content,
      icon: 'none',
      duration: 3000
    })
  }

  getXTrackingId() {
    let chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
    let XTrackingId = "";
    for (let i = 0; i < 32; i++) {
      let id = parseInt(Math.random() * 61);
      XTrackingId += chars[id];
    }
    return XTrackingId;
  }

}

源码地址点我前往
总结(summary)
在工具类中其实还封装了baseRequest的网络请求方法,大家可以按照具体需求自行修改。
没bug 祝好运 ~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容