#yyds干货盘点#【愚公系列】2023年02月 微信小程序-Page页面扩展

时间:2023-02-02 13:03:33

前言

在小程序日常开发中,有些功能是所有页面都需要使用的,所以就需要对每个page页面进行扩展,在小程序中Page 的作用相当于构造函数, Page 会初始化页面对象然后将配置参数中的属性 merge 到页面对象上。

一、Page页面扩展

1.组件的封装和引用

extend-page.js


const extendPage = Page => {
  return object => {
    // 登录面板开头
    if (!object.data) object.data = {}
    object.data.showLoginPanel = false 

    // method
    object.hi = function(name){
      console.log(`hi ${name}`);
    }

    // 派发一个等待处理,需要有代码处理的事件
    // 但这个方法没有什么用
    object.triggerWaitingEvent = function (type, data = {}){
      return new Promise((resolve,reject)=>{
        let eventCallback = res => resolve(res)
        Object.assign(data, {
          eventCallback
        })
        this.triggerEvent(type, data)
      })
    }

    return Page(object)
  }
}

const originalPage = Page
Page = extendPage(originalPage)

app.js中引入扩展页面

require("./lib/extend-page")

2.页面使用

<button bindtap="extendPage" type="primary">请求</button>
<LoginPanel show="{{showLoginPanel}}"></LoginPanel>
  async extendPageTest(){
    this.hi('weapp')
    // 使用request3
    let res4 = await wx.wxp.request4({
      url: 'http://localhost:3000/user/home',
    })
    if (res4) console.log('res4', res4)
  },

3.效果

#yyds干货盘点#【愚公系列】2023年02月 微信小程序-Page页面扩展

二、其他相关封装

1.pop-up组件


Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },
  /**
   * 组件的属性列表
   */
  properties: {
    visible: {
      type: Boolean,
      value: false
    }
  },

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

  ready(){
    this.triggerEvent('ready')
  },

  /**
   * 组件的方法列表
   */
  methods: {
    popPreventTouchmove() { },
    popPreventTouchmove2() { },
    popPreventTouchmove3() { },
    cityChange() { },
    close() {
      this.triggerEvent('close')
    },
    handleClickMask(e) {
      // console.log(e)
      if (e.target.dataset.type !== 'unclose') this.close()
    }
  }
})

<view catchtouchmove="popPreventTouchmove">
  <view class="q-pp-mask  {{ visible ? 'q-pp-mask-show' : '' }} ptp_exposure" bindtap="handleClickMask" catchtouchmove="popPreventTouchmove">
    <view class=" q-pp {{ visible ? 'q-pp-show' : '' }}" catchtouchmove="popPreventTouchmove">
      <slot name="content" ></slot>
    </view>
  </view>
</view>
.q-pp {
  position: fixed;
  width: 100%;
  box-sizing: border-box;
  left: 0;
  right: 0;
  bottom: 0;
  background: #f7f7f7;
  transform: translate3d(0, 100%, 0);
  transform-origin: center;
  transition: all 0.2s ease-in-out;
  z-index: 900;
  visibility: hidden;
}

.q-pp-show {
  transform: translate3d(0, 0, 0);
  visibility: visible;
}

.q-pp-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  z-index: 900;
  transition: all 0.2s ease-in-out;
  opacity: 0;
  visibility: hidden;
}

.q-pp-mask-show {
  opacity: 1;
  visibility: visible;
}

2.LoginPanel组件

