diff --git a/entrypoints/background.ts b/entrypoints/background.ts index 407cb90..c835720 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -41,6 +41,8 @@ export default defineBackground(() => { await browser.action.openPopup(); } catch (err) { console.error('[Context Menu] 打开 popup 失败:', err); + // 打开失败时清除残留数据,避免下次打开 popup 时误触发 + await chrome.storage.local.remove('contextMenu/pendingData'); } }); diff --git a/pages/Timestamp/useTimestampConverter.ts b/pages/Timestamp/useTimestampConverter.ts index b7fa2fb..4b378a1 100644 --- a/pages/Timestamp/useTimestampConverter.ts +++ b/pages/Timestamp/useTimestampConverter.ts @@ -74,41 +74,44 @@ export function useTimestampConverter(): UseTimestampConverterReturn { }, [mode, tsInput, dtInput, unit, zone, t]); // 处理右键菜单传递的数据 - const handleContextMenuData = useCallback((payload: string) => { - const trimmed = payload.trim(); - if (isTimestampLike(trimmed)) { - // 看起来是时间戳,切换到 ts2dt 模式 - setMode('ts2dt'); - setTsInput(trimmed); - // 如果是 13 位毫秒级时间戳,自动选择 ms 单位 - const detectedUnit: UnitType = trimmed.length >= 13 ? 'ms' : 's'; - setUnit(detectedUnit); - // 直接执行转换 - const num = Number(trimmed); - if (!isNaN(num)) { - const d = detectedUnit === 'ms' ? dayjs(num) : dayjs.unix(num); - if (d.isValid()) { - setError(''); - setResult(d.tz('Asia/Shanghai').format(DATE_FORMAT)); - } - } - } else { - // 尝试作为日期时间解析 - const d = dayjs(trimmed); - if (d.isValid()) { - setMode('dt2ts'); - setDtInput(d.format(DATE_FORMAT)); - // 直接执行转换 - const ms = d.valueOf(); - setError(''); - setResult(String(ms)); - } else { - // 无法识别,作为时间戳处理 + const handleContextMenuData = useCallback( + (payload: string) => { + const trimmed = payload.trim(); + if (isTimestampLike(trimmed)) { + // 看起来是时间戳,切换到 ts2dt 模式 setMode('ts2dt'); setTsInput(trimmed); + // 如果是 13 位毫秒级时间戳,自动选择 ms 单位 + const detectedUnit: UnitType = trimmed.length >= 13 ? 'ms' : 's'; + setUnit(detectedUnit); + // 直接执行转换 + const num = Number(trimmed); + if (!isNaN(num)) { + const d = detectedUnit === 'ms' ? dayjs(num) : dayjs.unix(num); + if (d.isValid()) { + setError(''); + setResult(d.tz(zone).format(DATE_FORMAT)); + } + } + } else { + // 尝试作为日期时间解析 + const d = dayjs(trimmed); + if (d.isValid()) { + setMode('dt2ts'); + setDtInput(d.format(DATE_FORMAT)); + // 直接执行转换 + const ms = d.valueOf(); + setError(''); + setResult(String(ms)); + } else { + // 无法识别,作为时间戳处理 + setMode('ts2dt'); + setTsInput(trimmed); + } } - } - }, []); + }, + [zone], + ); useContextMenuData({ featureKey: 'timestamp', onData: handleContextMenuData }); diff --git a/providers/RouterProvider.tsx b/providers/RouterProvider.tsx index 8e6c718..10017d2 100644 --- a/providers/RouterProvider.tsx +++ b/providers/RouterProvider.tsx @@ -14,7 +14,7 @@ import { getDefaultPageOrder, getDefaultVisibleFeatureKeys, } from '@/config/features'; -import { saveContextMenuData } from '@/utils/useContextMenuData'; +import { saveContextMenuData, CONTEXT_MENU_DATA_EXPIRY_MS } from '@/utils/useContextMenuData'; /** * 校验是否为合法的页面类型 @@ -215,7 +215,7 @@ export function RouterProvider({ if ( pendingData && isValidPage(pendingData.featureKey) && - Date.now() - pendingData.timestamp < 5000 + Date.now() - pendingData.timestamp < CONTEXT_MENU_DATA_EXPIRY_MS ) { navigateTo(pendingData.featureKey as PageType); // 不在这里清除数据,让目标页面的 useContextMenuData 来消费和清除 @@ -292,7 +292,11 @@ export function RouterProvider({ if (changes['contextMenu/pendingData']) { const newData = changes['contextMenu/pendingData'] .newValue as ContextMenuPendingData | null; - if (newData && isValidPage(newData.featureKey) && Date.now() - newData.timestamp < 5000) { + if ( + newData && + isValidPage(newData.featureKey) && + Date.now() - newData.timestamp < CONTEXT_MENU_DATA_EXPIRY_MS + ) { setCurrentPage(newData.featureKey as PageType); } } diff --git a/utils/contextMenu.ts b/utils/contextMenu.ts index dc56b9a..5452a9f 100644 --- a/utils/contextMenu.ts +++ b/utils/contextMenu.ts @@ -22,6 +22,19 @@ const PARENT_MENU_ID = 'testing-tools-parent'; export const MAX_PAYLOAD_LENGTH = 10000; +/** 菜单项 ID 到 PageType 的映射(仅处理非常规映射) */ +const MENU_ID_TO_PAGE_TYPE: Record = { + 'qrCode-page': 'qrCode', +}; + +/** + * 将菜单项 ID 转换为 PageType + * 如果存在显式映射则使用映射,否则直接使用 menuItemId + */ +function getMenuPageType(menuItemId: string): PageType { + return MENU_ID_TO_PAGE_TYPE[menuItemId] ?? (menuItemId as PageType); +} + export const CONTEXT_MENU_CONFIGS: ContextMenuItemConfig[] = [ { id: PARENT_MENU_ID, @@ -81,14 +94,7 @@ export function parseContextMenuClick( menuItemId: string, info: chrome.contextMenus.OnClickData, ): ParseResult { - const featureKey = menuItemId as PageType; - - if (menuItemId === 'qrCode-page') { - return { - success: true, - data: { featureKey: 'qrCode', payload: info.pageUrl || '' }, - }; - } + const featureKey = getMenuPageType(menuItemId); if (info.selectionText) { const text = info.selectionText; diff --git a/utils/useContextMenuData.ts b/utils/useContextMenuData.ts index cdc78cd..71d353e 100644 --- a/utils/useContextMenuData.ts +++ b/utils/useContextMenuData.ts @@ -3,7 +3,9 @@ import { storageUtil } from '@/utils/chromeStorage'; import type { ContextMenuPendingData, PageType } from '@/types/storage'; const STORAGE_KEY = 'contextMenu/pendingData' as const; -const DATA_EXPIRY_MS = 5000; + +/** 右键菜单数据过期时间(毫秒) */ +export const CONTEXT_MENU_DATA_EXPIRY_MS = 5000; export interface UseContextMenuDataOptions { /** 当前页面的功能标识 */ @@ -29,7 +31,7 @@ export function useContextMenuData({ featureKey, onData }: UseContextMenuDataOpt if (data.featureKey !== featureKey) return; - if (Date.now() - data.timestamp > DATA_EXPIRY_MS) { + if (Date.now() - data.timestamp > CONTEXT_MENU_DATA_EXPIRY_MS) { await storageUtil.remove(STORAGE_KEY); return; }