239 lines
6.7 KiB
JavaScript
239 lines
6.7 KiB
JavaScript
/**
|
||
* 麦腾后台菜单爬虫脚本
|
||
* 用于自动探索和记录麦腾智能管理平台的所有菜单功能
|
||
*
|
||
* 使用方法:
|
||
* 1. 在浏览器中打开麦腾后台并登录
|
||
* 2. 打开浏览器开发者工具 (F12)
|
||
* 3. 切换到 Console 标签
|
||
* 4. 复制粘贴此脚本并运行
|
||
* 5. 等待探索完成后,复制输出的 JSON 数据
|
||
*/
|
||
|
||
class MaitengCrawler {
|
||
constructor() {
|
||
this.discoveredMenus = [];
|
||
this.discoveredPages = [];
|
||
this.visitedUrls = new Set();
|
||
this.delayBetweenActions = 1000; // 毫秒
|
||
}
|
||
|
||
// 等待函数
|
||
async wait(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
// 获取当前页面的菜单结构
|
||
getMenuStructure() {
|
||
const menus = [];
|
||
const menuItems = document.querySelectorAll('[role="menuitem"], .ant-menu-item, .menu-item');
|
||
|
||
menuItems.forEach((item, index) => {
|
||
const menuInfo = {
|
||
id: index,
|
||
text: item.textContent?.trim() || '',
|
||
element: item,
|
||
href: item.querySelector('a')?.href || '',
|
||
isExpanded: item.classList?.contains('expanded') || false,
|
||
isCollapsed: item.classList?.contains('collapsed') || false,
|
||
hasSubmenu: item.querySelector('ul, [role="menu"]') !== null
|
||
};
|
||
|
||
if (menuInfo.text) {
|
||
menus.push(menuInfo);
|
||
}
|
||
});
|
||
|
||
return menus;
|
||
}
|
||
|
||
// 获取当前页面信息
|
||
getCurrentPageInfo() {
|
||
return {
|
||
url: window.location.href,
|
||
title: document.title,
|
||
timestamp: new Date().toISOString(),
|
||
pageElements: this.getPageElements()
|
||
};
|
||
}
|
||
|
||
// 获取页面关键元素
|
||
getPageElements() {
|
||
const elements = {
|
||
buttons: [],
|
||
inputs: [],
|
||
tables: [],
|
||
filters: []
|
||
};
|
||
|
||
// 获取按钮
|
||
document.querySelectorAll('button, [role="button"]').forEach((btn, idx) => {
|
||
elements.buttons.push({
|
||
id: idx,
|
||
text: btn.textContent?.trim() || '',
|
||
disabled: btn.disabled || false
|
||
});
|
||
});
|
||
|
||
// 获取输入框
|
||
document.querySelectorAll('input, textarea, select').forEach((input, idx) => {
|
||
elements.inputs.push({
|
||
id: idx,
|
||
type: input.tagName?.toLowerCase() || '',
|
||
placeholder: input.placeholder || '',
|
||
name: input.name || ''
|
||
});
|
||
});
|
||
|
||
// 获取表格
|
||
document.querySelectorAll('table').forEach((table, idx) => {
|
||
elements.tables.push({
|
||
id: idx,
|
||
rowCount: table.querySelectorAll('tr')?.length || 0
|
||
});
|
||
});
|
||
|
||
return elements;
|
||
}
|
||
|
||
// 尝试展开菜单
|
||
async expandMenu(menuElement) {
|
||
try {
|
||
menuElement.click();
|
||
await this.wait(this.delayBetweenActions);
|
||
return true;
|
||
} catch (error) {
|
||
console.log('展开菜单失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 点击菜单项并记录页面
|
||
async clickMenuItem(menuItem) {
|
||
try {
|
||
if (menuItem.href && !menuItem.href.startsWith('javascript:')) {
|
||
if (!this.visitedUrls.has(menuItem.href)) {
|
||
this.visitedUrls.add(menuItem.href);
|
||
window.location.href = menuItem.href;
|
||
await this.wait(3000); // 等待页面加载
|
||
|
||
const pageInfo = this.getCurrentPageInfo();
|
||
this.discoveredPages.push(pageInfo);
|
||
console.log('发现新页面:', pageInfo.title);
|
||
|
||
// 返回上一页继续探索
|
||
window.history.back();
|
||
await this.wait(this.delayBetweenActions);
|
||
}
|
||
} else if (menuItem.element) {
|
||
menuItem.element.click();
|
||
await this.wait(this.delayBetweenActions);
|
||
|
||
const pageInfo = this.getCurrentPageInfo();
|
||
if (!this.visitedUrls.has(pageInfo.url)) {
|
||
this.visitedUrls.add(pageInfo.url);
|
||
this.discoveredPages.push(pageInfo);
|
||
console.log('发现新页面:', pageInfo.title);
|
||
}
|
||
}
|
||
return true;
|
||
} catch (error) {
|
||
console.log('点击菜单失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 主爬虫函数
|
||
async crawl() {
|
||
console.log('🚀 开始麦腾后台菜单爬虫...');
|
||
console.log('当前页面:', window.location.href);
|
||
|
||
// 获取初始菜单结构
|
||
const initialMenus = this.getMenuStructure();
|
||
console.log('发现菜单数量:', initialMenus.length);
|
||
this.discoveredMenus = initialMenus;
|
||
|
||
// 尝试展开所有可展开的菜单
|
||
for (const menu of initialMenus) {
|
||
if (menu.isCollapsed && menu.hasSubmenu) {
|
||
console.log('尝试展开菜单:', menu.text);
|
||
await this.expandMenu(menu.element);
|
||
}
|
||
}
|
||
|
||
// 重新获取展开后的菜单结构
|
||
const expandedMenus = this.getMenuStructure();
|
||
console.log('展开后菜单数量:', expandedMenus.length);
|
||
this.discoveredMenus = expandedMenus;
|
||
|
||
// 记录当前页面信息
|
||
const currentPage = this.getCurrentPageInfo();
|
||
this.discoveredPages.push(currentPage);
|
||
this.visitedUrls.add(currentPage.url);
|
||
|
||
console.log('\n✅ 爬虫完成!');
|
||
console.log('\n📊 发现的菜单:');
|
||
this.discoveredMenus.forEach((menu, idx) => {
|
||
console.log(`${idx + 1}. ${menu.text} ${menu.isExpanded ? '[展开]' : menu.isCollapsed ? '[折叠]' : ''}`);
|
||
});
|
||
|
||
console.log('\n📄 发现的页面:');
|
||
this.discoveredPages.forEach((page, idx) => {
|
||
console.log(`${idx + 1}. ${page.title} - ${page.url}`);
|
||
});
|
||
|
||
// 返回完整数据
|
||
return {
|
||
menus: this.discoveredMenus.map(m => ({
|
||
text: m.text,
|
||
href: m.href,
|
||
isExpanded: m.isExpanded,
|
||
isCollapsed: m.isCollapsed
|
||
})),
|
||
pages: this.discoveredPages.map(p => ({
|
||
url: p.url,
|
||
title: p.title,
|
||
timestamp: p.timestamp,
|
||
buttons: p.pageElements.buttons.map(b => b.text),
|
||
inputs: p.pageElements.inputs.map(i => i.placeholder || i.name)
|
||
}))
|
||
};
|
||
}
|
||
|
||
// 导出数据为JSON
|
||
exportToJSON() {
|
||
const data = {
|
||
crawlTime: new Date().toISOString(),
|
||
menus: this.discoveredMenus,
|
||
pages: this.discoveredPages
|
||
};
|
||
|
||
const jsonStr = JSON.stringify(data, null, 2);
|
||
console.log('\n📋 完整JSON数据:');
|
||
console.log(jsonStr);
|
||
|
||
// 下载到文件
|
||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `maiteng-crawl-${Date.now()}.json`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
|
||
console.log('\n💾 JSON文件已下载!');
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
console.log('=== 麦腾后台菜单爬虫 ===');
|
||
console.log('请运行: const crawler = new MaitengCrawler(); const result = await crawler.crawl(); crawler.exportToJSON();');
|
||
|
||
// 自动运行(可选)
|
||
// (async () => {
|
||
// const crawler = new MaitengCrawler();
|
||
// const result = await crawler.crawl();
|
||
// crawler.exportToJSON();
|
||
// })();
|
||
|