diff --git a/docs/M2.md b/docs/M2.md index fdd2e1a..d282cc9 100644 --- a/docs/M2.md +++ b/docs/M2.md @@ -69,6 +69,7 @@ - [ ] 如果右侧超出视口,显示在左侧 - [ ] 如果下方超出视口,显示在上方 - [ ] 面板边界与视口保留 10px 边距 +- [ ] 可以通过拖拽面板头部更改面板位置 ### M2.8 图标-面板联动 [目标版本: 0.1.8] **任务**: 点击图标显示面板 diff --git a/docs/QUICK_REF.md b/docs/QUICK_REF.md index c6e3114..fc595f1 100644 --- a/docs/QUICK_REF.md +++ b/docs/QUICK_REF.md @@ -7,7 +7,7 @@ ## 版本速查 ### 当前版本 -`0.1.6` → 下一目标 `0.1.7` ([M2.7](./M2.md)) +`0.1.7` → 下一目标 `0.1.8` ([M2.8](./M2.md)) ### 模块版本范围 diff --git a/docs/README.md b/docs/README.md index 5934bad..45aec7a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,9 +65,9 @@ M11.10完成 → 1.0.0 (正式发布) ## 当前状态 -**当前版本**: `0.1.6` -**当前进度**: 11/97 (11%) -**下一任务**: [M2.7 面板位置计算](./M2.md#m27-面板位置计算--目标版本-0117) +**当前版本**: `0.1.7` +**当前进度**: 12/97 (12%) +**下一任务**: [M2.8 图标-面板联动](./M2.md#m28-图标-面板联动--目标版本-0118) --- @@ -105,8 +105,9 @@ M11.10完成 → 1.0.0 (正式发布) 1. **阅读规范**: 开始前阅读 [AGENTS.md](./AGENTS.md) 和 [QUICK_REF.md](./QUICK_REF.md) 2. **查看模块**: 根据当前版本打开对应的模块文件(如 [M1.md](./M1.md)) -3. **开发实现**: 按任务顺序实现,每个任务完成更新版本号 -4. **提交验收**: 用户确认后更新 VERSION.md 并提交 commit +3. **开发实现**: 按任务顺序实现需求 +4. **更新文档**:任务完成后更新`docs/README.md`的`当前状态`小节,更新`docs/VERSION.md`中对应人物的`状态`和`日期`,更新`docs/QUICK_REF.md`的`当前版本`小节,更新`package.json`和`manifest.json`的版本号 +5. **提交验收**: 用户确认后提交代码 ### Commit 格式 ```bash diff --git a/docs/VERSION.md b/docs/VERSION.md index 8634369..88b2d30 100644 --- a/docs/VERSION.md +++ b/docs/VERSION.md @@ -4,15 +4,6 @@ --- -## 当前版本 - -**版本**: `0.0.0` -**状态**: 🟡 初始状态 -**进度**: 0/97 (0%) -**下一目标**: [M1.1 项目初始化](./M1.md#m11) - ---- - ## M1 基础架构 (0.0.1 - 0.0.5) | 任务 | 版本 | 描述 | 状态 | 日期 | @@ -35,7 +26,7 @@ | M2.4 | 0.1.4 | 图标定位显示 | ✅ | 2026-02-10 | | M2.5 | 0.1.5 | 图标点击事件 | ✅ | 2026-02-10 | | M2.6 | 0.1.6 | 基础面板组件 | ✅ | 2026-02-11 | -| M2.7 | 0.1.7 | 面板位置计算 | ⬜ | - | +| M2.7 | 0.1.7 | 面板位置计算 | ✅ | 2026-02-09 | | M2.8 | 0.1.8 | 图标-面板联动 | ⬜ | - | | M2.9 | 0.1.9 | 图标显示开关 | ⬜ | - | @@ -185,11 +176,3 @@ | M11.9 | 0.10.9 | 打包配置 | ⬜ | - | | M11.10 | 1.0.0 | 图标和素材 | ⬜ | - | ---- - -## 统计 - -- **总计**: 97 个任务 -- **已完成**: 0 -- **剩余**: 97 -- **当前进度**: 0% diff --git a/manifest.json b/manifest.json index cd7e282..6434778 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "沙拉查词", - "version": "0.1.6", + "version": "0.1.7", "description": "聚合词典划词翻译", "permissions": [ "storage", diff --git a/package.json b/package.json index 8057042..855ae7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salad-dict", - "version": "0.1.6", + "version": "0.1.7", "description": "聚合词典划词翻译", "private": true, "type": "module", diff --git a/src/content/components/DictPanel.js b/src/content/components/DictPanel.js index 8fb8302..9e7d9e6 100644 --- a/src/content/components/DictPanel.js +++ b/src/content/components/DictPanel.js @@ -5,6 +5,7 @@ const PANEL_WIDTH = 400; const PANEL_HEIGHT = 300; +const VIEWPORT_MARGIN = 10; /** * 词典结果面板类 @@ -12,6 +13,9 @@ const PANEL_HEIGHT = 300; export class DictPanel { constructor() { this.element = this.createElement(); + this.isDragging = false; + this.dragOffset = { x: 0, y: 0 }; + this.setupDragHandlers(); } /** @@ -54,6 +58,12 @@ export class DictPanel { font-size: 16px; font-weight: 500; border-radius: 8px 8px 0 0; + cursor: move; + user-select: none; + } + + .header:hover { + background-color: #45a049; } .content { @@ -88,13 +98,111 @@ export class DictPanel { } /** - * 显示面板在指定位置 + * 设置拖拽事件处理 + */ + setupDragHandlers() { + const header = this.element.shadowRoot.querySelector('.header'); + + // 鼠标按下开始拖拽 + header.addEventListener('mousedown', (e) => { + this.isDragging = true; + this.dragOffset.x = e.clientX - this.element.offsetLeft; + this.dragOffset.y = e.clientY - this.element.offsetTop; + + // 阻止默认行为和冒泡 + e.preventDefault(); + e.stopPropagation(); + + console.log('[SaladDict] Panel drag start'); + }); + + // 鼠标移动时更新位置 + const handleMouseMove = (e) => { + if (!this.isDragging) return; + + let newX = e.clientX - this.dragOffset.x; + let newY = e.clientY - this.dragOffset.y; + + // 限制不超出视口 + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + newX = Math.max(VIEWPORT_MARGIN, Math.min(newX, viewportWidth - PANEL_WIDTH - VIEWPORT_MARGIN)); + newY = Math.max(VIEWPORT_MARGIN, Math.min(newY, viewportHeight - PANEL_HEIGHT - VIEWPORT_MARGIN)); + + this.element.style.left = `${newX}px`; + this.element.style.top = `${newY}px`; + }; + + // 鼠标抬起结束拖拽 + const handleMouseUp = () => { + if (this.isDragging) { + this.isDragging = false; + console.log('[SaladDict] Panel drag end'); + } + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + // 保存引用以便销毁时移除 + this._dragHandlers = { handleMouseMove, handleMouseUp }; + } + + /** + * 计算面板位置,确保不超出视口 + * @param {number} x - 目标X坐标(图标右下方) + * @param {number} y - 目标Y坐标 + * @returns {Object} {x, y} 调整后的坐标 + */ + calculatePosition(x, y) { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // 默认显示在图标右下方 + let panelX = x; + let panelY = y; + + // 检查右侧是否超出视口 + if (panelX + PANEL_WIDTH + VIEWPORT_MARGIN > viewportWidth) { + // 显示在左侧 + panelX = x - PANEL_WIDTH; + } + + // 检查下方是否超出视口 + if (panelY + PANEL_HEIGHT + VIEWPORT_MARGIN > viewportHeight) { + // 显示在上方 + panelY = y - PANEL_HEIGHT; + } + + // 确保不超出左边界 + panelX = Math.max(VIEWPORT_MARGIN, panelX); + + // 确保不超出上边界 + panelY = Math.max(VIEWPORT_MARGIN, panelY); + + // 确保不超出右边界(如果从左侧显示仍超出) + if (panelX + PANEL_WIDTH > viewportWidth - VIEWPORT_MARGIN) { + panelX = viewportWidth - PANEL_WIDTH - VIEWPORT_MARGIN; + } + + // 确保不超出下边界(如果从上方显示仍超出) + if (panelY + PANEL_HEIGHT > viewportHeight - VIEWPORT_MARGIN) { + panelY = viewportHeight - PANEL_HEIGHT - VIEWPORT_MARGIN; + } + + return { x: panelX, y: panelY }; + } + + /** + * 显示面板在指定位置(自动调整不超出视口) * @param {number} x - X坐标 * @param {number} y - Y坐标 */ show(x, y) { - this.element.style.left = `${x}px`; - this.element.style.top = `${y}px`; + const position = this.calculatePosition(x, y); + this.element.style.left = `${position.x}px`; + this.element.style.top = `${position.y}px`; this.element.style.display = 'block'; } @@ -110,6 +218,13 @@ export class DictPanel { */ destroy() { this.hide(); + + // 移除拖拽事件监听 + if (this._dragHandlers) { + document.removeEventListener('mousemove', this._dragHandlers.handleMouseMove); + document.removeEventListener('mouseup', this._dragHandlers.handleMouseUp); + } + if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } diff --git a/src/content/selection.js b/src/content/selection.js index 98c53d8..6ece737 100644 --- a/src/content/selection.js +++ b/src/content/selection.js @@ -147,10 +147,12 @@ function showSaladIcon(x, y) { currentIcon = null; } - // 显示面板(位置暂时写死 0,0,后续 M2.7 完善) + // 显示面板在图标右下方 + const panelX = x + 24; // 图标宽度 + const panelY = y + 24; // 图标高度 currentPanel = new DictPanel(); - currentPanel.show(0, 0); - console.log('[SaladDict] Panel shown at (0, 0)'); + currentPanel.show(panelX, panelY); + console.log('[SaladDict] Panel shown at:', panelX, panelY); } });