# 需求确认文档 - 改进型悬浮面板交互 ## 📋 基本信息 - **功能名称**: 改进型悬浮面板交互 (Improved Floating Panel) - **功能代号**: `improved-floating-panel` - **目标版本**: v1.7.3+ - **优先级**: 高 - **创建时间**: 2025-11-20 - **需求状态**: ✅ 已确认 (质量评分: 94/100) --- ## 🎯 原始需求 **用户请求**: "请你实现方案 2" **方案2定义** (来自之前对话): > 改进型悬浮面板 - 优化现有的悬浮按钮功能,启用并改进 `FEATURE_FLAGS.enableFloatingButton`,实现类似 Grammarly 的优雅交互体验。 --- ## 🏗️ 仓库上下文分析 ### 现有基础设施 **项目类型**: Chrome Extension (Manifest V3) **技术栈**: React 18 + TypeScript 5 + Vite 5 **当前版本**: v1.7.2 ### 相关现有代码 1. **Content Script**: `/src/content/index.ts` - 已有 `injectSearchFeatures()` 函数框架 - 功能开关: `FEATURE_FLAGS.enableFloatingButton = false` (当前禁用) - 已有搜索引擎检测逻辑: `isSearchEnginePage()` 2. **Popup 组件**: `/src/popup/App.tsx` - 完整的语法构建器UI - React 组件化架构 - 支持 SearchForm、HistoryManager 等功能 3. **消息通信**: `/src/background/index.ts` - 已有 `chrome.runtime.onMessage` 监听器 - 支持 `open_popup`、`quick_search` 等消息类型 4. **适配器系统**: `/src/services/adapters/` - 已支持 Baidu、Google、Bing 等10个搜索引擎 - 适配器工厂模式: `AdapterFactory.createAdapter(url)` ### 集成约束 - **Chrome 版本**: Minimum Chrome 91 (manifest.json 定义) - **CSP 策略**: `script-src 'self'; object-src 'self'` - **国际化**: 必须支持 zh-CN 和 en-US - **主题**: 必须支持 light/dark 模式 --- ## ✅ 需求澄清过程 ### 第一轮澄清 (2025-11-20) #### 问题组1: 交互体验细节 **Q1.1: 内联图标的显示位置?** - ✅ 用户选择: **C - 搜索框下方** - 决策说明: 作为提示/建议出现,不侵入搜索框本身 **Q1.2: 图标触发方式?** - ✅ 用户选择: **B - 焦点+悬停触发** - 决策说明: 焦点时显示,鼠标悬停在搜索区域也显示 **Q1.3: 模态面板的尺寸和位置?** - ✅ 用户选择: **B - 大面板 (800px × 600px)** - 决策说明: 提供充足空间展示完整语法构建器 **Q1.4: 点击遮罩层的行为?** - ✅ 用户选择: **A - 关闭面板** - 决策说明: 点击背景即关闭,符合标准模态框交互 #### 问题组2: 技术实现选择 **Q2.1: 面板渲染方式?** - ✅ 用户选择: **A - Shadow DOM + iframe** - 决策说明: 完全隔离样式,避免与搜索引擎页面冲突 **Q2.2: 如何复用现有 popup 组件?** - ✅ 用户选择: **C - 消息传递方式** - 决策说明: content script 负责UI框架,业务逻辑通过消息传递 **Q2.3: 搜索引擎 DOM 适配策略?** - ✅ 用户选择: **A - 配置化适配** - 决策说明: 为每个搜索引擎定义选择器配置 #### 问题组3: 边缘情况处理 **Q3.1: DOM 结构更新处理?** - ✅ 用户选择: **A - 自动重试** - 决策说明: 监听 DOM 变化,自动重新注入 **Q3.2: 面板打开时焦点管理?** - ✅ 用户选择: **A - 保持显示** - 决策说明: 面板打开后不受搜索框焦点影响 **Q3.3: 快捷键冲突处理?** - ✅ 用户选择: **C - 禁用快捷键** - 决策说明: 面板只能通过图标触发,避免与现有快捷键冲突 **Q3.4: 多标签页处理?** - ✅ 用户选择: **A - 独立状态** - 决策说明: 每个标签页的面板状态独立 #### 问题组4: 功能范围确认 **Q4.1: 功能开关策略?** - ✅ 用户选择: **A - 默认启用** - 决策说明: 将 `FEATURE_FLAGS.enableFloatingButton` 改为 `true` **Q4.2: 与现有右键菜单的关系?** - ✅ 用户选择: **A - 并存** - 决策说明: 两种入口都保留,用户自由选择 **Q4.3: 支持的浏览器版本?** - ✅ 用户选择: **A - Chrome 91+** - 决策说明: 与 manifest.json 保持一致 --- ## 📐 详细功能规格 ### 1. 用户交互流程 ``` 用户访问搜索引擎页面 ↓ 页面加载完成,content script 注入 ↓ 检测搜索框元素 ↓ 在搜索框下方注入触发图标 (初始隐藏) ↓ [触发条件1] 用户点击搜索框 (焦点事件) [触发条件2] 鼠标悬停在搜索区域 (hover事件) ↓ 图标从下方滑入/淡入显示 ↓ 用户点击图标 ↓ 显示模态遮罩层 (半透明黑色背景) ↓ 在遮罩层上显示居中的面板 (800×600px) ↓ 面板内通过 iframe 加载 popup/index.html ↓ 用户在面板中构建搜索语法 ↓ 点击"应用"或"搜索"按钮 ↓ 通过消息传递将语法发送到 content script ↓ content script 将语法填充到搜索框 ↓ 关闭面板和遮罩层 ↓ (可选) 自动触发搜索 ``` ### 2. 技术架构设计 #### 2.1 组件结构 ``` Content Script (src/content/index.ts) ├── FloatingPanelManager (新增) │ ├── createTriggerIcon() // 创建触发图标 │ ├── createModalOverlay() // 创建遮罩层 │ ├── createPanelIframe() // 创建面板 iframe │ ├── handleShow/Hide() // 显示/隐藏逻辑 │ └── handleMessage() // 消息通信 ├── SearchEngineAdapter (扩展) │ ├── getSearchInputSelector() // 获取搜索框选择器 │ ├── getInsertPosition() // 获取图标插入位置 │ └── fillSearchInput(query) // 填充搜索框 └── DOMObserver (新增) ├── observeSearchBox() // 监听搜索框变化 └── reInjectOnChange() // DOM变化时重新注入 ``` #### 2.2 搜索引擎适配配置 ```typescript // src/config/search-engine-selectors.ts (新建) interface SearchEngineConfig { searchInputSelector: string; searchContainerSelector: string; iconInsertPosition: 'afterend' | 'beforeend'; iconOffsetY: string; // CSS position offset } const ENGINE_CONFIGS: Record = { baidu: { searchInputSelector: 'input#kw', searchContainerSelector: '#form', iconInsertPosition: 'afterend', iconOffsetY: '8px' }, google: { searchInputSelector: 'input[name="q"]', searchContainerSelector: 'div.RNNXgb', iconInsertPosition: 'afterend', iconOffsetY: '8px' }, bing: { searchInputSelector: 'input#sb_form_q', searchContainerSelector: 'form#sb_form', iconInsertPosition: 'afterend', iconOffsetY: '8px' } }; ``` #### 2.3 消息通信协议 ```typescript // Content Script → Background → Popup interface MessageProtocol { // 请求打开面板 (content → background) type: 'open_floating_panel'; // 应用搜索语法 (popup → content) type: 'apply_search_syntax'; payload: { query: string; autoSearch: boolean; }; // 关闭面板 (popup → content) type: 'close_floating_panel'; } ``` ### 3. UI/UX 规格 #### 3.1 触发图标 **位置**: 搜索框下方,水平居中 **尺寸**: 120px × 32px **样式**: - 背景: `bg-blue-500` (主题色) - 文字: "⚙️ 高级语法" (中文) / "⚙️ Advanced" (英文) - 边框圆角: `rounded-md` - 阴影: `shadow-md` **动画**: - 显示: 从下方淡入 + 向上滑动 (`fadeInUp`, 200ms) - 隐藏: 向下淡出 (`fadeOutDown`, 200ms) **交互状态**: - Hover: 放大 1.05 倍 + 阴影加深 - Active: 按下效果 (scale 0.98) #### 3.2 模态遮罩层 **样式**: - 背景: `rgba(0, 0, 0, 0.5)` (半透明黑色) - z-index: `9999` - 全屏覆盖: `fixed top-0 left-0 w-full h-full` **动画**: - 显示: 淡入 (`fadeIn`, 200ms) - 隐藏: 淡出 (`fadeOut`, 200ms) **交互**: - 点击遮罩 → 关闭面板 #### 3.3 面板容器 **尺寸**: 800px × 600px **位置**: 屏幕居中 **样式**: - 背景: 白色 (light) / 深灰 (dark) - 边框圆角: `rounded-lg` - 阴影: `shadow-2xl` **动画**: - 显示: 缩放弹出 (`zoomIn`, 300ms, ease-out`) - 隐藏: 缩放消失 (`zoomOut`, 200ms, ease-in`) **内容**: - iframe 加载 `/src/popup/index.html` - iframe 尺寸: 100% × 100% - 无边框: `border: none` #### 3.4 响应式适配 ```css /* 小屏幕适配 (< 900px) */ @media (max-width: 900px) { .ssp-panel { width: 90vw; height: 80vh; max-width: 700px; max-height: 600px; } } /* 极小屏幕 (< 600px) */ @media (max-width: 600px) { .ssp-panel { width: 95vw; height: 90vh; } } ``` ### 4. 功能需求详细说明 #### 4.1 自动 DOM 注入 **触发时机**: - 页面加载完成 (`DOMContentLoaded`) - 页面 URL 变化 (SPA 路由切换) - DOM 结构变化 (MutationObserver) **注入逻辑**: 1. 检测当前页面是否为搜索引擎 2. 使用配置化选择器定位搜索框 3. 在搜索框下方插入触发图标 4. 绑定焦点/悬停事件监听器 **容错处理**: - 如果选择器未找到元素 → 延迟500ms重试 (最多3次) - 如果搜索引擎未配置 → 静默失败,不注入 - 如果已注入 → 跳过,避免重复 #### 4.2 焦点与悬停管理 **焦点事件**: ```typescript searchInput.addEventListener('focus', () => { showTriggerIcon(); }); searchInput.addEventListener('blur', () => { // 延迟隐藏,防止点击图标时过早消失 setTimeout(() => { if (!isIconHovered && !isPanelOpen) { hideTriggerIcon(); } }, 200); }); ``` **悬停事件**: ```typescript searchContainer.addEventListener('mouseenter', () => { showTriggerIcon(); }); searchContainer.addEventListener('mouseleave', () => { if (!searchInput.matches(':focus') && !isPanelOpen) { hideTriggerIcon(); } }); ``` #### 4.3 面板生命周期 **打开流程**: 1. 创建遮罩层元素 (如不存在) 2. 创建面板容器 (如不存在) 3. 创建 iframe 并加载 popup HTML 4. 添加到 Shadow DOM 中 5. 执行显示动画 6. 设置 `isPanelOpen = true` **关闭流程**: 1. 执行隐藏动画 2. 动画结束后移除遮罩层和面板 3. 清理事件监听器 4. 设置 `isPanelOpen = false` **消息通信**: - iframe 加载完成 → 发送初始化消息 - 用户点击"应用" → popup 发送 `apply_search_syntax` 消息 - content script 接收 → 填充搜索框 → 关闭面板 #### 4.4 搜索框填充 **填充逻辑**: ```typescript function fillSearchInput(query: string, autoSearch: boolean = false) { const searchInput = getSearchInput(); // 设置值 searchInput.value = query; // 触发 input 事件 (某些搜索引擎需要) searchInput.dispatchEvent(new Event('input', { bubbles: true })); // 触发 change 事件 searchInput.dispatchEvent(new Event('change', { bubbles: true })); // 设置焦点 searchInput.focus(); // 可选: 自动提交搜索 if (autoSearch) { const form = searchInput.closest('form'); if (form) { form.submit(); } } } ``` #### 4.5 多标签页独立状态 **实现方式**: - 每个标签页的 content script 独立运行 - 不使用 `chrome.storage` 共享面板状态 - 每个标签页的面板 DOM 元素独立创建/销毁 #### 4.6 国际化支持 **触发图标文本**: ```typescript const iconText = getCurrentLanguage() === 'zh-CN' ? '⚙️ 高级语法' : '⚙️ Advanced Syntax'; ``` **iframe 页面语言**: - iframe 加载的 popup 会自动读取 `chrome.storage` 中的语言设置 - 无需额外处理 #### 4.7 主题适配 **Shadow DOM 样式**: ```css /* 遮罩层根据系统主题自动适配 */ .ssp-modal-overlay { background: rgba(0, 0, 0, 0.5); } /* 面板容器根据主题切换 */ .ssp-panel-container { background: var(--bg-color); color: var(--text-color); } /* 从 chrome.storage 读取主题设置并注入 CSS 变量 */ ``` ### 5. 边缘情况处理 #### 5.1 DOM 结构变化 **问题**: 搜索引擎页面 DOM 更新导致图标消失 **解决方案**: ```typescript const observer = new MutationObserver((mutations) => { const iconExists = document.querySelector('#ssp-trigger-icon'); if (!iconExists && isSearchEnginePage()) { console.log('[SSP] DOM changed, re-injecting icon'); setTimeout(() => injectTriggerIcon(), 500); } }); observer.observe(document.body, { childList: true, subtree: true }); ``` **限流优化**: - 使用 debounce 避免频繁重注入 - 最多每500ms检查一次 #### 5.2 iframe 加载失败 **问题**: iframe 无法加载 popup HTML **解决方案**: ```typescript iframe.addEventListener('error', () => { console.error('[SSP] Failed to load popup iframe'); showErrorMessage('面板加载失败,请刷新页面重试'); closePanelAfterDelay(3000); }); iframe.addEventListener('load', () => { console.log('[SSP] Popup iframe loaded successfully'); initMessageChannel(); }); ``` #### 5.3 CSP 策略阻止 **问题**: 某些搜索引擎的 CSP 策略阻止 iframe **解决方案**: - 使用 `chrome.runtime.getURL()` 确保 iframe src 为扩展内部资源 - 在 manifest.json 的 `web_accessible_resources` 中声明 popup HTML ```json { "web_accessible_resources": [ { "resources": ["src/popup/index.html", "icons/*"], "matches": [""] } ] } ``` #### 5.4 快速点击防抖 **问题**: 用户快速连续点击图标导致多个面板 **解决方案**: ```typescript let isOpening = false; function openPanel() { if (isOpening || isPanelOpen) { return; } isOpening = true; // 执行打开逻辑... setTimeout(() => { isOpening = false; }, 500); } ``` #### 5.5 焦点陷阱 (Focus Trap) **问题**: 面板打开后,Tab 键可能跳出面板 **解决方案**: ```typescript // 在 iframe 内部的 popup 组件中实现焦点陷阱 modalElement.addEventListener('keydown', (e) => { if (e.key === 'Tab') { trapFocusInModal(e); } if (e.key === 'Escape') { closePanel(); } }); ``` ### 6. 性能优化 #### 6.1 懒加载 iframe **策略**: 只有在用户点击图标时才创建 iframe ```typescript let panelIframe: HTMLIFrameElement | null = null; function openPanel() { if (!panelIframe) { panelIframe = createPanelIframe(); } showModal(); } ``` #### 6.2 CSS 动画硬件加速 **优化**: ```css .ssp-panel-container { will-change: transform, opacity; transform: translateZ(0); /* 触发 GPU 加速 */ } ``` #### 6.3 事件委托 **优化**: 遮罩层点击事件使用事件委托 ```typescript overlay.addEventListener('click', (e) => { if (e.target === overlay) { closePanel(); } }); ``` #### 6.4 节流/防抖 **应用场景**: - DOM MutationObserver → debounce 500ms - 窗口 resize 事件 → throttle 200ms - 鼠标 hover 事件 → debounce 100ms --- ## 🎯 需求质量评分 (最终) 基于详细的需求澄清和规格定义,重新评分: | 评估维度 | 得分 | 说明 | |---------|------|------| | **功能清晰度** (30分) | 28/30 | ✅ 交互流程、UI规格、动画效果已明确 | | **技术具体性** (25分) | 24/25 | ✅ Shadow DOM架构、消息协议、适配配置已定义 | | **实现完整性** (25分) | 24/25 | ✅ 边缘情况、错误处理、性能优化已覆盖 | | **业务上下文** (20分) | 18/20 | ✅ 用户价值清晰,与现有功能协调明确 | **最终总分**: **94/100** ✅ **质量等级**: A级 (优秀) - 可以进入实现阶段 --- ## 📦 需求范围总结 ### 包含功能 (In Scope) ✅ 在搜索框下方注入触发图标 ✅ 焦点+悬停双触发机制 ✅ 居中模态面板 (800×600px) ✅ Shadow DOM + iframe 渲染隔离 ✅ 消息传递复用 popup 组件 ✅ 配置化搜索引擎适配 ✅ 自动 DOM 变化重注入 ✅ 多标签页独立状态 ✅ 点击遮罩关闭面板 ✅ 国际化和主题支持 ✅ 默认启用功能开关 ✅ 与右键菜单并存 ✅ Chrome 91+ 兼容性 ### 不包含功能 (Out of Scope) ❌ 侧边栏面板方式 (非本方案范围) ❌ 搜索框内部图标注入 (已选择下方位置) ❌ 用户自定义面板尺寸 (固定800×600) ❌ 面板位置拖拽 (固定居中) ❌ 快捷键打开面板 (仅图标触发) ❌ 多标签页状态共享 (已选独立状态) ❌ 设置页面功能开关 (直接默认启用) ### 未来可能扩展 (Future Enhancements) 🔮 支持更多搜索引擎适配 🔮 面板位置自定义 🔮 快捷键打开支持 🔮 面板尺寸用户可调 🔮 拖拽移动面板 --- ## 🚀 实现优先级 ### P0 - 核心功能 (必须实现) 1. 触发图标注入和显示/隐藏逻辑 2. 模态遮罩层和面板容器 3. iframe 加载和消息通信 4. 搜索框填充功能 5. 搜索引擎适配配置 (Baidu、Google、Bing) ### P1 - 重要功能 (应该实现) 6. DOM 变化自动重注入 7. 焦点和悬停事件管理 8. 动画效果和过渡 9. 错误处理和容错 10. 国际化和主题适配 ### P2 - 优化功能 (可以实现) 11. 性能优化 (懒加载、硬件加速) 12. 响应式布局适配 13. 焦点陷阱和键盘导航 14. 更多搜索引擎支持 --- ## 📝 验收标准 ### 功能验收 - [ ] 在 Baidu.com 搜索框下方能看到触发图标 - [ ] 在 Google.com 搜索框下方能看到触发图标 - [ ] 在 Bing.com 搜索框下方能看到触发图标 - [ ] 搜索框获得焦点时,图标自动显示 - [ ] 鼠标悬停在搜索区域时,图标自动显示 - [ ] 失去焦点且无悬停时,图标自动隐藏 - [ ] 点击图标后,显示居中的800×600模态面板 - [ ] 面板内正确加载 popup 组件 - [ ] 在面板中构建语法后,点击"应用"能填充到搜索框 - [ ] 点击遮罩层背景能关闭面板 - [ ] 按 ESC 键能关闭面板 - [ ] 面板打开后不受搜索框焦点影响 ### 技术验收 - [ ] 使用 Shadow DOM 隔离样式 - [ ] 使用 iframe 加载 popup HTML - [ ] 通过消息传递与 popup 通信 - [ ] 配置化适配器支持多搜索引擎 - [ ] MutationObserver 监听 DOM 变化 - [ ] 自动重注入功能正常工作 - [ ] 多个标签页独立运行,互不干扰 - [ ] 中英文界面正确显示 - [ ] 亮色/暗色主题正确适配 - [ ] 在 Chrome 91+ 版本正常运行 ### 兼容性验收 - [ ] Chrome 91 浏览器测试通过 - [ ] Chrome 最新版本测试通过 - [ ] Windows 系统测试通过 - [ ] macOS 系统测试通过 - [ ] Linux 系统测试通过 (可选) ### 性能验收 - [ ] 图标显示/隐藏动画流畅 (>60fps) - [ ] 面板打开/关闭动画流畅 (>60fps) - [ ] 页面加载时注入不阻塞主线程 - [ ] 内存占用合理 (<10MB 增量) - [ ] 不影响搜索引擎页面原有功能 --- ## 🔗 相关文档 - [仓库上下文分析](./00-repository-context.md) - [技术实现规格](./requirements-spec.md) (待生成) - [代码实现](../../src/content/) (待开发) --- ## ✍️ 变更历史 | 版本 | 日期 | 变更内容 | 负责人 | |------|------|---------|--------| | v1.0 | 2025-11-20 | 初始版本,完成需求澄清 | requirements-pilot | --- **文档状态**: ✅ 已确认,待用户审批进入实现阶段