diff --git a/extension/entrypoints/panel.content.ts b/extension/entrypoints/panel.content.ts index 03b7074..5fde150 100644 --- a/extension/entrypoints/panel.content.ts +++ b/extension/entrypoints/panel.content.ts @@ -33,6 +33,32 @@ const STYLES = ` .fab:hover { transform: scale(1.08); } .fab.hidden { display: none; } + /* 收起按钮:骑在面板左上边缘(一半在面板外)。必须在 iframe 外渲染—— + iframe 裁剪内容,iframe 内的元素永远溢不出面板边界 */ + .panel-close { + position: fixed; + top: 20px; + right: min(880px, 100vw); /* 面板左边缘 = 视口右沿 - 面板宽度 */ + transform: translateX(50%); + width: 28px; + height: 28px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + color: #555; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18); + z-index: 4; + opacity: 0; + pointer-events: none; + transition: opacity 0.25s ease, transform 0.15s ease; + } + .panel-close.visible { opacity: 1; pointer-events: auto; } + .panel-close:hover { color: #6d28d9; border-color: #6d28d9; transform: translateX(50%) scale(1.08); } + .panel { position: fixed; top: 0; @@ -92,23 +118,34 @@ export default defineContentScript({ const iframe = document.createElement('iframe'); iframe.title = '电商套图工作台'; panel.append(iframe); - container.append(fab, panel); + + // 外置收起按钮(骑在面板左上边缘,一半在面板外) + const closeBtn = document.createElement('button'); + closeBtn.className = 'panel-close'; + closeBtn.title = '收起面板(Esc)'; + closeBtn.innerHTML = + ''; + container.append(fab, panel, closeBtn); const open = () => { if (!iframe.src) iframe.src = chrome.runtime.getURL('/sidepanel.html'); panel.classList.add('open'); + closeBtn.classList.add('visible'); fab.classList.add('hidden'); // 聚焦进面板,键盘操作(Esc 关闭 / 预览翻页)直接可用 iframe.focus(); }; const close = () => { panel.classList.remove('open'); + closeBtn.classList.remove('visible'); if (isProductPage()) fab.classList.remove('hidden'); }; fab.addEventListener('click', open); + closeBtn.addEventListener('click', close); - // 面板内 App(✕ / Esc)→ 收起 + // 面板内 App(Esc)→ 收起;✕ 按钮已外置到宿主层(closeBtn) window.addEventListener('message', (e) => { if (e.source === iframe.contentWindow && (e.data as any)?.type === PANEL_CLOSE_MSG) close(); }); diff --git a/extension/entrypoints/sidepanel/App.tsx b/extension/entrypoints/sidepanel/App.tsx index e0bc1b4..da6ee6e 100644 --- a/extension/entrypoints/sidepanel/App.tsx +++ b/extension/entrypoints/sidepanel/App.tsx @@ -11,7 +11,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { App as AntApp, ConfigProvider, Popover, Progress, Select } from 'antd'; -import { SettingOutlined, DownloadOutlined, ThunderboltOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons'; +import { SettingOutlined, DownloadOutlined, ThunderboltOutlined, UploadOutlined } from '@ant-design/icons'; import type { ScanResult, ImageMaterial } from '../../src/collector/scan'; import { cleanFilename } from '../../src/collector/url'; import { @@ -671,11 +671,9 @@ const App: React.FC = () => { return (