微信小程序 全局任务弹窗方案分享(微侵入式) Template + Request 拦截器

Posted by Yinode on Friday, November 15, 2019

TOC

最近在工作中需要在微信小程序中加入一套任务系统,某些操作将会触发一个任务弹窗

整个子任务会比较分散,如果使用小程序的自定义组件系统,可能会导致过于过于偏离 DRY 原则 的代码,在经过一些调研以及和后端的协调之后,最终决定使用一套基于 Request 中间件拦截 + template WXML 模板 共同组成的方案,由于微信小程序的限制(无法直接操作 DOM) ,所以不可避免的会拥有轻微的侵入性,不过好在这个代价应该还算是可以接受的。

任务弹窗模板部分

首先需要建立我们的任务弹窗模板

/template/missionDialog/index.wxml

<!-- 任务完成消息提示弹窗 -->
<template name="mission">
  <view wx:if="{{showMission}}" class="mission-dialog-mask">
    <view class="mission-dialog-box">
      <image src="/images/ic-mission-bg.png" class="bg" />
      <view class="inner">
        <view class="top">
          <view class="title">— 恭喜您 —</view>
          <view class="text">{{missionInfo.title}}</view>
          <view class="text">{{missionInfo.desc}}</view>
        </view>
        <view class="style">
          <image src="/images/ic-mission-light.png" class="light" />
          <image src="/images/ic-mission-ic.png" class="ic" />
        </view>
        <view class="btns {{missionInfo.showKnow ? 'double' : ''}}">
          <view bind:tap="_handleTapMissionClose" class="btn close">关闭</view>
          <view bind:tap="_handleTapMissionKnow" class="btn know" wx:if="{{missionInfo.showKnow}}">
            我知道了
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

我们采用 showMission + missionInfo 对象来共同控制整个弹窗。这两个属性将会在所有需要启动该弹窗的页面实例上进行控制。

引入 Template 模板

接下来在所有需要启动任务弹窗的页面中嵌入引用

<import  src="/template/missionDialog/index.wxml"></import>
<template  is="mission"  data="{{showMission:showMission,missionInfo:missionInfo}}" />

修改请求中间件

我们的约定是一但某个操作完成了一个目标任务,后端就会在请求结果里面的 task 字段中嵌入任务信息。比如说任务名称,任务奖励。所以我们能够在中间件中统一处理。

 return function(appId, url, data, successCallback) {
    const token = userInfo.getToken()
    wx.showLoading({
      title: '处理中'
    })
    wx.request({
      method: method,
      dataType: 'json',
      responseType: 'text',
      header: {
        token: token,
        'content-type': 'application/json'
      },
      // 该正则调试时使用
      url: /mock/.test(host) ? `${host}${url}` : `${host}/${appId}${url}`,
      data: data,
      success: function(res) {
       // xxx 某些业务
        
       // 先触发请求回调 该干嘛干嘛
        successCallback(res.data)

        // 如果完成了某个任务 弹出任务弹窗
        setTimeout(() => {
          const pages = getCurrentPages()
          let context = pages[pages.length - 1]
          if (res.data.tasks && res.data.tasks.length > 0) {
            let taskInfo = res.data.tasks[0]
            if (!context) {
              return console.log(`请给${url}添加上下文`)
            }
            let title =
              taskInfo.type === '10'
                ? `完成${taskInfo.title}任务`
                : `完成新手任务`
            let desc =
              taskInfo.type === '10'
                ? `影响力+${taskInfo.influenceScore}`
                : `${taskInfo.title}`

            context._handleTapMissionClose = () => {
              context.setData({
                showMission: false
              })
              // 点击按钮 A 之后 xxx
            }
            context._handleTapMissionKnow = () => {
              context.setData({
                showMission: false
              })
              // 点击按钮 B 之后 xxx
            }
            
            // 显示弹窗
            context.setData({
              showMission: true,
              missionInfo: {
                title: title,
                desc: desc,
                showKnow: true
              }
            })
          }
        }, 800)
      },
      fail: function(res) {
        console.log(res)
      },
      complete: function(res) {
        if (res.statusCode != 200) {
          console.log(res)
        }
      }
    })
  }
}

有两点值得注意

  1. 我们可以通过 currentPages 获取页面上下文 从而 setData 来控制弹窗
  2. setTimeout 的主要目的是为了避免某些操作可能会切换页面,做一定的延迟处理

总结

总的来说结果还是比较让人满意的,在付出较低的代价的情况下,做到了具有一定扩展性的技术方案

事实上嵌入模板的操作通过手写一个打包器来进行所有页面的注入,但是因为整个项目体量并不是很大,所以还是没有这么做,不过这里还是希望微信能够放开一些打包部分的自定义能力。