vue开发h5,仿小程序页面栈的实现

326 阅读1分钟

关键词:keep-alive、include、路由守卫、栈

一、问题背景

  1. 用户行为习惯不同 ==> 移动端得用路由跳转来控制页面展示

    在pc端用户基本不会点击浏览器自带的返回按钮,而移动端用手机自带返回更方便。这就导致了在开发pc页面时用弹窗或者抽屉的形式展示某些内容(详情、表单等)就足够,而移动端则更多使用路由跳转。

  2. 路由跳转需要考虑页面缓存机制

    这里参考微信小程序的页面栈的方式: 路由方式 | 页面栈表现 | | ------ | ------------------- | | 初始化 | 新页面入栈 | | 打开新页面 | 新页面入栈 | | 页面重定向 | 当前页面出栈,新页面入栈 | | 页面返回 | 页面不断出栈,直到目标返回页 | | Tab 切换 | 页面全部出栈,只留下新的 Tab 页面 | | 重加载 | 页面全部出栈,只留下新的页面 |

二、设计

在pinia里定义一个页面栈,在全局路由守卫中维护栈,在router-view中将keep-alive的include的属性值设为本栈。

include属性会根据组件的name选项进行匹配,而路由守卫只能获取到当前路由的配置。所以这里需要路由的name和组件的name手动保持一致

在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。

3.2.34以下版本用unplugin-vue-setup-extend,更方便的设置组件name:

<!-- /src/views/login.vue -->
<script setup name="login"></script>
{
    path: '/login',
    name: 'login',
    component: () => import('@/views/login.vue')
}

三、代码实现

// src/store/keep.js
import { defineStore } from 'pinia';

export const useKeepStore = defineStore('keep', {
  state: () => ({
    include: [],
  }),
  actions: {
    add(name) {
      if (!name) return;
      let index = this.include.indexOf(name);
      if (index + 1) {
        this.include.splice(index + 1);
      } else {
        this.include.push(name);
      }
    },
  },
});

// src/router/index.js
import { useUserStore } from '@/store/user';
import { useKeepStore } from '@/store/keep';
router.beforeEach((to, from, next) => {
  const { token, setToken } = useUserStore();
  const { add } = useKeepStore()
  if (
    to.name !== 'login' &&
    to.matched.some((route) => route.meta.requireAuth)
  ) {
    if (to.query.token) {
      setToken(to.query.token);
    }
    if (token) {
      add(to.name)
      next();
    } else {
      console.log('当前尚未登录,请先登录');
      add('login')
      next({ name: 'login' });
    }
  } else {
    add(to.name)
    next();
  }
});
// ...
<!-- src/views/App.vue -->
<template>
    <router-view class="router_view" v-slot="{ Component }">
      <keep-alive :include="keepStore.include">
        <component :is="Component" :key="$route.path" />
      </keep-alive>
    </router-view>
</template>
<script setup>
import { useKeepStore } from "./store/keep";
const keepStore = useKeepStore()
</script>