feat(sidepanel): 重构路由系统并优化页面加载逻辑
- 移除冗余的开发预览页面 `index.html` - 路由配置改为声明式结构,支持自动加载 HTML 和脚本 - 新增 `data-route` 属性用于导航按钮绑定路由路径 - 实现基于事件委托的路由跳转机制 - 重写 `Router.js` 核心逻辑,支持异步加载页面模块 - 改进 `domUtils.js` 中 `addClass` 和 `removeClass` 方法兼容性与健壮性 - 新增 `scriptLoader.js` 工具模块用于动态加载和清理页面脚本 - 时间戳页面初始化方式调整为 IIFE 并挂载至全局作用域
This commit is contained in:
-32
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Testing Tool - Development</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 600px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Testing Tool - Development Preview</h1>
|
|
||||||
<p>这是一个用于开发预览的页面,下方是sidepanel的实时预览。</p>
|
|
||||||
<iframe src="/sidepanel/index.html" title="Sidepanel Preview"></iframe>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
<body>
|
<body>
|
||||||
<!-- 导航栏 -->
|
<!-- 导航栏 -->
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<button class="nav-button active" id="timestamp-jump">时间戳转换</button>
|
<button class="nav-button active" id="timestamp-jump" data-route="/home">时间戳转换</button>
|
||||||
<button class="nav-button" id="todo-jump">任务清单</button>
|
<button class="nav-button" id="todo-jump" data-route="/todo">任务清单</button>
|
||||||
<button id="close-panel-btn" class="close-button">X</button>
|
<button id="close-panel-btn" class="close-button">X</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|||||||
+16
-37
@@ -1,48 +1,21 @@
|
|||||||
console.log('Before creating router');
|
console.log('Before creating router');
|
||||||
import { Router } from '../modules/Router.js';
|
import { Router } from '../modules/Router.js';
|
||||||
|
|
||||||
import * as PageModules from '../../dist/timestamp.js';
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
routerViewId: 'app', // 路由切换的挂载点 id
|
routerViewId: 'app',
|
||||||
stackPages: false, // 多级页面缓存
|
stackPages: false,
|
||||||
animationName: 'fade', // 切换页面时的动画
|
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/home',
|
path: '/home',
|
||||||
name: 'home',
|
name: 'timestamp',
|
||||||
callback: async () => {
|
html: 'pages/timestamp.html',
|
||||||
// 1. 加载 HTML
|
script: '../dist/timestamp.js',
|
||||||
const html = await fetch('pages/timestamp.html').then((r) => r.text());
|
|
||||||
document.getElementById('home').innerHTML = html;
|
|
||||||
|
|
||||||
// 2. 动态加载对应 JS
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = '/dist/timestamp.js'; // IIFE 打包后的文件
|
|
||||||
script.onload = () => {
|
|
||||||
// 3. 执行 IIFE 暴露的全局对象方法
|
|
||||||
TimestampPage.init(); // 假设 timestamp.js 输出 name: 'TimestampPage'
|
|
||||||
};
|
|
||||||
document.body.appendChild(script);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/todo',
|
path: '/todo',
|
||||||
name: 'todo',
|
name: 'todo',
|
||||||
callback: async () => {
|
html: 'pages/todolist.html',
|
||||||
// 1. 加载 HTML
|
script: '',
|
||||||
const html = await fetch('pages/todolist.html').then((r) => r.text());
|
|
||||||
document.getElementById('todo').innerHTML = html;
|
|
||||||
|
|
||||||
// 2. 动态加载对应 JS
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = '/dist/timestamp.js'; // IIFE 打包后的文件
|
|
||||||
script.onload = () => {
|
|
||||||
// 3. 执行 IIFE 暴露的全局对象方法
|
|
||||||
TimestampPage.init(); // 假设 timestamp.js 输出 name: 'TimestampPage'
|
|
||||||
};
|
|
||||||
document.body.appendChild(script);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -52,7 +25,13 @@ console.log('Router created:', router);
|
|||||||
router.init(config);
|
router.init(config);
|
||||||
console.log('Router initialized');
|
console.log('Router initialized');
|
||||||
|
|
||||||
document.getElementById('todo-jump').addEventListener('click', () => {
|
document.addEventListener('click', (event) => {
|
||||||
console.log('Jumping to todo page');
|
const btn = event.target.closest('[data-route]');
|
||||||
window.linkTo('#/todo');
|
if (!btn) return;
|
||||||
|
|
||||||
|
const path = btn.getAttribute('data-route');
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
// 触发 hash 路由跳转
|
||||||
|
window.location.hash = path;
|
||||||
});
|
});
|
||||||
|
|||||||
+25
-23
@@ -12,27 +12,29 @@ import {
|
|||||||
} from '../modules/eventHandlers.js';
|
} from '../modules/eventHandlers.js';
|
||||||
import { addEventListenerById } from '../utils/domUtils.js';
|
import { addEventListenerById } from '../utils/domUtils.js';
|
||||||
|
|
||||||
export function init() {
|
(function (global) {
|
||||||
console.log('timestamp init')
|
global.timestampInit = async function () {
|
||||||
// 初始化时间戳显示
|
console.log('timestamp init');
|
||||||
initTimestampDisplay();
|
// 初始化时间戳显示
|
||||||
|
initTimestampDisplay();
|
||||||
|
|
||||||
// 绑定事件处理器
|
// 绑定事件处理器
|
||||||
// 绑定时间戳相关按钮事件
|
// 绑定时间戳相关按钮事件
|
||||||
addEventListenerById('toggle-unit-btn', 'click', handleToggleUnit);
|
addEventListenerById('toggle-unit-btn', 'click', handleToggleUnit);
|
||||||
// 绑定计时器相关按钮事件
|
// 绑定计时器相关按钮事件
|
||||||
addEventListenerById('stop-timer-btn', 'click', handleStopTimer);
|
addEventListenerById('stop-timer-btn', 'click', handleStopTimer);
|
||||||
addEventListenerById('start-timer-btn', 'click', handleStartTimer);
|
addEventListenerById('start-timer-btn', 'click', handleStartTimer);
|
||||||
// 绑定时间戳转换按钮事件
|
// 绑定时间戳转换按钮事件
|
||||||
addEventListenerById('convert-timestamp-to-date-btn', 'click', handleConvertTimestamp);
|
addEventListenerById('convert-timestamp-to-date-btn', 'click', handleConvertTimestamp);
|
||||||
// 绑定日期转换按钮事件
|
// 绑定日期转换按钮事件
|
||||||
addEventListenerById('convert-date-to-timestamp-btn', 'click', handleConvertDateToTimestamp);
|
addEventListenerById('convert-date-to-timestamp-btn', 'click', handleConvertDateToTimestamp);
|
||||||
// 绑定复制时间戳按钮事件
|
// 绑定复制时间戳按钮事件
|
||||||
addEventListenerById('copy-timestamp-btn', 'click', handleCopyTimestamp);
|
addEventListenerById('copy-timestamp-btn', 'click', handleCopyTimestamp);
|
||||||
// 绑定关闭面板按钮事件
|
// 绑定关闭面板按钮事件
|
||||||
addEventListenerById('close-panel-btn', 'click', handleClosePanel);
|
addEventListenerById('close-panel-btn', 'click', handleClosePanel);
|
||||||
// 绑定切换时区事件
|
// 绑定切换时区事件
|
||||||
addEventListenerById('timezone-result', 'change', handleTimezoneResult);
|
addEventListenerById('timezone-result', 'change', handleTimezoneResult);
|
||||||
// 绑定输入时区事件
|
// 绑定输入时区事件
|
||||||
addEventListenerById('timezone-input', 'change', handleTimezoneInput);
|
addEventListenerById('timezone-input', 'change', handleTimezoneInput);
|
||||||
}
|
};
|
||||||
|
})(window);
|
||||||
|
|||||||
+122
-227
@@ -1,284 +1,179 @@
|
|||||||
import { getParamsUrl, closure, genKey } from '../utils/routeUtils.js';
|
import { getParamsUrl, closure, genKey } from '../utils/routeUtils.js';
|
||||||
|
import { addClass, removeClass } from '../utils/domUtils.js';
|
||||||
|
import { loadScript } from '../utils/scriptLoader.js';
|
||||||
|
|
||||||
import { addClass, removeClass, hasClass } from '../utils/domUtils.js';
|
|
||||||
// var config = {
|
|
||||||
// routerViewId: '#routerView', // 路由切换的挂载点 id
|
|
||||||
// stackPages: true, // 多级页面缓存
|
|
||||||
// animationName: "slide", // 多级页面缓存
|
|
||||||
// routes: [
|
|
||||||
// // {
|
|
||||||
// // path: "/home",
|
|
||||||
// // name: "home",
|
|
||||||
// // callback: function(transition) {
|
|
||||||
// // home()
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
class Router {
|
class Router {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 路由表
|
|
||||||
this.routes = {};
|
this.routes = {};
|
||||||
// 路由跳转前执行
|
|
||||||
this.beforeFun = null;
|
this.beforeFun = null;
|
||||||
// 路由跳转后执行
|
|
||||||
this.afterFun = null;
|
this.afterFun = null;
|
||||||
// 路由挂载点
|
|
||||||
this.routerViewId = 'router-view';
|
this.routerViewId = 'router-view';
|
||||||
// 路由重定向hash
|
|
||||||
this.redirectRoute = null;
|
this.redirectRoute = null;
|
||||||
// 多级页面缓存
|
|
||||||
this.stackPages = true;
|
this.stackPages = true;
|
||||||
// 路由遍历
|
|
||||||
this.routerMap = [];
|
this.routerMap = [];
|
||||||
// 路由状态
|
|
||||||
this.historyFlag = '';
|
this.historyFlag = '';
|
||||||
// 路由历史
|
|
||||||
this.history = [];
|
this.history = [];
|
||||||
// 动画名称
|
|
||||||
this.animationName = 'fade';
|
|
||||||
}
|
|
||||||
|
|
||||||
init(config) {
|
|
||||||
this.routerMap = config ? config.routes : this.routerMap;
|
|
||||||
this.routerViewId = config ? config.routerViewId : this.routerViewId;
|
|
||||||
this.stackPages = config ? config.stackPages : this.stackPages;
|
|
||||||
|
|
||||||
const name = document.querySelector('#app').getAttribute('data-animationName');
|
|
||||||
if (name) {
|
|
||||||
this.animationName = name;
|
|
||||||
}
|
|
||||||
this.animationName = config ? config.animationName : this.animationName;
|
|
||||||
|
|
||||||
this.map();
|
|
||||||
|
|
||||||
if (!this.routerMap.length) {
|
|
||||||
// 找到routerViewId 节点下的所有page 节点
|
|
||||||
const pages = document.querySelectorAll(this.routerViewId + ' .page');
|
|
||||||
for (let i = 0; i < pages.length; i++) {
|
|
||||||
// 遍历所有的page节点
|
|
||||||
let page = pages[i];
|
|
||||||
let hash = page.getAttribute('hash');
|
|
||||||
let name = hash.substring(1);
|
|
||||||
this.routerMap.push({
|
|
||||||
name: name,
|
|
||||||
path: hash,
|
|
||||||
callback: closure(name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.linkTo = (path) => {
|
|
||||||
console.log('path', path);
|
|
||||||
if (path.indexOf('?') !== -1) {
|
|
||||||
window.location.hash = path + '&key=' + genKey();
|
|
||||||
} else {
|
|
||||||
window.location.hash = path + '?key=' + genKey();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('load', (event) => {
|
|
||||||
this.historyChange(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('hashchange', (event) => {
|
|
||||||
this.historyChange(event);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由历史纪录变化
|
* 初始化路由
|
||||||
* @param {*} event
|
*/
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPage(route) {
|
||||||
|
console.log('加载页面:', route);
|
||||||
|
const html = await fetch(route.html).then((r) => r.text());
|
||||||
|
document.getElementById(this.routerViewId).innerHTML = html;
|
||||||
|
|
||||||
|
const initName = route.name + 'Init';
|
||||||
|
await loadScript(route.script, initName);
|
||||||
|
|
||||||
|
if (window[initName] && route.script) {
|
||||||
|
await window[initName]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载 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) {
|
historyChange(event) {
|
||||||
// {path, query, params}
|
const { path, query } = getParamsUrl();
|
||||||
const currentHash = getParamsUrl();
|
console.log('路由变化page{}, query{}', path, query);
|
||||||
// router-#app-history
|
|
||||||
const nameString = 'router-' + this.routerViewId + '-history';
|
|
||||||
|
|
||||||
this.history = window.sessionStorage[nameString]
|
|
||||||
? JSON.parse(window.sessionStorage[nameString])
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// 返回上一级
|
|
||||||
let back = false;
|
|
||||||
// 刷新页面
|
|
||||||
let refresh = false;
|
|
||||||
// 前进上一级
|
|
||||||
let forward = false;
|
|
||||||
// 获取当前路由的索引
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < this.history.length; i++) {
|
|
||||||
let h = this.history[i];
|
|
||||||
|
|
||||||
// 判断是否是当前路由
|
|
||||||
if (h.hash === currentHash.path && h.key === currentHash.query.key) {
|
|
||||||
// 获取当前路由的索引
|
|
||||||
index = i;
|
|
||||||
// 判断是否是刷新页面
|
|
||||||
if (i === this.history.length - 1) {
|
|
||||||
refresh = true;
|
|
||||||
} else {
|
|
||||||
// 判断是否是返回上一级
|
|
||||||
back = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
// 判断是否是前进
|
|
||||||
forward = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (back) {
|
|
||||||
this.historyFlag = 'back';
|
|
||||||
this.history.length = index + 1;
|
|
||||||
} else if (refresh) {
|
|
||||||
this.historyFlag = 'refresh';
|
|
||||||
} else {
|
|
||||||
this.historyFlag = 'forward';
|
|
||||||
this.history.push({
|
|
||||||
key: currentHash.query.key,
|
|
||||||
hash: currentHash.path,
|
|
||||||
query: currentHash.query,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('historyFlag', this.historyFlag);
|
|
||||||
|
|
||||||
if (!this.stackPages) {
|
|
||||||
this.historyFlag = 'forward';
|
|
||||||
}
|
|
||||||
window.sessionStorage[nameString] = JSON.stringify(this.history);
|
|
||||||
this.urlChange();
|
this.urlChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更改页面
|
* 路由解析与渲染
|
||||||
* @param {*} currentHash
|
|
||||||
*/
|
|
||||||
changeView(currentHash) {
|
|
||||||
const pages = document.querySelectorAll(' .page');
|
|
||||||
const previousPage = document.querySelector('.' + this.routerViewId + ' .page.active');
|
|
||||||
let currentPage = null;
|
|
||||||
let currHash = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < pages.length; i++) {
|
|
||||||
let page = pages[i];
|
|
||||||
let hash = page.getAttribute('hash');
|
|
||||||
page.setAttribute('class', 'page');
|
|
||||||
if (hash === currentHash.path) {
|
|
||||||
currHash = hash;
|
|
||||||
currentPage = page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const enterName = 'enter-' + this.animationName;
|
|
||||||
const leaveName = 'leave-' + this.animationName;
|
|
||||||
|
|
||||||
if (this.historyFlag === 'back') {
|
|
||||||
addClass(currentPage, 'current');
|
|
||||||
if (previousPage) {
|
|
||||||
addClass(previousPage, leaveName);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (previousPage) {
|
|
||||||
removeClass(previousPage, leaveName);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
} else if (this.historyFlag === 'forward' || this.historyFlag === 'refresh') {
|
|
||||||
if (previousPage) {
|
|
||||||
addClass(previousPage, leaveName);
|
|
||||||
}
|
|
||||||
addClass(currentPage, enterName);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (previousPage) {
|
|
||||||
removeClass(previousPage, leaveName);
|
|
||||||
}
|
|
||||||
addClass(currentPage, enterName);
|
|
||||||
removeClass(currentPage, 'current');
|
|
||||||
}, 300);
|
|
||||||
currentPage.scrollTop = 0;
|
|
||||||
this.routes[currHash].callback ? this.routes[currHash].callback() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.afterFun ? this.afterFun(currentHash) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* url改变
|
|
||||||
*/
|
*/
|
||||||
urlChange() {
|
urlChange() {
|
||||||
const currentHash = getParamsUrl();
|
const currentHash = getParamsUrl();
|
||||||
if (this.routes[currentHash.path]) {
|
const { path, query } = currentHash;
|
||||||
if (this.beforeFun) {
|
|
||||||
this.beforeFun({
|
if (!this.routes[path]) {
|
||||||
to: {
|
|
||||||
path: currentHash.path,
|
|
||||||
query: currentHash.query,
|
|
||||||
},
|
|
||||||
next: () => {
|
|
||||||
this.changeView(currentHash);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.changeView(currentHash);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
location.hash = this.redirectRoute;
|
location.hash = this.redirectRoute;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = () => this.changeView(currentHash);
|
||||||
|
|
||||||
|
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() {
|
map() {
|
||||||
for (let i = 0; i < this.routerMap.length; i++) {
|
for (let r of this.routerMap) {
|
||||||
let route = this.routerMap[i];
|
if (r.name === 'redirect') {
|
||||||
if (route.name === 'redirect') {
|
this.redirectRoute = r.path;
|
||||||
this.redirectRoute = route.path;
|
} else if (!this.redirectRoute) {
|
||||||
} else {
|
|
||||||
this.redirectRoute = this.routerMap[0].path;
|
this.redirectRoute = this.routerMap[0].path;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newPath = route.path;
|
this.routes[r.path] = {
|
||||||
let path = newPath.replace(/\s+/g, '');
|
name: r.name,
|
||||||
this.routes[path] = {
|
html: r.html,
|
||||||
callback: route.callback,
|
script: r.script,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换页面前的hook
|
* before 钩子
|
||||||
* @param {*} callback hook执行函数
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
beforeEach(callback) {
|
beforeEach(callback) {
|
||||||
if (Object.prototype.toString.call(callback) === '[object Function]') {
|
if (typeof callback === 'function') {
|
||||||
// 判断callback是否为函数
|
|
||||||
this.beforeFun = callback;
|
this.beforeFun = callback;
|
||||||
} else {
|
} else {
|
||||||
// 抛出错误
|
console.trace('beforeEach 必须是函数');
|
||||||
console.trace('beforeEach callback must be a function');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换页面后的hook
|
* after 钩子
|
||||||
* @param {*} callback hook执行函数
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
afterEach(callback) {
|
afterEach(callback) {
|
||||||
if (Object.prototype.toString.call(callback) === '[object Function]') {
|
if (typeof callback === 'function') {
|
||||||
// 判断callback是否为函数
|
|
||||||
this.afterFun = callback;
|
this.afterFun = callback;
|
||||||
} else {
|
} else {
|
||||||
// 抛出错误
|
console.trace('afterEach 必须是函数');
|
||||||
console.trace('afterEach callback must be a function');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 };
|
export { Router };
|
||||||
|
|||||||
+30
-12
@@ -98,9 +98,18 @@ export function hasClass(elem, cls) {
|
|||||||
* @param {*} cls
|
* @param {*} cls
|
||||||
*/
|
*/
|
||||||
export function addClass(elem, cls) {
|
export function addClass(elem, cls) {
|
||||||
if (!hasClass(elem, cls)) {
|
if (!elem || !cls) return;
|
||||||
elem.className = elem.className === '' ? cls : elem.className + ' ' + cls;
|
|
||||||
}
|
// 统一空白字符(避免 \n \t 等导致匹配问题)
|
||||||
|
let current = elem.className.replace(/[\t\r\n]/g, ' ').trim();
|
||||||
|
|
||||||
|
// 已存在则忽略
|
||||||
|
const classes = current.split(/\s+/);
|
||||||
|
if (classes.includes(cls)) return;
|
||||||
|
|
||||||
|
// 添加并规整
|
||||||
|
classes.push(cls);
|
||||||
|
elem.className = classes.join(' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,13 +118,22 @@ export function addClass(elem, cls) {
|
|||||||
* @param {*} cls
|
* @param {*} cls
|
||||||
*/
|
*/
|
||||||
export function removeClass(elem, cls) {
|
export function removeClass(elem, cls) {
|
||||||
if (hasClass(elem, cls)) {
|
if (!elem || !cls) return;
|
||||||
let newClass = ' ' + elem.className.replace(/[\t\r\n]/g, '') + ' ';
|
|
||||||
// 删除指定类名
|
// 使用 classList 优先(更安全)
|
||||||
while (newClass.indexOf(' ' + cls + ' ') >= 0) {
|
if (elem.classList) {
|
||||||
newClass = newClass.replace(' ' + cls + ' ', ' ');
|
elem.classList.remove(cls);
|
||||||
}
|
return;
|
||||||
// 重新赋值
|
|
||||||
elem.className = newClass.replace(/^\s+|\s+$/);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 传统写法的修复版本
|
||||||
|
let klass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' ';
|
||||||
|
|
||||||
|
// 持续删除目标 class
|
||||||
|
while (klass.indexOf(' ' + cls + ' ') !== -1) {
|
||||||
|
klass = klass.replace(' ' + cls + ' ', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤多余空格
|
||||||
|
elem.className = klass.trim().replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
let currentScript = null;
|
||||||
|
export function loadScript(path, globalInitName) {
|
||||||
|
console.log('load script', currentScript);
|
||||||
|
|
||||||
|
if (currentScript) {
|
||||||
|
console.log('remove script')
|
||||||
|
currentScript.remove();
|
||||||
|
currentScript = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalInitName && window[globalInitName]) {
|
||||||
|
delete window[globalInitName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = path;
|
||||||
|
script.type = 'module';
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
currentScript = document.querySelector(`script[src='${path}']`);
|
||||||
|
console.log('script loaded', currentScript);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
script.onerror = reject;
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user