search-syntax-pro/.claude/specs/improved-floating-panel/requirements-confirm.md
Zain 0b39914071 feat(floating-panel): 新增悬浮按钮功能及高级搜索面板
实现搜索引擎页面的悬浮按钮功能,支持拖动调整位置和点击打开高级搜索面板。新增浮动面板组件,包含完整的搜索表单和交互逻辑。优化URL解码和搜索引擎选择器配置,提升用户体验。

- 新增浮动面板组件及样式
- 实现悬浮按钮的拖动和点击交互
- 添加URL安全解码工具函数
- 配置搜索引擎选择器和注入位置
- 优化多语言支持和响应式布局
- 添加性能测试和单元测试
2025-11-25 09:07:49 +08:00

18 KiB

需求确认文档 - 改进型悬浮面板交互

📋 基本信息

  • 功能名称: 改进型悬浮面板交互 (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 搜索引擎适配配置

// src/config/search-engine-selectors.ts (新建)
interface SearchEngineConfig {
  searchInputSelector: string;
  searchContainerSelector: string;
  iconInsertPosition: 'afterend' | 'beforeend';
  iconOffsetY: string; // CSS position offset
}

const ENGINE_CONFIGS: Record<string, SearchEngineConfig> = {
  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 消息通信协议

// 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 响应式适配

/* 小屏幕适配 (< 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 焦点与悬停管理

焦点事件:

searchInput.addEventListener('focus', () => {
  showTriggerIcon();
});

searchInput.addEventListener('blur', () => {
  // 延迟隐藏,防止点击图标时过早消失
  setTimeout(() => {
    if (!isIconHovered && !isPanelOpen) {
      hideTriggerIcon();
    }
  }, 200);
});

悬停事件:

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 搜索框填充

填充逻辑:

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 国际化支持

触发图标文本:

const iconText = getCurrentLanguage() === 'zh-CN'
  ? '⚙️ 高级语法'
  : '⚙️ Advanced Syntax';

iframe 页面语言:

  • iframe 加载的 popup 会自动读取 chrome.storage 中的语言设置
  • 无需额外处理

4.7 主题适配

Shadow DOM 样式:

/* 遮罩层根据系统主题自动适配 */
.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 更新导致图标消失

解决方案:

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

解决方案:

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
{
  "web_accessible_resources": [
    {
      "resources": ["src/popup/index.html", "icons/*"],
      "matches": ["<all_urls>"]
    }
  ]
}

5.4 快速点击防抖

问题: 用户快速连续点击图标导致多个面板

解决方案:

let isOpening = false;

function openPanel() {
  if (isOpening || isPanelOpen) {
    return;
  }

  isOpening = true;

  // 执行打开逻辑...

  setTimeout(() => {
    isOpening = false;
  }, 500);
}

5.5 焦点陷阱 (Focus Trap)

问题: 面板打开后,Tab 键可能跳出面板

解决方案:

// 在 iframe 内部的 popup 组件中实现焦点陷阱
modalElement.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    trapFocusInModal(e);
  }

  if (e.key === 'Escape') {
    closePanel();
  }
});

6. 性能优化

6.1 懒加载 iframe

策略: 只有在用户点击图标时才创建 iframe

let panelIframe: HTMLIFrameElement | null = null;

function openPanel() {
  if (!panelIframe) {
    panelIframe = createPanelIframe();
  }

  showModal();
}

6.2 CSS 动画硬件加速

优化:

.ssp-panel-container {
  will-change: transform, opacity;
  transform: translateZ(0); /* 触发 GPU 加速 */
}

6.3 事件委托

优化: 遮罩层点击事件使用事件委托

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 - 重要功能 (应该实现)

  1. DOM 变化自动重注入
  2. 焦点和悬停事件管理
  3. 动画效果和过渡
  4. 错误处理和容错
  5. 国际化和主题适配

P2 - 优化功能 (可以实现)

  1. 性能优化 (懒加载、硬件加速)
  2. 响应式布局适配
  3. 焦点陷阱和键盘导航
  4. 更多搜索引擎支持

📝 验收标准

功能验收

  • 在 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 增量)
  • 不影响搜索引擎页面原有功能

🔗 相关文档


✍️ 变更历史

版本 日期 变更内容 负责人
v1.0 2025-11-20 初始版本,完成需求澄清 requirements-pilot

文档状态: ✅ 已确认,待用户审批进入实现阶段