mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-07-19 01:09:49 +08:00
重构获取元素选择器功能,优化最优 CSS 选择器算法。
This commit is contained in:
parent
b33ef1be7f
commit
c3ca34bd2a
@ -376,85 +376,6 @@ const waitForElement = async (selector, timeout = 5000) => {
|
||||
throw new Error(`等待元素 ${selector} 超时`);
|
||||
};
|
||||
|
||||
const getSelector = async () => {
|
||||
return await executeScript(`
|
||||
return new Promise((resolve) => {
|
||||
// 创建高亮元素
|
||||
const highlight = document.createElement('div');
|
||||
highlight.style.cssText = 'position: fixed; pointer-events: none; z-index: 10000; background: rgba(130, 180, 230, 0.4); border: 2px solid rgba(130, 180, 230, 0.8); transition: all 0.2s;';
|
||||
document.body.appendChild(highlight);
|
||||
|
||||
// 获取最优选择器
|
||||
function getOptimalSelector(element) {
|
||||
if (!element || element === document.body) return null;
|
||||
|
||||
// 尝试使用id
|
||||
if (element.id) {
|
||||
return '#' + element.id;
|
||||
}
|
||||
|
||||
// 尝试使用类名组合
|
||||
if (element.className && typeof element.className === 'string') {
|
||||
const classes = element.className.trim().split(/\\s+/);
|
||||
if (classes.length) {
|
||||
const selector = '.' + classes.join('.');
|
||||
if (document.querySelectorAll(selector).length === 1) {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取元素在同级中的索引
|
||||
const parent = element.parentElement;
|
||||
if (!parent) return null;
|
||||
|
||||
const siblings = Array.from(parent.children);
|
||||
const index = siblings.indexOf(element);
|
||||
|
||||
// 递归获取父元素选择器
|
||||
const parentSelector = getOptimalSelector(parent);
|
||||
if (!parentSelector) return null;
|
||||
|
||||
return \`\${parentSelector} > \${element.tagName.toLowerCase()}:nth-child(\${index + 1})\`;
|
||||
}
|
||||
|
||||
// 处理鼠标移动
|
||||
function handleMouseMove(e) {
|
||||
const target = e.target;
|
||||
if (!target || target === highlight) return;
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
highlight.style.left = rect.left + 'px';
|
||||
highlight.style.top = rect.top + 'px';
|
||||
highlight.style.width = rect.width + 'px';
|
||||
highlight.style.height = rect.height + 'px';
|
||||
}
|
||||
|
||||
// 处理点击
|
||||
function handleClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.target;
|
||||
if (!target || target === highlight) return;
|
||||
|
||||
const selector = getOptimalSelector(target);
|
||||
|
||||
// 清理
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
highlight.remove();
|
||||
|
||||
resolve(selector);
|
||||
return false;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('click', handleClick, true);
|
||||
});
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
launchBrowser,
|
||||
getUrl,
|
||||
@ -476,5 +397,4 @@ module.exports = {
|
||||
getScrollPosition,
|
||||
getPageSize,
|
||||
waitForElement,
|
||||
getSelector,
|
||||
};
|
||||
|
180
plugin/lib/quickcomposer/browser/getSelector.js
Normal file
180
plugin/lib/quickcomposer/browser/getSelector.js
Normal file
@ -0,0 +1,180 @@
|
||||
const { executeScript } = require("./browser");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const getOptimalSelector = () => {
|
||||
return `
|
||||
// 获取最优选择器
|
||||
function getOptimalSelector_secondary(element) {
|
||||
if (!element || element === document.body) return 'body';
|
||||
|
||||
// 尝试使用id
|
||||
if (element.id) {
|
||||
return '#' + element.id;
|
||||
}
|
||||
|
||||
// 构建当前元素的选择器
|
||||
let currentSelector = element.tagName.toLowerCase();
|
||||
if (element.className && typeof element.className === 'string') {
|
||||
const classes = element.className.trim().split(/\\s+/);
|
||||
if (classes.length) {
|
||||
currentSelector += '.' + classes.join('.');
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 尝试仅使用类名组合
|
||||
if (element.className && typeof element.className === 'string') {
|
||||
const classes = element.className.trim().split(/\\s+/);
|
||||
if (classes.length) {
|
||||
const classSelector = '.' + classes.join('.');
|
||||
if (document.querySelectorAll(classSelector).length === 1) {
|
||||
return classSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 尝试仅使用标签名和类名组合
|
||||
if (document.querySelectorAll(currentSelector).length === 1) {
|
||||
return currentSelector;
|
||||
}
|
||||
|
||||
// 3. 如果需要使用 nth-child,先尝试简单组合
|
||||
const siblings = Array.from(element.parentElement?.children || []);
|
||||
const index = siblings.indexOf(element);
|
||||
if (index !== -1) {
|
||||
const nthSelector = currentSelector + ':nth-child(' + (index + 1) + ')';
|
||||
if (document.querySelectorAll(nthSelector).length === 1) {
|
||||
return nthSelector;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 向上查找最近的有id的祖先元素
|
||||
let ancestor = element;
|
||||
let foundSelectors = [];
|
||||
|
||||
while (ancestor && ancestor !== document.body) {
|
||||
if (ancestor.id) {
|
||||
foundSelectors.push({
|
||||
selector: '#' + ancestor.id,
|
||||
element: ancestor
|
||||
});
|
||||
}
|
||||
|
||||
// 收集所有可能有用的类名组合
|
||||
if (ancestor.className && typeof ancestor.className === 'string') {
|
||||
const classes = ancestor.className.trim().split(/\\s+/);
|
||||
if (classes.length) {
|
||||
const classSelector = ancestor.tagName.toLowerCase() + '.' + classes.join('.');
|
||||
if (document.querySelectorAll(classSelector).length < 10) { // 只收集相对独特的选择器
|
||||
foundSelectors.push({
|
||||
selector: classSelector,
|
||||
element: ancestor
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ancestor = ancestor.parentElement;
|
||||
}
|
||||
|
||||
// 5. 尝试各种组合,找到最短的唯一选择器
|
||||
for (const {selector: anchorSelector} of foundSelectors) {
|
||||
// 尝试直接组合
|
||||
const simpleSelector = anchorSelector + ' ' + currentSelector;
|
||||
if (document.querySelectorAll(simpleSelector).length === 1) {
|
||||
return simpleSelector;
|
||||
}
|
||||
|
||||
// 如果直接组合不唯一,尝试加上 nth-child
|
||||
if (index !== -1) {
|
||||
const nthSelector = anchorSelector + ' ' + currentSelector + ':nth-child(' + (index + 1) + ')';
|
||||
if (document.querySelectorAll(nthSelector).length === 1) {
|
||||
return nthSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 如果还是找不到唯一选择器,使用两层有特征的选择器组合
|
||||
for (let i = 0; i < foundSelectors.length - 1; i++) {
|
||||
for (let j = i + 1; j < foundSelectors.length; j++) {
|
||||
const combinedSelector = foundSelectors[i].selector + ' ' + foundSelectors[j].selector + ' ' + currentSelector;
|
||||
if (document.querySelectorAll(combinedSelector).length === 1) {
|
||||
return combinedSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 最后的后备方案:使用完整的父子选择器
|
||||
const parent = element.parentElement;
|
||||
if (!parent) return null;
|
||||
|
||||
const parentSelector = getOptimalSelector(parent);
|
||||
if (!parentSelector) return null;
|
||||
|
||||
return parentSelector + ' ' + currentSelector + (index !== -1 ? ':nth-child(' + (index + 1) + ')' : '');
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
const getSelector = async () => {
|
||||
return await executeScript(`
|
||||
return new Promise((resolve) => {
|
||||
// 创建高亮元素
|
||||
const highlight = document.createElement('div');
|
||||
highlight.style.cssText = 'position: fixed; pointer-events: none; z-index: 10000; background: rgba(130, 180, 230, 0.4); border: 2px solid rgba(130, 180, 230, 0.8); transition: all 0.2s;';
|
||||
document.body.appendChild(highlight);
|
||||
|
||||
if (typeof OptimalSelect === 'undefined') {
|
||||
${fs.readFileSync(path.join(__dirname, "optimalSelect.js"), "utf-8")}
|
||||
}
|
||||
|
||||
function getOptimalSelector(element) {
|
||||
return OptimalSelect.select(element)
|
||||
}
|
||||
|
||||
${getOptimalSelector()}
|
||||
|
||||
// 处理鼠标移动
|
||||
function handleMouseMove(e) {
|
||||
const target = e.target;
|
||||
if (!target || target === highlight) return;
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
highlight.style.left = rect.left + 'px';
|
||||
highlight.style.top = rect.top + 'px';
|
||||
highlight.style.width = rect.width + 'px';
|
||||
highlight.style.height = rect.height + 'px';
|
||||
}
|
||||
|
||||
// 处理点击
|
||||
function handleClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.target;
|
||||
if (!target || target === highlight) return;
|
||||
let selector = null;
|
||||
try {
|
||||
selector = getOptimalSelector(target);
|
||||
} catch (e) {
|
||||
selector = getOptimalSelector_secondary(target);
|
||||
}
|
||||
|
||||
// 清理
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
highlight.remove();
|
||||
|
||||
resolve(selector);
|
||||
return false;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('click', handleClick, true);
|
||||
});
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getSelector,
|
||||
};
|
@ -1,3 +1,7 @@
|
||||
const browser = require("./browser");
|
||||
const getSelector = require("./getSelector");
|
||||
|
||||
module.exports = browser;
|
||||
module.exports = {
|
||||
...browser,
|
||||
...getSelector,
|
||||
};
|
||||
|
1767
plugin/lib/quickcomposer/browser/optimalSelect.js
Normal file
1767
plugin/lib/quickcomposer/browser/optimalSelect.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user