feat(sidepanel): 实现基于组件的架构并优化路由管理
- 引入 BaseComponent 基类统一管理事件、定时器和脚本加载 - 重构 timestamp 模块为继承自 BaseComponent 的类组件 - 新增 ScriptManager 工具类用于动态加载和卸载脚本 - 修改路由配置支持按需加载页面对应的 JS 模块 - 路由切换时自动销毁旧组件实例以防止内存泄漏 - 更新文件引用路径适配新的目录结构 - 修复 rollup 配置中的语法问题及端口配置 - 移除无用的日志输出和冗余代码提升性能
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* DOM工具类函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 为元素添加事件监听器
|
||||
* @param {string} id - 元素ID
|
||||
* @param {string} event - 事件类型
|
||||
* @param {Function} handler - 事件处理函数
|
||||
*/
|
||||
export function addEventListenerById(id, event, handler) {
|
||||
const ele = document.getElementById(id);
|
||||
if (ele) {
|
||||
window.eventManager.add(ele, event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否包含指定类名
|
||||
* @param {*} elem
|
||||
* @param {*} cls
|
||||
* @returns
|
||||
*/
|
||||
export function hasClass(elem, cls) {
|
||||
cls = cls || '';
|
||||
// 检测类名是否为''
|
||||
if (cls.replace(/\s/g, '').length === 0) return false;
|
||||
// 检测elem类名是否包含cls指定类名
|
||||
return new RegExp(' ' + cls + ' ').test(' ' + elem.className + ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 为元素添加类名
|
||||
* @param {*} elem
|
||||
* @param {*} cls
|
||||
*/
|
||||
export function addClass(elem, cls) {
|
||||
if (!elem || !cls) return;
|
||||
|
||||
// 统一空白字符(避免 \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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 为元素删除类名
|
||||
* @param {*} elem
|
||||
* @param {*} cls
|
||||
*/
|
||||
export function removeClass(elem, cls) {
|
||||
if (!elem || !cls) return;
|
||||
|
||||
// 使用 classList 优先(更安全)
|
||||
if (elem.classList) {
|
||||
elem.classList.remove(cls);
|
||||
return;
|
||||
}
|
||||
|
||||
// 传统写法的修复版本
|
||||
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,32 @@
|
||||
export class EventManager {
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
add(ele, type, handler, options) {
|
||||
ele.addEventListener(type, handler, options);
|
||||
this.listeners.push({ ele, type, handler, options });
|
||||
}
|
||||
|
||||
remove(ele, type, handler, options) {
|
||||
ele.removeEventListener(type, handler, options);
|
||||
this.listeners = this.listeners.filter(
|
||||
(listener) =>
|
||||
listener.ele !== ele ||
|
||||
listener.type !== type ||
|
||||
listener.handler !== handler ||
|
||||
listener.options !== options
|
||||
);
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
this.listeners.forEach((listener) => {
|
||||
listener.ele.removeEventListener(
|
||||
listener.type,
|
||||
listener.handler,
|
||||
listener.options
|
||||
);
|
||||
});
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
export class ScriptManager {
|
||||
constructor() {
|
||||
this._loadedClasses = new Map();
|
||||
this.currentScriptEl = null;
|
||||
this.currentModule = null;
|
||||
this.currentName = null;
|
||||
}
|
||||
|
||||
async loadScript({path, name, isModule = false, deps = []}) {
|
||||
console.log('loadScript', name);
|
||||
if (this._loadedClasses.has(name)) {
|
||||
console.log(`Component ${name} loaded from cache`);
|
||||
return this._loadedClasses.get(name);
|
||||
}
|
||||
|
||||
for (const dep of deps) {
|
||||
await this._appendScript({path: dep, isModule: false});
|
||||
}
|
||||
|
||||
let componentReference = null;
|
||||
|
||||
console.log(isModule);
|
||||
if (isModule) {
|
||||
const module = await import(path);
|
||||
this.currentModule = module;
|
||||
componentReference = module;
|
||||
} else {
|
||||
await this._appendScript({path, isModule: false});
|
||||
componentReference = window[name] || null;
|
||||
}
|
||||
|
||||
if (componentReference) {
|
||||
this._loadedClasses.set(name, componentReference);
|
||||
}
|
||||
|
||||
this.currentName = name;
|
||||
return componentReference;
|
||||
}
|
||||
|
||||
async _appendScript({path, isModule}) {
|
||||
const s = document.createElement('script');
|
||||
s.src = path;
|
||||
s.type = isModule ? 'module' : 'text/javascript';
|
||||
s.async = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
s.onload = () => {
|
||||
this.currentScriptEl = s;
|
||||
console.log(s);
|
||||
resolve();
|
||||
};
|
||||
s.onerror = () => {
|
||||
reject(new Error(`Failed to load script: ${path}`));
|
||||
}
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async unloadScript() {
|
||||
console.log('unloadScript');
|
||||
if (this.currentScriptEl) {
|
||||
try {
|
||||
this.currentScriptEl.remove();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentModule = null;
|
||||
this.currentName = null;
|
||||
this.currentScriptEl = null;
|
||||
}
|
||||
|
||||
async getComponentInstance(name) {
|
||||
const ComponentClassReference = this._loadedClasses.get(name);
|
||||
const BaseClassForCheck = window.BaseComponent;
|
||||
|
||||
if (!ComponentClassReference) {
|
||||
console.error('ComponentClassReference not found');
|
||||
return null
|
||||
}
|
||||
|
||||
const ComponentClass = ComponentClassReference.default
|
||||
|| ComponentClassReference;
|
||||
|
||||
if (typeof ComponentClass === 'function') {
|
||||
if (!(ComponentClass.prototype instanceof BaseClassForCheck)) {
|
||||
console.error(`Component ${name} does not inherit BaseComponent.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ComponentClass();
|
||||
}
|
||||
|
||||
console.error(`Component ${name} does not exist`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
export class TimerManager {
|
||||
constructor() {
|
||||
// 定时器对象数组
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加定时器到数组中
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
setTimeout(fn, delay, ...args) {
|
||||
const id = window.setTimeout(fn, delay, ...args);
|
||||
this.timers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加定时器到数组中
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
setInterval(fn, delay, ...args) {
|
||||
const id = window.setInterval(fn, delay, ...args);
|
||||
this.timers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除定时器
|
||||
* @param {*} id
|
||||
*/
|
||||
clearTimeout(id) {
|
||||
window.clearTimeout(id);
|
||||
this.timers = this.timers.filter((timerId) => timerId !== id);
|
||||
}
|
||||
|
||||
clearInterval(id) {
|
||||
window.clearInterval(id);
|
||||
this.timers = this.timers.filter((timerId) => timerId !== id);
|
||||
}
|
||||
|
||||
cleanAll() {
|
||||
this.timers.forEach((timerId) => {
|
||||
window.clearTimeout(timerId);
|
||||
window.clearInterval(timerId);
|
||||
});
|
||||
this.timers = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 时间戳工具类函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 更新时间戳显示
|
||||
* @param {*} ele
|
||||
* @param {*} showMilliseconds
|
||||
*/
|
||||
export function updateTimestamp(ele, showMilliseconds = true) {
|
||||
const timestamp = Date.now();
|
||||
ele.textContent = showMilliseconds ? timestamp : Math.floor(timestamp / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换时间戳为日期格式
|
||||
* @param {string} timestamp - 输入的时间戳
|
||||
* @param {boolean} isSeconds - 是否为秒格式
|
||||
* @param {string} timeZone - 时区,默认为本地时区
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
export function convertTimestampToDate(
|
||||
timestamp,
|
||||
isSeconds,
|
||||
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
) {
|
||||
// 转换为数字
|
||||
let timestampNum = Number(timestamp);
|
||||
|
||||
// 如果是秒,转换为毫秒
|
||||
if (isSeconds) {
|
||||
timestampNum = timestampNum * 1000;
|
||||
}
|
||||
|
||||
// 检查是否为有效数字
|
||||
if (isNaN(timestampNum)) {
|
||||
return '请输入有效的时间戳';
|
||||
}
|
||||
|
||||
// 创建日期对象
|
||||
const date = new Date(timestampNum);
|
||||
|
||||
// 检查日期是否有效
|
||||
if (isNaN(date.getTime())) {
|
||||
return '无效的日期';
|
||||
}
|
||||
|
||||
// 使用Intl.DateTimeFormat来根据时区格式化日期
|
||||
const formatter = new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZone: timeZone,
|
||||
});
|
||||
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换日期格式为时间戳
|
||||
* @param {string|Date} date - 输入的日期字符串或Date对象
|
||||
* @param {string} timeZone - 时区,默认为本地时区
|
||||
* @returns {number|string} 时间戳或错误信息
|
||||
*/
|
||||
export function convertDateToTimestamp(
|
||||
date,
|
||||
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
) {
|
||||
try {
|
||||
// 创建日期对象
|
||||
const dateObj = new Date(date);
|
||||
|
||||
// 检查日期是否有效
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
return '无效的日期';
|
||||
}
|
||||
|
||||
// 如果提供了时区,则需要特殊处理
|
||||
if (timeZone) {
|
||||
// 获取给定时区相对于UTC的时间差(毫秒)
|
||||
const utc = dateObj.getTime() + dateObj.getTimezoneOffset() * 60000;
|
||||
|
||||
// 计算目标时区相对于UTC的偏移量
|
||||
const targetOffset = getTimeZoneOffset(timeZone);
|
||||
|
||||
// 返回目标时区对应的时间戳
|
||||
const targetTimestamp = utc + targetOffset;
|
||||
return targetTimestamp;
|
||||
} else {
|
||||
// 没有时区参数,直接返回时间戳
|
||||
const timestamp = dateObj.getTime();
|
||||
console.log('timestamp: ', timestamp);
|
||||
return timestamp;
|
||||
}
|
||||
} catch (error) {
|
||||
return '日期转换错误: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定时区相对于UTC的偏移量(毫秒)
|
||||
* @param {string} timeZone - 时区名称
|
||||
* @returns {number} 偏移量(毫秒)
|
||||
*/
|
||||
function getTimeZoneOffset(timeZone) {
|
||||
const now = new Date();
|
||||
const utc = new Date(now.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||
const target = new Date(now.toLocaleString('en-US', { timeZone: timeZone }));
|
||||
return target.getTime() - utc.getTime();
|
||||
}
|
||||
Reference in New Issue
Block a user