From 8b608341e709525510e353c5af306fe4d22467f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Mon, 17 Nov 2025 00:25:03 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(sidepanel):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=88=B3=E8=BD=AC=E6=8D=A2=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将代码模块化,拆分为 eventHandlers、domUtils 和 timestampUtils 等文件 - 使用 Rollup 配置文件替代内联构建命令,提升可维护性 - 引入页面切换动画与交互反馈动画,增强用户体验 - 更新 HTML 元素 ID 命名以提高语义化和一致性 - 改进 CSS 样式,增加悬停效果、过渡动画和响应式支持 - package.json 中添加 type: module 并升级版本至 1.1 ``` --- package.json | 7 +- rollup.config.js | 7 ++ sidepanel/index.css | 123 +++++++++++++++----- sidepanel/index.html | 36 +++--- sidepanel/index.js | 174 ++++++---------------------- sidepanel/modules/eventHandlers.js | 176 +++++++++++++++++++++++++++++ sidepanel/utils/domUtils.js | 78 +++++++++++++ sidepanel/utils/timestampUtils.js | 64 +++++++++++ 8 files changed, 476 insertions(+), 189 deletions(-) create mode 100644 rollup.config.js create mode 100644 sidepanel/modules/eventHandlers.js create mode 100644 sidepanel/utils/domUtils.js create mode 100644 sidepanel/utils/timestampUtils.js diff --git a/package.json b/package.json index c6c3c3c..33275e5 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "Chrome Extensions Gemini Demo", - "version": "1.0", + "version": "1.1", + "type": "module", "scripts": { - "build": "rollup sidepanel/index.js --file dist/sidepanel.bundle.js --format iife" + "build": "rollup -c" }, "private": true, "devDependencies": { "@google/generative-ai": "0.15.0", "rollup": "4.22.4" } -} +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..a143c7a --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,7 @@ +export default { + input: 'sidepanel/index.js', + output: { + file: 'dist/sidepanel.bundle.js', + format: 'iife' + } +}; \ No newline at end of file diff --git a/sidepanel/index.css b/sidepanel/index.css index 0aee4fb..9bb10f7 100644 --- a/sidepanel/index.css +++ b/sidepanel/index.css @@ -1,4 +1,10 @@ /* sidepanel/style.css */ +#app { + display: flex; + flex-direction: column; + height: 100vh; +} + .navbar { display: flex; background-color: #f8f9fa; @@ -13,7 +19,7 @@ border: none; cursor: pointer; text-align: center; - transition: background-color 0.2s; + transition: all 0.3s ease; } .nav-button:hover { @@ -23,6 +29,8 @@ .nav-button.active { background-color: #007bff; color: white; + transform: translateY(-2px); + box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .close-button { @@ -33,6 +41,12 @@ border-radius: 0.25rem; cursor: pointer; margin-left: auto; + transition: all 0.3s ease; +} + +.close-button:hover { + background-color: #c82333; + transform: scale(1.05); } .datetime-box { @@ -40,49 +54,44 @@ align-items: center; flex-wrap: wrap; gap: 0.5rem; -} - -@media screen and (max-width: 768px) { - .datetime-box { - flex-direction: column; - align-items: stretch; - } + transition: all 0.3s ease; } .input-group { display: flex; gap: 1rem; align-items: center; - margin: 0.5rem 0; - min-width: 200px; -} - -@media screen and (max-width: 768px) { - .input-group { - min-width: unset; - width: 100%; - } + transition: all 0.3s ease; } .input-text { width: 200px; padding: 0.5rem; - margin: 0.5rem 0; box-sizing: border-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: all 0.3s ease; } -@media screen and (max-width: 768px) { - .input-text { - width: 100%; - flex: 1; - } +.input-text:focus { + border-color: #007bff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + outline: none; } .select-box { - width: 150px; + min-width: 150px; padding: 0.5rem; - margin: 0.5rem 0; box-sizing: border-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: all 0.3s ease; +} + +.select-box:focus { + border-color: #007bff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + outline: none; } .convert-button { @@ -94,24 +103,78 @@ cursor: pointer; height: 2rem; width: 4rem; - margin: 0.5rem 0; align-self: flex-start; + transition: all 0.3s ease; } -@media screen and (max-width: 768px) { - .convert-button { - width: 100%; - } +.convert-button:hover { + background-color: #218838; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.2); } .content { padding: 1rem; + position: relative; + flex: 1; + 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, .nav-button:active, .close-button:active { + transform: scale(0.95); +} + +/* 输入框动画 */ +.input-text, .select-box { + transition: all 0.2s ease-in-out; +} + +.input-text:hover, .select-box:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +/* 结果显示动画 */ +#timestamp-conversion-result, #date-conversion-result, #current-timestamp-value { + transition: all 0.3s ease; +} + +#timestamp-conversion-result:focus, #date-conversion-result:focus, #current-timestamp-value:focus { + transform: scale(1.02); +} + +@media screen and (max-width: 900px) { + .input-group { + width: 100%; + } + + .input-text { + width: 100%; + } + + .page { + padding: 0.5rem; + } } \ No newline at end of file diff --git a/sidepanel/index.html b/sidepanel/index.html index 604a350..b0c4e1a 100644 --- a/sidepanel/index.html +++ b/sidepanel/index.html @@ -11,9 +11,9 @@
@@ -24,12 +24,12 @@

当前时间戳

- 1762873747965 - 毫秒 - + 1762873747965 + 毫秒 + - - + +

@@ -40,24 +40,24 @@
-
- +
@@ -75,7 +75,7 @@
@@ -85,17 +85,17 @@
- +
- @@ -105,13 +105,13 @@
-
+

其他工具页面

这里可以放置其他工具的内容。

- + diff --git a/sidepanel/index.js b/sidepanel/index.js index 1b73ab6..f2322d9 100644 --- a/sidepanel/index.js +++ b/sidepanel/index.js @@ -1,147 +1,45 @@ -document.getElementById('close').addEventListener('click', () => { - window.parent.postMessage({ action: 'indexind' }, '*'); -}); +// document.getElementById('close').addEventListener('click', () => { +// window.parent.postMessage({ action: 'indexind' }, '*'); +// }); // sidepanel/index.js +import { switchPage } from './utils/domUtils.js'; +import { + initTimestampDisplay, + handleToggleUnit, + handleStopTimer, + handleStartTimer, + handleConvertTimestamp, + handleConvertDateToTimestamp, + handleCopyTimestamp, + handleClosePanel +} from './modules/eventHandlers.js'; +import { addEventListenerById } from './utils/domUtils.js'; + // 页面切换逻辑 -document.getElementById('nav-timestamp').addEventListener('click', () => { +addEventListenerById('nav-timestamp-btn', 'click', () => { switchPage('timestamp'); }); -document.getElementById('nav-other').addEventListener('click', () => { - switchPage('other'); +// 计时器页面切换 +addEventListenerById('nav-other-tools-btn', 'click', () => { + switchPage('other-tools'); }); -/** - * 更新时间戳显示 - */ -function updateTimestamp(showMilliseconds = true) { - const currentTimestamp = document.getElementById('current-timestamp'); - const currentTimestampUnit = document.getElementById('timestamp-unit'); - const timestamp = Date.now(); - const unit = showMilliseconds ? '毫秒' : '秒'; - currentTimestamp.textContent = showMilliseconds ? timestamp : Math.floor(timestamp / 1000); - currentTimestampUnit.textContent = unit; -} +// 初始化时间戳显示 +initTimestampDisplay(); -const toggleTimestampBtn = document.getElementById('toggle-timestamp-btn'); -const stopTimestampBtn = document.getElementById('stop-timestamp-btn'); -const startTimestampBtn = document.getElementById('start-timestamp-btn'); - -stopTimestampBtn.addEventListener('click', () => { - clearInterval(timestampInterval); - stopTimestampBtn.style.display = 'none'; - startTimestampBtn.style.display = 'inline-block'; -}); - -startTimestampBtn.addEventListener('click', () => { - timestampInterval = setInterval(() => updateTimestamp(showMilliseconds), 100); - startTimestampBtn.style.display = 'none'; - stopTimestampBtn.style.display = 'inline-block'; -}); - -let showMilliseconds = true; -updateTimestamp(showMilliseconds); // 初始化时更新一次时间戳 -let timestampInterval = setInterval(() => updateTimestamp(showMilliseconds), 100); -toggleTimestampBtn.addEventListener('click', () => { - showMilliseconds = !showMilliseconds; - updateTimestamp(showMilliseconds); -}); - -const inputTimestamp = document.getElementById('input-timestamp'); -const convertTimestampBtn = document.getElementById('convert-timestamp-btn'); -const timestampInputResult = document.getElementById('timestamp-input-result'); -const timestampUnitSelect = document.querySelector('#page-timestamp select'); // 时间戳单位选择器 - -convertTimestampBtn.addEventListener('click', () => { - const timestamp = inputTimestamp.value.trim(); - console.log('输入时间戳:', timestamp); - - if (!timestamp) { - timestampInputResult.value = '请输入时间戳'; - return; - } - - // 获取选择的时间戳单位(秒或毫秒) - const isSeconds = timestampUnitSelect.value === 'seconds'; - - // 转换为数字 - let timestampNum = Number(timestamp); - - // 如果是秒,转换为毫秒 - if (isSeconds) { - timestampNum = timestampNum * 1000; - } - - // 检查是否为有效数字 - if (isNaN(timestampNum)) { - timestampInputResult.value = '请输入有效的时间戳'; - return; - } - - // 创建日期对象 - const date = new Date(timestampNum); - - // 检查日期是否有效 - if (isNaN(date.getTime())) { - timestampInputResult.value = '无效的日期'; - return; - } - - console.log('转换日期对象:', date); - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - const hours = date.getHours(); - const minutes = date.getMinutes(); - const seconds = date.getSeconds(); - - console.log('转换结果:', `${year}年${month}月${day}日 ${hours}时${minutes}分${seconds}秒`); - - timestampInputResult.value = `${year}年${month}月${day}日 ${hours}时${minutes}分${seconds}秒`; -}); - -// 复制时间戳到剪贴板 -const copyTimestampBtn = document.getElementById('copy-timestamp-btn'); -const currentTimestamp = document.getElementById('current-timestamp'); -copyTimestampBtn.addEventListener('click', () => { - // 获取时间戳文本 - const timestampText = currentTimestamp.textContent; - - // 检查扩展上下文是否仍然有效 - if (chrome.runtime && chrome.runtime.sendMessage) { - chrome.runtime.sendMessage({ action: 'copyText', data: timestampText }, (res) => { - if (chrome.runtime.lastError) { - console.error('❌ 传送时间戳文本失败:', chrome.runtime.lastError); - } else if (res?.success) { - console.log('✅ 已复制:', timestampText); - } else { - console.error('❌ 复制失败:', res); - } - }); - } -}); - -function switchPage(pageName) { - // 隐藏所有页面 - document.querySelectorAll('.page').forEach((page) => { - page.classList.remove('active'); - }); - - // 移除所有导航按钮的 active 状态 - document.querySelectorAll('.nav-button').forEach((button) => { - button.classList.remove('active'); - }); - - // 显示目标页面 - document.getElementById(`page-${pageName}`).classList.add('active'); - - // 激活对应的导航按钮 - document.getElementById(`nav-${pageName}`).classList.add('active'); -} - -// 关闭按钮逻辑 (使用消息传递) -document.getElementById('close').addEventListener('click', () => { - // 向父页面发送消息请求关闭 - window.parent.postMessage({ action: 'closeSidebar' }, '*'); -}); +// 绑定事件处理器 +// 绑定时间戳相关按钮事件 +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); diff --git a/sidepanel/modules/eventHandlers.js b/sidepanel/modules/eventHandlers.js new file mode 100644 index 0000000..ce690e2 --- /dev/null +++ b/sidepanel/modules/eventHandlers.js @@ -0,0 +1,176 @@ +import { updateTimestamp, convertTimestampToDate } from '../utils/timestampUtils.js'; +import { getElementById } from '../utils/domUtils.js'; + +// 定义一个全局变量来保存是否显示毫秒 +let showMilliseconds = true; +// 定义一个全局变量来保存计时器 +let timestampInterval; + +/** + * 初始化时间戳显示 + */ +export function initTimestampDisplay() { + updateTimestamp(showMilliseconds); // 初始化时更新一次时间戳 + timestampInterval = setInterval(() => updateTimestamp(showMilliseconds), 100); +} + +/** + * 切换单位按钮事件处理器 + */ +export function handleToggleUnit() { + showMilliseconds = !showMilliseconds; + updateTimestamp(showMilliseconds); +} + +/** + * 停止计时器事件处理器 + */ +export function handleStopTimer() { + clearInterval(timestampInterval); + const stopTimestampBtn = getElementById('stop-timer-btn'); + const startTimestampBtn = getElementById('start-timer-btn'); + stopTimestampBtn.style.display = 'none'; + startTimestampBtn.style.display = 'inline-block'; +} + +/** + * 开始计时器事件处理器 + */ +export function handleStartTimer() { + const startTimestampBtn = getElementById('start-timer-btn'); + const stopTimestampBtn = getElementById('stop-timer-btn'); + timestampInterval = setInterval(() => updateTimestamp(showMilliseconds), 100); + startTimestampBtn.style.display = 'none'; + stopTimestampBtn.style.display = 'inline-block'; +} + +/** + * 时间戳转换事件处理器 + */ +export function handleConvertTimestamp() { + const inputTimestamp = getElementById('timestamp-input'); + const timestampInputResult = getElementById('timestamp-conversion-result'); + const timestampUnitSelect = document.querySelector( + '#page-timestamp #timestamp-input-unit-select' + ); + + const timestamp = inputTimestamp.value.trim(); + console.log('输入时间戳:', timestamp); + + if (!timestamp) { + timestampInputResult.value = '请输入时间戳'; + // 添加动画效果 + timestampInputResult.style.borderColor = '#dc3545'; + setTimeout(() => { + timestampInputResult.style.borderColor = ''; + }, 1000); + return; + } + + // 获取选择的时间戳单位(秒或毫秒) + const isSeconds = timestampUnitSelect.value === 'seconds'; + + const result = convertTimestampToDate(timestamp, isSeconds); + timestampInputResult.value = result; + + // 添加成功反馈动画 + if (result.includes('无效') || result.includes('请输入')) { + timestampInputResult.style.borderColor = '#dc3545'; + } else { + timestampInputResult.style.borderColor = '#28a745'; + } + + // 添加闪烁动画效果 + timestampInputResult.style.transition = 'all 0.3s'; + timestampInputResult.style.transform = 'scale(1.02)'; + setTimeout(() => { + timestampInputResult.style.transform = 'scale(1)'; + }, 300); +} + +/** + * 日期转换事件处理器 + */ +export function handleConvertDateToTimestamp() { + const inputDate = getElementById('datetime-input'); + const dateInputResult = getElementById('date-conversion-result'); + const timestampUnitSelect = document.querySelector('#page-timestamp #date-result-unit-select'); + + const dateStr = inputDate.value.trim(); + console.log('输入日期字符串:', dateStr); + + if (!dateStr) { + dateInputResult.value = '请输入日期字符串'; + // 添加动画效果 + dateInputResult.style.borderColor = '#dc3545'; + setTimeout(() => { + dateInputResult.style.borderColor = ''; + }, 1000); + return; + } + + const date = new Date(dateStr); + if (isNaN(date.getTime())) { + dateInputResult.value = '无效的日期字符串'; + dateInputResult.style.borderColor = '#dc3545'; + return; + } + + const timestamp = date.getTime(); + // 根据选择的单位决定显示秒还是毫秒 + const isSeconds = timestampUnitSelect.value === 'seconds'; + const finalTimestamp = isSeconds ? Math.floor(timestamp / 1000) : timestamp; + dateInputResult.value = finalTimestamp; + + // 添加成功反馈动画 + dateInputResult.style.borderColor = '#28a745'; + + // 添加闪烁动画效果 + dateInputResult.style.transition = 'all 0.3s'; + dateInputResult.style.transform = 'scale(1.02)'; + setTimeout(() => { + dateInputResult.style.transform = 'scale(1)'; + }, 300); +} + +/** + * 复制时间戳事件处理器 + */ +export function handleCopyTimestamp() { + // 获取时间戳文本 + const currentTimestamp = getElementById('current-timestamp-value'); + const timestampText = currentTimestamp.textContent; + + // 添加复制反馈动画 + const originalText = currentTimestamp.textContent; + currentTimestamp.textContent = '已复制!'; + currentTimestamp.style.color = '#28a745'; + currentTimestamp.style.fontWeight = 'bold'; + + setTimeout(() => { + currentTimestamp.textContent = originalText; + currentTimestamp.style.color = ''; + currentTimestamp.style.fontWeight = ''; + }, 1000); + + // 检查扩展上下文是否仍然有效 + if (chrome.runtime && chrome.runtime.sendMessage) { + chrome.runtime.sendMessage({ action: 'copyText', data: timestampText }, (res) => { + if (chrome.runtime.lastError) { + console.error('❌ 传送时间戳文本失败:', chrome.runtime.lastError); + } else if (res?.success) { + console.log('✅ 已复制:', timestampText); + } else { + console.error('❌ 复制失败:', res); + } + }); + } +} + +/** + * 关闭面板事件处理器 + */ +export function handleClosePanel() { + // 向父页面发送消息请求关闭 + window.parent.postMessage({ action: 'closeSidebar' }, '*'); +} diff --git a/sidepanel/utils/domUtils.js b/sidepanel/utils/domUtils.js new file mode 100644 index 0000000..2fd3df4 --- /dev/null +++ b/sidepanel/utils/domUtils.js @@ -0,0 +1,78 @@ +/** + * DOM工具类函数 + */ + +/** + * 切换页面显示(带动画效果) + * @param {string} pageName - 页面名称 + */ +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')) { + currentPage.style.display = 'none'; + } + }, 300); + } + + // 移除所有导航按钮的 active 状态 + document.querySelectorAll('.nav-button').forEach((button) => { + button.classList.remove('active'); + }); + + // 显示目标页面 + 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'; + targetPage.style.transform = 'translateX(0)'; + }, 10); + } + + // 激活对应的导航按钮 + const navButton = document.getElementById(`nav-${pageName}-btn`) || document.getElementById(`nav-${pageName}`); + if (navButton) { + navButton.classList.add('active'); + } +} + +/** + * 根据ID获取元素的便捷方法 + * @param {string} id - 元素ID + * @returns {HTMLElement} - DOM元素 + */ +export function getElementById(id) { + return document.getElementById(id); +} + +/** + * 为元素添加事件监听器 + * @param {string} id - 元素ID + * @param {string} event - 事件类型 + * @param {Function} handler - 事件处理函数 + */ +export function addEventListenerById(id, event, handler) { + const element = getElementById(id); + if (element) { + element.addEventListener(event, handler); + } +} \ No newline at end of file diff --git a/sidepanel/utils/timestampUtils.js b/sidepanel/utils/timestampUtils.js new file mode 100644 index 0000000..ef7d9b3 --- /dev/null +++ b/sidepanel/utils/timestampUtils.js @@ -0,0 +1,64 @@ +/** + * 时间戳工具类函数 + */ + +/** + * 更新时间戳显示 + * @param {boolean} showMilliseconds - 是否显示毫秒 + */ +export function updateTimestamp(showMilliseconds = true) { + const currentTimestamp = document.getElementById('current-timestamp-value'); + const currentTimestampUnit = document.getElementById('current-timestamp-unit'); + const timestamp = Date.now(); + const unit = showMilliseconds ? '毫秒' : '秒'; + currentTimestamp.textContent = showMilliseconds ? timestamp : Math.floor(timestamp / 1000); + currentTimestampUnit.textContent = unit; +} + +/** + * 转换时间戳为日期格式 + * @param {string} timestamp - 输入的时间戳 + * @param {boolean} isSeconds - 是否为秒格式 + * @returns {string} 格式化后的日期字符串 + */ +export function convertTimestampToDate(timestamp, isSeconds) { + // 转换为数字 + let timestampNum = Number(timestamp); + + // 如果是秒,转换为毫秒 + if (isSeconds) { + timestampNum = timestampNum * 1000; + } + + // 检查是否为有效数字 + if (isNaN(timestampNum)) { + return '请输入有效的时间戳'; + } + + // 创建日期对象 + const date = new Date(timestampNum); + + // 检查日期是否有效 + if (isNaN(date.getTime())) { + return '无效的日期'; + } + + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + const minutes = date.getMinutes(); + const seconds = date.getSeconds(); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} + +/** + * 转换日期格式为时间戳 + * @param {string} date - 输入的日期字符串 + * @returns {number|string} 时间戳或错误信息 + */ +export function convertDateToTimestamp(date) { + const timestamp = Date.parse(date); + return isNaN(timestamp) ? '无效的日期' : timestamp; +}