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

756 行
18 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 需求确认文档 - 改进型悬浮面板交互
## 📋 基本信息
- **功能名称**: 改进型悬浮面板交互 (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<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 消息通信协议
```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": ["<all_urls>"]
}
]
}
```
#### 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 |
---
**文档状态**: ✅ 已确认,待用户审批进入实现阶段