目录
一. utils/router-history/index.ts 封装记录路由的具体方法
二. store/index.ts 用 vuex 实现路由存储
2.在 main.ts 中,引入 vuex 相关内容 store/index.ts
3.在 home.vue 中,引入 vuex 相关内容,编写路由切换规则
4.在 vue页面 中,引入 vuex 相关内容,编写路由切换规则
封装原因
点击左右侧第一层页面,进入了更深层的页面;
这时,如果点击了地图点位,会有弹框出现在右侧页面;
如果点击关闭弹框,应该出现之前的更深层的页面,而不是第一层页面;
因此需要 记忆路由
一. utils/router-history/index.ts 封装记录路由的具体方法
1.定义 路由接口、路由数组、记录路由类
路由接口:路由名称必填,路由参数选填(键值对、null、undefined)
- export interface RouterData {
name: string;
params?: {
[key: string]: any
} | null | undefined;
}
路由数组类型:export type RouterStackData = RouterData[];
记录路由类:export class RouterHistory {}
2.定义记录路由类
私有路由栈:private routerStack: RouterStackData = [];
记录路由类的构造函数:可以传入参数,或者默认为空数组
- constructor(parameters?: RouterStackData) {
this.routerStack = parameters || [];
}
2.1 判断两个对象是否相等
@memberof:属于哪个类
判断方式:对比两个路由对象的 JSON格式的 名字
注意:要定义成 私有静态 的方法
/**
* @description 判断两个对象是否相等
* @private
* @param routerA 对象甲
* @param routerB 对象乙
* @returns {boolean} 等吗
* @memberof RouterHistory
*/
private static isEqual(routerA: RouterData, routerB: RouterData): boolean {
return JSON.stringify(routerA.name) === JSON.stringify(routerB.name);
}
2.2 判断路由是否可以进栈
如果当前路由栈为空,就可以进栈
调用当前类 RouterHistory 中,前面定义的方法 isEqual()
判断即将进栈的路由,是否等于路由栈中的最后一个路由
二者不等即可防止重复进栈
/**
* @description 判断路由是否可以进栈
* @private
* @param route 路由
* @returns {boolean} 可以吗
* @memberof RouterHistory
*/
private canPush(route: RouterData): boolean {
if (this.routerStack.length === 0) {
return true;
}
// 防止路由重复入栈
return !RouterHistory.isEqual(this.routerStack[this.routerStack.length - 1], route);
}
2.3 查询路由栈是否存在当前路由
假设当前路由不存在于路由栈,也就是序列号为 index = -1
遍历路由栈,判断其中的每一项,是否与传入的当前路由一致
一致的话,返回当前路由 在路由栈中的序列号位置【本质上就是数组下标】
不一致的话,说明路由栈中没有当前路由对象,因此返回不存在的序列号 index = -1
/**
* @description 查询路由栈是否存在当前路由
* @private
* @param route 当前路由
* @returns {*} 若存在返回序列号,不存在-1
* @memberof RouterHistory
*/
private getSameIndex(route: RouterData) {
let index = -1;
// eslint-disable-next-line no-return-assign
this.routerStack.some((item, i) =>
(RouterHistory.isEqual(item, route) ? index = i : false));
return index;
}
2.4 路由进栈
如果传入的对象不是路由,或者传入的路由对象不满足进栈条件(和路由栈中最后一个元素相等)
返回 false,禁止路由进栈
获取当前路由栈的元素个数(长度)
当前路由栈不止一个元素时,获取当前传入路由在路由栈中的位置(可能有位置,可能没位置)
- 如果当前路由不存在于路由栈,就直接向路由栈中添加当前路由
- 如果当前路由存在于路由栈,就执行下面定义的删除当前路由的方法
当前路由栈只有一个元素时,直接执行进栈操作(给路由数组 push 路由对象)
/**
* @description 路由进栈
* @param route 路由
* @returns {boolean} 成功与否
* @memberof RouterHistory
*/
public push(route: RouterData): boolean {
if (!route || !this.canPush(route)) {
return false;
}
const oldLength = this.routerStack.length;
// 防止路由循环入栈
if (oldLength > 1) {
const index = this.getSameIndex(route);
if (index === -1) {
return this.routerStack.push(route) === oldLength + 1;
}
return this.pop(index);
}
// 正常入栈
return this.routerStack.push(route) === oldLength + 1;
}
2.5 路由出栈
该方法接受一个参数 index,表示某路由对象在路由栈中的位置
index 可以是存在的,也可以是不存在的,即某路由对象不一定存在于路由栈
如果 index 不存在,也就是该路由对象不存在于路由栈,那就意味着不可以出栈
如果 index 存在,那就删除那个路由所在位置之后的全部路由
/**
* @description 路由出栈
* @param [index=undefined] 指定序号:会弹出指定序号之后所有的路由
* @returns {boolean} 成功与否
* @memberof RouterHistory
*/
public pop(index: number | undefined = undefined): boolean {
if (index === undefined) {
return this.routerStack.pop() !== undefined;
}
return this.routerStack.splice(index + 1, this.routerStack.length).length > 0;
}
2.6 获取全部路由记录
就是返回当前路由栈数组
/**
* @description 获取全部路由记录
* @returns {RouterStackData} 路由记录
* @memberof RouterHistory
*/
public getAll(): RouterStackData {
return this.routerStack;
}
2.7 获取当前(父级)路由、获取当前(父级)路由序号
获取当前路由:如果路由栈存在内容,就返回最后一个元素,不存在,就返回空,即不存在当前路由
获取当前路由序号:当前路由栈长度 - 1
获取父级内容,就相当于获取路由栈的 倒数第二个元素
/**
* @description 获取当前路由
* @returns {RouterData} 当前路由
* @memberof RouterHistory
*/
public getCurrent(): RouterData | null {
return this.routerStack.length > 0 ? this.routerStack[this.routerStack.length - 1] : null;
}
/**
* @description 获取当前路由序号
* @returns {number} 序号
* @memberof RouterHistory
*/
public getCurrentIndex(): number {
return this.routerStack.length - 1;
}
二. store/index.ts 用 vuex 实现路由存储
1.初始化 stroe/index.ts
引入相关内容:
- import { createStore } from 'vuex';
- import { RouterHistory } from '../utils/router-history/index';
设置路由历史数据:export const SET_ROUTERHISTORY = 'SET_ROUTERHISTORY';
编写通用状态:export default createStore({ });
2.通用状态具体编写
定义左右侧记录路由(记录路由类的实例化),传入默认左右侧页面:
- state: {
leftRouterHistory: new RouterHistory([{ name: 'status' }]), // 左侧路由
rightRouterHistory: new RouterHistory([{ name: 'logbuch' }]), // 右侧路由
},
存储路由,形成路由历史:
- mutations: {
[SET_ROUTERHISTORY](state, options) {
state.leftRouterHistory = options.left;
state.rightRouterHistory = options.right;
},
}, - actions: {
setRouterHistory({ commit }, params) {
commit(SET_ROUTERHISTORY, params);
},
},
三. 使用 vuex 记录路由
1.home.vue 的基本构成
左侧面板:
-
:is="currentLeftComponent"
:data="currentLeftParams"
@toggleComponent="toggleComponent"
@toggleDialogComponent="toggleDialogComponent">
- :is="currentRightComponent"
:data="currentRightParams"
@toggleComponent="toggleComponent"
@toggleDialogComponent="toggleDialogComponent">
中间弹框:
- :is="currentDialogComponent" @close-dialog="closeDialogComponent">
引入各种 .vue组件:
- const status = defineAsyncComponent(() => import('./status-analysis/status.vue')); // 实况
const logbuch = defineAsyncComponent(() => import('./status-analysis/logbuch.vue')); // 战况
定义各个面板默认绑定的组件:
- const state = reactive({
currentLeftComponent: xxx, // 左侧组件名称 —— 此时是写成固定的绑定组件
currentLeftParams: {}, // 左侧组件参数
currentRightComponent: xxx, // 右侧组件名称 —— 此时是写成固定的绑定组件
currentRightParams: {}, // 右侧组件参数
currentDialogComponent: '', // 中间弹框组件名称
});
实现切换组件:
- 子页面触发 home.vue 中的 toggleComponent 方法,实现组件切换
- 子页面传入的 data 对象中,根据 data 内容,更改各个面板绑定的组件
- 每次切换组件时,都应自动关闭中间弹框,因此每次都要清空弹框组件
- const toggleComponent = (data: any) => {
state.currentDialogComponent = ''; // 弹框组件清空
// 左侧
if (data.left) {
state.currentLeftComponent = data.left.name;
state.currentLeftParams = data.left.params;
}
// 右侧
if (data.right) {
state.currentRightComponent = data.right.name;
state.currentRightParams = data.right.params;
}
};
中间的弹框,本质上是依附于左右面板的子组件,因此想要打开弹框,需要让左右面板监听打开弹窗的事件
- const toggleDialogComponent = (data: any) => {
state.currentDialogComponent = data.name;
}; - 关闭中间的弹框,就是弹框本身监听事件了
- const closeDialogComponent = () => {
state.currentDialogComponent = '';
};
2.在 main.ts 中,引入 vuex 相关内容 store/index.ts
必须引入,不然会导致 home.vue 中无法使用 vuex
- import store from './store';
instance.use(store);
3.在 home.vue 中,引入 vuex 相关内容,编写路由切换规则
import { useStore } from 'vuex';
setup() 中,实例化 store:const store = useStore(); // 状态管理
setup() 中,从 store 里读取左右记忆路由对象
- const { leftRouterHistory } = store.state;
const { rightRouterHistory } = store.state;
通过 vuex,更改各个面板默认绑定的组件
- const state = reactive({
currentLeftComponent: leftRouterHistory.getCurrent().name, // 左侧组件名称
currentLeftParams: {}, // 左侧组件参数
currentRightComponent: rightRouterHistory.getCurrent().name, // 右侧组件名称
currentRightParams: {}, // 右侧组件参数
currentDialogComponent: '', // 中间弹框组件名称
});
通过 vuex,更改切换组件:
除了静态的切换组件需要更改,还应该给 记录路由 传递更改后的路由
- const toggleComponent = (data: any) => {
state.currentDialogComponent = ''; // 弹框组件清空
// 左侧
if (data.left) {
state.currentLeftComponent = data.left.name;
state.currentLeftParams = data.left.params;
leftRouterHistory.push({ name: data.left.name }); // 记录路由
}
// 右侧
if (data.right) {
state.currentRightComponent = data.right.name;
state.currentRightParams = data.right.params;
rightRouterHistory.push({ name: data.right.name }); // 记录路由
}
// 更新store里的数据
store.dispatch('setRouterHistory', {
left: leftRouterHistory,
right: rightRouterHistory,
});
};
home.vue 卸载之后,应该还原 store 中的记录路由
- onUnmounted(() => {
leftRouterHistory.push({ name: 'status' }); // 记录路由
rightRouterHistory.push({ name: 'logbuch' }); // 记录路由
store.dispatch('setRouterHistory', {
left: leftRouterHistory,
right: rightRouterHistory,
});
});
4.在 vue页面 中,引入 vuex 相关内容,编写路由切换规则
页面元素定义一个返回方法:
- 引入 vuex:import { useStore } from 'vuex';
- setup() 中,实例化 vuex:const store = useStore();
实现跳转:
- const goBack = () => {
const { rightRouterHistory } = store.state; // 获取当前面板记录路由
rightRouterHistory.pop(); // 删除当前页面
// 跳转
const data = {
right: {
name: rightRouterHistory.getCurrent().name, // 删除当前页面后,此时的 getCurrent() 就相当于获取当前页面的上一级页面了
},
};
emit('toggleComponent', data);
};