李岩岩 3c363a14b0 feat(M2.8): 图标-面板联动 (v0.1.8)
- 点击图标显示面板,图标保持可见
- 再次点击图标可切换面板显示/隐藏
- 点击面板外部区域关闭面板
- 按 ESC 键关闭面板
- 优化事件监听:show时绑定,hide时解绑
2026-02-11 11:27:42 +08:00

4.8 KiB
Raw Blame History

快速参考

版本速查、命名规范、代码模板


版本速查

当前版本

0.1.8 → 下一目标 0.1.9 (M2.9)

模块版本范围

模块 范围 任务数
M1 0.0.1 ~ 0.0.5 5
M2 0.1.1 ~ 0.1.9 9
M3 0.2.1 ~ 0.2.11 11
M4 0.3.1 ~ 0.3.8 8
M5 0.4.1 ~ 0.4.16 16
M6 0.5.1 ~ 0.5.7 7
M7 0.6.1 ~ 0.6.6 6
M8 0.7.1 ~ 0.7.10 10
M9 0.8.1 ~ 0.8.7 7
M10 0.9.1 ~ 0.9.8 8
M11 0.10.1 ~ 1.0.0 10

版本更新

# M1.1 完成后
版本: 0.0.0 → 0.0.1
git commit -m "feat(M1.1): 项目初始化 (v0.0.1)"

# M2.1 完成后 (进入M2)
版本: 0.0.5 → 0.1.1
git commit -m "feat(M2.1): 文本选择检测 (v0.1.1)"

命名速查

类型 规范 示例
文件 小写+连字符 salad-icon.js
PascalCase DictionaryBase
函数 camelCase+动词 getSelectionCoords()
常量 UPPER_SNAKE DEFAULT_TIMEOUT
私有 _前缀 _cache, _handle()
消息 大写+点 DICT.SEARCH

消息类型速查

DICT.SEARCH / DICT.SEARCH_RESPONSE
CONFIG.GET / CONFIG.SET / CONFIG.CHANGED
WORD.ADD_FAVORITE / WORD.REMOVE_FAVORITE
CLIPBOARD.READ / CLIPBOARD.WRITE
HTTP.GET / HTTP.POST

代码模板

package.json0.0.0

{
  "name": "salad-dict",
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "vue-router": "^4.2.0",
    "pinia": "^2.1.0",
    "localforage": "^1.10.0"
  },
  "devDependencies": {
    "@crxjs/vite-plugin": "^2.0.0",
    "@vitejs/plugin-vue": "^5.0.0",
    "vite": "^5.0.0"
  }
}

manifest.json0.0.0

{
  "manifest_version": 3,
  "name": "沙拉查词",
  "version": "0.0.0",
  "permissions": ["storage", "contextMenus", "clipboardWrite"],
  "optional_permissions": ["clipboardRead"],
  "host_permissions": ["<all_urls>"],
  "background": {
    "service_worker": "src/background/index.js",
    "type": "module"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["src/content/index.js"],
    "run_at": "document_end"
  }],
  "action": {
    "default_popup": "src/popup/index.html"
  },
  "options_page": "src/options/index.html"
}

词典实现

import { DictionaryBase } from './base.js';
import { messaging } from '../messaging.js';

export class XxxDictionary extends DictionaryBase {
  constructor(config = {}) {
    super({ name: '词典名', icon: 'icon.png', languages: ['en', 'zh'], ...config });
  }

  async search(word) {
    if (!word?.trim()) throw new Error('Word is empty');
    const html = await messaging.send('HTTP.GET', { url: `...${encodeURIComponent(word)}` });
    return this.parse(html, word);
  }

  parse(html, word) {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    return {
      word,
      phonetic: '',
      meanings: [{ partOfSpeech: 'n.', definitions: ['释义'] }],
      examples: [{ sentence: '例句', translation: '翻译' }],
      url: ''
    };
  }
}

UI组件

export class XxxComponent extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: 'open' });
    this.render();
  }

  render() {
    this.shadow.innerHTML = `
      <style>
        :host { display: block; position: fixed; }
        .container { background: var(--salad-bg, #fff); }
      </style>
      <div class="container">...</div>
    `;
  }

  show(x, y) {
    this.style.left = `${x}px`;
    this.style.top = `${y}px`;
    this.style.display = 'block';
  }

  hide() {
    this.style.display = 'none';
  }
}

通信封装

class MessageClient {
  async send(type, payload, timeout = 5000) {
    const requestId = generateUUID();
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => reject(new Error('Timeout')), timeout);
      const handler = (response) => {
        if (response.meta?.requestId !== requestId) return;
        clearTimeout(timer);
        chrome.runtime.onMessage.removeListener(handler);
        response.error ? reject(new Error(response.error.message)) : resolve(response.payload);
      };
      chrome.runtime.onMessage.addListener(handler);
      chrome.runtime.sendMessage({ type, payload, meta: { requestId } });
    });
  }
}
export const messaging = new MessageClient();

调试技巧

// Content Script
console.log('[Content]', message);

// Background (看 Service Worker console)
chrome.action.onClicked.addListener(() => {
  console.log('[Background]', 'clicked');
});

// 强制刷新扩展
chrome.runtime.reload();

Chrome 扩展 DevTools:

  • chrome://extensions/ → "service worker" (background)
  • "inspect views" (popup)