feat(M2.6): 基础面板组件 (v0.1.6)

- 创建 src/content/components/DictPanel.js
- 面板尺寸 400x300px,白色背景,带阴影
- 使用 Shadow DOM 封装样式
- 点击图标显示面板,图标消失
- 点击页面空白处面板消失
This commit is contained in:
李岩岩 2026-02-11 10:03:39 +08:00
parent 4e12552437
commit 20dd127fa6
7 changed files with 178 additions and 16 deletions

View File

@ -7,7 +7,7 @@
## 版本速查 ## 版本速查
### 当前版本 ### 当前版本
`0.1.5` → 下一目标 `0.1.6` ([M2.6](./M2.md)) `0.1.6` → 下一目标 `0.1.7` ([M2.7](./M2.md))
### 模块版本范围 ### 模块版本范围

View File

@ -65,9 +65,9 @@ M11.10完成 → 1.0.0 (正式发布)
## 当前状态 ## 当前状态
**当前版本**: `0.1.5` **当前版本**: `0.1.6`
**当前进度**: 10/97 (10%) **当前进度**: 11/97 (11%)
**下一任务**: [M2.6 基础面板组件](./M2.md#m26-基础面板组件--目标版本-0116) **下一任务**: [M2.7 面板位置计算](./M2.md#m27-面板位置计算--目标版本-0117)
--- ---

View File

@ -29,12 +29,12 @@
| 任务 | 版本 | 描述 | 状态 | 日期 | | 任务 | 版本 | 描述 | 状态 | 日期 |
|------|------|------|------|------| |------|------|------|------|------|
| M2.1 | 0.1.1 | 文本选择检测 | ✅ | 2026-02-09 | | M2.1 | 0.1.1 | 文本选择检测 | ✅ | 2026-02-10 |
| M2.2 | 0.1.2 | 获取选中文本坐标 | ✅ | 2026-02-09 | | M2.2 | 0.1.2 | 获取选中文本坐标 | ✅ | 2026-02-10 |
| M2.3 | 0.1.3 | 沙拉图标组件 | ✅ | 2026-02-09 | | M2.3 | 0.1.3 | 沙拉图标组件 | ✅ | 2026-02-10 |
| M2.4 | 0.1.4 | 图标定位显示 | ✅ | 2026-02-09 | | M2.4 | 0.1.4 | 图标定位显示 | ✅ | 2026-02-10 |
| M2.5 | 0.1.5 | 图标点击事件 | ✅ | 2026-02-09 | | M2.5 | 0.1.5 | 图标点击事件 | ✅ | 2026-02-10 |
| M2.6 | 0.1.6 | 基础面板组件 | ⬜ | - | | M2.6 | 0.1.6 | 基础面板组件 | ✅ | 2026-02-11 |
| M2.7 | 0.1.7 | 面板位置计算 | ⬜ | - | | M2.7 | 0.1.7 | 面板位置计算 | ⬜ | - |
| M2.8 | 0.1.8 | 图标-面板联动 | ⬜ | - | | M2.8 | 0.1.8 | 图标-面板联动 | ⬜ | - |
| M2.9 | 0.1.9 | 图标显示开关 | ⬜ | - | | M2.9 | 0.1.9 | 图标显示开关 | ⬜ | - |

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "沙拉查词", "name": "沙拉查词",
"version": "0.1.5", "version": "0.1.6",
"description": "聚合词典划词翻译", "description": "聚合词典划词翻译",
"permissions": [ "permissions": [
"storage", "storage",

View File

@ -1,6 +1,6 @@
{ {
"name": "salad-dict", "name": "salad-dict",
"version": "0.1.5", "version": "0.1.6",
"description": "聚合词典划词翻译", "description": "聚合词典划词翻译",
"private": true, "private": true,
"type": "module", "type": "module",

View File

@ -0,0 +1,129 @@
/**
* @file 词典结果展示面板
* @description 显示词典查询结果的基础面板组件
*/
const PANEL_WIDTH = 400;
const PANEL_HEIGHT = 300;
/**
* 词典结果面板类
*/
export class DictPanel {
constructor() {
this.element = this.createElement();
}
/**
* 创建面板元素
* @returns {HTMLElement} 面板元素
*/
createElement() {
// 创建容器
const container = document.createElement('div');
container.style.cssText = `
position: fixed;
z-index: 2147483646;
display: none;
width: ${PANEL_WIDTH}px;
height: ${PANEL_HEIGHT}px;
`;
// 创建 Shadow DOM
const shadow = container.attachShadow({ mode: 'open' });
// Shadow DOM 内容
shadow.innerHTML = `
<style>
.panel {
width: 100%;
height: 100%;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.header {
padding: 12px 16px;
background-color: #4CAF50;
color: white;
font-size: 16px;
font-weight: 500;
border-radius: 8px 8px 0 0;
}
.content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.placeholder {
color: #999;
text-align: center;
margin-top: 100px;
}
</style>
<div class="panel">
<div class="header">词典结果</div>
<div class="content">
<div class="placeholder">查询结果将显示在这里</div>
</div>
</div>
`;
// 阻止点击事件冒泡
container.addEventListener('click', (e) => {
e.stopPropagation();
});
// 添加到页面
document.body.appendChild(container);
return container;
}
/**
* 显示面板在指定位置
* @param {number} x - X坐标
* @param {number} y - Y坐标
*/
show(x, y) {
this.element.style.left = `${x}px`;
this.element.style.top = `${y}px`;
this.element.style.display = 'block';
}
/**
* 隐藏面板
*/
hide() {
this.element.style.display = 'none';
}
/**
* 销毁组件
*/
destroy() {
this.hide();
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
}
}
/**
* 创建并显示词典面板
* @param {number} x - X坐标
* @param {number} y - Y坐标
* @returns {DictPanel} 面板实例
*/
export function createDictPanel(x, y) {
const panel = new DictPanel();
panel.show(x, y);
return panel;
}

View File

@ -5,8 +5,10 @@
import { logger } from './logger.js'; import { logger } from './logger.js';
import { createSaladIcon } from './components/SaladIcon.js'; import { createSaladIcon } from './components/SaladIcon.js';
import { DictPanel } from './components/DictPanel.js';
let currentIcon = null; let currentIcon = null;
let currentPanel = null;
/** /**
* 获取当前选中的文本 * 获取当前选中的文本
@ -83,6 +85,16 @@ export function hasSelection() {
* @param {MouseEvent} event * @param {MouseEvent} event
*/ */
function handleMouseUp(event) { function handleMouseUp(event) {
// 如果点击的是当前图标元素,则直接返回
if (currentIcon && (currentIcon.element == event.target || currentIcon.element.contains(event.target))) {
return;
}
// 如果点击的是当前面板元素,则直接返回
if (currentPanel && (currentPanel.element == event.target || currentPanel.element.contains(event.target))) {
return;
}
// 延迟执行,等待选区完成 // 延迟执行,等待选区完成
setTimeout(() => { setTimeout(() => {
const selectedText = getSelectedText(); const selectedText = getSelectedText();
@ -114,17 +126,31 @@ function handleMouseUp(event) {
* @param {number} y - Y坐标 * @param {number} y - Y坐标
*/ */
function showSaladIcon(x, y) { function showSaladIcon(x, y) {
// 隐藏旧图标 // 隐藏旧图标和面板
if (currentIcon) { if (currentIcon) {
currentIcon.destroy(); currentIcon.destroy();
currentIcon = null; currentIcon = null;
} }
if (currentPanel) {
currentPanel.destroy();
currentPanel = null;
}
// 创建新图标,传入点击回调 // 创建新图标,传入点击回调
currentIcon = createSaladIcon(x, y, { currentIcon = createSaladIcon(x, y, {
onClick: (event) => { onClick: (event) => {
logger.info('Icon click callback triggered'); logger.info('Icon clicked, showing panel');
// 点击后图标不消失(后续再处理)
// 图标消失
if (currentIcon) {
currentIcon.destroy();
currentIcon = null;
}
// 显示面板(位置暂时写死 0,0后续 M2.7 完善)
currentPanel = new DictPanel();
currentPanel.show(0, 0);
console.log('[SaladDict] Panel shown at (0, 0)');
} }
}); });
@ -132,7 +158,7 @@ function showSaladIcon(x, y) {
} }
/** /**
* 处理页面点击事件用于隐藏图标 * 处理页面点击事件用于隐藏图标和面板
* @param {MouseEvent} event * @param {MouseEvent} event
*/ */
function handleDocumentClick(event) { function handleDocumentClick(event) {
@ -142,6 +168,13 @@ function handleDocumentClick(event) {
currentIcon = null; currentIcon = null;
logger.info('Icon hidden by document click'); logger.info('Icon hidden by document click');
} }
// 如果点击的不是面板元素,则隐藏面板
if (currentPanel && currentPanel.element !== event.target && !currentPanel.element.contains(event.target)) {
currentPanel.destroy();
currentPanel = null;
logger.info('Panel hidden by document click');
}
} }
/** /**