导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


登录

🔥 微信登录流程

登录流程是调wx.login获取code传给后台服务器获取微信用户唯一标识openid及本次登录的会话密钥(session_key)等)。拿到开发者服务器传回来的会话密钥(session_key)之后,前端要保存wx.setStorageSync('sessionKey', 'value')

持久登录状态:session信息存放在cookie中以请求头的方式带回给服务端,放到 request.js 里的 wx.request 的header 里

核心:通过 vuex 的 hasLogin 来判断是进行后续操作,还是跳转到登录页

  1. 程序启动 onLaunch
  2. 判断本地有没有 userInfo 和 userInfo.tel
    1. 有,把 openIdtelunionid 传给后端,换取最新 userInfo,设置 hasLogin = true
      1. openId 在不同应用下唯一
      2. unionid 在同一公司下,不同应用中唯一
    2. 无,设置 hasLogin = false,清空 userInfo,清空 token
  3. 登录页
    1. 通过 wx.login 获取 wxCode
    2. 授权手机号后
      1. wxCode手机号iv手机号encryptedData 发给后端换取 token、tel、userInfo
      2. 设置 登录状态 hasLogin
    3. 跳转回之前登录前的页面
      1. 写个公共跳转 auth 页面的方法
      2. 通过 getCurrentPages()[getCurrentPages().length - 1] 拿到当前页面
onLaunch: function () {
  this.wxLogin()
}
async wxLogin(context) {
  let userInfo = storage.getUserInfo();
  if (userInfo && userInfo.tel) {
    // 有电话,以前登录过,更新 userInfo
    const res = await user.doLoginMobile({
      openid: userInfo.openidApp,
      phone: userInfo.tel,
      unionid: userInfo.unionid,
    });
    if (res.code === 0 && res.data) {
      // 设置vuex登录状态
      context.commit("setHasLogin", true);
      context.commit("setUserInfo", res.data ? res.data : {});
      context.commit("setToken", res.data ? res.data.token : "");
      return context.state.userInfo;
    } else {
      // 登录失败
      // 登出
      context.commit("logout");
      return false;
    }
  } else {
    context.commit("logout");
    return false;
  }
},

// 登出
logout(state) {
  state.userInfo = {};
  state.hasLogin = false;
  storage.removeUserInfo();
  storage.removeToken();
},
// 手机号登录
async weChatLogin() {
  // 获取code
  this.wxCode = await this.getWxCode();

  const data = {
    code: this.wxCode, // 必传
    IV: this.userData.iv, // 必传(手机号的iv)
    encryptedData: this.userData.encryptedData, // 必传(手机号的encryptedData)
  };
  const res = await user.weChatLogin(data);

  if (res.code === 0 && res.data) {
    // 登录成功
    this.setHasLogin(true);
    this.$storage.setToken(res.data.token);
    this.setUserInfo(res.data);
    uni.showToast({ title: "授权登陆成功" });
    setTimeout(() => {
      this.jumpUrl(this.redirectUrl);
    }, 500);
  } else {
    // 登录失败
    uni.showModal({
      title: "登录失败,请重试",
      content: res.msg,
      showCancel: false,
    });
  }
},

🔥 页面跳转拦截

  1. 重写 navigateTo 等跳转方法,使其除了接收 options 对象参数,还接受一个 needAuth
  2. 如果 needAuth 为真,就看下 hasLogin 是否为真
    1. 为真代表已登录,就直接跳转
    2. 为假就代表未登录,跳转登录页
// 对于需要登录才可以进入的页面,先做一下登录校验,如果未登录,则跳转前先跳转至登录页面。
import store from "@/store";
import { gotoAuth } from "@/utils/index";

// 对路由相关的几个方法进行遍历并缓存原方法,
// 在uni对象上重写方法,每个方法增加needAuth参数,表示跳转前是否需要登录
// 如果需要登录,则首先获取用户信息,
// 如果获取不到用户信息,将会自动跳转到登录页(store里面的user模块做的),
// 否则获取到信息就调用缓存起来的原方法,实现跳转 如果不需要登录,则直接调用原方法
export default function () {
  ["navigateTo", "redirectTo", "switchTab", "navigateBack"].map(item => {
    const nativeFunc = uni[item];
    uni[item] = function (opts, needAuth) {
      if (needAuth) {
        // store.dispatch("user/getToken").then(() => {
        //   nativeFunc.call(this, opts);
        // });
        if (store.state.hasLogin) {
          nativeFunc.call(this, opts);
        } else {
          gotoAuth();
        }
      } else {
        return nativeFunc.call(this, opts);
      }
    };
  });
}

数据缓存

🔥 数据缓存

自定义组件

🔥 自定义导航栏

  1. components 文件夹下新建 hx-header
    1. index.json 中设置 component: true
    2. 父容器 position: fixed
    3. 导航栏高度 = 胶囊上下pending + 胶囊高度 + 状态栏高度
    4. 返回方法:wx.navigateBack
      1. 判断是否显示返回按钮:getCurrentPages().length === 1
      2. 返回首页 wx.switchTab({ url: 'pages/index/index' })
  2. 使用组件
    1. 组件内或全局 json:
      1. usingComponents: { "hx-header": 'path'}
      2. navigationStyle: "custom"
