feat(router): 重构路由模块并优化页面加载逻辑
- 引入 ScriptManager、TimerManager 和 EventManager 统一管理资源 - 路由配置支持默认重定向路径 '/' - 移除旧版 scriptLoader 与 routeUtils,使用新的模块化加载机制 - 增加路由生命周期控制,自动清理定时器和事件监听器 - 改进点击代理逻辑,增强按钮激活状态切换 - 页面切换时卸载旧脚本及资源,防止内存泄漏 - 修复初始化日志冗余问题,提升代码可维护性
This commit is contained in:
+64
-108
@@ -1,141 +1,111 @@
|
||||
import { getParamsUrl, closure, genKey } from '../utils/routeUtils.js';
|
||||
import { addClass, removeClass } from '../utils/domUtils.js';
|
||||
import { loadScript } from '../utils/scriptLoader.js';
|
||||
import { TimerManager } from '../utils/timerManager.js';
|
||||
import { EventManager } from '../utils/eventManager.js';
|
||||
import { ScriptManager } from '../utils/scriptManager.js';
|
||||
|
||||
class Router {
|
||||
constructor() {
|
||||
this.routes = {};
|
||||
this.beforeFun = null;
|
||||
this.afterFun = null;
|
||||
this.routerViewId = 'router-view';
|
||||
this.routerViewId = 'app';
|
||||
this.redirectRoute = null;
|
||||
this.stackPages = true;
|
||||
this.routerMap = [];
|
||||
this.historyFlag = '';
|
||||
this.history = [];
|
||||
this.scriptManager = window.scriptManager;
|
||||
this.eventManager = window.eventManager;
|
||||
this.timerManager = window.timerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由
|
||||
*/
|
||||
init(config) {
|
||||
// 配置路由
|
||||
this.routerMap = config?.routes || this.routerMap;
|
||||
this.routerViewId = config?.routerViewId || this.routerViewId;
|
||||
this.stackPages = config?.stackPages ?? this.stackPages;
|
||||
|
||||
// 映射路由表
|
||||
this.map();
|
||||
|
||||
window.linkTo = (path) => {
|
||||
if (path.includes('?')) {
|
||||
location.hash = `${path}&key=${genKey()}`;
|
||||
} else {
|
||||
location.hash = `${path}?key=${genKey()}`;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('load', (e) => {
|
||||
this.historyChange(e);
|
||||
this.updateActiveButton(e);
|
||||
});
|
||||
window.addEventListener('hashchange', (e) => {
|
||||
this.historyChange(e);
|
||||
this.updateActiveButton(e);
|
||||
});
|
||||
// 监听路由变化
|
||||
window.addEventListener('hashchange', () => this.urlChange());
|
||||
window.addEventListener('load', () => this.urlChange());
|
||||
window.lintTo = (path) => this.naviage(path);
|
||||
}
|
||||
|
||||
async loadPage(route) {
|
||||
console.log('加载页面:', route);
|
||||
const html = await fetch(route.html).then((r) => r.text());
|
||||
document.getElementById(this.routerViewId).innerHTML = html;
|
||||
map() {
|
||||
if (!this.routerMap.length) {
|
||||
console.error('请配置路由');
|
||||
return;
|
||||
}
|
||||
|
||||
const initName = route.name + 'Init';
|
||||
await loadScript(route.script, initName);
|
||||
|
||||
if (window[initName] && route.script) {
|
||||
await window[initName]();
|
||||
for (const r of this.routerMap) {
|
||||
if (r.name == 'redirect') this.redirectRoute = r.path;
|
||||
this.routes[r.path] = r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 HTML 文件
|
||||
* 导航
|
||||
*/
|
||||
async loadHTML(htmlPath) {
|
||||
try {
|
||||
const resp = await fetch(htmlPath);
|
||||
if (!resp.ok) throw new Error(`HTML 加载失败: ${resp.status}`);
|
||||
return await resp.text();
|
||||
} catch (err) {
|
||||
return `<h2>页面加载失败:${err.message}</h2>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理路由历史变化
|
||||
*/
|
||||
historyChange(event) {
|
||||
const { path, query } = getParamsUrl();
|
||||
console.log('路由变化page{}, query{}', path, query);
|
||||
this.urlChange();
|
||||
naviage(path) {
|
||||
window.location.hash = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由解析与渲染
|
||||
*/
|
||||
urlChange() {
|
||||
const currentHash = getParamsUrl();
|
||||
const { path, query } = currentHash;
|
||||
const path = location.hash.replace('#', '') || this.redirectRoute;
|
||||
console.log('当前路由:', path);
|
||||
const route = this.routes[path];
|
||||
|
||||
if (!this.routes[path]) {
|
||||
if (!route) {
|
||||
location.hash = this.redirectRoute;
|
||||
return;
|
||||
}
|
||||
|
||||
const next = () => this.changeView(currentHash);
|
||||
const doChange = async () => {
|
||||
this.timerManager.cleanAll();
|
||||
this.eventManager.removeAll();
|
||||
|
||||
if (this.beforeFun) {
|
||||
this.beforeFun({ to: { path, query }, next });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染页面
|
||||
*/
|
||||
async changeView(currentHash) {
|
||||
const { path } = currentHash;
|
||||
const route = await this.routes[path];
|
||||
const mountEl = document.getElementById(this.routerViewId);
|
||||
|
||||
if (!mountEl) {
|
||||
console.error('挂载点不存在:', this.routerViewId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('路由:', route);
|
||||
this.loadPage(route);
|
||||
|
||||
// 执行 after 钩子
|
||||
if (this.afterFun) this.afterFun(currentHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建路由映射
|
||||
*/
|
||||
map() {
|
||||
for (let r of this.routerMap) {
|
||||
if (r.name === 'redirect') {
|
||||
this.redirectRoute = r.path;
|
||||
} else if (!this.redirectRoute) {
|
||||
this.redirectRoute = this.routerMap[0].path;
|
||||
const mount = document.getElementById(this.routerViewId);
|
||||
if (!mount) {
|
||||
console.error('挂载点不存在:', this.routerViewId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.routes[r.path] = {
|
||||
name: r.name,
|
||||
html: r.html,
|
||||
script: r.script,
|
||||
};
|
||||
if (!this.stackPages) mount.innerHTML = '';
|
||||
|
||||
if (route.html) {
|
||||
try {
|
||||
const html = await fetch(route.html).then((r) => r.text());
|
||||
mount.innerHTML = html;
|
||||
} catch (e) {
|
||||
mount.innerHTML = `<h2>页面加载失败:${e.message}</h2>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (route.script) {
|
||||
await this.scriptManager.loadScript({
|
||||
path: route.script,
|
||||
name: route.name,
|
||||
isModule: route.isModule,
|
||||
deps: route.deps,
|
||||
});
|
||||
}
|
||||
|
||||
await this.scriptManager.runInit(route.name);
|
||||
|
||||
this.currentRoute = route;
|
||||
if (this.afterFun) this.afterFun(route);
|
||||
};
|
||||
|
||||
if (this.beforeFun) {
|
||||
this.beforeFun({ to: route, next: doChange });
|
||||
} else {
|
||||
doChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,20 +130,6 @@ class Router {
|
||||
console.trace('afterEach 必须是函数');
|
||||
}
|
||||
}
|
||||
|
||||
updateActiveButton(event) {
|
||||
const currentRoute = location.hash.replace('#', '') || '/home';
|
||||
|
||||
document.querySelectorAll('[data-route]').forEach((btn) => {
|
||||
const route = btn.getAttribute('data-route');
|
||||
if (route === currentRoute) {
|
||||
console.log('当前路由:', route);
|
||||
addClass(btn, 'active');
|
||||
} else {
|
||||
removeClass(btn, 'active');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { Router };
|
||||
|
||||
Reference in New Issue
Block a user