JS 注入标注快捷键面板:数据驱动 radio 分组与可拖拽悬浮窗

基础版:数字键触发单个标签 click

(function () {
  const labels = ["路中障碍物", "施工区域", "基本可见", "部分可见", "运动", "静上"];

  // 防止重复注入
  const old = document.getElementById("label-hotkey-panel");
  if (old) old.remove();

  const panel = document.createElement("div");
  panel.id = "label-hotkey-panel";
  panel.style.cssText =
    "position:fixed;top:20px;right:20px;z-index:999999;background:rgba(0,0,0,.85);" +
    "color:#fff;padding:12px;border-radius:10px;font-size:14px;min-width:220px;";

  function getEl(name) {
    return document.querySelector(`span.label[title="${name}"]`);
  }

  labels.forEach((name, idx) => {
    const row = document.createElement("div");
    row.style.cssText = "display:flex;align-items:center;justify-content:space-between;margin:6px 0;";
    const info = document.createElement("span");
    info.textContent = `[${idx + 1}] ${name}`;
    const btn = document.createElement("button");
    btn.textContent = getEl(name) ? "点击" : "不存在";
    btn.style.cssText = `cursor:pointer;border:none;padding:4px 8px;border-radius:6px;
      background:${getEl(name) ? "#409eff" : "#666"};color:white;`;
    btn.onclick = () => {
      const el = getEl(name);
      el ? el.click() : console.warn("未找到:", name);
    };
    row.append(info, btn);
    panel.appendChild(row);
  });

  document.body.appendChild(panel);

  document.addEventListener("keydown", (e) => {
    const n = parseInt(e.key);
    if (!isNaN(n) && n >= 1 && n <= labels.length) {
      const el = getEl(labels[n - 1]);
      el ? el.click() : console.warn("未找到:", labels[n - 1]);
    }
  });
})();

数据驱动 radio 分组版(推荐维护方式)

多个分组各自单选,按反引号 ` 执行当前选择:

(function () {
  if (document.getElementById("label-panel")) return;

  // 只改这里就能增减标签
  const groups = {
    g1: { title: "第一组", options: ["路中障碍物", "施工区域"] },
    g2: { title: "第二组", options: ["基本可见", "部分可见", "基本不可见"] },
    g3: { title: "���三组", options: ["运动", "禁止"] }
  };

  const panel = document.createElement("div");
  panel.id = "label-panel";
  panel.style.cssText =
    "position:fixed;top:20px;right:20px;z-index:999999;background:#fff;" +
    "border:1px solid #ccc;border-radius:8px;padding:12px;" +
    "box-shadow:0 0 10px rgba(0,0,0,.2);font-size:14px;width:240px;";

  // 动态渲染 HTML
  const html = Object.entries(groups)
    .map(([key, group]) => {
      const radios = group.options
        .map((opt, idx) => `
          <label style="display:block;margin:2px 0;">
            <input type="radio" name="${key}" value="${opt}" ${idx === 0 ? "checked" : ""}>${opt}
          </label>`)
        .join("");
      return `<div style="margin-bottom:8px;"><b>${group.title}</b><div>${radios}</div></div>`;
    })
    .join("");

  panel.innerHTML = `
    <div style="font-weight:bold;margin-bottom:10px;">标签快捷选择</div>
    ${html}
    <hr>
    <div style="font-size:12px;color:#666;">反引号 = 执行</div>
  `;

  document.body.appendChild(panel);

  function clickLabel(title) {
    const el = document.querySelector(`span.label[title="${title}"]`);
    el ? el.click() : console.warn("未找到:", title);
  }

  function execute() {
    Object.keys(groups).forEach(key => {
      const selected = document.querySelector(`input[name="${key}"]:checked`);
      if (selected) clickLabel(selected.value);
    });
  }

  document.addEventListener("keydown", e => {
    if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
    if (e.key === "`") { e.preventDefault(); execute(); }
    if (e.key === "Tab") {
      e.preventDefault();
      document.querySelector('input[type="checkbox"][value="开启高层过滤"]')?.click();
    }
  });
})();

添加拖拽与折叠

// 拖动
let dragging = false, ox = 0, oy = 0;
panel.addEventListener("mousedown", e => {
  if (e.target.tagName === "INPUT") return;
  dragging = true;
  ox = e.clientX - panel.offsetLeft;
  oy = e.clientY - panel.offsetTop;
});
document.addEventListener("mousemove", e => {
  if (!dragging) return;
  panel.style.left = (e.clientX - ox) + "px";
  panel.style.top  = (e.clientY - oy) + "px";
  panel.style.right = "auto";
});
document.addEventListener("mouseup", () => dragging = false);

// 折叠
let collapsed = false;
panel.querySelector("#toggle-btn").onclick = () => {
  collapsed = !collapsed;
  panel.querySelector(".body").style.display = collapsed ? "none" : "block";
};

面板头部加折叠按钮:

<div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer">
  <b>label</b>
  <span id="toggle-btn">[-]</span>
</div>
<div class="body">...</div>

Python 批量转义 JS 中的汉字

把 JS 文件里的中文字符替换成 \uXXXX,代码仍然可以在浏���器运行:

import re

def encode_chinese(js):
    def repl(m):
        return ''.join(f'\\u{ord(c):04x}' for c in m.group(0))
    return re.sub(r'[一-鿿]+', repl, js)

js_code = '''
const title = "路中障碍���";
document.querySelector('span.label[title="施工区域"]');
'''

print(encode_chinese(js_code))
# const title = "路中障碍物";
# document.querySelector('span.label[title="施工区域"]');

只替换汉字,JS 语法和 ASCII 字符保持不变,浏览器完全等价执行。