content.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /**
  2. * 侧边栏管理类
  3. */
  4. class SidebarManager {
  5. /**
  6. * @constant {Object} 配置常量
  7. */
  8. static CONFIG = {
  9. SIDEBAR_ID: "paiwise-sidebar",
  10. WRAPPER_ID: "paiwise-main-wrapper",
  11. STORAGE_KEY: "sidebarOpen",
  12. };
  13. constructor() {
  14. this.sidebarId = SidebarManager.CONFIG.SIDEBAR_ID;
  15. this.wrapperId = SidebarManager.CONFIG.WRAPPER_ID;
  16. this.isOpen = false;
  17. this.currentUrl = window.location.href;
  18. this.currentTitle = document.title;
  19. this.init();
  20. }
  21. /**
  22. * 初始化侧边栏
  23. */
  24. async init() {
  25. try {
  26. await this.checkAndRestoreState();
  27. this.setupEventListeners();
  28. } catch (error) {
  29. console.error("Sidebar initialization failed:", error);
  30. }
  31. }
  32. /**
  33. * 设置事件监听器
  34. */
  35. setupEventListeners() {
  36. // 监听页面加载完成事件
  37. window.addEventListener("load", () => this.checkAndRestoreState());
  38. // 监听来自 sidebar 的消息
  39. window.addEventListener("message", this.handleMessage.bind(this));
  40. // 监听窗口大小变化
  41. window.addEventListener(
  42. "resize",
  43. Utils.debounce(() => this.handleResize(), 100)
  44. );
  45. // 监听页面可见性变化
  46. document.addEventListener("visibilitychange", () => {
  47. if (!document.hidden) {
  48. this.checkAndRestoreState();
  49. }
  50. });
  51. // 监听 history 变化
  52. window.addEventListener("popstate", () => {
  53. this.checkAndRestoreState();
  54. });
  55. // 监听标题变化
  56. const titleObserver = new MutationObserver(() => {
  57. if (document.title !== this.currentTitle) {
  58. this.currentTitle = document.title;
  59. this.updatePageInfo();
  60. }
  61. });
  62. titleObserver.observe(
  63. document.querySelector("head > title") || document.head,
  64. {
  65. subtree: true,
  66. characterData: true,
  67. childList: true,
  68. }
  69. );
  70. const simulateUserInput = (element, value) => {
  71. // 设置值
  72. element.value = value;
  73. // 创建并触发 input 事件
  74. const inputEvent = new Event('input', { bubbles: true });
  75. element.dispatchEvent(inputEvent);
  76. // 创建并触发 change 事件
  77. const changeEvent = new Event('change', { bubbles: true });
  78. element.dispatchEvent(changeEvent);
  79. // 可选:如果表单使用了 React/Vue 等框架,可能还需要触发以下事件
  80. element.dispatchEvent(new Event('blur'));
  81. element.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
  82. }
  83. // 监听页面 URL 变化
  84. new MutationObserver(() => {
  85. const url = location.href;
  86. if (url !== this.currentUrl) {
  87. this.currentUrl = url;
  88. this.updatePageInfo();
  89. this.checkAndRestoreState();
  90. }
  91. }).observe(document, { subtree: true, childList: true });
  92. const handleFillInput = (data, i) => {
  93. data.forEach(item => {
  94. if (item.id) {
  95. const input = formChildren.find(child => child.id === item.id)
  96. console.log(input);
  97. if (item.type === 'text') {
  98. if (input) {
  99. simulateUserInput(input, excelDataA[item.excelColumn][i])
  100. }
  101. }
  102. } else if (item.findBy === 'placeholder') {
  103. const input = formChildren.find(child => child.placeholder === item.findByValue)
  104. if (input) {
  105. input.value = excelDataA[item.excelColumn][i]
  106. }
  107. }
  108. })
  109. }
  110. let formChildren = []
  111. let excelDataA = []
  112. // 监听来自sidebar的消息
  113. window.addEventListener("message", async (event) => {
  114. if (event.data.type === "COPY_TO_CLIPBOARD") {
  115. try {
  116. await navigator.clipboard.writeText(event.data.text);
  117. // 可选:发送成功消息回sidebar
  118. event.source.postMessage(
  119. {
  120. type: "COPY_SUCCESS",
  121. },
  122. "*"
  123. );
  124. } catch (err) {
  125. console.error("Failed to copy text:", err);
  126. // 可选:发送失败消息回sidebar
  127. event.source.postMessage(
  128. {
  129. type: "COPY_ERROR",
  130. error: err.message,
  131. },
  132. "*"
  133. );
  134. }
  135. }
  136. if (event.data.type === "HANDLE_FILL_INPUT") {
  137. console.log(event.data.data)
  138. const { formData, excelData } = event.data.data
  139. excelDataA = excelData
  140. console.log(formChildren, excelDataA);
  141. handleFillInput(formData, 0)
  142. }
  143. });
  144. // 监听来自iframe的消息
  145. window.addEventListener("message", (event) => {
  146. // 确保消息来自我们的iframe
  147. if (
  148. event.source ===
  149. document.getElementById("paiwise-sidebar")?.contentWindow
  150. ) {
  151. if (event.data.type === "FILL_INPUT") {
  152. event.source.postMessage(
  153. {
  154. type: "GE_T",
  155. pageInfo: pageInfo,
  156. },
  157. "*"
  158. );
  159. }
  160. if (event.data.type === "ANALYZE_PAGE") {
  161. const form = document.querySelectorAll("form")[1];
  162. formChildren = [...form.elements]
  163. // 分析页面并返回结果
  164. const pageInfo = window.pageAnalyzer.analyzePage();
  165. // 发送分析结果回iframe
  166. event.source.postMessage(
  167. {
  168. type: "PAGE_ANALYSIS_RESULT",
  169. pageInfo: form.outerHTML,
  170. },
  171. "*"
  172. );
  173. }
  174. }
  175. });
  176. }
  177. handleUserName(input) {
  178. return input.placeholder.includes('账号') || input.placeholder.includes('用户名')
  179. }
  180. /**
  181. * 处理接收到的消息
  182. * @param {MessageEvent} event
  183. */
  184. handleMessage(event) {
  185. if (event.data.action === "closeSidebar") {
  186. this.removeSidebar();
  187. }
  188. }
  189. /**
  190. * 处理窗口大小变化
  191. */
  192. handleResize() {
  193. const sidebar = document.getElementById(this.sidebarId);
  194. if (sidebar && this.isOpen) {
  195. sidebar.style.height = `${window.innerHeight}px`;
  196. }
  197. }
  198. /**
  199. * 检查并恢复侧边栏状态
  200. */
  201. async checkAndRestoreState() {
  202. try {
  203. const isOpen = await Utils.getStorageData(
  204. SidebarManager.CONFIG.STORAGE_KEY
  205. );
  206. if (isOpen && !this.isOpen) {
  207. // 只有当应该打开且当前未打开时才创建
  208. this.createSidebar();
  209. } else if (!isOpen && this.isOpen) {
  210. // 只有当应该关闭且当前打开时才移除
  211. this.removeSidebar();
  212. }
  213. } catch (error) {
  214. console.error("Failed to restore sidebar state:", error);
  215. }
  216. }
  217. /**
  218. * 包装页面内容
  219. */
  220. wrapPageContent() {
  221. if (document.getElementById(this.wrapperId)) return;
  222. const wrapper = document.createElement("div");
  223. wrapper.id = this.wrapperId;
  224. // 保存body的原始样式
  225. this.originalBodyStyle = {
  226. width: document.body.style.width,
  227. margin: document.body.style.margin,
  228. position: document.body.style.position,
  229. overflow: document.body.style.overflow,
  230. };
  231. // 包装内容
  232. while (document.body.firstChild) {
  233. if (document.body.firstChild.id !== this.sidebarId) {
  234. wrapper.appendChild(document.body.firstChild);
  235. } else {
  236. document.body.removeChild(document.body.firstChild);
  237. }
  238. }
  239. document.body.appendChild(wrapper);
  240. }
  241. /**
  242. * 解除页面内容包装
  243. */
  244. unwrapPageContent() {
  245. const wrapper = document.getElementById(this.wrapperId);
  246. if (!wrapper) return;
  247. // 恢复body的原始样式
  248. if (this.originalBodyStyle) {
  249. Object.assign(document.body.style, this.originalBodyStyle);
  250. }
  251. while (wrapper.firstChild) {
  252. document.body.insertBefore(wrapper.firstChild, wrapper);
  253. }
  254. wrapper.remove();
  255. }
  256. /**
  257. * 发送页面信息到iframe
  258. */
  259. sendPageInfo() {
  260. this.updatePageInfo();
  261. }
  262. /**
  263. * 更新页面信息
  264. */
  265. updatePageInfo() {
  266. const iframe = document.getElementById(this.sidebarId);
  267. if (!iframe) return;
  268. // 获取最新的favicon
  269. const favicon = this.getFavicon();
  270. // 发送更新后的页面信息
  271. iframe.contentWindow.postMessage(
  272. {
  273. type: "PAGE_INFO",
  274. pageInfo: {
  275. favicon,
  276. title: document.title,
  277. url: window.location.href,
  278. iframe: window.pageAnalyzer.analyzePage()
  279. },
  280. },
  281. "*"
  282. );
  283. }
  284. /**
  285. * 获取最新的favicon
  286. */
  287. getFavicon() {
  288. // 尝试获取动态favicon
  289. const iconLinks = Array.from(
  290. document.querySelectorAll('link[rel*="icon"]')
  291. );
  292. const favicon = iconLinks
  293. .sort((a, b) => {
  294. // 优先使用大尺寸图标
  295. const sizeA = parseInt(a.sizes?.value) || 0;
  296. const sizeB = parseInt(b.sizes?.value) || 0;
  297. return sizeB - sizeA;
  298. })
  299. .map((link) => link.href)
  300. .find(Boolean);
  301. if (favicon) return favicon;
  302. // 如果没有找到,返回网站根目录的favicon.ico
  303. const url = new URL(window.location.href);
  304. return `${url.protocol}//${url.hostname}/favicon.ico`;
  305. }
  306. /**
  307. * 创建侧边栏
  308. */
  309. async createSidebar() {
  310. if (document.getElementById(this.sidebarId)) return;
  311. try {
  312. this.wrapPageContent();
  313. const iframe = document.createElement("iframe");
  314. iframe.id = this.sidebarId;
  315. iframe.src = chrome.runtime.getURL("sidebar.html");
  316. // 先添加到DOM,但不添加show类
  317. document.body.appendChild(iframe);
  318. // 等待iframe加载完成
  319. await new Promise((resolve) => {
  320. iframe.onload = () => {
  321. // 发送页面信息
  322. this.sendPageInfo();
  323. resolve();
  324. };
  325. });
  326. // 等待一帧以确保DOM更新
  327. await new Promise((resolve) => requestAnimationFrame(resolve));
  328. // 触发动画
  329. document.body.classList.add("sidebar-open");
  330. iframe.classList.add("show");
  331. // 等待动画完成
  332. await new Promise((resolve) => {
  333. iframe.addEventListener("transitionend", resolve, { once: true });
  334. });
  335. this.isOpen = true;
  336. await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, true);
  337. } catch (error) {
  338. console.error("Failed to create sidebar:", error);
  339. this.unwrapPageContent();
  340. }
  341. }
  342. /**
  343. * 移除侧边栏
  344. */
  345. async removeSidebar() {
  346. if (!this.isOpen) return;
  347. try {
  348. const sidebar = document.getElementById(this.sidebarId);
  349. if (sidebar) {
  350. document.body.classList.remove("sidebar-open");
  351. sidebar.classList.remove("show");
  352. // 等待动画完成后再移除元素
  353. await new Promise((resolve) => {
  354. sidebar.addEventListener(
  355. "transitionend",
  356. () => {
  357. this.unwrapPageContent();
  358. sidebar.remove();
  359. resolve();
  360. },
  361. { once: true }
  362. );
  363. });
  364. }
  365. this.isOpen = false;
  366. await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, false);
  367. } catch (error) {
  368. console.error("Failed to remove sidebar:", error);
  369. }
  370. }
  371. /**
  372. * 切换侧边栏显示状态
  373. */
  374. toggle() {
  375. if (this.isOpen) {
  376. this.removeSidebar();
  377. } else {
  378. this.createSidebar();
  379. }
  380. }
  381. }
  382. // 初始化侧边栏管理器
  383. const sidebarManager = new SidebarManager();
  384. // 监听来自背景脚本的消息
  385. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  386. if (message.action === "toggleSidebar") {
  387. sidebarManager.toggle();
  388. sendResponse({ success: true });
  389. }
  390. sendResponse({ success: true });
  391. });
  392. window.onload = () => {
  393. inputs = [...document.querySelectorAll('input')]
  394. }
  395. let inputs = []
  396. // 创建一个新的js文件用于处理侧边栏内部的关闭按钮事件
  397. const simulateCompleteUserAction = async (clickElement, inputElement, inputText, tdTitle) => {
  398. // 1. 模拟鼠标弹起事件
  399. const simulateMouseUp = (element) => {
  400. const mouseUpEvent = new MouseEvent('mouseup', {
  401. bubbles: true,
  402. cancelable: true,
  403. view: window,
  404. button: 0,
  405. buttons: 0,
  406. clientX: 0,
  407. clientY: 0,
  408. detail: 1
  409. });
  410. element.dispatchEvent(mouseUpEvent);
  411. };
  412. // 2. 模拟键盘输入
  413. const simulateTyping = async (element, text, delay = 50) => {
  414. element.focus();
  415. for (let char of text) {
  416. await new Promise(resolve => setTimeout(resolve, delay));
  417. // 按键按下
  418. const keydownEvent = new KeyboardEvent('keydown', {
  419. key: char,
  420. code: `Key${char.toUpperCase()}`,
  421. bubbles: true,
  422. cancelable: true
  423. });
  424. element.dispatchEvent(keydownEvent);
  425. // 更新输入值
  426. element.value += char;
  427. // 触发输入事件
  428. const inputEvent = new InputEvent('input', {
  429. bubbles: true,
  430. cancelable: true,
  431. data: char,
  432. inputType: 'insertText'
  433. });
  434. element.dispatchEvent(inputEvent);
  435. // 按键弹起
  436. const keyupEvent = new KeyboardEvent('keyup', {
  437. key: char,
  438. code: `Key${char.toUpperCase()}`,
  439. bubbles: true,
  440. cancelable: true
  441. });
  442. element.dispatchEvent(keyupEvent);
  443. }
  444. // 触发change事件
  445. element.dispatchEvent(new Event('change', { bubbles: true }));
  446. };
  447. // 3. 查找td元素
  448. const findTdByTitle = (title, timeout = 5000) => {
  449. return new Promise((resolve, reject) => {
  450. const startTime = Date.now();
  451. const find = () => {
  452. const td = document.querySelector(`td[title="${title}"]`);
  453. if (td) {
  454. resolve(td);
  455. return;
  456. }
  457. if (Date.now() - startTime > timeout) {
  458. reject(new Error(`未找到title为"${title}"的td元素`));
  459. return;
  460. }
  461. requestAnimationFrame(find);
  462. };
  463. find();
  464. });
  465. };
  466. // 4. 模拟点击事件
  467. const simulateClick = (element) => {
  468. const clickEvent = new MouseEvent('click', {
  469. bubbles: true,
  470. cancelable: true,
  471. view: window,
  472. detail: 1
  473. });
  474. element.dispatchEvent(clickEvent);
  475. };
  476. try {
  477. // 执行操作序列
  478. console.log('开始模拟用户操作...');
  479. // 1. 触发鼠标弹起
  480. console.log('触发鼠标弹起事件...');
  481. simulateMouseUp(clickElement);
  482. await new Promise(resolve => setTimeout(resolve, 100));
  483. // 2. 模拟键盘输入
  484. console.log('开始模拟键盘输入...');
  485. await simulateTyping(inputElement, inputText);
  486. await new Promise(resolve => setTimeout(resolve, 200));
  487. // 3. 查找并点击td元素
  488. console.log('查找目标td元素...');
  489. const tdElement = await findTdByTitle(tdTitle);
  490. console.log(tdElement);
  491. console.log('找到td元素,触发点击事件...');
  492. setTimeout(() => {
  493. tdElement.click()
  494. }, 1000)
  495. console.log('所有操作完成');
  496. return true;
  497. } catch (error) {
  498. console.error('操作过程中出现错误:', error);
  499. throw error;
  500. }
  501. };