diff --git a/rollup.config.js b/rollup.config.js index 5536cef..5c924d4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,32 +5,41 @@ import json from '@rollup/plugin-json'; import livereload from 'rollup-plugin-livereload'; import serve from 'rollup-plugin-serve'; -export default defineConfig({ - input: 'sidepanel/js/index.js', - output: { - file: 'dist/bundle.js', - format: 'iife', +const basePlugins = [resolve(), commonjs(), json()]; + +const devPlugins = [ + livereload({ + watch: ['dist', 'sidepanel'], + verbose: false, + }), + serve({ + open: true, + port: 8082, + contentBase: ['.', 'sidepanel'], + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }), +]; + +export default defineConfig([ + { + input: 'sidepanel/js/index.js', + output: { + file: 'dist/index.js', + format: 'iife', + name: 'MainPage', + }, + plugins: [...basePlugins, ...devPlugins], }, - plugins: [ - resolve(), - commonjs(), - json(), - livereload({ - watch: ['dist', 'sidepanel'], - verbose: false, - }), - serve({ - open: true, - port: 8082, - contentBase: ['.', 'sidepanel'], - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }), - ], - watch: { - include: ['sidepanel/**/*'], - exclude: ['node_modules/**/*'], - clearScreen: false, + + { + input: 'sidepanel/js/timestamp.js', + output: { + file: 'dist/timestamp.js', + format: 'iife', + name: 'TimestampPage', + }, + plugins: [...basePlugins, ...devPlugins], }, -}); +]); diff --git a/sidepanel/index.html b/sidepanel/index.html index bed4405..9dfabcf 100644 --- a/sidepanel/index.html +++ b/sidepanel/index.html @@ -10,12 +10,18 @@ -
+
+
+
+
+
+
+
- + diff --git a/sidepanel/js/index.js b/sidepanel/js/index.js index f3936ce..d522987 100644 --- a/sidepanel/js/index.js +++ b/sidepanel/js/index.js @@ -1,17 +1,58 @@ -// 导入时间戳工具的初始化函数和事件处理 -import './timestamp.js'; +console.log('Before creating router'); +import { Router } from '../modules/Router.js'; -// 导入页面切换功能 -import { switchPage } from '../utils/domUtils.js'; +import * as PageModules from '../../dist/timestamp.js'; -// 页面切换逻辑 -import { addEventListenerById } from '../utils/domUtils.js'; +const config = { + routerViewId: 'app', // 路由切换的挂载点 id + stackPages: false, // 多级页面缓存 + animationName: 'fade', // 切换页面时的动画 + routes: [ + { + path: '/home', + name: 'home', + callback: async () => { + // 1. 加载 HTML + const html = await fetch('pages/timestamp.html').then((r) => r.text()); + document.getElementById('home').innerHTML = html; -addEventListenerById('nav-timestamp-btn', 'click', () => { - switchPage('timestamp'); + // 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', + name: 'todo', + callback: async () => { + // 1. 加载 HTML + 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); + }, + }, + ], +}; + +const router = new Router(); +console.log('Router created:', router); +router.init(config); +console.log('Router initialized'); + +document.getElementById('todo-jump').addEventListener('click', () => { + console.log('Jumping to todo page'); + window.linkTo('#/todo'); }); - -// 计时器页面切换 -addEventListenerById('nav-other-tools-btn', 'click', () => { - switchPage('other-tools'); -}); \ No newline at end of file diff --git a/sidepanel/js/timestamp.js b/sidepanel/js/timestamp.js index 692ba88..8291fe4 100644 --- a/sidepanel/js/timestamp.js +++ b/sidepanel/js/timestamp.js @@ -12,24 +12,27 @@ import { } from '../modules/eventHandlers.js'; import { addEventListenerById } from '../utils/domUtils.js'; -// 初始化时间戳显示 -initTimestampDisplay(); +export function init() { + console.log('timestamp init') + // 初始化时间戳显示 + initTimestampDisplay(); -// 绑定事件处理器 -// 绑定时间戳相关按钮事件 -addEventListenerById('toggle-unit-btn', 'click', handleToggleUnit); -// 绑定计时器相关按钮事件 -addEventListenerById('stop-timer-btn', 'click', handleStopTimer); -addEventListenerById('start-timer-btn', 'click', handleStartTimer); -// 绑定时间戳转换按钮事件 -addEventListenerById('convert-timestamp-to-date-btn', 'click', handleConvertTimestamp); -// 绑定日期转换按钮事件 -addEventListenerById('convert-date-to-timestamp-btn', 'click', handleConvertDateToTimestamp); -// 绑定复制时间戳按钮事件 -addEventListenerById('copy-timestamp-btn', 'click', handleCopyTimestamp); -// 绑定关闭面板按钮事件 -addEventListenerById('close-panel-btn', 'click', handleClosePanel); -// 绑定切换时区事件 -addEventListenerById('timezone-result', 'change', handleTimezoneResult); -// 绑定输入时区事件 -addEventListenerById('timezone-input', 'change', handleTimezoneInput); + // 绑定事件处理器 + // 绑定时间戳相关按钮事件 + addEventListenerById('toggle-unit-btn', 'click', handleToggleUnit); + // 绑定计时器相关按钮事件 + addEventListenerById('stop-timer-btn', 'click', handleStopTimer); + addEventListenerById('start-timer-btn', 'click', handleStartTimer); + // 绑定时间戳转换按钮事件 + addEventListenerById('convert-timestamp-to-date-btn', 'click', handleConvertTimestamp); + // 绑定日期转换按钮事件 + addEventListenerById('convert-date-to-timestamp-btn', 'click', handleConvertDateToTimestamp); + // 绑定复制时间戳按钮事件 + addEventListenerById('copy-timestamp-btn', 'click', handleCopyTimestamp); + // 绑定关闭面板按钮事件 + addEventListenerById('close-panel-btn', 'click', handleClosePanel); + // 绑定切换时区事件 + addEventListenerById('timezone-result', 'change', handleTimezoneResult); + // 绑定输入时区事件 + addEventListenerById('timezone-input', 'change', handleTimezoneInput); +} diff --git a/sidepanel/modules/Router.js b/sidepanel/modules/Router.js index 63074bf..f45b096 100644 --- a/sidepanel/modules/Router.js +++ b/sidepanel/modules/Router.js @@ -1,12 +1,20 @@ -import { - RouteUtils, - addClass, - removeClass, - hasClass, - getParamsUrl, - closure, - genKey, -} from '../utils/routeUtils.js'; +import { getParamsUrl, closure, genKey } from '../utils/routeUtils.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 { constructor() { // 路由表 @@ -32,14 +40,23 @@ class Router { } init(config) { - this.routerMap = config ? config.routers : this.routerMap; + 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) { - let selector = this.routerViewId + '.page'; - let pages = document.querySelectorAll(selector); + // 找到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); @@ -60,13 +77,13 @@ class Router { } }; - window.addEventListener( - 'load', - function (event) { - this.historyChange(event); - }, - false - ); + window.addEventListener('load', (event) => { + this.historyChange(event); + }); + + window.addEventListener('hashchange', (event) => { + this.historyChange(event); + }); } /** @@ -74,29 +91,41 @@ class Router { * @param {*} event */ historyChange(event) { + // {path, query, params} const currentHash = getParamsUrl(); + // router-#app-history const nameString = 'router-' + this.routerViewId + '-history'; + this.history = window.sessionStorage[nameString] ? JSON.parse(window.sessionStorage[nameString]) : []; - let back = false, - refresh = false, - forward = false, - index = 0, - len = this.history.length; + // 返回上一级 + let back = false; + // 刷新页面 + let refresh = false; + // 前进上一级 + let forward = false; + // 获取当前路由的索引 + let index = 0; - for (let i = 0; i < len; i++) { + 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 === len - 1) { + // 判断是否是刷新页面 + if (i === this.history.length - 1) { refresh = true; } else { + // 判断是否是返回上一级 back = true; } break; } else { + // 判断是否是前进 forward = true; } } @@ -124,18 +153,22 @@ class Router { this.urlChange(); } + /** + * 更改页面 + * @param {*} currentHash + */ changeView(currentHash) { - const pages = document.querySelectorAll('.page'); + const pages = document.querySelectorAll(' .page'); const previousPage = document.querySelector('.' + this.routerViewId + ' .page.active'); let currentPage = null; - let currentHash = 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) { - currentHash = hash; + currHash = hash; currentPage = page; } } @@ -167,12 +200,15 @@ class Router { removeClass(currentPage, 'current'); }, 300); currentPage.scrollTop = 0; - this.routes[currentHash].callback ? this.routes[currentHash].callback() : null; + this.routes[currHash].callback ? this.routes[currHash].callback() : null; } this.afterFun ? this.afterFun(currentHash) : null; } + /** + * url改变 + */ urlChange() { const currentHash = getParamsUrl(); if (this.routes[currentHash.path]) { @@ -194,6 +230,9 @@ class Router { } } + /** + * 映射路由 + */ map() { for (let i = 0; i < this.routerMap.length; i++) { let route = this.routerMap[i]; @@ -204,26 +243,42 @@ class Router { } let newPath = route.path; - let path = newPath.repalce(/\s+/g, ''); + let path = newPath.replace(/\s+/g, ''); this.routes[path] = { callback: route.callback, }; } } + /** + * 切换页面前的hook + * @param {*} callback hook执行函数 + */ + beforeEach(callback) { if (Object.prototype.toString.call(callback) === '[object Function]') { + // 判断callback是否为函数 this.beforeFun = callback; } else { + // 抛出错误 console.trace('beforeEach callback must be a function'); } } + /** + * 切换页面后的hook + * @param {*} callback hook执行函数 + */ + afterEach(callback) { if (Object.prototype.toString.call(callback) === '[object Function]') { + // 判断callback是否为函数 this.afterFun = callback; } else { + // 抛出错误 console.trace('afterEach callback must be a function'); } } } + +export { Router }; diff --git a/sidepanel/pages/timestamp.html b/sidepanel/pages/timestamp.html index c4adf68..6177ca9 100644 --- a/sidepanel/pages/timestamp.html +++ b/sidepanel/pages/timestamp.html @@ -1,4 +1,4 @@ -
+

时间戳工具

当前时间戳

diff --git a/sidepanel/pages/todolist.html b/sidepanel/pages/todolist.html new file mode 100644 index 0000000..24f6b8f --- /dev/null +++ b/sidepanel/pages/todolist.html @@ -0,0 +1,3 @@ +
+

任务清单

+
diff --git a/sidepanel/style/style.css b/sidepanel/style/style.css index 82fe21b..ae92ba1 100644 --- a/sidepanel/style/style.css +++ b/sidepanel/style/style.css @@ -53,9 +53,10 @@ p a { box-sizing: border-box; color: #2b2b2b; - font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, + monospace; } - + button { padding: 0.5rem 1rem; background-color: #28a745; @@ -150,27 +151,6 @@ overflow: hidden; } -/* 页面切换动画 */ -.page { - display: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - padding: 1rem; - opacity: 0; - transform: translateX(20px); - transition: opacity 0.3s ease, transform 0.3s ease; -} - -.page.active { - display: block; - position: relative; - opacity: 1; - transform: translateX(0); - transition: opacity 0.3s ease, transform 0.3s ease; -} - /* 按钮点击效果 */ .button:active, .convert-button:active, diff --git a/sidepanel/utils/domUtils.js b/sidepanel/utils/domUtils.js index 2fd3df4..0f56feb 100644 --- a/sidepanel/utils/domUtils.js +++ b/sidepanel/utils/domUtils.js @@ -11,11 +11,11 @@ export function switchPage(pageName) { const currentPage = document.querySelector('.page.active'); if (currentPage) { currentPage.classList.remove('active'); - + // 添加离开动画 currentPage.style.opacity = '0'; currentPage.style.transform = 'translateX(20px)'; - + // 等待动画完成后真正隐藏 setTimeout(() => { if (!currentPage.classList.contains('active')) { @@ -33,14 +33,14 @@ export function switchPage(pageName) { const targetPage = document.getElementById(`page-${pageName}`); if (targetPage) { targetPage.style.display = 'block'; - + // 触发重排以确保display变化生效 targetPage.offsetHeight; - + // 添加进入动画 targetPage.style.opacity = '0'; targetPage.style.transform = 'translateX(-20px)'; - + setTimeout(() => { targetPage.classList.add('active'); targetPage.style.opacity = '1'; @@ -49,7 +49,8 @@ export function switchPage(pageName) { } // 激活对应的导航按钮 - const navButton = document.getElementById(`nav-${pageName}-btn`) || document.getElementById(`nav-${pageName}`); + const navButton = + document.getElementById(`nav-${pageName}-btn`) || document.getElementById(`nav-${pageName}`); if (navButton) { navButton.classList.add('active'); } @@ -75,4 +76,46 @@ export function addEventListenerById(id, event, handler) { if (element) { element.addEventListener(event, handler); } -} \ No newline at end of file +} + +/** + * 判断元素是否包含指定类名 + * @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 (!hasClass(elem, cls)) { + elem.className = elem.className === '' ? cls : elem.className + ' ' + cls; + } +} + +/** + * 为元素删除类名 + * @param {*} elem + * @param {*} cls + */ +export function removeClass(elem, cls) { + if (hasClass(elem, cls)) { + let newClass = ' ' + elem.className.replace(/[\t\r\n]/g, '') + ' '; + // 删除指定类名 + while (newClass.indexOf(' ' + cls + ' ') >= 0) { + newClass = newClass.replace(' ' + cls + ' ', ' '); + } + // 重新赋值 + elem.className = newClass.replace(/^\s+|\s+$/); + } +} diff --git a/sidepanel/utils/routeUtils.js b/sidepanel/utils/routeUtils.js index 23493bd..90a4524 100644 --- a/sidepanel/utils/routeUtils.js +++ b/sidepanel/utils/routeUtils.js @@ -1,13 +1,16 @@ /** - * 获取路由的路径和详情参数 - * @returns + * 获取路由的路径和详细参数 + * @returns */ export function getParamsUrl() { - let hasDetail = location.hash.split('?'); - let hasName = hasDetail[0].split('#')[1]; - let params = hasDetail[1] ? hasDetail[1].split('&') : []; + // 获取路由 + const hasDetail = location.hash.split('?'); + // 获取路由名称 + const hasName = hasDetail[0].split('#')[1]; + // 获取请求参数 + const params = hasDetail[1] ? hasDetail[1].split('&') : []; + // 解析请求参数 let query = {}; - for (let i = 0; i < params.length; i++) { let param = params[i].split('='); query[param[0]] = param[1]; @@ -20,40 +23,26 @@ export function getParamsUrl() { }; } +/** + * 闭包返回函数 + * @param {*} name + * @returns + */ export function closure(name) { - function fun(currentHash) { - window.name&&window[name](currentHash) - } - return fun; + return (currentHash) => { + window.name && window[name](currentHash); + }; } +/** + * 生成随机key + * @returns + */ export function genKey() { - let temp = 'xxxxxxxx'; - return temp.replace(/[xy]/g, function (c) { - let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + const KEY_TEMPLATE = 'xxxxxxxx'; + return KEY_TEMPLATE.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); } - -export function hasClass(elem, cls) { - cls = cls || ''; - if (cls.replace(/\s/g, '').length === 0) - return false; - return new RegExp(' ' + cls + ' ').test(' ' + elem.className + ' '); -} - -export function addClass(elem, cls) { - if (!hasClass(elem, cls)) { - elem.className = elem.className === '' ? cls : elem.className + ' ' + cls; - } -} - -export function removeClass(elem, cls) { - if (hasClass(elem, cls)) { - let newClass = ' ' + elem.className.replace(/[\t\r\n]/g, '') + ' '; - while (newClass.indexOf(' ' + cls + ' ') >= 0) { - newClass = newClass.replace(' ' + cls + ' ', ' '); - } - elem.className = newClass.replace(/^\s+|\s+$/) - } -} \ No newline at end of file