New Version 0.2.0

This commit is contained in:
naibo 2023-03-23 07:04:50 +08:00
parent 661c87c922
commit c5a871a484
22 changed files with 12901 additions and 1 deletions

@ -1 +0,0 @@
Subproject commit 79d7bb1095a21dd6b6c800daf86d2becd14c19df

6
Extension/manifest_v31/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules
dist
.DS_Store
.env
EasySpider_en
EasySpider_zh

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,18 @@
# Quick Start
## Install
```
npm install
```
[//]: # (## Build)
[//]: # (`npm run build`)
## Hot reload the extension
`npm run dev`
## Package the extension for both Chinese and English versions
`npm run package`
Will automatically generate two .crx files and then copy to the `ElectronJS` folder (old files will be replaced).

10709
Extension/manifest_v31/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import readline from 'readline';
import { execSync } from 'child_process';
import {remove} from "fs-extra";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let config = fs.readFileSync(path.join(__dirname, `src/content-scripts/config.json`), 'utf8');
config = JSON.parse(config);
// 生成英文插件
removeDir(path.join(__dirname, `EasySpider_en`));
config.language = "en";
let data = JSON.stringify(config);
// write JSON string to a file
fs.writeFileSync(path.join(__dirname, `src/content-scripts/config.json`), data, (err) => {
if (err) {
throw err;
}
});
execSync(`npm run build`, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
fs.renameSync(path.join(__dirname, `dist/`), path.join(__dirname, `EasySpider_en`));
execSync(`npm run crx EasySpider_en`, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
fs.copyFileSync(path.join(__dirname, './EasySpider_en.crx'), path.join(__dirname, '../../ElectronJS/EasySpider_en.crx'));
// 生成中文插件
removeDir(path.join(__dirname, `EasySpider_zh`));
config.language = "zh";
data = JSON.stringify(config);
// write JSON string to a file
fs.writeFileSync(path.join(__dirname, `src/content-scripts/config.json`), data, (err) => {
if (err) {
throw err;
}
});
execSync(`npm run build`, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
fs.renameSync(path.join(__dirname, `dist/`), path.join(__dirname, `EasySpider_zh`));
execSync(`npm run crx EasySpider_zh`, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
fs.copyFileSync(path.join(__dirname, './EasySpider_zh.crx'), path.join(__dirname, '../../ElectronJS/EasySpider_zh.crx'));
function removeDir(dir) {
let files = fs.readdirSync(dir)
for(var i=0;i<files.length;i++){
let newPath = path.join(dir,files[i]);
let stat = fs.statSync(newPath)
if(stat.isDirectory()){
//如果是文件夹就递归下去
removeDir(newPath);
}else {
//删除文件
fs.unlinkSync(newPath);
}
}
fs.rmdirSync(dir)//如果文件夹是空的,就将自己删除掉
}

View File

@ -0,0 +1,50 @@
{
"name": "EasySpider",
"version": "0.2.0",
"type": "module",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"crx": "crx3",
"package": "node package.js"
},
"dependencies": {
"crx": "^5.0.1",
"crx3": "^1.1.3",
"dotenv": "^16.0.0",
"jquery": "^3.6.4",
"rollup-plugin-alias": "^2.2.0",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-import-css": "^3.0.3",
"rollup-plugin-vue": "^6.0.0",
"vue": "^2.7.14",
"vue-template-compiler": "^2.7.14",
"webextension-polyfill": "^0.7.0",
"webextension-polyfill-ts": "^0.22.0"
},
"devDependencies": {
"@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"@rollup/plugin-replace": "^2.3.4",
"@types/chrome": "0.0.164",
"@types/fs-extra": "^9.0.13",
"@types/node": "^16.11.10",
"@vitejs/plugin-vue": "^1.9.3",
"esno": "^0.12.1",
"firebase": "^9.5.0",
"fs-extra": "^10.0.0",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"rollup": "^2.38.5",
"rollup-plugin-chrome-extension": "^3.5.3",
"rollup-plugin-empty-dir": "^1.0.4",
"rollup-plugin-inject-process-env": "^1.3.1",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-typescript2": "^0.31.0",
"rollup-plugin-vue2": "^0.8.1",
"rollup-plugin-zip": "^1.0.1",
"typescript": "^4.4.3"
}
}

View File

@ -0,0 +1,63 @@
import json from '@rollup/plugin-json';
import vue from 'rollup-plugin-vue2';
import {
chromeExtension,
simpleReloader,
} from 'rollup-plugin-chrome-extension';
import { emptyDir } from 'rollup-plugin-empty-dir';
import typescript from 'rollup-plugin-typescript2'; // '@rollup/plugin-typescript'
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace';
import postcss from 'rollup-plugin-postcss';
import alias from 'rollup-plugin-alias';
import _dotenv from 'dotenv/config';
import path from "path";
export default {
input: 'src/manifest.json',
output: {
dir: 'dist',
format: 'esm',
chunkFileNames: 'chunks/[name]-[hash].js',
},
onwarn: (warning, defaultHandler) => {
if (warning.code === 'THIS_IS_UNDEFINED') return;
defaultHandler(warning)
},
// watch: { clearScreen: false }, // for dev debug
plugins: [
alias({
entries: {
['@']: path.resolve(__dirname, 'src')
}}),
// chromeExtension() must be first, in order to properly treat manifest.json as the entry point
chromeExtension({
extendManifest: {
"key": process.env.VUE_APP_MV3_KEY
}
}),
simpleReloader(), // Adds a Chrome extension reloader during watch mode
vue(),
replace({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
"process.env.NODE_ENV": JSON.stringify("production"),
"process.env.VUE_APP_FIREBASE_APIKEY": JSON.stringify(process.env.VUE_APP_FIREBASE_APIKEY),
"process.env.VUE_APP_FIREBASE_AUTHDOMAIN": JSON.stringify(process.env.VUE_APP_FIREBASE_AUTHDOMAIN),
"process.env.VUE_APP_FIREBASE_PROJECTID": JSON.stringify(process.env.VUE_APP_FIREBASE_PROJECTID),
"process.env.VUE_APP_FIREBASE_STORAGEBUCKET": JSON.stringify(process.env.VUE_APP_FIREBASE_STORAGEBUCKET),
"process.env.VUE_APP_FIREBASE_MESSAGINGSENDERID": JSON.stringify(process.env.VUE_APP_FIREBASE_MESSAGINGSENDERID),
"process.env.VUE_APP_FIREBASE_APPID": JSON.stringify(process.env.VUE_APP_FIREBASE_APPID),
"process.env.VUE_APP_MEASUREMENTID": JSON.stringify(process.env.VUE_APP_MEASUREMENTID),
preventAssignment: true
}),
typescript(),
postcss(),
json(),
resolve(),
commonjs(),
emptyDir(),
],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,66 @@
//此变量用于监听是否加载了新的页面(包括新窗口打开),如果是,增加变量值,用于传回后台。
var tabList = []; //用来记录打开的新的tab的id
var nowTabId = null;
var nowTabIndex = 0; //重要变量!!
var parameterNum = 1; //默认参数索引值
chrome.storage.local.set({ "parameterNum": 1 }); //修改默认的参数索引值
// chrome.tabs.update(6,{"active":true}) //一行就可以切换标签页
chrome.tabs.onActivated.addListener(function(activeInfo) {
nowTabId = activeInfo.tabId; //记录现在活动的tabid
if (tabList.indexOf(nowTabId) != -1) {
nowTabIndex = tabList.indexOf(nowTabId);
}
});
// 监听来自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.type == 0) {
if (tabList.indexOf(sender["tab"]["id"]) < 0) { //元素不存在加入数组
tabList.push(sender["tab"]["id"]);
}
nowTabIndex = tabList.indexOf(nowTabId);
sendResponse({ type: 0, "msg": "Get!" }); //回传一个消息
} else if (request.type == 1) { //前台询问参数索引值
sendResponse({ type: 1, "value": parameterNum }); //回传一个消息
} else if (request.type == 2) {
let message = {
type: 2, //消息类型2代表键盘输入
message: { "keyboardStr": "{}{BS}" + request.msg } // {}全选{BS}退格
};
ws.send(JSON.stringify(message));
} else if (request.type == 3) {
let tmsg = request.msg;
tmsg.tabIndex = nowTabIndex; //赋值当前tab的id
let message = {
type: 3, //消息类型3代表元素增加事件
from: 0, //0代表从浏览器到流程图1代表从流程图到浏览器
message: { "pipe": JSON.stringify(request.msg) } // {}全选{BS}退格
};
console.log(message);
ws.send(JSON.stringify(message));
}
});
// 打开一个 web socket
var ws = new WebSocket("ws://localhost:8084");
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
console.log("已连接");
let message = {
type: 0, //消息类型0代表链接操作
message: {
id: 0, //socket id
}
};
this.send(JSON.stringify(message));
};
ws.onmessage = function(evt) {
evt = JSON.parse(evt.data);
if (evt["type"] == "0") { //0代表更新参数添加索引值
chrome.storage.local.set({ "parameterNum": parseInt(evt["value"]) }); //修改值
}
};
ws.onclose = function() {
// 关闭 websocket
console.log("连接已关闭...");
};

View File

@ -0,0 +1 @@
{"language":"zh"}

View File

@ -0,0 +1,733 @@
import config from './config.json';
export var global = {
nodeList: [], //已被选中的节点列表
readyList: [], //预备选中的list
outputParameters: [], //输出参数列表
outputParameterNodes: [], //输出参数节点列表
NowNode: null,
xnode: null,
step: 0, //记录这是第几次点击操作
style: "", //记录上个元素的颜色
oe: null, //记录上个元素
app: null,
div:null,
tdiv:null,
selectedColor: "rgba(151,255,255, 0.6)",
defaultbgColor: 'rgba(221,221,255,0.8)',
boxShadowColor: "blue 0px 0px 5px",
lang: config.language,
};
export function getOS () {
if (navigator.userAgent.indexOf('Window') > 0) {
return 'Windows'
} else if (navigator.userAgent.indexOf('Mac') > 0) {
return 'Mac'
} else if (navigator.userAgent.indexOf('Linux') > 0) {
return 'Linux'
} else {
return 'NULL'
}
}
//返回element相对node节点的xpath默认的node节点是: /
export function readXPath(element, type = 1, node = document.body) {
try {
if (type == 0) //type=0代表默认可通过id生成xpath type=1代表只能从根节点生成xpath, nodeList里必须使用绝对xpath!
{
if (element.id !== "") { //判断id属性如果这个元素有id则显示//*[@id="xPath"] 形式内容
return '//*[@id=\"' + element.id + '\"]';
}
if (element.className != ""){ //如果有class且某个class name只有一个元素则使用class name生成xpath
console.log("class name: " + element.className);
let names = element.className.split(" ");
for (var i = 0; i < names.length; i++) {
if (names[i] != "") {
// return '//*[@class=\"' + names[i] + '\"]';
console.log('//*[@contains(@class, \"' + names[i] + '\")]');
let elements_of_class = node.getElementsByClassName(names[i]);
console.log("Length of elements_of_class: " + elements_of_class.length);
if(elements_of_class.length == 1){
return '//*[contains(@class, \"' + names[i] + '\")]'
}
}
}
}
}
//这里需要需要主要字符串转译问题可参考js 动态生成html时字符串和变量转译注意引号的作用
if (element == node) { //递归到body处结束递归
if (node == document.body) {
return '/html/' + element.tagName.toLowerCase();
} else {
return "";
}
}
var ix = 1, //在nodelist中的位置且每次点击初始化
siblings = element.parentNode.childNodes; //同级的子元素
for (var i = 0, l = siblings.length; i < l; i++) {
var sibling = siblings[i];
//如果这个元素是siblings数组中的元素则执行递归操作;arguments.callee代表当前函数的名称
if (sibling == element) {
return readXPath(element.parentNode, type, node) + '/' + element.tagName.toLowerCase() + '[' + (ix) + ']';
//如果不符合判断是否是element元素并且是否是相同元素如果是相同的就开始累加
} else if (sibling.nodeType == 1 && sibling.tagName == element.tagName) {
//注意此处为了防止多计算了插入的操作台的3个div元素导致定位错误这里需要屏蔽掉三个元素的索引号
if(sibling.id != "wrapperDiv" && sibling.id != "wrapperTdiv" &&sibling.id != "wrapperToolkit"){
ix++;
}
}
}
} catch {
return "/"
}
}
//选中元素到列表中
export function addEl() {
// if (tooltips) {
// return;
// }
let exist = false;
for (let o of global.nodeList) {
if (o["node"] == global.oe) {
exist = true;
break;
}
}
//元素没有被添加过才去添加
if (!exist) {
global.step++;
exist = false; //判断刚加入的元素是否在readyList中如果在则将所有readylist中的元素全部放入list中
for (let o of global.readyList) {
if (o["node"] == global.oe) {
exist = true;
break;
}
}
if (exist) { //存在在readylist就全选中
readyToList(global.step);
if (global.app._data.selectedDescendents) {
handleDescendents(); //如果之前有选中子元素,新加入的节点又则这里也需要重新选择子元素
}
} else //不然只添加一个元素
{
clearReady(); //readylist清零重新算
global.nodeList.push({ node: global.NowNode, "step": global.step, bgColor: global.style, "boxShadow": global.NowNode.style.boxShadow == "" || global.boxShadowColor ? "none" : global.NowNode.style.boxShadow, xpath: readXPath(global.NowNode, 1) });
global.NowNode.style.backgroundColor = global.selectedColor;
}
handleElement(); //处理新状态
//将虚线框显示在元素上方但屏蔽其鼠标操作
var pos = global.NowNode.getBoundingClientRect();
global.div.style.display = "block";
global.div.style.height = global.NowNode.offsetHeight + "px";
global.div.style.width = global.NowNode.offsetWidth + "px";
global.div.style.left = pos.left + "px";
global.div.style.top = pos.top + "px";
global.div.style.zIndex = 2147483645;
global.div.style.pointerEvents = "none";
}
// console.log("------");
// for (i = 0; i < global.nodeList.length; i++) {
// console.log(global.nodeList[i]["xpath"]);
// }
//对于可点击元素屏蔽a标签默认点击事件
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
//清除选择项
export function clearEl() {
//如果最后停留的元素被选中则调整此元素的style为原始style否则不进行调整
for (let node of global.nodeList) {
node["node"].style.backgroundColor = node["bgColor"];
node["node"].style.boxShadow = node["boxShadow"];
if (global.NowNode == node["node"]) {
global.style = node["bgColor"];
}
}
global.step = 0;
clearReady();
clearParameters();
global.nodeList.splice(0, global.nodeList.length); //清空数组
global.app._data.option = 0; //选项重置
global.app._data.page = 0; //恢复原始页面
}
//清除预备数组
export function clearReady() {
for (let node of global.readyList) //节点列表状态恢复原状
{
node["node"].style.boxShadow = node["boxShadow"];
}
global.readyList.splice(0, global.readyList.length); //清空数组
}
//每次对元素进行增删之后需要执行的操作
export function handleElement() {
clearReady(); //预备元素每次处理都先处理掉
if (global.nodeList.length > 1) { //选中了许多元素的情况
global.app._data.option = relatedTest();
if (global.app._data.option == 100) {
generateMultiParameters();
} else {
generateParameters(0);
}
} else if (global.nodeList.length == 1) {
findRelated(); //寻找和元素相关的元素
}
}
export function clearParameters(deal = true) //清空参数列表
{
if (deal) //是否取消对选中的子元素进行处理
{
global.app._data.selectedDescendents = false;
}
for (let o of global.outputParameterNodes) {
o["node"].style.boxShadow = o["boxShadow"];
}
global.outputParameterNodes.splice(0);
global.outputParameters.splice(0); //清空原来的参数列表
global.app._data.valTable = []; //清空展现数组
global.app._data.selectStatus = false;
}
function parameterName(value){
if (global.lang == 'zh'){
return value;
} else{
switch(value){
case "文本": return "text";
case "链接": return "link";
case "_链接": return "_link";
case "_文本": return "_text";
case "链接文本": return "link_text";
case "_链接文本": return "_link_text";
case "链接地址": return "link_address";
case "_链接地址": return "_link_address";
case "按钮": return "button";
case "输入文本框": return "input_text";
case "单选框": return "radio";
case "复选框": return "checkbox";
case "下拉框": return "select";
case "下拉框选项": return "select_option";
case "地址": return "address";
case "参数": return "para";
case "_图片": return "_image";
case "_图片地址": return "_image_address";
default: return "";
}
}
}
//根据nodelist列表内的元素生成参数列表
//适合nodelist中的元素为同类型元素
//type:0为全部文本 1为节点内直接的文字 2为innerhtml 3为outerhtml
//nodetype:0,对应全type0123
//nodetype:1 链接对应type0123
//nodetype:2 链接地址 对应type0
//nodetype:3 按钮和输入文本框 对应type
//nodetype:4 按钮和输入文本框 对应type
export function generateParameters(type, linktext = true, linkhref = true) {
clearParameters(false);
let n = 1;
chrome.storage.local.get({ parameterNum: 1 }, function(items) {
let at = parseInt(new Date().getTime());
n = items.parameterNum;
let ndPath = "";
for (let num = 0; num < global.nodeList.length; num++) {
let nd = global.nodeList[num]["node"];
ndPath = global.nodeList[num]["xpath"];
global.outputParameterNodes.push({ "node": nd, "boxShadow": nd.style.boxShadow == "" || global.boxShadowColor ? "none" : nd.style.boxShadow });
nd.style.boxShadow = global.boxShadowColor;
let pname = parameterName("文本");
let ndText = "";
if (type == 0) {
// ndText = $(nd).text();
ndText = nd.textContent;
pname = parameterName("文本");
if (nd.tagName == "IMG") {
ndText = nd.getAttribute("src") == null ? "" : nd.getAttribute("src");
pname = parameterName("地址");
} else if (nd.tagName == "INPUT") {
ndText = nd.getAttribute("value") == null ? "" : nd.getAttribute("value");
}
} else if (type == 1) {
// ndText = $(nd).contents().filter(function() { return this.nodeType === 3; }).text().replace(/\s+/g, '');
ndText = "";
let ndContents = nd.childNodes;
for (var i = 0; i < ndContents.length; i++) {
if (ndContents[i].nodeType === 3) { // if it's a text node
ndText += ndContents[i].textContent.trim(); // add its content to the string
}
}
ndText = ndText.replace(/\s+/g, ''); // remove any whitespace characters
pname = parameterName("文本");
if (nd.tagName == "IMG") {
ndText = nd.getAttribute("src") == null ? "" : nd.getAttribute("src");
pname = parameterName("地址");
} else if (nd.tagName == "INPUT") {
ndText = nd.getAttribute("value") == null ? "" : nd.getAttribute("value");
}
} else if (type == 2) {
// ndText = $(nd).html();
ndText = nd.innerHTML;
pname = "Innerhtml";
} else if (type == 3) {
// ndText = $(nd).prop("outerHTML");
ndText = nd.outerHTML;
pname = "outerHTML";
}
if (num == 0) { //第一个节点新建,后面的增加即可
if (nd.tagName == "IMG") { //如果元素是图片
global.outputParameters.push({
"nodeType": 4, //节点类型
"contentType": type, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_图片") + pname,
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? "" : ndPath,
"exampleValues": [{ "num": num, "value": ndText }]
});
} else if (nd.tagName == "A") { //如果元素是超链接
if (linktext) {
global.outputParameters.push({
"nodeType": 1,
"contentType": type, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接") + pname,
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? "" : ndPath,
"exampleValues": [{ "num": num, "value": ndText }]
});
}
if (linkhref) {
global.outputParameters.push({
"nodeType": 2,
"contentType": type, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接地址"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? "" : ndPath,
"exampleValues": [{ "num": num, "value": nd.getAttribute("href") == null ? "" : nd.getAttribute("href") }]
});
}
} else if (nd.tagName == "INPUT") { //如果元素是输入项
global.outputParameters.push({
"nodeType": 3,
"contentType": type, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + "_" + pname,
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? "" : ndPath,
"exampleValues": [{ "num": num, "value": ndText }]
});
} else { //其他所有情况
global.outputParameters.push({
"nodeType": 0,
"contentType": type, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + "_" + pname,
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? "" : ndPath,
"exampleValues": [{ "num": num, "value": ndText }]
});
}
} else { //如果元素节点已经存在,则只需要插入值就可以了
if (nd.tagName == "IMG") { //如果元素是图片
global.outputParameters[0]["exampleValues"].push({ "num": num, "value": ndText });
} else if (nd.tagName == "A") { //如果元素是超链接
global.outputParameters[0]["exampleValues"].push({ "num": num, "value": ndText });
global.outputParameters[1]["exampleValues"].push({ "num": num, "value": nd.getAttribute("href") == null ? "" : nd.getAttribute("href") });
} else if (nd.tagName == "INPUT") { //如果元素是输入项
global.outputParameters[0]["exampleValues"].push({ "num": num, "value": ndText });
} else { //其他所有情况
global.outputParameters[0]["exampleValues"].push({ "num": num, "value": ndText });
}
}
}
let at2 = parseInt(new Date().getTime());
console.log("generateParameters:", at2, at, at2 - at);
generateValTable();
console.log(global.outputParameters);
});
}
//根据nodelist列表内的元素生成参数列表
//适合nodelist中的元素为不同类型元素
export function generateMultiParameters() {
clearParameters(false);
let n = 1;
chrome.storage.local.get({ parameterNum: 1 }, function(items) {
let at = parseInt(new Date().getTime());
n = items.parameterNum;
let nd, ndText, ndPath, pname;
for (let num = 0; num < global.nodeList.length; num++) {
let nd = global.nodeList[num]["node"];
ndPath = global.nodeList[num]["xpath"];
global.outputParameterNodes.push({ "node": nd, "boxShadow": nd.style.boxShadow == "" || global.boxShadowColor ? "none" : nd.style.boxShadow });
nd.style.boxShadow = global.boxShadowColor;
// ndText = $(nd).text();
ndText = nd.textContent;
if (nd.tagName == "IMG") { //如果元素是图片
global.outputParameters.push({
"nodeType": 4, //节点类型
"contentType": 0, // 内容类型
"relative": false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_图片地址"),
"desc": "", //参数描述
"relativeXpath": ndPath,
"exampleValues": [{ "num": 0, "value": nd.getAttribute("src") == null ? "" : nd.getAttribute("src") }]
});
} else if (nd.tagName == "A") { //如果元素是超链接
global.outputParameters.push({
"nodeType": 1,
"contentType": 0, // 内容类型
"relative": false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接文本"),
"desc": "", //参数描述
"relativeXpath": ndPath,
"exampleValues": [{ "num": 0, "value": ndText }]
});
global.outputParameters.push({
"nodeType": 2,
"contentType": 0, // 内容类型
"relative": false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接地址"),
"desc": "", //参数描述
"relativeXpath": ndPath,
"exampleValues": [{ "num": 0, "value": nd.getAttribute("href") == null ? "" : nd.getAttribute("href") }]
});
} else if (nd.tagName == "INPUT") { //如果元素是输入项
global.outputParameters.push({
"nodeType": 3,
"contentType": 0, // 内容类型
"relative": false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_文本"),
"desc": "", //参数描述
"relativeXpath": ndPath,
"exampleValues": [{ "num": 0, "value": nd.getAttribute("value") == null ? "" : nd.getAttribute("value") }]
});
} else { //其他所有情况
global.outputParameters.push({
"nodeType": 0,
"contentType": 0, // 内容类型
"relative": false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_文本"),
"desc": "", //参数描述
"relativeXpath": ndPath,
"exampleValues": [{ "num": 0, "value": ndText }]
});
}
}
// console.log(global.outputParameters);
let at2 = parseInt(new Date().getTime());
console.log("generateMultiParameters", at2, at, at2 - at);
generateValTable(false);
});
}
//处理子元素,对于每个块中多出的特殊元素,需要特殊处理
export function handleDescendents() {
let n = 1;
chrome.storage.local.get({ parameterNum: 1 }, function(items) {
let at = parseInt(new Date().getTime());
n = items.parameterNum;
clearParameters(); //清除原来的参数列表
global.app._data.selectedDescendents = true;
let nd, ndText, ndPath, pname;
for (let num = 0; num < global.nodeList.length; num++) {
let tnode = global.nodeList[num]["node"];
let stack = new Array(); //深度优先搜索遍历元素
stack.push(tnode); //从此节点开始
while (stack.length > 0) {
let nd = stack.pop(); // 挨个取出元素
if (nd.parentNode.tagName == "A" && nd.tagName == "SPAN") {
continue; //对A标签内的SPAN元素不进行处理,剪枝此时子元素根本不加入stack即实现了此功能
}
ndPath = readXPath(nd, 1, tnode);
let index = -1;
for (let i = 0; i < global.outputParameters.length; i++) {
if (global.outputParameters[i]["relativeXpath"] == ndPath) {
index = i;
break;
}
}
global.outputParameterNodes.push({
"node": nd,
"boxShadow": nd.style.boxShadow == "" || global.boxShadowColor ? "none" : nd.style.boxShadow
});
nd.style.boxShadow = global.boxShadowColor;
// ndText = $(nd).contents().filter(function() {
// return this.nodeType === 3;
// }).text().replace(/\s+/g, '');
ndText = "";
let ndContents = nd.childNodes;
for (var i = 0; i < ndContents.length; i++) {
if (ndContents[i].nodeType === 3) { // if it's a text node
ndText += ndContents[i].textContent.trim(); // add its content to the string
}
}
ndText = ndText.replace(/\s+/g, ''); // remove any whitespace characters
if (index == -1) { //从第二个节点开始只插入那些不在参数列表中的元素根据xpath进行寻址
//如果当前节点除了子元素外仍然有其他文字或者该元素是图片/表单项,加入子元素节点
if (ndText != "" || nd.tagName == "IMG" || nd.tagName == "INPUT" || nd.tagName == "A") {
if (nd.tagName == "IMG") { //如果元素是图片
global.outputParameters.push({
"nodeType": 4, //节点类型
"contentType": 1, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径,注意当只选择了子元素没有选中全部的时候,需要判断
"name": parameterName("参数") + (n++) + parameterName("_图片地址"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? ndPath : readXPath(nd), //同理需要判断
"exampleValues": [{
"num": num,
"value": nd.getAttribute("src") == null ? "" : nd.getAttribute("src")
}]
});
} else if (nd.tagName == "A") { //如果元素是超链接
global.outputParameters.push({
"nodeType": 1,
"contentType": 0, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接文本"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? ndPath : readXPath(nd),
"exampleValues": [{ "num": num, "value": nd.textContent }] //注意这里的ndtext是整个a的文字
});
global.outputParameters.push({
"nodeType": 2,
"contentType": 0, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_链接地址"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? ndPath : readXPath(nd),
"exampleValues": [{
"num": num,
"value": nd.getAttribute("href") == null ? "" : nd.getAttribute("href")
}]
});
} else if (nd.tagName == "INPUT") { //如果元素是输入项
global.outputParameters.push({
"nodeType": 3,
"contentType": 1, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_文本"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? ndPath : readXPath(nd),
"exampleValues": [{
"num": num,
"value": nd.getAttribute("value") == null ? "" : nd.getAttribute("value")
}]
});
} else { //其他所有情况
global.outputParameters.push({
"nodeType": 0,
"contentType": 1, // 内容类型
"relative": global.nodeList.length > 1 ? true : false, //是否为相对xpath路径
"name": parameterName("参数") + (n++) + parameterName("_文本"),
"desc": "", //参数描述
"relativeXpath": global.nodeList.length > 1 ? ndPath : readXPath(nd),
"exampleValues": [{ "num": num, "value": ndText }]
});
}
}
} else //如果元素节点已经存在,则只需要插入值就可以了
{
if (nd.tagName == "IMG") { //如果元素是图片
global.outputParameters[index]["exampleValues"].push({
"num": num,
"value": nd.getAttribute("src") == null ? "" : nd.getAttribute("src")
});
} else if (nd.tagName == "A") { //如果元素是超链接
global.outputParameters[index]["exampleValues"].push({ "num": num, "value": nd.textContent });
global.outputParameters[index + 1]["exampleValues"].push({
"num": num,
"value": nd.getAttribute("href") == null ? "" : nd.getAttribute("href")
});
} else if (nd.tagName == "INPUT") { //如果元素是输入项
global.outputParameters[index]["exampleValues"].push({
"num": num,
"value": nd.getAttribute("value") == null ? "" : nd.getAttribute("value")
});
} else { //其他所有情况
global.outputParameters[index]["exampleValues"].push({ "num": num, "value": ndText });
}
}
for (let i = nd.children.length - 1; i >= 0; i--) {
stack.push(nd.children[i]);
}
}
}
let at2 = parseInt(new Date().getTime());
console.log("选中子元素", at2, at, at2 - at);
generateValTable();
});
}
//根据参数列表生成可视化参数界面
export function generateValTable(multiline = true) {
let paravalues = [];
for (let i = 0; i < global.outputParameters.length; i++) {
let tvalues = [];
let tindex = 0;
let l = multiline ? global.nodeList.length : 1;
for (let j = 0; j < l; j++) {
//注意第一个循环条件index超出界限了就不需要再寻找了其他的全是空
if (tindex < global.outputParameters[i]["exampleValues"].length && global.outputParameters[i]["exampleValues"][tindex]["num"] == j) {
tvalues.push(global.outputParameters[i]["exampleValues"][tindex]["value"]);
tindex++;
} else {
tvalues.push(" ");
}
}
paravalues.push(tvalues);
}
global.app._data.valTable = paravalues;
}
// 选中第一个节点,自动寻找同类节点
// 方法:/div[1]/div[2]/div[2]/a[1]
// 从倒数第一个节点开始找,看去掉方括号之后是否元素数目变多,如上面的变成/div[1]/div[2]/div[2]/a
// 如果没有,则恢复原状,然后试试倒数第二个:/div[1]/div[2]/div/a[1]
// 直到找到第一个变多的节点或者追溯到根节点为止
export function findRelated() {
let at = parseInt(new Date().getTime());
let testPath = global.nodeList[0]["xpath"].split("/").splice(1); //分离xpath成 ["html","body","div[0]"]这样子
let nodeNameList = [];
let nodeIndexList = [];
for (let i = 0; i < testPath.length; i++) {
nodeNameList.push(testPath[i].split("[")[0]);
if (testPath[i].indexOf("[") >= 0) { //如果存在索引值
nodeIndexList.push(parseInt(testPath[i].split("[")[1].replace("]", ""))); //只留下数字
} else {
nodeIndexList.push(-1);
}
}
var tempPath = "";
for (let i = nodeIndexList.length - 1; i >= 0; i--) {
if (nodeIndexList[i] == -1) { //没有索引值直接跳过
continue;
}
let tempIndexList = [...nodeIndexList]; //复刻一个index数组
tempIndexList[i] = -1; //删除索引值
tempPath = combineXpath(nodeNameList, tempIndexList); //生成新的xpath
var result = document.evaluate(tempPath, document, null, XPathResult.ANY_TYPE, null);
result.iterateNext(); //枚举第一个元素
if (result.iterateNext() != null) { //如果能枚举到第二个元素,说明存在同类元素,选中同类元素,结束循环
global.app.$data.nowPath = tempPath; //标记此元素xpath
pushToReadyList(tempPath);
break;
}
}
let at2 = parseInt(new Date().getTime());
console.log("findRelated", at2, at, at2 - at);
}
//根据path将元素放入readylist中
export function pushToReadyList(path) {
let result = document.evaluate(path, document, null, XPathResult.ANY_TYPE, null);
var node = result.iterateNext(); //枚举第一个元素
while (node) { //只添加不在已选中列表内的元素
let exist = false;
for (let o of global.nodeList) {
if (o["node"] == node) {
exist = true;
break;
}
}
if (!exist) {
global.readyList.push({ "node": node, "bgColor": node.style.backgroundColor, "boxShadow": node.style.boxShadow == "" || global.boxShadowColor ? "none" : node.style.boxShadow });
}
node.style.boxShadow = global.boxShadowColor;
node = result.iterateNext(); //枚举下一个元素
}
}
//将readyList中的元素放入选中节点中
export function readyToList(step, dealparameters = true) {
for (let o of global.readyList) {
global.nodeList.push({ node: o["node"], "step": global.step, bgColor: o["bgColor"], "boxShadow": o["boxShadow"], xpath: readXPath(o["node"], 1) });
o["node"].style.backgroundColor = global.selectedColor;
}
clearReady();
if (dealparameters) { //防止出现先选中子元素再选中全部失效的问题
generateParameters(0); //根据nodelist列表内的元素生成参数列表0代表纯文本
}
}
//根据节点列表和索引列表生成XPATH
// 如:["html","body","div"],[-1,-1,2],生成/html/body/div[2]
export function combineXpath(nameList, indexList) {
let finalPath = "";
for (let i = 0; i < nameList.length; i++) {
finalPath = finalPath + "/" + nameList[i];
if (indexList[i] != -1) {
finalPath = finalPath + "[" + indexList[i] + "]";
}
}
return finalPath;
}
//专门测试已经选中的这些元素之间有没有相关性
// 举例:
// /html/body/div[3]/div[1]/div[1]/div[1]/div[3]/div[1]/div[3]/a[22]
// /html/body/div[3]/div[1]/div[1]/div[1]/div[3]/div[2]/div[3]/a[25]
// 最终转换为:
// /html/body/div[3]/div[1]/div[1]/div[1]/div[3]/div/div[3]/a
export function relatedTest() {
let at = new Date().getTime()
var testList = [];
var testpath = "";
for (let i = 0; i < global.nodeList.length; i++) {
var testnumList = []; //用于比较节点索引号不同
var tpath = global.nodeList[i]["xpath"].split("/").splice(1); //清理第一个空元素
for (let j = 0; j < tpath.length; j++) {
if (tpath[j].indexOf("[") >= 0) { //如果存在索引值
testnumList.push(parseInt(tpath[j].split("[")[1].replace("]", ""))); //只留下数字
} else {
testnumList.push(-1);
}
tpath[j] = tpath[j].split("[")[0];
}
let tp = tpath.join("/");
if (i > 0 && testpath != tp) { //如果去除括号后元素内存在不一致情况直接返回默认情况代码100
global.app.$data.nowPath = ""; //标记此元素xpath
return 100;
}
testpath = tp;
testList.push(testnumList);
}
testpath = testpath.split("/"); //清理第一个空元素
var indexList = []; //记录新生成的xpath
//如果选中的元素属于同样的序列则计算出序列的最佳xpath表达式
for (let j = 0; j < testList[0].length; j++) {
indexList.push(testList[0][j]);
for (let i = 1; i < testList.length; i++) {
if (testList[i][j] != testList[i - 1][j]) {
indexList[j] = -1; //不一致就记录成-1
break;
}
}
}
var finalPath = combineXpath(testpath, indexList);
global.app.$data.nowPath = finalPath; //标记此元素xpath
pushToReadyList(finalPath);
let at2 = parseInt(new Date().getTime());
console.log("手动:", at2, at, at2 - at);
return 50; //先返回给默认码
}

View File

@ -0,0 +1,203 @@
import $ from "jquery";
import Vue from "vue";
import {global, getOS, readXPath, addEl, clearEl, clearReady, handleElement, clearParameters, generateParameters, generateMultiParameters, handleDescendents, generateValTable, findRelated, pushToReadyList, readyToList, combineXpath, relatedTest} from "./global.js";
import ToolKit from "./toolkit.vue";
//表现逻辑层的处理
if (window.location.href.indexOf("backEndAddressServiceWrapper") >= 0) {
throw "serviceGrid"; //如果是服务器网页页面,则不执行工具
}
//创造div作为选中元素后的样式存在
global.div = document.createElement('div');
global.div.style.zIndex = "-2147483647";
global.div.setAttribute("id", "wrapperDiv");
global.div.style.position = "fixed";
global.div.style.boxSizing = "border-box";
global.div.style.border = "dotted";
global.tdiv = document.createElement('div');
global.tdiv.style.zIndex = "2147483647";
global.tdiv.style.position = "fixed";
global.tdiv.setAttribute("id", "wrapperTdiv");
// @ts-ignore
global.tdiv.classList = "tdiv";
global.tdiv.style.top = "0px";
global.tdiv.style.width = "3000px";
global.tdiv.style.height = "3000px";
global.tdiv.style.pointerEvents = "none";
var mousemovebind = false; //如果出现元素默认绑定了mousemove事件导致匹配不到元素的时候开启第二种模式获得元素
var toolkit = document.createElement("div")
// @ts-ignore
toolkit.classList = "tooltips"; //添加样式
toolkit.setAttribute("id", "wrapperToolkit");
var tooltips = false; //标记鼠标是否在提示框上
//右键菜单屏蔽
document.oncontextmenu = () => false;
document.addEventListener("mousemove", function() {
if (mousemovebind) {
global.tdiv.style.pointerEvents = "none";
}
//如果鼠标在元素框内则点击和选中失效
var x = event.clientX;
var y = event.clientY;
var divx1 = toolkit.offsetLeft;
var divy1 = toolkit.offsetTop;
var divx2 = toolkit.offsetLeft + toolkit.offsetWidth;
var divy2 = toolkit.offsetTop + toolkit.offsetHeight;
if (x >= divx1 && x <= divx2 && y >= divy1 && y <= divy2) {
tooltips = true;
return;
}
global.oe = document.elementFromPoint(event.x, event.y);
if (global.oe == global.tdiv) {
return;
}
tooltips = false;
global.NowNode = global.oe;
let te = 0;
let exist = 0;
let exist2 = 0;
for (let o of global.nodeList) {
if (o["node"] == global.oe) {
exist = 1;
break;
}
}
for (let o of global.nodeList) {
if (o["node"] == global.xnode) {
exist2 = 1;
break;
}
}
// console.log(oe);
if (global.xnode == null) {
global.xnode = global.oe;
}
if (global.xnode != global.oe) {
if (exist2 == 0) { //如果上个元素不在数组里,改回上个元素的初始颜色
try {
global.xnode.style.backgroundColor = global.style; //上个元素改回原来元素的背景颜色
} catch {
global.xnode.style.backgroundColor = ""; //上个元素改回原来元素的背景颜色
}
}
try {
global.xnode = global.oe.style.backgroundColor;
} catch {
global.xnode = "";
}
if (exist == 1) {
} else {
try {
global.oe.style.backgroundColor = global.defaultbgColor; //设置新元素的背景元素
} catch {}
}
global.xnode = global.oe;
global.div.style.display = "none";
}
if (mousemovebind) {
global.tdiv.style.pointerEvents = "";
}
});
//点击没反应时候的替代方案
document.onkeydown = function(event) {
// console.log("keydown");
var e = event || window.event || arguments.callee.caller.arguments[0];
if (e && e.keyCode == 118) { // 按 F7
addEl();
} else if (e && e.keyCode == 119) { //按F8
clearEl();
} else if (e && e.keyCode == 120) { //按F9
global.NowNode.focus();
global.NowNode.click();
// console.log("click",global.NowNode);
} else {
return event.keyCode;
}
};
document.addEventListener("mousedown", addEl);
toolkit.addEventListener("mousedown", function(e) { e.stopPropagation(); }); //重新定义toolkit里的点击事件
document.body.append(global.div); //默认如果toolkit不存在则div和tdiv也不存在
document.body.append(global.tdiv);
document.body.append(toolkit);
var timer;
//生成Toolkit
function generateToolkit() {
$(".tooltips").html(`
<div id="realcontent"></div>
`);
global.app = new Vue(ToolKit);
let h = $(".tooldrag").height();
let difference = 26 - h; //获得高度值差
if (difference > 0) {
$(".tooldrag").css("cssText", "height:" + (26 + difference) + "px!important")
}
timer = setInterval(function() { //时刻监测相应元素是否存在(防止出现如百度一样元素消失重写body的情况),如果不存在,添加进来
if (document.body != null && document.getElementById("wrapperToolkit") == null) {
this.clearInterval(); //先取消原来的计时器,再设置新的计时器
document.body.append(global.div); //默认如果toolkit不存在则div和tdiv也不存在
document.body.append(global.tdiv);
document.body.append(toolkit);
generateToolkit();
}
}, 3000);
}
//Vue元素
generateToolkit();
//实现提示框拖拽功能
$('.tooldrag').mousedown(function(e) {
// e.pageX
var positionDiv = $(this).offset();
var distanceX = e.pageX - positionDiv.left;
var distanceY = e.pageY - positionDiv.top;
//alert(distanceX)
// alert(positionDiv.left);
$(document).mousemove(function(e) {
var x = e.clientX - distanceX;
var y = e.clientY - distanceY;
if (x < 0) {
x = 0;
} else if (x > window.innerWidth - $('.tooldrag').outerWidth(true)) {
x = window.innerWidth - $('.tooldrag').outerWidth(true);
}
if (y < 0) {
y = 0;
} else if (y > window.innerHeight - $('.tooldrag').outerHeight(true)) {
y = window.innerHeight - $('.tooldrag').outerHeight(true);
}
$('.tooltips').css({
'right': window.innerWidth - x - $('.tooltips').outerWidth(true) + 'px',
'bottom': window.innerHeight - y - $('.tooltips').outerHeight(true) + 'px',
});
});
$(document).mouseup(function() {
$(document).off('mousemove');
});
});

View File

@ -0,0 +1,161 @@
//实现与后台和流程图部分的交互
import {global, readXPath} from "./global.js";
var startMsg = { "type": 0, msg: ""};
chrome.runtime.sendMessage(startMsg, function(response) {
console.log(response.msg);
}); //每次打开新页面的时候需要告诉后台
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request["type"] == 1){
sendResponse("回答处理结果");
}
}
);
export function input(value) {
let message = {
"type": "InputText",
"history": history.length, //记录history的长度
"tabIndex": -1,
"xpath": readXPath(global.nodeList[0]["node"], 0),
"value": value,
};
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
msg = { "type": 2, msg: value };
chrome.runtime.sendMessage(msg);
}
//点击元素操作
export function sendSingleClick() {
let message = {
"type": "singleClick",
"history": history.length, //记录history的长度
"tabIndex": -1,
"useLoop": false, //是否使用循环内元素
"xpath": readXPath(global.nodeList[0]["node"], 0),
};
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//采集单个元素
export function collectSingle() {
let message = {
"type": "singleCollect",
"history": history.length, //记录history的长度
"tabIndex": -1,
"parameters": global.outputParameters,
};
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//采集无规律多元素
export function collectMultiNoPattern() {
let message = {
"type": "multiCollectNoPattern",
"history": history.length, //记录history的长度
"tabIndex": -1,
"parameters": global.outputParameters,
};
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//采集有规律多元素
export function collectMultiWithPattern() {
//先点击选择全部然后再
let message = {
"type": "multiCollectWithPattern",
"history": history.length, //记录history的长度
"tabIndex": -1,
"loopType": 1,
"xpath": "", //默认值设置为空
"isDescendents": global.app._data.selectedDescendents, //标记是否采集的是子元素
"parameters": global.outputParameters,
};
if (!detectAllSelected()) //如果不是全部选中的话
{
message.loopType = 2; //固定元素列表
}
if (message.loopType == 1) {
message["xpath"] = global.app._data.nowPath;
} else { //固定元素列表
message["pathList"] = [];
for (let i = 0; i < global.nodeList.length; i++) {
message["pathList"].push(readXPath(global.nodeList[i]["node"], 0));
}
}
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//循环点击单个元素
export function sendLoopClickSingle(name) {
let message = {
"type": "loopClickSingle",
"history": history.length, //记录history的长度
"tabIndex": -1,
"useLoop": true, //是否使用循环内元素
"xpath": readXPath(global.nodeList[0]["node"], 0),
"loopType": 0, //循环类型0为单个元素
"nextPage": false, //是否循环点击下一页
};
if (name == "下一页元素") {
message.nextPage = true;
}
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//循环点击每个元素
export function sendLoopClickEvery() {
let message = {
"type": "loopClickEvery",
"history": history.length, //记录history的长度
"tabIndex": -1,
"xpath": "", //默认值设置为空
"useLoop": true, //是否使用循环内元素
"loopType": 1, //循环类型1为不固定元素列表
};
if (!detectAllSelected()) //如果不是全部选中的话
{
message.loopType = 2; //固定元素列表
}
if (message.loopType == 1) {
message["xpath"] = global.app._data.nowPath;
} else { //固定元素列表
//有的网站像淘宝每个元素都有一个独一无二的ID号这时候就不适用用id进行xpath定位了这个问题暂时搁置
message["pathList"] = [];
for (let i = 0; i < global.nodeList.length; i++) {
message["pathList"].push(readXPath(global.nodeList[i]["node"], 0));
}
}
let msg = { "type": 3, msg: message };
chrome.runtime.sendMessage(msg);
}
//检测是否xpath对应的元素被全选了个数判断即可
export function detectAllSelected() {
if (global.app._data.nowPath == "") {
return false;
} else {
let num = 0;
let result = document.evaluate(global.app._data.nowPath, document, null, XPathResult.ANY_TYPE, null);
var node = result.iterateNext(); //枚举第一个元素
while (node) {
// console.log(node.innerHTML);
num++;
node = result.iterateNext();
}
if (num == global.nodeList.length) {
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,713 @@
<template>
<div id="realcontent">
<div v-if="lang == 'zh'">
<div class="tooldrag">操作提示框可点此拖动</div>
<div class="realcontent">
<div v-if="page==0">
<input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"
v-on:mousedown="specialSelect"/>
<p style="margin-bottom:10px;display:inline-block">特殊点选模式</p>
<div v-if="list.nl.length==0" :style="{overflow: 'auto', maxHeight: winHeight * 0.4 + 'px'}">
<p style="color:black; margin-top: 10px"> 鼠标移动到元素上后<strong>右键</strong>点击或者按<strong>F7</strong>键选中页面元素
</p>
<p style="color:black; margin-top: 10px"> 通过鼠标左键进行点击时页面也会有反应但此点击操作不会被记录在任务流程中
</p>
<p style="color:black; margin-top: 10px"> 同理如果想输入文本框但并不想将动作记录如想要在数据模式输入密码仅此一次的操作下次加载页面已经是已登录状态可以鼠标移动到文本框并按键盘的<strong>F9</strong>进行输入</p>
<p style="color:black; margin-top: 10px"> 如果不小心左键点选了元素导致页面跳转直接后退或者切换回标签页即可</p>
{{ initial() }}
</div>
<div v-if="list.nl.length==1">
<div v-if="tname()!='null'">
已选中{{ numOfList() }}{{ tname() }}<span
v-if="numOfReady()>0&&tname()!='下一页元素'">同时发现{{ numOfReady() }}个同类元素</span>您可以:
<div class="innercontent">
<div v-if="numOfReady()>0 && !selectStatus"><a v-on:mousedown="selectAll">选中全部</a> <span
title=""></span></div>
<div v-if="existDescendents()&& !selectStatus &&(tname()=='元素' || tname()=='链接')"><a
v-on:mousedown="selectDescendents">选中子元素</a> <span title=""></span></div>
<div v-if="!selectedDescendents && !selectStatus" id="Single">
<div v-if="tname()=='选择框'"><a>循环切换下拉选项</a><span title=""></span></div>
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput">输入文字</a><span title=""></span></div>
<div v-if="tname()!='图片'"><a v-on:mousedown="getText">采集该{{ tname() }}的文本</a><span
title="采集文本"></span></div>
<div v-if="tname()=='选择框'"><a>采集选中项的文本</a><span title=""></span></div>
<div v-if="tname()=='链接'||tname()=='图片'"><a
v-on:mousedown="getLink">采集该{{ tname() }}的地址</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框'"><a
v-on:mousedown="clickElement">点击该{{ tname() }}</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框'"><a
v-on:mousedown="loopClickSingleElement">循环点击该{{ tname() }}</a><span title=""></span></div>
<div v-if="tname()=='链接'||tname()=='元素'"><a v-on:mousedown="getInnerHtml">采集该{{
tname()
}}的Inner
Html</a><span title=""></span></div>
<div><a v-on:mousedown="getOuterHtml">采集该{{ tname() }}的Outer Html</a><span title=""></span></div>
<div><a href="#">鼠标移动到该{{ tname() }}</a><span title=""></span></div>
<div v-if="tname()=='文本框'"><a>识别验证码</a><span title=""></span></div>
</div>
<div v-if="selectedDescendents" id="Single">
<div><a v-on:mousedown="confirmCollectSingle">采集数据</a><span title=""></span></div>
</div>
<div v-if="selectStatus" id="Confirm">
<div><a v-on:mousedown="confirmCollectSingle">确认采集</a><span title=""></span></div>
</div>
</div>
</div>
</div>
<div v-if="list.nl.length>1">
<div v-if="option==100">
已选择了以下元素您可以
<div class="innercontent">
<div><a v-on:mousedown="confirmCollectMulti">采集数据</a><span title=""></span></div>
<div><a v-on:mousedown="revoke">撤销本次选择</a><span title=""></span></div>
</div>
</div>
<div v-if="option!=100">
已选择了{{ numOfList() }}个同类元素<span
v-if="numOfReady()>0">另外发现{{ numOfReady() }}个同类元素</span>您可以
<div class="innercontent">
<div v-if="numOfReady()>0"><a v-on:mousedown="selectAll">选中全部</a><span title=""></span></div>
<div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
v-on:mousedown="selectDescendents">选中子元素</a><span title=""></span></div>
<div><a v-on:mousedown="confirmCollectMultiAndDescendents">采集数据</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
v-on:mousedown="loopClickEveryElement">循环点击每个{{ tname() }}</a><span title=""></span></div>
<div><a v-on:mousedown="revoke">撤销本次选择</a><span title=""></span></div>
</div>
</div>
</div>
<div v-if="valTable.length>0">
<div class="toolkitcontain">{{ setWidth("290px") }}
<table cellSpacing="0" class="toolkittb2">
<tbody>
<th v-for="i in list.opp">{{ i["name"] }}</th>
<th style="width:40px">删除</th>
</tbody>
</table>
<table cellSpacing="0" class="toolkittb4">
<tbody>
<tr v-for="i in valTable[0].length">
<td v-for="j in list.opp.length">{{ valTable[j - 1][i - 1] }}</td>
<td style="font-size: 22px!important;width:40px;cursor:pointer" v-bind:index="i-1"
v-on:mousedown="deleteSingleLine">×
</td>
</tr>
</table>
</div>
</div>
<div v-if="valTable.length==0&&tname()!='下一页元素'">{{ setWidth("290px") }}</div>
<div v-if="list.nl.length>0"
style="bottom:12px;position:absolute;color:black!important;left:17px;font-size:13px">
<div style="margin-bottom:5px">
<button v-on:mousedown="cancel">取消选择</button>
<button v-if="!selectStatus" v-on:mousedown="enlarge">扩大选区</button>
</div>
<p style="margin-left:16px;margin-bottom:0px">{{ lastElementXPath() }}</p>
</div>
</div>
<div v-if="page==1">
请输入文字
<input id="WTextBox" v-model="text" autoFocus="autofocus" type="text"></input>
<button style="margin-left:0px!important;" v-on:click="getInput">确定</button>
<button style="margin-left:0px!important;" v-on:click="cancelInput">取消</button>
<div class="innercontent">
</div>
</div>
</div>
</div>
<div v-else-if="lang=='en'">
<div class="tooldrag">Operation Toolbox (Can drag)</div>
<div class="realcontent">
<div v-if="page==0">
<input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"
v-on:mousedown="specialSelect"> </input>
<p style="margin-bottom:10px;display:inline-block">Special click mode</p>
<div v-if="list.nl.length==0" :style="{overflow: 'auto', maxHeight: winHeight * 0.4 + 'px'}">
<p style="color:black"> When your mouse moves to the element, please <strong>right-click</strong> your
mouse button or press <strong>F7</strong> on the keyboard to select it.</p>
<p style="color:black; margin-top: 10px"> When clicked with the left mouse button, the page will also respond, but this click operation will not be recorded in the task flow.</p>
<p style="color:black; margin-top: 10px"> Similarly, if you want to input in a text box but do not want the action to be recorded (such as wanting to input a password in data mode, this operation is only performed once, and the next time the page is loaded, it is already logged in), you can move the mouse to the text box and press <strong>F9</strong> on the keyboard to input.</p>
<p style="color:black; margin-top: 10px"> If you accidentally left-click on an element and cause the page to jump, simply go back or switch back to the tab.</p>
{{ initial() }}
</div>
<div v-if="list.nl.length==1">
<div v-if="tname()!='null'">
Already selected {{ numOfList() }} {{ tname() | toEng }}, <span
v-if="numOfReady()>0&&tname()!='下一页元素'"> meanwhile we find {{ numOfReady() }} element with the same type, </span>you
can:
<div class="innercontent">
<div v-if="numOfReady()>0 && !selectStatus"><a v-on:mousedown="selectAll">Select All</a><span
title=""></span></div>
<div v-if="existDescendents()&& !selectStatus &&(tname()=='元素' || tname()=='链接')"><a
v-on:mousedown="selectDescendents">Select child elements</a> <span title=""></span></div>
<div v-if="!selectedDescendents && !selectStatus" id="Single">
<!-- <div v-if="tname()=='selection box'"> <a>循环切换该下拉项</a><span title=""></span></div> -->
<div v-if="tname()=='文本框'"><a v-on:mousedown="setInput">Input Text</a><span title=""></span>
</div>
<div v-if="tname()!='图片'"><a v-on:mousedown="getText">Extract {{ tname() | toEng }}'s text</a><span
title="collect text"></span></div>
<div v-if="tname()=='选择框'"><a>Collect text from this element</a><span title=""></span>
</div>
<div v-if="tname()=='链接'||tname()=='图片'"><a v-on:mousedown="getLink">Collect address of this
{{ tname() | toEng }}</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框'"><a v-on:mousedown="clickElement">Click
this {{ tname() | toEng }}</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框'"><a
v-on:mousedown="loopClickSingleElement">Loop-click this {{ tname() | toEng }}</a><span
title=""></span>
</div>
<div v-if="tname()=='链接'||tname()=='元素'"><a v-on:mousedown="getInnerHtml">Collect Inner Html of
this {{ tname() | toEng }}</a><span title=""></span></div>
<div><a v-on:mousedown="getOuterHtml">Collect Outer Html of this element</a><span title=""></span>
</div>
<!-- <div> <a href="#">鼠标移动到该元素上----{{tname()}}-</a><span title=""></span></div> -->
<!-- <div v-if="tname()=='text box'"> <a>识别验证码</a><span title=""></span></div> -->
</div>
<div v-if="selectedDescendents" id="Single">
<div><a v-on:mousedown="confirmCollectSingle">Collect Data</a><span title=""></span></div>
</div>
<div v-if="selectStatus" id="Confirm">
<div><a v-on:mousedown="confirmCollectSingle">Confirm Collect</a><span title=""></span></div>
</div>
</div>
</div>
</div>
<div v-if="list.nl.length>1">
<div v-if="option==100">
Already selected the following element, you can:
<div class="innercontent">
<div><a v-on:mousedown="confirmCollectMulti">Collect Data</a><span title=""></span></div>
<div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
</div>
</div>
<div v-if="option!=100">
Already selected {{ numOfList() }} similar elements, <span
v-if="numOfReady()>0">and we find other{{ numOfReady() }} similar elements, </span>you can:
<div class="innercontent">
<div v-if="numOfReady()>0"><a v-on:mousedown="selectAll">Select All</a><span title=""></span></div>
<div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
v-on:mousedown="selectDescendents">Select child elements</a><span title=""></span></div>
<div><a v-on:mousedown="confirmCollectMultiAndDescendents">Collect Data</a><span title=""></span></div>
<div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
v-on:mousedown="loopClickEveryElement">Loop-click every {{ tname() | toEng}}</a><span title=""></span>
</div>
<div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
</div>
</div>
</div>
<div v-if="valTable.length>0">
<div class="toolkitcontain">{{ setWidth("350px") }}
<table cellspacing="0" class="toolkittb2">
<tbody>
<th v-for="i in list.opp">{{ i["name"] }}</th>
<th style="width:40px">Delete</th>
</tbody>
</table>
<table cellspacing="0" class="toolkittb4">
<tbody>
<tr v-for="i in valTable[0].length">
<td v-for="j in list.opp.length">{{ valTable[j - 1][i - 1] }}</td>
<td style="font-size: 22px!important;width:40px;cursor:pointer" v-bind:index="i-1"
v-on:mousedown="deleteSingleLine">×
</td>
</tr>
</table>
</div>
</div>
<div v-if="valTable.length==0&&tname()!='下一页元素'">{{ setWidth("290px") }}</div>
<div v-if="list.nl.length>0"
style="bottom:12px;position:absolute;color:black!important;left:17px;font-size:13px">
<div style="margin-bottom:5px">
<button v-on:mousedown="cancel">Deselect</button>
<button v-if="!selectStatus" v-on:mousedown="enlarge">Expand Path</button>
</div>
<p style="margin-left:16px;margin-bottom:0px">{{ lastElementXPath() }}</p>
</div>
</div>
<div v-if="page==1">
Please input text:
<input id="WTextBox" v-model="text" autofocus="autofocus" type="text"></input>
<button style="margin-left:0px!important;" v-on:click="getInput">Confirm</button>
<button style="margin-left:0px!important;" v-on:click="cancelInput">Cancel</button>
<div class="innercontent">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
global,
getOS,
readXPath,
addEl,
clearEl,
clearReady,
handleElement,
clearParameters,
generateParameters,
generateMultiParameters,
handleDescendents,
generateValTable,
findRelated,
pushToReadyList,
readyToList,
combineXpath,
relatedTest
} from "./global.js";
import {
input,
sendSingleClick,
collectSingle,
collectMultiNoPattern,
collectMultiWithPattern,
sendLoopClickSingle,
sendLoopClickEvery,
detectAllSelected
} from "./messageInteraction.js";
import $ from "jquery";
export default {
el: '#realcontent',
data: {
lang: global.lang,
option: 0,
list: {nl: global.nodeList, opp: global.outputParameters},
valTable: [], //
special: false, //
selectedDescendents: false, //
selectStatus: false, //
page: 0, //1
text: "", //
tNodeName: "", //
nowPath: "", //xpath
winHeight: window.outerHeight,
},
mounted(){
this.$nextTick(() => {
window.addEventListener('resize', this.onResize);
});
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize);
},
watch: {
nowPath: { //
handler: function (newVal, oldVal) {
console.log("xpath:", newVal);
}
}
},
filters: {
toEng: function (value) {
if (value == "下一页元素") {
return "elements in next page";
} else if (value == "链接") {
return "link";
} else if (value == "图片") {
return "image";
} else if (value == "按钮") {
return "button";
} else if (value == "文本框") {
return "text box";
} else if (value == "选择框") {
return "selection box";
} else {
return "element";
}
}
},
methods: {
onResize() {
this.winHeight = window.outerHeight
},
initial: function () { //0
this.selectedDescendents = false;
this.selectStatus = false;
this.nowPath = "";
},
confirmCollectSingle: function () { //
collectSingle();
clearEl();
},
confirmCollectMulti: function () { //
collectMultiNoPattern();
clearEl();
},
confirmCollectMultiAndDescendents: function () { //
collectMultiWithPattern();
clearEl();
},
deleteSingleLine: function (event) { //
let at = new Date().getTime()
//使
let index = event.target.getAttribute("index");
let tnode = global.nodeList.splice(index, 1)[0]; //
tnode["node"].style.backgroundColor = tnode["bgColor"];
tnode["node"].style.boxShadow = tnode["boxShadow"];
if (global.nodeList.length > 1) { //
handleElement();
if (this.selectedDescendents) {
handleDescendents(); //
}
} else {
this.valTable = [];
this.selectStatus = false;
clearParameters(); //
}
let at2 = parseInt(new Date().getTime());
console.log("delete:", at2, at, at2 - at);
},
clickElement: function () { //
sendSingleClick();
//
global.nodeList[0]["node"].focus(); //
global.nodeList[0]["node"].click(); //
clearEl();
},
loopClickSingleElement: function () { //
sendLoopClickSingle(this.tname()); //,
if (this.tname() != "下一页元素") { //
global.nodeList[0]["node"].focus(); //
global.nodeList[0]["node"].click(); //
}
clearEl();
},
loopClickEveryElement: function () { //
sendLoopClickEvery(); //,
global.nodeList[0]["node"].focus(); //
global.nodeList[0]["node"].click(); //
clearEl();
},
setInput: function () { //
this.page = 1;
this.$nextTick(function () { //
document.getElementById("WTextBox").focus();
})
},
getInput: function () { //
global.nodeList[0]["node"].focus(); //
if (getOS() == "Mac") {
global.nodeList[0]["node"].setAttribute("value", this.text); // box
} else{
global.nodeList[0]["node"].setAttribute("value", ""); // box
}
input(this.text); //
this.text = "";
clearEl();
},
cancelInput: function () {
this.page = 0;
},
setWidth: function (width) { //
$(".tooltips").css("width", width);
return "";
},
getText: function () { //
generateParameters(0, true, false);
this.selectStatus = true;
clearReady();
},
getLink: function () { //
generateParameters(0, false, true);
this.selectStatus = true;
clearReady();
},
getOuterHtml: function () { //OuterHtml
generateParameters(3, true, false);
this.selectStatus = true;
clearReady();
},
getInnerHtml: function () { //InnerHtml
generateParameters(2, true, false);
this.selectStatus = true;
clearReady();
},
tname: function () {
let tag = global.nodeList.length == 0 ? "" : global.nodeList[0]["node"].tagName;
let inputType = global.nodeList.length == 0 ? "" : global.nodeList[0]["node"].getAttribute("type");
if (inputType != null) { //typetext
inputType = inputType.toLowerCase();
} else {
inputType = "text";
}
if (tag == "") {
return "null";
} else if ($(global.nodeList[0]["node"]).contents().filter(function () {
return this.nodeType === 3;
}).text().indexOf("下一页") >= 0) {
this.setWidth("280px");
return "下一页元素";
} else if (tag == "A") {
return "链接";
} else if (tag == "IMG") {
return "图片";
} else if (tag == "BUTTON" || (tag == "INPUT" && (inputType == "button" || inputType == "submit"))) {
return "按钮";
} else if (tag == "TEXTAREA" || (tag == "INPUT" && (inputType != "checkbox" || inputType != "ratio"))) { //
return "文本框";
} else if (tag == "SELECT") {
return "选择框";
} else {
return "元素";
}
},
existDescendents: function () { //,
return global.nodeList.length > 0 && global.nodeList[0]["node"].children.length > 0 && !this.selectedDescendents;
},
numOfReady: function () {
return global.readyList.length;
},
numOfList: function () {
return global.nodeList.length;
},
lastElementXPath: function () { //5xpath
let path = global.nodeList[global.nodeList.length - 1]["xpath"];
path = path.split("/");
let tp = "";
if (path.length > 5) { //
path = path.splice(path.length - 5, 5);
tp = ".../"
}
for (let i = 0; i < path.length; i++) {
path[i] = path[i].split("[")[0];
}
path = path.join("/");
path = "Path: " + tp + path;
return path;
},
cancel: function () {
clearEl();
},
specialSelect: function () { //
if (mousemovebind) {
global.tdiv.style.pointerEvents = "none";
this.special = false;
} else {
this.special = true;
}
mousemovebind = !mousemovebind;
},
enlarge: function () { //
if (global.nodeList[global.nodeList.length - 1]["node"].tagName != "BODY") {
global.nodeList[global.nodeList.length - 1]["node"].style.backgroundColor = global.nodeList[global.nodeList.length - 1]["bgColor"]; //
global.nodeList[global.nodeList.length - 1]["node"].style.boxShadow = global.nodeList[global.nodeList.length - 1]["boxShadow"]; //
let tNode = global.nodeList[global.nodeList.length - 1]["node"].parentNode; //
let sty;
if (tNode != global.NowNode) { //
sty = tNode.style.backgroundColor;
} else {
sty = global.style;
}
global.nodeList[global.nodeList.length - 1]["node"] = tNode;
global.nodeList[global.nodeList.length - 1]["bgColor"] = sty;
global.nodeList[global.nodeList.length - 1]["xpath"] = readXPath(tNode, 1);
//
var pos = tNode.getBoundingClientRect();
global.div.style.display = "block";
global.div.style.height = tNode.offsetHeight + "px";
global.div.style.width = tNode.offsetWidth + "px";
global.div.style.left = pos.left + "px";
global.div.style.top = pos.top + "px";
global.div.style.zIndex = 2147483645;
global.div.style.pointerEvents = "none";
handleElement(); //
global.oe = tNode;
tNode.style.backgroundColor = "rgba(0,191,255,0.5)";
this.selectedDescendents = false;
}
},
selectAll: function () { //
global.step++;
readyToList(global.step, false);
handleElement();
if (this.selectedDescendents) {
handleDescendents(); //
}
},
revoke: function () { //
let tstep = global.step;
global.step--; //-1
console.log(global.step, global.nodeList)
while (tstep == global.nodeList[global.nodeList.length - 1]["step"]) //
{
let node = global.nodeList.splice(global.nodeList.length - 1, 1)[0]; //
node["node"].style.backgroundColor = node["bgColor"]; //
node["node"].style.boxShadow = node["boxShadow"];
if (global.NowNode == node["node"]) {
global.style = node["bgColor"];
}
//
// if (this.selectedDescendents) {
clearParameters(); //
// }
}
handleElement(); //
},
selectDescendents: function () { //
handleDescendents();
}
},
}
</script>
<style>
.tooltips {
width: 330px;
min-height: 300px;
background-color: white;
position: fixed;
z-index: 2147483647;
right: 30px;
bottom: 30px;
font-size: 13px !important;
font-weight: normal !important;
border: solid navy 2px;
-webkit-user-select: none;
/* 文字不可被选中 */
}
.tooldrag {
background-color: navy;
width: 100%;
text-align: center;
font-size: 13px;
height: 26px !important;
padding-top: 8px !important;
color: white;
}
.realcontent {
text-align: left;
padding-top: 10px !important;
padding-bottom: 80px !important;
padding-left: 20px !important;
padding-right: 10px !important;
}
.innercontent {
text-align: left;
padding-top: 5px !important;
padding-left: 12px !important;
}
.innercontent a {
display: inline-block;
text-decoration: none;
margin-top: 2px !important;
font-size: 13px;
color: navy !important;
cursor: pointer;
text-decoration: none !important;
}
.innercontent a:hover {
color: blue !important;
}
.innercontent span {
font-size: 20px;
color: navy;
line-height: normal;
padding-left: 5px !important;
}
.tooltips button {
margin-top: 7px !important;
font-size: 13px;
border-radius: 5px;
border: solid 2px navy;
background-color: white;
color: navy;
width: 100px;
height: 30px;
cursor: pointer;
margin-left: 15px !important;
}
.tooltips input[type=text] {
display: block;
margin-top: 7px !important;
padding-left: 5px !important;
margin-bottom: 7px !important;
font-size: 15px;
border-radius: 5px;
border: solid 2px navy;
width: 204px;
height: 30px;
}
.tooltips button:hover {
color: blue;
}
/* 下面用来对冻结表格首行元素和固定表格宽度和高度设定样式 */
.toolkitcontain {
border: 1px solid #cdd !important;
/*width: 280px !important;*/
/* 上面的宽度设定很重要 */
height: 150px;
overflow: auto;
margin-top: 10px !important;
position: relative;
}
.toolkitcontain table {
table-layout: fixed;
border-spacing: 0!important;
word-break: break-all;
word-wrap: break-word;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
.toolkitcontain th,
.toolkitcontain td,
.toolkitcontain tr {
border: 1px solid rgb(78, 78, 78) !important;
height: 25px !important;
width: 100px !important;
text-align: center !important;
font-weight: normal !important;
overflow: hidden !important;
font-size: 11px !important;
padding-left: 1px !important;
padding-right: 0px !important;
padding-top: 0px !important;
padding-bottom: 0px !important;
vertical-align: middle!important;
}
.toolkitcontain .toolkittb2 {
position: sticky;
top: 0px;
margin-bottom: 0px;
background-color: azure;
z-index: 1000;
}
.toolkitcontain .toolkittb4 {
position: absolute;
}
</style>

8
Extension/manifest_v31/src/env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -0,0 +1,47 @@
{
"name": "EasySpider",
"version": "0.2",
"description": "EasySpider's chrome extension",
"author": "Naibo Wang",
"manifest_version": 3,
"action": {
"default_icon": {
"16": "assets/icon-16.png",
"19": "assets/icon-19.png",
"38": "assets/icon-38.png",
"128": "assets/icon-128.png"
},
"default_title": "EasySpider"
},
"icons": {
"16": "assets/icon-16.png",
"38": "assets/icon-38.png",
"128": "assets/icon-128.png"
},
"background": {
"service_worker": "background.ts"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": ["content-scripts/main.js"],
"run_at": "document_end",
"all_frames": false
}
],
"web_accessible_resources": [
{
"resources": [ "*/*.js" ],
"matches": [
"http://*/*",
"https://*/*"
]
}
],
"permissions": [
"identity", "storage", "tabs"
]
}

View File

@ -0,0 +1,23 @@
{
"defaultSeverity": "none",
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"noImplicitAny": false,
"checkJs": false,
"lib": ["esnext", "dom"],
"types": [
"@types/chrome",
"@types/node",
"@types/fs-extra"
]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "scripts/firebase.ts", "scripts/firebase.ts"]
}