<pop-up visible="{{visible}}">
	<view slot="content">
		<view class="picker-view">
			<view class="picker-view__pane">
				<text bindtap="close">取消</text>
			</view>
			<view style="font-size:17px;font-weight:bold;line-height:30px;border-bottom:1px solid #f2f2f2;padding: 0px 0 20px;width: 100%;color:#353535;text-align: center;">微信授权登录</view>
			<image style="width: 40px;height: 40px;padding-top: 20px;" src="../../components/custom-tab-bar/component-on.png"></image>
			<view style="font-size:17px;font-weight:400;color:#353535;line-height: 40px;">小程序将申请获取以下权限</view>
			<view style="font-size:11px;color:#b2b2b2;border-bottom:1px solid #f2f2f2;width: 100%;padding: 0px 0 40px;text-align: center;">● 获得你的公开信息(昵称、头像、地区及性别)</view>
			<view style="height:80px;padding-top: 20px;">
				<button bindgetuserinfo="login" open-type="getUserInfo" type="primary">登陆</button>
			</view>
		</view>
	</view>
</pop-up>

.picker-view {
  width: 100%;
  display: flex;
  background-color: #fff;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  bottom: 0rpx;
  left: 0rpx;
}

.picker-item {
  line-height: 70rpx;
  margin-left: 5rpx;
  margin-right: 5rpx;
  text-align: center;
}

.picker-view__pane {
  height: 100rpx;
  width: 100%;
  padding: 20rpx 32rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-sizing: border-box;
}

.picker-view__pane text{
  color: #00cc88;
  font-size: 30rpx;
}

.pick-view__group {
  width: 96%;
  height: 450rpx;
}


Component({
  options: {
    multipleSlots: false
  },
  properties: {
    show: {
      type: Boolean,
      value: false
    }
  },
  observers: {
    'show': function (value) {
      console.log(value);

      this.setData({
        visible: value
      })
    }
  },
  data: {
    visible: false
  },
  ready() {},
  methods: {
    close(e) {
      this.setData({
        visible: false
      })
    },
    async login(e, retryNum = 0) {
      let {
        userInfo,
        encryptedData,
        iv
      } = e.detail

      // 本地token与微信服务器上的session要分别对待
      let tokenIsValid = false, sessionIsValid = false
      let res0 = await getApp().wxp.checkSession().catch(err=>{
        // 清理登陆状态,会触发该错误
        // checkSession:fail 系统错误,错误码:-13001,session time out…d relogin
        console.log("err",err);
        tokenIsValid = false
      })
      console.log("res0", res0);
      if (res0 && res0.errMsg === "checkSession:ok") sessionIsValid = true
      let token = wx.getStorageSync('token')
      if (token) tokenIsValid = true

      if (!tokenIsValid || !sessionIsValid) {
        let res1 = await getApp().wxp.login()
        let code = res1.code
        console.log("code",code);
        
        let res = await getApp().wxp.request({
          url: 'http://localhost:3000/user/wexin-login2',
          method: 'POST',
          header: {
            'content-type': 'application/json',
            'Authorization': `Bearer ${token || ''}`
          },
          data: {
            code,
            userInfo,
            encryptedData,
            iv,
            sessionKeyIsValid:sessionIsValid
          }
        })
        
        if (res.statusCode == 500){
          if (retryNum < 3){
            this.login.apply(this, [e, ++retryNum])
          }else{
            wx.showModal({
              title: '登录失败',
              content: '请退出小程序,清空记录并重试',
            })
          }
          return
        }
        // Error: Illegal Buffer at WXBizDataCrypt.decryptData
        console.log('登录接口请求成功', res.data)
        token = res.data.data.authorizationToken
        wx.setStorageSync('token', token)
        console.log('authorization', token)
      }

      getApp().globalData.token = token
      wx.showToast({
        title: '登陆成功了',
      })
      this.close()
      this.triggerEvent('loginSuccess')
      getApp().globalEvent.emit('loginSuccess')

    },
    login2(e) {
      let {
        userInfo,
        encryptedData,
        iv
      } = e.detail
      // console.log('userInfo', userInfo);

      const requestLoginApi = (code) => {
        //发起网络请求
        wx.request({
          url: 'http://localhost:3000/user/wexin-login2',
          method: 'POST',
          header: {
            'content-type': 'application/json'
          },
          data: {
            code: code,
            userInfo,
            encryptedData,
            iv
          },
          success(res) {
            console.log('请求成功', res.data)
            let token = res.data.data.authorizationToken
            wx.setStorageSync('token', token)
            onUserLogin(token)
            console.log('authorization', token)
          },
          fail(err) {
            console.log('请求异常', err)
          }
        })
      }

      const onUserLogin = (token) => {
        getApp().globalData.token = token
        wx.showToast({
          title: '登陆成功了',
        })
        this.close()
        this.triggerEvent('loginSuccess')
        getApp().globalEvent.emit('loginSuccess')
      }

      const login = () => {
        wx.login({
          success(res0) {
            if (res0.code) {
              requestLoginApi(res0.code)
            } else {
              console.log('登录失败!' + res0.errMsg)
            }
          }
        })
      }

      wx.checkSession({
        success() {
          //session_key 未过期,并且在本生命周期一直有效
          console.log('在登陆中');
          let token = wx.getStorageSync('token')
          if (token) {
            onUserLogin(token)
          } else {
            // session会重复,需要处理
            login()
          }
        },
        fail() {
          // session_key 已经失效,需要重新执行登录流程
          login()
        }
      })

    }
  }
})
{
  "component": true,
  "usingComponents": {
    "pop-up": "../pop-up/index"
  }
}

