/** * picker.js —— 页面元素拾取器 v3(确认式弹窗) * * 配合 tools/ui-picker/ui-picker-server.js 使用: * 页面引入 * URL 带 ?pick 才激活(不带就完全不碰页面)。 * * 操作:鼠标划过元素出蓝框 → 点一下 → 弹窗里直接给出该组件的 * 「选择器 / 地址 / 文本」,点「确认」即复制进剪贴板,粘到对话框里说明怎么改即可。 */ (function () { 'use strict'; if (!new URLSearchParams(location.search).has('pick')) return; var API = 'http://127.0.0.1:8890'; try { if (document.currentScript && document.currentScript.src) { API = new URL(document.currentScript.src).origin; } } catch (e) {} /* ---------- 路径与上下文提取 ---------- */ /* 生成候选路径;forceNth = true 时每一级都带 :nth-of-type,等于完整树路径 */ function rawPath(el, forceNth) { if (!el || el.nodeType !== 1) return ''; if (el.id && document.querySelectorAll('#' + CSS.escape(el.id)).length === 1) { return '#' + CSS.escape(el.id); } var parts = []; var node = el; while (node && node.nodeType === 1 && node !== document.documentElement) { var sel = node.nodeName.toLowerCase(); if (node.id) { sel += '#' + CSS.escape(node.id); parts.unshift(sel); break; } var cls = Array.prototype.slice.call(node.classList).filter(function (c) { return !/^(ng-|css-|sc-|is-|has-)/.test(c); }).slice(0, 3); if (cls.length) sel += '.' + cls.map(function (c) { return CSS.escape(c); }).join('.'); var parent = node.parentElement; if (parent) { var same = Array.prototype.filter.call(parent.children, function (c) { return c.nodeName === node.nodeName; }); if (forceNth || same.length > 1) sel += ':nth-of-type(' + (same.indexOf(node) + 1) + ')'; } parts.unshift(sel); node = parent; if (parts.length >= 7) break; } return parts.join(' > '); } /* 校验:这条选择器在页面上是不是恰好只命中这一个元素 */ function matchesExactly(sel, el) { if (!sel) return false; try { var list = document.querySelectorAll(sel); return list.length === 1 && list[0] === el; } catch (e) { return false; } } /* 先给最短路径;若不唯一,退到每一级都带下标的完整路径 */ function cssPath(el) { var base = rawPath(el, false); if (matchesExactly(base, el)) return base; var full = rawPath(el, true); if (matchesExactly(full, el)) return full; return base; } function breadcrumb(el) { var names = []; var node = el; while (node && node.nodeType === 1 && node !== document.body) { var n = node.nodeName.toLowerCase(); if (node.id) n += '#' + node.id; else if (node.classList.length) n += '.' + Array.prototype.slice.call(node.classList, 0, 2).join('.'); names.unshift(n); node = node.parentElement; } return names.join(' > '); } var STYLE_KEYS = ['display', 'position', 'width', 'height', 'margin', 'padding', 'fontSize', 'fontWeight', 'lineHeight', 'color', 'backgroundColor', 'border', 'borderRadius', 'boxShadow', 'gap', 'flexDirection', 'justifyContent', 'alignItems', 'gridTemplateColumns', 'textAlign']; function stylesOf(el) { var cs = getComputedStyle(el); var out = {}; STYLE_KEYS.forEach(function (k) { out[k] = cs[k]; }); return out; } function attrsOf(el) { var out = {}; Array.prototype.forEach.call(el.attributes, function (a) { if (a.name === 'style' || a.name === 'class' || a.name === 'id') return; var v = a.value || ''; out[a.name] = v.length > 120 ? v.slice(0, 120) + '…' : v; }); return out; } function textOf(el) { return (el.textContent || '').trim().replace(/\s+/g, ' '); } function collect(el, sel) { var r = el.getBoundingClientRect(); return { page: location.href, pageTitle: (document.title || '').trim(), note: '', selector: sel, breadcrumb: breadcrumb(el), tag: el.tagName.toLowerCase(), classes: Array.prototype.slice.call(el.classList), text: textOf(el).slice(0, 200), attrs: attrsOf(el), rect: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }, styles: stylesOf(el), html: el.outerHTML.length > 1500 ? el.outerHTML.slice(0, 1500) + ' ' : el.outerHTML }; } /* ---------- 剪贴板 ---------- */ function legacyCopy(text) { try { var ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', ''); ta.style.cssText = 'position:fixed;top:-1000px;left:-1000px;opacity:0;'; document.body.appendChild(ta); ta.select(); ta.setSelectionRange(0, ta.value.length); var ok = document.execCommand('copy'); ta.remove(); return ok; } catch (e) { return false; } } function copyText(text) { try { if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () {}, function () { legacyCopy(text); }); return true; } } catch (e) {} return legacyCopy(text); } /* ---------- UI ---------- */ var host = document.createElement('div'); host.id = '__ui_picker_host'; host.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483647;'; document.documentElement.appendChild(host); var root = host.attachShadow({ mode: 'open' }); root.innerHTML = [ '', '
', '', '