Component({
  properties: {},
  data: {
    navigationTop: 0, // 导航栏顶部高度
    navigationHei: 20, // 导航栏高度
    paddingLeft: 0, // 导航栏左内边距
    padddingTop: 0, // 导航栏上内边距
    imgArrow: "<https://cdn2.iconfinder.com/data/icons/font-awesome/1792/angle-left-512.png>" // 返回箭头
  },
  ready: function () {
    // 状态栏高度
    const { screenWidth, statusBarHeight } = wx.getSystemInfoSync();
    // 胶囊按钮
    //bottom: 胶囊底部距离屏幕顶部的距离
    //height: 胶囊高度
    //left:   胶囊左侧距离屏幕左侧的距离
    //right:  胶囊右侧距离屏幕左侧的距离
    //top:    胶囊顶部距离屏幕顶部的距离
    //width:  胶囊宽度
    const { height: capsuleHeight, top: capsuleTop, right: capsuleRight } = wx.getMenuButtonBoundingClientRect();
    // 左、上边内边距
    const paddingLeft = screenWidth - capsuleRight;
    const paddingTop = statusBarHeight;
    // 导航栏高度 = 胶囊上下pending + 胶囊高度 + 状态栏高度
    const navigationHei = (capsuleTop - statusBarHeight) * 2 + capsuleHeight + statusBarHeight;
    this.setData({
      navigationTop: 0,
      navigationHei,
      paddingLeft,
      paddingTop
    })
  },
  methods: {
    back: function () {
      wx.navigateBack({
        delta: 1,
      })
    }
  }
})
{
  "usingComponents": {
    "hx-header": "../../components/hx-header/index"
  },
  "navigationStyle": "custom"
}
{
  ...
  "window": {
    ...
    "navigationStyle": "custom"
  },
  "usingComponents": {
    "hx-header": "components/hx-header/index"
  },
  ...
}

🔥 自定义 tabbar

  1. app.json 填写 tabBar ,设置 custom: truelist
    1. pagePath、text、iconPath、selectedIconPath(图片得是本地的)
    2. 配置 usingComponents: { "custom-tab-bar": "custom-tab-bar/index" }
    3. click 事件获取 e.currentTarget.dataset
      1. index 是索引
      2. path 是路径
      3. wx.switchTab({ url: path }) 跳转
  2. 项目根目录下新建 custom-tab-bar 组件,index.json 下设置 component: true
<cover-view class="tab-bar">
  <cover-view class="tab-bar-border"></cover-view>
  <cover-view wx:for="{{list}}" wx:key="index" class="tab-bar-item" data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab">
    <cover-image src="{{selected === index ? item.selectedIconPath : item.iconPath}}"></cover-image>
    <cover-view style="color: {{selected === index ? selectedColor : color}}">{{item.text}}</cover-view>
  </cover-view>
</cover-view>
Component({
  data: {
    selected: 0,
    color: "#7A7E83",
    selectedColor: "#3cc51f",
    list: [{
      pagePath: "/pages/index/index",
      text: "首页",
      iconPath: "/images/Home.png",
      selectedIconPath: "/images/Home.png"
    }, {
      pagePath: "/pages/mall/index",
      text: "商城",
      iconPath: "/images/Mall.png",
      selectedIconPath: "/images/Mall.png"
    },
    {
      pagePath: "/pages/cart/index",
      iconPath: "/images/Cart.png",
      text: "购物车",
      selectedIconPath: "/images/Cart.png"
    },{
      pagePath: "/pages/me/index",
      text: "个人中心",
      iconPath: "/images/My.png",
      selectedIconPath: "/images/My.png"
    }]
  },
  attached() {
  },
  methods: {
    switchTab(e) {
      const data = e.currentTarget.dataset
      console.log(data);
      const url = data.path
      wx.switchTab({url})
      this.setData({
        selected: data.index
      })
    }
  }
})
{
  ...
  "usingComponents": {
    "hx-header": "components/hx-header/index",
    "custom-tab-bar": "custom-tab-bar/index"
  },
  "tabBar": {
    "custom": true,
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "./images/Home.png",
        "selectedIconPath": "./images/Home.png"
      },
      {
        "pagePath": "pages/mall/index",
        "text": "商城",
        "iconPath": "./images/Mall.png",
        "selectedIconPath": "./images/Mall.png"
      },
      {
        "pagePath": "pages/cart/index",
        "text": "购物车",
        "iconPath": "./images/Cart.png",
        "selectedIconPath": "./images/Cart.png"
      },
      {
        "pagePath": "pages/me/index",
        "text": "个人中心",
        "iconPath": "./images/My.png",
        "selectedIconPath": "./images/My.png"
      }
    ]
  },
  ...
}

来源:

生命周期函数

显示

小程序 wx:if 和 hidden 的区别

数据传递

小程序页面间有哪些传递数据的方法?

使用全局变量实现数据传递 globalData

在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面

// app.js
App({
     // 全局变量
  globalData: {
    userInfo: null
  }
})

使用的时候,直接使用 getApp() 拿到存储的信息