3.LoginPanel组件

//app.js
require("./lib/extend-page")
// import Event from './lib/event'
import Event from './lib/event2'
import wxp from './lib/wxp'

// getApp().globalEvent
App({
  wxp: (wx.wxp = wxp),
  globalData: (wx.globalData = {}),
  globalEvent: (wx.globalEvent = new Event()),
  onLaunch: async function () {
    if (!wx.cloud) {
      console.error('请使用 2.2.3 或以上的基础库以使用云能力')
    } else {
      wx.cloud.init({
        // env 参数说明:
        //   env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
        //   此处请填入环境 ID, 环境 ID 可打开云控制台查看
        //   如不填则使用默认环境(第一个创建的环境)
        env: 'default-98491d',
        traceUser: true,
      })
    }

    this.globalData = {}
  },
import {
  promisifyAll
} from 'miniprogram-api-promise';

const wxp = {}
promisifyAll(wx, wxp)

// compatible usage
// wxp.getSystemInfo({success(res) {console.log(res)}})

wxp.request2 = function (args) {
  let token = wx.getStorageSync('token')
  if (token) {
    if (!args.header) args.header = {}
    args.header['Authorization'] = `Bearer ${token}`
  }
  return wxp.request(args).catch(function (reason) {
    console.log('reason', reason)
  })
}

// 
wxp.request3 = function(args){
  let token = wx.getStorageSync('token')
  if (!token){
    return new Promise((resolve, reject)=>{
      let pageStack = getCurrentPages()
      if (pageStack && pageStack.length > 0) {
        let currentPage = pageStack[pageStack.length-1]
        currentPage.setData({
          showLoginPanel2:true
        })
        getApp().globalEvent.once("loginSuccess", ()=>{
          wxp.request2(args).then(res=>{
            resolve(res)
          }, err=>{
            console.log('err', err);
            reject(err)
          })
        })
      }else{
        reject('page valid err')
      }
    })
  }
  return wxp.request2(args)
}

wxp.request4 = function (args) {
  let token = wx.getStorageSync('token')
  if (!token) {
    let pages = getCurrentPages()
    let currentPage = pages[pages.length - 1]
    // 展示登陆浮窗
    currentPage.setData({
      showLoginPanel: true
    })
    return new Promise((resolve, reject) => {
      getApp().globalEvent.once('loginSuccess', function (e) {
        wxp.request2(args).then(function (result) {
          resolve(result)
        }).catch(function (reason) {
          console.log('reason', reason);
        })
      })
    })
  }
  return wxp.request2(args).catch(function (reason) {
    console.log('reason', reason);
  })
}

export default wxp